# CPU 作業說明與心得
###### tags: `assigment` `sophomore` `computer organization`
## 檔案架構
[ 先來看看助教到底給了我們多少材料和要做些什麼吧 ]

### 需要編譯的檔案:
* ``CPU.v``:你的code該寫在這
* ``SRAM.v``:負責讀 / 寫instruction memory 和data memory
(對你沒看錯,這兩條datapath是共用同個檔案)
* ``top.v``:規範如何連接各個module,這裡你可以看到SRAM被寫了兩次,一條是DM(data memory)和一條是IM(instruction memory)
* ``top_tb.v``:測試檔,萬惡的龍貓,所有訊號都可以在這裡查看(左邊可以展開喔)
(不要跟我一開始一樣把tb檔以外的拿進去看波型,會看不到任何訊號)

[ 如果想看暫存器的東西可以從這裡按添加波形 ]

### 指令測試檔案:
* ``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``:裡面的資料就是他跑測資的記錄檔,也是提供你比較好閱讀而已

[ 子揚說他是一個指令一個指令對喇 ]

[ 幫大家分解一下 ]
<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但好處是後面有算好的數字結果可以參考

## 作要要求
[ 講了那麼多所以這次到底要寫什麼呢? ]

:::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串的方式很奇怪的要特別小心
[ 例如 ]

這裡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 黃宇衡
:::