§ 4.6.3. Branch Prediction¶
Fetch predicts a next PC while Execute remains responsible for the actual outcome. Prediction changes throughput, never the architectural meaning of an instruction.
| Component | Description | Structural | Behavioral |
|---|---|---|---|
RV32IPredictionCounter |
Stateless counter-update circuit | Yes | Yes |
RV32IBranchPredecode |
B/J instruction recognition and target calculation | Yes | Yes |
RV32IBranchPredictor |
Static policy or indexed FF table with next-PC selection | Yes | Yes |
RV32IPredictionEntry |
Internal row: one FF in one-bit mode, two in two-bit mode | Yes | Via predictor |
RV32IPredictionTable<N> |
Internal update decoder and read mux tree | Yes | Via predictor |
Policies and configuration¶
prediction |
Conditional branch prediction | Reset / training |
|---|---|---|
not-taken |
PC+4 | Default; no table |
taken |
Encoded target | No table |
backward-taken |
Target when the signed immediate is negative; otherwise PC+4 | No table |
one-bit |
Most recent outcome at this index | Reset not-taken; taken writes 11, untaken writes 00 |
two-bit |
Taken when counter is 10 or 11 |
Reset 00; saturating increment/decrement |
prediction_entries is a power of two from 1 to 256, default 16. The table index is (PC >> 2) & (N - 1). It has no address tags: different branches can intentionally alias. Static policies ignore table contents but still validate the supplied size.
All policies predict a direct JAL's encoded target. JALR uses PC+4 until Execute resolves its register-dependent target. There is no target cache, BTB, return-address stack or instruction-history predictor. Switching policy means constructing/reloading a configured core, not changing a pin while a program runs. Fidelity is an independent structural/behavioral choice.
PredictionCounter¶
IO pins¶
| Pin | Direction | Width | Description |
|---|---|---|---|
CURRENT |
Input | 2 | Current state |
TAKEN, ONE_BIT |
Input | 1 each | Observed outcome and update mode |
NEXT |
Output | 2 | Next state; no clock or storage in this block |
Behavior and C++ example¶
Two-bit states are strongly not-taken, weakly not-taken, weakly taken, strongly taken in numeric order. Saturation prevents wraparound. One-bit mode reports 00 or 11 through this two-bit interface, but each table row stores only one outcome bit.
inline unsigned next_counter(unsigned state,bool taken,bool one_bit) {
state&=3;
if(one_bit) return taken?3:0;
return unsigned(std::clamp(int(state)+(taken?1:-1),0,3));
}
Implementation¶
The structural implementation uses increment/decrement Boolean equations and direction/mode muxes. The behavioral implementation computes the same next-state function. Storage belongs to RV32IPredictionEntry, not this component.
Examples¶
All 16 recorded rows of RV32IPredictionCounterTest, in checkpoint order. Outputs are test expectations verified against the simulation. Time is the absolute settled checkpoint time in simulator units, not milliseconds or CPU cycles.
Default scenario profile: prediction-preview-preview / structural (c00b720a1b70c77a). Changing fidelity or the circuit can change these times.
| Checkpointzero-based number · timeline label | Timesimulator units | CURRENTInput · 2 bit |
ONE_BITInput · 1 bit |
TAKENInput · 1 bit |
NEXTExpected output · 2 bit |
|---|---|---|---|---|---|
#0mode-0-state-0-taken-0 | 6 | 0x0 |
0 |
0 |
0x0 |
#1mode-0-state-0-taken-1 | 11 | 0x0 |
0 |
1 |
0x1 |
#2mode-0-state-1-taken-0 | 16 | 0x1 |
0 |
0 |
0x0 |
#3mode-0-state-1-taken-1 | 21 | 0x1 |
0 |
1 |
0x2 |
#4mode-0-state-2-taken-0 | 29 | 0x2 |
0 |
0 |
0x1 |
#5mode-0-state-2-taken-1 | 34 | 0x2 |
0 |
1 |
0x3 |
#6mode-0-state-3-taken-0 | 42 | 0x3 |
0 |
0 |
0x2 |
#7mode-0-state-3-taken-1 | 47 | 0x3 |
0 |
1 |
0x3 |
#8mode-1-state-0-taken-0 | 53 | 0x0 |
1 |
0 |
0x0 |
#9mode-1-state-0-taken-1 | 57 | 0x0 |
1 |
1 |
0x3 |
#10mode-1-state-1-taken-0 | 62 | 0x1 |
1 |
0 |
0x0 |
#11mode-1-state-1-taken-1 | 66 | 0x1 |
1 |
1 |
0x3 |
#12mode-1-state-2-taken-0 | 73 | 0x2 |
1 |
0 |
0x0 |
#13mode-1-state-2-taken-1 | 77 | 0x2 |
1 |
1 |
0x3 |
#14mode-1-state-3-taken-0 | 84 | 0x3 |
1 |
0 |
0x0 |
#15mode-1-state-3-taken-1 | 88 | 0x3 |
1 |
1 |
0x3 |
Scroll within the table for all rows and pins. Buses use hexadecimal; values containing X/Z use MSB-first binary. Inputs show the declared values in effect at each checkpoint, including retained inputs. Sequential tests also replay preparation changes between checkpoints, such as lowering the clock. Use the matching checkpoint label in the simulation below.
BranchPredecode¶
IO pins¶
| Pin | Direction | Width | Description |
|---|---|---|---|
PC, INSTRUCTION |
Input | 32 each | Current fetch address and instruction bits |
CONDITIONAL, DIRECT_JUMP, BACKWARD |
Output | 1 each | Recognized branch, JAL, negative selected immediate |
TARGET, PC_PLUS_4 |
Output | 32 each | Candidate target and sequential address |
Behavior and C++ example¶
Recognize branch opcode 0x63 with funct3 other than reserved 010/011, and JAL opcode 0x6f. Reassemble and sign-extend the B/J immediate, then add it to PC modulo 2³². For other instructions, the selected immediate is zero, TARGET=PC, and BACKWARD=0. Full legality and target-alignment checks still happen later; predecode never raises a trap or authorizes a side effect.
struct Predecode { bool conditional,direct_jump,backward; uint32_t target,pc_plus_4; };
inline Predecode predecode(uint32_t pc,uint32_t instruction) {
const auto opcode=instruction&127,funct3=(instruction>>12)&7;
const bool branch=opcode==0x63&&funct3!=2&&funct3!=3,jump=opcode==0x6f;
uint32_t imm=0;
if(branch) {
imm=((instruction>>31)<<12)|(((instruction>>7)&1)<<11)|
(((instruction>>25)&63)<<5)|(((instruction>>8)&15)<<1);
if(imm&0x1000) imm|=0xffffe000u;
} else if(jump) {
imm=((instruction>>31)<<20)|(instruction&0xff000)|
(((instruction>>20)&1)<<11)|(((instruction>>21)&1023)<<1);
if(imm&0x100000) imm|=0xffe00000u;
}
return {branch,jump,bool(imm&0x80000000u),pc+imm,pc+4};
}
Implementation¶
Structural opcode matchers, field/sign-extension rewiring, muxes and adders implement the calculation. Behavioral predecode performs the same bit extraction and unsigned arithmetic. These are Fetch adders: prediction must run concurrently with Execute, so they cannot use Execute's busy ALU.
Examples¶
All 8 recorded rows of RV32IBranchPredecodeTest, in checkpoint order. Outputs are test expectations verified against the simulation. Time is the absolute settled checkpoint time in simulator units, not milliseconds or CPU cycles.
Default scenario profile: prediction-preview-preview / structural (c00b720a1b70c77a). Changing fidelity or the circuit can change these times.
| Checkpointzero-based number · timeline label | Timesimulator units | INSTRUCTIONInput · 32 bit |
PCInput · 32 bit |
BACKWARDExpected output · 1 bit |
CONDITIONALExpected output · 1 bit |
DIRECT_JUMPExpected output · 1 bit |
PC_PLUS_4Expected output · 32 bit |
TARGETExpected output · 32 bit |
|---|---|---|---|---|---|---|---|---|
#0forward-branch | 22 | 0x00208463 |
0x00000100 |
0 |
1 |
0 |
0x00000104 |
0x00000108 |
#1backward-branch | 48 | 0xFE208EE3 |
0x00000100 |
1 |
1 |
0 |
0x00000104 |
0x000000FC |
#2branch-to-fallthrough | 71 | 0x00000263 |
0x00000100 |
0 |
1 |
0 |
0x00000104 |
0x00000104 |
#3jal | 83 | 0x008000EF |
0x00000100 |
0 |
0 |
1 |
0x00000104 |
0x00000108 |
#4jalr-needs-register | 94 | 0x00008067 |
0x00000100 |
0 |
0 |
0 |
0x00000104 |
0x00000100 |
#5reserved-branch | 101 | 0x00002063 |
0x00000100 |
0 |
0 |
0 |
0x00000104 |
0x00000100 |
#6ordinary | 106 | 0x00108093 |
0x00000100 |
0 |
0 |
0 |
0x00000104 |
0x00000100 |
#7wraparound | 133 | 0x008000EF |
0xFFFFFFFC |
0 |
0 |
1 |
0x00000000 |
0x00000004 |
Scroll within the table for all rows and pins. Buses use hexadecimal; values containing X/Z use MSB-first binary. Inputs show the declared values in effect at each checkpoint, including retained inputs. Sequential tests also replay preparation changes between checkpoints, such as lowering the clock. Use the matching checkpoint label in the simulation below.
BranchPredictor¶
IO pins¶
| Pin | Direction | Width | Description |
|---|---|---|---|
CLK, RST |
Input | 1 each | Rising-edge training; reset clears table immediately |
PC, TARGET, PC_PLUS_4 |
Input | 32 each | Lookup PC and predecoded candidate addresses |
CONDITIONAL, DIRECT_JUMP, BACKWARD |
Input | 1 each | Predecode results |
UPDATE, UPDATE_TAKEN |
Input | 1 each | Qualified conditional-branch training and actual outcome |
UPDATE_PC |
Input | 32 | Branch's own PC, not current Fetch PC |
NEXT_PC |
Output | 32 | Predicted target or PC+4 |
TAKEN |
Output | 1 | Selected target; always true for DIRECT_JUMP |
COUNTER |
Output | 2 | Current indexed state; zero for static policies |
Behavior and C++ example¶
Lookup is combinational. Training updates one row on the rising edge when UPDATE=1 and reset is inactive. Reset clears every row to zero regardless of UPDATE. Low PC bits are ignored for indexing. Static policies never modify state; ordinary instructions always select PC+4 even if the indexed counter predicts taken.
In the code, call evaluate for state updates, then lookup for settled outputs. Capturing a prediction at the same edge as a colliding update uses the old table value; PredictionPath makes that ordering explicit.
enum class Policy { NotTaken,Taken,BackwardTaken,OneBit,TwoBit };
struct Predictor {
Policy policy;
std::vector<unsigned> table;
bool previous_clock=false;
Predictor(Policy mode,size_t entries):policy(mode) {
if(!entries||entries>256||(entries&(entries-1))) throw std::invalid_argument("table size");
table.assign(entries,0);
}
unsigned counter(uint32_t pc) const {return table[(pc>>2)&(table.size()-1)];}
struct Output {uint32_t next_pc;bool taken;unsigned counter;};
Output lookup(uint32_t pc,Predecode d) const {
const bool direction=policy==Policy::Taken ||
(policy==Policy::BackwardTaken&&d.backward) ||
((policy==Policy::OneBit||policy==Policy::TwoBit)&&counter(pc)>=2);
const bool taken=d.direct_jump||(d.conditional&&direction);
return {taken?d.target:d.pc_plus_4,taken,counter(pc)};
}
void evaluate(bool clk,bool reset,bool update,uint32_t update_pc,bool taken) {
if(reset) std::fill(table.begin(),table.end(),0);
else if(!previous_clock&&clk&&update&&(policy==Policy::OneBit||policy==Policy::TwoBit)) {
auto& state=table[(update_pc>>2)&(table.size()-1)];
state=next_counter(state,taken,policy==Policy::OneBit);
}
previous_clock=clk;
}
};
Implementation¶
Structural dynamic policies instantiate TABLE, with numbered ROW_n entries and update-address decoding. A one-bit row stores one FF and uses one read-selection tree; the public two-bit counter output duplicates that bit as 00 or 11. A two-bit row stores two FFs and includes the saturating-counter update network and two read trees. Static policies omit the table entirely. The behavioral implementation replaces the selected bank with a bounded array and follows the same indexing, edge/reset, saturation, and aliasing rules.
Examples¶
The embedded preview starts with a four-entry two-bit predictor. The registered contract test additionally covers all five policies at 1, 4 and 16 entries. Every preview checkpoint, including its time and number, appears below.
All 21 recorded rows of RV32IBranchPredictorTest, in checkpoint order. Outputs are test expectations verified against the simulation. Time is the absolute settled checkpoint time in simulator units, not milliseconds or CPU cycles.
Default scenario profile: prediction-preview-preview / structural (c00b720a1b70c77a). Changing fidelity or the circuit can change these times.
| Checkpointzero-based number · timeline label | Timesimulator units | BACKWARDInput · 1 bit |
CLKInput · 1 bit |
CONDITIONALInput · 1 bit |
DIRECT_JUMPInput · 1 bit |
PCInput · 32 bit |
PC_PLUS_4Input · 32 bit |
RSTInput · 1 bit |
TARGETInput · 32 bit |
UPDATEInput · 1 bit |
UPDATE_PCInput · 32 bit |
UPDATE_TAKENInput · 1 bit |
COUNTERExpected output · 2 bit |
NEXT_PCExpected output · 32 bit |
TAKENExpected output · 1 bit |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
#0reset | 15 | 0 |
0 |
1 |
0 |
0x00000000 |
0x00000004 |
1 |
0x00000064 |
0 |
0x00000000 |
0 |
0x0 |
0x00000004 |
0 |
#1train-0 | 34 | 0 |
1 |
1 |
0 |
0x00000000 |
0x00000004 |
0 |
0x00000064 |
1 |
0x00000000 |
1 |
0x1 |
0x00000004 |
0 |
#2train-1 | 51 | 0 |
1 |
1 |
0 |
0x00000000 |
0x00000004 |
0 |
0x00000064 |
1 |
0x00000000 |
1 |
0x2 |
0x00000064 |
1 |
#3train-2 | 63 | 0 |
1 |
1 |
0 |
0x00000000 |
0x00000004 |
0 |
0x00000064 |
1 |
0x00000000 |
1 |
0x3 |
0x00000064 |
1 |
#4train-3 | 69 | 0 |
1 |
1 |
0 |
0x00000000 |
0x00000004 |
0 |
0x00000064 |
1 |
0x00000000 |
1 |
0x3 |
0x00000064 |
1 |
#5train-4 | 89 | 0 |
1 |
1 |
0 |
0x00000000 |
0x00000004 |
0 |
0x00000064 |
1 |
0x00000000 |
0 |
0x2 |
0x00000064 |
1 |
#6train-5 | 106 | 0 |
1 |
1 |
0 |
0x00000000 |
0x00000004 |
0 |
0x00000064 |
1 |
0x00000000 |
0 |
0x1 |
0x00000004 |
0 |
#7train-6 | 118 | 0 |
1 |
1 |
0 |
0x00000000 |
0x00000004 |
0 |
0x00000064 |
1 |
0x00000000 |
0 |
0x0 |
0x00000004 |
0 |
#8train-7 | 124 | 0 |
1 |
1 |
0 |
0x00000000 |
0x00000004 |
0 |
0x00000064 |
1 |
0x00000000 |
0 |
0x0 |
0x00000004 |
0 |
#9train-8 | 126 | 0 |
1 |
1 |
0 |
0x00000000 |
0x00000004 |
0 |
0x00000064 |
1 |
0x00000000 |
0 |
0x0 |
0x00000004 |
0 |
#10train-9 | 145 | 0 |
1 |
1 |
0 |
0x00000000 |
0x00000004 |
0 |
0x00000064 |
1 |
0x00000000 |
1 |
0x1 |
0x00000004 |
0 |
#11train-10 | 162 | 0 |
1 |
1 |
0 |
0x00000000 |
0x00000004 |
0 |
0x00000064 |
1 |
0x00000000 |
0 |
0x0 |
0x00000004 |
0 |
#12train-11 | 181 | 0 |
1 |
1 |
0 |
0x00000000 |
0x00000004 |
0 |
0x00000064 |
1 |
0x00000000 |
1 |
0x1 |
0x00000004 |
0 |
#13train-12 | 198 | 0 |
1 |
1 |
0 |
0x00000000 |
0x00000004 |
0 |
0x00000064 |
1 |
0x00000000 |
1 |
0x2 |
0x00000064 |
1 |
#14backward-direction | 200 | 1 |
0 |
1 |
0 |
0x00000000 |
0x00000004 |
0 |
0x00000064 |
0 |
0x00000000 |
1 |
0x2 |
0x00000064 |
1 |
#15disabled-update | 205 | 1 |
1 |
1 |
0 |
0x00000000 |
0x00000004 |
0 |
0x00000064 |
0 |
0x00000000 |
1 |
0x2 |
0x00000064 |
1 |
#16independent-row | 217 | 1 |
0 |
1 |
0 |
0x00000004 |
0x00000004 |
0 |
0x00000064 |
0 |
0x00000000 |
1 |
0x0 |
0x00000004 |
0 |
#17index-alias | 229 | 1 |
0 |
1 |
0 |
0x00000010 |
0x00000004 |
0 |
0x00000064 |
0 |
0x00000000 |
1 |
0x2 |
0x00000064 |
1 |
#18jal-always-target | 232 | 1 |
0 |
0 |
1 |
0x00000010 |
0x00000004 |
0 |
0x00000064 |
0 |
0x00000000 |
1 |
0x2 |
0x00000064 |
1 |
#19ordinary-fallthrough | 237 | 1 |
0 |
0 |
0 |
0x00000010 |
0x00000004 |
0 |
0x00000064 |
0 |
0x00000000 |
1 |
0x2 |
0x00000004 |
0 |
#20reset-clears-training | 253 | 1 |
0 |
1 |
0 |
0x00000010 |
0x00000004 |
1 |
0x00000064 |
0 |
0x00000000 |
1 |
0x0 |
0x00000004 |
0 |
Scroll within the table for all rows and pins. Buses use hexadecimal; values containing X/Z use MSB-first binary. Inputs show the declared values in effect at each checkpoint, including retained inputs. Sequential tests also replay preparation changes between checkpoints, such as lowering the clock. Use the matching checkpoint label in the simulation below.
Tests¶
RV32IPredictionCounterTest exhausts all 16 mode/state/outcome combinations. RV32IBranchPredecodeTest covers targets, wraparound, reserved encodings and JALR fallback. RV32IBranchPredictorTest checks saturation, reset, disabled training, independent rows, aliasing and all policies in both fidelities. RV32IAdvancedExamplesTest replays their actions through these exact C++ examples and rejects malformed configuration values.