【TIS-100 攻略】第 1~3 关:自检诊断、信号放大器、差分信号转换器

本文首发于 B 站《TIS-100》文集(https://www.bilibili.com/read/readlist/rl626023)。原创不易,转载请注明出处。
第 1 关《自检诊断》(Self-test Diagnostic)关卡展示

本关需要将 IN.X 中的数据流按顺序输出到 OUT.X 中,将 IN.A 中的数据流按顺序输出到 OUT.A 中。本关我们需要学习本游戏中的第一条指令:mov 指令。
mov 指令用法:
作用:将数据源(src)中的数据写入目标点(dst)。
数据源可以是一个 -999~999 间的立即数,也可以是节点内部的 acc 寄存器,还可以是四个方向上的邻居节点,分别用 up(上方节点)、down(下方节点)、left(左侧节点)和 right(右侧节点)表示。当数据源是 acc 寄存器或邻居节点时,表示需要从对应的位置读取数据。
目标点则只能是本节点的 acc 寄存器或四个方向上的相邻节点,不能是立即数。
每个节点中还有一个 bak 寄存器,但是在游戏设定里,bak 寄存器不能通过 mov 指令读取或写入值,而且接下来要用到的各种算术指令也不能直接读取 bak 中的值。关于 bak 寄存器的使用方法,以后的攻略里我们会说到。
举例说明:
左半部分的工作,游戏里已经帮我们写好了,三个节点的代码都是 mov up down:
左上角节点从它【上方】的 IN.X 接收数据,并将收到的数据传给【下方】的中央节点(mov up down);
中央节点从它【上方】的节点收到传来的数据,并将收到的数据传给【下方】的左下角节点(mov up down);
左下角节点从它【上方】的中央节点收到传来的数据,并将收到的数据传给【下方】的 OUT.X(mov up down)。这就完成了 IN.X 的传输工作。
右半部分稍微麻烦一点,因为右上角的节点从 IN.A 收到数据后,没法把这份数据直接往下传。它下方的节点已经损坏了,显示 communication failure(通讯失败),所以我们只能通过从旁边绕行的方法,将数据送往 OUT.A。代码及数据流向如下:

右上角的节点从它【上方】的 IN.A 接收数据,并传给【左边】的节点(mov up left);
左上角的节点从它【右边】的节点接收数据,并传给【下方】的节点(mov right down);
中央节点从它【上方】的节点接收数据,并传给【下方】的节点(mov up down);
左下角的节点从它【上方】的节点接收数据,并传给【右边】的节点(mov up right);
右下角的节点从它【左边】的节点接收数据,并传给【下方】的 OUT.A(mov left down)。
此时我们点击左下角的【RUN】按钮运行程序,稍等片刻,便会弹出结算界面:

结算界面会从【运行的周期数】(cycle count)、【用到的节点数】(node count)及【代码行数】(instruction count)三大维度来评判你的设计方案的优秀程度,并和全世界的玩家们竞争。显然,三项指标都是数值越低越强,但是在将来的关卡里,你很难设计出十全十美的方案:想要速度快就需要多节点并行操作,人多力量大;想要节省节点数量那就只能单一节点揽下所有的活,同一时间内只能做一项任务。
本关因为需求过于简单,以上方案就是唯一正确且十全十美的答案。可以看到直方图中,三项指标都只有这一条竖线。全世界的玩家都是这么做的。

第 2 关《信号放大器》(Signal Amplifier)关卡展示

本关要求我们将 IN.A 中的数字扩大 2 倍后送到 OUT.A 中。本关我们需要学习三条算术指令:
加法指令:add <src>,令 acc 加上数据源中的数字,并将计算结果重新写入 acc,覆盖曾经的值;
减法指令:sub <src>,令 acc 减去数据源中的数字,并将计算结果重新写入 acc,覆盖曾经的值;
取反指令:neg,令 acc 变为自己的相反数(即乘以 -1)。
加法指令和减法指令的数据源(src)和 mov 指令的数据源一样,可以是 -999~999 间的立即数,也可以是 acc 寄存器或四周的邻居节点。当算术指令里的操作数是 acc 自己时,加上/减去的数字是 acc 被覆盖前的数字。也就是说,add acc 会将 acc 里的数字乘以 2,而 sub acc 会将 acc 里的数字清零。
遗憾的是,TIS-100 并没有提供乘法和除法指令。你能简单实现的乘法只有乘以 -1(neg)、乘以 2(add acc)、乘以 4(两次 add acc)、乘以 8(三次 add acc,乘以其余 2 的幂同理)等少数几种。后续有专门实现任意数的乘法、除法的题,我们到时候再说怎么做复杂的乘法和除法。
举例说明:
看到这里,相信你已经知道这道题该怎么做了。从 IN.A 收一个数字,把它放到 acc 里,并使用 add acc 这样的指令将 acc 的数字乘以 2。计算完毕后,将存在 acc 里的计算结果往下发就好了。

左边的三个节点纯粹是用于传话的(mov up down, mov up down, mov up right),将 IN.A 中的数字传到右下角的节点里。
右下角的节点收到由左侧传来的数字后,将它放入 acc 中(mov left acc)并乘以 2(add acc),最后将计算好的结果传给下方的 OUT.A 即完成任务(mov acc down)。
点击左下角的【RUN】,稍等片刻,便会弹出结算界面:


第 3 关《差分信号转换器》(Differential Converter)关卡展示

本关要求从 IN.A 和 IN.B 各读入一个数字,将 IN.A - IN.B 的值写入 OUT.P,将 IN.B - IN.A 的值写入 OUT.N。
上一关我们用到了算数指令里的加法指令,本关则该用到减法和取反指令了。代码如下:

接收 IN.B 的节点直接把对应的值汇总到左边的节点(mov up left)。
左边的节点先从 IN.A 读一个值放入 acc(mov up acc),紧接着减去右边节点发来的 IN.B 的值(sub right)。此时 acc 里的值就是 IN.A - IN.B 的值了,将这个值往下传(mov acc down)。
中央节点无法直接输出这个值,所以收到这个值以后,直接往下面传话(mov up down)。
左下角的节点要输出的正是 IN.A - IN.B 的值,但是我们不能直接 mov up down 完事。因为我们还需要把取反后的 IN.B - IN.A 传给右边的节点,让它输出这个值。所以收到 IN.A - IN.B 后,我们要先复制一份到 acc 里(mov up acc)。复制好后,将这个值传给下方的 OUT.P(mov acc down)。传完以后,我们将 acc 取反,让它从 IN.A - IN.B 变成 IN.B - IN.A(neg),处理完成后,将该值发给右边的节点(mov acc right)。
右下角的节点在收到左边传来的 IN.B - IN.A 后,直接送到下方的 OUT.N 里(mov left down)。
点击左下角的【RUN】,稍等片刻,便会弹出结算界面:


解锁成就 RTFM
RTFM 即 Read The F**king Manual,阅读这**的手册。当你第一次进入游戏的时候,你就会收到一条阅读手册的提示。如果你错过了,那么现在你只要打开任意一个关卡,然后按下 ESC 键,在弹出的菜单中选择【View the TIS-100 Manual】(阅读 TIS-100 手册)即可在浏览器中打开 pdf 格式的手册,完成成就。


解锁成就 PARALLELIZE
该成就的说明是 Solve SIGNAL AMPLIFIER in fewer than 100 cycles,在 100 个时钟周期以内完成第二关《信号放大器》。我们的第一个方案里,最终的结算界面上说用了 160 个周期完成任务。那么怎么提速呢?答案是使用并行操作,令两个节点同时做两份不同的任务,并将结果统一汇报到最终输出的节点处。代码如下:

以上所有写了代码的节点,我们按照从左到右、从上到下的顺序,依次编号为 1~5 号节点。
1 号节点用于将 IN.A 中的数字发给 2 号和 3 号节点,其中第奇数个数字发给 2 号节点(mov up right),第偶数个数字发给 3 号节点(mov up down)。
2 号和 3 号节点要做的事情完全一样,都是从 1 号节点收到数字后(mov left/up acc),将对应的数字乘以 2(add acc)并汇总到 4 号节点处(mov acc down/right)。
4 号节点用于汇总 2 号和 3 号节点的计算结果。为了让输出的数字和输入的数字一一对应,4 号节点先传送 2 号节点发来的第奇数个结果(mov up down),再传送 3 号节点发来的第偶数个结果(mov left down)。
5 号节点不用说,纯粹用来接 4 号节点传来的话的,将 4 号节点发来的数无脑往 OUT.A 传就行了(mov up down)。
点击左下角的【RUN】,稍等片刻,便会弹出结算界面:

其实,本成就的成就名 Parallelize(并行)就已经在提示你这道题该怎么去做了——并行操作,多块芯片同时计算。

解锁成就 HALT AND CATCH FIRE
该成就要求使用隐藏指令让 TIS-100 宕机。同样从成就名上找线索:Halt and Catch Fire。现在随意打开一个关卡,在任意节点上写上 HCF 指令,并点击左下角的【RUN】运行,即可让 TIS-100 宕机,达成成就。
彩蛋:HCF 是一条真实存在的汇编指令,它的作用是让电脑进入无序状态,电脑从此进入混沌状态,随机执行任意的指令,除非强制断电,否则无法阻止。以下内容摘自 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]