---
tags: RISCV, 伴學松 第一組筆記
---
# 從0到有製作自己的CPU!! 第一周第一組課後KIM教學 20220707
[TOC]
# 講解 CPU 結構
> [name=Kim Weng]
- 所謂的computer,其學名叫做計算機,不外乎計算加減乘除法或是一些邏輯、移位的運算
- 早期要做機器運算做加減乘除就是要拿這個ALU做處理,而將資料放進左邊,再由中間的ALU做運算後再從右邊輸出結果但基於較沒效率的關係,有一個人叫做約翰·馮紐曼發明一個馮·紐曼模型(Von Neumann model)或普林斯頓架構(Princeton architecture),他將右邊的ab輸入額外做出一個儲存資料的記憶體,這樣一來右邊不需要一直有個輸入裝置去馬不停蹄地送資料,而這個記憶體具有儲存資料的電腦,學名叫做儲存程式電腦, 意思是指將儲存裝置與中央處理器分開的概念。
- 然而,由於馮紐曼架構將指令與數據放在同一記憶體的關係,會導致CPU在讀取instruction和存取data會互相競爭,造成的CPU利用率(吞吐率)受到限制,也就是所謂的馮紐曼瓶頸。為了改善此一缺點,因此instruction和data分開存取的哈佛架構(Harvard architecture)就相繼被提出
- 原本純馮紐曼架構下的CPU可以讀取指令或讀/寫記憶體資料,它們都不能使指令和資料同時使用同一個的匯流排系統。而使用哈佛架構的電腦中CPU,即使沒有快取的情況下也可以在讀取指令的同時進行資料存取。由於指令和資料存取不使用同一個記憶體通道,因此哈佛結構的電腦可以在相同的電路複雜度下有更好的表現。而目前即將要實現的RV32I即屬於哈佛架構。
![](https://i.imgur.com/NeeB5Mg.png)
- 由於RV32I採用哈佛架構,所以基本上包括從instruction memory提取指令、ALU執行、把計算的結果存入data memory,這樣一來就包括三個stage了。由於RISC-V屬於精簡指令集,即會把一個比較複雜的操作拆成兩步驟來完成,即第一個指令先用ALU計算結果,下一指令再把結果存入data memory,而第一個指令計算完的結果需要先放入某一處「暫存」,下一指令再從這個暫存的地方把結果store進data memory,因此我們還需要實現register,即所謂的暫存器。而CPU提取指令後,在進入ALU執行之前,需要知道目前提取出來的指令需要做什麼事情,所以需要有一個Decode模塊來負責解碼。讀取暫存器和寫入暫存器各暫一個stage,而解碼器解碼則與讀取暫存器在同一stage完成,因此暫存器和解碼器的操作又佔了兩個stage,再加上原先的三個stage,就構成了5 stage的CPU架構,如下圖所示:
![](https://i.imgur.com/2yW1q3o.png)
# 講解 ALU 與 verilog 程式結構
> [name=bill503084699]
1. 這是CPU裏頭某一個很像多工器形狀的一個在計算機中,算術邏輯單元(ALU)是專門執行算術和邏輯運算的數字電路。 ALU是計算機中央處理器的最重要組成部分,甚至連最小的微處理器也包含ALU作計數功能。 在現代CPU和GPU處理器中已含有功能強大和複雜的ALU; 一個單一的元件也可能含有ALU。
![](https://i.imgur.com/Ge7u6aE.png)
2. 介紹以ALU的加減法架構所呈現出來的圖
![](https://i.imgur.com/L1jjDeh.png)
![](https://i.imgur.com/xAvIo7F.png)
3. 依照慣例我們先把模組名稱定義出來, 接下來就是小括號" 埠列信號 "的部分, 將輸入跟輸出以input 跟output 給列出來
```verilog=
module alu(input[31:0] a,
input[31:0] b,
input[31:0] op,
output reg[31:0] y );
endmodule
```
![]( https://i.imgur.com/t0dPVi1.png)
4. 設定一個always 來指定一個特定事件觸發的基本型 (組合邏輯語法)
- always語句有兩種觸發的方式
- 第一種是"電平觸發",
- 例如always @(a or b or c),a、b、c均為變數,當其中一個發生變化時,下方的語句將被執行。
- 第二種是"沿觸發",沿觸發就是"相當於posedge之類的語言約束的信號" ,如果沒有如posedge之類的約束的話就是電平變化觸發
- 例如always @(posedge clk or negedge rstn),即當時鍾處在上升沿或下降沿時,語句被執行 。
- 而對於"always@(*),意思是以上兩種觸發方式都包含在內,任意一種發生變化都會觸發該語句,就是因為敏感清單過多的情況下太麻煩
- 補充第*種 : verilog 2001標準說可以使用替換敏感清單,表示"缺省,"編譯器會根據always塊內部的內容自動識別敏感變數。
| 設定一個always 來指定一個特定事件觸發的基本型 (組合邏輯語法) | always語句有兩種觸發的方式 |
| ---- |:----:|
| - 第一種是"電平觸發"|例如always @(a or b or c),a、b、c均為變數,當其中一個發生變化時,下方的語句將被執行 | | | | | || ---- |:----:|:----:|:---------:|:----:|:-------:| -------- |
| 第二種是"沿觸發",沿觸發就是"相當於posedge之類的語言約束的信號" ,如果沒有如posedge之類的約束的話就是電平變化觸發 |例如always @(posedge clk or negedge rstn),即當時鍾處在上升沿或下降沿時,語句被執行| | ---- |:----:|
| - 而對於"always@(*),意思是以上兩種觸發方式都包含在內,任意一種發生變化都會觸發該語句,就是因為敏感清單過多的情況下太麻煩
|補充第*種 : verilog 2001標準說可以使用替換敏感清單,表示"缺省,"編譯器會根據always塊內部的內容自動識別敏感變數。| | | | | || ---- |:----:|:----:|:---------:|:----:|:-------:| -------- |
```verilog=
always@(a or b or c) begin
```
```verilog=
always@(posedge clk or negedge rstn) begin
```
```verilog=
always@(*) begin
```
------------
```verilog=
module alu(input[31:0] a,
input[31:0] b,
input[31:0] op,
output reg[31:0] y );
always@(*)// 這邊我是用@(*)
endmodule
```
* ![](https://i.imgur.com/fehi4Hu.png)
5. 再以多路分支的結構下去描寫電路"多則一" 多工器?的情況但最後結果會把它變成更簡潔的一種表示法
- 像是if...else...多則一的描述語法
- 像是c語言的 switch...case
- 以case做關鍵字 以endcase做收尾
- 剩餘未達條件的項目用default 一網打盡
- 行為描述區塊的行數如果是多行的表達方式需要用begin end的方式下去把它包覆起來
```verilog=
always@(?) begin
case(訊號(變數))
條件值1: 行為描述區塊 // 多行
條件值2: 行為描述區塊
條件值3: 行為描述區塊
...
default : 剩餘條件行為描述區塊
endcase
end
```
```verilog=
module alu(input[31:0] a,
input[31:0] b,
input[31:0] op,
output reg[31:0] y );
always@(*) begin
case(op)// 括號裏頭代表訊號,而在case裏頭的值是表達訊號的輸出值可以去決定
// case到endcase之間列的條件(條件值x:行為描述區塊)
endcase
end
endmodule
```
6. 而在題目上我們要表達 reg[31:0]表達的是一個32值得比特向量(vector) 而我們又該怎麼表達行為區塊的表現式
- 條件值1: 行為描述區塊
- 條件值 x 最前面的數字為這個串的長度
- 例如: 001 為 3'001
0001為 4'0001
- x'一個英文數字 代表示是哪種位數的進位表現式
- b說明這是二進位串
- o說明這是八進位串
- d說明這是十進位串
- h說明這是十六進位串
- 例如:1'h0 , 3'b001
```verilog=
module alu(input[31:0] a,
input[31:0] b,
input[31:0] op,
output reg[31:0] y );
always@(*)begin
case(op)
3'h0: y = a+b; //當op=3'h0時,out為a+b
3'h1: y = a-b; //當op=3'h1時,out為a-b
3'h2: y = a|b; //當op=3'h2時,out為a|b
3'h3: y = a&b; //當op=3'h3時,out為a&b
3'h4: y = a^b; //當op=3'h4時,out為a^b
default : y = 0;
endcase
end
endmodule
```
7. 行為描述條件若剩剩餘未達條件的項目則用default 一網打盡,至於default要不要寫,則取決於case條件是否完備啦
如果你的case條件已經完備,那default不寫也無所謂,一定要寫的話,隨便賦值都可以
如果case條件不完備,default肯定要寫一個確定的值。
```verilog=
3'h3: y = a&b;
3'h4: y = a^b;
default : y = 0; //行為描述條件若剩剩餘未達條件的項目則用 default 一網打盡
```
8. 最終呈現 ALU
![](https://i.imgur.com/WK0UJ90.png)
## 參考資料
* [FPGA系統設計實務_蕭宇宏_Verilog 硬體描述語言介紹](https://youtube.com/playlist?list=PLI6pJZaOCtF3_-vE7VUn9RdhQ6KXpcFQD)
* [Verilog初級教程(10)Verilog的always塊](https://blog.csdn.net/Reborn_Lee/article/details/107052261)
* [在Verilog裡邊 always@(*)語句是什麼意思?](https://zhidao.baidu.com/question/261901454.html?fr=iks&word=ALU+ALWAY%40&ie=gbk)
* [HDLbits刷題中文完整版,按照刷題網站順序每日更新一道](https://blog.csdn.net/wszwszwszqwer/article/details/123764784)
* [yf869778412 verilog中的default應該賦捨麼樣的值](https://www.cnblogs.com/chengqi521/p/6721276.html)
* 名名在群組上寫的答案
```verilog
module alu(input [31:0] a, input [31:0] b, input [31:0] op, output reg [31:0] y);
always@(*) begin
case(op)
3'b000: y = a + b;
3'b001: y = a - b;
3'b010: y = a * b;
3'b011: y = a / b;
3'b100: y = a & b;
3'b101: y = a | b;
3'b110: y = ~a;
3'b111: y = a ^ b;
default: y = 0;
endcase
end
endmodule
```