Skip to content

regfile: why a register file placed as a macro needs a bigger core (study) - #1221

Closed
oharboe wants to merge 26 commits into
mainfrom
regfile/macro-area
Closed

oharboe wants to merge 26 commits into
mainfrom
regfile/macro-area

Conversation

@oharboe

@oharboe oharboe commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Study for The-OpenROAD-Project/OpenROAD#11669: why riscv32i's register file placed as a macro needs a bigger core than the same cells inlined. Draft: the fixes come here one at a time, each measured before it moves to the OpenROAD PR or the ORFS patches.

test/regfile/riscv32i builds riscv32i's register file in each shape the generator offers (now generate_ram), on ORFS's own riscv32i config, beside ORFS's riscv32i (flip-flops). ledger.tcl accounts for every µm² of the core on any stage's ODB.

riscv32i, 62 %, global route, 950 ps clock. Minimum period from placement parasitics on the global-route ODB (about 5 ps off the route log's reading); the core is sized by utilization, so the channel width doesn't change it.

shape core µm² RTLMP_MIN_CHANNEL_SIZE 8 8 (riscv32i) 2 2 (*_narrow)
flops 4012 950 ps 992 ps
inlined 3544 944 ps 950 ps
inlined, periphery verilog 3516 938 ps 946 ps
netlist, 996 µm² abstract (macro-placed, then dissolved) 4319 953 ps FLW-0004, placed partly outside the core
netlist, service_sites 0, 835 µm² abstract 4067 MPL-0003 939 ps
macro 4319 MPL-0003 PDN-0233
after the fixes: netlist, 673 µm² abstract + row halo, 62 % 3803 MPL-0003 MPL-0003
after the fixes: netlist, 55 % 4283 938 ps
netlist, periphery verilog, 55 % 4287 940 ps

At 55 % with the padded abstract, AutoMemories.md measured 959 ps on 4644 µm².

Left unset, ORFS uses 20 20 and every shape fails macro placement, flops included.

The register file's abstract is 53.5 × 18.6 µm = 996 µm² and holds 478 µm² of cells. The rest is a decode column mode netlist leaves to the parent (225 µm²), row ends and service columns (198 µm²) and tile slack (95 µm²). initialize_floorplan -utilization charges the whole abstract: 96 % of the 775 µm² larger core.

Steps

  • The ledger and the four shapes, *_narrow beside them.
  • service_sites 0, no code change: the abstract drops to 44.8 × 18.6 µm = 835 µm² and the core to 4067 µm², and macro placement fails with MPL-0003. riscv32i's RTLMP_MIN_CHANNEL_SIZE 8 8, sized for its FakeRAMs, puts the macros at 3279 µm² with channels against 2239 µm² without; the padded abstract was what gave the placer room. So the generator fix needs the ORFS side with it.
  • RTLMP_MIN_CHANNEL_SIZE 2 2 on every shape (table). Narrow channels cost flops 42 ps, so the fix is a narrow channel for the register file only, not a global setting.
  • OpenROAD, generate_regfile (patch 0010): in mode netlist the abstract is the placed core's bounding box plus one row for row parity, and no service columns by default: 996 → 673 µm², 48 % → 72 % cells.
  • ORFS (patch 0089): each mode-netlist register file gets an instance halo of MACRO_ROWS_HALO_X/Y, so the rows cut around the FakeRAMs as it dissolves stay clear of its core (FLW-0014 without it; the padded outline hid this).
  • At 62 % riscv32i's four 42 µm tall FakeRAMs and the 17 µm register file with its halo need 63 µm of a 61.6 µm core and do not tile; at 55 % it reaches global route at 938 ps on 4283 µm².
  • Tests: OpenROAD's ram tests (generate_ram's 4 and a golden test per new option) run here as //test/regfile:openroad_ram_tests; the regfile tests read //test's asap7 files instead of a symlink bazel's patcher cannot create.
  • periphery generator|verilog: who builds the periphery. verilog writes it as assigns for synthesis to map with the parent; an option, since which is better depends on the integration. Simulated against riscv32i's RTL.
  • periphery verilog with mode netlist: the macro is the core alone, <module>_core, and a wrapper of the periphery is read in the module's place (ORFS patch 0108). Simulated against riscv32i's RTL.
  • Port into generate_ram (Austin's generator), one patch per future PR:
    • 0011, more than one write port: a decoder, data, write enables and address per port, the last port winning a word two write. generate_ram's existing tests unchanged; a new golden test in its format; simulated against its behavioral model on Nangate45 (tristate bitlines), collisions included. lib_to_verilog learned tristate outputs and Nangate45's quoted flip-flops to make that simulation possible.
    • 0012, a read path without tristates: -read_style and_or, an AOI22/NAND2/NOR2 tree per bit column, the default where the library has no tristate cell (asap7). A flop whose output is its data inverted (asap7's DFFHQNx1) taken there; buildPortMap now counts next_state in a flop's polarity. Golden asap7 test in generate_ram's format; simulated in three shapes against the behavioral model.
    • 0013 -zero_word: a word that reads 0 and drops its writes.
    • 0014 -reset_value: asynchronous reset of every word, per bit cleared or preset by the flop's own polarity; polarity and synchronous reset adapted outside.
    • 0015 -banks, -bank_columns, -bank_order: each word decoded whole, so banks need not be powers of two (XiangShan's 224 words in 4 interleaved banks). Also gives every inverter column a port: arrays with fewer rows than address bits had no address inverters.
    • 0016 -rb_ports: banked read ports, an address and data per bank.
    • 0017 -read_latency 1: registered read addresses.
    • 0018 -write_style mux: a hold mux per bit instead of a clock gate per word.
    • 0019 -periphery unplaced: the core alone placed and outlined, pins on a layer's tracks where their nets land (generate_regfile's mode netlist).
    • Each with a golden test in generate_ram's format; test/regfile/sim simulates 24 shapes of netlist against generate_ram's behavioral model on Nangate45 and asap7, 20 000 cycles each, no mismatch. generate_ram's own tests unchanged.
    • 0020 -cells {role master} and -name: cells by role (the smallest asap7 cells are the dont_use xp ones and the hold buffer), blocks named per module. findMaster4 now reads the pins of the cell it picks.
    • 0021 -decoder word: a balanced AND2 tree per word; the row decoder's AND2 chain was 60 ps of riscv32i's 220 ps read path.
    • 0022 -bit_folds k: a word in k rows, its write logic shared by its folds; XiangShan's IntRegFile (12 reads, 9 writes) went from 1458 x 31 um to square.
    • 0023 -periphery verilog: the core a block of its own, the periphery a Verilog wrapper.
    • 0024 -write_priority or: ports writing one word in a cycle write the OR of their data, as firtool's register files describe it (generate_regfile's default); the mux-write equivalence checks against XiangShan's RTL failed without it.
    • 0022, amended: -bit_folds with a short last fold (FtqMetaQueueRedirect, 211 bits in 4; FtqMetaQueueResolve, 954 in 8).
    • 0025 -service_columns {every sites}: empty columns in the array for a parent's buffers, generate_regfile's tap_columns/service_sites.
    • 0026 -bank_column_layout contiguous: a row's words side by side instead of interleaved bit by bit (below).
  • ORFS 0109: AUTO_MEMORIES_REGFILES through generate_ram: memories/regfile_ram.py translates a spec to generate_ram_netlist options and writes a wrapper in the RTL module's place (ports from the yosys JSON, reset polarity adapted there); flops renamed to store_name/read_reg_name. Every spec translates, kept macros included (pins placed, a power grid with VDD/VSS pins, a timing model with its area).

riscv32i through generate_ram (global route, placement parasitics, 950 ps clock):

shape generator core µm² min period
inlined, 62 % generate_regfile 3544 944 ps
inlined, 62 % generate_ram 3503 943 ps
netlist, 55 %, 2 µm channels generate_regfile 4283 938 ps
netlist, 55 %, 2 µm channels generate_ram 3968 945 ps
netlist, 62 %, 2 µm channels generate_ram 3512 939 ps

The macro-placed shape now fits riscv32i's own 62 % and matches the inlined one: the dissolved core is 55.3 × 8.6 µm and 93 % cells, where generate_regfile's padded abstract was 48 %.

  • ORFS onto generate_ram, 0010 dropped: the equivalence checks (test/structured_gen_lec, all 21 pass), structured_gen's golden test, the planner, the scaffolds and the planned parent's probes find generate_ram's RAM in the wrapper.

  • XiangShan's register files at real size in a parent (test/structured_netlist, through CTS). Tight outlines exposed what generate_regfile's half-empty tiles had hidden: a word's selects and write enables are as long as its bits are spread, and a parent buffers them inside a full array, where nothing legalizes.

    a word spans parent at place
    RobEntryFile, 4 words a row interleaved 278 µm 6,483 slew violations, fails
    RobEntryFile, contiguous (0026) 74 µm 608, passes
    RenameBufferFile, a word a row 258 µm fails
    RenameBufferFile, folded in 2 129 µm passes
    IntRegFile, 2 words a row, 8 folds 96 µm passes

    regfile_ram.py lays out a row's words contiguous by default and folds a word whose bits would span more than 150 µm. The scaffold's ring holds the periphery generate_ram leaves to the parent (7,027 µm², 82,040 cells for RenameBufferFile: a balanced decode per word and port).

  • The reply to Austin on OpenROAD#11669 (the planned PRs, each with its use case), and #11669 closed in favour of them.

  • The breakout: 16 single-concern OpenROAD PRs and the ORFS ones; the usage text and README of the ram module tidied first.

  • ORFS: the floorplan sized by the register file's cell area. On riscv32i this makes the core smaller, which the FakeRAMs already do not fit; to measure on XiangShan.

  • Macro mode's MPL-0003: later, with RTL MP placing banks of memories on XiangShan.

Every target is manual. To reproduce:

bazelisk build //test/regfile/riscv32i:riscv_top_netlist_grt
bazelisk run //test/regfile/riscv32i:netlist_floorplan_odb_debug

🤖 Generated with Claude Code

oharboe and others added 26 commits October 12, 2026 22:47
… area ledger

riscv32i's register file inlined, in mode netlist (macro-placed, then
dissolved) and in mode macro, on ORFS's own riscv32i config, beside
ORFS's riscv32i (flip-flops) to global route. Manual: a study.

ledger.tcl accounts for every um2 of the core on any stage's ODB:
macros, rows cut beyond the macro boxes, standard cells with the
register file's split out, taps, fill and empty row area. On this
commit, at 62 %:

  flops    951 ps, core 4012 um2
  inlined  954 ps, core 3544 um2
  netlist  954 ps, core 4319 um2; the 996 um2 abstract holds 478 um2
           of cells, and the floorplan is sized by the abstract
  macro    MPL-0003

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

netlist.regfile with service_sites 0: the abstract shrinks from
53.46 x 18.63 to 44.82 x 18.63 um (996 to 835 um2) and the core sized
by riscv32i's 62 % from 4319 to 4067 um2, and macro placement fails:
MPL-0003, no valid tiling, as in mode macro. The 8 um channels
(RTLMP_MIN_CHANNEL_SIZE) around the four FakeRAMs and the register
file need more core than the cells do; the larger abstract was what
gave the placer its room.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
riscv32i's RTLMP_MIN_CHANNEL_SIZE 8 8 is sized for its FakeRAMs; left
unset ORFS uses 20 20 and every shape, flops included, fails macro
placement. At 2 2, flops, inlined and netlist_service0 reach global
route; the core is unchanged (the floorplan is sized by utilization).
netlist (the 996 um2 abstract) fails FLW-0004, the register file placed
partly outside the core, and macro fails PDN-0233.

Minimum period at global route, placement parasitics, 950 ps clock:

  shape               8 8      2 2
  flops               950 ps   992 ps
  inlined             944 ps   950 ps
  netlist             953 ps   FLW-0004
  netlist_service0    MPL-0003 939 ps

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
generate_regfile (patch 0010), mode netlist: the abstract is the
bounding box of the cells it leaves placed, plus one row for a parent
of the other row parity, moved to the origin; the header column, the
address band and the ring margin were the periphery's or nobody's, and
initialize_floorplan -utilization charged them as macro area. No
service columns by default in mode netlist (service_sites -1: 40 in a
macro, none in netlist), the clock tree's buffers go wherever the
parent has room. riscv32i's register file: 53.46 x 18.63 um (996 um2,
48 % cells) to 39.58 x 17.01 um (673 um2, 72 %).

macro_place.tcl (patch 0089): each register file in mode netlist gets
an instance halo of MACRO_ROWS_HALO_X/Y. The rows are cut that far
around the other macros as it dissolves; without the halo the macro
placer could set a FakeRAM closer and the cut took the rows under the
register file's edge cells (FLW-0014). The padded outline hid this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
At riscv32i's 62 % the netlist shape no longer tiles at either channel
setting (MPL-0003): the four 42 um tall FakeRAMs and the 17 um register
file with its 2 um row halo need 63 um of a 61.6 um core. At 55 %, the
utilisation AutoMemories.md measured the padded abstract at (959 ps on
4 644 um2): 938 ps on 4 283 um2, global route, placement parasitics,
950 ps clock.

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

Patch 0010 gave src/ram/test an asap7 symlink to ../../../test/asap7,
as Nangate45 and sky130hd are upstream; bazel's patcher does not create
symlinks, so the seven generate_regfile tests could not run from this
repository (missing input file asap7/...). They now read
../../../test/asap7 and depend on //test:asap7_data and a new
//test:asap7_nldm_data, as src/est/test does.

test/regfile:openroad_ram_tests runs OpenROAD's ram tests, generate_ram's
four and generate_regfile's seven, so CI, which tests this repository's
packages and not @openroad's, covers the generator. All eleven pass on
the cropped netlist-mode abstract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
generate_regfile (patch 0010) gets `periphery generator|verilog`. With
`verilog` the periphery (address inverters, decode, write muxes, ties)
is written into the netlist as assigns of its cells' functions, for
synthesis to map with the parent's logic; the clock gates and the core
stay cells. `generator`, the default, is what it built before. Neither
dominates: it depends on how the array is integrated, so it is an
option. Not yet with mode netlist, whose cells the flow dissolves from
the netlist; that needs the core in a module of its own.

riscv32i, inlined, global route, placement parasitics, 950 ps clock:

  periphery   channels  min period  core
  generator   8 8       944 ps      3544 um2
  verilog     8 8       938 ps      3516 um2
  generator   2 2       950 ps      3544 um2
  verilog     2 2       946 ps      3516 um2

The netlist: 418 assigns for the 403 decode ANDs and 15 address
inverters, the 31 clock gates kept. test/regfile/sim simulates it beside
riscv32i's RTL as it does the other two write styles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
generate_regfile (patch 0010): with `periphery verilog` and `mode
netlist` the block is the core alone, <module>_core. The periphery
leaves it for a wrapper module in the RTL module's place: the periphery
as assigns, the clock gates as cells, around an instance of the core
declared a blackbox. A net the core shares with the periphery becomes a
core port; a module port only the periphery uses leaves the core. The
LEF, DEF and a liberty of area and pins describe the core; -verilog
writes the wrapper and <module>_core.v the core's netlist.

ORFS patch 0108: gen_memories inlines the wrapper (inline.txt) and makes
the core the macro: blackboxes.txt, regfiles.txt, its .lef, .lib and
.place, so it is macro-placed with its halo and dissolves.

test/regfile/sim simulates the wrapper and core beside riscv32i's RTL.
riscv32i at 55 %, global route, placement parasitics, 950 ps clock:
940 ps on 4 287 um2, the generator's periphery 938 ps on 4 283 um2. The
ledger matches riscv.dp.rf/*, the wrapper flattened by synthesis.

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

Three gaps that kept Nangate45's cells from simulating:

- A pin's `three_state` was ignored, so a tristate buffer was a plain
  buffer and every driver of a shared net drove it at once; it now
  releases the net (1'bz) while three_state holds.
- `ff ("IQ" , "IQN")`, Nangate45's quoted state variables, did not
  parse, so DFF_X1 assigned Q from an undeclared IQ; ff and latch take
  them bare or quoted.
- An asynchronous set or clear branch of a cell with Q and QN assigned
  both outputs without begin/end, a syntax error; each branch is now a
  block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
The first generate_regfile feature carried onto generate_ram, Austin
Rovinski's generator, as OpenROAD #11669's review asked: patch 0011, on
0010, one commit per future pull request.

generate_ram took one read/write or one write port (RAM-0030). Now any
number of write ports, each with its own decoder, data, write enables
and address; one write port keeps its names, and generate_ram's four
existing tests pass unchanged. A word written by two ports in a cycle
keeps the last port's data, as the behavioral model does; the AO22 for
the data is found by truth table, as the AOI22 is (an AOI22 and an
inverter where the library has none, as on Nangate45). Not yet with
-use_latch or -column_mux_ratio above 1.

Tests: make_7x7_1r2w_nangate45, golden LEF, DEF and behavioral model in
generate_ram's own format; and test/regfile/sim:ram_2w_sim_test, the
netlist beside the behavioral model on Nangate45 under random writes on
both ports, a quarter to the same word: 20 008 cycles, no mismatch.

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

The second generate_regfile feature carried onto generate_ram, on 0011.
generate_ram's read path is a tristate bitline per bit, so a library
without a tristate cell, asap7, could not generate (RAM-0010).
-read_style and_or reads through a tree per bit column: an AOI22 over
each pair of rows, then NAND2 and NOR2 by turns, each node in a row of
the span it covers. It is the default where there is no tristate cell,
and can be asked for where there is one.

A flop whose output is its data inverted is taken on that path, an
inverter at each root undoing it. buildPortMap reckoned polarity from
the state variable alone and took asap7's DFFHQNx1, whose state holds
!D, for a true output; it now counts next_state too. NAND2 and NOR2 are
found by truth table, as asap7 writes NAND2 as (!A) + (!B).

Tests: make_8x8_2r1w_and_or_asap7, golden DEF and behavioral model in
generate_ram's format; test/regfile/sim simulates generate_ram's 8 x 8
two-write RAM against its behavioral model in three shapes, Nangate45's
tristates, Nangate45's trees asked for and asap7's: 20 008 cycles each,
no mismatch. generate_ram's existing tests are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
The third generate_regfile feature carried onto generate_ram, on 0012:
-zero_word n reads 0 at word n and drops its writes, as a RISC-V x0.
The row keeps its place in the grid without flops or clock gate, and
the AND-OR trees are built over the other rows; with an inverted
storage output the zero word stays in them with its own select as its
term, so the root's inverter reads it as 0. Needs the AND-OR read path.

Tests: make_8x8_2r1w_zero_asap7 in generate_ram's format; and the
two-write RAM simulated with a zero word on asap7 (QN flops) and on
Nangate45's trees (Q flops), 20 008 cycles each, no mismatch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
The fourth generate_regfile feature carried onto generate_ram, on 0013:
-reset_value v resets every word to v asynchronously. Each bit's flop is
cleared or preset, the other pin held off by a tie per word slice; the
pins come from the flop's ff group, and which gives which value counts
the flop's output polarity (asap7's DFFASRHQNx1 stores !D). The reset
port takes the flop's polarity, rst_n or rst; a parent adapts any other.
An option, off by default: ibex, cva6, ariane, swerv and coralnpu reset
their register files, each for reasons of its own.

Tests: make_8x8_2r1w_reset_asap7 in generate_ram's format; the two-write
RAM with -reset_value 0xa5 simulated on Nangate45 (tristates and trees,
DFFRS_X1) and asap7: every word checked after each reset, random
resets among 20 000 cycles of writes on both ports, no mismatch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
The fifth generate_regfile feature carried onto generate_ram, on 0014:
the words in banks, contiguous or interleaved in the address, some side
by side and the rest stacked, as XiangShan's register files ask (224
words in 4 interleaved banks, 2 side by side). With banks each port
decodes every word's whole address, an AND2 tree per word, so a bank
need not be a power of two words; a read port is one tristate bitline
or one AND-OR tree over every word. Without -banks nothing changes.
Also gives each inverter column at least one port: an array with fewer
rows than address bits had no address inverters at all.

Tests: make_8x8_2r1w_banks_asap7 in generate_ram's format; the two-write
RAM simulated in five bank shapes, tristate and AND-OR, contiguous and
interleaved, side by side and stacked, with a zero word, and 12 words
in banks of 3 (the simulation now takes a word count). No mismatch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
The sixth generate_regfile feature carried onto generate_ram, on 0015:
-rb_ports n adds read ports with an address and a data output per bank,
each bank read independently in the same cycle, as XiangShan's integer
register file reads. Each (port, bank) is a read port of its own to the
generator, decoding and reading only its bank's words.

Tests: make_8x8_rb_asap7 in generate_ram's format; a plain and a banked
read port simulated against the behavioral model on Nangate45
(interleaved, tristate) and asap7 (contiguous, AND-OR, zero word), every
output every cycle, 20 008 cycles each, no mismatch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
The seventh generate_regfile feature carried onto generate_ram, on 0016:
-read_latency 1 registers every read port's address, a plain
positive-edge flop per bit in the address inverter's cell, as
XiangShan's register files read. A flop giving the address inverted
(asap7's DFFHQNx1) swaps the decoder's literals instead of adding a
cell. The storage flop serves where it is a plain one: findMaster
passes over two-output cells, as Nangate45's flops all are.

Tests: make_8x8_rb_latency_asap7 in generate_ram's format; registered
reads simulated on Nangate45 (Q) and asap7 (QN, banks, a banked read
port), 20 008 cycles each, no mismatch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
The eighth generate_regfile feature carried onto generate_ram, on 0017:
-write_style mux holds each word without a clock gate, every flop on
the clock and an AO22 per bit taking the new data or the flop's own
value (through an inverter where the flop's output is inverted), as
XiangShan's register files are built by default. clock_gate, the
default, is generate_ram's clock gate per word as before.

Tests: make_8x8_2r1w_mux_asap7 in generate_ram's format; the hold mux
simulated on Nangate45 (tristate) and asap7 (QN flops with banks and
registered reads; and with a reset), no mismatch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
The ninth generate_regfile feature carried onto generate_ram, on 0018:
generate_regfile's mode netlist. -periphery unplaced leaves the
periphery (address inverters and registers, decoders, select buffers,
each word's select cell, the input buffers) for the parent to place,
size and buffer; the outline is the placed core and a row; each pin
sits where its connections land, on -pin_layer's tracks, for a macro
placer before the parent dissolves the core. The same cut that took
riscv32i's register file abstract from 996 to 673 um2.

Tests: make_8x8_2r1w_unplaced_asap7 in generate_ram's format (176
cells placed inside the outline, 105 unplaced, 35 pins inside); the
netlist simulated with its periphery unplaced, no mismatch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
-cells {role master ...} picks the cell for each role instead of the
smallest of its function: on asap7 those are the xp cells ORFS marks
dont_use and the hold buffer HB1xp67, which a fixed core cannot resize
after; generate_regfile named its cells for that reason. An AOI22 or
AO22 given is checked and pinned by its truth table, and findMaster4
reads the pins of the cell it picks rather than the last one matched.
-name names the block, so two RAMs of one size can share a design; the
ORFS switch onto generate_ram needs both.

Tests: the two-write RAM on asap7 with every role given, simulated
against its behavioral model; the netlist uses each given cell.

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

ORFS patch 0109: a register-file spec generate_ram can build is built
by it. memories/regfile_ram.py translates the spec to
generate_ram_netlist's options and writes a wrapper in the RTL module's
place connecting its ports (widths from the yosys JSON) to
generate_ram's, a reset's polarity adapted there; the RAM, <module>_ram,
is inlined, or in mode netlist a macro with its periphery unplaced that
regfile_dissolve.tcl dissolves as before. Specs with flop names for
equivalence checking, folded bits or a Verilog periphery, and macros
kept to the end, stay with generate_regfile for now. The translation
names generate_regfile's asap7 cells where a spec names none, and
NOR2x1, measured: NOR2xp33 at the read tree's root cost 23 ps.

OpenROAD patch 0021: -decoder word, a balanced AND2 tree per word, as
the banks decode; generate_ram's row decoder is an AND2 chain, about
60 ps of riscv32i's 220 ps read path.

riscv32i, global route, placement parasitics, 950 ps clock:

  shape                       generator         core      period
  inlined, 62 %               generate_regfile  3544 um2  944 ps
  inlined, 62 %               generate_ram      3503 um2  943 ps
  netlist, 55 %, 2 um ch.     generate_regfile  4283 um2  938 ps
  netlist, 55 %, 2 um ch.     generate_ram      3968 um2  945 ps
  netlist, 62 %, 2 um ch.     generate_ram      3512 um2  939 ps

The macro-placed shape now fits riscv32i's own 62 % and matches the
inlined one: the dissolved core is 55.3 x 8.6 um, 93 % cells, where
generate_regfile's padded abstract was 48 %. At riscv32i's 8 um
channels it does not fit (MPL-0006).

test/regfile: the macro is RegFile_ram behind the wrapper; the blackbox,
keep-synth and dissolve-probe expectations say so, and the probe finds
the dissolved cells under the wrapper's instance. The ledger matches
riscv.dp/rf.ram.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
generate_ram puts a word in one row, a bit's cells side by side, so a
wide word with many ports is a long thin array: XiangShan's integer
register file (64 bits, 12 read and 9 write ports) came out 1458 x 31
um. -bit_folds k lays the word's slices in k rows; the decoder,
select-buffer and address-inverter columns take k rows a word. A folded
word is written whole, one write enable a port, and its write logic is
built in its first fold and shared by the others: as mask slices, the
folds had each repeated it.

ORFS 0109 translates a spec's bit_folds, and with store_name and
read_reg_name now renamed in the database, every XiangShan register-file
spec translates to generate_ram.

Tests: the two-write RAM folded on Nangate45 (2 folds) and asap7 (4
folds, banks, registered reads, hold mux), simulated against the
behavioral model, no mismatch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
generate_regfile chose its rows per word itself; generate_ram puts a
word in one row unless told. Where a spec names no bit_folds and the
first generation comes out more than 8 times as wide as tall, the
translation regenerates it folded into the power of two of rows nearest
the square: XiangShan's IntRegFile, 1458 x 31 um in one row, 247 x 242
um in 8. A heuristic with a high bar: riscv32i's 55 x 9 um strip, 6 to
1, is the shape that sits under its FakeRAMs, so it stays; a spec names
bit_folds to choose.

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

With every fold's input buffers in one cell a column, IntRegFile's 72
buffers a column (8 folds, 9 write ports) set its width: 16 columns
pitched 246.7 um wide around 182.3 um of widest placed cells, fill 58 %.
A row a fold for them, above the words, gives the columns back to the
bit cells: IntRegFile folded 8 is 182.3 x 242.2 um, 44 153 um2, below
its 44 497 um2 unfolded, where folding had cost 34 %.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
-periphery verilog makes the block the core alone, <name>_core, and
writes the periphery as a Verilog module <name> around it
(-wrapper_verilog): its logic cells as assigns of their liberty
functions, its clock gates, flops and ties as instances. A net the core
shares with the periphery becomes a core port, renamed to a Verilog and
liberty identifier, its net with it: the netlist names a port's net by
the port. ORFS 0109 translates the spec's periphery verilog: the macro,
or the inlined core, is <module>_ram_core inside generate_ram's
periphery module and the RTL's wrapper; the address registers keep
generate_ram's names there.

riscv32i, global route, 950 ps clock, periphery as Verilog:

  shape                    generate_regfile     generate_ram
  inlined, 62 %            3516 um2, 938 ps     3452 um2, 951 ps
  netlist, 55 %, 2 um ch.  4287 um2, 940 ps     3999 um2, 933 ps

Tests: the two-write RAM with its periphery as Verilog simulated on
Nangate45 (tristates, clock gates) and asap7 (AND-OR, banks, hold mux)
against the behavioral model, no mismatch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
ORFS 0109's translation now covers a register file kept as a macro to
the end: its pins placed on its edges by OpenROAD's pin placer on the
spec's pin layers (tracks made from those layers' own pitch), and its
liberty from write_timing_model, the block's own timing for the parent's
STA, with the area the flow costs it by written in. riscv32i's macro arm
gets there and stops at the floorplan: 61.5 x 8.9 um with its periphery
does not fit a 60.2 um core at 62 %. Every port an `unused` or `reset`
line lists is declared, not the first only.

test/regfile/cores: regfile_sim_test takes generator = "generate_ram",
which builds the spec through ORFS's own regfile_ram.py, the netlist
AUTO_MEMORIES_REGFILES inlines. ibex's register file through it matches
ibex's RTL under random reads, writes and resets (asynchronous reset to
0, unused inputs, x0); write priority as before; both negative controls
fail as they should. regfile_ram.py is exported from the generated flow
package for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
Nothing uses generate_regfile any more: the equivalence checks
(test/structured_gen_lec), structured_gen's golden test, the planner,
the scaffolds and the planned parent's probes build or find
generate_ram's RAM, through ORFS's regfile_ram.py, in the wrapper that
takes the module's place.

What the XiangShan register files needed beyond 0011 to 0023, each an
option on generate_ram, default unchanged:

- 0024 -write_priority or: colliding writes OR'd, as firtool's register
  files describe them (the mux-write equivalence checks failed without).
- 0022, amended: -bit_folds with a short last fold (211 bits in 4,
  954 in 8).
- 0025 -service_columns: generate_regfile's tap_columns and
  service_sites, which regfile_ram.py had ignored.
- 0026 -bank_column_layout contiguous: a row's words side by side, not
  interleaved bit by bit; RobEntryFile's parent found 6,483 slew
  violations on selects running the row, 608 with it, and legalizes.

regfile_ram.py: write_priority, bank_column_layout (contiguous by
default where a row holds more than one word, as generate_regfile's tile
per word), the service keys, a power grid and VDD/VSS pins for a macro
kept to the end, and a fold where a word's bits would span more than
150 um (RenameBufferFile, a word a row 258 um wide, did not legalize).

The scaffolds' outlines are generate_ram's, and their ring holds the
periphery generate_ram leaves to the parent.

ideas/tile-generator.md: a tile generator beside generate_ram, for
later in the campaign.

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

oharboe commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #1234, which carries everything from this study.

@oharboe oharboe closed this Oct 13, 2026
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