Repository navigation
Conversation
generate_ram took exactly one read/write or one write port (RAM-0030). It now takes any number of write ports: each has its own address decoder, data bus (D0, D1, ...), write enables (we0, we1, ...) and address (addr_w0, addr_w1, ...); one write port keeps the names it had. Per word, port w writes when its row select and write enable are high; a word written by two ports in a cycle keeps the last one's data, as the behavioral model does; the word's clock gate opens for any of them. Per bit an AO22 per pair of ports, and an OR2 across pairs, picks the data. The AO22 is found by its truth table, as the AOI22 is, through findMaster4, which both now share; the OR2 by its function. Not yet with -use_latch, whose data passes a latch per column, or with -column_mux_ratio above 1, whose word select comes from the first port's address (RAM-0054, RAM-0055). Tested: src/ram/test/write_ports_nangate45.tcl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
generate_ram's read path is a tristate bitline per bit, and a library without a tristate cell (asap7) could not generate at all (RAM-0010). -read_style and_or reads through a tree per bit column instead: an AOI22 over each pair of rows, then NAND2 and NOR2 levels by turns, each node placed in a row of the span it covers, beside its inputs. It is the default where the library has no tristate cell (RAM-0057 says so), and can be asked for where it has one; -read_style tristate insists on the bitlines. A storage flop whose output is the data inverted is taken on the AND-OR path, an inverter at the root of each tree undoing it; the tristate path refuses it (RAM-0058). buildPortMap now reckons a flop's output polarity from both the state variable a pin reads and its next_state: asap7's DFFHQNx1 stores !D in IQN and outputs IQN on QN, which it took for a true output. Cells with next_state "D" are as before. NAND2 and NOR2 are found by truth table (asap7 writes NAND2 as (!A) + (!B)), through the evaluator findMaster4 used, now shared. Tested: src/ram/test/and_or_read_asap7.tcl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
-zero_word n makes word n read 0 and drop its writes, as a RISC-V register file's x0: its row keeps its place in the grid but has no flops and no clock gate, and the AND-OR read trees are built over the other rows; where the storage flop's output is inverted the zero word stays in them, its term its own select, so the inverter at the root reads it as 0. The behavioral model reads 0 there and ignores writes. Needs -read_style and_or and -column_mux_ratio 1 (RAM-0060). Tested: src/ram/test/zero_word_asap7.tcl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
-reset_value v resets every word to v asynchronously. Each bit's flop is cleared or preset by the reset, the other pin held off by one tie cell per word slice: the data, clear and preset pins are read from the flop's ff group, and which pin gives which value accounts for the flop's output polarity (asap7's DFFASRHQNx1 stores !D, so its `clear` sets the data to 1). The reset port takes the flop's own polarity, rst_n or rst; a parent adapts any other reset, or a synchronous one, to it. The storage cell, found or given, has both a clear and a preset. The behavioral model resets the same way. Not with -use_latch (RAM-0065). Several ORFS designs reset their register files (ibex, whose ECC and lockstep checks need valid codewords from the first cycle; cva6, ariane, swerv, coralnpu); it is an option, off by default. Tested: src/ram/test/reset_asap7.tcl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
-banks splits the words into banks, contiguous or interleaved in the address (-bank_order), -bank_columns of them side by side and the rest stacked, for an array's aspect ratio and for the bank counts register files use (XiangShan's 224 words in 4 interleaved banks). With banks every port decodes every word's whole address, an AND2 tree per word, so a bank need not be a power of two words and any placement of words reads and writes alike; a row holds -bank_columns words, laid out as the column mux lays out its words. A read port is one tristate bitline shared by every word, or one AND-OR tree over all of them. Without -banks, nothing changes. Not with -column_mux_ratio above 1 or -use_latch. Also: an array with fewer rows than address bits got no address inverters (ports_per_col was 0), so its decoders read floating literals; at least one port's inverters now go in each column. Tested: src/ram/test/banks_asap7.tcl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
-rb_ports n adds n banked read ports to a RAM with -banks: each has an address and a data output per bank (addr_rb<k>_b<b>, Qrb<k>_b<b>), the address the word's index in its bank, every bank read independently in the same cycle, as XiangShan's integer register file reads. To the generator below, each (port, bank) is a read port of its own that decodes and reads only its bank's words: a tristate bitline or an AND-OR tree over them. The behavioral model reads the same words. Tested: src/ram/test/banked_read_ports_asap7.tcl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
-read_latency 1 registers every read port's address: a plain positive-edge flop per address bit, found by its ff group, in the address inverter's cell; the data follows a cycle after the address, as XiangShan's register files read. Where the flop gives the address inverted (asap7's DFFHQNx1) the decoders take the flop's output as the complement and the inverter makes the true literal, so no cell is added. The behavioral model registers the addresses the same way. Not with -column_mux_ratio above 1 or with read/write ports (RAM-0072). Tested: src/ram/test/read_latency_asap7.tcl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
-write_style mux holds each word without a clock gate: every flop is on the clock, its D the new data where the word is written and its own value elsewhere, an AO22 per bit (the AOI22 and an inverter where the library has none) and an inverter per word for the hold; a flop whose output is inverted feeds back through an inverter per bit. The default, clock_gate, is generate_ram's clock gate per word, as before. Which is better depends on the design: a clock gate per word saves a mux per bit, a mux keeps every flop on the plain clock. Not with -use_latch. Tested: src/ram/test/write_mux_asap7.tcl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
-periphery unplaced leaves the periphery (address inverters and registers, decoders, select buffers, each word's select cell and the input buffers) for a parent to place, size and buffer: the parent alone knows the drivers and loads outside the array. The outline is cut to what stays placed, the core, and a row above it for a parent of the other row parity, so a parent sizing its floorplan by utilization charges the core and nothing else. Each pin goes where its connections land, on -pin_layer's track grid inside the outline, for a macro placer to orient the core by before the parent dissolves it into its own rows. generate_ram_netlist only; generate_ram's flow places everything. Tested: src/ram/test/periphery_unplaced_asap7.tcl. 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 a role (inv, and2, buffer,
clock_gate, aoi22, ao22, or2, nand2, nor2, tie, addr_flop) instead of
the smallest the libraries have of its function. On asap7 the smallest
are the xp cells a flow marks dont_use (INVxp33, AOI22xp33), and the
smallest buffer is the hold buffer HB1xp67; a core the parent keeps
fixed cannot be resized after. An AOI22 or AO22 given is checked by its
truth table, and its pins are read from it; findMaster4 now reads them
from the cell it picks, not from the last that matched in the search.
-name names the block and the behavioral model, instead of
RAM<words>x<bits>, so two RAMs of one size can live in one design.
Tested: src/ram/test/cells_name_asap7.tcl.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
-decoder word decodes each word's whole address through a balanced AND2 tree, the banks' decoders (the -banks commit) without banks. The default, row, is generate_ram's decoder per row, an AND2 chain over the row address: four AND2 deep for 32 rows where a tree is three. On riscv32i's register file the chain is about 60 ps of the read path's 220. Tested: src/ram/test/decoder_word_asap7.tcl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
-bit_folds k lays a word's mask slices (-mask_size, word/k rounded up) in k rows instead of side by side; where k does not divide the word the last fold is short, its missing bits empty columns (XiangShan's FtqMetaQueueRedirect, 211 bits in 4 folds). 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. With folds the decoder, select-buffer and address-inverter columns take k rows a word, and a column's input buffers stack a row a fold above the words, so a wide word's buffers do not set the column's width. A folded word is written whole: one write enable a port, and the word's write logic (enables, priority, hold, clock gate, reset tie, select inverters) built in its first fold and shared by the others. Not with -use_latch or the row decoder's column mux. Tested: src/ram/test/bit_folds_asap7.tcl. 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 to -wrapper_verilog as a module <name> around an instance of it: the periphery's logic cells as assigns of their liberty functions, for synthesis to map with the parent's logic, and its other cells (clock gates, flops, ties) as instances. A net the core shares with the periphery becomes a core port; a port only the periphery uses leaves the core. The core is then outlined and pinned as with -periphery unplaced. An option beside generator-built periphery, not a replacement: which is better depends on how the array is integrated. Tested: src/ram/test/periphery_verilog_asap7.tcl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
-write_priority says what a word takes when two write ports write it in one cycle: last, the last port's data, as before; or or, the OR of every writing port's data. firtool's register files (XiangShan's IntRegFile, FpRegFile, VfRegFile) merge their writes with an OR, so a generated register file in their place is equivalent to the RTL only with or; it also drops each word's priority logic, an inverter and an AND2 per port but the last. The behavioral model follows the option. The fourteenth of the generate_regfile features carried onto generate_ram (generate_regfile's default); see OpenROAD The-OpenROAD-Project#11669. Not upstreamed. Tested: src/ram/test/write_priority_or_asap7.tcl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
-service_columns {every sites} puts an empty column `sites` sites wide
after every `every` storage columns. An array of placed standard cells
is full: a parent flow that buffers the long nets crossing it (repair
inserts wire buffers on the word selects and read bitlines) has no site
to put them on, and legalization fails. XiangShan's RobEntryFile,
352 words of 21 bits with 16 read and 8 write ports, dissolved into a
parent, left 1,230 such buffers overlapping without these columns. The
columns are inside the core, so -periphery unplaced keeps them in the
outline. Default: none, as before.
Tested: src/ram/test/service_columns_asap7.tcl.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
With banks a row holds -bank_columns words, laid out as the column mux lays out its words: bit by bit, interleaved. Each word is decoded whole, so nothing reads the row through a mux, and the interleave only costs: a word's selects and write enables run the whole row to reach its bits. -bank_column_layout contiguous puts each word's bits side by side with its select column after them; the per-bit read trees cross the row instead. XiangShan's RobEntryFile (352 words of 21 bits, 16 read and 8 write ports, 4 words a row) had rows 278 um wide: dissolved into a parent, the resizer found 6,483 slew violations on those nets and put 6,043 buffers inside the full array, which detailed placement could not legalize. Default: interleaved, as before. Tested: src/ram/test/bank_column_layout_asap7.tcl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
|
This PR exceeds the recommended size of 1000 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size. |
There was a problem hiding this comment.
Code Review
This pull request significantly expands the RAM generator's capabilities by introducing several new configuration options, such as read styles, banked configurations, asynchronous resets, custom decoders, bit folding, and Verilog wrapper generation. It also adds a cycle simulator and extensive test cases to verify these features. The code review feedback is highly constructive, suggesting robust input validation for the newly introduced parameters (such as -zero_word, -banks, -bank_columns, -service_columns, -bit_folds, -read_latency, and -rb_ports) to prevent potential runtime errors like division-by-zero, out-of-bounds access, or infinite loops, as well as addressing a potential check bypass when -rb_ports is explicitly set to 0.
|
@rovinski FYI |
A value out of range stopped nothing: -banks, -bank_columns and -bit_folds below 1 were clamped to 1, a negative -read_latency read as 0, a negative -zero_word as no zero word, -service_columns below 1 was ignored, and a negative -rb_ports went into the read port count. -rb_ports 0 with no other read port also passed the check for at least one read port. Each now stops with its own message (RAM-0036, RAM-0071, RAM-0092 to RAM-0096). Tested: src/ram/test/option_errors_asap7.tcl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
Buildifier's unsorted-dict-items lint: the new tests' entries in the per-test data dictionary are sorted by name. No change to what any test reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
|
@maliberty @rovinski pr-merge green, if pr-head fails then that's not important. |
|
Mmm.... green. 😌 |
|
None of the tests actually check that the rams are routable. |
|
I have a lot of concerns and I don't have time to review a 4k line PR. You should really take a look at this paper as it covers several strategies for high performance RAMs that we are looking to implement.
Use De Morgan's Law and convert to NAND-NOR tree for reduced delay and size
I would want to see an experiment showing that this is better than implementing the functionality in RTL outside of the RAM. I am not convinced that putting in a 0 row in the RAM is the best methodology.
For reasons mentioned in #11669 I am opposed to implementing this feature. I have not seen any convincing use case that this is a good feature.
The feature is ok but I don't like the naming. Either the output should be registered or unregistered. There is not really a reason why the latency would be anything other than 0 or 1. So maybe change the flag to See #11669 for other comments It would be very helpful to see screenshots of the layout(s) generated to review. |
|
@rovinski thank you for taking the time to go through this, here and on #11669. You're under no obligation to, and a little direction from you goes a long way. (This continues the discussion from #11669 (comment).) Before I roll up my sleeves, here is how I read your comments and what I plan to do about each one. First, I apologize for my ignorance on memories. I'm not trying to become an expert. As my open PRs show, I'm trying to get the whole flow in place, from RTL to XiangShan-sized designs, so that experts can work on the individual pieces one concern at a time, from an OpenROAD+ORFS baseline, warts and all. That's what this PR is: a new baseline of features. Some will be replaced by something better, some made automatic, some removed. None of that can be tried against OpenROAD, ORFS and real designs until the features are in master; out of tree, the turnaround is too long. So I'll keep this as one PR and address the concerns in it. Routing and Screenshots. I'll commit them in bazel-orfs#1234 and embed them here. NAND/NOR read tree. I'll measure it against the AND-OR tree and post delay and area.
On swerv, you're right, I worded that badly: I meant the flop cell swerv's RTL instantiates, which always has a reset, not a PDK.
12 read and 9 write ports. Register files like that are normal in high-performance CPUs, and XiangShan is where this is headed. Synthesized flops are what the generated register file is compared against. The paper. I couldn't get it. IEEE has it behind a paywall, and the open-access listing for the journal version (ACM TODAES 2016) at EPFL has no PDF attached. What in it do you think applies here? That's exactly the kind of direction that goes a long way. It's also another reason to put a baseline into OpenROAD and ORFS master: people who have that knowledge can then improve the generator against real designs, one concern at a time. |
|
@rovinski on the bigger question this PR raises: you asked for these features to go into
OpenROAD already splits commands where the steps are separate: macro placement and detailed routing are two commands. Here a list is less obvious. A split could look like this, with cleaner syntax per command and the code above shared behind them:
What I found on XiangShan is that the details of the RAM weren't the problem. Global placement was: long wires. If the array isn't placed where it needs to be, there's nothing the RAM can do about it. To help global placement, this PR separates the periphery from the regular structure, and it also lets a flow take the netlist only and leave the placement to global placement. Which is best depends on the context; no single option is best everywhere. By its own QoR function, Memories are one case of a wider pattern: a repeating structure the general flow doesn't handle well. CAMs are another, and so are design-specific structures. XiangShan's reorder buffer is hundreds of copies of one entry, each decoding the same wide bus locally. That's a very large number of broadcast pins and thousands of cells to hold a few dozen bits of state. Synthesis can't share that decode across entries, and global placement can't spread it. The aim is a large cut in synthesis time along with better QoR. Those structures don't belong in I don't think we'll get the split right on the first try. Reworking the code for a split is cheap now. The real cost is the cognitive load on users each time the API changes. These features are experimental until they're stable. OpenROAD has no releases, and anyone can see from the git history how mature a feature is. A note in the docs saying "experimental" would be out of date as soon as it was written, and less true than the history. A cross-cutting change such as My main concern is a baseline that supports the features in this PR, so contributors can work on it one concern at a time, as their interest and resources allow. Given that, I'm flexible on the split: one command now, the split above, or another you prefer. |
|
@rovinski about that paper: I never read it, I never saw it, and I don't know anybody who has. But I may have overheard some people talking about it, and then I happened to be looking at the code. Word is it describes how to build a small standard-cell memory with controlled placement:
It places those cells by construction, then lets the standard flow finish. The way I hear it, the scope is low-power, low-voltage, two-port (one read, one write) memories from 32×32 to 128×32 in 28 nm FD-SOI. Each block is measured on its own: area, wirelength, power and runtime, against the same RTL placed without control. Beyond about 256 rows it goes to banks. If that's right, it's a clear result within that scope, and much of it is already in From what I'm told, the paper puts the whole flow into making a macro. That's the right choice for what it measures, but it can't show what the general flow would do with the same cells in context, or whether that would be the better solution there. I want to keep that signal. So this PR keeps both paths: a block placed by construction, and a netlist, or a placed core with its periphery, that the parent's flow places and optimizes with its surroundings. On XiangShan the comparison between the two is the interesting part. The latch storage, I hear, relies on its latch-to-output path having 1.5 cycles. In XiangShan, the only multicycle path I know of is on L2's data SRAMs, which the RTL reads over two cycles. A 1.5-cycle path would fit inside that on timing. But an L2 data array is far beyond the sizes the paper targets, so it isn't a strong fit. This PR is aimed at the cases outside that scope:
|
The option registers each read port's address: the decoders and read trees come after the flop, and the data follows a cycle after the address. -read_latency read as a register on the data, a different circuit. 0 and 1 only; more than one cycle stops with RAM-0071. Tested: src/ram/test/register_read_address_asap7.tcl, src/ram/test/option_errors_asap7.tcl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
Two things kept generate_ram from routing an asap7 block. -tracks: generate_ram made its tracks from the technology LEF's offsets, and asap7's M2 offset is negative (IFP-0039). -tracks names a file of make_tracks commands to source instead, as asap7's flows do; without it the tracks are made as before. A file that cannot be read stops with RAM-0097. Pins off the power straps: the pin placer did not know where generate_ram's own straps run, and put pins on the tracks under them. On asap7 the M4 straps start at the block's bottom edge, its first track 12 nm above it, and detailed routing could not reach the pin (DRT-0255). Each strap's span, with its layer's spacing, is now excluded from the edges that layer's pins go on. The existing sky130 and Nangate45 blocks keep their pins where they were. The asap7 tracks file joins //test:asap7_data. Tested: the asap7 tests in src/ram/test, which route their blocks through generate_ram in the commit that follows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
generate_ram takes -periphery verilog and -wrapper_verilog: the routed block is the core, <name>_core, and the periphery is written as a Verilog module, <name>, around it, for a parent's synthesis to map and place with its own logic. The netlist puts each of the core's pins where its connections land, for a macro placer to orient the core by; generate_ram drops those and places the pins on the core's edges as it does for any block, so the core routes. -periphery unplaced, which leaves the periphery unplaced inside the block, stays generate_ram_netlist's, for a flow that places it; generate_ram stops on it with RAM-0098. Tested: src/ram/test/periphery_verilog_asap7.tcl, which routes the core in the commit that follows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
Every option test now builds its block with generate_ram, so it is placed, given its power grid, pins and fillers, and routed, and then checks what the option computes on the routed block. src/ram/test/routed.tcl holds generate_ram's arguments for asap7 and Nangate45, and routed::check: every signal net with two or more pins has a wire, and detailed routing ran and left no violations. What changed besides the command: - banks and bank_column_layout use 32 words: at 8 words in four banks, two side by side, the block was a strip 36 um wide and 1.35 um tall whose east edge had six pin slots for nine address pins. - service_columns counts fillers and taps as empty sites: in a routed block they fill the columns the option leaves. - periphery_unplaced stays on generate_ram_netlist: generate_ram does not take -periphery unplaced. - option_errors runs generate_ram, and adds -zero_word past the last word (RAM-0059), -register_read_address 2 (RAM-0071), an unreadable -tracks file (RAM-0097) and -periphery unplaced (RAM-0098). Tested: src/ram/test, all 21 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
generate_ram takes the register-file options and passes them to generate_ram_netlist, but its help listed none of them, so `help generate_ram` did not show what the command takes. The README's synopsis lacked -periphery, -wrapper_verilog and -tracks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
|
@rovinski five new commits on top, no force-push, so the review history stays.
Routing. Routing the tests found the two asap7 problems above. A thin banked strip, 8 words in four banks two wide (36 × 1.35 µm), can't be routed: its East edge has six pin slots for nine address pins. It fails loudly (PPL-0111/DRT-0255), and the bank tests use 32 words. On one thread, three tests spend 5–12 minutes in detailed route (banks, bank_column_layout, write_priority_or). Changing the power straps or limiting the routing layers didn't shorten it. The rest take seconds to two minutes. NAND/NOR read tree. It's already that: Integration test: The-OpenROAD-Project/bazel-orfs#1239. It carries these 23 commits and runs each option through the ORFS flow to detailed route on a small design (12 designs, all routed clean). Routed blocks across asap7, Nangate45 and sky130hd, a sample of the options: All 14, with the options each shows: https://github.com/The-OpenROAD-Project/bazel-orfs/blob/c2481240914d97010eef8d89a23b040e38decb90/test/regfile/images/README.md |
|
The insight behind generate_ram, and behind the paper referenced earlier in this thread, is that a repeating structure is generated faster and better by explicit code than by general-purpose synthesis and placement. To reach real designs, that structure has to match the abstractions the RTL already uses. For design space exploration, a memory gets used when it fits the flow people already trust. That means formal equivalence with open-source tools, static timing (flip-flops are better supported there than latches), a match to the RTL as written, and an SDC users can write. Asking RTL authors to write for a new memory works when a tapeout pays for it. For small memories like register files, where a memory compiler is too expensive or not available, the memory has to come to the RTL. That is where generate_ram shines. This study maps that reach for the designs in ORFS: which register files yosys plus Verilog surgery can hand to generate_ram today, and what QoR that buys.
Results through detailed route on asap7 with RCX extraction, best shape against flip-flops. Min period = clock period − WNS; reg→reg is the same for register-to-register paths alone:
One run per arm. Name and hierarchy perturbation alone moves results by more than 7 % (Kahng and Mantik, ISQED 2002), and routed wirelength by more than 10 % (Chan, Kahng and Woo, SLIP 2020). Differences of a few percent are within noise. Full tables, stage times and the harness: The-OpenROAD-Project/bazel-orfs#1250 Try it, from bazel-orfs with that PR: Claude made the changes and stared at paint drying. |





This replaces #11669.
This PR adds the options a register file needs to
generate_ramandgenerate_ram_netlist, one commit per option, so each can be reviewed on its own. Every option is off by default: with none given,generate_rambuilds exactly what it builds today, and the existingramtests are unchanged.Each option comes from a register file in a real design: the ORFS designs (riscv32i, ibex, cva6, coralnpu, swerv) and XiangShan's (IntRegFile has 12 read and 9 write ports, banked reads and an x0 word). The commits are in dependency order, and each commit message says what its option does and why.
write_ports_nangate45and_or_read_asap7zero_word_asap7reset_asap7banks_asap7banked_read_ports_asap7register_read_address_asap7(renamed in 19)write_mux_asap7periphery_unplaced_asap7cells_name_asap7decoder_word_asap7bit_folds_asap7periphery_verilog_asap7write_priority_or_asap7service_columns_asap7bank_column_layout_asap7option_errors_asap7register_read_address_asap7periphery_verilog_asap7Testing. Each option has a test of what it is meant to do, not a snapshot of the netlist. Since commit 22 each test builds its block with
generate_ram, so the block is placed and routed, and checks that every net is routed with no DRC violations before it simulates the routed block.src/ram/test/ram_sim.tclsimulates the generated block cycle by cycle: combinational cells from their Liberty functions, and the few flops and clock gates the tests use from a one-line model each. The tests then write and read random words against a model of what the RAM should hold. Examples: word 0 reads 0 with-zero_word 0; every word reads the reset value after-reset_value; colliding writes give the last port's data, or their OR with-write_priority or; with-register_read_address 1the data follows a clock after its address. Layout options also check placement: rows per word, empty service columns, contiguous words, pins on the pin layer's tracks. Each test was checked to fail when its option is removed or reversed. The existing tests are unchanged.The commits are split one per option for reading. They were built and tested together, at the last commit, not one by one.
The integration test is The-OpenROAD-Project/bazel-orfs#1239 (before it, The-OpenROAD-Project/bazel-orfs#1234): it carries these commits and the ORFS side, and runs register files from ORFS designs through simulation, equivalence checks and placement. It is being extended to XiangShan. The ORFS changes that use these options (
AUTO_MEMORIES_REGFILESthroughgenerate_ram, The-OpenROAD-Project/OpenROAD-flow-scripts#4651) are carried as patches in bazel-orfs until OpenROAD has this support and ORFS is bumped onto it.🤖 Generated with Claude Code