【深圳 IO 攻略】第 15 关:卡宾枪瞄准照明器

本文首发于 B 站《深圳 IO》文集(https://www.bilibili.com/read/readlist/rl569860)。原创不易,转载请注明出处。
关卡展示

这一关我们需要在【雷达输出】信号出现时开始计时,【雷达输入】信号出现时停止计时,然后根据计时时长给【激光】、【泛光 20】和【泛光 60】三个输出口输出对应的映射值。映射关系需要参考数据手册:

首先我们肯定是计算【雷达输入】和【雷达输出】信号的差值。若为负数,说明【雷达输出】信号激活,开始计时。若为正数,说明【雷达输入】信号激活,停止计时,同时根据计时值向右边的三个输出口写入映射值。平时没有【雷达输出】和【雷达输入】信号时,正常计时。



左边的芯片用来计时。首先不断监测输入和输出的差值(tcp p0 p1),当输出信号出现时(差值为负数)清除 acc(-mov 0 acc);当输入信号出现时(差值为正数)将经过的秒数传给右边的芯片(+ mov acc x1),由右边的芯片控制激光和泛光。做完这些事后睡一秒。当然,如果没有检测到差分信号,那就什么都不用做,直接睡觉(slp 1)。睡完一秒后,令经过的秒数 +1(add 1)。
然后我们看右边的芯片。首先等待左边的芯片传入信号(slx x0),然后因为秒数在后面需要读两次,所以需要放入 acc 寄存器暂存。(mov x0 acc)。后续的判断又是一个典型的三态判断。这里我们把六种状态压缩成了三种,先默认向激光和泛光端口写中间状态的值,然后用 tcp 指令判断实际状态值是否位于两端。如果位于两端,则在同一秒内马上改写。
点击左下角的【模拟】,稍等片刻,便会弹出结算界面:

优化三项指标
我们发现右边的那块芯片一共有 10 行代码,相比于 MC4000 的最大容量只多了一行代码。如果我们能想办法压缩掉一行代码,那么就可以替换成 MC4000,节省两块钱成本了。
突破口在于右边那块芯片的两句 tcp 指令上,我们现在要想办法压缩成一句,把【伪三态】变成【真正的三态】。但是在这个案例里,中间态有两种啊,3 秒和 4 秒都是中间态。如果我们能把 1~6 秒这六种状态改写成 1、2、3 三种状态,然后把状态值发到右边上去,就能实现真正的三态。
我们需要想办法找到一个公式,建立如下表所示的时间→状态值映射:

聪明的你,很快就发现了这样一条规律:

状态值等于时间值 +1 后除以 2 并向下取整的值!
然而我之前就说过,MC 系列芯片里只有加、减、乘三则运算,没有除法运算。不过,MC 系列芯片倒是提供了一些和十进制位运算相关的指令,用它们可以实现一些特定的除法及取余运算。
取位指令:dgt I/R/P,取得 acc 寄存器的个/十/百位数,保留正负号,并覆盖原先的值。操作数为 0 时取个位,操作数为 1 时取十位,操作数为 2 时取百位,操作数为其他数时则将 acc 归零。
置位指令:dst I1/R1/P1 I2/R2/P2,将 acc 寄存器中的某一位置为特定的值。位数由第一个操作数决定,0~2 分别表示个位/十位/百位,若在此范围外,则不执行任何操作。具体设置的值由第二个操作数决定,会只看最低位,忽略最高位,同时会将数字的符号设置为和该数一致。
下表详细说明了当 acc 为 123 时,执行不同的 dgt 和 dst 指令后的结果:

问题来了,这些位运算指令和除以 2 并向下取整有什么关系?其实,除以 2 相当于乘以 5 再除以 10 向下取整。与此同时,当原数小于 100 时,【取十位】就相当于【除以 10 向下取整】。进而推理可得:当原数小于 20 时,【乘以 5 再取十位】就相当于【除以 2 向下取整】!而我们的时间间隔只有 1~6 秒这几种状态,远小于 20 这个上限值。现在我们把映射公式改写成这样:

然后你又想到了:既然最后的状态值不是跟 time 而是跟 time + 1 呈等比关系,那我为何还非要从 0 开始计时而不从 1 开始计时呢?这样就把额外的 +1 操作去掉了。
再进一步:既然最终计算状态值时我总是要把时间 ×5,那我为什么还非得 1 秒 1 秒计时,每个时钟周期计 5 秒不香吗?计时也改为从 5 开始计!这样最后直接 dgt 1 就得到状态码了,什么 add 1 啊,mul 5 啊都是冗余操作!
于是你洋洋洒洒地写下了这样的代码:




学了一个 dgt 指令后,你惊奇地发现,何止成本降下来了,明明是三项指标全面下降了好不好!
进一步优化电量和代码行数
经过了上面的优化后,你发现,两块芯片的总代码行数只有 14 行了。这个行数正好是一个 MC6000 的最大代码行数。于是你很自然地想到:能不能把两块芯片合并成一块呢,这样还能省去传输 acc 的开销。答案是可以的!
这道题一共有五个 p 口输入/输出,所以如果将所有的代码写在一块芯片里的话,那至少需要两块 DX-300 来帮忙。现在,我们将左侧的【雷达输出】也通过 DX-300 转接一下。最终的电路图和代码如下图所示,其实就是上一个方案的两块芯片合并后的结果,逻辑上大同小异,我不再详细解读,请读者自行解读。

