取指、译码、执行、访存、回写

注意:个人学习笔记要点记录,后续会完善更改,参考《从零开始写RISC-V处理器》。

取指:

目前tinyriscv所有外设(包括rom和ram)、寄存器的读取都是与时钟无关的,或者说所有外设、寄存器的读取采用的是组合逻辑的方式。

tinyriscv并没有具体的取指模块和代码。PC寄存器模块的输出pc_o会连接到外设rom模块的地址输入,又由于rom的读取是组合逻辑,因此每一个时钟上升沿到来之前(时序是满足要求的),从rom输出的指令已经稳定在if_id模块的输入,当时钟上升沿到来时指令就会输出到id模块。

取到的指令和指令地址会输入到if_id模块(if_id.v),if_id模块是一个时序电路,作用是将输入的信号打一拍后再输出到译码(id.v)模块

译码:

译码(id)模块是一个纯组合逻辑电路,主要作用有以下几点:

1.根据指令内容,解析出当前具体是哪一条指令(比如add指令)。

2.根据具体的指令,确定当前指令涉及的寄存器。比如读寄存器是一个还是两个,是否需要写寄存器以及写哪一个寄存器。

3.访问通用寄存器,得到要读的寄存器的值。

译码模块的输入输出信号如下表所示:

序号信号名输入/输出位宽(bits)说明
1rst输入1复位信号
2inst_i输入32指令内容
3inst_addr_i输入32指令地址
4reg1_rdata_i输入32寄存器1输入数据
5reg2_rdata_i输入32寄存器2输入数据
6csr_rdata_i输入32CSR寄存器输入数据
7ex_jump_flag_i输入1跳转信号
8reg1_raddr_o输出5读寄存器1地址,即读哪一个通用寄存器
9reg2_raddr_o输出5读寄存器2地址,即读哪一个通用寄存器
10csr_raddr_o输出32读csr寄存器地址,即读哪一个CSR寄存器
11mem_req_o输出1向总线请求访问内存信号
12inst_o输出32指令内容
13inst_addr_o输出32指令地址
14reg1_rdata_o输出32通用寄存器1数据
15reg2_rdata_o输出32通用寄存器2数据
16reg_we_o输出1通用寄存器写使能
17reg_waddr_o输出5通用寄存器写地址,即写哪一个通用寄存器
18csr_we_o输出1CSR寄存器写使能
19csr_rdata_o输出32CSR寄存器读数据
20csr_waddr_o输出32CSR寄存器写地址,即写哪一个CSR寄存器

 可知,add指令被编码成6部分内容。通过第1、4、6这三部分可以唯一确定当前指令是否是add指令。知道是add指令之后,就可以知道add指令需要读两个通用寄存器(rs1和rs2)和写一个通用寄存器(rd)。下面看具体的代码:

case (opcode)//opcode对应第6部分
...
    `INST_TYPE_R_M: begin
        if ((funct7 == 7'b0000000) || (funct7 == 7'b0100000)) begin//funct7对应第1部分
            case (funct3)//funct3对应第4部分
//第1、4、6这三部分已经译码完毕,已经可以确定当前指令是add指令
                `INST_ADD_SUB, `INST_SLL, `INST_SLT, `INST_SLTU, `INST_XOR, `INST_SR, `INST_OR, `INST_AND: begin
                     reg_we_o = `WriteEnable;//设置写寄存器标志为1
                     reg_waddr_o = rd;//设置写寄存器地址为rd
                     reg1_raddr_o = rs1;//设置读寄存器地址为rs1
                     reg2_raddr_o = rs2;
                 end
...

译码模块的输出会送到id_ex模块(id_ex.v)的输入,id_ex模块是一个时序电路,作用是将输入的信号打一拍后再输出到执行模块(ex.v)。

执行:

执行(ex)模块是一个纯组合逻辑电路,主要作用有以下几点:

1.根据当前是什么指令执行对应的操作,比如add指令,则将寄存器1的值和寄存器2的值相加。

2.如果是内存加载指令,则读取对应地址的内存数据。

3.如果是跳转指令,则发出跳转信号。

执行模块的输入输出信号如下表所示:

序号信号名输入/输出位宽(bits)说明
1rst输入1复位信号
2inst_i输入32指令内容
3inst_addr_i输入32指令地址
4reg_we_i输入1寄存器写使能
5reg_waddr_i输入5通用寄存器写地址,即写哪一个通用寄存器
6reg1_rdata_i输入32通用寄存器1读数据
7reg2_rdata_i输入32通用寄存器2读数据
8csr_we_i输入1CSR寄存器写使能
9csr_waddr_i输入32CSR寄存器写地址,即写哪一个CSR寄存器
10csr_rdata_i输入32CSR寄存器读数据
11int_assert_i输入1中断信号
12int_addr_i输入32中断跳转地址,即中断发生后跳转到哪个地址
13mem_rdata_i输入32内存读数据
14div_ready_i输入1除法模块是否准备好信号,即是否可以进行除法运算
15div_result_i输入64除法结果
16div_busy_i输入1除法模块忙信号,即正在进行除法运算
17div_op_i输入3具体的除法运算,即DIV、DIVU、REM和REMU中的哪一种
18div_reg_waddr_i输入5除法运算完成后要写的通用寄存器地址
19mem_wdata_o输出32内存写数据
20mem_raddr_o输出32内存读地址
21mem_waddr_o输出32内存写地址
22mem_we_o输出1内存写使能
23mem_req_o输出1请求访问内存信号
24reg_wdata_o输出32通用寄存器写数据
25reg_we_o输出1通用寄存器写使能
26reg_waddr_o输出5通用寄存器写地址
27csr_wdata_o输出32CSR寄存器写数据
28csr_we_o输出1CSR寄存器写使能
29csr_waddr_o输出32CSR寄存器写地址,即写哪一个CSR寄存器
30div_start_o输出1开始除法运算
31div_dividend_o输出32除法运算中的被除数
32div_divisor_o输出32除法运算中的除数
33div_op_o输出3具体的除法运算,即DIV、DIVU、REM和REMU中的哪一种
34div_reg_waddr_o输出5除法运算完成后要写的通用寄存器地址
35hold_flag_o输出1暂停流水线信号
36jump_flag_o输出1跳转信号
37jump_addr_o输出32跳转地址

下面以add指令为例说明,add指令的作用就是将寄存器1的值和寄存器2的值相加,最后将结果写入目的寄存器。代码如下:

...
`INST_TYPE_R_M: begin
     if ((funct7 == 7'b0000000) || (funct7 == 7'b0100000)) begin
         case (funct3)//译码
             `INST_ADD_SUB: begin
                 jump_flag = `JumpDisable;
                 hold_flag = `HoldDisable;
                 jump_addr = `ZeroWord;
                 mem_wdata_o = `ZeroWord;
                 mem_raddr_o = `ZeroWord;
                 mem_waddr_o = `ZeroWord;
                 mem_we = `WriteDisable;
//当前指令不涉及到的操作(比如跳转、写内存等)需要将其置回默认值
                 if (inst_i[30] == 1'b0) begin
//指令编码中的第30位区分是add指令还是sub指令。0表示add指令,1表示sub指令
                     reg_wdata = reg1_rdata_i + reg2_rdata_i;//加法
                 end else begin
                     reg_wdata = reg1_rdata_i - reg2_rdata_i;//减法
                 end
        ...
     end
...

if和case情况要写全,避免产生锁存器。

下面以beq指令说明跳转指令的执行。beq指令的编码如下:

 beq指令的作用就是当寄存器1的值和寄存器2的值相等时发生跳转,跳转的目的地址为当前指令的地址加上符号扩展的imm的值。具体代码如下:

...
`INST_TYPE_B: begin
    case (funct3)//译码beq指令
        `INST_BEQ: begin
            hold_flag = `HoldDisable;
            mem_wdata_o = `ZeroWord;
            mem_raddr_o = `ZeroWord;
            mem_waddr_o = `ZeroWord;
            mem_we = `WriteDisable;
            reg_wdata = `ZeroWord;
//同add,没有涉及的信号置为默认值
            if (reg1_rdata_i == reg2_rdata_i) begin//判断两个寄存器的值是否相等
                jump_flag = `JumpEnable;//使能
                jump_addr = inst_addr_i + {{20{inst_i[31]}}, inst_i[7], inst_i[30:25], inst_i[11:8], 1'b0};
//计算出跳转的目的地址
            end else begin
                jump_flag = `JumpDisable;
                jump_addr = `ZeroWord;
//不发生跳转
            end
    ...
end
...

访存:

由于tinyriscv只有三级流水线,因此没有访存这个阶段,访存的操作放在了执行模块中。具体是这样的,在译码阶段如果识别出是内存访问指令(lb、lh、lw、lbu、lhu、sb、sh、sw),则向总线发出内存访问请求:

...
`INST_TYPE_L: begin
    case (funct3)
        `INST_LB, `INST_LH, `INST_LW, `INST_LBU, `INST_LHU: begin//译码
            reg1_raddr_o = rs1;//读取寄存器1
            reg2_raddr_o = `ZeroReg;//不需要读寄存器2
            reg_we_o = `WriteEnable;//写目的寄存器使能
            reg_waddr_o = rd;//写目的寄存器的地址
            mem_req = `RIB_REQ;//发出访问内存请求
         end
         default: begin
            reg1_raddr_o = `ZeroReg;
            reg2_raddr_o = `ZeroReg;
            reg_we_o = `WriteDisable;
            reg_waddr_o = `ZeroReg;
         end
    endcase
end
`INST_TYPE_S: begin
    case (funct3)
    	`INST_SB, `INST_SW, `INST_SH: begin//译码
        	reg1_raddr_o = rs1;//读取寄存器1
            reg2_raddr_o = rs2;//读取寄存器2
            reg_we_o = `WriteDisable;//不需要写目的寄存器
            reg_waddr_o = `ZeroReg;
            mem_req = `RIB_REQ;//发出访问内存请求
 	end
...

在译码阶段向总线发出内存访问请求后,在执行阶段就会得到对应的内存数据。

下面看执行阶段的内存加载操作,以lb指令为例,lb指令的作用是访问内存中的某一个字节:

...
`INST_TYPE_L: begin
	case (funct3)
		`INST_LB: begin//译码
        	jump_flag = `JumpDisable;
        	hold_flag = `HoldDisable;
        	jump_addr = `ZeroWord;
        	mem_wdata_o = `ZeroWord;
        	mem_waddr_o = `ZeroWord;
            mem_we = `WriteDisable;
//没有涉及的信号置为默认值
            mem_raddr_o = reg1_rdata_i + {{20{inst_i[31]}}, inst_i[31:20]};
//得到访存的地址
            case (mem_raddr_index)
//由于访问内存的地址必须是4字节对齐的,因此这里的mem_raddr_index的含义就是32位内存数据(4个字节)中的哪一个字节,2’b00表示第0个字节,即最低字节,2’b01表示第1个字节,2’b10表示第2个字节,2’b11表示第3个字节,即最高字节
            	2'b00: begin
                	reg_wdata = {{24{mem_rdata_i[7]}}, mem_rdata_i[7:0]};//写寄存器数据
               	end
                2'b01: begin
                	reg_wdata = {{24{mem_rdata_i[15]}}, mem_rdata_i[15:8]};
               	end
                2'b10: begin
                  	reg_wdata = {{24{mem_rdata_i[23]}}, mem_rdata_i[23:16]};
              	end
              	default: begin
                  	reg_wdata = {{24{mem_rdata_i[31]}}, mem_rdata_i[31:24]};
               	end
			endcase
		end
...

回写:

由于tinyriscv只有三级流水线,因此也没有回写(write back,或者说写回)这个阶段,在执行阶段结束后的下一个时钟上升沿就会把数据写回寄存器或者内存。

需要注意的是,在执行阶段,判断如果是内存存储指令(sb、sh、sw),则向总线发出访问内存请求。而对于内存加载(lb、lh、lw、lbu、lhu)指令是不需要的。因为内存存储指令既需要加载内存数据又需要往内存存储数据。

以sb指令为例:

...
`INST_TYPE_S: begin
	case (funct3)
    	`INST_SB: begin//译码sb指令
        	jump_flag = `JumpDisable;
            hold_flag = `HoldDisable;
            jump_addr = `ZeroWord;
            reg_wdata = `ZeroWord;
//没有涉及到的指令设置为默认值
            mem_we = `WriteEnable;//写内存使能
            mem_req = `RIB_REQ;//发出访问内存请求
            mem_waddr_o = reg1_rdata_i + {{20{inst_i[31]}}, inst_i[31:25], inst_i[11:7]};
            mem_raddr_o = reg1_rdata_i + {{20{inst_i[31]}}, inst_i[31:25], inst_i[11:7]};
//内存写地址和读地址,两个地址是一样的
            case (mem_waddr_index)//mem_waddr_index的含义就是写32位内存数据中的哪一个字节
            	2'b00: begin
                	mem_wdata_o = {mem_rdata_i[31:8], reg2_rdata_i[7:0]};
                end
                2'b01: begin
                   mem_wdata_o = {mem_rdata_i[31:16], reg2_rdata_i[7:0], mem_rdata_i[7:0]};
               	end
                2'b10: begin
                	mem_wdata_o = {mem_rdata_i[31:24], reg2_rdata_i[7:0], mem_rdata_i[15:0]};
                end
                default: begin
                	mem_wdata_o = {reg2_rdata_i[7:0], mem_rdata_i[23:0]};
                end
          	endcase
     	end
...

sb指令只改变读出来的32位内存数据中对应的字节,其他3个字节的数据保持不变,然后写回到内存中。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值