###### tags: `VHDL` `NCKU`
# CPU作業心得補充
- 建立於[此篇](https://hackmd.io/@jamesrush/SywjLdQsU)的概念上我仍然遇到的問題 [name=112張娟鳴]
## 檔案架構
- `main.s` + `Makefile`: 可以更改指令,改完後`make`他即可(助教很貼心的給`makefile`了。
- `top_tb.v`: 跑完測試拉波形的時候,自己宣告的register file會在**Memory**那邊,沒有跟**sim**放在一起;通常拉i_CPU, register file以及i_DM即可(假設你很順利能讀指令)
- 當初沒注意到`main.s`裡面已經有答案了,我是使用`main.log`debug的,可以兩個對照啊這樣就不用自己算值了
- 四個.v檔,main0~main3以及golden_hex一定要跟工作專案是同個資料夾,最簡單的方法是直接把資料夾的全部複製過去,最後單獨把CPU.v和自己增加的module複製回來即可
## CPU的I/O訊號
### 在還是XXXXX的時候
- 前文也說了,是因為指令沒寫好,但不可能改那麼快,所以基本上是先將下面註解掉
``` Verilog=
initial begin
#(`MAX*`CYCLE)
$display("Simulation Failed");
$finish;
end
```
他的意思是,當你時間超過他們規定的時間時,就要算失敗,但是助教給的時間非常充裕,所以問題是出在
- instr_out沒有東西,沒有指令DECODE
- 某幾個指令寫錯,讓他沒辦法跳到結束那一段程式
>在log檔裡,有一個叫做`main_exit`的block,你要跳到那邊
以我來說,我沒辦法跳到是因為我的simm和register file沒有做signed,做了以後就跳到了
如果註解掉後就可以看到DM裡的結果,那問題是第二種;如果還是不行,就是第一種,或是如原文說的初始化不夠正確
### 開始測試
補個讓人霧煞煞的LW和SW
- **當你要lw**
- 先將data_read改成1
- data_addr給入需要的register_file位置
> 注意:給值時候是給類似`x[7]+4`,只要data_addr顯示了這個的值(假設x[7]=3092,那data_addr總共就是3096)就是對的
- data_out出現值,這時要將這個值分到rd
> data_out的值不等於data_addr(data_addr=3096,但data_out會是另外的值,這就要看原本在那裡存了什麼)
- 總之,做的都是地址,地址!(我debug的時候忘記這件事情,自己陷入死迴圈)
- **當你要sw**
- 將data_addr放入你要存的地址
- 依據需要寫的地方更改data_write,1是可以寫入,0是不可以,所以大部分時間都要是0
> 進階的部份:data_addr <= x[7] + 13和data_addr <= x[7] +16在DM的addr顯示的是同個東西,但對我們來說,我們要寫入的位置會變得不同,以sb為例
> __ \__ __ __ | __ __ __ __
> **16** 15 14 13 | 16 15 14 **13**
> 差別大概是這種感覺,要用imm的末兩位來判斷,可以直接參考裡面的文章寫,如果出來結果還是不對就調換順序(快速debug方式,概念則是這樣)
- 依據要寫的地方來放置data_in,才能存入
>基本寫法和data_write差不多,只要注意在data_write是1的地方data_in要對就好,至於其他部份即使是前一時刻的值也沒什麼關係
:::info
**lb**: load byte (8 bit)
**lh**: load half word (16 bit),這兩個在你分配`x[rd]`要做好位置的分配與補0
:::
- **關於imm**
- 我是依照指令分了不同種類的imm,而且有的imm有reg signed有的沒有,有的甚至也不是32bit(32 bit和12bit的register是可以相加的)
- 會特別需要注意signed extend的是I-type和S-type的,在加地址的時候,會有要加負號的情況,但如果他不是signed的,像-4就會變成+4092
> 解決方法:
> 1.如果是32bit:設成reg signed(或是用$signed個別補充)並在讀取imm的時候要自己sign extend他,讓他依據你讀入的最高位補
> 2. 12 bit register: 直接設成reg signed(附帶一題:register file設成signed會比較好,不用自己轉),不用補東西
> 他做加法時會自己幫你補剩下20位,好耶!
> 
- 缺點是要設定很多register,優點是很直觀的直接把看到的數字分配塞進去
### 易寫錯或有爭議的指令
#### `JAL`
:::danger
那個是**故意**的,**你絕對不可以改x[0]**
:::
- 比較建議的做法每個步驟最後面都加`x[0] <= 0`
- 我是分FSM,並且一個變數一個always block,這樣我的變數x[]在不知道要做什麼,甚至是每個state後面,我都可以塞`x[0] <= 0`(模擬電腦不讓給x[0]的情況)
#### `JALR`
:::info
jalr本身是因為要跳到某個地方,必須先把現在位置存到暫存器,然後才能跳過去;存的時候要存的是現在位置+4,因為現在這裡已經做過了,我是要接下一步
:::
問題指令:`jalr $t0,$t0,0` 原意
```verilog=
x[rd] = pc + 4;
pc = imm + x[rs1];
```
出問題時的流程:
```verilog=
//pc=912,imm=400,$t0=200
$t0 = pc + 4; //$t0 = 916
pc = imm + $t0; //pc = 1316
```
實際上我們想要的:
(1)
```verilog=
//pc=912,imm=400,$t0=200
pc = imm + $t0; //pc = 600
$t0 = pc + 4; //$t0 = 916
```
這樣也能解決問題
(2)
```verilog=
//pc=912,imm=400,$t0=200
$t0 <= pc + 4; //$t0 = 916
pc <= imm + $t0; //pc = 600
//前提,兩個在同個clk一起做,pc不可以比較晚
```
(3)
```verilog=
//pc=912,imm=400,$t0=200
reg [32:0]temp;
temp = $t0;
$t0 = pc + 4; //$t0 = 916
pc = imm + temp; //pc = 600
```
也就是,會不會遇到要看程式架構:
- unblocking(<=):如果rd和PC在同個state處理(或是PC比較早處理),因為他取的都是上個clk的值,所以沒有遇到這個問題,但這樣會有幾個複雜的指令(其他地方)要提前處理
- blocking(=):有同學是另外開個變數去存rd的東西

(我就想放圖不要阻撓我)
## 結果
errr,去年是44個指令,但今年(112級)是54筆測資,今年多了mul系列,不知道明年會不會又多,郭
所以DM的參考指令不一定準,前面幾個差不多,大概在sw後面開始會不一樣,不一樣的話:
- 看一下錯在哪個DM
- 看那個DM輸出什麼值,然後你$t0(x[5])什麼時候是這個值
- 差不多就在那邊(因為他每次都存在$t0,所以去那邊看就好)
或是可以數已經使用了幾個sw,但指令很多會數到亂掉Q
## 心得
我本身的程式架構
```Verilog=
--------------------------
助教給的input,output(有的output可以自己加reg,這個是從紅綠燈就知道的事情ㄛ)
--------------------------
--------------------------
自己的register
我用到了:
reg [31:0] x [31:0]//register file
reg [11:0]iimm,simm...//只需要12 bit的imm
reg [31:0]jimm...//需要31 bit imm(因為中間一坨0)
reg tmp, flag...//輔助用,依照自己需求加減
---------------------------
---------------------------
parameter implement
我做了兩組parameter宣告,一組是單純FSM,一組是opcode的type
optype其實可有可無,有的話比較好閱讀,因為你可以記你是Rtype或是什麼,
分case處理時很容易閱讀,至少比binary好閱讀
缺點就浪費行數跟空間r
---------------------------
---------------------------
always@(posedge clk)begin
if(rst)begin
大多變數為0的初始化
end
else begin
依據不同state不同狀態改變
end
end
有n個,總之就是一個always block只放一個變數,有幾個我就寫幾個
一開始會先處理parameter變數變化//例如 FSM 和 opcode type
再來處理各種會連到外面的output//data_in,data_addr
各種指令會用到的變數的讀取//rs1,rs2,funct3,imm
x[]和PC等計算//加減乘除會在x[]|beq,bne會在PC
---------------------------
```
- 優點:
很好debug(只要會用modelsim,到後期6小時把所有bug de完,errr雖然前面花很久),看波形圖哪塊出問題,直接去那塊變數看,不用整個架構從頭找到尾還找不到,現在很多IDE都有良好的編輯功能,可以把block縮起來,真的是良心
確保變數不會被亂改,因為分成一個個變數寫,很容易知道自己漏寫什麼,比較不會有layout
- 缺點:
行數很高,檔案特別大,會覺得自己是不是笨蛋,怎麼寫那麼多還沒寫完,而且要一直寫重複的語句可能會很煩啦
我看過的程式架構
- 把ALU和decoder獨立出來,R-type另外處理
- 優點:主程式行數少,也很好懂
- 缺點:自己要會接module,因為我不太會接所以對我來說很難,我很抱歉

- 全部擠在一起
- 優點:行數很少,檔案很小,狀態沒有那麼細,速度應該會比較快
- 缺點:如果當初沒有寫註解的話自然很難debug,因為參考到了這種類型的,我又快看不懂(我就爛),還好有朋友給我其他份有註解的
如果不知道如何下手,或許可以
- 先處令指令跟register file, PC的相加減關係
- 先接線
本來就是分這兩部份啦,只是會忘記
然後modelsim有個小技巧是在波形圖左下角有時間,時間可以自己輸入和複製,這樣就不用一直重複拉波形圖了,可以直接跳到那邊,好耶!
## 可能用得上的debug法
1. 先確定自己的instr_out是否有讀入東西,且是否是從PC讀的
2. 接著使用log檔比對指令是否正確,可以看一下他PC位置是否正確(main.log大約在300行左右可以看到一開始他幫你執行什麼指令,main.s裡面沒辦法知道)
modelsim內PC可以使用16進位顯示,和log檔相對應
3. 在開始測試add,sub指令是否正確前,會有一長串得初始化以及stack的寫入,基本上其實你大部分程式時間都會在這邊
4. 一開始顯示XXXXX的時候不要慌,如果指令有正常讀入,修正到後面答案會跑出來;也可能出現一度顯示答案後來又顯示不了的情況
5. stack的寫入其實是他會比較你的x[10]和x[11],然後x[10]一定要比x[11]大,如果沒有比較大的話就要+4,然後再比一次,重複此動作直到x[10]>=x[11]
在這裡,+4以後會跳回比較指令的第一行,PC顯示會是01000114之類的,前面的數字不用管,只要看後面是不是正確即可
6. 第五個動作會做兩次,做完後才進入測試add那些指令
這時,PC位置是**ee>>ec>>118**,不可以跑到f0,他會結束你的程式
7. 進到這邊以後大部分PC不會有太大問題(頂多是jalr),等到所有指令順利跑到底後才會進入f0>>f4>>f8>>fc>>100
8. 如果順利是這樣的話答案應該要能夠顯示了(儘管不一定對),再不能顯示應該會是SW問題
9. 剩下的就是一般指令問題了
這樣de可能會比較踏實,當然,普通的一行一行對也可以,因為通常是小錯誤,修改完後可以多對很多,後面不會差太多