Bypass in Rocket Core
Today we start with a simple part of Rocket Core: bypass.
Bypass in rocket core is faily simple, since the Rocket is an in-order pipelined core. The bypass module only detect bypass opportunites and bypass the correct values befor the execute stage.
If not bypass
Before we discuss about the bypass, we first review the data path without bypass.
Assuming there is no data hazard, the data in the registers will be loaded by
RocketCore.scala, two registers are read together as follows:
val id_rs = id_raddr.map(rf.read _)
Note: the id_raddr only contains id_raddr1 and id_raddr2, without id_raddr3.
id_rs is a sequence contains the values of the two source registersa. If there is no bypass in this instruction, the values will goto
ex_rs(2) after bypass. After that the value will be sent to ALU or DIV.
How to know whether there are bypass opportunites in the pipeline or not? The answer is “the stage will write a register”.
bypass_sources lists all possible bypass opportunites and the corresponding stages.
val bypass_sources = IndexedSeq( (Bool(true), UInt(0), UInt(0)), // treat reading x0 as a bypass (ex_reg_valid && ex_ctrl.wxd, ex_waddr, mem_reg_wdata), (mem_reg_valid && mem_ctrl.wxd && !mem_ctrl.mem, mem_waddr, wb_reg_wdata), (mem_reg_valid && mem_ctrl.wxd, mem_waddr, dcache_bypass_data)) val id_bypass_src = id_raddr.map(raddr => bypass_sources.map(s => s._1 && s._2 === raddr))
In each item,
- first argument(eg:
ex_reg_valid && ex_ctrl.wxd), represents whether the source is valid;
- second argument(eg:
ex_waddr), is the tag indicating the target register of this source;
- last argument(eg:
mem_reg_wdata), is the location of the value in the next cycle.
For example, the 2nd item means: when
ex_reg_valid && ex_ctrl.wxd, this source can be used in bypass, the register is indicated by
ex_waddr, and in the next cycyle, the value is in
The first item in
bypass_sources is quiet easy: Always(when true), the x0 can be bypassed as 0.
id_bypass_src, each read addr in id_raddr will decide which bypass source to use by comparing the
Now we know which register can be bypassed, the next aim is to mark it and pass the value into execute stage. Note: In the code, you need to read not only the beginning of the execute stage, but also code around
First, for each read addr,
do_bypass decide whether the register can do bypass,
bypass_src decide which value is used to do bypass. Both values are from
val do_bypass = id_bypass_src(i).reduce(_||_) val bypass_src = PriorityEncoder(id_bypass_src(i))
In addition, each origin value in
id_rs is devided into two parts:
- When do not bypass: correct values are
Cat(ex_reg_rs_msb, ex_reg_rs_lsb), which is consistent with it in
- When do bypass: correct values are selected by
bypass_mux, which is a list of the last arguments of each item in
bypass_sources. In this case
Finally, the correct values of the source register is in the
For example, you are adding an instruction reading registers and do not plan to use ALU or DIV, where are the correct values in the pipeline?
The answer is
ex_rs, not the
id_rs, since the
id_rs are the values in the RegFile, without considering the bypass.