首页 > 代码库 > 自己动手写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

      为了方便实现对数据存储器按字节寻址,在设计的时候使用48位存储器代替一个32位存储器,如图9-25所示,读操作时,从48位存储器中各读出一个字节,组合为一个32位的数据输出,写操作时,依据sel的值,修改其中特定存储器对应的字节即可。因此,地址addr的最低两位不需要使用,比如:读取地址n处的字,实际就是从48位存储器的地址n/4处各读取一个字节,组合起来就来地址n处的字。读者可以结合本节实现的数据存储器理解9.3.3节中MEM模块的输出。



9.4.2 修改最小SOPC

      添加数据存储器RAM后的SOPC如图9-26所示。读者可以与图4-9对比。



      此处需要修改openmips_min_sopc.v,在其中将OpenMIPSROMRAM按照图9-26所示连接起来,具体代码不在书中列出,读者可以参考本附带光盘Code\Chapter9_1目录下的同名文件。

9.5 测试程序

      下面的测试程序是用来验证前几节实现的加载存储指令(除llsc指令)是否正确,程序的注释给出了预期执行效果。源文件是本书附带光盘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