欢迎光临散文网 会员登陆 & 注册

【TIS-100 攻略】TIS-NET 第 6 关:信号预分频器

2022-11-04 10:26 作者:ココアお姉ちゃん  | 我要投稿

本文首发于 B 站《TIS-100》文集(https://www.bilibili.com/read/readlist/rl626023)。原创不易,转载请注明出处。

TIS-NET 第 6 关《信号预分频器》(Signal Prescaler)关卡展示

本关要求将 IN 中的值依次除以 2、4、8,并将结果依次送往 OUT.2、OUT.4 和 OUT.8 端口。

注意到 IN 里的数都是 8 的倍数,所以我们可以先计算 8 分频的值,将得到的结果乘以 2 就得到了 4 分频的值,再乘以 2 就得到了 2 分频的值。

另外,我在第 19 关《除法器》的攻略(传送门:【TIS-100 攻略】第 19 关:除法器)里提过:除法可以使用【线性查找】和【二分查找】两种方式来做。本关也一样,我会用线性查找和二分查找这两种方法各做一遍。

线性查找法(最省节点和代码行数的做法)

核心思想是将 IN 里的值不断减去 8,每减去一个 8 就令商加上 1,直到原值减到 0 为止,此时的商就是 8 分频的值。之后将这个值乘以 2 得到 4 分频的值,再乘以 2 得到 2 分频的值。代码如下:

左侧的三个节点纯传话(mov up down, mov up down, mov up left)。然后看 OUT.2 上方的节点:

  1. 将 in 中的值放到 acc 里(mov left acc),

  2. 每次循环令 bak 加上 1(swp)

  3. (add 1)

  4. (swp)

  5. 以及令 acc 减去 8(sub 8)。

  6. acc 尚未减到 0 时,跳回第 2 行继续循环(jnz 2),

  7. 直到 acc 到达 0 为止,bak 中的值就是 8 分频的值,将它发给右边的节点(swp)

  8. (mov acc right)

  9. 将 8 分频的值乘以 2 后(add acc)

  10. 得到 4 分频的值,同样发给右边的节点(mov acc right)。

  11. 将 4 分频的值乘以 2 后(add acc)

  12. 得到 2 分频的值,发给自己下方的 OUT.2 端口(mov acc down)。

OUT.4 上方的节点会从左边依次收到 8 分频和 4 分频的值,将前者传给右边(mov left right),后者发到自己下方的 OUT.4 端口(mov left down)。

OUT.8 上方的节点会从左边收到 8 分频的值,将它发到自己下方的 OUT.8 端口(mov left down)。

点击左下角的【RUN】,稍等片刻,便会弹出结算界面:

二分查找法(运行速度最快的做法)

由于 IN 里的值必为 8 的倍数,且最多为 3 位数,所以 IN 里的最大有效值为 8×124=992,最大的 8 分频值为 124,位于 0~127 范围内。因此,使用二分查找,每次将商的取值范围缩小一半,最多经过 7 次计算即可得到答案。步骤如下:

  1. 从 IN 里读入一个数,设它为 a。检查 a 是否大于等于 512。若是,则令 a 减去 512,同时令商加上 64(64×8=512,所以此时的 a 至少能商 64);若否,则什么也不做。

  2. 检查 a 是否大于等于 256。若是,则令 a 减去 256,同时令商加上 32(32×8=256,所以此时的 a 至少能商 32);若否,则什么也不做。

  3. 检查 a 是否大于等于 128。若是,则令 a 减去 128,同时令商加上 16(16×8=128,所以此时的 a 至少能商 16);若否,则什么也不做。

  4. 检查 a 是否大于等于 64。若是,则令 a 减去 64,同时令商加上 8(8×8=64,所以此时的 a 至少能商 8);若否,则什么也不做。

  5. 检查 a 是否大于等于 32。若是,则令 a 减去 32,同时令商加上 4(4×8=32,所以此时的 a 至少能商 4);若否,则什么也不做。

  6. 检查 a 是否大于等于 16。若是,则令 a 减去 16,同时令商加上 2(2×8=16,所以此时的 a 至少能商 2);若否,则什么也不做。

  7. 检查 a 是 0 还是 8。若是 8,则令商加上 1,否则商不变。此时的商就是 8 分频的值。

本关的代码如下:

由于用到的节点数量过多,我在图上将用到的节点都编了号,并且用箭头标识出了数据流向。这次的数据是按蛇形流动的。

1~6 号节点用于负责 7 次二分查找。6 个节点要做 7 次运算,根据抽屉原理,至少有一个节点要做其中的 2 次运算。这里我选择了让 1 号节点做前两步运算:

  1. 从 IN 里读入一个数,视它为 a。检查 a 是否大于等于 512,即 a-512(mov -512 acc)

  2. (add up)是否大于等于 0。

  3. 大于等于 0 时按顺序执行,小于 0 时跳到第 6 行执行(jlz 6)。

  4. a-512 大于等于 0 时,令商加上 64,将增量 64 传给 2 号节点(mov 64 down)

  5. (jmp 8)

  6. 否则将增量 0 传给 2 号节点(mov 0 down)

  7. 并将 a-512 还原成 a(add 512)。

  8. 检查 a 是否大于等于 256,即 a-256(sub 256)是否大于等于 0。

  9. 大于等于 0 时按顺序执行,小于 0 时跳到第 12 行执行(jlz c)。

  10. a-256 大于等于 0 时,令商加上 32,将增量 32 传给 2 号节点(mov 32 down)

  11. (jmp e)

  12. 否则将增量 0 传给 2 号节点(mov 0 down)

  13. 并将 a-256 还原成 a(add 256)。

  14. 操作完毕后,将处理后的 a 发给 2 号节点(mov acc down)。

2 号节点执行的是第 3 步运算:

  1. 1 号节点会将前两次商的增量 q、dq 发来,我们计算出两者之和(mov up acc)

  2. (add up)

  3. 发给 3 号节点(mov acc right)

  4. 然后 1 号节点会发来最新的 a 值,我们检查 a 是否大于 128,即 a-128(mov up acc)

  5. (sub 128)是否大于等于 0。

  6. 大于等于 0 时按顺序执行,小于 0 时跳到第 9 行执行(jlz 9)。

  7. a-128 大于等于 0 时,令商加上 16,将增量 16 传给 3 号节点(mov 16 right)

  8. (jmp b)

  9. 否则将增量 0 传给 3 号节点(mov 0 right),

  10. 并将 a-128 还原成 a(add 128)。

  11. 操作完毕后,将处理后的 a 发给 3 号节点(mov acc right)。

3 号节点执行的是第 4 步运算:

  1. 2 号节点会发来一个老的商值 q 和一个增量值 dq,我们计算出两者之和,作为新的商(mov left acc)

  2. (add left)

  3. 发给 4 号节点(mov acc right)。

  4. 然后 2 号节点会发来最新的 a 值,我们检查 a 是否大于 64,即 a-64(mov left acc)

  5. (sub 64)是否大于等于 0。

  6. 大于等于 0 时按顺序执行,小于 0 时跳到第 9 行执行(jlz 9)。

  7. a-64 大于等于 0 时,令商加上 8,将增量 8 传给 4 号节点(mov 8 right)

  8. (jmp b)

  9. 否则将增量 0 传给 4 号节点(mov 0 right),

  10. 并将 a-64 还原成 a(add 64)。

  11. 操作完毕后,将处理后的 a 发给 4 号节点(mov acc right)。

4 号节点执行的是第 5 步运算:

  1. 3 号节点会发来一个老的商值 q 和一个增量值 dq,我们计算出两者之和,作为新的商(mov left acc)

  2. (add left)

  3. 发给 5 号节点(mov acc right)。

  4. 然后 3 号节点会发来最新的 a 值,我们检查 a 是否大于 32,即 a-32(mov left acc)

  5. (sub 32)是否大于等于 0。

  6. 大于等于 0 时按顺序执行,小于 0 时跳到第 9 行执行(jlz 9)。

  7. a-32 大于等于 0 时,令商加上 4,将增量 4 传给 5 号节点(mov 4 right)

  8. (jmp b)

  9. 否则将增量 0 传给 5 号节点(mov 0 right),

  10. 并将 a-32 还原成 a(add 32)。

  11. 操作完毕后,将处理后的 a 发给 5 号节点(mov acc right)。

5 号节点执行的是第 6 步运算:

  1. 4 号节点会发来一个老的商值 q 和一个增量值 dq,我们计算出两者之和,作为新的商(mov left acc)

  2. (add left)

  3. 发给 6 号节点(mov acc down)。

  4. 然后 4 号节点会发来最新的 a 值,我们检查 a 是否大于 16,即 a-16(mov left acc)

  5. (sub 16)是否大于等于 0。

  6. 大于等于 0 时按顺序执行,小于 0 时跳到第 9 行执行(jlz 9)。

  7. a-16 大于等于 0 时,令商加上 2,将增量 2 传给 6 号节点(mov 2 down)

  8. (jmp b)

  9. 否则将增量 0 传给 6 号节点(mov 0 down),

  10. 并将 a-16 还原成 a(add 16)。

  11. 操作完毕后,将处理后的 a 发给 6 号节点(mov acc down)。

6 号节点执行的是第 7 步运算。注意这个节点是收尾节点,所以逻辑和前辈们不完全一样

  1. 5 号节点会发来一个老的商值 q 和一个增量值 dq,我们计算出两者之和,作为新的商(mov up acc)

  2. (add up)

  3. 放入 bak 中暂存(sav)。

  4. 然后 5 号节点会发来最新的 a 值(mov up acc),我们检查 a 是 0 还是 8

  5. a 是 0 时按顺序执行,a 是 8 时跳到第 8 行执行(jnz 8)。

  6. a 是 0 时,商自身就是 8 分频的值,只需拿出 bak(swp)

  7. 准备输出即可(jmp a);

  8. a 是 8 时,需要在原商的基础上加 1(swp)

  9. 才能得到正确的 8 分频值(add 1)。

  10. 我们将 8 分频值复制一份给 7 号节点后(mov acc left),

  11. 再输出到 OUT.8 口(mov acc down)。

7 号节点在得到 6 号节点发来的 8 分频值后,将它乘以 2 得到 4 分频的值(mov right acc, add acc),然后将这个 4 分频的值复制一份给 8 号节点后,再输出到 OUT.4 口(mov acc left, mov acc down)。

8 号节点在得到 7 号节点发来的 4 分频值后,将它乘以 2 得到 2 分频的值(mov right acc, add acc)并输出到 OUT.2 口(mov acc down)。

点击左下角的【RUN】,稍等片刻,便会弹出结算界面:

第 19 关除法器的攻略里我就提到了二分除法,但是效率仅仅提升了 3 倍。那是因为被除数只有两位数,即使用线性查找慢慢找也不会过于费时。但是本关的被除数几乎都是三位数,线性查找和二分查找的效率差异一下就被放大了:线性查找的话,商是多大,就需要进行多少次运算,本题里消耗了 13454 周期;而二分查找的话,只要商在 0~127 范围内,不管商是多少,都是雷打不动的 7 次运算,本题里只消耗了 660 周期,两者的效率相差了两个数量级

【TIS-100 攻略】TIS-NET 第 6 关:信号预分频器的评论 (共 条)

分享到微博请遵守国家法律