1.1 执行概述
1.1.1 指令译码
指令所包含的信息编码在有限长度的指令字中,信息如下:
- 指令所需要读取的操作数寄存器索引
- 指令需要写回的寄存器索引
- 指令的其他信息如指令类型、指令的操作信息等
顺便注意:并非所有的处理器流水线都会在译码阶段读取操作数。在目前众多高性能处理器中,普遍采用在每个运算单元前配置乱序发射队列的方式,待指令的相关性解除之后并从发射队列中发射出来时读取通用寄存器组,然后送给运算单元开始计算
1.1.2 指令执行
常见的运算单元有以下几种:
- 算术逻辑运算单元(ALU),主要负责普通逻辑运算、加减法运算、和移位运算等基本运算。
- 整数乘法单元,主要负责有符号或无符号数中整数的乘法
- 整数除法单元
- 浮点运算单元,常常分为不同的运算单元
1.1.3 流水线的冲突
除了根据指令的具体类型运算之外,指令执行阶段另外一个最重要的只能就是维护并解决流水线的冲突,包括资源冲突和数据冲突(WAR,WAW等)
1.1.4 指令的交付
1.1.5 指令发射、派遣、执行、写回的顺序
将指令发射给运算单元,由运算单元执行,然后写回的相对顺序,是执行阶段需要解决的重要问题。
- 派遣(dipatch):可以按顺序派遣,也可以乱序派遣。
- 发射(issue):可以按顺序发射,也可以乱序发射。
以上两个定义很容易被混淆,在简单的处理器中两者属于同一概念,都是指令经过译码之后被派发到不同的运算单元并执行的过程。
根据每个时钟周期一次能够发射的指令数,处理器可以分为单发射处理器和多发射处理器。根据各种顺序,处理器可以分为很多种流派:
- 顺序发射,顺序执行,顺序写回
如经典的5级流水线。这种方式的性能比较低,但是硬件实现最简单面积最小 - 顺序发射,乱序执行,顺序写回
执行不同指令所需的时钟周期不同,如除法指令往往要耗费几十个时钟周期,而最简单的逻辑运算仅需要一个时钟周期。乱序执行是指在指令的执行阶段由不同的运算单元同时执行不同的指令从而提高性能。但是在最终的写回阶段仍要要严格地按照顺序写回,因此很多时候运算单元需要等待其他的先写回,从而导致停滞。 - 乱序写回
有些处理器会配备重排序缓冲器(ROB),但是这种方案会占用过大的空间,并且会增加功耗。还有一些别的方法。 - 顺序派遣,乱序发射
存在于高性能超标量处理器
1.1.6 分支解析
主要是对于带条件的分支指令
在执行阶段需要使用ALU对指令进行条件判断(如大小)。计算结果与之前的预测结果进行比较如果结果不一致则代表之前的预测是错误的,需要进行流水线冲刷。需要放在流水线比较前端的位置。
1.2 蜂鸟E203处理器的执行实现
1.3 译码模块
译码模块完全由组合逻辑组成。其主要逻辑即根据RISC-V架构的指令编码规则进行译码,产生不同的指令类型信息、操作数寄存器索引等。相关源代码片段如下所示。
RISC-V指令标准参考
module e203_exu_decode(
//
// The IR stage to Decoder
input [`E203_INSTR_SIZE-1:0] i_instr, //指令
input [`E203_PC_SIZE-1:0] i_pc, //该指令的pc值
input i_prdt_taken, //预测为需要跳转
input i_misalgn, // The fetch misalign //产生取址非对齐异常
input i_buserr, // The fetch bus error //取址存储其访问错误标志
input i_muldiv_b2b, // The back2back case for mul/div //暂时不知道
input dbg_mode, //来自外部
//
// The Decoded Info-Bus
output dec_rs1x0, //该指令原操作数1的寄存器索引为x0
output dec_rs2x0, //该指令原操作数2的寄存器索引为x0
output dec_rs1en, //需要读取原操作数1
output dec_rs2en, //需要读取原操作数2
output dec_rdwen, //该指令需要写结果操作数
output [`E203_RFIDX_WIDTH-1:0] dec_rs1idx, //该指令原操作数1的寄存器索引
output [`E203_RFIDX_WIDTH-1:0] dec_rs2idx, //该指令原操作数2的寄存器索引
output [`E203_RFIDX_WIDTH-1:0] dec_rdidx, //该指令结果寄存器索引
output [`E203_DECINFO_WIDTH-1:0] dec_info, //该指令的其他信息
output [`E203_XLEN-1:0] dec_imm, //该指令使用的立即数值
output [`E203_PC_SIZE-1:0] dec_pc, //该指令的pc值
output dec_misalgn, //产生取址非对齐异常
output dec_buserr, //取址存储其访问错误标志
output dec_ilegl, //该指令是一个非法指令
`ifdef E203_HAS_NICE//{
//
//nice decode
input nice_xs_off, //接1‘b0
output dec_nice, //接1‘b0
output nice_cmt_off_ilgl_o, //接1‘b0
/
`endif//}
output dec_mulhsu, //指令是mulshu指令
output dec_mul ,//指令是乘法指令
output dec_div , //指令是除法指令
output dec_rem , //指令是取余指令
output dec_divu , //指令是无符号除法指令
output dec_remu , //指令是无符号取余指令
output dec_rv32, //指令是32位指令还是16位指令
output dec_bjp,//指令是普通指令还是分支指令(跳转指令)
output dec_jal, //指令是无条件直接跳转指令
output dec_jalr, //指令是无条件间接跳转指令
output dec_bxx,//指令是带条件直接跳转指令
output [`E203_RFIDX_WIDTH-1:0] dec_jalr_rs1idx,//无条件间接跳转指令rs1的索引 5bit
output [`E203_XLEN-1:0] dec_bjp_imm //分支指令中的立即数 32bit
);
对于32位指令的译码比较直接,因为RISC-V的32位指令较规整
wire [32-1:0] rv32_instr = i_instr;
wire [16-1:0] rv16_instr = i_instr[15:0];
wire [6:0] opcode = rv32_instr[6:0];
wire opcode_1_0_00 = (opcode[1:0] == 2'b00);
wire opcode_1_0_01 = (opcode[1:0] == 2'b01);
wire opcode_1_0_10 = (opcode[1:0] == 2'b10);
wire opcode_1_0_11 = (opcode[1:0] == 2'b11);
wire rv32 = (~(i_instr[4:2] == 3'b111)) & opcode_1_0_11; //该指令是一个32位指令 指令的234位不全是1且指令的最低两位全是1
wire [4:0] rv32_rd = rv32_instr[11:7]; //把32位指令码分段表示不同的含义
wire [2:0] rv32_func3 = rv32_instr[14:12];
wire [4:0] rv32_rs1 = rv32_instr[19:15];
wire [4:0] rv32_rs2 = rv32_instr[24:20];
wire [6:0] rv32_func7 = rv32_instr[31:25];
指令译码:
wire rv32_load = opcode_6_5_00 & opcode_4_2_000 & opcode_1_0_11; //opcode==0000011 属于I类load指令
wire rv32_store = opcode_6_5_01 & opcode_4_2_000 & opcode_1_0_11; //opcode==0100011 属于S类存储指令
wire rv32_madd = opcode_6_5_10 & opcode_4_2_000 & opcode_1_0_11;
wire rv32_branch = opcode_6_5_11 & opcode_4_2_000 & opcode_1_0_11; //opcode==1100011 属于32位分支指令
wire rv32_load_fp = opcode_6_5_00 & opcode_4_2_001 & opcode_1_0_11;
wire rv32_store_fp = opcode_6_5_01 & opcode_4_2_001 & opcode_1_0_11;
wire rv32_msub = opcode_6_5_10 & opcode_4_2_001 & opcode_1_0_11;
wire rv32_jalr = opcode_6_5_11 & opcode_4_2_001 & opcode_1_0_11; //opcode==1100111 属于I类无条件间接跳转指令
1.4 整数通用寄存器组
整数通用寄存器组(Inter Register File, IRF)主要用于实现RISC-V架构定义的整数通用寄存器组。整数指令最多两个操作数,而且蜂鸟是单发射,则只需要两个读端口,一个写端口。结构如下图所示。
写端口通过比较输入的结果寄存器索引和通用寄存器号来产生写使能信号。读端口是纯粹的并行多路选择器。选择信号就是读操作数的寄存器索引,放到专用的寄存器寄存来降低功耗。
E203_exu_regfile.v
module e203_exu_regfile(
input [`E203_RFIDX_WIDTH-1:0] read_src1_idx, // 从minidecode的指令源操作数rs1的索引 在读数据时候用
input [`E203_RFIDX_WIDTH-1:0] read_src2_idx, // 从minidecode的指令源操作数rs2的索引 在读数据时候用
output [`E203_XLEN-1:0] read_src1_dat, // 取出的源操作数的值
output [`E203_XLEN-1:0] read_src2_dat, // 取出的源操作数的值
input wbck_dest_wen, //写回操作的写使能,由wbck模块给出
input [`E203_RFIDX_WIDTH-1:0] wbck_dest_idx, // 写回操作的寄存器地址,由wbck模块给出
input [`E203_XLEN-1:0] wbck_dest_dat, // 写回操作的待写入数据,由wbck模块给出
output [`E203_XLEN-1:0] x1_r, // 由于x1常用于link寄存器用于函数的跳转返回,这里直接接出来的x1的值做加速,读x1不需要读端口,但是需要检查数据相关性
input test_mode,
input clk,
input rst_n
);
wire [`E203_XLEN-1:0] rf_r [`E203_RFREG_NUM-1:0]; //32个32位通用寄存器
wire [`E203_RFREG_NUM-1:0] rf_wen; //控制打开哪个寄存器进行写入数据
1.5 CSR(控制和状态寄存器)
RISC-V架构中国定义了一些控制和状态寄存器用于配置或者记录一些运行的状态。CSR是处理器核内部的寄存器,使用其自己的地址编码空间,与存储器寻址的地址空间完全没有关系。
CSR的访问采用专用的CSR读写指令。包括csrrw、csrrs、csrrc、csrrwi等
e203_exu_csr.v
就是用来实现E203处理器所支持的CSR功能。
端口定义代码如下所示
module e203_exu_csr(
////用于配置或记录一些运行状态////
input nonflush_cmt_ena, //这个信号只有输入,但啥也没干,来自commit
`ifdef E203_HAS_NICE
output nice_xs_off, //跟写处理器相关,接了0
`endif
input csr_ena, //来自alu的csr读写使能信号 来自alu的csrctrl
input csr_wr_en, //csr的写使能信号 来自alu的csrctrl
input csr_rd_en, //csr的读使能信号 来自alu的csrctrl
input [12-1:0] csr_idx,//csr寄存器的地址索引 来自alu的csrctrl
output csr_access_ilgl, //固定接1'b0
output tm_stop, //自定义的配置寄存器的配置信息相应位段的输出,停止timer计数指示信号
output core_cgstop, //停止cpu core逻辑的clk gating
output tcm_cgstop, //停止TCM clk gating
output itcm_nohold, //itcm 不hold数据的指示信号
output mdv_nob2b, //mul/div 不采用back2back特性的指示信号
1.6 指令发射派遣
表示指令经过译码且从寄存器组中读取操作数之后被派发到不同的运算单元并执行的过程
1.7 流水线冲突、长指令和OITF
资源冲突
比如将指令派遣给除法单元并进行计算,但是除法单元需要数十个时钟周期才可以完成此指令。后续的指令派遣就需要等待时防。因此采用了严谨的valid-ready握手接口。一旦出现资源冲突ready信号就会拉低从而无法完成握手。
假设指令需要派遣到AGU子单元并执行,那么agu_op为高,但是AGU在占用,那么她的agu_i_ready为低这样i_ready就为低。反馈给上游模块的话就无法完成握手。
assign i_ready = (agu_i_ready & agu_op)
`ifdef E203_SUPPORT_SHARE_MULDIV //{
| (mdv_i_ready & mdv_op)
`endif//E203_SUPPORT_SHARE_MULDIV}
| (alu_i_ready & alu_op)
| (ifu_excp_i_ready & ifu_excp_op)
| (bjp_i_ready & bjp_op)
| (csr_i_ready & csr_op)
`ifdef E203_HAS_NICE//{
| (nice_i_ready & nice_op)
`endif//}
数据冲突
E20将所有需要执行的指令分为两类
- 单周期执行的指令。
- 多周期执行的指令。
由于E203是按顺序派遣和顺序写回的架构,因此不会发生WAR相关性。
对于RAW。如果前序指令为多周期指令则会发生这种相关性。
对于WAW。同RAW
为了能够检测出与长指令的RAW和WAW相关性,蜂鸟E203设计了一个滞外指令追踪FIFO(OITF)模块。
1.8 ALU
一共分为5个部分
- 普通ALU模块,主要负责普通的ALU指令(逻辑运算、加减法和移位等指令)的执行。
- 地址生成单元(AGU)模块,主要负责load、Store和A拓展指令的地址生成。
- 分支预测(BJP)模块 ,主要负责跳转和分支指令的分析和执行。
- CSR读写控制模块,主要负责分支与跳转指令的结果分析和执行
- 多周期乘除法模块,对于有符号的乘法采用常用的Booth编码算法计算部分积,然后使用迭代的方法对部分积进行累加得到最终的乘积。;对于有符号的整数除法采用常用的加减交替法,然后使用迭代的方法进行计算。