Skip to content

AUTO_MEMORIES_REGFILES: register files as macros that dissolve after macro placement - #1187

Closed
oharboe wants to merge 22 commits into
mainfrom
regfile/auto-memories
Closed

oharboe wants to merge 22 commits into
mainfrom
regfile/auto-memories

Conversation

@oharboe

@oharboe oharboe commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Draft, scaffolding: not for merge as is. This is the integration branch for register files built as placed standard cells (AUTO_MEMORIES_REGFILES). The feature lands upstream, piece by piece. The pieces are carried here as patches so they can be exercised together on real designs. Each patch retires at the //:bump onto the upstream that carries it, and is left as it is while its upstream pull request settles (upstream-pr, section 6). CI does not have to pass.

Upstream

Piece Carried here Upstream
generate_regfile: the array as placed standard cells OpenROAD patch 0010 OpenROAD #11669
AUTO_MEMORIES_REGFILES, AUTO_MEMORIES_MACRO_PLACE, the dissolve after macro placement ORFS 0089, 0091 ORFS #4651 (draft)
A generated macro's blackbox costs its view's area ORFS 0090 ORFS #4650
tinyRocket's memories modelled ORFS 0092 ORFS #4643
SYNTH_VERILOG_SURGERY ORFS 0094 (from main) ORFS #4649
coralnpu's register file ORFS 0102, 0103 ORFS #4653 (draft)
memory <module>.<cell>: a seam yosys makes for an inline array ORFS 0093 none yet
kepler-formal skips flops behind a clock gate, so the clock-gated register file can be checked kepler-formal patch 0002 none yet
Ladder designs: picorv32, ibex, swerv, cva6, the riscv32i modes and seeds, the mux variants ORFS 0095-0100, 0104, 0105 not proposed: campaign only

What is here to work on it

  • test/regfile: dissolve, clocking and grt build tests, and the register file in simulation.
  • test/structured_gen_lec: generate_regfile checked against its RTL by kepler-formal.
  • test/regfile/ladder: each design with its register file in each generate_regfile mode, its simulation, and the surgery that gives an inline array a module.
  • test/regfile/campaign: the riscv32i clock-period campaign, paused for a bigger machine. The state and the claims it supersedes are in the reseat comment and test/regfile/campaign/results.md. Numbers in this pull request's earlier comments are superseded there.

Rebased onto main

  • 0094 is main's, the net diff of ORFS #4649, listed once.
  • 0072 and 0079 retired on main at the bump onto ORFS ef421749.
  • "designs: a design directory gets its own config" is main's; the ladder keeps its own test of it over its variant designs.
  • SYNTH_POST_HIERARCHY_SCRIPTS is no longer a source variable: no design here sets it in a config.mk any more, and as one it failed the variables.yaml check for veer, so no package loaded.

🤖 Generated with Claude Code

@oharboe
oharboe changed the base branch from regfile/openroad-generate-regfile to main October 6, 2026 17:15
@oharboe

oharboe commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator Author

Fewer register files that need a seam in the RTL

Seams are still needed, but yosys can make one for any register file it
infers as a $mem_v2. That leaves fewer cases that need a hand-made seam.

Category Examples (from AutoMemories.md) Seam
Own module, ports a spec can name riscv32i regfile spec, today
Inferred as $mem_v2, anywhere in the hierarchy picorv32 cpuregs; to check: tinyRocket, coralnpu Regfile/FRegfile, swerv dec_gpr_ctl yosys makes it, no RTL change
Array with a reset ibex ibex_register_file_ff, cva6 ariane_regfile_ff needs one in the RTL
Chisel Reg(Vec) XiangShan needs one in the RTL: by default firtool splits it into one register per element plus mux trees, so yosys never sees an array

How yosys makes the seam. The passes are memory_libmap and then
techmap. This is how FPGA flows put an inline array into a block RAM
(passes/memory/memlib.md).

  • memory_libmap turns each matching $mem_v2 into a $__<name> cell,
    and techmap turns that cell into an instance of the generated macro.
  • Anything the RAM definition doesn't describe stays in the parent as
    logic: x0 reading 0, bypass, scoreboard, read enables. That's what
    keeps coralnpu's and swerv's register files out of a spec today.
  • memlib's ar port kind describes the combinational reads register
    files have.

One RAM definition per memory. gen_memories.py would write, from
memories_inferred.json:

  • a memlib ram block for each accepted memory, with its own abits,
    width, ports, and style "<macro>";
  • a techmap wrapper for each block, naming the pins generate_regfile
    builds.

setattr ram_style=<macro> on each accepted $mem_v2 makes it map only
to its own macro. Unlisted memories still become flip-flops through
memory_map.

Where it goes in synthesis. In synth.tcl, between synth -run :fine
and synth -run fine:. That's where SYNTH_MOCK_LARGE_MEMORIES already
works on $mem_v2: after memory_dff, before memory_map.

Opt-in by name. List each memory by its inventory name,
<module>.<cell>. 0066 would still refuse any array that isn't listed.

Open:

  • The main synthesis has to see the same $mem_v2 the extraction run
    inventoried, with the same name and shape. It runs opt and
    memory_share that the scoped extraction (0079) doesn't, and slang's
    uniquified names (0081) have already broken name matching once. A
    listed memory that doesn't map should stop the build.
  • The dissolve hasn't been checked with the instance sitting in the CPU
    module instead of in a module of its own.

Not measured. The cheap test is picorv32's cpuregs: one hand-written
memlib entry and techmap wrapper, then count flip-flops before and after.
That takes seconds.

@oharboe

oharboe commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator Author

Some data for OpenROAD's ram tracker (OpenROAD #9392) from an experiment on asap7, and questions
before going further.

Claude made the changes and I stared at paint drying: a register file of
placed standard cells for ORFS's riscv32i (32 x 32, 2R1W), to try three
items from that tracker in a full flow: asap7 support, the AND2 decoder,
and exporting the array as cells on the core's rows rather than as a
block. Neither of us read RamGen first, so it ended up as a separate
generator, carried as a patch in a test scaffold. That's the wrong place
for it. RamGen is the generator, and I'd like to carry as little as
possible on top of it.

riscv32i to global route, 950 ps clock, global-route parasitics:

flip-flops placed register file
minimum period 954 ps 998 ps
failing endpoints 79 992, all in the array

The worst path goes through the decode as three serial AND2 stages, then
an OR2 tree: 232 ps from instr to read data. Placed as a macro and then
dissolved into fixed cells on the core's rows, the array reaches global
route with no PDN or halo of its own.

Questions:

  1. asap7 has no tristate. Would you rather have a fake tristate cell in
    the platform, as the tracker suggests, or an AND/OR read path in
    RamGen?
  2. For "export as a structured placement group": should RamGen place the
    cells into the parent's rows itself, or write a placed block that the
    flow dissolves after macro placement, as we did?
  3. Is a 2R1W register file in scope for RamGen, or does it belong in a
    different tool?

@oharboe

oharboe commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator Author

Plan: from this PR to upstream

This PR is integration testing. It carries every change as a patch and
runs them in full flows. The changes go upstream as small pull requests,
the base first, and each one links here for the full-flow evidence. When
the last one merges and //:bump moves onto it, the patches are retired
and nothing is carried.

Extend generate_ram; no new command

OpenROAD's generate_ram (src/ram) already builds a memory from
standard cells: decoder column, tap columns, rows, LEF/DEF, a netlist-only
variant (generate_ram_netlist), and options that swap parts of the bit
(-use_latch, -column_mux_ratio). Two things in its bit stop it from
building these register files:

  • Writes: each bit's flop takes the column's shared data line and is
    written through a per-word clock gate, so there is one write port
    (read-write plus write ports must sum to 1).
  • Reads: one tristate per read port on a shared bit line. asap7 has
    no tristate cell.

Patch 0010's generate_regfile solved both as a separate command (a
write select per bit; an AND per read port and OR trees). Upstream, they
become options on generate_ram, and 0010 retires.

Upstream stack

# Repo Change Needs
1 OpenROAD generate_ram: an AND/OR read path in place of tristates, with an asap7 test —
2 OpenROAD generate_ram: more than one write port, a write select per bit in place of the clock-gated write 1
3 OpenROAD generate_ram: decode and read trees sized for their fanout (AND3/AND4, AO22, NAND/NOR) 1
4 ORFS AUTO_MEMORIES_REGFILES: a spec per module mapped onto generate_ram options, blackboxed and placed like an AUTO_MEMORIES macro 2
5 ORFS the dissolve after macro placement: the macro becomes FIRM cells on the core's rows 4

Spec-only features (RTL port names, zero_word, banks, bit folds) live
in ORFS's mapping (4), not in OpenROAD's arguments, unless a reviewer
wants them there.

Upstream pull requests are opened by a human. This PR stays a draft
until 1 has merged.

Gate: riscv32i must win

riscv32i is the fast-turnaround baseline: it reaches global route in
minutes. Today the generated register file loses to flip-flops:

to global route, 950 ps clock flip-flops register file
minimum period 954 ps 998 ps
failing endpoints 79 992
core area 4 012 um² 7 573 um²

The worst path runs through the decode as three serial AND2 stages and
an OR2 read tree: 232 ps from instr to read data.

Experiments to find a structure that wins can continue on 0010, where
the turnaround is already set up. The proof each upstream pull request
links to is riscv32i built with generate_ram and that pull request's
options: minimum period at or below flip-flops', with area reported.

Why register files need this

XiangShan's register files are Reg(Vec). yosys never infers a memory
from them, and it does not finish synthesising them as flip-flops at
this size:

Register file Words × bits Ports
IntRegFile 224 × 64 12 banked reads, 9 writes
FpRegFile 256 × 64 14 reads, 7 writes
VfRegFile 128 × 128 14 reads, 7 writes
RobEntryFile / RenameBufferFile 352 × 21 / 20 16 reads, 8 writes

Rules for the upstream pull requests

  • One concern per pull request; each one builds, tests and is useful on
    its own.
  • With the new options off, generate_ram's existing sky130 and
    Nangate45 tests pass unchanged.
  • New tests assert intent and pass or fail: ports as asked, every cell
    placed, no overlaps, reads and writes correct in simulation of the
    behavioral model against the netlist. No new golden .defok, .lefok
    or .libok files.
  • The description gives the evidence and links here.

@oharboe

oharboe commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator Author

Reseat: the riscv32i register-file campaign

Paused here to carry on on a machine that can run several experiments
at once. Everything needed is on branch regfile/campaign-wip
(68178d9), five commits on this PR's head, not merged into it:

commit what
52b09b9 dissolve: cut the rows around macros before the array is FIRM (cut_rows skips a row holding a fixed cell, ODB-0386; an endcap landed inside a FakeRAM), FLW-0014 if a cell is off-row
6a69961 generate_regfile: read bitlines as an AOI22 per word pair and a NAND2/NOR2 tree, OR2 at the root
e5dcafd generate_regfile: tile width from simulating the row fill (46 -> 30 sites on riscv32i); write-mux cell count fixed
9967f88 riscv32i-regfile: flat array (bit_folds 1) at riscv32i's own 62 %
68178d9 test/regfile/campaign/: results, the scripts, generator variants, the lined-up experiments

Results so far (global route, 950 ps clock)

configuration min period core um2 std cells um2
flip-flops (the gate) 954 4012 1284
flip-flops at 45 % (control) 951 5533 1273
this PR's head 998 7573 1413
this PR's head, array unfixed (control) 999 7573 1411
+ AOI22/NAND/NOR read tree, folds 2, 45 % 977 7353 1190
+ 30-site tiles, flat, 62 % 1003 4466 1161
+ 30-site tiles, flat, 45 % 982 6156 1116
+ 30-site tiles, folds 2, 35 % 973 8055 1170

Nothing reaches parity yet. The register file is 7-13 % smaller in
standard cells than the flops in every configuration. The trade is
shape: a folded array is faster (half-length select lines; the flat 53
um ones take three BUFx12f), a flat one fits a flip-flop-sized core.

Disregard in this PR's description and earlier comments

These are superseded or wrong; test/regfile/campaign/results.md on the
branch replaces them:

  1. The riscv32i tables (998 ps, 7 573 um2, 1 413 um2) in the
    description, in the comment for OpenROAD #9392 and in the plan's
    "Gate" section: superseded by the table above.
  2. "232 ps from instr to read data", "three serial AND2 stages, then
    ... five OR2 levels"
    as the reason it is slower: with the
    AOI22/NAND/NOR tree the read path is off the critical path in most
    runs. Three serial AND2s remain in the decode and still matter on the
    flat array.
  3. "cells the resizer may swap in place" as a fix, and any reading
    that FIRM or dont_touch costs timing: the unfixed control (999 vs 998
    ps) shows it costs nothing. The netlist structure is what mattered.
  4. "The core is bigger because config.mk sets 45 %": the cause was
    the tile width estimate padding every tile by a flop's width; with
    30-site tiles the flat array fits riscv32i's 62 %.
  5. The same text is in docs/user/AutoMemories.md inside patch 0089;
    it is stale on this PR's head and not yet rewritten on the branch.

Still correct: asap7 has no tristate cell (no three_state pin in any
RVT standard-cell library), and the memlib comment is an unmeasured
proposal, as it says.

Not verified on the branch

  • The goldens in src/ram/test (generate_regfile_asap7.*ok) are not
    regenerated after 6a69961; OpenROAD's ram tests cannot be analysed
    from bazel-orfs (they need an @llvm repository). Regenerate with
    OPENROAD_SRC=<OpenROAD checkout> test/regfile/campaign/golden.py <openroad>.
  • Run on the branch: the LEC suite (12/12 after 6a69961; e5dcafd does
    not change the netlist), //test/regfile:{regfile,riscv32i}_clocked_test,
    riscv32i-regfile to global route (each row above), fix_lint,
    public_surface, host_tools. Not re-run on the branch head:
    //test/regfile:check_dissolve_test,
    //test/regfile:regfile_grt_build_test, //test:all,
    //test/planned_parent:all.

To carry on

test/regfile/campaign/README.md has the commands. One worktree per
lane: a lane builds from the patches in its own checkout, so two lanes
in one worktree change each other's inputs mid-run. A riscv32i run to
global route is about 10 minutes and under 1 GB.

Lined up, in parallel:

  1. Predecode (predecode.diff) on flat 45 %: one serial AND2 less
    on the select path.
  2. Dissolve, then unplace at 62 % (unplace.tcl): free placement of
    the better netlist.
  3. Never a macro: riscv32i with regfile.v replaced by the generated
    netlist, the core sized from the real cell area.
  4. Shape annealer at floorplan, before macro placement: register-file
    shapes with unchanged ports, the FakeRAMs, channels and the real
    standard-cell area; the chosen shape generated there and swapped in
    with swapMaster; mode netlist only. It must weigh select-line
    length as well as area.
  5. Write side: a clock gate per word instead of the per-bit AO22 hold
    mux, as generate_ram does. It changes CTS: decide before building.

Word groups (word-groups.diff, two words sharing a bit column's rows)
narrow the tiles 30 -> 26 sites; measured, not taken.

🤖 Generated with Claude Code

@oharboe

oharboe commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator Author

riscv32i register-file campaign: a clock gate per word reaches the flip-flops

Branch regfile/campaign-wip at 060865e, five lanes in parallel. Global route, 950 ps clock.

configuration min period core µm² std cells µm²
flip-flops (the gate) 954 4012 1284
flip-flops at 45 % (control) 951 5533 1273
AOI22/NAND/NOR read, 30-site tiles, flat, 45 % (before) 982 6156 1116
predecode, flat, 45 % 1000 6106 1112
dissolved, then placed freely, flat, 62 % 1012 4466 1171
never a macro, 62 % 994 3770 1144
write clock gate, flat, 45 % 951 5687 962
write clock gate, flat, 55 % 959 4644 980
write clock gate, flat, 62 % MPL-0003
never a macro + write clock gate, 62 % 955 3544 973

The write clock gate is the change that reaches the flip-flops. Each word gets one ICGx1, enabled by its write select, in place of the per-bit AO22 hold mux. This is how generate_ram already writes.

  • With one write port, a flop's D is the write data bit itself.
  • Array endpoints that miss timing fall from 920 to 7.
  • Standard-cell area is 25 % under the flip-flops'.

Never a macro is the shape that wins on area. riscv32i reads the generated netlist in synthesis in place of regfile.v. Its cells are placed and sized like any others: no macro tiling, no dissolve. With the write clock gate it reaches 955 ps against the flops' 954 (parity) on a core 12 % smaller. As a macro, the smaller netlist does not fit riscv32i's 62 %.

Negatives:

  • Predecode: its xp33 select drivers need three buffers on the 53 µm select line.
  • Dissolving the array and then placing it freely: no faster than keeping it fixed.

Not verified:

  • The clock-gated array's function: there is no simulation or equivalence check of generate_regfile's output yet. //test/structured_gen_lec checks the other generator.
  • Hold, clock power and CTS with 31 gated clocks.
  • The src/ram/test goldens, which still lag.
  • //test/regfile:all passes, including riscv32i_clocked_test with the clock gate: every register is clocked at floorplan.

Next, if this direction holds: never a macro as a flow mode. The memories step would hand synthesis the generated netlist in place of the module. That would retire the dissolve and its row cutting rather than add to them. It changes this PR's design, so it waits for your call.

🤖 Generated with Claude Code

@oharboe

oharboe commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator Author

generate_regfile checked by LEC, clock-gated flops included

//test/structured_gen_lec now also checks OpenROAD's generate_regfile against yosys's synthesis of the RTL. It runs on the comb, fp, int and vf fixtures (banked and interleaved reads, the zero word, registered reads, up to 7 write ports), in both write styles. 21 of 21 pass.

The clock gate per word needed a carried kepler-formal patch. kepler-formal makes an integrated clock gate's latch opaque on purpose, so a clock-gated netlist could not be checked at all.

Patch kepler-formal-0002 is experimental and not upstreamed. It adds --skip-gated-clock-flops, which:

  • leaves out the inputs of each flop whose clock pin is driven by a cell output with no truth table but a combinational dependency (an ICG's GCLK);
  • warns with the count and lists the skipped flops in skipped_gated_clock_pos.txt;
  • compares everything else, i.e. every read, through every flop's output.

The write enable is the one thing it does not prove. test/regfile/sim covers that by simulation.

A clock-gated twin of fp8_unregistered, a read-address bug, is still caught, so the skip hides no read-path difference.

It is meant as a strong signal for automated loops, not as production sign-off.

🤖 Generated with Claude Code

oharboe added a commit that referenced this pull request Oct 7, 2026
#1187 at aeae86a, XSTile built from source to global route: every
block identical to row 12, the parent 3,386 ps against 3,162 (+7.1 %)
on row 12's own worst path. Recorded to revisit, not fixed: entry 47's
zero-wire census on the checkpoint separates #1184's mechanism, which
#1187 includes, from what the dissolve adds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
@oharboe

oharboe commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

Register-file ladder: ORFS's asap7 designs with a register file

test/regfile/ladder runs each design against a -regfile variant (carried ORFS patches) to global route. A row passes when the register file's minimum period is at most 3 % above the flip-flops'. The period is ORFS's own timing__fmax__clock:<name> for the slowest clock, port paths included; a reload of each arm's global-route ODB gives the same numbers. Both arms of every measured row have the same macro count.

design register file route min period, flops / regfile std-cell area power
riscv32i 32×32, 2R 1W module spec 923 / 922 ps (−0.1 %) −24.0 % −24.1 % passes
picorv32 32×32, 1R 1W seam (0093) 3 seeds: mean 444 / 450 ps (+1.4 %) −4.3 % −9.3 % passes on its noise band
ibex 32×32, 2R 1W, async reset module spec 970 / 960 ps (−1.0 %) −9.8 % −33.2 % passes
tinyRocket 32×32, 2R 1W seam (0093) 1282 / 1171 ps (−8.7 %) −4.7 % −21.2 % passes
swerv_wrapper 32×32, 4R 3W, async reset surgery + module spec 1297 / 1299 ps (+0.2 %) +0.6 % +3.6 % passes
cva6 (cv32a65x) 32×32, 4R 2W, async reset, later write wins surgery + module spec — blocked, see below

picorv32 at GPL_RANDOM_SEED 1, 2 and 3: +4.0 %, +3.4 %, −3.3 %. Each arm spreads by about 4 % across seeds; the single-seed difference was placement noise. Its worst paths in both arms are the multiplier's adder chains, not the register file.

swerv is the one row where the register file costs area and power: its flip-flop baseline already gates the clock per word, and its 992 reset flops are larger cells.

What the ladder added

  • OpenROAD 0011 async_reset <port> low|high: storage that resets to zero (DFFASRHQNx1, set tied off per word).
  • OpenROAD 0012: the port check reads SystemVerilog headers (#(...), widths in parameters, packed dimensions) and stops on a declaration it cannot read; unused <port>.
  • OpenROAD 0013 write_priority last: the later write port wins a collision (cva6's two commit ports).
  • ORFS 0094 SYNTH_VERILOG_SURGERY: a script edits copies of the sources before synthesis. Documented as a hack.
  • ORFS 0095–0098: the picorv32, ibex, swerv and cva6 variants. 0099: an inlined register file in place of a parameterized module. 0100: picorv32's seed variants.

Evidence that the register files compute what the RTL does

bazelisk test //test/regfile/ladder:all, nine tests:

  • Each spec's generated netlist against the RTL module it replaces, or against a behavioural memory for a seam spec: 20 000 random cycles with reads, writes and resets.
  • swerv and cva6: the module before and after surgery, with the register file as generated: 20 000 cycles, 38 and 41 resets, 0 mismatches. cva6's colliding writes are driven, so write_priority last is exercised.
  • A negative control (ibex's spec with its reset dropped) must mismatch, and does.

Two findings outside the register file

  • ORFS's aggregate timing__fmax metric is the maximum over clocks. riscv32i, ibex and picorv32 have a virtual I/O clock whose fmax is the more optimistic one, so the aggregate understates the period: ibex's read 807 ps against 970 ps for core_clock. The rows read the per-clock metrics.
  • ORFS never defines OPENROAD_CLKGATE, so the platform's OPENROAD_CLKGATE is a wire and every rvdffe word in swerv_wrapper reloads on every clock: its register file holds no value. Both swerv arms here define it (asap7/swerv_wrapper-yosys), so the flip-flop baseline is the gated register file SweRV's source describes. Both arms go through yosys, because the register-file inlining exists only there and swerv_wrapper itself uses SYNTH_USE_SYN.

Open

  • cva6: ORFS's own asap7/cva6, the flip-flop baseline, stops in slang's elaboration inside the Bazel sandbox ("Compilation failed", no diagnostic printed), while the same read succeeds outside Bazel. Its surgery and simulation are verified; the row waits on the baseline.
  • coralnpu: Regfile (8R 6W) and FRegfile (3R 2W) are flops among scoreboard and bypass logic; they need a surgery script of their own.
  • At global route, ibex's register-file arm spends most of its time in repair_timing's endpoint loop (incremental reroute and STA). Recorded, not gated.

Rows: bazelisk build //test/regfile/ladder:<design>_row. Worst paths per arm: //test/regfile/ladder:<design>_{flops,regfile}_paths.

oharboe added a commit that referenced this pull request Oct 8, 2026
… +3.6 %, placed files beat inlined

T1, #1187 at f681c30 from source: Frontend 2,170 -> 1,792 ps and
VecRegionModule 1,479 -> 1,278 with generate_regfile's new read path,
FltRegionModule 895 -> 951, the parent 3,276 ps (row 12 3,162, entry 48
3,386). The zero-wire census puts the parent's floor lowest of the four
measured (2,021 ps); the loss is wire and repeaters, #1184's mechanism
in part. T2b, the parent's three files inlined on the same plan: 3,529
ps and 29 % congestion, so the planned, dissolved files stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
@oharboe

oharboe commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

@maliberty FYI: this PR is the integration test for a register-file feature that spans OpenROAD and ORFS. It shows the OpenROAD and ORFS changes working together on ORFS's own designs.

TL;DR: a register-file generator, generate_regfile, built on the RAM generator. It replaces flip-flop register files in ORFS designs with a structured standard-cell array. Across ORFS's asap7 cores it holds the clock period within noise, cuts standard-cell area by up to 24 % and power by up to 33 %, and simulation shows each one computes what the RTL does.

design register file min period, flops → regfile std-cell area power
riscv32i 32×32, 2R 1W 923 → 922 ps (−0.1 %) −24.0 % −24.1 %
ibex 32×32, 2R 1W, async reset 970 → 960 ps (−1.0 %) −9.8 % −33.2 %
tinyRocket 32×32, 2R 1W 1282 → 1171 ps (−8.7 %) −4.7 % −21.2 %
picorv32 32×32, 1R 1W 444 → 450 ps (+1.4 %, mean of 3 seeds) −4.3 % −9.3 %
swerv_wrapper 32×32, 4R 3W, async reset 1297 → 1299 ps (+0.2 %) +0.6 % +3.6 %

Global route, each design's own config and clock; the period is the slowest clock's, port paths included. In every measured design the generated register file matches the RTL it replaces, simulated under random reads, writes and resets.

Synthesis time. Trivial for these examples. Yosys synthesis (1_2_yosys): ibex 41 → 28 s, picorv32 33 → 29 s, tinyRocket 46 → 41 s.

On XiangShan, synthesis with flip-flop register files ran forever. With this feature it completes. Together with parallel hierarchical synthesis and macros, XiangShan's run time is tolerable: overnight. I haven't studied exactly where the time went, and don't plan to: measuring "forever" would take days or weeks.

OpenROAD

  • generate_regfile in src/ram, built on the RAM generator. Multi-port, an integrated clock gate (ICG) per word, async reset, write priority. It checks its ports against the RTL module it replaces.

ORFS

  • AUTO_MEMORIES_REGFILES: lists the register files to generate. Each is inlined (placed and sized with the rest of the design) or placed as a macro.
  • Yosys seams: most cores keep the register file as an inline array with no module of its own (Rocket, picorv32). Synthesis cuts a module around the memory yosys infers there, and the generated register file replaces it. The RTL is never edited.
  • SYNTH_VERILOG_SURGERY: a deliberately hacky pre-synthesis hook. A script edits copies of the sources before yosys reads them: it carves out a register file yosys can't infer (swerv, cva6), ties off test bits, and so on. In my own flows that step lives in Bazel; this gives a make-based ORFS flow the same hook without Makefile hacking.

Here both are carried as patches on OpenROAD and ORFS. Details and open items are in the comment above.

oharboe added a commit that referenced this pull request Oct 8, 2026
A harness property learned simulating generated register files beside
their RTL (#1187): in Verilator a reset already asserted at the first
eval() never falls, so a cell model that clears on its reset's falling
edge never does, while RTL that clears on a clock edge still clears.
Release the reset for one evaluation before asserting it; a flop behind
a clock gate has no other way to clear.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
oharboe added a commit that referenced this pull request Oct 8, 2026
A KPI row for a change that is not on main, this once, to show where it
stands while its OpenROAD and ORFS parts are in review (OpenROAD#11669,
OpenROAD-flow-scripts#4651): #1187 builds the register files with
OpenROAD's generate_regfile through ORFS's AUTO_MEMORIES_REGFILES in
place of bazel-orfs's structured_gen. Measured on 2026-10-07 at
aeae86a, #1187's commit of that day: the blocks unchanged, the parent
3,386 ps, 224 ps slower than run 12, its worst path CtrlBlock's own; the
cause is not diagnosed.

The README stated the KPI as 3,860 ps, three rows stale; it now states
main's 3,162 ps and says run 13 is in flight.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
oharboe added a commit that referenced this pull request Oct 8, 2026
… +3.6 %, placed files beat inlined

T1, #1187 at f681c30 from source: Frontend 2,170 -> 1,792 ps and
VecRegionModule 1,479 -> 1,278 with generate_regfile's new read path,
FltRegionModule 895 -> 951, the parent 3,276 ps (row 12 3,162, entry 48
3,386). The zero-wire census puts the parent's floor lowest of the four
measured (2,021 ps); the loss is wire and repeaters, #1184's mechanism
in part. T2b, the parent's three files inlined on the same plan: 3,529
ps and 29 % congestion, so the planned, dissolved files stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
@oharboe
oharboe force-pushed the regfile/auto-memories branch from 46afcbf to 14e9af1 Compare October 8, 2026 07:39
@oharboe

oharboe commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

cva6 (cv32a65x), the last rung: passes. Rebased on main, which now carries #1200: the flip-flop baseline used to stop in slang's elaboration in the Bazel sandbox because a generated BUILD file hid common_cells/registers.svh from cva6's include group.

design register file min period, flops → regfile std-cell area power
cva6 (cv32a65x) 32×32, 4R 2W, async reset, later write wins 909.5 → 909.8 ps (+0.03 %) 14,458 → 13,900 µm² (−3.9 %) 185.1 → 159.2 mW (−14.0 %)

Global route, ORFS's own asap7/cva6 config and clock against the cva6-regfile variant (surgery + module spec, write_priority last); 8 macros in both arms. Run time to global route: 1,698 → 1,550 s, synthesis 483 → 454 s.

Correctness: on the rebased branch all 9 ladder tests pass, including cva6_surgery_test (ORFS's ariane_regfile against the surgered one with the generated register file, colliding commit-port writes driven) and cva6_sim_test.

With cva6, every design on the ladder passes: riscv32i −0.1 %, ibex −1.0 %, tinyRocket −8.7 %, picorv32 +1.4 % (mean of 3 seeds), swerv_wrapper +0.2 %, cva6 +0.03 %.

Also in the rebase: row.py now counts global route in a row's stage times; it used to leave the grt log out of the total.

@oharboe

oharboe commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

coralnpu, rung 7: passes, and it's the first rung where the wrapper around the register file mattered.

coralnpu (CoreMiniAxi), global route flops regfile
min period 2,009 ps 1,899 ps (−5.5 %), meets the 1,900 ps target, so a bound
setup TNS −93,647 ps 0
std-cell area 16,525 µm² 15,487 µm² (−6.3 %)
power 33.0 mW 35.8 mW (+8.5 %)
stage time, total 4,514 s 1,072 s (4.2× less)

Register file: Regfile, 32×32 with 8 reads and 6 writes (twice the ports of any earlier rung), x0 zero, async reset high, colliding writes ORed.

An idiomatic baseline first (ORFS 0102). ORFS's asap7/coralnpu had problems of the flow's own making, fixed here without touching the snapshot:

  • a 10 ps clock (ORFS's rules file implies about 2.04 ns achieved); the SDC now uses the platform's constraints.sdc at 1,900 ps, with io_te held at 0 and the async reset not timed;
  • USE_ASAP7 fakerams with no write mask, so every masked write wrote all 16 bytes; surgery now turns each TCM SRAM into a firtool read-write memory module, and AUTO_MEMORIES generates masked macros;
  • firtool's verification layers concatenated in (61 binds); surgery strips them, which is firtool's output without layers;
  • ClockGate left as a latch and an AND; it now maps onto ASAP7's ICG through SYNTH_POST_HIERARCHY_SCRIPTS (0101 declares the variable);
  • the kept hierarchy is now coralnpu's units by name (SYNTH_KEEP_MODULES, OPENROAD_HIERARCHICAL=1).

CoreMiniAxi.sv is unchanged: the adaptation is a config, an SDC, a surgery script, a yosys script and a spec.

Two iterations (0103). The first moved Regfile's 31 words into Regfile_rf and nothing else:

  • period −0.2 %, but area +1.7 %, power +9.5 %;
  • global route stopped after more than 8,800 s, against 1,322 s for flops.

Regfile's read bypass selected among all 31 words' decoded write data, and that decode now fed nothing but the bypass. The second iteration rewrites the bypass as a write-through read: the read address compared with each write port's address, using only the register file's ports. That gives the table above. The pattern applies to any dissolved register file; the generator stays plain storage.

Where the time goes. In the flip-flop arm, repair_timing is 3,369 s of 4,514 (75 %): 2,421 s inside CTS (clock_tree_synthesis itself takes 16 s) and 928 s inside global route, chasing a target the design does not reach. In the register-file arm, which meets it, repair takes 83 s. The flip-flop arm, re-run, reproduced its QoR exactly (TNS −93,646.7 both times); its CTS time moved 2,181 → 2,562 s.

Correctness. All 13 ladder tests pass. The coralnpu ones:

  • coralnpu_surgery_test: Regfile as ORFS has it against the surgered one with the generated array, 20 000 random cycles, colliding writes on six ports, masks, readSet, bypass, resets;
  • coralnpu_sim_test: the spec against the behavioural register file;
  • coralnpu_write_last_sim_test: a negative control (later write wins) that must mismatch, and does;
  • coralnpu_synth_test: checks the build is what 0102 says.

Open: the +8.5 % power, where clock skew also rose (306 → 415 ps), points at the per-word clock gates. And the repair long tail: ORFS runs repair_timing without per-iteration progress, so finding where repair stops paying needs a run with it.

@oharboe

oharboe commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

A correction, a bug fix, and clock gate vs mux per word.

Bug: two design directories that shared a DESIGN_NICKNAME shared a config. @orfs_designs keyed DESIGNS by nickname, so a variant that includes its base's config.mk replaced the base's entry. Since the seed variants, asap7/picorv32-regfile ran picorv32-regfile-seed3's config (GPL_RANDOM_SEED 3). Fixed in private/designs.bzl: the directory key always holds the directory's own config. designs_by_directory_test checks it and fails on the old keying. Only this PR's variants shared a nickname, all asap7.

Correction: picorv32's seed band. The earlier mean of +1.4 % counted one register-file arm twice. Re-run, all three seeds on one ORFS:

picorv32 flops regfile Δ period Δ area Δ power
seed 1 443.35 ps 445.13 ps +0.4 % −4.1 % −8.8 %
seed 2 462.64 ps 443.48 ps −4.1 % −4.9 % −10.1 %
seed 3 434.10 ps 451.02 ps +3.9 % −6.0 % −12.0 %
mean 446.7 ps 446.5 ps −0.03 %

Clock gate per word or mux per word (write_style; ORFS 0104 adds -mux variants):

design flops clock gate mux
swerv_wrapper power 84.5 mW 87.1 mW (+3.1 %) 78.8 mW (−6.7 %)
area 17,123 µm² 17,281 µm² 17,188 µm²
period 1,293 ps 1,299 ps 1,298 ps
coralnpu power 63.3 mW 45.4 mW (−28 %) 52.1 mW (−18 %)
area 16,563 µm² 15,512 µm² 15,743 µm²
clock skew ~400 ps 73 ps

The better style depends on the design: muxes for swerv, clock gates for coralnpu. So write_style is a per-design choice in the spec, not a default to fix. Power is estimated without switching activity; read it with that caveat. The skew cost of a clock gate per word is structural, though: many small gated subtrees under CTS.

coralnpu's numbers are now as ORFS draft The-OpenROAD-Project/OpenROAD-flow-scripts#4653 has it, without the ClockGate mapping onto ASAP7's ICG. The flip-flop arm reproduced its QoR exactly across two runs.

@oharboe

oharboe commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

riscv32i in each generate_regfile shape (ORFS 0105, rows riscv32i_netlist and riscv32i_macro), one run each:

riscv32i period std-cell area power stage time
flops 907 ps 1,289 µm² 31.1 mW 228 s
inlined 922 ps (+1.6 %) 985 µm² (−24 %) 23.9 mW (−23 %) 180 s
mode netlist (macro-placed, then dissolved) 951 ps (+4.9 %) 1,006 µm² (−22 %) 24.3 mW (−22 %) 166 s
mode macro does not place: MPL-0003, its edge pins need channels the floorplan does not have

On XSTile the order flips (entry 49: inlining +7.7 % against placed arrays). No shape dominates; it is a per-design choice. These numbers answer a review comment on The-OpenROAD-Project/OpenROAD#11669, where mode netlist's unplaced periphery was asked about.

oharboe and others added 21 commits October 9, 2026 14:28
The measurements so far (results.md), the scripts that took them
(measure.sh and the odb-debug queries), the generator variants not yet
measured in a flow (predecode.diff, word-groups.diff), golden and shape
tools for patch 0010, and the experiments lined up (README.md), so the
campaign can carry on, lanes in parallel, on a machine with room for
them. odb-debug sessions on each stage of both riscv32i variants are
manual targets in test/regfile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
… flops

generate_regfile gets write_style clock_gate: one integrated clock gate
per word, enabled by its write select, in place of the per-bit AO22 hold
mux, as generate_ram writes; riscv32i-regfile uses it at 45 %. At 45 %
riscv32i reaches 951 ps, the flops' 954 at 4012 um2 and 951 at the same
utilisation. As a macro it does not fit riscv32i's 62 % (MPL-0003).
riscv32i with the generated netlist in place of regfile.v, no macro and
no dissolve (never_macro.sh), reaches 955 ps on a core 12 % smaller than
the flops'.

Measured and negative: predecode (1000 against 982), the dissolved
array unplaced and placed freely (1012 against 1003).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
The campaign after the clock gate, every arm simulated first by
test/regfile/sim and measured inlined at riscv32i's 62 %: predecode, a
mux-tree read, a four-input read tree, stronger cells and the two
together. None beats 955 ps by more than the noise; results.md has each
arm and why the mux tree loses (the address fanout needs as many buffer
levels as the decode it replaces). The clock gate's cost: hold clean,
179 clock-tree buffers against 122, no hold repair in either design.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
… yosys makes

ORFS patch 0093, carried in bazel-orfs for now: a spec's
`memory <module>.<cell>` names a memory yosys infers inside a module, and
synthesis makes the seam while it reads the sources -- memory_libmap
with a memlib entry and techmap with a wrapper, both written from the
spec by gen_memories.py -- then reads the generated netlist as the
register file's body. The memory is found by cell and module definition
and selected by attribute (no slang $ name spelled); opt -full, scoped
to its module, makes Chisel's per-bit write enables one signal, or the
array maps as 32 one-bit slices. A memory not found once, or not mapped
to one instance, stops the build.

asap7/tinyRocket-regfile is Rocket's integer register file (RocketCore's
_T_288, 31 x 32, 2R1W) that way, the RTL unchanged: one rocket_rf,
32 clock gates, 3777 um2 at global route against tinyRocket's 3956.
test/tinyrocket_regfile checks the inventory: the register file in the
array's place, no flip-flop entry left for the array.

Patch 0092 takes the upstream review's change (OpenROAD-flow-scripts
#4643): memories.v's loop variable local to its block, the same memory
inferred with both frontends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
…gery

The register-file campaign on ORFS's asap7 designs, smallest first,
each against a -regfile variant at global route: a row passes when the
register file's minimum period is at most 3 % above the flip-flops'.

- test/regfile/ladder: ladder_row (both arms' stage logs and global
  route metrics into one JSON row), run_row.sh and report.py for the
  unattended runs, and regfile_sim_test: a spec's generated netlist
  simulated beside the RTL module it replaces, or beside a behavioural
  memory for a seam spec, under random reads, writes and resets. A
  negative control (ibex's spec with its reset dropped) must mismatch.
- OpenROAD 0011: generate_regfile async_reset, storage that resets to
  zero (DFFASRHQNx1, set tied off per word).
- OpenROAD 0012: the port check reads SystemVerilog headers and refuses
  a declaration it cannot read; `unused <port>`.
- ORFS 0094: SYNTH_VERILOG_SURGERY, a hack: a script edits copies of
  the sources before synthesis reads them.
- ORFS 0095, 0096: asap7/picorv32-regfile (seam), asap7/ibex-regfile
  (module spec with reset).
- .regfile specs are exported from ORFS design packages, and
  SYNTH_VERILOG_SURGERY's script is staged as a source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
…read QoR

- OpenROAD 0013: generate_regfile `write_priority last`, the later write
  port wins a collision (cva6's two commit ports).
- ORFS 0097: asap7/swerv_wrapper-yosys and -regfile. A surgery script
  rewrites dec_gpr_ctl as SweRV's source reads, the array in
  dec_gpr_ctl_rf. Both arms define OPENROAD_CLKGATE: without it the
  platform's clock gate is a wire and swerv's register file holds no
  value, a baseline cheaper than any register file that does.
- ORFS 0098: asap7/cva6-regfile, ariane_regfile recognised token for
  token and its array moved into ariane_regfile_rf.
- ORFS 0099: an inlined register file in place of a parameterized module
  (ibex): the instances' parameters are dropped first.
- ORFS 0094: SYNTH_VERILOG_SURGERY's description says why: the RTL
  changes, so a fix is an operation redone on every version.
- Simulation: surgery_cmp_test and cva6's bench compare the module
  before and after surgery, the register file as generated; colliding
  writes are driven under write_priority last. The driver releases
  reset once before asserting it, so a clock-gated flop sees an edge.
- Rows: the register-to-register minimum period from ORFS's global
  route metrics (the jsons output group), macro counts for a
  like-for-like flag, repair_timing progress, and a worst-path probe
  per arm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
…rv32

- Rows read each clock's timing__fmax__clock:<name> and gate on the
  slowest: ORFS's aggregate timing__fmax is the maximum over clocks, and
  riscv32i, ibex and picorv32 have a virtual I/O clock whose fmax is the
  more optimistic one. find_clk_min_period's argument is
  include_port_paths: the period includes port paths, and the report
  says so.
- The worst-path probe reports the period with and without port paths.
- ORFS 0100: picorv32 and picorv32-regfile at GPL_RANDOM_SEED 2 and 3,
  ladder rows of their own: picorv32's difference is read against its
  seed spread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
row.py took 5_1_grt.log as the place to read the routed timing from and
skipped it when it summed stage times, so every row's total left out
global route.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
…verdict

gen_cmp_tb reads an ANSI header where one declaration names several
ports (firtool writes `input clock,` then `reset,`): the names after
the first carry its direction and width. surgery_cmp_test passes
surgery_args, the design's SYNTH_VERILOG_SURGERY_ARGS. tb_main judges a
negative control on being caught before it asks for a reset: the
control stops at its tenth mismatch, which may come first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
ORFS 0101 declares SYNTH_POST_HIERARCHY_SCRIPTS so a config.mk can set
it, and config_mk_parser stages it. ORFS 0102 makes asap7/coralnpu a
synthesis build of its snapshot: firtool's verification layers
removed, the TCM SRAMs as firtool memory modules that AUTO_MEMORIES
turns into masked macros instead of USE_ASAP7's maskless fakerams,
ClockGate on ASAP7's ICG, the platform's constraints.sdc at 1,900 ps
instead of a 10 ps clock, coralnpu's units kept by name. ORFS 0103 is
coralnpu-regfile: Regfile's 31 words moved into Regfile_rf, 8 reads
and 6 writes ORed on a collision, async reset high, and generated.

coralnpu_synth_test checks the synthesis is that build;
coralnpu_surgery_test compares Regfile before and after surgery with
the generated register file; coralnpu_sim_test the spec against the
behavioural Regfile_rf, and coralnpu_write_last_sim_test, the spec
with the later write winning, must mismatch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
A variant that includes its base design's config.mk names the base's
SYNTH_POST_HIERARCHY_SCRIPTS script from another package
(asap7/coralnpu-regfile, coralnpu's clockgate.ys).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
ORFS 0103's surgery also rewrites each of Regfile's eight read bypasses,
a select by read address over every word's decoded write data, into a
compare of the read address with each write port's. It is the same
function (coralnpu_surgery_test) and leaves the per-word write decode,
which the generated register file does inside, with no reader. At
global route the register file goes from a long tail of repair to
meeting 1,900 ps: 2,009 -> 1,899 ps, std-cell area -6.3 %.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
row.py records, per stage, the commands ORFS's log_cmd timed (those of
5 s or more), and repair_timing's progress at CTS as well as at global
route: the breakdown that shows a long tail of repair.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
#4653 is coralnpu's upstream draft, and it can only use what will be
upstream: the ClockGate mapping onto ASAP7's ICG needs a hook that is
only carried here. 0102 drops it and 0101, which declared that hook's
variable for it; coralnpu_synth_test no longer expects ICG cells.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
ORFS 0104 adds coralnpu-regfile-mux and swerv_wrapper-regfile-mux, the
register files with write_style mux; coralnpu_mux and swerv_wrapper_mux
compare them with flops.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
@orfs_designs keyed DESIGNS by platform/DESIGN_NICKNAME, adding a
directory key only when it differed from the nickname. Variants that
include a base design's config.mk share its nickname, so the last one
parsed replaced the base's entry, and orfs_design() found it for the
base's own package: since the seed variants, asap7/picorv32-regfile ran
picorv32-regfile-seed3's config (GPL_RANDOM_SEED 3), and since the mux
variants coralnpu-regfile and swerv_wrapper-regfile ran the mux spec.
The directory key now always holds the directory's own config; the
nickname key is a fallback that never overwrites one.

designs_by_directory_test checks the seed and mux variants against
their bases; it fails on the old keying.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
repair_timing -verbose marks each progress row's iteration with its
phase, `10*` in the main phases and `1500+` in last gasp, and restarts
the count per phase. row.py took only plain numbers and kept nothing but
the final row; it now keeps every row with its phase, in log order,
which is the time axis.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
ORFS 0105 adds riscv32i-regfile-netlist and -macro, the register file
macro-placed in mode netlist (dissolved, the periphery the parent's) and
in mode macro (fully placed). With riscv32i (flops) and riscv32i-regfile
(inlined) they are a few-minute A/B/C of the shapes; riscv32i_netlist
and riscv32i_macro compare them with flops. mode macro does not place
here (MPL-0003): its edge pins need channels the floorplan does not have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
main carries the base of this branch as one commit: OpenROAD 0010 at
OpenROAD#11669's head, which has async_reset, SystemVerilog ports,
`unused` and write_priority, and ORFS 0089, 0091 and 0099 as #4651
rebuilt on #4650. Rebased onto it:

- OpenROAD 0011, 0012 and 0013 are gone, folded into main's 0010; the
  designs that needed them (0096, 0097, 0098) say 0010.
- 0099 is listed once, where main lists it.
- 0093 refreshed onto main's 0089 and 0091: FlowVariables.md's moved
  row, run_regfiles's black-formatted loop, and the register-file lists
  in gen_memories_test's flow contract (regfiles.txt, inline.txt,
  seams.txt).
- check_memories_applied.py reads the module from the first field of a
  blackboxes.txt line, which carries an area since 0090.

The whole ORFS series applies at ef421749 (bazel fetch of the ORFS
repository) and ORFS's gen_memories_test passes on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
asap7/coralnpu and asap7/coralnpu-regfile are in
The-OpenROAD-Project/OpenROAD-flow-scripts#4653, its files the same as
ORFS patches 0102 and 0103 but for black's line breaks in surgery.py and
a space in constraint.sdc. The ladder stops carrying them: 0102 and
0103 go, 0104 keeps only swerv_wrapper-regfile-mux, and coralnpu's
rows, its synthesis, surgery and simulation tests leave
test/regfile/ladder. designs_by_directory_test checks the swerv mux
variant against its base.

The design DSL stops exporting .ys files: no design in the series names
a SYNTH_POST_HIERARCHY_SCRIPTS script from another package since
coralnpu took #4653's form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
@oharboe
oharboe force-pushed the regfile/auto-memories branch from 0bb6b3d to 7ee797b Compare October 9, 2026 12:29
@oharboe

oharboe commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Closed as reference only; the branch stays.

Main carries the base of this work (1644583: OpenROAD 0010 at #11669's head, ORFS 0089/0091/0099 as ORFS #4651), and #1209 took everything else that stood on main alone. What this branch still held at its tip (7ee797b):

  • Seams, ORFS patch 0093: a register file in place of a memory yosys infers inside a module, with asap7/tinyRocket-regfile and its seam_test. Nothing on main needs it today; pick it up from here when a design does.
  • The ladder, test/regfile/ladder and ORFS 0095–0098, 0100, 0104, 0105: -regfile variants of picorv32, ibex, swerv, cva6 and riscv32i against their flip-flop arms at global route, plus simulation of each generated register file against the RTL module it replaces, which exercises 0010's async_reset, SystemVerilog ports, unused and write_priority on real cores. Its per-design rows were never recorded in the repository and are not reproduced here.
  • The riscv32i campaign, test/regfile/campaign and ORFS 0101: results.md has the measurements. Riscv32i's register file reaches parity with flip-flops (955 ps against 954 ps, on less area) with a clock gate per word and no macro.

coralnpu lives in OpenROAD-flow-scripts#4653.

🤖 Generated with Claude Code

@oharboe oharboe closed this Oct 9, 2026
oharboe added a commit that referenced this pull request Oct 9, 2026
…rity under test

OpenROAD patch 0010 (OpenROAD#11669) has spec keys nothing on main
exercised: async_reset, the port check reading a SystemVerilog header,
`unused` inputs and `write_priority last`. OpenROAD's own tests for them
do not run from bazel-orfs. Each is now simulated against the RTL it
replaces, every read compared under random reads, writes and resets,
seconds per test:

- ibex_sim_test: ibex's register file, ORFS's src/ibex_sv RTL as it is,
  with its async reset (29 resets in 20,000 cycles), a parameterised
  SystemVerilog header and two unused inputs. ibex_no_reset_sim_test,
  the spec without the reset, must mismatch.
- write_last_sim_test: an 8 x 8 file with two write ports that collide,
  the later port winning. write_first_sim_test, RTL where the earlier
  port wins, must mismatch, or the stimulus never collides.

The harness (sim.bzl, gen_tb.py, tb_main.cc) is the register-file
ladder's from the closed #1187, without its surgery comparison.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
oharboe added a commit that referenced this pull request Oct 9, 2026
The riscv32i register-file campaign of the closed #1187 reached parity
with the flip-flops (955 against 954 ps, a 12 % smaller core) with a
clock gate per word and inlining; ORFS patch 0091's AutoMemories.md has
that. This keeps the rest: predecode, a mux-tree read, wider and
stronger read trees, word groups, bit folds, a freely placed array and
a macro, each with what it measured and why it lost; what sets the
period now; the clock gate's cost; and that inlining loses on XSTile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
oharboe added a commit that referenced this pull request Oct 9, 2026
The parent's worst paths are a mass of CtrlBlock control (a false-path
peel: about 9 ps a register over 40), and the repairs before global
route time them with estimated wires. With the route's parasitics the
stage's own setup repair takes the parent from 3,276 to 2,406 ps on
row 14's build (#1187 at f681c30): 1,525 VT swaps, 74 buffers, 34 pin
swaps; its worst path is now CtrlBlock's flush redirect into Frontend.
The blocks are unchanged, so the design period is the parent's, 2,406.

SKIP_INCREMENTAL_REPAIR goes from the parent; HOLD_SLACK_MARGIN -1000 in
its grt stage leaves hold to CTS's repair, as this stage's hold repair
took an earlier run of it to 120 GB. Global route costs 84 min against
19 and peaks at 102 GB against 83.

KPI row 15 is that measurement, in flight like rows 13 and 14: it was
built on #1187's commit, not on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant