Rong晔大佬教程学习(4):寄存器堆和立即数扩展

发布时间 2023-12-14 10:29:13作者: 郭若晨是我的rbq

  在第一节的设计结构图中,我们可以看到,ctrl.v模块译码后,就可以得到我们实际运算所需要的数据,有两种:1.寄存器值,通过译码后得到的地址在寄存器堆中读出;2.立即数值,在译码后进行扩展得到的完整的立即数值

  首先来看寄存器堆,一共有32个寄存器:

   接口名称表示每个寄存器的功能,本章暂不涉及。特别的,x0寄存器始终为0(只读存储器,这在之后的代码中可以看出)

1.reg_file.v(寄存器堆)

`include "rvseed_defines.v"
//主要功能是定义了一个32*32的寄存器堆,实现了寄存器堆的读和写逻辑
module reg_file (
    input                            clk,
    input                            rst_n,

    input                            reg_wen,    // register write enable
    input      [`REG_ADDR_WIDTH-1:0] reg_waddr,  // register write address
    input      [`CPU_WIDTH-1:0]      reg_wdata,  // register write data
    
    input      [`REG_ADDR_WIDTH-1:0] reg1_raddr, // register 1 read address
    input      [`REG_ADDR_WIDTH-1:0] reg2_raddr, // register 2 read address
    output reg [`CPU_WIDTH-1:0]      reg1_rdata, // register 1 read data
    output reg [`CPU_WIDTH-1:0]      reg2_rdata  // register 2 read data
);

reg [`CPU_WIDTH-1:0] reg_f [0:`REG_DATA_DEPTH-1]; //深度固定为32,因为一共有32个寄存器,每一个寄存器为32位宽

// register write,寄存器写逻辑
always @(posedge clk or negedge rst_n) begin
    if (rst_n && reg_wen && (reg_waddr != `REG_ADDR_WIDTH'b0)) // x0 read only,不能是复位状态,写使能有效,写地址不能为0(反向说明x0寄存器只读)
        reg_f[reg_waddr] <= reg_wdata;  //将数据写入寄存器
end

// register 1 read,寄存器读逻辑
always @(*) begin
    if(reg1_raddr == `REG_ADDR_WIDTH'b0)
        reg1_rdata = `CPU_WIDTH'b0;
    else
        reg1_rdata = reg_f[reg1_raddr]; //只要给一个读地址,那么就会把数据读出,没有“开关”控制
end

// register 2 read,寄存器读逻辑
always @(*) begin
    if(reg2_raddr == `REG_ADDR_WIDTH'b0)
        reg2_rdata = `CPU_WIDTH'b0;
    else
        reg2_rdata = reg_f[reg2_raddr];
end

endmodule

  相应的解释说明以在注释中给出,这部分主要就是定义了一个寄存器堆,然后通过输入地址信号和控制信号可以实现寄存器值的读出,特别注意的是,输入的控制信号和地址信号均由上一章ctrl.v模块给出,也就是译码的结果。

 

  立即数扩展模块主要实现立即数的补全(将立即数扩展为32位),这里我们直接上代码:

2.imm_gen.v(立即数扩展模块)

`include "rvseed_defines.v"
//立即数扩展模块
module imm_gen (
    input      [`CPU_WIDTH-1:0]        inst,       // instruction input,输入为我们的指令(有一个报错是inst的最低7位没有用到,那个是opcode,和这个程序没有关系)
    input      [`IMM_GEN_OP_WIDTH-1:0] imm_gen_op, // immediate extend opcode,输入立即数扩展操作码,决定立即数扩展为什么样子(由上一章的ctrl.v模块提供)

    output reg [`CPU_WIDTH-1:0]        imm         // immediate  
);

//扩展思路:将指令中的立即数摆在正确的位置上,拼接后进行高位符号位扩展,低位0扩展
always @(*) begin
    imm = `CPU_WIDTH'b0;
    case (imm_gen_op)
        `IMM_GEN_I: 
             imm = {{20{inst[31]}},inst[31:20]};
        `IMM_GEN_S: 
             imm = {{20{inst[31]}},inst[31:25],inst[11:7]};
        `IMM_GEN_B: 
             imm = {{20{inst[31]}},inst[7],inst[30:25],inst[11:8], 1'b0};
        `IMM_GEN_J: 
             imm = {{12{inst[31]}},inst[19:12],inst[20],inst[30:21], 1'b0};
        `IMM_GEN_U: 
             imm = {inst[31:12],12'b0};
    endcase
end

endmodule

  我们以I型指令为例:I型指令的立即数在指令的31-20位,同时也是立即数的最低12位,因此在扩展中截取inst[31:20]作为立即数的低12位,高20位则将inst[31]复制20次,这也就是高位符号位扩展的意思。

   至于低位0扩展,可以参考U型指令,U型指令的立即数在指令的高20位,同时也是立即数的高20位,所以低12位就全部补0即可。

 

P.S:本章的测试中up主定义了一个顶层模块rvseed.v,因为我没有做仿真所以我暂时不考虑了