Repository navigation
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces configuration and surgery scripts for the coralnpu design, including a new register file specification (Regfile_rf.regfile) and a Python script (surgery.py) to post-process the generated Verilog by stripping verification layers, rewriting SRAM wrappers, and optionally restructuring the register file. Feedback on the changes highlights a critical indexing bug in the simulation-only memory initialization task within surgery.py, along with recommendations to ensure the output directory is created before writing and to specify UTF-8 encoding when reading and writing files.
|
Re-measured exactly as this PR has it, without the
Period and area hold. Power does not: with the latch-and-AND clock gate the flop build's power nearly doubles (33.0 → 63.3 mW) and the register file's less (35.8 → 45.4 mW), so the power figure depends on how that one clock gate is modelled. The description now carries these numbers, with that caveat. 🤖 Generated with Claude Code |
🔍 QoR checkMetrics reflect the PR merge build — i.e. what will land on the target branch. Commit 62 design(s) checked — 0 with regression(s), 0 without a comparable baseline. |
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>
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>
|
Where the flop baseline's period goes, and why the register file is the lever I took this PR's flop baseline to global route and asked where its reg2reg period goes: OpenROAD 22b7c537, ORFS ef42174 with this PR applied, built with bazel-orfs. Here it reaches 2,031 ps against the 1,900 ps clock. The description reports 1,976 ps; the OpenROAD commit and flow differ. One path family sets the period. All of the worst 200 reg2reg paths start at the register file's read data ( It is logic, not placement. Mean over those 200 paths:
The modules on the path are compact. Measured by the radius holding half of each module's cell area, against a disc at the design's density, the LSU is 0.83× and the register file 1.18×. A perfect placement would recover at most the roughly 550 ps of repeaters and wire. It is a mass, not one path. Removing the worst launching register 40 times ( This is the cycle the design chose. CoralNPU is an in-order three-stage pipeline: fetch, decode/dispatch, execute/writeback, up to 4 instructions per cycle (microarch.md). Operands are registered at the dispatch → execute boundary. Then one execute cycle does the rest: the register file adds the immediate ( Compared with the literature, the following appears to be left as an exercise for the reader:
What silicon exists. Synaptics' Astra SL2610 (12 nm) is the first production implementation of Coral NPU, "a tightly-coupled CPU optimized for Synaptics' PPA requirements", with the scalar core only; the vector and matrix units are still in development. Google offers the design as IP, with VeriSilicon productising it, and states a target of 800 MHz (1,250 ps) at 22 nm. This snapshot is the open reference design that production silicon is derived from, not a taped-out netlist; how Synaptics' implementation handled the points above is not public. What follows for this PR. The period is set by the register file's read and address logic feeding the LSU, in a cycle the design intends to close as written. Of the exercises above, the register file is the one a flow can take on without changing the RTL's behaviour, and building it as a generated array is what the second commit does (1,976 → 1,900 ps in the description). The remaining gap to the design's own target, 1,250 ps at 22 nm against 2,031 ps here on asap7, a faster node, is either implementation (how synthesis maps this one cycle) or this snapshot's configuration (instruction lanes, FPU and vector enabled), which may be heavier than the 800 MHz one. I have not separated the two. Sources: CoralNPU Chisel and docs, Google Developers Blog, EE Times, VeriSilicon, Ibex ID/EX, CV32E40P LSU. |
|
Notes for later: getting the most testing value out of CoralNPU with one timing budget These are notes on what to investigate later, written up here for now so they sit next to the design they're about. Nothing below has been tried in this PR yet: the budget, its value and its effect on WNS are proposals to check, not results. CoralNPU is a valuable addition to ORFS's asap7 set. It's an open RISC-V design with production silicon behind it (Synaptics' Astra SL2610), it's hierarchical, with this PR its SRAMs are generated macros and its register file can be a generated array, and it runs synthesis to global route in about an hour. Few designs exercise memory generation, macro placement and timing closure together at a turnaround a flow developer can iterate on. Where its WNS comes from today. Taken to global route with this PR's flop baseline, its reg2reg period is about 2,031 ps (OpenROAD 22b7c537, ORFS ef42174). That number comes from one family: the register file's read data into the LSU's input queue ( Why that family is special. CoralNPU's pipeline does the register file read, the address add and the LSU enqueue in one execute cycle (microarch.md), with an 8-read, 6-write register file written as flops. The literature resolves this cycle in one of two ways. Classic pipelines register the effective address before the memory side (the EX/MEM latch of Hennessy & Patterson's five-stage pipeline, as in Rocket and CV32E40P). Register files with this many ports are built as a structure of their own, banked or custom arrays (Rixner et al., HPCA 2000), rather than flops behind mux trees. The open RTL leaves that step to the implementer. An implementation for silicon would most likely take one of those routes. Synaptics describes its version as "optimized for Synaptics' PPA requirements"; the details aren't public. The idea: time that family against its own budget. Without touching the RTL, # The register file's read data into the LSU's input queue: one execute
# cycle in the open RTL, a cycle an implementation for silicon resolves
# (an address register, or a register file built as an array). Timed
# against its own budget, so the rest of the design sets this test
# case's WNS; the paths stay timed and repaired, and a regression shows
# as negative slack here.
set rf_read [get_cells core.score/regfile/io_readData_*]
set lsu_q [get_pins core.score/lsu/ctrl.mem_*/D]
if { [llength $rf_read] == 0 || [llength $lsu_q] == 0 } {
error "constraint.sdc: the register-file -> LSU family matched nothing"
}
set_max_delay 2100 -from $rf_read -to $lsu_qWhat it should give, to be confirmed:
Two numbers per run: WNS for the design against the clock, and the register-file family's slack against its budget. Together they would say how the flow does on the design as a whole, and how the one-cycle read-to-LSU path does on its own. With the generated register file. This PR's second commit takes one of the literature's routes for real: To investigate: whether the budget matches the paths it should and only those; what WNS the rest of the design reports with it; whether that number responds to floorplan and placement changes; and what budget value keeps the family met today while still catching regressions. |
|
@maliberty TL;DR, no action needed now:
🤖 Generated with Claude Code |
|
Results: with the EX/MEM register, coralnpu goes from 2,031 ps to 1,701 ps at global route (1,700 ps clock); with the generated register file too, it meets 1,700 ps. The worst path moves to register file → CSR Correction to my earlier analysis: the queue is 🤖 Generated with Claude Code |
|
@maliberty CoralNPU moved upstream + we've learned a bunch of things, once memory stuff + various and sundry other PRs have landed and we've discussed XiangShan, perhaps we talk about the framing for what an update of CoralNPU should look like. |
17ade36 to
32a3062
Compare
32a3062 to
e6a6657
Compare
e6a6657 to
818b99e
Compare
A SYNTH_KEEP_MODULES name that matched no module was a yosys warning and nothing else: synthesis went on with that module flattened. A misspelled name, or a module the RTL has renamed, changed the result without a word. That is how asap7/coralnpu's keep list went stale in The-OpenROAD-Project#4653: the current RTL renamed DispatchV1, LsuV1 and DBus2AxiV1 to DispatchV2, LsuV3 and DBus2AxiV2, and a full flow ran with three of its ten units flattened before anything noticed. Each name now has to select a module, by either of the two patterns already used (the name itself, or slang's uniquified name$...); synthesis stops otherwise, naming it. On asap7/riscv32i a misspelled name stops synthesis and the real one passes; asap7/chi_async_bridge, whose kept modules have slang's uniquified names, passes unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
SYNTH_MEMORY_MAX_BITS is the AUTO_MEMORIES policy's tripwire: a read/write memory larger than it belongs in a macro (AUTO_MEMORIES, fakeram) or a mock, and must not go undetected into flip-flops. mem_dump.py counted ROMs too. A ROM, a $mem_v2 with no write port, holds constants that synthesis maps to logic like any other logic; it is not storage a RAM would replace, and it goes through the normal flow. Admitting a large ROM meant raising the limit, which let read/write memories of that size through as well. coralnpu's FPU divider has one (#4653): its SRT bound table, 128 x 83 = 10,624 bits, which synthesises on its own in about 3 s to about 700 cells on asap7. ROMs are rare, so they get no limit of their own: they are not counted. The table still lists every memory, a ROM marked as one, and variables.yaml says so under SYNTH_MEMORY_MAX_BITS. test_mem_dump.py checks that a read/write memory over the limit stops synthesis and that a ROM of the same size does not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
|
what is your plan with this DNR PR? |
I was going to mark it as ready for review when everything all needed PRs in OpenROAD and ORFS are merged. I have so many other PRs open that I didn't think to check if we are there at this point. This has been a very useful smaller than XiangShan design for me to iterate on when working on XiangShan, so I wanted to offer it to ORFS community in case it proved helpful. |
818b99e to
42c2db8
Compare
|
Split out three changes, independent of each other, that wire up more of generate_ram's experimental features in the flow:
#4653 is now coralnpu alone, rebased on master, and stays a draft until those merge; then the numbers are rerun on master and it goes up for review. The OpenROAD it needs is already in master's |
flow/designs/src/coralnpu/generate is a Bazel module that writes
CoreMiniAxi.sv from coralnpu's Chisel source at a pinned commit:
cd flow/designs/src/coralnpu/generate
bazelisk run //:update
It elaborates coralnpu's CoreAxi as upstream's CoreMiniAxi build
configures it (128-bit fetch and load/store, float with Zfbfmin), in a
wrapper that ties the test enable te to 0, so synthesis removes the
test-mode reset bypasses and clock-gate overrides, and without the debug
ports a synthesis build does not use. firtool runs without the
verification layers and without register randomization. The dependency
pins and coralnpu's patches to cvfpu are coralnpu's own.
coralnpu's ClockGate.sv and Sram.v carry an `ifdef branch per foundry
PDK, naming cells and macros of proprietary PDKs. resources.py removes
those branches before firtool sees the files, so CoreMiniAxi.sv keeps
the FPGA and generic branches as upstream wrote them; a file with no
such branch to scrub stops the build. The scrubber assembles the names
from fragments so that it does not itself trip ORFS's security scan,
which still runs on every file, the generated Verilog included.
The module is in a directory of its own, so flow/designs/src/coralnpu
holds no Bazel package. CoreMiniAxi.sv is regenerated in the next commit,
with the design changes the new RTL needs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
CoreMiniAxi.sv is regenerated from coralnpu b50b85a9 by src/coralnpu/generate, and the design is configured for it: - The five TCM banks (ITCM one, DTCM four) are coralnpu's Sram, 512 x 128 with one read-write port and a 16-lane byte mask. sram.memories names its pins (ADDITIONAL_MEMORIES) and AUTO_MEMORIES replaces it with a generated macro, write mask and all. The previous build swapped in USE_ASAP7 fakerams, which have no write mask, so a masked write wrote all 16 bytes. - Regfile and FRegfile keep their words as separate registers, with no array for yosys to infer. surgery.py (SYNTH_VERILOG_SURGERY) moves them into Regfile_rf and FRegfile_rf, which AUTO_MEMORIES_REGFILES has generate_ram build as standard cells, every word with its asynchronous reset: Regfile 32 x 32 with x0 reading zero, nine reads and six writes; FRegfile 32 x 32, four reads and two writes. - The clock is 1,900 ps in the platform's picoseconds (it was 10.00), with the platform's set_max_delay IO budgets; the asynchronous reset is not timed. The kept hierarchy is coralnpu's units by name, with OPENROAD_HIERARCHICAL=1. - The divider's 128 x 83 SRT table is a ROM yosys maps to logic; a ROM is not counted against SYNTH_MEMORY_MAX_BITS. surgery.py recognises each register file token for token and stops the build on anything else. Each was simulated before and after the surgery, and generate_ram's netlists against the behavioural modules the surgery writes, on the same random inputs, every output compared every cycle, with a negative control for each that is caught. Needs AUTO_MEMORIES_REGFILES (The-OpenROAD-Project#4781), the supply pins of dont_touch instances connected (The-OpenROAD-Project#4782) and a parameterized module as a macro (The-OpenROAD-Project#4783). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
42c2db8 to
32ab208
Compare
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
coralnpu on asap7 at its current RTL, generated from coralnpu's source, with its SRAMs and both register files generated and the function unchanged.
Draft: it waits on three changes, independent of each other, that wire up more of generate_ram's experimental features in the flow:
This PR carries none of them; it is the design alone.
Built through final at a 1,900 ps clock in about 90 minutes, hermetically with bazel-orfs on OpenROAD 89e24308 (The-OpenROAD-Project/bazel-orfs#1415), with a baseline for scale:
The baseline is ballpark numbers only, not a controlled comparison. It is master's asap7/coralnpu built through global route with the same tools as this PR, not with master's OpenROAD and yosys. Its 10 ps clock sets every repair step chasing an impossible target, which likely accounts for much of its CTS and global-route time: at global route,
repair_timingalone takes 605 s. This PR's design is the more congested of the two at global route.The worst path at the end runs from the debug module's request queue through the CSRs, the float core and the load/store unit to the retirement buffer. The generated register files' write enables close with 36 ps to spare.
#4744 (a ROM is not counted against
SYNTH_MEMORY_MAX_BITS; the FPU divider's 128 × 83 SRT table is one) and #4745 (aSYNTH_KEEP_MODULESname not in the design stops synthesis) are merged.The numbers above were measured on OpenROAD 89e24308 with these changes applied, before the split; they will be rerun on master once #4781, #4785 and #4783 are in.
Commits:
src/coralnpu/generate, that writesCoreMiniAxi.svfrom coralnpu's Chisel source at a pinned commit (bazelisk run //:update).AUTO_MEMORIES. The earlierUSE_ASAP7fakerams had no write mask, so a masked write wrote all 16 bytes.surgery.pymovesRegfile's andFRegfile's words into modules of their own.generate_rambuilds both as standard cells, every word with its asynchronous reset.How the function was checked:
surgery.pyrecognises each register file token for token and stops the build on anything else. Each register file was simulated before and after the surgery on the same random inputs, every output compared every cycle. The run was 200,000 cycles, with resets and with addresses biased to collide; there were no mismatches. Negative controls were caught within 20 cycles: a write port's mask dropped, and the write priority swapped.🤖 Generated with Claude Code