將陸續上傳新書《自己動手寫CPU》,今天是第45篇。
這幾天事情多,好久沒更新了
前幾篇實現了加載存儲指令,今天將修改最小SOPC,用以測試加載存儲指令是否實現正確。閒話少說,進入正題。
9.4 修改最小SOPC
爲了驗證上一節添加的加載存儲指令是否實現正確,需要修改在第4章中設計的最小SOPC,爲其添加數據存儲器RAM。
9.4.1 添加數據存儲器RAM
數據存儲器RAM的接口如圖9-24所示,還是採用左邊是輸入接口,右邊是輸出接口的方式繪製,這樣便於理解。接口含義如表9-7所示。
數據存儲器RAM模塊的代碼如下,源文件是本書附帶光盤Code\Chapter9_1目錄下的data_ram.v文件。
module data_ram(
input wire clk,
input wire ce,
input wire we,
input wire[`DataAddrBus] addr,
input wire[3:0] sel,
input wire[`DataBus] data_i,
output reg[`DataBus] data_o
);
// 定義四個字節數組
reg[`ByteWidth] data_mem0[0:`DataMemNum-1];
reg[`ByteWidth] data_mem1[0:`DataMemNum-1];
reg[`ByteWidth] data_mem2[0:`DataMemNum-1];
reg[`ByteWidth] data_mem3[0:`DataMemNum-1];
// 寫操作
always @ (posedge clk) begin
if (ce == `ChipDisable) begin
//data_o <= ZeroWord;
end else if(we == `WriteEnable) begin
if (sel[3] == 1'b1) begin
data_mem3[addr[`DataMemNumLog2+1:2]] <= data_i[31:24];
end
if (sel[2] == 1'b1) begin
data_mem2[addr[`DataMemNumLog2+1:2]] <= data_i[23:16];
end
if (sel[1] == 1'b1) begin
data_mem1[addr[`DataMemNumLog2+1:2]] <= data_i[15:8];
end
if (sel[0] == 1'b1) begin
data_mem0[addr[`DataMemNumLog2+1:2]] <= data_i[7:0];
end
end
end
// 讀操作
always @ (*) begin
if (ce == `ChipDisable) begin
data_o <= `ZeroWord;
end else if(we == `WriteDisable) begin
data_o <= {data_mem3[addr[`DataMemNumLog2+1:2]],
data_mem2[addr[`DataMemNumLog2+1:2]],
data_mem1[addr[`DataMemNumLog2+1:2]],
data_mem0[addr[`DataMemNumLog2+1:2]]};
end else begin
data_o <= `ZeroWord;
end
end
endmodule
其中涉及到的相關宏定義在defines.v中定義,如下:
`define DataAddrBus 31:0 //地址總線寬度
`define DataBus 31:0 //數據總線寬度
`define DataMemNum 131071 //RAM的大小,單位是字,此處是128K word
`define DataMemNumLog2 17 //實際使用到的地址寬度
`define ByteWidth 7:0 //一個字節的寬度,是8bit
爲了方便實現對數據存儲器按字節尋址,在設計的時候使用4個8位存儲器代替一個32位存儲器,如圖9-25所示,讀操作時,從4個8位存儲器中各讀出一個字節,組合爲一個32位的數據輸出,寫操作時,依據sel的值,修改其中特定存儲器對應的字節即可。因此,地址addr的最低兩位不需要使用,比如:讀取地址n處的字,實際就是從4個8位存儲器的地址n/4處各讀取一個字節,組合起來就來地址n處的字。讀者可以結合本節實現的數據存儲器理解9.3.3節中MEM模塊的輸出。
9.4.2 修改最小SOPC
添加數據存儲器RAM後的SOPC如圖9-26所示。讀者可以與圖4-9對比。
此處需要修改openmips_min_sopc.v,在其中將OpenMIPS、ROM、RAM按照圖9-26所示連接起來,具體代碼不在書中列出,讀者可以參考本附帶光盤Code\Chapter9_1目錄下的同名文件。
9.5 測試程序
下面的測試程序是用來驗證前幾節實現的加載存儲指令(除ll、sc指令)是否正確,程序的註釋給出了預期執行效果。源文件是本書附帶光盤Code\Chapter9_1\AsmTest目錄下的inst_rom.S文件。
.org 0x0
.set noat
.set noreorder
.set nomacro
.global _start
_start:
############## 第一段:測試sb、lb、lbu指令 ################
ori $3,$0,0xeeff # $3 = 0x0000eeff
sb $3,0x3($0) # 向RAM地址0x3處存儲0xff, [0x3] = 0xff
srl $3,$3,8 # 邏輯右移8位, $3 = 0x000000ee
sb $3,0x2($0) # 向RAM地址0x2處存儲0xee, [0x2] = 0xee
ori $3,$0,0xccdd # $3 = 0x0000ccdd
sb $3,0x1($0) # 向RAM地址0x1處存儲0xdd, [0x1] = 0xdd
srl $3,$3,8 # 邏輯右移8位, $3 = 0x000000cc
sb $3,0x0($0) # 向RAM地址0x0處存儲0xcc, [0x0] = 0xcc
lb $1,0x3($0) # 加載0x3處的字節並作符號擴展, $1 = 0xffffffff
lbu $1,0x2($0) # 加載0x2處的字節並作無符號擴展, $1 = 0x000000ee
################ 第二段:測試sh、lh、lhu指令 ##############
ori $3,$0,0xaabb # $3 = 0x0000aabb
sh $3,0x4($0) # 向RAM地址0x4處存儲0xaabb,
# [0x4] = 0xaa, [0x5] = 0xbb
lhu $1,0x4($0) # 加載0x4處的半字並作無符號擴展, $1 = 0x0000aabb
lh $1,0x4($0) # 加載0x4處的半字並作符號擴展, $1 = 0xffffaabb
ori $3,$0,0x8899 # $3 = 0x00008899
sh $3,0x6($0) # 向RAM地址0x6處存儲0x8899,
# [0x6] = 0x88, [0x7] = 0x99
lh $1,0x6($0) # 加載0x6處的半字並作符號擴展, $1 = 0xffff8899
lhu $1,0x6($0) # 加載0x6處的半字並作無符號擴展, $1 = 0x00008899
################ 第三段:測試sw、lw、lwl、lwr指令 ##############
# 經過上面指令的執行,此時RAM的內容如下
# [0x0] = 0xcc, [0x1] = 0xdd
# [0x2] = 0xee, [0x3] = 0xff
# [0x4] = 0xaa, [0x5] = 0xbb
# [0x6] = 0x88, [0x7] = 0x99
ori $3,$0,0x4455
sll $3,$3,0x10
ori $3,$3,0x6677 # $3 = 0x44556677
sw $3,0x8($0) # 向RAM地址0x8處存儲0x44556677,
# [0x8] = 0x44, [0x9] = 0x55,
# [0xa] = 0x66, [0xb] = 0x77
lw $1,0x8($0) # 加載0x8處的字, $1 = 0x44556677
lwl $1,0x5($0) # 非對齊加載指令lwl,執行後使得$1 = 0xbb889977,
# 讀者可以結合圖9-8理解
lwr $1,0x8($0) # 非對齊加載指令lwr,執行後使得$1 = 0xbb889944,
# 讀者可以結合圖9-10理解
nop
################ 第四段:測試swl、swr指令 ################
swr $1,0x2($0) # 非對齊存儲指令swr,執行效果如下
# [0x0] = 0x88, [0x1] = 0x99,
# [0x2] = 0x44, [0x3] = 0xff
# 讀者可以結合圖9-16理解
swl $1,0x7($0) # 非對齊存儲指令swl,執行效果如下
# [0x4] = 0xaa, [0x5] = 0xbb,
# [0x6] = 0x88, [0x7] = 0xbb
# 讀者可以結合圖9-14理解
lw $1,0x0($0) # 加載RAM地址0x0處的字, $1 = 0x889944ff,
# 驗證swr指令的執行效果
lw $1,0x4($0) # 加載RAM地址0x4處的字, $1 = 0xaabb8844,
# 驗證swl指令的執行效果
_loop:
j _loop
nop
上面的測試代碼可分爲四段,分別測試了不同的加載、存儲指令,在ModelSim中的仿真效果如圖9-27所示,通過觀察寄存器$1的變化,可知OpenMIPS處理器正確實現了加載存儲指令。