【TIS-100 攻略】第 1~3 關(guān):自檢診斷、信號放大器、差分信號轉(zhuǎn)換器

本文首發(fā)于 B 站《TIS-100》文集(https://www.bilibili.com/read/readlist/rl626023)。原創(chuàng)不易,轉(zhuǎn)載請注明出處。
第 1 關(guān)《自檢診斷》(Self-test Diagnostic)關(guān)卡展示

本關(guān)需要將 IN.X?中的數(shù)據(jù)流按順序輸出到 OUT.X 中,將 IN.A 中的數(shù)據(jù)流按順序輸出到 OUT.A 中。本關(guān)我們需要學(xué)習(xí)本游戲中的第一條指令:mov?指令。
mov 指令用法:
作用:將數(shù)據(jù)源(src)中的數(shù)據(jù)寫入目標(biāo)點(diǎn)(dst)。
數(shù)據(jù)源可以是一個(gè) -999~999 間的立即數(shù),也可以是節(jié)點(diǎn)內(nèi)部的 acc 寄存器,還可以是四個(gè)方向上的鄰居節(jié)點(diǎn),分別用 up(上方節(jié)點(diǎn))、down(下方節(jié)點(diǎn))、left(左側(cè)節(jié)點(diǎn))和 right(右側(cè)節(jié)點(diǎn))表示。當(dāng)數(shù)據(jù)源是 acc 寄存器或鄰居節(jié)點(diǎn)時(shí),表示需要從對應(yīng)的位置讀取數(shù)據(jù)。
目標(biāo)點(diǎn)則只能是本節(jié)點(diǎn)的 acc 寄存器或四個(gè)方向上的相鄰節(jié)點(diǎn),不能是立即數(shù)。
每個(gè)節(jié)點(diǎn)中還有一個(gè) bak 寄存器,但是在游戲設(shè)定里,bak 寄存器不能通過 mov 指令讀取或?qū)懭胫?,而且接下來要用到的各種算術(shù)指令也不能直接讀取 bak 中的值。關(guān)于 bak 寄存器的使用方法,以后的攻略里我們會說到。
舉例說明:
左半部分的工作,游戲里已經(jīng)幫我們寫好了,三個(gè)節(jié)點(diǎn)的代碼都是 mov up down:
左上角節(jié)點(diǎn)從它【上方】的 IN.X 接收數(shù)據(jù),并將收到的數(shù)據(jù)傳給【下方】的中央節(jié)點(diǎn)(mov up down);
中央節(jié)點(diǎn)從它【上方】的節(jié)點(diǎn)收到傳來的數(shù)據(jù),并將收到的數(shù)據(jù)傳給【下方】的左下角節(jié)點(diǎn)(mov up down);
左下角節(jié)點(diǎn)從它【上方】的中央節(jié)點(diǎn)收到傳來的數(shù)據(jù),并將收到的數(shù)據(jù)傳給【下方】的 OUT.X(mov up down)。這就完成了 IN.X 的傳輸工作。
右半部分稍微麻煩一點(diǎn),因?yàn)橛疑辖堑墓?jié)點(diǎn)從 IN.A 收到數(shù)據(jù)后,沒法把這份數(shù)據(jù)直接往下傳。它下方的節(jié)點(diǎn)已經(jīng)損壞了,顯示 communication failure(通訊失?。?,所以我們只能通過從旁邊繞行的方法,將數(shù)據(jù)送往 OUT.A。代碼及數(shù)據(jù)流向如下:

右上角的節(jié)點(diǎn)從它【上方】的 IN.A 接收數(shù)據(jù),并傳給【左邊】的節(jié)點(diǎn)(mov up left);
左上角的節(jié)點(diǎn)從它【右邊】的節(jié)點(diǎn)接收數(shù)據(jù),并傳給【下方】的節(jié)點(diǎn)(mov right down);
中央節(jié)點(diǎn)從它【上方】的節(jié)點(diǎn)接收數(shù)據(jù),并傳給【下方】的節(jié)點(diǎn)(mov up down);
左下角的節(jié)點(diǎn)從它【上方】的節(jié)點(diǎn)接收數(shù)據(jù),并傳給【右邊】的節(jié)點(diǎn)(mov up right);
右下角的節(jié)點(diǎn)從它【左邊】的節(jié)點(diǎn)接收數(shù)據(jù),并傳給【下方】的 OUT.A(mov left down)。
此時(shí)我們點(diǎn)擊左下角的【RUN】按鈕運(yùn)行程序,稍等片刻,便會彈出結(jié)算界面:

結(jié)算界面會從【運(yùn)行的周期數(shù)】(cycle count)、【用到的節(jié)點(diǎn)數(shù)】(node count)及【代碼行數(shù)】(instruction count)三大維度來評判你的設(shè)計(jì)方案的優(yōu)秀程度,并和全世界的玩家們競爭。顯然,三項(xiàng)指標(biāo)都是數(shù)值越低越強(qiáng),但是在將來的關(guān)卡里,你很難設(shè)計(jì)出十全十美的方案:想要速度快就需要多節(jié)點(diǎn)并行操作,人多力量大;想要節(jié)省節(jié)點(diǎn)數(shù)量那就只能單一節(jié)點(diǎn)攬下所有的活,同一時(shí)間內(nèi)只能做一項(xiàng)任務(wù)。
本關(guān)因?yàn)樾枨筮^于簡單,以上方案就是唯一正確且十全十美的答案。可以看到直方圖中,三項(xiàng)指標(biāo)都只有這一條豎線。全世界的玩家都是這么做的。

第 2 關(guān)《信號放大器》(Signal Amplifier)關(guān)卡展示

本關(guān)要求我們將 IN.A 中的數(shù)字?jǐn)U大 2 倍后送到 OUT.A 中。本關(guān)我們需要學(xué)習(xí)三條算術(shù)指令:
加法指令:add <src>,令 acc 加上數(shù)據(jù)源中的數(shù)字,并將計(jì)算結(jié)果重新寫入 acc,覆蓋曾經(jīng)的值;
減法指令:sub <src>,令 acc 減去數(shù)據(jù)源中的數(shù)字,并將計(jì)算結(jié)果重新寫入 acc,覆蓋曾經(jīng)的值;
取反指令:neg,令 acc 變?yōu)樽约旱南喾磾?shù)(即乘以 -1)。
加法指令和減法指令的數(shù)據(jù)源(src)和 mov 指令的數(shù)據(jù)源一樣,可以是 -999~999 間的立即數(shù),也可以是 acc 寄存器或四周的鄰居節(jié)點(diǎn)。當(dāng)算術(shù)指令里的操作數(shù)是 acc 自己時(shí),加上/減去的數(shù)字是 acc 被覆蓋前的數(shù)字。也就是說,add acc 會將 acc 里的數(shù)字乘以 2,而 sub acc 會將 acc 里的數(shù)字清零。
遺憾的是,TIS-100 并沒有提供乘法和除法指令。你能簡單實(shí)現(xiàn)的乘法只有乘以 -1(neg)、乘以 2(add acc)、乘以 4(兩次 add?acc)、乘以 8(三次 add acc,乘以其余 2 的冪同理)等少數(shù)幾種。后續(xù)有專門實(shí)現(xiàn)任意數(shù)的乘法、除法的題,我們到時(shí)候再說怎么做復(fù)雜的乘法和除法。
舉例說明:
看到這里,相信你已經(jīng)知道這道題該怎么做了。從 IN.A 收一個(gè)數(shù)字,把它放到 acc 里,并使用 add acc 這樣的指令將 acc 的數(shù)字乘以 2。計(jì)算完畢后,將存在 acc 里的計(jì)算結(jié)果往下發(fā)就好了。

左邊的三個(gè)節(jié)點(diǎn)純粹是用于傳話的(mov up down, mov up down, mov up right),將 IN.A 中的數(shù)字傳到右下角的節(jié)點(diǎn)里。
右下角的節(jié)點(diǎn)收到由左側(cè)傳來的數(shù)字后,將它放入 acc 中(mov left acc)并乘以 2(add acc),最后將計(jì)算好的結(jié)果傳給下方的 OUT.A 即完成任務(wù)(mov acc down)。
點(diǎn)擊左下角的【RUN】,稍等片刻,便會彈出結(jié)算界面:


第 3 關(guān)《差分信號轉(zhuǎn)換器》(Differential Converter)關(guān)卡展示

本關(guān)要求從 IN.A 和 IN.B 各讀入一個(gè)數(shù)字,將 IN.A - IN.B 的值寫入 OUT.P,將 IN.B - IN.A 的值寫入 OUT.N。
上一關(guān)我們用到了算數(shù)指令里的加法指令,本關(guān)則該用到減法和取反指令了。代碼如下:

接收 IN.B 的節(jié)點(diǎn)直接把對應(yīng)的值匯總到左邊的節(jié)點(diǎn)(mov up left)。
左邊的節(jié)點(diǎn)先從 IN.A 讀一個(gè)值放入 acc(mov up acc),緊接著減去右邊節(jié)點(diǎn)發(fā)來的 IN.B 的值(sub right)。此時(shí) acc 里的值就是 IN.A - IN.B 的值了,將這個(gè)值往下傳(mov acc down)。
中央節(jié)點(diǎn)無法直接輸出這個(gè)值,所以收到這個(gè)值以后,直接往下面?zhèn)髟挘╩ov up down)。
左下角的節(jié)點(diǎn)要輸出的正是 IN.A - IN.B 的值,但是我們不能直接 mov up down 完事。因?yàn)槲覀冞€需要把取反后的 IN.B - IN.A 傳給右邊的節(jié)點(diǎn),讓它輸出這個(gè)值。所以收到 IN.A - IN.B 后,我們要先復(fù)制一份到?acc 里(mov up acc)。復(fù)制好后,將這個(gè)值傳給下方的 OUT.P(mov acc down)。傳完以后,我們將 acc 取反,讓它從 IN.A - IN.B 變成 IN.B - IN.A(neg),處理完成后,將該值發(fā)給右邊的節(jié)點(diǎn)(mov acc right)。
右下角的節(jié)點(diǎn)在收到左邊傳來的 IN.B - IN.A 后,直接送到下方的 OUT.N 里(mov left down)。
點(diǎn)擊左下角的【RUN】,稍等片刻,便會彈出結(jié)算界面:


解鎖成就 RTFM
RTFM 即 Read The F**king Manual,閱讀這**的手冊。當(dāng)你第一次進(jìn)入游戲的時(shí)候,你就會收到一條閱讀手冊的提示。如果你錯(cuò)過了,那么現(xiàn)在你只要打開任意一個(gè)關(guān)卡,然后按下 ESC 鍵,在彈出的菜單中選擇【View the TIS-100 Manual】(閱讀 TIS-100 手冊)即可在瀏覽器中打開 pdf 格式的手冊,完成成就。


解鎖成就 PARALLELIZE
該成就的說明是 Solve SIGNAL AMPLIFIER in fewer than 100 cycles,在 100 個(gè)時(shí)鐘周期以內(nèi)完成第二關(guān)《信號放大器》。我們的第一個(gè)方案里,最終的結(jié)算界面上說用了 160 個(gè)周期完成任務(wù)。那么怎么提速呢?答案是使用并行操作,令兩個(gè)節(jié)點(diǎn)同時(shí)做兩份不同的任務(wù),并將結(jié)果統(tǒng)一匯報(bào)到最終輸出的節(jié)點(diǎn)處。代碼如下:

以上所有寫了代碼的節(jié)點(diǎn),我們按照從左到右、從上到下的順序,依次編號為 1~5 號節(jié)點(diǎn)。
1 號節(jié)點(diǎn)用于將 IN.A 中的數(shù)字發(fā)給 2 號和 3 號節(jié)點(diǎn),其中第奇數(shù)個(gè)數(shù)字發(fā)給 2 號節(jié)點(diǎn)(mov up right),第偶數(shù)個(gè)數(shù)字發(fā)給 3 號節(jié)點(diǎn)(mov up down)。
2 號和 3 號節(jié)點(diǎn)要做的事情完全一樣,都是從 1 號節(jié)點(diǎn)收到數(shù)字后(mov left/up acc),將對應(yīng)的數(shù)字乘以 2(add acc)并匯總到 4 號節(jié)點(diǎn)處(mov acc down/right)。
4 號節(jié)點(diǎn)用于匯總 2 號和 3 號節(jié)點(diǎn)的計(jì)算結(jié)果。為了讓輸出的數(shù)字和輸入的數(shù)字一一對應(yīng),4 號節(jié)點(diǎn)先傳送?2 號節(jié)點(diǎn)發(fā)來的第奇數(shù)個(gè)結(jié)果(mov up down),再傳送 3 號節(jié)點(diǎn)發(fā)來的第偶數(shù)個(gè)結(jié)果(mov left down)。
5 號節(jié)點(diǎn)不用說,純粹用來接 4 號節(jié)點(diǎn)傳來的話的,將 4 號節(jié)點(diǎn)發(fā)來的數(shù)無腦往 OUT.A 傳就行了(mov up down)。
點(diǎn)擊左下角的【RUN】,稍等片刻,便會彈出結(jié)算界面:

其實(shí),本成就的成就名 Parallelize(并行)就已經(jīng)在提示你這道題該怎么去做了——并行操作,多塊芯片同時(shí)計(jì)算。

解鎖成就 HALT AND CATCH FIRE
該成就要求使用隱藏指令讓 TIS-100 宕機(jī)。同樣從成就名上找線索:Halt and Catch Fire?,F(xiàn)在隨意打開一個(gè)關(guān)卡,在任意節(jié)點(diǎn)上寫上 HCF 指令,并點(diǎn)擊左下角的【RUN】運(yùn)行,即可讓 TIS-100 宕機(jī),達(dá)成成就。
彩蛋:HCF 是一條真實(shí)存在的匯編指令,它的作用是讓電腦進(jìn)入無序狀態(tài),電腦從此進(jìn)入混沌狀態(tài),隨機(jī)執(zhí)行任意的指令,除非強(qiáng)制斷電,否則無法阻止。以下內(nèi)容摘自 Wikipedia 的 Halt and Catch Fire 詞條:
In computer engineering,?Halt and Catch Fire, known by the?assembly mnemonic?HCF, is an?idiom?referring to a computer?machine code?instruction?that causes the computer's?central processing unit?(CPU) to cease meaningful operation, typically requiring a restart of the computer. It originally referred to a fictitious instruction in?IBM System/360?computers (introduced in 1964), making a joke about its numerous non-obvious instruction mnemonics.
With the advent of the?MC6800?(introduced in 1974), a design flaw was discovered by programmers. Due to incomplete opcode decoding, two?illegal opcodes, 0x9D and 0xDD, will cause the?program counter?on the processor to increment endlessly, which locks the processor until reset. Those codes have been unofficially named HCF. During the design process of the MC6802, engineers originally planned to remove this instruction, but kept it as-is for testing purposes. As a result, HCF was officially recognized as a real instruction.[1][2]?Later, HCF became a humorous catch-all term for instructions that may freeze a processor, including intentional instructions for testing purposes, and unintentional illegal instructions. Some are considered hardware defects, and if the?system is shared, a malicious user can execute it to launch a?denial-of-service attack.
In the case of real instructions, the implication of this expression is that, whereas in most cases in which a CPU executes an unintended instruction (a bug in the code) the computer may still be able to recover, in the case of an HCF instruction there is, by definition, no way for the system to recover without a restart.
The expression?catch fire?is a facetious exaggeration of the speed with which the CPU chip would be switching some bus circuits, causing them to overheat and burn.[3]