自己动手写CPU——单周期ORI指令的实现

单周期ORI指令的实现

    • ORI指令说明
    • 单周期系统结构框图
    • 模块划分以及功能代码实现
        • 1. 宏定义define
        • 2. 取指pc
        • 3. 译码id
        • 4. 通用寄存器RegFile
        • 5. 执行ex
        • 6.访存mem
        • 7. 回写wb
        • 8 .顶模块OpenMIPS
        • 9 .指令存储器
        • 10 .最小SOPC实现
    • 测试模块以及仿真波形
    • 指令存储文件inst_rom.dat
    • 遇到的问题以及解决
        • 1. 仿真波形输错_1
        • 2. 仿真波形出错_2
        • 3. 顶层模块段端口定义
        • 4. 解决取操作数不准确

写在最前面,后续的一些指令我还在慢慢学,在这个框架上继续增加,有兴趣可以私信我,代码可以互相分享学习

ORI指令说明

自己动手写CPU——单周期ORI指令的实现_第1张图片
这是一个I型指令,ORI指令的指令码为6’b00_1101
ORI指令作用:将16位立即数immediate进行无符号扩展至32位,然后与索引为rs的通用寄存器里的值进行“或”运算,运算结果保存到索引为rt的通用寄存器中
扩展1:立即数扩展

n位立即数扩展到3位。符号扩展是将n位立即数的最高为复制到扩展后的32位数据的高(32-n)位,无符号扩展是将扩展后的32位数据的高(32-n)位都置0
16位立即数: 0x8000、0x8000
符号扩展: 0xFFFF8000、0x00001000
无符号扩展: 0x00008000、0x00001000

扩展2:寄存器

MIPS架构定义了32个通用寄存器,使用$0、$1、…、%31表示,索引5位。其中%0一般用作常量0。

寄存器名字 约定命名 用途
$0 zero 总是为0
$1 at 留作汇编器生成一些合法指令
$2、$3 v0、v1 用来存放程序返回值
$4-$7 a0-a3 调用子程序时,使用这4个寄存器传输前4个非浮点参数
$8-$15 t0-t7 临时寄存器 、子程序使用时可以不用存储和恢复
$16-$23 s0-s7 子程序寄存器变量,改变这些寄存器值的子程序必须存储旧的值并在退出前恢复,对调用程序来说值不变
$24、$25 t7、t9 临时寄存器,子程序使用时可以不用存储和恢复
$26、$27 k 0 、 k0、 k0k1 由异常处理程序使用
28 或 28或 28gp gp 全局指针
29 或 29或 29sp sp 堆栈指针
30 或 30或 30fp fp 子程序可以用来作堆栈帧指针
$31 ra 存放程序返回地址

单周期系统结构框图

自己动手写CPU——单周期ORI指令的实现_第2张图片

模块划分以及功能代码实现

1. 宏定义define

// *****************  全局的宏定义  ***************
`define RstEnable       1'b1          // 复位信号有效
`define RstDisable      1'b0          // 复位信号无效
`define ZeroWord        32'h00000000  // 32位的数值0
`define WriteEnable     1'b1          // 使能写
`define WriteDisable    1'b0          // 禁止写
`define ReadEnable      1'b1          // 使能读
`define ReadDisable     1'b0          // 禁止读
`define AluOpBus        7:0           // 译码阶段的输出aluop_o的宽度  子类型
`define AluSelBus       2:0           // 译码阶段的输出alusel_o的宽度 类型
`define InstValid       1'b0          // 指令有效
`define InstInvalid     1'b1          // 指令无效
`define True_v          1'b1          // 逻辑真
`define False_v         1'b0          // 逻辑假
`define ChipEnable      1'b1          // 芯片使能
`define ChipDisable     1'b0          // 芯片禁止

// ***************  与具体指令有关的宏定义  ***************
`define EXE_ORI         6'b001101     // 指令ori的指令码
`define EXE_NOP         6'b000000     // 

//AluOp   子类型
`define EXE_OR_OP       8'b00100101   // or_func
`define EXE_NOP_OP      8'b00000000
//AluSel  类型
`define EXE_RES_LOGIC   3'b001        // 逻辑运算
`define EXE_RES_NOP     3'b000

// ***************  与指令存储器ROM有关的宏定义  ***************
`define InstAddrBus     31:0          // ROM的地址总线宽度
`define InstBus         31:0          // ROM数的据总线宽度
`define InstMemNum      131071
`define InstMemNumLog2  17

// **************  与通用寄存器Regfile有关的宏定义  ***************
`define RegAddrBus      4:0           // Regfile模块的地址线宽度
`define RegBus          31:0          // Regfile模块的数据线宽度
`define RegWidth        32
`define DoubleRegWidth  64
`define DoubleRegBus    63:0
`define RegNum          32            // 通用寄存器的数量
`define RegNumLog2      5             // 寻址通用寄存器使用的地址位数
`define NOPRegAddr      5'b00000

2. 取指pc

// 取出指令存储器中的指令、PC增值,准备取下一条指令
`include "defines.v"
module pc_reg (
  output  reg [`InstAddrBus]  pc,     // 要读取的指令地址
  output  reg                 ce,     // 指令存储器使能信号
  input   wire                clk,    // 时钟信号
  input   wire                rst     // 复位信号 
);
  always @ (posedge clk) begin
    if (rst == `RstEnable) begin
      ce <= `ChipDisable;     // 复位的时候指令存储器禁用
    end else begin
      ce <= `ChipEnable;      // 复位结束后指令存储器使能
    end
  end
    always @ (posedge clk) begin
    if (ce == `ChipDisable) begin
      pc <= 32'h00000000;        // 指令存储器禁用的时候,pc为0
    end else begin
      pc <= pc + 4'h4;        // 指令存储器使能的时候,pc值每时钟周期+4字节
    end
  end
  
endmodule

3. 译码id

// 对指令进行译码,得到最终运算的类型、子类型、源操作数1、源操作数2、要写入的目的寄存器地址信息等
`include "defines.v"

module id (
  // 输出到regfile的信息
  output  reg                   reg1_read_o,    // regfile模块第一个读寄存器端口的读使能信号
  output  reg                   reg2_read_o,    // regfile模块第二个读寄存器端口的读使能信号
  output  reg   [`RegAddrBus]   reg1_addr_o,    // regfile模块第一个读寄存器端口的读地址信号
  output  reg   [`RegAddrBus]   reg2_addr_o,    // regfile模块第二个读寄存器端口的读地址信号
  // 送到执行阶段的信息
  output  reg   [`AluOpBus]     aluop_o,        // 译码阶段的指令要进行的运算的子类型
  output  reg   [`AluSelBus]    alusel_o,       // 译码阶段的指令要进行的运算的类型
  output  reg   [`RegBus]       reg1_o,         // 译码阶段的指令要进行的运算的源操作数1
  output  reg   [`RegBus]       reg2_o,         // 译码阶段的指令要进行的运算的源操作数2
  output  reg   [`RegAddrBus]   wd_o,           // 译码阶段的指令要写入的目的寄存器地址
  output  reg                   wreg_o,         // 译码阶段的指令是否有要写入的目的寄存器
  input   wire                  rst,            // 复位
  input   wire  [`InstAddrBus]  pc_i,           // 译码阶段指令对应的地址
  input   wire  [`InstBus]      inst_i,         // 译码阶段的指令
  // 读取的regfile的值
  input   wire  [`RegBus]       reg1_data_i,    // 从regfile输入的第一个读寄存器端口的输出
  input   wire  [`RegBus]       reg2_data_i     // 从regfile输入的第一个读寄存器端口的输出  
);

  // 取得指令的指令码,功能码
  // 对于ori指令只需通过判断26-31bits的值,即可判断是否是ori指令,6'b00_1101
  wire  [5:0]     op  = inst_i[31:26];    // 高6位决定具体操作
  wire  [4:0]     op2 = inst_i[10:6];     
  wire  [5:0]     op3 = inst_i[5:0];
  wire  [4:0]     op4 = inst_i[20:16];
  
  // 保存指令执行需要的立即数
  reg   [`RegBus] imm	 
  // 指令指示是否有效
  reg             instvalid;
  
  // ****************************************************************************
  // **************************第一阶段:对指令进行译码******************************
  // ****************************************************************************
  always @ (*) begin
    if (rst == `RstEnable) begin
      aluop_o   <=  `EXE_NOP_OP;
      alusel_o  <=  `EXE_RES_NOP;
      wd_o      <=  `NOPRegAddr;      // 写入地址赋值为无效
      wreg_o    <=  `WriteDisable;    // 没有要写入的目的寄存器
      instvalid <=  `InstValid;       // 指令有效
      reg1_read_o <=  1'b0;
      reg2_read_o <=  1'b0;
      reg1_addr_o <=  `NOPRegAddr;
      reg2_addr_o <=  `NOPRegAddr;
      imm         <=  32'h0;
    end else begin
      aluop_o   <=  `EXE_NOP_OP;
      alusel_o  <=  `EXE_RES_NOP;
      wd_o      <=  inst_i[15:11];      // rd,目的寄存器编号
      wreg_o    <=  `WriteDisable; 
      instvalid <=  `InstInvalid;       // 指令无效
      reg1_read_o <=  1'b0;
      reg2_read_o <=  1'b0;
      reg1_addr_o <=  inst_i[25:21];  // 默认通过regfile读端口1读取的寄存器地址,rs,源寄存器地址
      reg2_addr_o <=  inst_i[20:16];  // 默认通过regfile读端口2读取的寄存器地址,rt,源寄存器地址
      imm         <=  `ZeroWord;
      case (op)
        `EXE_ORI: begin               // 依据op的值判断指令是否为ori指令
          // ori指令需要将结果写入目的寄存器
          wreg_o      <=  `WriteEnable;
          // 运算子类型是逻辑'or'运算
          aluop_o     <=  `EXE_OR_OP;
          // 运算类型是逻辑运算
          alusel_o    <= `EXE_RES_LOGIC;
          // 需要通过regfile的读端口1读取寄存器
          reg1_read_o <=  `WriteEnable;
          // 不需要通过regfile的读端口2读取寄存器
          reg2_read_o <=  `WriteDisable;
          // 指令执行需要的立即数,无符号扩展
          imm         <=  {16'h0, inst_i[15:0]};
          // 指令执行要写的目的寄存器地址
          wd_o        <=  inst_i[20:16];
          // ori指令是有效指令
          instvalid   <=  `InstValid;
        end
        default: begin
        end  
      endcase // case
    end //if
  end //always
  // ****************************************************************************
  // ********************第二阶段:确定进行运算的操作数1 ******************************
  // ****************************************************************************   
  always @ (*) begin
    if (rst == `RstEnable) begin
      reg1_o  <=  `ZeroWord;
    end else if (reg1_read_o == 1'b1) begin
      reg1_o  <=  reg1_data_i;    // regfile读端口1的输出值
    end else if (reg1_read_o == 1'b0) begin
      reg1_o  <=  imm;            // 立即数
    end else begin
      reg1_o  <=  `ZeroWord;
    end
  end    
  // ****************************************************************************
  // ********************第三阶段:确定进行运算的操作数2 ******************************
  // ****************************************************************************   
  always @ (*) begin
    if (rst == `RstEnable) begin
      reg2_o  <=  `ZeroWord;
    end else if (reg2_read_o == 1'b1) begin
      reg2_o  <=  reg2_data_i;    // regfile读端口2的输出值
    end else if (reg2_read_o == 1'b0) begin
      reg2_o  <=  imm;            // 立即数
    end else begin
      reg2_o  <=  `ZeroWord;
    end
  end
endmodule      

4. 通用寄存器RegFile

// 寄存器堆栈,实现了32个32位通用整数寄存器,可以同时进行两个寄存器的读操作和一个寄存器的写操作
`include	"defines.v"

module regfile (
  // 写端口
  input   wire                  we,          // 写使能信号
  input   wire  [`RegAddrBus]   waddr,       // 要写入的寄存器地址
  input  reg   [`RegBus]       wdata,       // 要写入的数据
  
  // 读端口1
  input   wire                  re1,          // 第一个读寄存器端口读使能信号   
  input   wire  [`RegAddrBus]   raddr1,       // 第一个读寄存器端口要读取的寄存器的地址
  output  reg   [`RegBus]       rdata1,       // 第一个读寄存器端口输出的寄存器值
  // 读端口2
  input   wire                  re2,          // 第二个读寄存器端口读使能信号
  input   wire  [`RegAddrBus]   raddr2,       // 第二个读寄存器端口要读取的寄存器的地址
  output  reg   [`RegBus]       rdata2,       // 第二个读寄存器端口输出的寄存器值
  
  input   wire                  clk,
  input   wire                  rst
);

// **********************第一阶段:定义32个32位寄存器************************
reg     [`RegBus]     regs[0:`RegNum-1];

// **************************第二阶段:写操作******************************
  always @ (posedge clk) begin
    if (rst == `RstDisable) begin  // 复位信号无效
      if ((we ==`WriteEnable) && (waddr != `RegNumLog2'h0)) begin  // 写使能&&写目的寄存器≠$0
        regs[waddr] <= wdata;
      end
    end
  end
  
// *************************第三阶段:读端口1的读操作******************************
  always @ (*) begin
    if (rst == `RstEnable) begin
      rdata1  <= `ZeroWord;     // 复位信号有效,输出0
    end else if (raddr1 == `RegNumLog2'h0) begin    // 读目的寄存器为$0,输出0
      rdata1  <= `ZeroWord;
    end else if (re1 == `ReadEnable) begin    // 输出要读取的目的寄存器地址对应的值
      rdata1  <= regs[raddr1];
    end else if ((raddr1 == waddr) && (we == `WriteEnable) && (re1 == `ReadEnable)) begin    // 读目的寄存器是写目的寄存器&&写使能&&读使能,输出写入的值
      rdata1  <= wdata;
    end else begin
      rdata1  <= `ZeroWord;
    end
  end
       
// *************************第四阶段:读端口2的读操作******************************
  always @ (*) begin
    if (rst == `RstEnable) begin
      rdata2  <= `ZeroWord;
    end else if (raddr2 == `RegNumLog2'h0) begin
      rdata2  <= `ZeroWord;
    end else if ((raddr2 == waddr) && (we == `WriteEnable) && (re2 == `ReadEnable)) begin
      rdata2  <= wdata;
    end else if (re2 == `ReadEnable) begin
      rdata2  <= regs[raddr2];
    end else begin
      rdata2  <= `ZeroWord;
    end
  end
endmodule

/*
注:
读操作时组合逻辑电路,输入的要读取的寄存器地址raddr1和raddr2发生变化,立刻得到新地址对应的寄存器的值,这样可以保证在译码阶段取得要读取的寄存器的值
写操作时时序逻辑电路,发生在时钟上升沿
*/


5. 执行ex

// 从ID模块得到运算类型alusel_i、运算子类型aluse_i、源操作数reg1_i、源操作数reg2_i、要写入的目的寄存器地址wd_i
// EX模块依据这些数据进行运算
`include "defines.v"

module ex (
  // 执行的结果
  output  reg   [`RegAddrBus]   wd_o,         // 最终要写入的目的寄存器地址
  output  reg                   wreg_o,       // 最终是否有要写入的目的寄存器
  output  reg   [`RegBus]       wdata_o,      // 最终要写入目的寄存器的值
  
  input   wire                  rst,
  // 译码阶段送到执行阶段的信息
  input   wire  [`AluOpBus]     aluop_i,      // 执行阶段运算子类型
  input   wire  [`AluSelBus]    alusel_i,     // 执行阶段运算类型
  input   wire  [`RegBus]       reg1_i,       // 执行阶段源操作数1
  input   wire  [`RegBus]       reg2_i,       // 执行阶段源操作数2
  input   wire  [`RegAddrBus]   wd_i,         // 执行阶段要写入的目的寄存器地址
  input   wire                  wreg_i        // 执行阶段是否有要写入的目的寄存器
);
  // 保存逻辑运算的结果
  reg   [`RegBus]   logicout;
  
  // ****************************************************************************
  // ***************第一阶段:依据aluop_i指示的运算子类型进行运算*********************
  // ****************************************************************************
  always @ (*) begin
    if (rst == `RstEnable) begin
      logicout  <=  `ZeroWord;
    end else begin
      case (aluop_i)
        `EXE_OR_OP: begin
          logicout  <=  reg1_i | reg2_i;
        end
        default:    begin
          logicout  <=  `ZeroWord;
        end
      endcase // case
    end // if
  end // always
  // ****************************************************************************
  // **********第二阶段:依据alusel_i指示的运算类型,选择一个运算结果作为最终结果********
  // ****************************************************************************
  always @ (*) begin
    wd_o    <=  wd_i;     // 要写入的目的寄存器地址
    wreg_o  <=  wreg_i;   // 是否要写入目的寄存器
    case (alusel_i)
      `EXE_RES_LOGIC: begin
        wdata_o <=  logicout;   // wdata_o中存放运算结果
      end
      default:        begin
        wdata_o <=  `ZeroWord;
      end
    endcase // case
  end // always  

endmodule

6.访存mem

// mem访存模块
`include "defines.v"

module mem (
  output  reg   [`RegAddrBus]   wd_o,     // 传到回写阶段的寄存器地址
  output  reg                   wreg_o,   // 是否有写入
  output  reg   [`RegBus]       wdata_o,  // 传到回写阶段的数据
  
  input   wire                  rst,
  input   wire  [`RegAddrBus]   wd_i,
  input   wire                  wreg_i,
  input   wire  [`RegBus]       wdata_i
);

  always @ (*) begin
    if (rst == `RstEnable) begin
      wd_o    <=  `NOPRegAddr;
      wreg_o  <=  `WriteDisable;
      wdata_o <=  `ZeroWord;
    end else begin
      wd_o    <=  wd_i;
      wreg_o  <=  wreg_i;
      wdata_o <=  wdata_i;
    end
  end
    
endmodule


7. 回写wb

// 将输入的执行阶段的结果直接作为输出
`include "defines.v"

module wb (
  // 访存阶段的结果
  output  reg   [`RegAddrBus]   wd_o,       // 访存阶段的指令最终要写入的目的寄存器地址
  output  reg                   wreg_o,     // 访存阶段的指令最终是否有要写入的目的寄存器
  output  reg   [`RegBus]       wdata_o,    // 访存阶段的指令最终要写入目的寄存器的值
  
  input   wire                  rst,
  // 来自执行阶段的信息
  input   wire  [`RegAddrBus]   wd_i,
  input   wire                  wreg_i,
  input   wire  [`RegBus]       wdata_i  
);

  always @ (*) begin
    if (rst == `RstEnable) begin
      wd_o    <=  `NOPRegAddr;
      wreg_o  <=  `WriteDisable;
      wdata_o <=  `ZeroWord;
    end else begin
      wd_o    <=  wd_i;
      wreg_o  <=  wreg_i;
      wdata_o <=  wdata_i;
    end
  end
  
endmodule 

8 .顶模块OpenMIPS

// 顶层模块
`include "defines.v"

module openmips (
  output  wire    [`RegBus]   rom_addr_o,     // 输出到指令存储器的地址
  output  wire                rom_ce_o,       // 指令寄存器使能信号
  
  input   wire    [`RegBus]   rom_data_i,     // 从指令寄存器取得的指令
  input   wire                rst,
  input   wire                clk
);

  // ****************************************************************************
  // **************************第一阶段:物理连线声明******************************
  // **************************************************************************** 
  
  // 连接ID模块和EX模块输入的变量
  wire  [`AluOpBus]     id_aluop;
  wire  [`AluSelBus]    id_alusel;
  wire  [`RegBus]       id_reg1;
  wire  [`RegBus]       id_reg2;
  wire                  id_wreg;
  wire  [`RegAddrBus]   id_wd;
  
  // 连接译码阶段ID模块和通用寄存器Regfile模块的变量
  wire                  reg1_read;
  wire                  reg2_read;
  wire  [`RegBus]       reg1_data;
  wire  [`RegBus]       reg2_data;
  wire  [`RegAddrBus]   reg1_addr;
  wire  [`RegAddrBus]   reg2_addr;

  // 连接EX模块输出和MEM模块的变量
  wire  [`RegAddrBus]   ex_wd;
  wire                  ex_wreg;
  wire  [`RegBus]       ex_wdata;  
    
  // 连接MEM模块输出和WB模块的变量
  wire  [`RegAddrBus]   mem_wd;
  wire                  mem_wreg;
  wire  [`RegBus]       mem_wdata;
  
  // 连接WB模块和RegFIle模块输入的变量
  wire  [`RegAddrBus]   wb_wd;
  wire                  wb_wreg;
  wire  [`RegBus]       wb_wdata;
  
  // ****************************************************************************
  // **********************第二阶段:对各个模块进行实例化*****************************
  // ****************************************************************************
  // pc_reg例化
  pc_reg  pc_reg0 (
          .pc(rom_addr_o), .ce(rom_ce_o),
          
          .clk(clk), .rst(rst)
  );
  // 译码阶段ID模块例化
  id      id0 (
          // 送到RegFile模块的信息
          .reg1_read_o(reg1_read), .reg2_read_o(reg2_read),
          .reg1_addr_o(reg1_addr), .reg2_addr_o(reg2_addr),
          // 送到EX模块的信息
          .aluop_o(id_aluop), .alusel_o(id_alusel),
          .reg1_o(id_reg1), .reg2_o(id_reg2),
          .wd_o(id_wd), .wreg_o(id_wreg),
          
          .rst(rst), .pc_i(rom_addr_o), .inst_i(rom_data_i),
          // 来自RegFile模块的输出
          .reg1_data_i(reg1_data), .reg2_data_i(reg2_data)
  );
  // 通用寄存器Regfile模块例化
  regfile regfile0 (
          .we(wb_wreg), .waddr(wb_wd), .wdata(wb_wdata),
          .re1(reg1_read), .raddr1(reg1_addr), .rdata1(reg1_data),
          .re2(reg2_read), .raddr2(reg2_addr), .rdata2(reg2_data),
          
          .clk(clk), .rst(rst)
  );
  // EX模块例化
  ex      ex0 (
          // 输出到MEM模块的信息
          .wd_o(ex_wd), .wreg_o(ex_wreg), .wdata_o(ex_wdata),
          
          .rst(rst),
          // 从ID模块传递过来的信息
          .aluop_i(id_aluop), .alusel_i(id_alusel),
          .reg1_i(id_reg1), .reg2_i(id_reg2),
          .wd_i(id_wd), .wreg_i(id_wreg)
  );
  
  // MEM模块例化
  mem     mem0 (
          // 输出到WB模块的信息
          .wd_o(mem_wd), .wreg_o(mem_wreg), .wdata_o(mem_wdata),
          
          .rst(rst),
          // 从EX模块接收到的信息
          .wd_i(ex_wd), .wreg_i(ex_wreg), .wdata_i(ex_wdata)
  );
  
  // WB模块例化
  wb     wb0 (
          // 输出的信息
          .wd_o(wb_wd), .wreg_o(wb_wreg), .wdata_o(wb_wdata),
          
          .rst(rst),
          // 从EX模块接收到的信息
          .wd_i(ex_wd), .wreg_i(ex_wreg), .wdata_i(ex_wdata)
  );

endmodule

9 .指令存储器

// 指令存储器ROM模块
`include "defines.v"

module inst_rom (
  output  reg   [`InstBus]      inst,     // 读出的指令
  
  input   wire                  ce,       // 使能信号
  input   wire  [`InstAddrBus]  addr      // 要读取的指令地址
);

  // 定义一个数组,大小是InstMemNum(131071),元素宽度是InstBus
  reg   [`InstBus]    inst_mem[0:`InstMemNum-1];
  
  // 使用文件inst_rom.data初始化指令存储器
  initial $readmemh ("inst_rom.dat", inst_mem);
  
  // 当复位信号无效时,依据输入的地址,给出指令存储器ROM中对应的元素
  always @ (*) begin
    if (ce == `ChipDisable) begin
      inst   = `ZeroWord;
    end else begin
      inst  <= inst_mem[addr[`InstMemNumLog2+1:2]];    // inst_mem[addr[5+1:2]]  
    end
  end

endmodule

10 .最小SOPC实现

// 最小SOPC模块,仅包括OpenMIPS和指令存储器ROM,例化处理这个两个模块
`include "defines.v"

module OpenMIPS_min_SOPC (
  input wire    clk,
  input wire    rst
);

  // 连接指令存储器
  wire  [`InstAddrBus]    inst_addr;
  wire  [`InstBus]        inst;
  wire                    rom_ce;
  
  // 例化处理器OpenMIPS
  openmips  openmips0 (
            .rom_addr_o(inst_addr), .rom_ce_o(rom_ce),
        
            .rom_data_i(inst),
            .clk(clk), .rst(rst)
  );
  
  // 例化指令存储器
  inst_rom  inst_rom0 (
            .inst(inst),
            
            .ce(rom_ce), .addr(inst_addr)
  );
  
endmodule

测试模块以及仿真波形

// testbench of "OpenMIPS_mini_SOPC_tb"
`include "defines.v"

module OpenMIPS_min_SOPC_tb ();
  reg   clk;
  reg   rst;

  module OpenMIPS_min_SOPC_tb ();
  reg   clk;
  reg   rst;
  
  // 例化最小SOPC
  OpenMIPS_min_SOPC OpenMIPS_min_SOPC0 (
                    .clk(clk), .rst(rst)
  );
  
  initial begin
    clk = 1'b0;
  end
  always #10 clk = ~clk; 
  
  // 最初时刻,复位信号有效,在第195ns,复位信号无效,最小SOPC开始运行
  initial begin
          rst = `RstEnable;
    #195  rst = `RstDisable;
//    #1000 $stop;
  end
  
endmodule

自己动手写CPU——单周期ORI指令的实现_第3张图片
自己动手写CPU——单周期ORI指令的实现_第4张图片
自己动手写CPU——单周期ORI指令的实现_第5张图片

指令存储文件inst_rom.dat

记事本中存放数据:

34011100 // $1 = 0000_0000 | 0000_1100 = 0000_1100
34020020 // $2 = 0000_0000 | 0000_0020 = 0000_0020
3403ff00 // $3 = 0000_0000 | 0000_ff00 = 0000_ff00
3404ffff // $4 = 0000_0000 | 0000_ffff = 0000_ffff
34210020 // $1 = 0000_1100 | 0000_0020 = 0000_1120
34214400 // $1 = 0000_1120 | 0000_4400 = 0000_5520
34210044 // $1 = 0000_5520 | 0000_0040 = 0000_5564

------------------------------------------------------------------------------------------------------------------------------------------------------

遇到的问题以及解决

1. 仿真波形输错_1

自己动手写CPU——单周期ORI指令的实现_第6张图片

  • 这里的reg1在执行完第五个周期时变为不定值是什么原因?

修改所有错误后,reg1_i等信号在第五个周期变为不定值都得到纠正

  • 有部分仿真波形从始至终都是蓝色直线?

在openmips.v中的实例化中端口对应没有匹配好,已修改,蓝线消失
ex ex0 (
.wreg_i(id_wreg),
// 错误代码:.wreg_i(ex_wreg),
);

2. 仿真波形出错_2

自己动手写CPU——单周期ORI指令的实现_第7张图片

  • sim:/OpenMIPS_min_SOPC_tb/OpenMIPS_min_SOPC0/openmips0/wb_wdata 和sim:/OpenMIPS_min_SOPC_tb/OpenMIPS_min_SOPC0/openmips0/regfile0/wdata 两条仿真波形始终都是红色直线?

对于没有初始化的信号,输入信号为高阻态蓝线(Z),输出信号为不定态红线(X)
regfile.v模块中的信号输入输出没有定义好,已修改,红线消失
module regfile (
input reg ['RegBus] wdata, // 要写入的数据
// 错误代码:output reg ['RegBus] wdata,
);

3. 顶层模块段端口定义

两个输出信号rom_addr_o、rom_ce_o需要定义成wire。定义成reg会报错,编译可通过,仿真不通过。代码已修改
自己动手写CPU——单周期ORI指令的实现_第8张图片

4. 解决取操作数不准确

指令5开始,rdata1本应该从寄存器$1读出上次写入的数据0x0000_1100,因为读写地址都相同,同时满足regfile.v中的读端口1读数据的结果值,实际读出指令5的执行结果0x0000_1120(0x0000_0000 ori ox0000_0020)
流水添加的是数据前推端口,没有这种困扰

解决方法1:
regfile.v读端口1模块使用if-case分支,顺序执行,交换分支顺序,优先判断是否从寄存器直接取到源操作数。当读写地址相同时,两个else都满足,但实际我们想要的是从寄存器中读出来的数据,为了防止被"被短路",需要交换顺序,先判断是否从寄存器取数
自己动手写CPU——单周期ORI指令的实现_第9张图片

解决方法2:
在EX模块新添加寄存器模块reg [`RegBus] result;连线到regfile.v模块
若想实现,必须添加在指定位置。
(太乱了,实在看不懂为什么都是哪些值,只能保证正确的值存在result中,但其他信号还会有取错的,我投降)


2021/05/09
再回来看看读取操作数不正确的原因,方法1看不懂,方法2当时跑通测试,但是没有写笔记…
救命,以后我一定好好写笔记,尽量不省略
我还写的没有这种困扰…现在都不知道是啥困扰了,还别说解决

.
.
.
会了
这里是单周期

// *************************第三阶段:读端口1的读操作******************************
  always @ (*) begin
    if (rst == `RstEnable) begin
      rdata1  <= `ZeroWord;     // 复位信号有效,输出0
    end else if (raddr1 == `RegNumLog2'h0) begin    // 读目的寄存器为$0,输出0
      rdata1  <= `ZeroWord;
    end else if (re1 == `ReadEnable) begin    		// 输出要读取的目的寄存器地址对应的值
      rdata1  <= regs[raddr1];
    end else if ((raddr1 == waddr) && (we == `WriteEnable) && (re1 == `ReadEnable)) begin    // 读目的寄存器是写目的寄存器&&写使能&&读使能,输出写入的值
      rdata1  <= wdata;
    end else begin
      rdata1  <= `ZeroWord;
    end
  end
       
// *************************第四阶段:读端口2的读操作******************************
  always @ (*) begin
    if (rst == `RstEnable) begin
      rdata2  <= `ZeroWord;
    end else if (raddr2 == `RegNumLog2'h0) begin
      rdata2  <= `ZeroWord;
    end else if ((raddr2 == waddr) && (we == `WriteEnable) && (re2 == `ReadEnable)) begin
      rdata2  <= wdata;
    end else if (re2 == `ReadEnable) begin
      rdata2  <= regs[raddr2];
    end else begin
      rdata2  <= `ZeroWord;
    end
  end
endmodule

这是交换后的代码,只需要交换读端口1的操作。因为指令如果是寄存器和立即数进行运算,默认通过读端口1读取寄存器数据
因为是单周期,在一个节拍下就要进行取数、执行、回写,交换之后就能保证,读取的数据是之前写入寄存器的数据,而不是本条指令执行后的运算结果。

流水线我也忘了需不需要处理这个读数据错误

你可能感兴趣的:(单周期CPU设计)