Skip to content

asap7/tpu: abutted tiles, the parent a macro too; flat as a variant - #4728

Closed
oharboe wants to merge 3 commits into
The-OpenROAD-Project:masterfrom
oharboe:tpu-abutted
Closed

oharboe wants to merge 3 commits into
The-OpenROAD-Project:masterfrom
oharboe:tpu-abutted

Conversation

@oharboe

@oharboe oharboe commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

Draft: base builds through global route; to final and the CI rules are in progress (below).

asap7/tpu built as 16 abutted tiles and a parent that is a macro too, with FLOW_VARIANT=flat as the reference. At global route, reg2reg minimum period stitched across every macro, means of three placement seeds:

tiles in a C (base) flat (FLOW_VARIANT=flat)
reg2reg minimum period 672.6 ps (92 %) 729.3 ps
a fresh build, critical path 76 s 436 s
an RTL edit in a processing element 99 s 487 s
die 1.39 x 1 x

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

  1. generate_abstract: WRITE_ABSTRACT_LEF_ARGS: the arguments write_abstract_lef writes a block's abstract with, -bloat_occupied_layers by 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.
  2. util: 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.
  3. asap7/tpu: the design. surgery.py (SYNTH_VERILOG_SURGERY) carves the netlist during synthesis; tpu_io.tcl places 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=flat builds 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.
  • The design builds to global route, base and flat; the carve is proven equivalent to the flat design with kepler-formal (registers matched by name, a broken port refuted) in the study above.

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_data through 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 in chi_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.json regenerated for base, once it runs to final.

  • 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_abstract also writes a block's netlist and parasitics and the parent's reports read those instead.

🤖 Generated with Claude Code

oharboe and others added 2 commits October 24, 2026 13:15
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>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread flow/util/carve.py
Comment thread flow/designs/asap7/tpu/surgery.py
@oharboe
oharboe force-pushed the tpu-abutted branch 3 times, most recently from e738f75 to 2feb4d2 Compare October 24, 2026 12:35
@openroad-ci

openroad-ci commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

🔍 QoR check

Metrics reflect the PR merge build — i.e. what will land on the target branch.

Commit 248e959 · Jenkins build #5 · Baseline: build · View build on dashboard

62 design(s) checked — 5 with regression(s), 0 without a comparable baseline.

❌ asap7/aes-block base — 1 failing metric(s)
Metric target base delta limit band
cts__timing__setup__tns -1703.730 -1276.570 -33.462% -1531.884 PeriodPadding 20.000%
❌ asap7/riscv32i-mock-sram base — 1 failing metric(s)
Metric target base delta limit band
finish__timing__setup__tns -2038.650 -9.358 -21684.429% -199.358 PeriodPadding 20.000%
❌ nangate45/swerv_wrapper base — 1 failing metric(s)
Metric target base delta limit band
cts__timing__setup__tns -393.977 -297.177 -32.573% -356.612 PeriodPadding 20.000%
❌ sky130hd/chameleon base — 2 failing metric(s)
Metric target base delta limit band
detailedroute__antenna__violating__nets 3.000 0.000 — 0.000 Padding 30.000%
detailedroute__antenna_diodes_count 172.000 165.000 4.242% 165.165 Metric 0.100%
❌ sky130hd/microwatt base — 2 failing metric(s)
Metric target base delta limit band
detailedroute__antenna_diodes_count 1746.000 1639.000 6.528% 1640.639 Metric 0.100%
globalroute__antenna_diodes_count 3146.000 2504.000 25.639% 2506.504 Metric 0.100%

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>
@oharboe

oharboe commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Let lie for now.

@oharboe oharboe closed this Oct 24, 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.

2 participants