Skip to content
Open
151 changes: 151 additions & 0 deletions flow/designs/asap7/tage_update/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# tage_update

This design adds to the variety, relevance and fast turnaround of the
ORFS tests: generated SRAMs at scale (6 / 40 / 160 macros through
`AUTO_MEMORIES`) on ORFS's default flow settings, the structure of a
TAGE branch predictor as in XiangShan, and a size ladder whose
10-minute base variant already shows the effect.

One cycle of a branch predictor's second stage: the TAGE's tables and
the main BTB are read from the same pc, and their responses decide the
fetch block's prediction. When that prediction overrides the first
stage's, the next fetch starts from its target, and the TAGE and main
BTB banks that pc hashes to are read again, so the path ends at the
bank being read: its SRAM's read address or enable, or the write
buffer beside a TAGE table, which cannot drain while the bank is read.

The decision logic needs both sets of SRAMs' outputs, so placement
puts it between them, and a path from one TAGE table back to a TAGE
table goes out to that logic and comes back. The design exists to
measure that detour: how far the path travels compared with the
distance between its ends, and how much of its delay is repeaters and
wire rather than logic.

## The path

```mermaid
flowchart LR
subgraph TAGE[TAGE: TABLES x BANKS x WAYS]
TS[entry and useful SRAMs] --> TR[s2 response regs]
WB[write buffers] --> TS
end
subgraph MBTB[main BTB: 2 halves x MBTB_BANKS x MBTB_WAYS]
MS[entry SRAMs] --> MR[s2 slots and their order]
end
TR -->|tag compare| D[provider and alternate: direction]
D --> C
MR --> C[first taken slot: direction, position, target]
C --> O[override of stage 1?]
O -->|s0 pc| WB
O -->|s0 pc| MS
```

The critical path starts at a stage-2 register, decides the TAGE's
direction, combines it with the main BTB's slots into the block's
prediction, compares that with the first stage's, picks the next pc,
hashes it to a bank, and ends at that bank: the SRAM's read address or
enable, or the write buffer, which does not drain while the bank is
read.

```mermaid
sequenceDiagram
participant S as stage control
participant T as TAGE tables
participant B as main BTB
S->>T: s0 read at pc
S->>B: s0 read at pc
T->>S: s2 responses (gather)
B->>S: s2 slots
S->>S: same cycle: direction, first taken slot, override, next pc
S->>T: next s0 read, at the bank that pc hashes to: no drain there
S->>B: next s0 read
```

## Where it comes from

XiangShan's Frontend (Kunminghu), its branch predictor:
`xiangshan/frontend/bpu/Bpu.scala` for the stage-2 prediction and the
override, `bpu/tage/` for the TAGE, `bpu/mbtb/` for the main BTB and
`bpu/WriteBuffer.scala` for the write buffers. The sources cite the
lines each piece models.

The SRAMs keep XiangShan's shapes: 512x17 for the TAGE's entries
(`array_512x17`), 64x16 for its useful counters (`array_64x16`, eight
2-bit counters to a row) and 256x46 for the main BTB's entries
(`array_256x46`). All use firtool's port convention, so
`AUTO_MEMORIES` turns them into generated macros.

What is left out: the TAGE predicts one direction per fetch block
where XiangShan's predicts one per slot (wider, not deeper); the
targets' upper-bit correction; the Ftq, which in XiangShan returns a
just-predicted branch's metadata to training and here is one wire.

## Variants

| FLOW_VARIANT | TAGE | main BTB | macros |
|---|---|---|---|
| small | 2 tables, 1 bank, 1 way | 1 bank, 1 way | 6 |
| medium (= base, the default) | 4 tables, 2 banks, 2 ways | 2 banks, 2 ways | 40 |
| large | 8 tables, 4 banks, 2 ways | 4 banks, 4 ways | 160 |

large is XiangShan's size for both. base, the variant ORFS runs when
FLOW_VARIANT is not set, is the smallest that shows the effect.

## What it shows

The worst path in every variant leaves stage 2's pc and ends at an
SRAM's read address or enable: the override has decided where the next
fetch starts, and the banks that pc hashes to have to be read. The
decision needs both the TAGE's and the main BTB's outputs, so it sits
between the two groups of SRAMs, and the path back from it to a bank
grows with the field of SRAMs around it.

- small is logic. The decision sits next to everything it reads, and
repeaters and wire are a small part of the path.
- medium starts to travel: a few repeaters appear, and the path is
longer than the distance between its ends.
- large is the size of XiangShan's predictor. The main BTB's SRAMs sit
along one side of the die and the TAGE's along the other, the path is
about a millimetre long, and close to half of its delay is repeaters
and wire. That half is what this design is for.

Two things decide how much of that half is paid:

- The metal stack. With resistance-aware global routing (asap7's
default) the critical nets go to M8 and M9, which this design routes
to, as XiangShan does.
- Detailed routing. It makes the long nets longer than global route
estimated, so large is worse at final than at global route.

A change to the flow for this class of path shows on large as a shorter
path or a smaller repeater and wire share. Look at the worst path after
global route:

```sh
make DESIGN_CONFIG=./designs/asap7/tage_update/config.mk FLOW_VARIANT=large gui_grt
```

XiangShan's Frontend has the same structure on a die 2.4 times wider,
the rest of the Frontend surrounding the predictor, so the effect is
larger there.

## Constraints

`constraint.sdc` sets a 473 ps clock (see its comments for why) and
sources `$PLATFORM_DIR/constraints.sdc`: the block's ports are budgeted
with `set_max_delay`, and only register-to-register paths can fail.

## Try this

```sh
make DESIGN_CONFIG=./designs/asap7/tage_update/config.mk
make DESIGN_CONFIG=./designs/asap7/tage_update/config.mk FLOW_VARIANT=small
make DESIGN_CONFIG=./designs/asap7/tage_update/config.mk FLOW_VARIANT=large
```

Then look at the worst register-to-register path and where its cells
are:

```sh
make DESIGN_CONFIG=./designs/asap7/tage_update/config.mk gui_grt
```
40 changes: 40 additions & 0 deletions flow/designs/asap7/tage_update/config.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# tage_update: a branch predictor's stage-2 decision between two sets of
# SRAMs, and back. README.md says what it is for and where it comes from.
export PLATFORM = asap7
export DESIGN_NAME = tage_update
export DESIGN_NICKNAME = tage_update

export VERILOG_FILES = $(sort $(wildcard $(DESIGN_HOME)/src/$(DESIGN_NICKNAME)/*.sv))
export SDC_FILE = $(DESIGN_HOME)/$(PLATFORM)/$(DESIGN_NICKNAME)/constraint.sdc

# Readable, parameterised SystemVerilog, read by slang.
export SYNTH_HDL_FRONTEND = slang

# The tables' SRAMs are behavioural modules in the firtool port
# convention; AUTO_MEMORIES turns each into a generated macro.
export AUTO_MEMORIES = 1

# All three threshold voltages, so the resizer can trade leakage for
# speed per path, as a 3 GHz-class core does.
export ASAP7_USE_VT = RVT LVT SLVT

export CORE_UTILIZATION = 30
export CORE_MARGIN = 2

# The full metal stack, as XiangShan's own configs route: asap7's power
# grid uses M1, M2, M5 and M6 only, so M8 and M9 are free for signals.
export MAX_ROUTING_LAYER = M9

# A ladder, FLOW_VARIANT=small|medium|large: small to iterate on, large
# at the size of XiangShan's TAGE and main BTB. base, ORFS's default
# variant, is medium: the smallest size that shows the effect (README.md,
# Variants).
ifeq ($(FLOW_VARIANT),small)
export VERILOG_TOP_PARAMS = TABLES 2 BANKS 1 WAYS 1 MBTB_BANKS 1 MBTB_WAYS 1
else ifeq ($(FLOW_VARIANT),medium)
export VERILOG_TOP_PARAMS = TABLES 4 BANKS 2 WAYS 2 MBTB_BANKS 2 MBTB_WAYS 2
else ifeq ($(FLOW_VARIANT),large)
export VERILOG_TOP_PARAMS = TABLES 8 BANKS 4 WAYS 2 MBTB_BANKS 4 MBTB_WAYS 4
else
export VERILOG_TOP_PARAMS = TABLES 4 BANKS 2 WAYS 2 MBTB_BANKS 2 MBTB_WAYS 2
endif
15 changes: 15 additions & 0 deletions flow/designs/asap7/tage_update/constraint.sdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# 473 ps is XiangShan's synthesis period on this library: its published
# goal is 3 GHz (333 ps) on 7 nm, 41.1 FO4 at ASAP7's published 8.1 ps,
# which is 591 ps at this flow's 14.37 ps FO4, and 0.8 of that.
set clk_name clk
set clk_port_name clock
set clk_period 473

# A macro: only reg2reg can fail closure. The boundaries are
# optimisation targets, set_max_delay in the platform's constraints.sdc,
# not set_input_delay/set_output_delay.
set in2reg_max [expr { $clk_period * 0.8 }]
set reg2out_max [expr { $clk_period * 0.8 }]
set in2out_max [expr { $clk_period * 0.6 }]

source $::env(PLATFORM_DIR)/constraints.sdc
94 changes: 94 additions & 0 deletions flow/designs/src/tage_update/bpu_ctrl.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// The branch predictor's top: the stage-2 prediction of a fetch block,
// and from it which pc stage 0 reads next.
//
// XiangShan: xiangshan/frontend/bpu/Bpu.scala. A slot of the main BTB's
// result is taken if it is a jump, or a conditional branch the TAGE says
// is taken (s2_takenMask, Bpu.scala:356-360); the block's branch is the
// first taken slot (s2_firstTakenBranchOH, :362), its target the block's
// target, and with no taken slot the block falls through (:367). Stage 2
// overrides stage 1 when the direction or the position differs from the
// fast stage-1 prediction (s2_override, :374-380); an override flushes
// stage 1, and stage 0 reads from the stage-2 target (s0_startPc,
// :533-541). Stage 0 is the next read of the TAGE and of the main BTB,
// at banks and sets hashed from that pc, so the stage-2 response of both
// decides, within the same cycle, which of their banks are read next.
module bpu_ctrl #(
parameter int SLOTS = 8
) (
input logic clock,
input logic enable, // s1_ready && sramResetDone, registered
// a redirect from the back end, registered at the boundary
input logic redirect_valid,
input logic [31:0] redirect_pc,
// the stage-1 prediction, from the fast predictor (uBTB)
input logic s1_valid,
input logic [31:0] s1_target,
// what stage 1 predicted for the block now in stage 2
input logic s2_valid,
input logic s2_s1_taken,
input logic [ 3:0] s2_s1_position,
input logic [31:0] s2_pc,
// the stage-2 results: the TAGE's direction, the main BTB's slots
input logic tage_taken,
input logic slot_hit [SLOTS],
input logic slot_cond [SLOTS],
input logic slot_jump [SLOTS],
input logic [ 3:0] slot_position[SLOTS],
input logic [31:0] slot_target [SLOTS],
input logic slot_before [SLOTS][SLOTS],
// the prediction, and stage 0
output logic s2_taken,
output logic s0_fire,
output logic [31:0] s0_pc,
output logic s1_flush
);
// ---- the stage-2 prediction ---------------------------------------------
// The TAGE here predicts one direction per block; XiangShan's predicts
// one per slot (fastTakenVec), which is wider and no deeper.
logic taken[SLOTS], first[SLOTS];
always_comb begin
for (int i = 0; i < SLOTS; i++)
taken[i] = slot_hit[i] && (slot_jump[i] || (slot_cond[i] && tage_taken));
// CompareMatrix.getLeastElementOH
for (int i = 0; i < SLOTS; i++) begin
first[i] = taken[i];
for (int j = 0; j < SLOTS; j++)
if (j != i) first[i] = first[i] && (!taken[j] || slot_before[i][j]);
end
end

logic [ 3:0] s2_position;
logic [31:0] s2_target;
always_comb begin
s2_taken = 1'b0;
s2_position = '0;
s2_target = s2_pc + 32'd64; // fall through: the next fetch block
for (int i = 0; i < SLOTS; i++) begin
s2_taken = s2_taken || taken[i];
if (first[i]) begin // one-hot: Mux1H
s2_position = slot_position[i];
s2_target = slot_target[i];
end
end
end

// ---- stage 0 -----------------------------------------------------------------
logic s2_override;
assign s2_override = s2_valid && (s2_taken != s2_s1_taken ||
(s2_taken && s2_position != s2_s1_position));
assign s1_flush = redirect_valid || s2_override;

logic [31:0] s0_pc_q;
always_ff @(posedge clock) if (s0_fire) s0_pc_q <= s0_pc;

always_comb begin
if (redirect_valid) s0_pc = redirect_pc;
else if (s2_override) s0_pc = s2_target;
else if (s1_valid) s0_pc = s1_target;
else s0_pc = s0_pc_q;
end

// s0_fire = s1_ready && sramResetDone (Bpu.scala:260); with the Ftq
// always ready, stage 1 is always ready
assign s0_fire = enable;
endmodule
Loading
Loading