首先要保证存储器的读延时足够小,通常使用指令紧耦合存储器(ITCM)和指令缓存器(ICache)。
如果处理器在每一个时钟周期都能够取一个指令,就可以源源不断地为处理器提供后续指令流,而不会出现空闲的时钟。但是不论是从ITCM还是从指令cache中都是使用的SRAM,端口往往是固定长度的。
首先介绍一下RISC架构处理器的分支指令架构
不需要判断条件一定会发生的跳转指令,按照跳转的目标地址计算方式,可以分为以下两种。
需要注意的是,带条件的跳转指令只有等到执行阶段之后才能够解析出最终的跳转结果。这样的话会造成大量的流水线空泡周期,从而影响性能。为了提高性能一般会采用分支预测(branch prediction),需要解决两个方面的问题
使用预测的方向和地址及逆行取值称为预测取值。
根据指令进行执行叫做预测执行。
主要分为静态预测和动态预测
最简单的静态预测方法是总预测分支指令不会发生跳转。因此,取指指令总是顺序取分支指令的下一条指令。在执行阶段之后如果发现要进行跳转则会冲刷流水线,重新进行取指。为了弥补冲刷流水线造成的性能损失,很多早期的RISC架构采用了分支延迟槽。
两级预测器对每条分支指令而言,将有限个两位饱和计数器组织成模式历史表,使用该分支的跳转历史作为PHT的索引。分支历史又可以分为局部分支历史和全局分支历史。
1、分支目标缓存区(Branch Target Buffer0,BTB)技术是指使用容量有限的缓冲区保存最近执行过的分支指令的PC之,以及他们的跳转坐标。比较PC指判断是否跳转,并且直接跳转这个地址。
缺点:
2、返回地址栈(Return Address Stack, RAS)技术是指使用容量有限的硬件栈(先入后出)来存储函数调用的返回地址。
3、间接BTB技术是专门为间接跳转指令设计的,通过高级的索引方法进行匹配,结合了BTB和两级预测器,但是缺点是硬件开销较大,适用于高级处理器。
RISC-V的指令集十分规整,可以非常便捷地译码
相对于E压缩指令集混合使用而说的,假如使用RV32I的话最低两位为11可以忽略。
jal和jalr配合可以实现函数的调用和返回。
由于现代的高性能处理器的分支预测算法的精度很高所以放弃了delay slot,得大于失
RISC-V架构文档明确规定,编译器生成的代码应该尽量优化使得,向后跳转的概率大于向前跳转指令的概率
架构规定,如果使用jal指令且目标寄存器索引值rd等于x1寄存器的值或者x5寄存器的值则需要入栈,jalr通过rd和rs1的不同规定了入栈出栈
画圈为取指子系统,主要包括取指令单元IFU和ITCM。
主要包括以下功能
IFU取出指令后放到和EX相连的指令寄存器(Insteuction Register, IR)中,同时PC也会放到PC寄存器中。
取指令要“快”和“连续不断”
针对快:
针对连续不断:
此处只需要译出IFU所需的部分指令信息,这些信息包括此指令是属于普通指令还是分支跳转指令,分支跳转指令的类型和细节。
调用了完成的译码模块,只不过把不需要的输入指令,不需要的输出悬空。综合工具会优化掉。
/*
Copyright 2018-2020 Nuclei System Technology, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
//=====================================================================
// Designer : Bob Hu
//
// Description:
// The mini-decode module to decode the instruction in IFU
//
// ====================================================================
`include "e203_defines.v"
module e203_ifu_minidec(
//
// The IR stage to Decoder
input [`E203_INSTR_SIZE-1:0] instr,//取指逻辑取回的指令,作为输入等待译码[31:0]
//
// The Decoded Info-Bus
output dec_rs1en,//解码判断reg-source1是否有效,一条指令的源操作数1和2
output dec_rs2en,
output [`E203_RFIDX_WIDTH-1:0] dec_rs1idx,//也就是说cpu中的寄存器单独编址(即cpu内部总线,独立于系统总线)成一个文件
output [`E203_RFIDX_WIDTH-1:0] dec_rs2idx,//这里的index就是原操作数寄存器在cpu内部总线上的地址
output dec_mulhsu,// mul-high-signed-unsigned rs1有符号,rs2无符号,相乘结果(64bit)的高32bit放入rd
output dec_mul ,// mul rs1无符号,rs2无符号,(有符号无符号其实是一样的)相乘结果(64bit)的低32bit放入rd
output dec_div ,// div rs1有符号,rs2有符号
output dec_rem ,// 取 rs1有符号与rs2有符号数,取余数送到rd;
output dec_divu ,// 无符号取商送rd
output dec_remu ,// 无符号取余送d
output dec_rv32,//表示当前指令是rsic-v32位指令,还是16位指令
output dec_bjp,//表示当前指令是普通指令还是分支跳转指令
output dec_jal,//属于jal指令
output dec_jalr,//数据jalr指令
output dec_bxx,//属于BXX指令,(BEQ,BNE等带条件分支指令)
output [`E203_RFIDX_WIDTH-1:0] dec_jalr_rs1idx,//针对jalr指令,其首先要取出rs1操作数,然后与立即数相加才能得到值+4作为新的pc,为了取rs1,指令里带了rs1的地址(cpu内部index)
output [`E203_XLEN-1:0] dec_bjp_imm //针对bxx指令,如果条件满足的话,会把12bit立即数(有符号)×2然后与pc相加,作为新的pc;这里是译码出的指令的立即数
);
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(),
`ifdef E203_HAS_NICE//{
.dec_nice (),
.nice_xs_off(1'b0),
.nice_cmt_off_ilgl_o(),
`endif//}
.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
主要对取回的指令进行简单译码后发现的分支跳转指令进行简单的静态分支预测。
/*
Copyright 2018-2020 Nuclei System Technology, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
//=====================================================================
// Designer : Bob Hu
//
// Description:
// The Lite-BPU module to handle very simple branch predication at IFU
//
// ====================================================================
`include "e203_defines.v"
module e203_ifu_litebpu(
// Current PC
input [`E203_PC_SIZE-1:0] pc,//被译码的指令(也就是当前EXU正在执行的指令)对应的pc指针值
// The mini-decoded info
input dec_jal,//从mini-decode模块送过来的,具体的指令译码结果信息,这个信号指示是直接跳转
input dec_jalr,
input dec_bxx,
input [`E203_XLEN-1:0] dec_bjp_imm,//跳转指令的立即数
input [`E203_RFIDX_WIDTH-1:0] dec_jalr_rs1idx,//跳转指令的rs1源操作数的cpu内部地址
// The IR index and OITF status to be used for checking dependency
input oitf_empty,//oitf模块为空的标志,不为空表明有长指令正在运行,用于判断跳转指令跟之前的指令的相关性
input ir_empty,//当前IR寄存器为空
input ir_rs1en,//这个信号源自指令预取模块入口的dec2ifu_rs1en模块,表示的是当前正在执行的指令,其rs1操作数是否有效
input jalr_rs1idx_cam_irrdidx,//jalr指令的rs1的index 和 IR寄存器中rd的index比较,如果相等则为1,//这里是针对jalr指令如果其源操作数rs1和目的操作数rd是同一个寄存器,这样会导致分支预测这里,这条指令本身具有相关性??
// The add op to next-pc adder
output bpu_wait,
output prdt_taken, // 表示分支预测单元的预测结果是否进行跳转
output [`E203_PC_SIZE-1:0] prdt_pc_add_op1, //用于计算PC值的操作数
output [`E203_PC_SIZE-1:0] prdt_pc_add_op2,
input dec_i_valid,// 当前正在执行的指令经解码后发现指令是有效的
// The RS1 to read regfile
output bpu2rf_rs1_ena,// 生成用第一个读端口的使能信号,该信号将加载和IR寄存器位于同一级的rs1索引(index)寄存器,从而读取REGFile
input ir_valid_clr,// 表示当前ir中的内容被clear(// The ir valid is cleared when it is accepted by EXU stage *or* the flush happening )
input [`E203_XLEN-1:0] rf2bpu_x1,// 从regfile直接拉过来的同IR处于同一级的x1的内容
input [`E203_XLEN-1:0] rf2bpu_rs1, // 从regfile的读端口取出来的rs1的内容
input clk,
input rst_n
);
// BPU of E201 utilize very simple static branch prediction logics
// * JAL: The target address of JAL is calculated based on current PC value
// and offset, and JAL is unconditionally always jump
// * JALR with rs1 == x0: The target address of JALR is calculated based on
// x0+offset, and JALR is unconditionally always jump
// * JALR with rs1 = x1: The x1 register value is directly wired from regfile
// when the x1 have no dependency with ongoing instructions by checking
// two conditions:
// ** (1) The OTIF in EXU must be empty
// ** (2) The instruction in IR have no x1 as destination register
// * If there is dependency, then hold up IFU until the dependency is cleared
// * JALR with rs1 != x0 or x1: The target address of JALR need to be resolved
// at EXU stage, hence have to be forced halted, wait the EXU to be
// empty and then read the regfile to grab the value of xN.
// This will exert 1 cycle performance lost for JALR instruction
// * Bxxx: Conditional branch is always predicted as taken if it is backward
// jump, and not-taken if it is forward jump. The target address of JAL
// is calculated based on current PC value and offset
// //解码出来的指令是jal、jalr一定要跳,当为bxx类指令时若最高位符号位为1则说明是负数向后跳转。
assign prdt_taken = (dec_jal | dec_jalr | (dec_bxx & dec_bjp_imm[`E203_XLEN-1]));
// The JALR with rs1 == x1 have dependency or xN have dependency
wire dec_jalr_rs1x0 = (dec_jalr_rs1idx == `E203_RFIDX_WIDTH'd0);//判断rs1的索引是否为x0寄存器的值
wire dec_jalr_rs1x1 = (dec_jalr_rs1idx == `E203_RFIDX_WIDTH'd1);
wire dec_jalr_rs1xn = (~dec_jalr_rs1x0) & (~dec_jalr_rs1x1);
wire jalr_rs1x1_dep = dec_i_valid & dec_jalr & dec_jalr_rs1x1 & ((~oitf_empty) | (jalr_rs1idx_cam_irrdidx));
wire jalr_rs1xn_dep = dec_i_valid & dec_jalr & dec_jalr_rs1xn & ((~oitf_empty) | (~ir_empty));//既要保证没有长指令运行也要保证指令寄存器为空
// If only depend to IR stage (OITF is empty), then if IR is under clearing, or
// it does not use RS1 index, then we can also treat it as non-dependency
wire jalr_rs1xn_dep_ir_clr = (jalr_rs1xn_dep & oitf_empty & (~ir_empty)) & (ir_valid_clr | (~ir_rs1en));//xn相关性、没有长指令运行、指令寄存器非空。。。需要清零
wire rs1xn_rdrf_r;
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;//使得set一个周期高电平
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);
assign bpu2rf_rs1_ena = rs1xn_rdrf_set;
assign bpu_wait = jalr_rs1x1_dep | jalr_rs1xn_dep | rs1xn_rdrf_set;//如果存在RAW相关性,则将bpu_wait拉高,将阻止IFU生成写一个PC等待相关性解除,
assign prdt_pc_add_op1 = (dec_bxx | dec_jal) ? pc[`E203_PC_SIZE-1:0]//如果指令是带条件直接跳转指令bxx,便使用它本身的pc当作加法器操作数1
: (dec_jalr & dec_jalr_rs1x0) ? `E203_PC_SIZE'b0//如果是jalr,jalr的rs1是x0的时候op1为0
: (dec_jalr & dec_jalr_rs1x1) ? rf2bpu_x1[`E203_PC_SIZE-1:0]//直接读出来防止EXU写会x1形成RAW相关性
: rf2bpu_rs1[`E203_PC_SIZE-1:0];
assign prdt_pc_add_op2 = dec_bjp_imm[`E203_PC_SIZE-1:0]; //使用立即数表示的偏移量
endmodule
1.3.4 PC生成
复位后的第一次取指采用CPU-TOP输入的pc_rtvec的值确定,如果32位指令顺序取值则自加4,对于分支指令则使用简单的BPU预测的目标地址,如果来自EXU的流水线冲刷,则使用EXU送过来的新PC。
两种指令生成:
wire jalr_rs1xn_dep_ir_clr = (jalr_rs1xn_dep & oitf_empty & (~ir_empty)) & (ir_valid_clr | (~ir_rs1en));//xn相关性、没有长指令运行、指令寄存器非空。。。需要清零
wire rs1xn_rdrf_r;
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;//使得set一个周期高电平
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);
这种只是拿set去做了一个判断,所以set带上了输出信号这样可以直接拉低set。
还有一种由两种req控制的命令生成
assign pipe_flush_ack = 1'b1;
wire dly_flush_set;
wire dly_flush_clr;
wire dly_flush_ena;
wire dly_flush_nxt;
// The dly_flush will be set when There is a flush requst is coming, but the ifu is not ready to accept new fetch request
// * There is a flush requst is coming, but the ifu
// is not ready to accept new fetch request
wire dly_flush_r;
assign dly_flush_set = pipe_flush_req & (~ifu_req_hsked);
// The dly_flush_r valid is cleared when The delayed flush is issued
// * The delayed flush is issued
assign dly_flush_clr = dly_flush_r & ifu_req_hsked;
assign dly_flush_ena = dly_flush_set | dly_flush_clr;
assign dly_flush_nxt = dly_flush_set | (~dly_flush_clr);
sirv_gnrl_dfflr #(1) dly_flush_dfflr (dly_flush_ena, dly_flush_nxt, dly_flush_r, clk, rst_n);
wire dly_pipe_flush_req = dly_flush_r;
wire pipe_flush_req_real = pipe_flush_req | dly_pipe_flush_req;
这种最后生成了一种真正的请求信号,set不能直接拉低,这样的话会带来毛刺,所以没有带上输出信号取反,set置高后的时钟上升沿r跟随nxt也置高,当clr置高时,ena置高,上升沿r随nxt置低。之后其他信号都回归。保持住了req信号,等待IFU的请求信号。