蜂鸟E203-取指

417 阅读12分钟

取指子系统包括指令单元IFU和ITCM。

在汇编语言中,每一条要执行的指令都放在一个寄存器中,而这个将要执行指令的地址我们就需要放在PC(程序计数器)中。

IFU:

对取回的指令进行简单译码

简单的分支预测

生成取指的PC

根据PC的地址访问ITCM或BIU

IFU取出指令后,会将其放置于和EXU连接的指令寄存器IR,该指令的PC值也会放置在和EXU连接的PC寄存器中。EXU使用此IR进行后续操作。

针对快设计理念:

没有使用指令缓存,主要使用ITCM进行存储。

ITCM使用单周期访问的SRAM,即该处理器在一个周期内就可以从ITCM取回一条指令。因此假设指令存放在ITCM中。

对于某些特殊情况,指令需要从外部存储器中读取,例如系统上电后的引导程序可能需要从外部闪存中读取。此外,IFU需要通过BIU总线接口单元,使用系统存储接口访问外部的存储器,访问不可能在单个时钟周期完成。对于外部存储器的取值,还无法做到快。

针对连续不断设计理念:

为了能够连续不断地取指令,需要在每个时钟周期都能生成下一条待取指令的PC值,因此需要判别本指令是普通指令还是分支跳转指令。理论上需要对当前取回的指令进行译码。

IFU选择直接将取回的指令在同一时钟周期内进行部分译码简单译码,如果译码的信息指示当前指令为分支跳转指令,则IFU直接在同一时钟周期内进行分支预测。最后使用译码得到的信息和分支预测的信息生成下一条待取指令的PC。

由于在一个时钟周期内完成了指令读取,ICTM取指,部分译码,分支预测和生成下一条代取指令的PC等连贯操作。理论上可以做到连续不断。

取指令需要用到分支预测技术,

针对分支预测,分支预测采用静态预测。对于向后跳转的条件分支指令预测为真的跳转。对于向前跳转的条件分支指令预测则不需要跳转。向后的跳转是指跳转的目标地址PC值比当前分支指令的PC值要小。

简单译码模块

用于对取回的指令进行译码。


  //////////////////////////////////////////////////////////////
  // The IR stage to Decoder
  input  [`E203_INSTR_SIZE-1:0] instr,  //对输入进行部分译码
  
  //////////////////////////////////////////////////////////////
  // The Decoded Info-Bus


  output dec_rs1en,    
  output dec_rs2en,    
  output [`E203_RFIDX_WIDTH-1:0] dec_rs1idx,    
  output [`E203_RFIDX_WIDTH-1:0] dec_rs2idx,    

  output dec_mulhsu,    
  output dec_mul   ,
  output dec_div   ,
  output dec_rem   ,
  output dec_divu  ,
  output dec_remu  ,

  output dec_rv32, //指示当前指令是16位还是32位

  output dec_bjp, //判断当前指令是普通指令还是分支跳转指令
  output dec_jal,   //jal
  output dec_jalr,   //jalr
  output dec_bxx,  //bxx指令
  output [`E203_RFIDX_WIDTH-1:0] dec_jalr_rs1idx,
  output [`E203_XLEN-1:0] dec_bjp_imm 

  );
  //此模块内部例化,调用一个完整的译码模块,但是将不相关的输入信号接零,不相关的输出信号悬空
  e203_exu_decode u_e203_exu_decode(

  .i_instr(instr),
  .i_pc(`E203_PC_SIZE'b0),
  .i_prdt_taken(1'b0), 
  .i_muldiv_b2b(1'b0), 

  .i_misalgn (1'b0),
  .i_buserr  (1'b0),

  .dbg_mode  (1'b0),

  .dec_misalgn(),
  .dec_buserr(),
  .dec_ilegl(),

  .dec_rs1x0(),
  .dec_rs2x0(),
  .dec_rs1en(dec_rs1en),
  .dec_rs2en(dec_rs2en),
  .dec_rdwen(),
  .dec_rs1idx(dec_rs1idx),
  .dec_rs2idx(dec_rs2idx),
  .dec_rdidx(),
  .dec_info(),  
  .dec_imm(),
  .dec_pc(),

  
  .dec_mulhsu(dec_mulhsu),
  .dec_mul   (dec_mul   ),
  .dec_div   (dec_div   ),
  .dec_rem   (dec_rem   ),
  .dec_divu  (dec_divu  ),
  .dec_remu  (dec_remu  ),

  .dec_rv32(dec_rv32),
  .dec_bjp (dec_bjp ),
  .dec_jal (dec_jal ),
  .dec_jalr(dec_jalr),
  .dec_bxx (dec_bxx ),

  .dec_jalr_rs1idx(dec_jalr_rs1idx),
  .dec_bjp_imm    (dec_bjp_imm    )  
  );


endmodule

jal 无条件直接跳转,将下一条指令的PC值写入其结果寄存器

jalr 无条件间接跳转,立即数与另一寄存器索引的操作数相加。

带条件直接跳转 bxx

简单BPU

BPU模块对取回的指令进行简单译码后发现的分支跳转指令 进行分支预测,只采用了简单的静态预测。

所有PC计算共享一个加法器。

1 带条件的直接跳转指令

带条件的直接跳转指令bxx,使用静态预测,对于向后跳转,预测为需要跳转。对于其他跳转,预测为不需要跳转。BPU将其PC和立即数表示的偏移量相加,得到其目标地址。

2 无条件直接跳转指令

由于无条件直接跳转jal一定会跳转,则无须预测器跳转方法。将其PC核立即数表示的偏移量相加,得到其目标地址。

3 无条件间接跳转指令

无条件间接跳转指令jalr一定会跳转,因此无须预测其跳转方向。

jalr的跳转目标计算所需的基地址来自rs1索引的操作数(基地址寄存器),该基地址需要从通用寄存器组中读取,并且还可能和EXU正在执行的指令形成RAW数据相关性。 根据rs1的索引值采取不同的方案。

rs1的索引值是x0寄存器的值,则意味着使用常数0,则无需从通用寄存器中读取。

rs1的索引是X1寄存器的值,由于x1寄存器作为链接寄存器用于使函数返回跳转指令,将x1寄存器从EXU的通用寄存器组中直接取出。简单BPU不仅需要确保当前EXU指令没有写回x1寄存器,还需确保OIFT为空。

如果rs1的索引是除x0和x1之外的其他寄存器的值,不进行专门的加速。xn寄存器需要使用通用寄存器组的第一个读端口从通用寄存器组中读取出来,因此需要判定当前第一个读端口是否空闲且不存在资源冲突。为了防止正在EXU执行的指令需要写会xn寄存器而形成RAW相关性。 简单的BPU需要确保当前EXU中没有任何指令。

module e203_ifu_litebpu(

  // Current PC
  input  [`E203_PC_SIZE-1:0] pc,

  // The mini-decoded info 
  input  dec_jal,
  input  dec_jalr,
  input  dec_bxx,
  input  [`E203_XLEN-1:0] dec_bjp_imm,
  input  [`E203_RFIDX_WIDTH-1:0] dec_jalr_rs1idx,

  // The IR index and OITF status to be used for checking dependency
  input  oitf_empty,
  input  ir_empty,
  input  ir_rs1en,
  input  jalr_rs1idx_cam_irrdidx,
  
  // The add op to next-pc adder
  output bpu_wait,  
  output prdt_taken,  
  output [`E203_PC_SIZE-1:0] prdt_pc_add_op1,  
  output [`E203_PC_SIZE-1:0] prdt_pc_add_op2,

  input  dec_i_valid,

  // The RS1 to read regfile
  output bpu2rf_rs1_ena,
  input  ir_valid_clr,
  input  [`E203_XLEN-1:0] rf2bpu_x1,
  input  [`E203_XLEN-1:0] rf2bpu_rs1,

  input  clk,
  input  rst_n
  );


 // 如果立即数表示的偏移量为负数,符号位为1. 意味着向后跳转,预测为需要跳转。
  assign prdt_taken   = (dec_jal | dec_jalr | (dec_bxx & dec_bjp_imm[`E203_XLEN-1]));  
  
  
 // 判断rs1的索引是否是X0。
  wire dec_jalr_rs1x0 = (dec_jalr_rs1idx == `E203_RFIDX_WIDTH'd0);
  // 判断rs1的索引是否是X1
  wire dec_jalr_rs1x1 = (dec_jalr_rs1idx == `E203_RFIDX_WIDTH'd1);
  //xn
  wire dec_jalr_rs1xn = (~dec_jalr_rs1x0) & (~dec_jalr_rs1x1);


//判断x1寄存器是否可能与EXU中的指令存在潜在的RAW的相关性。 若OIFT不为空,意味着长指令正在执行,其结果可能写回x1寄存器。当然也有可能夕会的不是x1寄存器。   若IR中指令的写回目标寄存器的索引为x1寄存器的值,意味着raw相关性。
  wire jalr_rs1x1_dep = dec_i_valid & dec_jalr & dec_jalr_rs1x1 & ((~oitf_empty) | (jalr_rs1idx_cam_irrdidx));
  
  
  
  // xn寄存器是否存在EXU中的指令存在潜在的RAW相关性。
  //若OIFT不为空,意味着长指令正在执行,其结果可能写回xn寄存器。

  wire jalr_rs1xn_dep = dec_i_valid & dec_jalr & dec_jalr_rs1xn & ((~oitf_empty) | (~ir_empty));

                      
  wire jalr_rs1xn_dep_ir_clr = (jalr_rs1xn_dep & oitf_empty & (~ir_empty)) & (ir_valid_clr | (~ir_rs1en));

  wire rs1xn_rdrf_r;
  
  // 需要使用通用寄存器组的第一个读端口从通用寄存器组中读取xn寄存器的值。需要判断第一个读端口是否空闲,且不存在资源冲突。  如果没有资源冲突和数据冲突,则使用第一个读端口,将其使能信号置1.
  wire rs1xn_rdrf_set = (~rs1xn_rdrf_r) & dec_i_valid & dec_jalr & dec_jalr_rs1xn & ((~jalr_rs1xn_dep) | jalr_rs1xn_dep_ir_clr);
  wire rs1xn_rdrf_clr = rs1xn_rdrf_r;
  wire rs1xn_rdrf_ena = rs1xn_rdrf_set |   rs1xn_rdrf_clr;
  wire rs1xn_rdrf_nxt = rs1xn_rdrf_set | (~rs1xn_rdrf_clr);

  sirv_gnrl_dfflr #(1) rs1xn_rdrf_dfflrs(rs1xn_rdrf_ena, rs1xn_rdrf_nxt, rs1xn_rdrf_r, clk, rst_n);
 
 
 // 生成使用第一个读端口的使能信号,该信号将加载和IR位于同一级rs1索引的寄存器。从而读取通用寄存器
  assign bpu2rf_rs1_ena = rs1xn_rdrf_set;
  
  
  // 如果存在raw相关性,则将bpu_wait拉高,此信号将阻止ifu生成下一个PC,等待下一个相关性解除。
  
  // 如果x1寄存器和EXU中的指令没有数据相关性,则不会造成bpu_wait拉高。不会有任何性能损失。
  assign bpu_wait = jalr_rs1x1_dep | jalr_rs1xn_dep | rs1xn_rdrf_set;
  // PC计算需要用到加法器,为了节省面积,所有的PC共享一个加法器,此处生成分支预测器进行PC计算所需的操作数。
  //加法器一   使用它本身的PC。指令BXX
  assign prdt_pc_add_op1 = (dec_bxx | dec_jal) ? pc[`E203_PC_SIZE-1:0]
                         : (dec_jalr & dec_jalr_rs1x0) ? `E203_PC_SIZE'b0
                         : (dec_jalr & dec_jalr_rs1x1) ? rf2bpu_x1[`E203_PC_SIZE-1:0]
                         : rf2bpu_rs1[`E203_PC_SIZE-1:0];  
// 使用立即数表示的偏移量,加法器二
  assign prdt_pc_add_op2 = dec_bjp_imm[`E203_PC_SIZE-1:0];  

endmodule

PC生成

  • 程序计数器(PC寄存器)

程序计数器是用于存放下一条指令所在单元的地方。当执行一条指令时,首先需要根据PC中存放的指令地址,将指令由内存取到指令寄存器中,此过程称为“取指令”。与此同时,PC中的地址自动加1,或者由转移指针给出下一条指令的地址。

PC生成的逻辑用于产生下一待取指令的PC,PC根据情形需要不同的处理方式。

对于复位后的第一次取指,使用CPU-TOP层的输入信号pc_rtvec 指示的值作为第一次取指的PC值。用户可以通过在集成SOC顶层时,为此信号赋予不同的值来控制PC的复位默认值。

对于顺序取指的情形,根据当前指令是16位还是32位指令判断自增值。

对于分支指令,使用简单的BPU预测目标地址

对于来自EXU的流水线冲刷,则使用简单的BPU预测的目标地址。



  // If the instruciton is 32bits length, increament 4, otherwise 2
  wire [2:0] pc_incr_ofst = minidec_rv32 ? 3'd4 : 3'd2;

  wire [`E203_PC_SIZE-1:0] pc_nxt_pre;
  wire [`E203_PC_SIZE-1:0] pc_nxt;


 // 如果当前指令是分支跳转指令,则简单BPU预测需要跳转,则跳转取值。
  wire bjp_req = minidec_bjp & prdt_taken;

  wire ifetch_replay_req;
  
  
  //PC计算需要用到加法器,为了节省面积,所有PC计算共享一个加法器。 此处选择加法器的输入
wire [`E203_PC_SIZE-1:0] pc_add_op1 = 
                            `ifndef E203_TIMING_BOOST//}
                               pipe_flush_req  ? pipe_flush_add_op1 :
                               dly_pipe_flush_req  ? pc_r :
                            `endif//}
                               ifetch_replay_req  ? pc_r :
                               bjp_req ? prdt_pc_add_op1    :
                               ifu_reset_req   ? pc_rtvec :
                                                 pc_r;

  wire [`E203_PC_SIZE-1:0] pc_add_op2 =  
                            `ifndef E203_TIMING_BOOST//}
                               pipe_flush_req  ? pipe_flush_add_op2 :
                               dly_pipe_flush_req  ? `E203_PC_SIZE'b0 :
                            `endif//}
                               ifetch_replay_req  ? `E203_PC_SIZE'b0 :
                               bjp_req ? prdt_pc_add_op2    :
                               ifu_reset_req   ? `E203_PC_SIZE'b0 :
                                                 pc_incr_ofst ;
  // 没有复位,没有刷新,指令不是分支跳转指令的情形下,顺序取指。
  assign ifu_req_seq = (~pipe_flush_req_real) & (~ifu_reset_req) & (~ifetch_replay_req) & (~bjp_req);
  
// 加法器计算下一条待取指令的PC初始值。
assign pc_nxt_pre = pc_add_op1 + pc_add_op2;

// 
  assign pc_nxt = 
              //如果EXU产生流水线冲刷,则使用EXU送过来的新PC值
               pipe_flush_req ? {pipe_flush_pc[`E203_PC_SIZE-1:1],1'b0} :
               // 否则,使用前面计算出的PC的值
               dly_pipe_flush_req ? {pc_r[`E203_PC_SIZE-1:1],1'b0} :
               {pc_nxt_pre[`E203_PC_SIZE-1:1],1'b0};
  `endif//}

//产生下一条待取指令的PC的值
sirv_gnrl_dfflr #(`E203_PC_SIZE) pc_dfflr (pc_ena, pc_nxt, pc_r, clk, rst_n);

访问ITCM和BIU

1 支持16位指令

32位和16位混合。 32位指令可能与32位地址不对齐。

IFU每次取指的固定宽度为32位,则每次试图取回32位的指令字。

如果访问的是ICTM,由于ICTM是由SRAM构成的,因此上一次访问SRAM之后,SRAM的输出值一直保持不变。由于ICTM的SRAM宽度为64位,因此其输出为一个与64位地址区间对齐的数据,在此称为一个通道。

如果顺序取出的32位指令且指令未对齐跨越了64位边界,那么会将SRAM当前输出的最高16位存入16位宽的剩余缓冲区中。并发起新的ICTM SRAM访问操作。

如果非顺序取指,分支跳转或者流水线冲刷,且地址未对齐跨越了64位边界。那么就需要连续发起两次ITCM读操作。两个时钟周期才能取回32位指令。



// 处理地址未对齐取指的主要状态机控制

localparam ICB_STATE_WIDTH  = 2;
  // State 0: The idle state, means there is no any oustanding ifetch request
  localparam ICB_STATE_IDLE = 2'd0;
  // State 1: Issued first request and wait response
  localparam ICB_STATE_1ST  = 2'd1;
  // State 2: Wait to issue second request 
  localparam ICB_STATE_WAIT2ND  = 2'd2;
  // State 3: Issued second request and wait response
  localparam ICB_STATE_2ND  = 2'd3;
  
  wire [ICB_STATE_WIDTH-1:0] icb_state_nxt;
  wire [ICB_STATE_WIDTH-1:0] icb_state_r;
  wire icb_state_ena;
  wire [ICB_STATE_WIDTH-1:0] state_idle_nxt   ;
  wire [ICB_STATE_WIDTH-1:0] state_1st_nxt    ;
  wire [ICB_STATE_WIDTH-1:0] state_wait2nd_nxt;
  wire [ICB_STATE_WIDTH-1:0] state_2nd_nxt    ;
  wire state_idle_exit_ena     ;
  wire state_1st_exit_ena      ;
  wire state_wait2nd_exit_ena  ;
  wire state_2nd_exit_ena      ;

  // Define some common signals and reused later to save gatecounts
  wire icb_sta_is_idle    = (icb_state_r == ICB_STATE_IDLE   );
  wire icb_sta_is_1st     = (icb_state_r == ICB_STATE_1ST    );
  wire icb_sta_is_wait2nd = (icb_state_r == ICB_STATE_WAIT2ND);
  wire icb_sta_is_2nd     = (icb_state_r == ICB_STATE_2ND    );

      // **** If the current state is idle,
          // If a new request come, next state is ICB_STATE_1ST
  assign state_idle_exit_ena = icb_sta_is_idle & ifu_req_hsked;
  assign state_idle_nxt      = ICB_STATE_1ST;

      // **** If the current state is 1st,
          // If a response come, exit this state
  wire ifu_icb_rsp2leftover;
  assign state_1st_exit_ena  = icb_sta_is_1st & (
                ifu_icb_rsp2leftover ? ifu_icb_rsp_hsked : i_ifu_rsp_hsked);
  assign state_1st_nxt     = 
                (
              // If it need two requests but the ifetch request is not ready to be 
              //   accepted, then next state is ICB_STATE_WAIT2ND
                  (req_need_2uop_r & (~ifu_icb_cmd_ready)) ?  ICB_STATE_WAIT2ND
              // If it need two requests and the ifetch request is ready to be 
              //   accepted, then next state is ICB_STATE_2ND
                  : (req_need_2uop_r & (ifu_icb_cmd_ready)) ?  ICB_STATE_2ND 
              // If it need zero or one requests and new req handshaked, then 
              //   next state is ICB_STATE_1ST
              // If it need zero or one requests and no new req handshaked, then
              //   next state is ICB_STATE_IDLE
                  :  ifu_req_hsked  ?  ICB_STATE_1ST 
                                    : ICB_STATE_IDLE 
                ) ;

      // **** If the current state is wait-2nd,
              // If the ICB CMD is ready, then next state is ICB_STATE_2ND
  assign state_wait2nd_exit_ena = icb_sta_is_wait2nd &  ifu_icb_cmd_ready;
  assign state_wait2nd_nxt      = ICB_STATE_2ND;

      // **** If the current state is 2nd,
          // If a response come, exit this state
  assign state_2nd_exit_ena     =  icb_sta_is_2nd &  i_ifu_rsp_hsked;
  assign state_2nd_nxt          = 
                (
              // If meanwhile new req handshaked, then next state is ICB_STATE_1ST
                  ifu_req_hsked  ?  ICB_STATE_1ST : 
                      // otherwise, back to IDLE
                      ICB_STATE_IDLE
                );

  // The state will only toggle when each state is meeting the condition to exit:
  assign icb_state_ena = 
            state_idle_exit_ena | state_1st_exit_ena | state_wait2nd_exit_ena | state_2nd_exit_ena;

  // The next-state is onehot mux to select different entries
  
  
  
  // 
  
  assign icb_state_nxt = 
              ({ICB_STATE_WIDTH{state_idle_exit_ena   }} & state_idle_nxt   )
            | ({ICB_STATE_WIDTH{state_1st_exit_ena    }} & state_1st_nxt    )
            | ({ICB_STATE_WIDTH{state_wait2nd_exit_ena}} & state_wait2nd_nxt)
            | ({ICB_STATE_WIDTH{state_2nd_exit_ena    }} & state_2nd_nxt    )
              ;

ITCM也有一组输入ICB总线接口来自LSU的访问,所以ITCM所在的地址区间同样能够通过LSU被读写指令访问到用于存储数据

总线接口单元BIU是负责CPU对存储器和外设进行访问

  • IFU 有两个ICB接口,一个用于访问ITCM(数据宽度为64位),另一个用于访问BIU (数据宽度为32位)

2 ICB接口访问ITCM和BIU块

IFU有两个ICB接口,一个访问ICTM,另一个访问BIU。

IFU根据访问的地址区间进行判断,如果访问的地址落在ITCM区间,则通过ITCM的ICB接口对其进行访问,否则通过BIU的ICB对外部存储器进行访问。

ICTM

采用ITCM作为指令存储器。 IFU有专门访问ITCM的数据通道,同时ITCM也能够通过load、store指令访问。

E203主体由一块数据宽度为64位的单口SRAM组成。

BIU

如果取指令的地址不落在ITCM所在的区间,IFU则会通过BIU访问外部的存储器。