--- 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 ```