# CPU 作業說明與心得 ###### tags: `assigment` `sophomore` `computer organization` ## 檔案架構 [ 先來看看助教到底給了我們多少材料和要做些什麼吧 ] ![](https://i.imgur.com/fFqcpXS.png) ### 需要編譯的檔案: * ``CPU.v``:你的code該寫在這 * ``SRAM.v``:負責讀 / 寫instruction memory 和data memory (對你沒看錯,這兩條datapath是共用同個檔案) * ``top.v``:規範如何連接各個module,這裡你可以看到SRAM被寫了兩次,一條是DM(data memory)和一條是IM(instruction memory) * ``top_tb.v``:測試檔,萬惡的龍貓,所有訊號都可以在這裡查看(左邊可以展開喔) (不要跟我一開始一樣把tb檔以外的拿進去看波型,會看不到任何訊號) ![](https://i.imgur.com/udScA3e.png) [ 如果想看暫存器的東西可以從這裡按添加波形 ] ![](https://i.imgur.com/w23GgrU.png =300x300) ### 指令測試檔案: * ``setup.s``:程式最開始要先執行的部分,會初始化所有register和memory區段 * ``main.s``:程式真正開始測試指令的部分 :::warning 這兩個檔案並不會真的被「執行」到,它是把裡面的內容編成16進制的``.hex``,翻譯指令成我們寫的cpu可以解讀的樣子,主要是給你看裡面到底有什麼指令(但註解沒很清楚有夠難懂),真正被執行的是``.hex`` ::: :::warning 這裡衍伸出一個問題,也就是說如果你```setup.s```裡面用到的指令(``jal``, ``begu``, ``addi``, ``sw``, ``auipc``)有寫錯,你一輩子都不能開始進到``main.s``測其他指令,等等後面會談到 ::: * ``main0.hex``, ``main1.hex``, ``main2.hex``, ``main3.hex``: 這四個檔就是``setup.s``和``main.s``翻譯過來的指令 (他把``setup.s``包在裡面一起編,我就不懂為什麼這麼重要的事情沒講,我當初找了很久找不到``setup.hex``在哪) * ``golden.hex``: 這個是答案,會比較這裡面和DM的資料是否一樣 ### 其他有用檔案: * ``main.log``:裡面的資料就是他跑測資的記錄檔,也是提供你比較好閱讀而已 ![](https://i.imgur.com/P2hCpVX.png =300x300) [ 子揚說他是一個指令一個指令對喇 ] ![](https://i.imgur.com/wspvKLV.png =300x300) [ 幫大家分解一下 ] <PC Address>: <16進的instr_out> <verilog的指令> ```verilog= 0: 00000093 li ra,0 4: 00000113 li sp,0 ... ``` **[ ``li`` 就是 ``addi ra 0($zero)``喔!複習個!]** 這裡可以得出一個結論, 從``setup.s``到``main.s``裡用的指令都是用我們實作的, 就算看起來沒有,也會用替代的方式實現 如果嫌log很難看,``main.s``裡面也可以看指令, 只是缺少address但好處是後面有算好的數字結果可以參考 ![](https://i.imgur.com/JPpdRZ1.png =300x300) ## 作要要求 [ 講了那麼多所以這次到底要寫什麼呢? ] ![](https://i.imgur.com/tIjTUZZ.png) :::success !就是紅色以外的部分! ::: - 紅色的instruction memory(IM)和data memory(DM)都靠助教給的SRAM處理,只要給出對的控制訊號就行了,等等會介紹控制訊號 - 所以要自己寫的包含: * Progarm counter(``PC``) * register file(管理32個register) * 還有各種計算元件(verilog會幫你處理,只要寫對運算式就好) ## CPU的I/O訊號 ```verilog= module CPU( input clk, //cpu clock input rst, //reset signal input [31:0] data_out, //data_read = 1時會給你來自DM的資料 input [31:0] instr_out, //當instr_read = 1時會給你來自IM的資料 output reg instr_read, //1bit控制訊號,決定要不要讀instruction進來 output reg data_read, //1bit控制訊號,決定要不要讀data進來 output reg [31:0] instr_addr, //instrunction的address,用來決定拿哪一個指令 output reg [31:0] data_addr, //DM的address,用來決定讀或寫DM上哪一個位置 output reg [3:0] data_write, //4bit控制訊號,決定每8bit要不要寫入DM(處理SW,SH,SB) output reg [31:0] data_in //要寫入DM的資料 ); ``` > 註:請依自己的需求在output上加上reg,上面的code已經加了跟助教給的不太一樣喔 :::warning - 因為SRAM裡面只靠``clk``訊號正源觸發,沒有偵測其他訊號 (我當初還天真地以為他們會在always裡檢查``data_read``和``instruction_read``) - 所以在``data_read``和``instruction_read`` = 1時**並不會馬上改變``data_out``和``instruction_out``的值** - 要等到下一次``clk``變1時才會拿到,這也是為什麼說拿**指令要delay 1個 cycle,而load系列還要再delay 1個 cycle** ::: ## 初始化(結果都是XXXXXXXX有高機率是這個階段出問題) - **記得先檢查你有沒有一直拿到新的instruction** -> 也就是``instr_out``要一直變動 - **再來是前面提到初始化會用到幾個必備的指令** * ``jal`` * ``bgeu`` * ``addi`` * ``sw`` 還有一些我分析不出來的(la) 這幾個如果有錯初始化失敗,結果就會是XXXXXXXX 而且會因為離不開這個block而進不到main裡 ```assembly= /* Fills memory blocks */ /* 這裡是main.s的內容 */ fill_block: bgeu a0, a1, fb_end sw a2, 0(a0) addi a0, a0, 4 j fill_block fb_end: ret ``` - **那如何檢查自己初始成功了沒?** * setup裡有初始化各個register成00000000(不用自己寫) 如果你連register都沒有全部變0 可能是``addi``寫錯 或是你的register架構有錯 (我一開始企圖分檔寫register結果接線跟用和什麼訊號觸發的會有很大的挑戰,後來放棄了) :::warning 記得檢查 register[0] 一定要是 0,初始化才會對哦 ::: * 我是在執行指令時加入``$dispaly("")``去顯示現在做了什麼指令 去看有沒有進到main 如果都是上面那4個指令 就代表你還卡在初始化裡 (助教你跳個錯誤訊息"setup failed"很難484) ## 開始測試 如果你的結果不是XXXXXXXX 那可以進到測試指令的部份了 這邊就是按照給的指令表去寫 但有時候有些imm串的方式很奇怪的要特別小心 [ 例如 ] ![](https://i.imgur.com/mtDz95I.png) 這裡imm串法長的很詭異,而且會後一個沒寫的隱藏bit是0喔 ``` imm =[20 bits signed extention, instruction[31], instruction[7], instruction[30:25], instruction[11:8], 1'b0] <- 這個被隱藏起來ㄌ ``` :::warning 每種 imm 的長度都是 32 位元,文件上沒說明的部分要自己補齊並做好 sign extension ::: > 另外提示有幾個實用的東西可以自己查如何使用:``$signed()``, ``$unsigned()``, ``>>>`` ## 易寫錯或有爭議的指令 * ``JAL``: 早上跟助教說過了,0x0000006f = $0000000000000000000000001101111_2$ 這串指令會去改寫``x0``(Zero register) 我們認為是bug ,但助教說不是,所以要想辦法解決 * `JALR` : 在作業說明是這麼說的: > rd = PC + 4 PC = imm + rs1 (Set LSB of PC to 0) 但是在實作上不能寫成: ``` regi[rd] = pc + 4; pc = imm + regi[rs1]; ``` 因為在測試的過程中可能會遇到下列的指令 ``` jalr t0, t0, 0 ``` 在上述的情況,若使用前者的寫法會導致最後 pc 的結果為 `pc = pc + 4` 但是 `t0` 原本不是 pc + 4 而是這個 cycle 結束才會被改為 pc + 4 的數值,所以 pc 數值也不應該被改為 (pc + 4)(t0 的數值) + (0)(imm 的數值) ,而是 pc = t0(未賦值前 t0 的數值) + imm * ``SW``、``SB``、``SH``、``LW``、``LB`` ...: 1. ``SW``:就不多解釋了。 2. ``SB``、``SH``:要特別注意的是你必須要依照`data_addr[1:0]`作為寫入的依據。 [自己看](https://blog.csdn.net/leishangwen/article/details/40485425?ops_request_misc=&request_id=&biz_id=102&utm_term=sw%20sb%20sh%20verilog&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-3-40485425&fbclid=IwAR1rmdr3W7rwf6hhbmLEJXYA9YUWPE7790geeokdwfe_AK8fakfkW4TCDl0) :::warning 因為在使用 `sh`, `sb` 時,不一定是寫在 data_addr 是 4 的倍數上,但是 memory 在讀取資料的時候會以 4 為單位讀取或寫入。 就像是 data memory 讀寫的指針永遠都只在 4 的倍數上,但是我們實際使用時可能會需要寫的 `data_addr` 是 13, 17, 5 等等不是 4 的倍數的位置上,這時候就需要透過 `data_write` 來指定正確的寫入位置。 因為 data memory 的指針指在 4 的倍數的 addr 上,所以實際上我們要決定的是要寫入的 byte (或是 half-word) 應該要放在這 4 byte 中的哪幾個 byte 上,如果是 `sb` 指令且需要寫在 LSB (Least significant byte) 上,就指定 `data_write` 為 0001; 如果寫在 MSB 上對應的 `data_write` 就是 1000,依此類推。 </br> - ex: 如果 data_addr 為 `0x000ffab3`,因為最後兩個 bit 是 11 所以使用 `sb` 的時候對應的 data_write 為 `1000` ::: 3. ``LB``、``LH``一樣ㄏ ## 結果 花了一點時間去把「結果」和「``main.s``」裡面區塊的位置對應(就是所有SW的位置) 可以快速對應哪個指令可能有問題 結果如下: | DM | value | instr| | -------- | -------- | -------- | | DM[8192] | fffffff0 | addi | | DM[8193] | fffffff8 | sub | | DM[8194] | 00000008 | sll | | DM[8195] | 00000001 | slti | | DM[8196] | 00000001 | sltu | | DM[8197] | 78787878 | xor | | DM[8198] | 000091a2 | srl | | DM[8199] | 00000003 | sra | | DM[8200] | fefcfefd | or | | DM[8201] | 10305070 | and | | DM[8202] | cccccccc | lw | | DM[8203] | ffffffcc | lb | | DM[8204] | ffffcccc | lh | | DM[8205] | 000000cc | lbu | | DM[8206] | 0000cccc | lhu | | DM[8207] | 00000d9d | addi | | DM[8208] | 00000004 | slti | | DM[8209] | 00000003 | sltiu| | DM[8210] | 000001a6 | xori | | DM[8211] | 00000ec6 | ori | | DM[8212] | 2468b7a8 | andi | | DM[8213] | 5dbf9f00 | slli | | DM[8214] | 00012b38 | srli | | DM[8215] | fa2817b7 | srai | | DM[8216] | ff000000 | jalr | | DM[8217] | 12345678 | sw | | DM[8218] | 0000f000 | sw | | DM[8219] | 00000f00 | sw | | DM[8220] | 000000f0 | sw | | DM[8221] | 0000000f | sw | | DM[8222] | 56780000 | <font color="#f00">**sh(困難)**</font> | | DM[8223] | 78000000 | <font color="#f00">**sb(困難)**</font> | | DM[8224] | 00005678 | sh | | DM[8225] | 00000078 | sb | | DM[8226] | 12345678 | sw | | DM[8227] | ce780000 | sh && sb| | DM[8228] | fffff000 | beq | | DM[8229] | fffff000 | bne | | DM[8230] | fffff000 | blt | | DM[8231] | fffff000 | bge | | DM[8232] | fffff000 | bltu | | DM[8233] | fffff000 | bgeu | | DM[8234] | 1357a064 | aupic| | DM[8235] | 13578000 | lui | | DM[8236] | fffff004 | jal | - 補充說明那兩個困難的指令,code如下: ```assembly= addi s0, s0, 20 lw t5, -40(s0) # t5 = t4 lw t4, -16(s0) sw t5, -4(s0) sb t5, -8(s0) sh t5, -12(s0) sb t5, -13(s0) <- 問題 sh t5, -18(s0) <- 問題 ``` :::warning 這邊會看到:明明都是資料的offest應該皆為4的倍數,上面卻出現了13和18! ::: :::success **[ 解釋 ]** - 助教設計的SRAM裡會去用4bit對齊,會忽略最後2個bit (這個根本挖坑給我們跳) - 也就是說寫入``13(0000_1101)``和``18(0001_0010)``位置的時候,其實是在寫``12(0000_1100)``和``16(0001_0000)`` 若沒有考慮直接把值寫入,就會出現問題(寫進錯誤的值) - 那就要靠``data_write``去偏移到正確的位置了(但寫入的資料也要調整) 所以``data_write``訊號是可以出現0010,0100,0110等組合喔 ::: ## 結語 這次作業真的不容易 作業說明又很模糊 (因為這份作業是每年一直傳下來進化的,所以有時候助教根本不知道之前是怎麼做的) 常常坐在電腦前4個小時但0進度 (我跟子揚都寫到看日出了QQ) 希望這些心得有助大家在期中前趕快把這份作業生出來 有時間救救學分 打個預防針 裡面可能有些小錯 多半都是我們作者的心得和推論 不是100%正確 但也有寫信騷擾助教後得到的答案 所以要麻煩大家一起debug 有寫錯的地方請多多包涵並私訊我 我會跟你討論討論再更正 也歡迎想加入協作提供建議或心得讓大家寫作業順利的人來找我 我會把權限開給你 祝大家寫作業順利! _(:3 」∠ )_ :::info 特別感謝其他協作者:111 沈子揚、111 張祐誠、111 向景亘 特別感謝心靈導師:110 王聖中 作者:111 黃宇衡 :::