【深圳 IO 攻略】第 15 關(guān):卡賓槍瞄準(zhǔn)照明器

本文首發(fā)于 B 站《深圳 IO》文集(https://www.bilibili.com/read/readlist/rl569860)。原創(chuàng)不易,轉(zhuǎn)載請(qǐng)注明出處。
關(guān)卡展示

這一關(guān)我們需要在【雷達(dá)輸出】信號(hào)出現(xiàn)時(shí)開始計(jì)時(shí),【雷達(dá)輸入】信號(hào)出現(xiàn)時(shí)停止計(jì)時(shí),然后根據(jù)計(jì)時(shí)時(shí)長(zhǎng)給【激光】、【泛光 20】和【泛光 60】三個(gè)輸出口輸出對(duì)應(yīng)的映射值。映射關(guān)系需要參考數(shù)據(jù)手冊(cè):

首先我們肯定是計(jì)算【雷達(dá)輸入】和【雷達(dá)輸出】信號(hào)的差值。若為負(fù)數(shù),說(shuō)明【雷達(dá)輸出】信號(hào)激活,開始計(jì)時(shí)。若為正數(shù),說(shuō)明【雷達(dá)輸入】信號(hào)激活,停止計(jì)時(shí),同時(shí)根據(jù)計(jì)時(shí)值向右邊的三個(gè)輸出口寫入映射值。平時(shí)沒有【雷達(dá)輸出】和【雷達(dá)輸入】信號(hào)時(shí),正常計(jì)時(shí)。



左邊的芯片用來(lái)計(jì)時(shí)。首先不斷監(jiān)測(cè)輸入和輸出的差值(tcp p0 p1),當(dāng)輸出信號(hào)出現(xiàn)時(shí)(差值為負(fù)數(shù))清除 acc(-mov 0 acc);當(dāng)輸入信號(hào)出現(xiàn)時(shí)(差值為正數(shù))將經(jīng)過(guò)的秒數(shù)傳給右邊的芯片(+ mov acc x1),由右邊的芯片控制激光和泛光。做完這些事后睡一秒。當(dāng)然,如果沒有檢測(cè)到差分信號(hào),那就什么都不用做,直接睡覺(slp 1)。睡完一秒后,令經(jīng)過(guò)的秒數(shù)?+1(add 1)。
然后我們看右邊的芯片。首先等待左邊的芯片傳入信號(hào)(slx x0),然后因?yàn)槊霐?shù)在后面需要讀兩次,所以需要放入 acc 寄存器暫存。(mov x0 acc)。后續(xù)的判斷又是一個(gè)典型的三態(tài)判斷。這里我們把六種狀態(tài)壓縮成了三種,先默認(rèn)向激光和泛光端口寫中間狀態(tài)的值,然后用 tcp 指令判斷實(shí)際狀態(tài)值是否位于兩端。如果位于兩端,則在同一秒內(nèi)馬上改寫。
點(diǎn)擊左下角的【模擬】,稍等片刻,便會(huì)彈出結(jié)算界面:

優(yōu)化三項(xiàng)指標(biāo)
我們發(fā)現(xiàn)右邊的那塊芯片一共有 10 行代碼,相比于 MC4000 的最大容量只多了一行代碼。如果我們能想辦法壓縮掉一行代碼,那么就可以替換成 MC4000,節(jié)省兩塊錢成本了。
突破口在于右邊那塊芯片的兩句 tcp 指令上,我們現(xiàn)在要想辦法壓縮成一句,把【偽三態(tài)】變成【真正的三態(tài)】。但是在這個(gè)案例里,中間態(tài)有兩種啊,3 秒和 4 秒都是中間態(tài)。如果我們能把 1~6 秒這六種狀態(tài)改寫成 1、2、3 三種狀態(tài),然后把狀態(tài)值發(fā)到右邊上去,就能實(shí)現(xiàn)真正的三態(tài)。
我們需要想辦法找到一個(gè)公式,建立如下表所示的時(shí)間→狀態(tài)值映射:

聰明的你,很快就發(fā)現(xiàn)了這樣一條規(guī)律:

狀態(tài)值等于時(shí)間值 +1 后除以 2 并向下取整的值!
然而我之前就說(shuō)過(guò),MC 系列芯片里只有加、減、乘三則運(yùn)算,沒有除法運(yùn)算。不過(guò),MC 系列芯片倒是提供了一些和十進(jìn)制位運(yùn)算相關(guān)的指令,用它們可以實(shí)現(xiàn)一些特定的除法及取余運(yùn)算。
取位指令:dgt I/R/P,取得 acc 寄存器的個(gè)/十/百位數(shù),保留正負(fù)號(hào),并覆蓋原先的值。操作數(shù)為 0 時(shí)取個(gè)位,操作數(shù)為 1 時(shí)取十位,操作數(shù)為 2 時(shí)取百位,操作數(shù)為其他數(shù)時(shí)則將 acc 歸零。
置位指令:dst I1/R1/P1 I2/R2/P2,將 acc 寄存器中的某一位置為特定的值。位數(shù)由第一個(gè)操作數(shù)決定,0~2 分別表示個(gè)位/十位/百位,若在此范圍外,則不執(zhí)行任何操作。具體設(shè)置的值由第二個(gè)操作數(shù)決定,會(huì)只看最低位,忽略最高位,同時(shí)會(huì)將數(shù)字的符號(hào)設(shè)置為和該數(shù)一致。
下表詳細(xì)說(shuō)明了當(dāng) acc 為 123 時(shí),執(zhí)行不同的 dgt 和 dst 指令后的結(jié)果:

問題來(lái)了,這些位運(yùn)算指令和除以 2 并向下取整有什么關(guān)系?其實(shí),除以 2 相當(dāng)于乘以 5 再除以 10 向下取整。與此同時(shí),當(dāng)原數(shù)小于 100 時(shí),【取十位】就相當(dāng)于【除以 10 向下取整】。進(jìn)而推理可得:當(dāng)原數(shù)小于 20 時(shí),【乘以 5 再取十位】就相當(dāng)于【除以 2 向下取整】!而我們的時(shí)間間隔只有 1~6 秒這幾種狀態(tài),遠(yuǎn)小于 20 這個(gè)上限值?,F(xiàn)在我們把映射公式改寫成這樣:

然后你又想到了:既然最后的狀態(tài)值不是跟 time 而是跟 time + 1 呈等比關(guān)系,那我為何還非要從 0 開始計(jì)時(shí)而不從 1 開始計(jì)時(shí)呢?這樣就把額外的 +1 操作去掉了。
再進(jìn)一步:既然最終計(jì)算狀態(tài)值時(shí)我總是要把時(shí)間 ×5,那我為什么還非得 1 秒 1 秒計(jì)時(shí),每個(gè)時(shí)鐘周期計(jì) 5 秒不香嗎?計(jì)時(shí)也改為從 5 開始計(jì)!這樣最后直接 dgt 1 就得到狀態(tài)碼了,什么 add 1 啊,mul 5 啊都是冗余操作!
于是你洋洋灑灑地寫下了這樣的代碼:




學(xué)了一個(gè) dgt 指令后,你驚奇地發(fā)現(xiàn),何止成本降下來(lái)了,明明是三項(xiàng)指標(biāo)全面下降了好不好!
進(jìn)一步優(yōu)化電量和代碼行數(shù)
經(jīng)過(guò)了上面的優(yōu)化后,你發(fā)現(xiàn),兩塊芯片的總代碼行數(shù)只有 14 行了。這個(gè)行數(shù)正好是一個(gè) MC6000 的最大代碼行數(shù)。于是你很自然地想到:能不能把兩塊芯片合并成一塊呢,這樣還能省去傳輸 acc 的開銷。答案是可以的!
這道題一共有五個(gè) p 口輸入/輸出,所以如果將所有的代碼寫在一塊芯片里的話,那至少需要兩塊 DX-300 來(lái)幫忙?,F(xiàn)在,我們將左側(cè)的【雷達(dá)輸出】也通過(guò)?DX-300 轉(zhuǎn)接一下。最終的電路圖和代碼如下圖所示,其實(shí)就是上一個(gè)方案的兩塊芯片合并后的結(jié)果,邏輯上大同小異,我不再詳細(xì)解讀,請(qǐng)讀者自行解讀。

