Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions flow/designs/asap7/l2_dir_hit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# l2_dir_hit

One cycle of an L2 cache slice's main pipeline. The directory decides
**hit** and **way** in s3, from tag and meta registers that its SRAM
macros filled the cycle before, and in the same cycle that decision
steers the slice:

- MainPipe writes the line's new meta into the directory, and the
replacer is written on every hit. The arrays are single ported, so
either write makes the directory unready, RequestArb withholds its ready
from SinkC, and SinkC's task does not fire: the enable of a 512-bit
register that reads SinkC's data buffer.
- A miss allocates one of 16 MSHRs, with the state the hit decided.
- A hit answered by MainPipe itself enqueues an L1 hint.
- The data array is read or written at the hit's way.

The design exists to measure one thing: how much of a register-to-register
path that starts beside SRAM macros, decides in one place and is
broadcast across the slice in the same cycle goes to repeaters and wire
rather than logic.

## The path

```mermaid
flowchart LR
subgraph DIR[directory]
T[tag SRAMs, 2] --> TR[s3 tag registers]
M[meta SRAM] --> MR[s3 meta registers]
end
TR & MR --> H[8 tag compares, hit, way one-hot, Mux1H of the meta]
H --> D[MainPipe: need_mshr, meta write]
D --> W[directory busy] --> R[RequestArb ready] --> C[SinkC task fire: 512-bit read enable]
D --> MS[MSHR allocation, 16 MSHRs]
D --> Q[hint queue]
D -->|two cycles| DS[data SRAMs, 4]
```

XiangShan's designers name the path in `MainPipe.scala`: "The
combinational logic path of Directory metaAll -> Directory response ->
MainPipe judging whether to respond data is too long", and latch the
SinkB response to s4 for it. The paths that remain single cycle are the
ones above.

## Where it comes from

XiangShan's L2, CoupledL2 (XSCache at 300515bc), in the configuration of
a 512 KiB, 8-way slice with a 64-byte block:

| piece | source |
|---|---|
| tag, meta arrays and the s3 hit, the single port | `coupledL2/Directory.scala:169-343, 390` |
| MainPipe's decision and its directory writes | `coupledL2/MainPipe.scala:244-262, 538-616` |
| the MSHR allocation | `coupledL2/MainPipe.scala:309, 1021-1059`, `coupledL2/MSHRCtl.scala:97-137` |
| RequestArb's ready, one task every other cycle | `coupledL2/RequestArb.scala:134-208` |
| SinkC's buffers and its read | `coupledL2/SinkC.scala:48-193` |
| the hint queue | `coupledL2/CustomL1Hint.scala:91-126` |
| the data array's request | `coupledL2/MainPipe.scala:484-519`, `coupledL2/DataStorage.scala:52-131` |

The source files cite the lines each piece models, and say what is left
out because it is not on the path.

## The two-cycle data array

XiangShan builds the data array with `readMCP2 = true`: "read data is
set MultiCycle Path 2", and the request, way, set and write data "must
hold for 2 cycles". The RTL asserts both that they hold and that no
request follows a request, and RequestArb stalls a cycle after every task
to make it so. `constraint.sdc` declares that contract, into the array
and out of it; the clock gate's enable is a one-cycle pulse and keeps one
cycle.

Without the declaration the data array's address and read data would be
among the worst paths of the design, and they would be failures the
designers never asked the flow to close.

## Configuration

| | |
|---|---|
| RTL | Readable, parameterised SystemVerilog, read by slang (`SYNTH_HDL_FRONTEND = slang`) |
| SRAMs | Behavioural modules in the firtool port convention (`RW0_*`). `AUTO_MEMORIES = 1` turns each into a generated macro at XiangShan's own shapes: two 1024x156 tag halves, a 1024x128 meta array and four 8192x137 data banks |
| Constraints | `constraint.sdc` sources `$PLATFORM_DIR/constraints.sdc`. It is a macro, so the boundaries are `set_max_delay` optimisation targets, with no `set_input_delay`/`set_output_delay`, and only reg2reg can fail. The 473 ps period is derived in the file |
| Flow | ORFS defaults throughout, all three asap7 Vt's, the full flow through final, no waivers |

## Results

The minimum clock period is the SDC period minus the worst slack of the
`reg2reg` group, at global route with the route's parasitics.

| | minimum period at grt | worst path | logic / repeaters / wire |
|---|---|---|---|
| XiangShan, CoupledL2 in XSTile | 1,128 ps | directory `req_s3_tag` to SinkC's buffer read | 341 ps (24 cells) / 473 ps (35) / 247 ps |
| this design | _pending_ | _pending_ | _pending_ |
33 changes: 33 additions & 0 deletions flow/designs/asap7/l2_dir_hit/config.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# l2_dir_hit: an L2 directory's hit, decided in one cycle from SRAM-fed
# registers, steering the cache's pipeline. README.md says what it is for
# and where it comes from.
export PLATFORM = asap7
export DESIGN_NAME = l2_dir_hit
export DESIGN_NICKNAME = l2_dir_hit

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 tag, meta and data arrays are behavioural modules in the firtool
# port convention; AUTO_MEMORIES turns each into a generated macro at
# XiangShan's own shapes.
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 = 35
export CORE_MARGIN = 2
# Channels between macros: room for the power grid to reach every SRAM.
export RTLMP_MIN_CHANNEL_SIZE = 4 4

# The slice's units stay modules, as CoupledL2's own flow keeps them: the
# macro placer clusters by them. Flat, one data bank is ~96 % of the area
# of the cluster around it and the area-balanced bisection cannot split
# it (MPL-0045).
export SYNTH_HIERARCHICAL = 1
export SYNTH_KEEP_MODULES = l2_directory l2_sink_c l2_mshr_ctl l2_custom_l1_hint
32 changes: 32 additions & 0 deletions flow/designs/asap7/l2_dir_hit/constraint.sdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# 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

# The data array is a two-cycle macro, a contract the RTL states:
# readMCP2 = true, "read data is set MultiCycle Path 2", and its request,
# way, set and write data "must hold for 2 cycles"; the RTL asserts both
# that they hold and that no request follows a request
# (coupledL2/DataStorage.scala:52-80, 119-131). Into the array and out of
# it, setup has two cycles and hold stays at the launching edge. The
# clock gate's enable is not part of the contract: it is a one-cycle
# pulse, a register in this design, and keeps one cycle.
set data_srams [get_cells -hierarchical -filter "ref_name == l2_data_sram"]
if { [llength $data_srams] == 0 } {
error "constraint.sdc: no l2_data_sram instance, the multicycle contract has nothing to hold"
}
set_multicycle_path -setup 2 -to $data_srams
set_multicycle_path -hold 1 -to $data_srams
set_multicycle_path -setup 2 -from $data_srams
set_multicycle_path -hold 1 -from $data_srams
54 changes: 54 additions & 0 deletions flow/designs/src/l2_dir_hit/l2_custom_l1_hint.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// CustomL1Hint: a hit answered by MainPipe tells the L1 at s3, through an
// arbiter with the s1 hints and a 16-entry queue of flops
// (coupledL2/CustomL1Hint.scala:91-126, XSCache at 300515bc). The queue is
// chisel3.util.Queue as Chisel builds it: enqueue and dequeue pointers and
// maybe_full.
module l2_custom_l1_hint #(
parameter int SOURCEBITS = 7,
parameter int ENTRIES = 16 // mshrsAll
) (
input logic clock,
input logic reset,
input logic enq_s3,
input logic enq_s3_has_data,
input logic [SOURCEBITS-1:0] enq_s3_source,
input logic s1_valid,
input logic [SOURCEBITS-1:0] s1_source,
input logic hint_ready,
output logic hint_valid,
output logic [SOURCEBITS-1:0] hint_source,
output logic hint_has_data
);
// the arbiter, s3 first (:124)
logic enq_valid, enq_data, enq_fire, deq_fire;
logic [SOURCEBITS-1:0] enq_source;
assign enq_valid = enq_s3 || s1_valid;
assign enq_source = enq_s3 ? enq_s3_source : s1_source;
assign enq_data = enq_s3 ? enq_s3_has_data : 1'b0;

logic [SOURCEBITS:0] q[ENTRIES];
logic [$clog2(ENTRIES)-1:0] enq_ptr, deq_ptr;
logic maybe_full, ptr_match, empty, full;
assign ptr_match = enq_ptr == deq_ptr;
assign empty = ptr_match && !maybe_full;
assign full = ptr_match && maybe_full;
assign enq_fire = enq_valid && !full;
assign deq_fire = !empty && hint_ready;
always_ff @(posedge clock) begin
if (enq_fire) q[enq_ptr] <= {enq_data, enq_source};
if (reset) begin
enq_ptr <= '0;
deq_ptr <= '0;
maybe_full <= 1'b0;
end else begin
if (enq_fire) enq_ptr <= enq_ptr + 1'b1;
if (deq_fire) deq_ptr <= deq_ptr + 1'b1;
if (enq_fire != deq_fire) maybe_full <= enq_fire;
end
end
always_ff @(posedge clock) begin
hint_valid <= !reset && !empty;
hint_source <= q[deq_ptr][SOURCEBITS-1:0];
hint_has_data <= q[deq_ptr][SOURCEBITS];
end
endmodule
31 changes: 31 additions & 0 deletions flow/designs/src/l2_dir_hit/l2_data_sram.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// One quarter of the L2 data array: 8192 entries (1024 sets of eight
// ways) of a 128-bit word with a 9-bit SECDED code, single-ported.
// XiangShan's CoupledL2 builds four of exactly this macro
// (array_8192x137) for a 64-byte block: coupledL2/DataStorage.scala:68-80,
// a GatedSplittedSRAM with dataSplit = 4 and readMCP2 = true.
//
// The array is a two-cycle macro in XiangShan: its inputs hold for two
// cycles and its read data is captured two cycles after the read edge
// (DataStorage.scala:52-65, 119-131). constraint.sdc declares that
// contract; this model is the ordinary one-cycle form, which is all
// AUTO_MEMORIES needs to generate the macro.
//
// Port convention and fixed size as in l2_tag_sram.sv.
module l2_data_sram (
input logic RW0_clk,
input logic RW0_en,
input logic RW0_wmode, // 1 write, 0 read
input logic [ 12:0] RW0_addr, // {way, set}
input logic [136:0] RW0_wdata,
output logic [136:0] RW0_rdata
);
logic [136:0] mem[8192];
logic ren_q;
logic [ 12:0] raddr_q;
always_ff @(posedge RW0_clk) begin
ren_q <= RW0_en && !RW0_wmode;
raddr_q <= RW0_addr;
if (RW0_en && RW0_wmode) mem[RW0_addr] <= RW0_wdata;
end
assign RW0_rdata = ren_q ? mem[raddr_q] : 'x;
endmodule
Loading
Loading