Repository navigation
Conversation
The arguments write_abstract_lef writes a block's abstract with, by default -bloat_occupied_layers as before: every layer the block uses obstructed over its whole outline. Empty, the abstract obstructs only the shapes the block occupies, so a block whose outline holds a notch where other macros sit leaves it free for them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
carve.py regroups a flat module's existing cells into identical tiles, one module built from the first tile and instantiated for every one, and leaves the rest as the parent: no RTL edited, no cell added. wrap_parent.py moves the parent's own cells into a module of their own, a port for every tile pin it meets, so the parent can be built as a macro beside its tiles and the top is instances only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a hierarchical physical design flow for a systolic array TPU using ASAP7, carving the design into 16 identical processing element tiles (tpu_block) and a C-shaped parent macro (tpu_rest) via custom Python scripts (carve.py and wrap_parent.py). It includes the necessary configuration files, SDC constraints, pin placement scripts, power grid definitions, and unit tests. The review feedback highlights two key robustness improvements: handling potential crashes in flow/util/carve.py when seed nets contain constant bits, and safely checking for None specs in flow/designs/asap7/tpu/surgery.py before module elaboration.
e738f75 to
2feb4d2
Compare
🔍 QoR checkMetrics reflect the PR merge build — i.e. what will land on the target branch. Commit 62 design(s) checked — 5 with regression(s), 0 without a comparable baseline. ❌ asap7/aes-block base — 1 failing metric(s)
❌ asap7/riscv32i-mock-sram base — 1 failing metric(s)
❌ nangate45/swerv_wrapper base — 1 failing metric(s)
❌ sky130hd/chameleon base — 2 failing metric(s)
❌ sky130hd/microwatt base — 2 failing metric(s)
|
tpu's 8 x 8 array of processing elements, built as 17 macros and no standard cell at the top: 16 identical tiles of 2 x 2 elements carved out of the flat design during synthesis (SYNTH_VERILOG_SURGERY running flow/util/carve.py; the RTL untouched), and the rest of tpu built as one C-shaped macro whose notch holds them (flow/util/wrap_parent.py). Every pin is placed by rule from the constants in tpu_io.tcl before anything is built, so the tile and the parent build side by side; the top level is placement and wires. FLOW_VARIANT=flat builds tpu flat from the same RTL, the reference. Both use the platform's constraints: tpu is mesochronous with its surroundings, so only reg2reg paths are judged and the boundary is a set_max_delay budget. Every macro routes on M2-M8, tpu's pins on its bottom edge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
|
Let lie for now. |
Draft: base builds through global route; to
finaland the CI rules are in progress (below).asap7/tpubuilt as 16 abutted tiles and a parent that is a macro too, withFLOW_VARIANT=flatas the reference. At global route, reg2reg minimum period stitched across every macro, means of three placement seeds:FLOW_VARIANT=flat)QoR is the best result within a build budget. In a hierarchical flow the parent places, repairs and routes around its macros' abstracts: it waits for every macro, and every macro change re-places it. Here the parent is a macro like the tiles, built against the same pin plan: every macro builds side by side, the top level is placement and wires, and an RTL edit in a tile rebuilds that tile alone. The measurements, figures and harness: The-OpenROAD-Project/bazel-orfs#1396.
Commits
WRITE_ABSTRACT_LEF_ARGS: the argumentswrite_abstract_lefwrites a block's abstract with,-bloat_occupied_layersby default as before; empty, the abstract obstructs only what the block occupies, so a block whose outline holds a notch leaves it free for the macros in it.carve.py,wrap_parent.py: carve identical macros out of a flat design's own cells, no RTL edited, no cell added; move the parent's own cells into a module of their own, a port for every tile pin it meets.surgery.py(SYNTH_VERILOG_SURGERY) carves the netlist during synthesis;tpu_io.tclplaces every pin of the tile and of the parent from constants, the parent's mirrored across their shared edge; the top level's hooks place the macros and skip what a top of macros only does not need (tap cells, legalization).FLOW_VARIANT=flatbuilds tpu flat. The README has the rules of the game (tpu is mesochronous with its surroundings, its FIFOs outside it) and how the clock runs through the tiles with the data.Tests
flow/test/test_carve.py,flow/test/test_wrap_parent.py: two identical tiles and a parent carved from hand-written netlists; every port, constant and net between tiles where it should be.In progress
Hold across macros fails today: 143 endpoints from a tile into the parent (worst -417.4 ps) and 75 inside the parent (-130.0 ps), timed with every macro's own cells and wires. The parent's deskew registers take the bottom tiles' clock through the parent's own tree, well after the tiles that feed them.
The clock balanced at a top level of its own closes hold, measured in the study: the clock taken out of the macros' wiring, a strip of four rows under the macros for the top level's clock tree, each tile's clock pin on M8 over its centre. Every hold class passes, at a 13 % longer period so far (754.0 ps against 665.2 ps, flat 724.0 ps): the chain's skew was free time for every path flowing east, and the parent's own tree is deeper than the tiles'. Next: a planned latency per macro at the top. ORFS hooks only; no OpenROAD change.
The worst path at the top level is in2reg:
a_valid/a_datathrough the parent into the first processing element's multiply-add, 100.8 ps through the parent and 570 ps in the tile, slack -370.8 ps against the 300 ps budget (the README has it drawn). Row 0 of the activations has no skew register by design, and the PE multiplies its input as it arrives: it is half a reg2reg path whose other half is outside tpu. The study's rig gets that half: a mesochronous FIFO on tpu's one clock in front of tpu and behind it, its interface valid in and valid out, no back-pressure, deep enough for the worst case, built from the async queue already inchi_async_bridge.Base to
final: the tiles and the parent route clean enough to finish; the top level's power reaches the parent only, as pdngen takes the parent's whole outline as occupied and draws nothing over the tiles.rules-base.jsonregenerated for base, once it runs tofinal.The parent's reported timing: ORFS reads the top level through the blocks' liberty models, which here reports setup slack -368 ps at the top against a stitched reg2reg period of 665 ps. A flag so
generate_abstractalso writes a block's netlist and parasitics and the parent's reports read those instead.🤖 Generated with Claude Code