tinyriscv这个SoC工程的内核cpu部分,采用经典的三级流水线结构进行设计,即大家所熟知的:取值—>译码—>执行三级流水线。
另外,在最后一个章节中会上传额外添加详细注释的工程代码,完全开源,如有需要可自行下载。
上一篇博文中注释了取值模块,现在来介绍译码模块:
目录
0 RISC-V SoC注解系列文章目录
1. 译码模块的整体介绍
2. RISCV指令RV32I、RV32M介绍
3. 译码模块的注解
3.1 id.v(组合逻辑电路)
3.2 id_ex.v(时序逻辑电路)
零、RISC-V SoC软核笔记详解——前言
一、RISC-V SoC内核注解——取指
二、RISC-V SoC内核注解——译码
三、RISC-V SoC内核注解——执行
四、RISC-V SoC内核注解——除法(试商法)
五、RISC-V SoC内核注解——中断
六、RISC-V SoC内核注解——通用寄存器
七、RISC-V SoC内核注解——总线
八、RISC-V SoC外设注解——GPIO
九、RISC-V SoC外设注解——SPI接口
十、RISC-V SoC外设注解——timer定时器
十一、RISC-V SoC外设注解——UART模块(终篇)
RISC-V内核的译码部分:涉及到id.v、id_ex.v、ctrl.v、clint.v、csr_reg.v等模块,其中clint.v、csr_reg.v两个模块是关于中断处理的模块,在中断章节会详细注解。ctrl.v模块是指令跳转和流水线暂停的控制信号,在后续的章节中会单独介绍。
译码部分详细结构图:
这个RISCV软核,实现了RISCV基础指令集RV32I和扩展指令集RV32M(乘除法拓展指令)。关于这两个指令,在下文会一一标注出来。那具体是怎么实现指令的解析的呢?我们需要对照《RISC-V指令集手册》来进行注解。
《RISC-V指令集手册》的中文翻译版放在下边链接中了,需要的小伙伴可自行下载:
提取码:63bz
《RISC-V指令集手册》https://pan.baidu.com/s/1086s0N-BdVxUoZfYF5byRA首先对RV32I指令集进行简单的介绍,如果想了解更详细的内容请查看上述链接中的RISC-V指令集的中文参考手册《RISC-V-Reader-Chinese-v2p1》,第23页,第二章“RV32I:RISC-V基础整数指令集”:
RV32I是RISC-V基础整数指令集,它有六种基本指令格式,分别是:
这六种指令的基本格式如下:
RV32I所有指令的具体的指令布局,操作码,格式类型和名称的操作码映射如下图所示:
这个RISC-V SoC工程完全实现了RV32I指令,因此上述表中的所有指令都会在译码模块中找到相应的代码。下文中会根据代码一一对应上述的指令。
接下来对扩展指令集RV32M(乘除法拓展指令)进行简单的介绍(详细介绍在中文参考手册《RISC-V-Reader-Chinese-v2p1》第51页)。
扩展指令集RV32M是32位整数的乘除法拓展指令:
乘法(指令的详细格式在中文参考手册152页):
1.mul指令将操作数寄存器rsl与rs2中的32位整数相乘,其64位结果的低32位写回寄存器rd中。两个32位整数操作数相乘的结果等于64位,而两个32位操作数当作有符号数相乘所得的低32位和当作无符号数相乘所得的低32位是相同的,因此RISC-V架构仅定义了一条mul指令作为取低32位结果的乘法指令。
2.mulh指令将操作数寄存器rsl与rs2中的32位整数当作有符号数相乘,结果的高32位写回寄存器rd中。
3.mulhu指令将操作数寄存器rsl与rs2中的32位整数当作无符号数相乘,结果的高32位写回寄存器rd中。
4.mulhsu指令将操作数寄存器rsl与rs2中的32位整数相乘,其中rsl当作有符号数和rs2当作无符号数,将结果的高32位写回寄存器rd中。
主要功能为:1.根据指令内容,解析出当前具体是哪一条指令(比如add指令);2.根据具体的指令,确定当前指令涉及的寄存器。比如读寄存器是一个还是两个,是否需要写寄存器以及写哪一个寄存器;3.访存:访问通用寄存器,得到要读的寄存器的值(地址)[1]。
输入信号:
// from if_id
input wire[`InstBus] inst_i, // 指令内容 32位
input wire[`InstAddrBus] inst_addr_i, // 指令地址 32位
// from regs
input wire[`RegBus] reg1_rdata_i, // 通用寄存器1输入数据
input wire[`RegBus] reg2_rdata_i, // 通用寄存器2输入数据
// from csr reg
input wire[`RegBus] csr_rdata_i, // CSR寄存器输入数据
// from ex
input wire ex_jump_flag_i, // 跳转标志
//----------------------------以上输入----------以下输出---------------------------------
输出信号:
// to regs
output reg[`RegAddrBus ] reg1_raddr_o, // 读通用寄存器1地址
output reg[`RegAddrBus ] reg2_raddr_o, // 读通用寄存器2地址
// to csr
output reg[`MemAddrBus ] csr_raddr_o , // reg读CSR寄存器地址
// to ex
output reg[`MemAddrBus ] op1_o , //从指令中解析出来的操作数1
output reg[`MemAddrBus ] op2_o , //从指令中解析出来的操作数2
output reg[`MemAddrBus ] op1_jump_o , //从指令中解析出来的跳转地址1
output reg[`MemAddrBus ] op2_jump_o , //从指令中解析出来的跳转地址2
output reg[`InstBus ] inst_o, // 指令内容 与输入时内容相同
output reg[`InstAddrBus] inst_addr_o , // 指令地址 与输入时内容相同
output reg[`RegBus ] reg1_rdata_o, // 通用寄存器1数据 与输入时内容相同
output reg[`RegBus ] reg2_rdata_o, // 通用寄存器2数据 与输入时内容相同
output reg reg_we_o, // 写通用寄存器标志
output reg[`RegAddrBus ] reg_waddr_o, // 写通用寄存器地址
output reg csr_we_o, // 写CSR寄存器标志
output reg[`RegBus ] csr_rdata_o, // CSR寄存器数据 与输入时内容相同
output reg[`MemAddrBus ] csr_waddr_o // 写CSR寄存器地址
首先将32位的指令码解析为6个部分,如下所示:
//将一条32位的指令分为6个部分,顺序如下
//funct7、rs2、rs1、funct3、rd、opcode
wire[6:0] funct7 = inst_i[31:25]; //功能2,由funct7和funct3共同确定一条具体的指令
wire[4:0] rs2 = inst_i[24:20]; //源寄存器2、立即数
wire[4:0] rs1 = inst_i[19:15]; //源寄存器1、立即数
wire[2:0] funct3 = inst_i[14:12]; //功能1,由funct7和funct3共同确定一条具体的指令
wire[4:0] rd = inst_i[11:7 ]; //目标寄存器、立即数
wire[6:0] opcode = inst_i[6 :0 ]; //操作码,指出该指令需要完成操作的类型或性质;
接下来是组合逻辑实现译码操作,我们截取其中一部分内容进行注解,其余部分类似。
case (opcode)//先判断操作码
`INST_TYPE_I: begin //短立即数和访存load操作 立即数类型 I type
case (funct3)
//当多个条件选项下需要执行相同的语句时,多个条件选项可以用逗号分开,放在同一个语句块的候选项中。
`INST_ADDI, `INST_SLTI, `INST_SLTIU, `INST_XORI, `INST_ORI, `INST_ANDI, `INST_SLLI, `INST_SRI:
begin//寄存器的值和立即数的值进行各种逻辑运算
reg_we_o = `WriteEnable; // 写通用寄存器标志
reg_waddr_o = rd; //目标寄存器(写通用寄存器地址)
reg1_raddr_o = rs1; //源寄存器1的地址,去通用寄存器模块中取reg1_rdata_i数据。
reg2_raddr_o = `ZeroReg;
op1_o = reg1_rdata_i; //rs1地址从reg中取回的数据。
op2_o = {{20{inst_i[31]}}, inst_i[31:20]}; //指令中的立即数
end
default: begin
reg_we_o = `WriteDisable;
reg_waddr_o = `ZeroReg;
reg1_raddr_o = `ZeroReg;
reg2_raddr_o = `ZeroReg;
end
endcase
end
代码解析如下:
由define.v文件可以查找到,宏定义INST_TYPE_I(opcode)的值是7'b0010011,INST_ADDI(func3)的值是3'b000,如下代码所示:
// I type inst
`define INST_TYPE_I 7'b0010011
`define INST_ADDI 3'b000 //立即数+寄存器
`define INST_SLTI 3'b010 //寄存器<立即数->则为1
`define INST_SLTIU 3'b011 //寄存器比较立即数,比较时视为无符号数,如果小则写入1
`define INST_XORI 3'b100 //寄存器与立即数按位异或
`define INST_ORI 3'b110 //立即数|寄存器
`define INST_ANDI 3'b111 //立即数&寄存器
`define INST_SLLI 3'b001 //寄存器<<立即数
`define INST_SRI 3'b101 //寄存器>>立即数
而操作码opcode,对应指令码的低7位[6:0],funct3对应指令码的[14:12]位:
wire[2:0] funct3 = inst_i[14:12]; //功能1
wire[6:0] opcode = inst_i[6 :0 ]; //操作码
即,inst_i[6 :0 ] = 7'b0010011,inst_i[14:12] = 3'b000,对应查找上文图2.3,可以找到唯一对应的指令如下所示:
其余指令虽然格式可能会有多不同,按相同的方法都能找到相对应的指令描述。
主要功能:译码模块(id.v)的输出会送到id_ex模块的输入端,id_ex模块是一个时序电路,作用是将输入的信号打一拍后再输出到执行模块(ex.v)。通过例化gen_pipe_dff模块,如果该模块没有接收到复位或者暂停流水线信号hold_flag_in(来自Ctrl模块),则将输入的信号打一拍,输出至下一级。
输入信号:来自id模块
输出信号:输出到ex模块
input wire clk,
input wire rst,
input wire[`InstBus] inst_i, // 指令内容
input wire[`InstAddrBus] inst_addr_i, // 指令地址
input wire reg_we_i, // 写通用寄存器标志
input wire[`RegAddrBus] reg_waddr_i, // 写通用寄存器地址
input wire[`RegBus] reg1_rdata_i, // 通用寄存器1读数据
input wire[`RegBus] reg2_rdata_i, // 通用寄存器2读数据
input wire csr_we_i, // 写CSR寄存器标志
input wire[`MemAddrBus] csr_waddr_i, // 写CSR寄存器地址
input wire[`RegBus] csr_rdata_i, // CSR寄存器读数据
input wire[`MemAddrBus] op1_i,
input wire[`MemAddrBus] op2_i,
input wire[`MemAddrBus] op1_jump_i,
input wire[`MemAddrBus] op2_jump_i,
input wire[`Hold_Flag_Bus] hold_flag_i, // 流水线暂停标志
output wire[`MemAddrBus] op1_o,
output wire[`MemAddrBus] op2_o,
output wire[`MemAddrBus] op1_jump_o,
output wire[`MemAddrBus] op2_jump_o,
output wire[`InstBus] inst_o, // 指令内容
output wire[`InstAddrBus] inst_addr_o, // 指令地址
output wire reg_we_o, // 写通用寄存器标志
output wire[`RegAddrBus] reg_waddr_o, // 写通用寄存器地址
output wire[`RegBus] reg1_rdata_o, // 通用寄存器1读数据
output wire[`RegBus] reg2_rdata_o, // 通用寄存器2读数据
output wire csr_we_o, // 写CSR寄存器标志
output wire[`MemAddrBus] csr_waddr_o, // 写CSR寄存器地址
output wire[`RegBus] csr_rdata_o // CSR寄存器读数据