首页 > 代码库 > 自己动手写CPU之第九阶段(6)——修改最小SOPC
自己动手写CPU之第九阶段(6)——修改最小SOPC
将陆续上传新书《自己动手写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处理器正确实现了加载存储指令。
自己动手写CPU之第九阶段(6)——修改最小SOPC