製作單週期CPU(分析)

用的是Vivado軟件,代碼部分將在下一期展現出來(內容太多我也很絕望),這一期來講講思路

要求

這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述

原理

1.單週期:

單週期CPU指一條指令在一個時鐘週期內完成並開始下一條指令的執行。由時鐘上升、下降沿控制相關操作。兩個相鄰時鐘上升沿或下降沿之間的時間間隔爲一個時鐘週期

2.CPU如何處理指令:

CPU處理指令的步驟如下:
a.取指令:根據PC中的指令地址,在指令存儲器中獲取相應指令。然後PC值會自動改變移動到下一條指令的地址
b.指令譯碼:對獲取的指令進行分析,確定這個指令要完成什麼操作,改變相應的控制信號
c.指令執行:相關組件獲取控制信號,執行相應操作,並將結果反饋
d.存儲器訪問:如果指令設計讀取和存儲內存,則需要對存儲器中相應地址進行讀取或者寫入
e.結果寫回:將得到的數據(訪問存儲器或者修改其它寄存器的值獲得)寫回相應的寄存器
CPU的工作就是無限循環上訴步驟。

3.要實現的指令格式:

實驗要求的操作滿足MIPS指令的三種格式
這裏寫圖片描述
op爲操作碼,是解析指令要完成什麼操作的重要參考,不同操作的操作碼獨一無二
rs,rt,rd爲寄存器編號,由於一共有32個,所以五位二進制編碼足夠表示每個寄存器(0號寄存器不能修改,值永遠爲0)
funct爲拓展碼,有時候op不足以完全表示所要完成的操作,拓展碼用於輔助描述這類操作

4.數據傳遞

根據數據通路圖
這裏寫圖片描述
整體是一個CPU,而Control Unit(控制單元)作爲CPU的大腦,向各個組件發送控制信號指導它們完成任務,相應控制信號及功能如圖
這裏寫圖片描述
關於ALUOp,因爲很多操作要用到ALU,所以ALUOp用於區分ALU到底要做什麼,以下是我設計的功能表
這裏寫圖片描述

5.掌握一個週期的時間:

一個週期時間很短,必須合理運用,如果寫寄存器或存儲器和PC地址轉移都在時鐘上升沿或都在時鐘下降沿執行,則有可能數據還沒來得及寫就執行下一條指令了,爲了提高穩定性,可以在時鐘下降沿開始保存指令執行的結果到寄存器和存儲器中,上升沿則用來改變PC。

分析

1.CPU架構建立:

由數據通路圖可得,一共包含11個組件:

1.1.PC:

給出當前要執行的指令地址,同時獲取下一條指令的地址。我將跳轉器直接加入到了PC組件中,如果需要跳轉,PC根據傳入的值獲取下一條指令地址,否則執行+4獲取下一條相鄰指令
輸入:CLK,Reset,PCWre,PCSrc,immediate(立即數,跳轉時有用)
輸出:Address(當前指令的地址)
這裏寫圖片描述
可以看出,PC是在上升沿獲取下一條指令,下降沿重置。立即數反映的是邏輯的跳轉數,然而真實的是每跳過一條指令相應PC地址需要+/-4

1.2.InstructionMemory(指令存儲器):

通過PC地址獲取相應指令的操作碼
輸入:CurPC(當前PC所指地址),instMemRW
輸出:instcode(找到的指令的操作碼)
指令寄存器我規定存儲單元寬度位8位,採用大端規則存儲,即指令高位存到寄存器低地址,低位存到高地址,這是$readmemb方法默認實現的,那麼獲取操作碼就可以通過
instcode = { InstMemory[CurPC], InstMemory[CurPC + 1], InstMemory[CurPc + 2], InstMemory[CurPC + 3] };
獲取
這裏寫圖片描述

1.3.Mux_TwoToOneReg(兩路寄存器選擇器):

由於MIPS指令格式的不同,進入寄存器組(RegisterFile)的Write Reg端的可能是rt,也可能是rd,因此在這裏要依據控制信號來選擇寄存器
輸入:RegDst,RegIn1(rt), RegIn2(rd)
輸出:RegOut(寄存器組的Write Reg輸入)
根據傳入的RegDst來選擇寄存器,如圖
這裏寫圖片描述

1.4.RegisterFile(寄存器組):

大多數指令涉及寄存器,寄存器組管理32個寄存器,同時可以獲取不同寄存器的值,可以修改允許修改的寄存器的值
輸入:RegWre,CLK,RegIn1(寄存器的編號),RegIn2(寄存器的編號),Write_Reg,WriteData
輸出:RegOut1(RegIn1號寄存器的值),RegOut2(RegIn2號寄存器的值)
由於0號寄存器默認值爲0,不能被寫入,只能讀取,我通過
這裏寫圖片描述
來屏蔽0號寄存器,實際上這個寄存器並不物理存在,爲確保0號寄存器不被寫入,在時鐘下降沿時
這裏寫圖片描述
If語句規定只有當寫寄存器編號不爲0時纔會執行寫操作

1.5.Sign_Zero_Extend(位擴展器):

指令中的立即數是16位的,要進入ALU的輸入端則必須變爲32位,邏輯操作(與、或)使用的是無符號擴展;加、減等操作使用的是有符號擴展
輸入:Imm_Number(指令中的立即數,16位),ExtSel
輸出:Result
這裏寫圖片描述

1.6.Mux_TwoToOne_For_ALU_InputA(ALU輸入A端數據選擇器):

通常ALU的A輸入端口是寄存器組1號輸出端口輸出的rs寄存器的值,但是當操作爲sll時,應該是指令中的sa段,但是sa只有5位,所以這個選擇器的功能有兩個:1.將sa通過無符號擴展變爲32位;2.根據控制信號ALUSrcA判斷輸入到ALU的值應該是寄存器組傳入的值還是sa的值
輸入:ALUSrcA,Reg1Out,sa
輸出:DataOut(ALU_Input_A)
數據選擇通過如圖實現
這裏寫圖片描述

1.7.Mux_TwoToOne_For_ALU_InputB(ALU輸入B端數據選擇器):

ALU的B輸入端可能來自於寄存器組2號輸出端口輸出的rt寄存器的值,也可能是指令中立即數的值,所以需要這個選擇器來選擇ALU的B的輸入
輸入:ALUSrcB,Reg2Out,immediate(來自位擴展器)
輸出:DataOut(ALU_Input_B)

模塊與Mux_TwoToOne_For_ALU_InputA類似,不需要位擴展
這裏寫圖片描述

1.8.Mux_TwoToOne_For_RegisterFile_WriteData(寄存器輸入WriteData端數據選擇器):

操作入lw,add需要對寄存器進行寫操作,目標數據可能來自於ALU的結果(如add)也可能來自存儲器中(如lw),所以需要這個選擇器對進入寄存器輸入WriteData端的數據進行選擇
輸入:DBDataSrc,ALU_Out,MemDataOut(來自數據存儲器)
輸出:WriteData
和Mux_TwoToOne_For_ALU_InputB所用模塊相同,只需要修改對應參數即可

1.9.ALU(運算單元):

大多數操作都可能用到ALU,包括add,sll,sub等,ALU負責的是對數據進行基本的算術操作,根據實驗內容部分ALU運算表可以定義相關方法,zero輸出端是判斷運算結果是否爲0的,主要用處是在beq中,判斷兩個值是否相等就是將其相減並判斷結果是否與0相等,ALU輸出的值可能是一個具體的數據(如add),也可能是某個地址(如sw、lw)
輸入:ALU_Input_A,ALU_Input_B,ALUOp
輸出:ALU_Out,zero
ALUOp是具體的操作指示碼,根據它的值ALU_Out的會有相應的結果
這裏寫圖片描述

1.10.DataMemory(數據存儲器):

lw操作和sw操作關係到了數據的存儲,那麼我就製作了一個數據存儲器用於存放數據。爲了保證該模擬CPU的穩定性,我設計數據存儲器的讀操作是隨時的,存儲單元寬度爲8位,同樣採用大端規則存儲
這裏寫圖片描述
而寫操作是時鐘下降沿觸發
這裏寫圖片描述

1.11.ControlUnit(控制單元):

作爲CPU的大腦,獲取指令操作碼進行解析,發出相應的控制信號,指揮所有組件的工作。根據數據通路圖,每種指令操作碼的控制信號真值表如圖
這裏寫圖片描述
x表示可以爲任意值並不影響結果,InsMemRW一直爲0,但我沒有刪掉這個控制信號,因爲以後的功能擴展可能會用到
輸入:opCode(指令碼的高6位),zero(用於判斷beq是否需要跳轉)
輸出:每種操作信號
根據真值表可以很好確定對應控制信號的值
這裏寫圖片描述
值得注意:ALUOp的最高位在最左邊

SCPU(單週期CPU):

作爲頂層模塊,包含上述所有組件
輸入:CLK,Reset
輸出:CurPC,instcode(指令碼)
作爲整體的設計,要弄清楚不同組件的數據傳遞關係,根據數據通路圖以及設計的組件可繪製出下圖
這裏寫圖片描述
所以很明確頂層模塊需要實例化每個組件,並將相關數據、控制信號聯繫起來

2.仿真測試:

在InstructionMemory模塊中採取了$readmemb方法,所以通過文件寫入,指令真值表爲
這裏寫圖片描述
在txt文檔中寫
這裏寫圖片描述
因爲$readmemb是一個字節一個字節的讀,所以字節與字節之間要用製表符或者空格之類的分隔符分開,之前因爲這個原因總是讀不了指令

在完成所有組件的定義後,新建一個仿真組件SCPU_sim
這裏寫圖片描述
並初始化
這裏寫圖片描述

就可以啓動仿真了
這是執行左移位操作時的仿真波形
這裏寫圖片描述
要驗證仿真是否正確,可以在每個週期查看對應組件中的I/O,雖然看起來很複雜,但是如果已經理解了數據通路圖,則能夠快速準確的驗證。比如查看RegisterFile
這裏寫圖片描述
此時已經驗證384被成功寫入,384 = 12 * 2^5

總結

創建一個單週期CPU,數據的流動必須要搞清楚,建議自上而下設計,根據總體需要什麼來設計相應組件,其次,數據通路圖很重要,它概括了一個項目所要攻克的所有問題,建議頭腦比較混亂的時候多寫寫,多畫畫,認真思考,一定可以做出來(我一開始也覺得根本做不出來)
關於vivado我也有了一點認識,input關鍵字表明這個參數是外界傳入的,仿真時必須賦值;output則表示是傳出外界的,在仿真時會在示波器中顯示相應波形。而reg、wire關鍵字都可以作爲該組件的內部屬性,這些屬性對於其它組件來說是透明的
但是還是有bug,我的對數據存儲器讀的操作是隨時的,但仿真結果是下降沿纔開始讀,還在嘗試解決中
如果有什麼不足,請提出我好改進,感謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章