【学习e200处理器日志】执行力是关键—执行操作

系列文章目录

第一篇 【学习e200处理器日志】万事开头难—取指操作-CSDN博客


文章目录


前言

        本系列是为了记录在学习《手把手教你设计CPU——RISC-V处理器》的过程中的一些新的以及思考,同时把一些内容进行了简化便于理解,如果小伙伴对处理器感兴趣的话务必拜读一下,能够让你对处理器架构有一个大体的了解。


一、译码操作

        取指后的流水线是“译码”。因指令信息编码在有限长度指令字(16 位或 32 位)中,需译码提取信息,常见信息包括:

        指令所需读取的操作数寄存器索引。例如指令ADD R1,R2,R3(将 R2 和 R3 的值相加,结果存入 R1)中,源寄存器索引是 R2 和 R3 的编号(如寄存器编号2和3)。

        需写回的寄存器索引。例如在ADD R1,R2,R3中,目标寄存器索引是 R1 的编号(如寄存器编号1)。

        指令类型及操作等其他信息 。如算术逻辑指令(ADD, SUB)、分支指令(BEQ, BNE)、加载/存储指令(LW, SW)等。

二、执行操作

        在经典五级流水线中,完成“译码”并从通用寄存器组读取操作数后的下一级流水线是“执行”。“执行”是将指令按具体操作类型发送给相应运算单元执行。常见运算单元有:算术逻辑运算单元(ALU)、整数乘除单元、浮点运算单元,还有特殊运算单元。

三、发射与派遣

3.1 发射派遣的概念        

        执行阶段需解决指令发射给运算单元执行及写回的顺序问题,涉及“派遣”和“发射”两个概念:

        派遣指的是将要执行的指令发送给不同的运算单元,但是如果遇到需要多周期运行的运算单元时,需要去进行排队,这就是“发射”的意义,从排队到开始执行这个阶段就叫做发射。

        根据每个周期发射指令个数,可分为“单发射”(每个周期只能发射一条指令)和“多发射”(每个周期能发射多条指令,如“双发射”“三发射”“四发射” )处理器。

3.2 发射派遣的类型

        这也衍生出很多流派,根据指令发射、执行、写回的先后次序来定义:顺序发射、顺序执行、顺序写回; 顺序发射、乱序执行、顺序写回; 顺序发射、乱序执行、乱序写回以及顺序派遣、乱序发射、乱序执行、乱序写回。

        很显然最后一种是最复杂的,其原理就是顺序将指令发送给不同的运算单元的等待队列,然后根据那一条指令先解决数据依赖性(也就是相关性,后面会说,反正就是符合要求)便可以先发送到执行单元进行执行,然后写会的时候也是谁先完成谁先写会。其他的顺序指的就是需要等待其他先输入的长周期指令运行完才执行下一步操作。

3.3 乱序操作原理

        不同处理器有不同的乱序写回实现方式:

        部分处理器配备重排序缓存(ROB):运算单元执行完后,结果先写回ROB,再由ROB按顺序写回通用寄存器组(Regfile),相当于一个中转站重新排队,这是典型乱序写回,性能好,但存在ROB面积大、数据腾挪两次导致动态功耗增加的问题。

        有的处理器使用统一物理寄存器组:动态管理逻辑寄存器组映射关系,运算单元执行完后结果乱序写回物理寄存器组,数据仅腾挪一次,更省电,但控制更复杂,相当于设计了一个名单,名单的顺序是写入的顺序,但是谁先完成谁先在名单上打勾,操作起来更为复杂 。

        部分处理器无ROB和统一物理寄存器组也支持乱序写回:运算单元执行完后,若与其他运算单元指令无数据相关性,可直接写回Regfile ,这种就不多说了。

四、冲突

        蜂鸟 E200 执行阶段的重要职能是维护并解决流水线冲突,包括资源冲突和数据冲突(如 WAR、RAW 等数据相关性)。资源冲突常出现在指令派给不同执行单元时,例如除法单元执行指令需数十个周期,后续除法指令需等前一指令完成释放该单元,否则会出现冲突。

        而数据冲突主要是由于数据相关性造成的,首先需要注意的概念就是处理器中有两种指令,一种是单周期指令,可以很快交付,一种是长指令,需要多周期才能交付。

        因此,在处理器运行的过程中,需要检查是否和之前派遣过但是没有交付的指令存在数据相关性,比如这条指令需要没交付长指令的运算结果。这种冲突有三种:

WAR(Write-After-Read)相关性

        在蜂鸟E200微架构中,指令按顺序派遣和按顺序写回。因此写入的操作肯定不会在派遣之前 ,所以不会产生WAR相关性导致的数据冲突。

RAW(Read-After-Write)相关性

        对于正在派遣且处于流水线第二级的指令,如果之前派遣的是长指令(需多个周期写回结果),正在派遣的指令则有可能产生与前序长指令的RAW相关性。

        比如指令A先对某个寄存器进行写操作,指令B后续要读取该寄存器的值,如果A是长指令还未完成写回,B就有可能产生RAW相关性冲突。

WAW(Write-After-Write)相关性

        同样对于处于流水线第二级正在派遣的指令,假设之前派遣的单周期执行指令(也处于流水线第二级写回)已经完成执行并写回Regfile,那么正在派遣的指令不会产生和前序单周期指令的WAW相关性造成的数据冲突。如果前一个指令是长指令,依然会引起该相关性。

        假设指令A和指令B都要对同一个寄存器进行写操作,若A先完成写回,B再进行写回就不会因为WAW相关性而产生数据冲突。

五、RISC-V的执行特点

        为了简化处理器结构,实现低功耗小面积,其硬件实现有以下几个特点:

        规整的指令编码格式,其主要表现在指令的索引等性质特点都在固定的位数,是的译码操作得到简化。

        优雅的16位指令,其每一个16位指令都有对应的32位指令,因此在执行的时候无需关注起是否是16位还是32位数。

        精简的指令个数,该指令集数目非常简洁,一共只有40多个,简化执行单元的复杂度。

        整数指令都是两操作数,这样可以简化操作数读取和数据相关性检测部分的硬件设计。

六、E200处理器执行的实现

        蜂鸟E200是两级流水线架构,其“译码”“执行”“交付”和“写回”功能均处于流水线的第二级 ,由EXU单元完成。

        蜂鸟E200的EXU单元的工作流程如下:首先将IFU通过IR寄存器发送给EXU的指令进行译码和派遣,然后通过译码出的操作数寄存器索引(Index)读取Regfile,同时维护指令的数据相关性,最后根据分析将指令派遣(Dispatch)给不同的运算单元执行。后续的操作还有指令交付以及将指令运算的结果写回Regfile。

4.1 译码Decode

        译码模块主要就是对IR寄存器中的指令进行译码,其代码主要是以组合逻辑构成,下面是其编译后的输出,包括该指令的操作数索引,结果寄存器索引,以及使用的立即数的值等信息。

        下面是其输入输出端口:

端口类型端口名称位宽描述
输入待解析数据i_instr输入待译码的指令
i_pc输入当前指令的程序计数器值
i_prdt_taken输入分支预测是否被采用的信号
i_misalgn输入取指时的地址未对齐信号
i_buserr输入取指时的总线错误信号,意味着
i_muldiv_b2b输入乘法/除法的连续操作信号
dbg_mode输入调试模式信号
解析数据dec_rs1x0输出指示源寄存器1是否为x0的信号
dec_rs2x0输出指示源寄存器2是否为x0的信号
dec_rs1en输出源寄存器1使能信号
dec_rs2en输出源寄存器2使能信号
dec_rdwen输出目的寄存器写使能信号
dec_rs1idx输出源寄存器1的索引
dec_rs2idx输出源寄存器2的索引
dec_rdidx输出目的寄存器的索引
dec_info输出译码后的信息总线
dec_imm输出译码后的立即数
dec_pc输出译码后的程序计数器值
错误信息dec_misalgn输出译码阶段检测到的地址未对齐信号
dec_buserr输出译码阶段检测到的总线错误信号
dec_ilegl输出非法指令信号
判断dec_mulhsu输出指示指令是否为有符号 - 无符号乘法高位指令
dec_mul输出指示指令是否为乘法指令
dec_div输出指示指令是否为有符号除法指令
dec_rem输出指示指令是否为有符号取余指令
dec_divu输出指示指令是否为无符号除法指令
dec_remu输出指示指令是否为无符号取余指令
dec_rv32输出指示指令是否为RV32指令集的指令
dec_bjp输出指示指令是否为分支或跳转指令
dec_jal输出指示指令是否为无条件跳转并链接指令
dec_jalr输出指示指令是否为跳转并链接寄存器指令
dec_bxx输出指示指令是否为条件分支指令
跳转dec_jalr_rs1idx输出跳转并链接寄存器指令中源寄存器1的索引
dec_bjp_imm输出分支或跳转指令的立即数
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,  //该指令的其他信息,将其打包成为一组
//宽信号,称之为信息总线(info bus)
output ['E203_XLEN-1:0'] dec_imm,  //该指令使用的立即数的值

        同时也用于生成后续模块的总线接口,下面是对BLP模块的实例。

//生成BJP单元所需的信息总线(Info Bus)。

wire bjp_op = dec_bjp | rv32_mret | (rv32_dret && (~rv32_dret_ilgl)) | rv3
2_fence fencei;

wire ['E203_DECINFO_BJP_WIDTH-1:0] bjp_info_bus;
assign bjp_info_bus['E203_DECINFO_GRP_BJP] = 'E203_DECINFO_GRP_BJP;
assign bjp_info_bus['E203_DECINFO_RV32] = rv32;
assign bjp_info_bus['E203_DECINFO_BJP_JUMP] = dec_jal | dec_jalr;
assign bjp_info_bus['E203_DECINFO_BJP_BPRDT] = i_prdt_taken;
assign bjp_info_bus['E203_DECINFO_BJP_BEQ] = rv32_beq | rv16_beqz;
assign bjp_info_bus['E203_DECINFO_BJP_BNE] = rv32_bne | rv16_bnez;

        最终在使用一个多路选择器将这些总线信号根据类型进行选择。

//以下逻辑是典型的5输入并行多路选择器(使用Verilog assign语法编码And-Or逻辑)

assign dec_info =
    ( {'E203_DECINFO_WIDTH{alu_op}} && {'E203_DECINFO_WIDTH-'E20
    3_DECINFO_ALU_WIDTH{1'b0}},alu_info_bus)
    || ( {'E203_DECINFO_WIDTH{amoldst_op}} && {'E203_DECINFO_WIDTH-'E20
    3_DECINFO_AGU_WIDTH{1'b0}},agu_info_bus)
    || ( {'E203_DECINFO_WIDTH{bip_op}} && {'E203_DECINFO_WIDTH-'E20
    3_DECINFO_BJP_WIDTH{1'b0}},bjp_info_bus)
    || ( {'E203_DECINFO_WIDTH{csr_op}} && {'E203_DECINFO_WIDTH-'E20
    3_DECINFO_CSR_WIDTH{1'b0}},csr_info_bus)
    || ( {'E203_DECINFO_WIDTH{muldiy_op}} && {'E203_DECINFO_WIDTH-'E20
    3_DECINFO_CSR_WIDTH{1'b0}},muldiy_info_bus)
;

        可以看到,译码这一模块有很高的复杂度,后续我会结合附录A的内容详细解释。

4.2 整数通用寄存器组

        RISC-V的整数指令都是单操作数或者两操作数指令,且蜂鸟E200属于单发射的微架构,因此Integer-Regfile模块只需要支持最多两个读端口,一个用于操作数1,另一个用于操作数2。同时,蜂鸟E200的写回策略是按顺序每次写回一条指令的微架构,因此Integer-Regfile模块只需要支持一个写端口。

        该模块的位置在整个系统的最后面,用于数据的写回操作,下面是该模块的系统框图:

        如图所示,Integer-Regfile的写端口逻辑将输入的结果寄存器索引和各自的寄存器号进行比较,产生写使能信号,被使能的通用寄存器即将写数据写入寄存器(x0由于是常数,因此无须写入)。

        Integer-Regfile的每个读端口都是一个纯粹的并行多路选择器,多路选择器的选择信号即读操作数的寄存器索引,这几个通用寄存器就是x0到x31,比如ADD x1,x2,x3表示的就是x1=x2+x3,这时候就需要读取x2和x3的数据,这时候将x2和x3的索引值指向多路选择器,读端口就可以顺利的读出数据了。

        该部分代码很简单,主要是加了锁存器的宏定义,使用锁存器和D触发器各有各的好处,这里需要注意一下:

module e203_exu_regfile(
  input  [`E203_RFIDX_WIDTH-1:0] read_src1_idx,
  input  [`E203_RFIDX_WIDTH-1:0] read_src2_idx,
  output [`E203_XLEN-1:0] read_src1_dat,
  output [`E203_XLEN-1:0] read_src2_dat,

  input  wbck_dest_wen,
  input  [`E203_RFIDX_WIDTH-1:0] wbck_dest_idx,
  input  [`E203_XLEN-1:0] wbck_dest_dat,

  output [`E203_XLEN-1:0] x1_r,

  input  test_mode,
  input  clk,
  input  rst_n
  );

  wire [`E203_XLEN-1:0] rf_r [`E203_RFREG_NUM-1:0];
  wire [`E203_RFREG_NUM-1:0] rf_wen;
  
  `ifdef E203_REGFILE_LATCH_BASED //{
  // Use DFF to buffer the write-port
  wire [`E203_XLEN-1:0] wbck_dest_dat_r;
  sirv_gnrl_dffl #(`E203_XLEN) wbck_dat_dffl (wbck_dest_wen, wbck_dest_dat, wbck_dest_dat_r, clk);
  wire [`E203_RFREG_NUM-1:0] clk_rf_ltch;
  `endif//}

  
  genvar i;
  generate //{
  
      for (i=0; i<`E203_RFREG_NUM; i=i+1) begin:regfile//{
  
        if(i==0) begin: rf0
            // x0 cannot be wrote since it is constant-zeros
            assign rf_wen[i] = 1'b0;
            assign rf_r[i] = `E203_XLEN'b0;
          `ifdef E203_REGFILE_LATCH_BASED //{
            assign clk_rf_ltch[i] = 1'b0;
          `endif//}
        end
        else begin: rfno0
            assign rf_wen[i] = wbck_dest_wen & (wbck_dest_idx == i) ;
          `ifdef E203_REGFILE_LATCH_BASED //{
            e203_clkgate u_e203_clkgate(
              .clk_in  (clk  ),
              .test_mode(test_mode),
              .clock_en(rf_wen[i]),
              .clk_out (clk_rf_ltch[i])
            );
                //from write-enable to clk_rf_ltch to rf_ltch
            sirv_gnrl_ltch #(`E203_XLEN) rf_ltch (clk_rf_ltch[i], wbck_dest_dat_r, rf_r[i]);
          `else//}{
            sirv_gnrl_dffl #(`E203_XLEN) rf_dffl (rf_wen[i], wbck_dest_dat, rf_r[i], clk);
          `endif//}
        end
  
      end//}
  endgenerate//}
  
  assign read_src1_dat = rf_r[read_src1_idx];
  assign read_src2_dat = rf_r[read_src2_idx];

4.3 CSR 寄存器

        RISC-V的架构中定义了控制和状态寄存器(CSR),用于配置或记录一些运行的状态。CSR寄存器是处理器核内部的寄存器,使用自己的地址编码空间,与存储器寻址的地址区间完全无关系。CSR寄存器的访问采用专用的CSR读写指令,包括CSRRW、CSRRS、CSRRC、CSRRWI、CSRRSI、CSRRCI指令,这个在后面的文章中会解答。

        下面是改模块的端口说明:

端口类型端口名称位宽描述
nonflush_cmt_ena输入非刷新提交使能信号
eai_xs_off输出可能是用于控制某个外部加速器(EAI)相关功能关闭的信号
CSR控制csr_ena输入CSR(控制和状态寄存器)访问使能信号,当该信号有效时,允许对CSR进行操作
csr_wr_en输入CSR写使能信号,用于指示当前操作是对CSR进行写操作
csr_rd_en输入CSR读使能信号,用于指示当前操作是对CSR进行读操作
csr_idx输入CSR索引,用于指定要访问的具体CSR寄存器
csr_access_ilgl输出CSR访问非法信号,当对CSR的访问不符合规则时,该信号有效
tm_stop输出可能用于控制跟踪模块(TM)停止工作的信号
core_cgstop输出核心时钟门控停止信号,用于控制核心的时钟门控
tcm_cgstop输出紧耦合内存(TCM)时钟门控停止信号,用于控制TCM的时钟门控
itcm_nohold输出指令紧耦合内存(ITCM)无保持信号,可能用于控制ITCM的保持状态
mdv_nob2b输出可能与某些数据验证或传输相关的无突发到突发(no burst - to - burst)信号
read_csr_dat输出从CSR中读取的数据
wbck_csr_dat输入写回到CSR的数据
core_mhartid输入核心的硬件线程ID,用于标识不同的硬件线程
中断信号ext_irq_r输入外部中断请求信号
sft_irq_r输入软件中断请求信号
tmr_irq_r输入定时器中断请求信号
status_mie_r输出状态寄存器中的全局中断使能位
机器模式mtie_r输出机器模式定时器中断使能位
msie_r输出机器模式软件中断使能位
meie_r输出机器模式外部中断使能位
wr_csr_nxt输出下一次要写入CSR的数据
调试wr_dcsr_ena输出写调试控制和状态寄存器(DCSR)使能信号
wr_dpc_ena输出写调试程序计数器(DPC)使能信号
wr_dscratch_ena输出写调试暂存寄存器(DSCRATCH)使能信号
dcsr_r输入调试控制和状态寄存器的当前值
dpc_r输入调试程序计数器的当前值
dscratch_r输入调试暂存寄存器的当前值
dbg_mode输入调试模式信号,当该信号有效时,系统进入调试模式
dbg_stopcycle输入调试停止周期信号,可能用于在调试模式下控制系统停止运行的周期
模式u_mode输出表示系统处于用户模式
s_mode输出表示系统处于监督模式
h_mode输出表示系统处于虚拟化模式
m_mode输出表示系统处于机器模式
提交cmt_badaddr输入提交的错误地址
cmt_badaddr_ena输入提交错误地址使能信号
cmt_epc输入提交的异常程序计数器值
cmt_epc_ena输入提交异常程序计数器使能信号
cmt_cause输入提交的异常原因
cmt_cause_ena输入提交异常原因使能信号
cmt_status_ena输入提交状态寄存器使能信号
cmt_instret_ena输入提交指令执行计数使能信号
cmt_mret_ena输入提交MRET(机器模式返回)使能信号
计数器csr_epc_r输出CSR中存储的异常程序计数器值
csr_dpc_r输出CSR中存储的调试程序计数器值
csr_mtvec_r输出CSR中存储的机器模式陷入向量基地址

        在每一个模块中(ALU等计算模块)都会产生CSR总线数据于CSR通信,以ALU为例,下面是有关ALU的接口代码:

input csr_ena, //来自于ALU的CSR读写使能信号
input csr_wr_en, //CSR写操作指示信号
input csr_rd_en, //CSR读操作指示信号
input [12 - 1:0] csr_idx, //CSR寄存器的地址索引
output ['E203_XLEN - 1:0] read_csr_dat, //读操作从CSR寄存器模块中读出的数据
input ['E203_XLEN - 1:0] wbck_csr_dat, //写操作写入CSR寄存器模块的数据

        同时,对CSR中的一些寄存器进行写入操作也是通过CSR总线进行配置,下面是以其中一个寄存器MTVEC,下面是对其进行配置的代码:

//0x305 MRW mtvec Machine trap-handler base address.
wire sel_mtvec = (csr_idx == 12'h305);
wire rd_mtvec = csr_rd_en & sel_mtvec;
`ifdef E203_SUPPORT_MTVEC //{
wire wr_mtvec = sel_mtvec & csr_wr_en;
wire mtvec_ena = (wr_mtvec & wbck_csr_wen);
wire [`E203_XLEN-1:0] mtvec_r;
wire [`E203_XLEN-1:0] mtvec_nxt = wbck_csr_dat;
sirv_gnrl_dfflr #(`E203_XLEN) mtvec_dfflr (mtvec_ena, mtvec_nxt, mtvec_r, clk, rst_n);
wire [`E203_XLEN-1:0] csr_mtvec = mtvec_r;
`else//}{
  // THe vector table base is a configurable parameter, so we dont support writeable to it
wire [`E203_XLEN-1:0] csr_mtvec = `E203_MTVEC_TRAP_BASE;
`endif//}
assign csr_mtvec_r = csr_mtvec;

        最后读操作输出的结果是由一个大型的多路选择器实现的,可以看到其一共有30个左右的寄存器,每一个寄存器都代表着一个状态或者信息的记录。

assign read_csr_dat = `E203_XLEN'b0 
               //| ({`E203_XLEN{rd_ustatus  }} & csr_ustatus  )
               | ({`E203_XLEN{rd_mstatus  }} & csr_mstatus  )
               | ({`E203_XLEN{rd_mie      }} & csr_mie      )
               | ({`E203_XLEN{rd_mtvec    }} & csr_mtvec    )
               | ({`E203_XLEN{rd_mepc     }} & csr_mepc     )
               | ({`E203_XLEN{rd_mscratch }} & csr_mscratch )
               | ({`E203_XLEN{rd_mcause   }} & csr_mcause   )
               | ({`E203_XLEN{rd_mbadaddr }} & csr_mbadaddr )
               | ({`E203_XLEN{rd_mip      }} & csr_mip      )
               | ({`E203_XLEN{rd_misa     }} & csr_misa      )
               | ({`E203_XLEN{rd_mvendorid}} & csr_mvendorid)
               | ({`E203_XLEN{rd_marchid  }} & csr_marchid  )
               | ({`E203_XLEN{rd_mimpid   }} & csr_mimpid   )
               | ({`E203_XLEN{rd_mhartid  }} & csr_mhartid  )
               | ({`E203_XLEN{rd_mcycle   }} & csr_mcycle   )
               | ({`E203_XLEN{rd_mcycleh  }} & csr_mcycleh  )
               | ({`E203_XLEN{rd_minstret }} & csr_minstret )
               | ({`E203_XLEN{rd_minstreth}} & csr_minstreth)
               | ({`E203_XLEN{rd_counterstop}} & csr_counterstop)// Self-defined
               | ({`E203_XLEN{rd_mcgstop}} & csr_mcgstop)// Self-defined
               | ({`E203_XLEN{rd_itcmnohold}} & csr_itcmnohold)// Self-defined
               | ({`E203_XLEN{rd_mdvnob2b}} & csr_mdvnob2b)// Self-defined
               | ({`E203_XLEN{rd_dcsr     }} & csr_dcsr    )
               | ({`E203_XLEN{rd_dpc      }} & csr_dpc     )
               | ({`E203_XLEN{rd_dscratch }} & csr_dscratch)
               ;

4.4 指令的发射派遣

        在E200处理器中,发射和派遣都叫做派遣。蜂鸟E200执行阶段的派遣机制特点如下:

        指令交付路径:所有指令都要派遣给ALU,并通过ALU与交付模块的接口进行交付。

        长指令处理:若是长指令,需通过ALU进一步发送至相应长指令运算单元。例如长指令类型的Load/Store指令,通过ALU的AGU子单元被进一步发送至LSU单元执行(LSU就是专门的长指令处理单元)。

        实际派遣位置:实际的派遣功能发生在ALU内部。因为E200的译码模块在译码时已根据执行运算单元进行分组,并译码出相应指示信号,所以能按照指示信号将指令派遣给相应运算单元 。

        该模块的输入包括两部分,一个是译码模块输出的数据,另一个便是其他模块的输出。输出部分也包括两个部分,一个是输入到ALU单元,另一个便是输入到OITF单元。

        这里我对该系统的输入输出信号做一个简单的描述,读者可以试着对里面的一些缩写理解一下,比如imm对应立即数,rs1表示操作数1等,这方便我们对后续的一些模块代码进行理解。

信号类型信号名称信号方向含义解释
Decodewfi_halt_exu_req输入这是一个请求信号,用于请求EXU(执行单元,Execution Unit)进入WFI(Wait For Interrupt,等待中断)的暂停状态。
wfi_halt_exu_ack输出这是对wfi_halt_exu_req请求的响应信号。
oitf_empty输入表示OITF为空的信号。
amo_wait输入ALU模块等待信号
disp_i_valid输入输入的握手信号
disp_i_ready输出输出的握手信号
disp_i_rs1x0输入表示指令源操作数1的寄存器索引是否为x0。
disp_i_rs2x0输入表示指令源操作数2的寄存器索引是否为x0。
disp_i_rs1en输入用于指示是否需要读取源操作数1。
disp_i_rs2en输入用于指示是否需要读取源操作数2。
disp_i_rs1idx输入表示源操作数1的寄存器索引。
disp_i_rs2idx输入表示源操作数2的寄存器索引。
disp_i_rs1输入表示源操作数1的值。
disp_i_rs2输入表示源操作数2的值。
disp_i_rdwen输入用于指示是否需要将指令运算的结果写回寄存器。
disp_i_rdidx输入表示写回结果的目标寄存器索引。
disp_i_info输入包含了指令的其他相关信息。作为信息总线(info bus)。
disp_i_imm输入表示指令中使用的立即数的值。
disp_i_pc输入表示当前指令的程序计数器(PC)值。
disp_i_misalgn输入用于指示数据访问时的对齐错误(misalignment)。
disp_i_buserr输入表示总线错误(bus error)。
disp_i_ilegl输入用于指示指令非法(illegal instruction)。
ALUdisp_o_alu_longpipe输入指示当前指令是否需使用ALU的长流水线资源
disp_o_alu_ready输入由ALU反馈,高电平时表示ALU准备好接收新数据
disp_o_alu_buserr输出表示总线错误,数据传输过程中出错时置高电平
disp_o_alu_ilegl输出指示指令非法,检测到非法指令时置高电平
disp_o_alu_misalgn输出指示数据访问时的对齐错误,地址未按预期对齐时置高电平
disp_o_alu_imm输出指令中使用的立即数的值,参与指令运算
disp_o_alu_info输出包含指令其他相关信息,作为信息总线辅助ALU执行指令
disp_o_alu_itag输出来源是oitf
disp_o_alu_pc输出当前指令的程序计数器值,用于如跳转指令目标地址计算等
disp_o_alu_rdidx输出写回结果的目标寄存器索引,ALU据此将结果写回相应寄存器
disp_o_alu_rdwen输出指示是否需要将运算结果写回寄存器,高电平表示执行写回操作
disp_o_alu_rs1输出源操作数1的值,作为指令运算的一个输入操作数发送给ALU
disp_o_alu_rs2输出源操作数2的值,作为指令运算的另一个输入操作数发送给ALU
disp_o_alu_valid输出表示派遣到ALU的数据有效,ALU可接收处理后续数据

        通过RTL视图可以看出来,该模块主要以assign线型连接为主,因此代码逻辑简单,如下所示:该部分的输入主要是译码模块的输出,因此可以说是译码模块与ALU模块相连,但为了代码完整性或者以后方便添加子模块,保留了派遣模块的主体。

  assign disp_o_alu_rs1   = disp_i_rs1_msked;
  assign disp_o_alu_rs2   = disp_i_rs2_msked;
  assign disp_o_alu_rdwen = disp_i_rdwen;
  assign disp_o_alu_rdidx = disp_i_rdidx;
  assign disp_o_alu_info  = disp_i_info;  
  
  assign disp_o_alu_imm  = disp_i_imm;
  assign disp_o_alu_pc   = disp_i_pc;
  assign disp_o_alu_itag = disp_oitf_ptr;
  assign disp_o_alu_misalgn= disp_i_misalgn;
  assign disp_o_alu_buserr = disp_i_buserr ;
  assign disp_o_alu_ilegl  = disp_i_ilegl  ;

        派遣模块中还会处理流水线冲突的问题,包括资源冲突和数据相关性造成的数据冲突。 派遣模块还会在某些特殊情况下将流水线的派遣阻塞 ,而阻塞流水线的原因便是数据冲突。

        可以看到,在disp_condition允许派遣的信号中使用了~dep,只有没有发生相关性冲突的时候才能派遣。

  wire raw_dep =  ((oitfrd_match_disprs1) |
                   (oitfrd_match_disprs2) |
                   (oitfrd_match_disprs3)); 

  wire waw_dep = (oitfrd_match_disprd); 

  wire dep = raw_dep | waw_dep;

  assign wfi_halt_exu_ack = oitf_empty & (~amo_wait);

  wire disp_condition = 
                ...
               & (~dep)   
                ...

4.5 OITF相关性检测

        为了避免冲突,需要进行相关性检测,这便是OITF模块存在的意义。

        OITF模块本质上来说就是一个先进先出的fifo。在流水线派遣点,每次派遣长指令会在 OITF 分配表项,存储其源操作数和结果寄存器索引;在写回点,按顺序写回长指令后会去除其在 OITF 的表项。保证长指令按顺序写回需借助 OITF 功能。OITF 存储已派遣但未写回的长指令信息。每条指令派遣时,会将自身源操作数和结果寄存器索引与 OITF 各表项比对,判断是否与未写回长指令产生 RAW、WAW 相关性。

        下面是改模块的端口信号说明:

端口类型端口名称位宽描述
派遣dis_ready输出派遣就绪信号,表明 OITF 是否准备好接收新指令
dis_ena输入派遣使能信号,控制是否允许新指令派遣到 OITF
dis_ptr输出派遣指针,指示新指令在 OITF 中的存储位置
disp_i_rs1en输入派遣指令源寄存器 1 有效信号
disp_i_rs2en输入派遣指令源寄存器 2 有效信号
disp_i_rs3en输入派遣指令源寄存器 3 有效信号
disp_i_rdwen输入派遣指令写使能信号
disp_i_rs1fpu输入派遣指令源寄存器 1 浮点寄存器标识信号
disp_i_rs2fpu输入派遣指令源寄存器 2 浮点寄存器标识信号
disp_i_rs3fpu输入派遣指令源寄存器 3 浮点寄存器标识信号
disp_i_rdfpu输入派遣指令目的寄存器浮点寄存器标识信号
disp_i_rs1idx输入派遣指令源寄存器 1 索引编号
disp_i_rs2idx输入派遣指令源寄存器 2 索引编号
disp_i_rs3idx输入派遣指令源寄存器 3 索引编号
disp_i_rdidx输入派遣指令目的寄存器索引编号
disp_i_pc输入派遣指令的程序计数器值
匹配oitfrd_match_disprs1输出派遣指令源寄存器 1 与 OITF 中目的寄存器匹配信号
oitfrd_match_disprs2输出派遣指令源寄存器 2 与 OITF 中目的寄存器匹配信号
oitfrd_match_disprs3输出派遣指令源寄存器 3 与 OITF 中目的寄存器匹配信号
oitfrd_match_disprd输出派遣指令目的寄存器与 OITF 中目的寄存器匹配信号
oitf_empty输出OITF 空标志信号
返回ret_ena输入返回使能信号,控制是否允许指令从 OITF 中退出
ret_ptr输出返回指针,指示可以从 OITF 中退出的指令位置
ret_rdidx输出返回的目的寄存器索引
ret_rdwen输出返回的写使能信号
ret_rdfpu输出返回的浮点寄存器标识信号
ret_pc输出返回的程序计数器值

        下面这段代码描述了该模块的FIFO特性,使用generate语块批量生成寄存器,并且使用计数器等方式记录fifo的地址等信息,在最下面将正在派遣的指令的源操作数以及结果操作数寄存器索引和各表项中的结果寄存器索引进行比较。

  genvar i;
  generate //{
      for (i=0; i<`E203_OITF_DEPTH; i=i+1) begin:oitf_entries//{
  
        assign vld_set[i] = alc_ptr_ena & (alc_ptr_r == i);
        assign vld_clr[i] = ret_ptr_ena & (ret_ptr_r == i);
        assign vld_ena[i] = vld_set[i] |   vld_clr[i];
        assign vld_nxt[i] = vld_set[i] | (~vld_clr[i]);
  
        sirv_gnrl_dfflr #(1) vld_dfflrs(vld_ena[i], vld_nxt[i], vld_r[i], clk, rst_n);
        //Payload only set, no need to clear
        sirv_gnrl_dffl #(`E203_RFIDX_WIDTH) rdidx_dfflrs(vld_set[i], disp_i_rdidx, rdidx_r[i], clk);
        sirv_gnrl_dffl #(`E203_PC_SIZE    ) pc_dfflrs   (vld_set[i], disp_i_pc   , pc_r[i]   , clk);
        sirv_gnrl_dffl #(1)                 rdwen_dfflrs(vld_set[i], disp_i_rdwen, rdwen_r[i], clk);
        sirv_gnrl_dffl #(1)                 rdfpu_dfflrs(vld_set[i], disp_i_rdfpu, rdfpu_r[i], clk);

        assign rd_match_rs1idx[i] = vld_r[i] & rdwen_r[i] & disp_i_rs1en & (rdfpu_r[i] == disp_i_rs1fpu) & (rdidx_r[i] == disp_i_rs1idx);
        assign rd_match_rs2idx[i] = vld_r[i] & rdwen_r[i] & disp_i_rs2en & (rdfpu_r[i] == disp_i_rs2fpu) & (rdidx_r[i] == disp_i_rs2idx);
        assign rd_match_rs3idx[i] = vld_r[i] & rdwen_r[i] & disp_i_rs3en & (rdfpu_r[i] == disp_i_rs3fpu) & (rdidx_r[i] == disp_i_rs3idx);
        assign rd_match_rdidx [i] = vld_r[i] & rdwen_r[i] & disp_i_rdwen & (rdfpu_r[i] == disp_i_rdfpu ) & (rdidx_r[i] == disp_i_rdidx );
  
      end//}
  endgenerate//}

        对应的在派遣模块中,如果发生了冲突,会阻塞流水线暂停运行,等待数据处理完毕后再进行读取,这一块的代码见派遣模块。

4.6 ALU模块

        蜂鸟 E200 ALU 单元包含多个功能子单元,各子单元功能如下:

  • 普通 ALU 运算:执行普通 ALU 指令(逻辑运算、加减法、移位指令)。

  • 访存地址生成:负责 Load、Store 和相关扩展指令的地址生成。

  • 分支预测解析:进行 Branch、Jump 指令的结果解析与执行。

  • CSR 读写控制:执行 CSR 指令。

  • 多周期乘除法器:执行乘法和除法指令。

4.6.1 普通 ALU 运算

        首先需要声明的是,所有的子模块代码里均没有加法器等运算操作,起都是通过调用另一个模块的加法器进行工作,极大程度减小了面积以及功耗。Regular ALU 模块由组合逻辑构成,本身无运算数据通路,主要逻辑是依据普通 ALU 指令类型向共享运算数据通路发起操作请求,并取回计算结果。

        下面是该模块的端口说明,可以看到主要是以共享请求为主,就是使用共享运算通路进行数据计算:

端口类型端口名称位宽描述
握手alu_i_valid输入输入握手有效信号,表明输入数据和控制信息有效
alu_i_ready输出输入握手就绪信号,指示单元准备好接收输入
alu_o_valid输出输出握手有效信号,表明输出数据和状态信息有效
alu_o_ready输入输出握手就绪信号,指示外部模块准备好接收输出
输入数据alu_i_rs1输入源寄存器 1 的数据
alu_i_rs2输入源寄存器 2 的数据
alu_i_imm输入立即数
alu_i_pc输入程序计数器的值
alu_i_info输入ALU 指令的解码信息
写回alu_o_wbck_wdat输出写回的数据
alu_o_wbck_err输出写回错误信号
alu_o_cmt_ecall输出系统调用提交信号
alu_o_cmt_ebreak输出断点提交信号
alu_o_cmt_wfi输出等待中断提交信号
共享请求alu_req_alu_add输出共享 ALU 数据通路加法运算请求
alu_req_alu_sub输出共享 ALU 数据通路减法运算请求
alu_req_alu_xor输出共享 ALU 数据通路异或运算请求
alu_req_alu_sll输出共享 ALU 数据通路逻辑左移运算请求
alu_req_alu_srl输出共享 ALU 数据通路逻辑右移运算请求
alu_req_alu_sra输出共享 ALU 数据通路算术右移运算请求
alu_req_alu_or输出共享 ALU 数据通路或运算请求
alu_req_alu_and输出共享 ALU 数据通路与运算请求
alu_req_alu_slt输出共享 ALU 数据通路有符号小于比较运算请求
alu_req_alu_sltu输出共享 ALU 数据通路无符号小于比较运算请求
alu_req_alu_lui输出共享 ALU 数据通路加载高位立即数运算请求
alu_req_alu_op1输出共享 ALU 数据通路的操作数 1
alu_req_alu_op2输出共享 ALU 数据通路的操作数 2
alu_req_alu_res输入共享 ALU 数据通路的运算结果

        使用何种运算的请求在info总线中定义,代码如下:

     // The NOP is encoded as ADDI, so need to uncheck it
  assign alu_req_alu_add  = alu_i_info [`E203_DECINFO_ALU_ADD ] & (~nop);
  assign alu_req_alu_sub  = alu_i_info [`E203_DECINFO_ALU_SUB ];
  assign alu_req_alu_xor  = alu_i_info [`E203_DECINFO_ALU_XOR ];
  assign alu_req_alu_sll  = alu_i_info [`E203_DECINFO_ALU_SLL ];
  assign alu_req_alu_srl  = alu_i_info [`E203_DECINFO_ALU_SRL ];
  assign alu_req_alu_sra  = alu_i_info [`E203_DECINFO_ALU_SRA ];
  assign alu_req_alu_or   = alu_i_info [`E203_DECINFO_ALU_OR  ];
  assign alu_req_alu_and  = alu_i_info [`E203_DECINFO_ALU_AND ];
  assign alu_req_alu_slt  = alu_i_info [`E203_DECINFO_ALU_SLT ];
  assign alu_req_alu_sltu = alu_i_info [`E203_DECINFO_ALU_SLTU];
  assign alu_req_alu_lui  = alu_i_info [`E203_DECINFO_ALU_LUI ];

4.6.2 AGU模块

        访存地址生成(AGU)模块主要负责 Load、Store 和“A”扩展指令的地址生成,以及“A”扩展指令的微操作拆分与执行。也就数据从寄存器中加载以及存储等操作,该模块需要与地址寄存器组进行联合,等待后续章节进行讲解。

4.6.3 分支预测解析模块

        该模块主要就是用于分支跳转部分,这个同样也在后续会讲解。

4.6.4 CSR读写控制

        CSR读写控制(CSR-CTRL)模块,主要负责CSR读写指令的执行,包括CSRRW、CSRRS、CSRRC、CSRRWI、CSRRSI以及CSRRCI指令。

        该模块的端口说明如下:

端口类型端口名称位宽描述
握手csr_i_valid输入输入握手有效信号,表明输入数据和控制信息有效
csr_i_ready输出输入握手就绪信号,指示模块准备好接收输入
csr_o_valid输出输出握手有效信号,表明输出数据和状态信息有效
csr_o_ready输入输出握手就绪信号,指示外部模块准备好接收输出
输入数据csr_i_rs1输入源寄存器 1 的数据
csr_i_info输入CSR 指令的解码信息
csr_i_rdwen输入CSR 写使能信号
csr_ena输出CSR 使能信号
csr_wr_en输出CSR 写使能信号
csr_rd_en输出CSR 读使能信号
csr_idx输出CSR 索引
csr_access_ilgl输入CSR 访问非法信号
需要读写的数read_csr_dat输入从 CSR 寄存器读取的数据
wbck_csr_dat输出要写回到 CSR 寄存器的数据
EAIcsr_sel_eai输出选择 EAI 的信号
eai_xs_off输入EAI 的关闭信号
eai_csr_valid输出EAI CSR 操作的握手有效信号
eai_csr_ready输入EAI CSR 操作的握手就绪信号
eai_csr_addr输出EAI CSR 操作的地址
eai_csr_wr输出EAI CSR 写使能信号
eai_csr_wdata输出要写入 EAI CSR 寄存器的数据
eai_csr_rdata输入从 EAI CSR 寄存器读取的数据
输出数据csr_o_wbck_wdat输出CSR 写回的数据
csr_o_wbck_err输出CSR 写回错误信号

        代码部分使用纯组合逻辑,基本就是解码,然后根据解码的结果控制写入或读出的数据以及相关变量,这里就不放代码了。

4.6.5 多周期乘除法

        上过学的都知道,无论是惩罚还是除法都可以使用多周期的加减迭代移位操作完成,这个也不例外。如果要完成一个乘法或者是除法操作,就要频繁调用共享加法器来实现操作。下面是乘除法的原理图示:

        MDV模块在乘除法运算上有如下特点:乘法操作采用基-4 Booth编码减少周期数,对无符号乘法进行一位符号扩展后按有符号数运算,需17个迭代周期;除法操作用普通加减交替法,同样对无符号数扩展后按有符号数运算,需33个迭代周期。因结果有比特精度问题,还可能需额外1个周期判断余数矫正、1个周期进行商和余数矫正,最多共36个周期。

        下面是该模块的端口说明:

端口类型端口名称位宽描述
握手信号muldiv_i_valid输入输入握手有效信号,表明输入数据和控制信息有效
muldiv_i_ready输出输入握手就绪信号,指示单元准备好接收输入
muldiv_o_valid输出输出握手有效信号,表明输出数据和状态信息有效
muldiv_o_ready输入输出握手就绪信号,指示外部模块准备好接收输出
输入数据muldiv_i_rs1输入源寄存器 1 的数据
muldiv_i_rs2输入源寄存器 2 的数据
muldiv_i_imm输入立即数
muldiv_i_info输入乘除法指令的解码信息
muldiv_i_itag输入指令标签
muldiv_i_longpipe输出长流水线标识信号
flush_pulse输入刷新脉冲信号
写回muldiv_o_wbck_wdat输出乘除法运算的写回数据
muldiv_o_wbck_err输出乘除法运算写回错误信号
共享请求muldiv_req_alu_op1输出共享 ALU 数据通路的操作数 1
muldiv_req_alu_op2输出共享 ALU 数据通路的操作数 2
muldiv_req_alu_add输出共享 ALU 数据通路的加法运算请求信号
muldiv_req_alu_sub输出共享 ALU 数据通路的减法运算请求信号
muldiv_req_alu_res输入共享 ALU 数据通路的运算结果
共享缓存区muldiv_sbf_0_ena输出共享缓冲区 0 的使能信号
muldiv_sbf_0_nxt输出共享缓冲区 0 的下一个值
muldiv_sbf_0_r输入从共享缓冲区 0 读取的数据
muldiv_sbf_1_ena输出共享缓冲区 1 的使能信号
muldiv_sbf_1_nxt输出共享缓冲区 1 的下一个值
muldiv_sbf_1_r输入从共享缓冲区 1 读取的数据

总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值