Place-and-Route
rb pnr takes a technology-mapped rb synth result, runs OpenROAD placement, clock-tree synthesis and routing, and reports area, timing and DRCs. It can also stream the layout to GDS and PNG with KLayout, harden a block for reuse in a larger design, and keep stage checkpoints.
Install the tools
OpenROAD 25Q1 or newer must be on PATH or set in cfg-pnr-tools. An older version logs a warning and the run continues, but that combination is not validated. On macOS, build from source with the template's tools/openroad/BUILD_OSX.md.
KLayout (brew install --cask klayout) is optional and only needed for --gds and --png. Without it those steps are skipped and the run does not fail. Install it later and export the saved result instead of rerunning P&R.
Define a P&R run
rtl-buddy-filetype: pnr_config
runs:
- name: demo_pnr_nangate45
desc: Nangate45 typical-corner P&R
tool: openroad
synth: demo_synth_nangate45
synth-path: ../../synth/demo/synth.yaml
constraints: ../../synth/demo/constraints.sdc
platform: nangate45_typ
floorplan:
utilization: 0.55
aspect: 1.0
core-margin: 2.0
reglvl: 1000
Paths resolve from pnr.yaml. The named synthesis must already have produced artefacts/<synth>/synth_netlist.v. The top module comes from that synthesis entry, and Liberty and LEF assets come from the platform. Only tool: openroad is supported; any other value reports SKIP. All fields are in YAML Formats: pnr.yaml.
Run P&R
rb pnr --list -c pnr/demo/pnr.yaml
rb pnr demo_pnr_nangate45 -c pnr/demo/pnr.yaml
rb pnr demo_pnr_nangate45 -c pnr/demo/pnr.yaml -l 1000
rb pnr demo_pnr_nangate45 -c pnr/demo/pnr.yaml --png --gds-mode strict
--png and --gds-mode imply --gds. KLayout runs after a successful OpenROAD run. In the default preview mode a KLayout failure logs a warning and leaves the P&R verdict unchanged. In strict mode an undelivered export fails the run; see Stream-out completeness.
Flow steps
The generated pnr.tcl runs these steps in order:
- Source
platform-tclwhen set, read Liberty, LEF, the synthesis netlist and the SDC, set the design's max fanout whenmax-fanoutis set, then sourcelayer-rc-tclwhen set. - Initialize the floorplan, create routing tracks and run
insert_tiecellsfor the PDK'stie-hiandtie-loports, one tie cell per constant net. - Place macros, insert tap and endcap cells when
tapcell-tclis set, build the power grid and place the IO pins, then runbuffer_ports -inputs -outputswhen the run buffers its ports (buffer-ports, on by default forharden: true; see Harden a block). - Run global placement, with
-reference_hpwlwhenplacement.reference-hpwlis set and-routability_driven/-routability_use_grtwhenplacement.routability-driven/placement.routability-use-grtaretrue, thenrepair_tie_fanoutfor each tie port, which gives every constant-driven load its own tie cellplacement.tie-separationmicrons away (default 0). - Run
repair_design, legalization, clock-tree synthesis (with-apply_ndrwhencts-apply-ndris set), setup repair whenpost-cts-setup-repairis set, hold repair and a final legalization. - Route globally with
-verboseand a congestion report, afterset_global_routing_layer_adjustmentwhenrouting-layer-adjustmentis set, repair hold on the global-route parasitics whenglobal-route-hold-repairis set, route in detail at the run'sdetailed-route-verboselevel, insert fill, extract parasitics whenrcx-rulesis set, then write reports and outputs.
A tie port the PDK leaves unset gets neither tie step.
Interpret results
The summary reports cell count, design area, setup and hold WNS, and the number of non-empty DRC report lines. Positive slack meets timing and zero DRC lines indicate a clean route. wns_setup_ps, wns_hold_ps, tns_ps (setup) and tns_hold_ps are converted to picoseconds from the Liberty time_unit; see Synthesis: Configure tools and the PDK.
Three result fields count cells; compare experiments on routed_cell_count:
cell_count, the summary's Cells column, is the input netlist's instance count at the floorplan. It leaves out every cell the flow adds.routed_cell_countis the finished design's instances, including tie, repair, clock-tree and hold buffers, excluding physical-only cells.physical_cell_countis the physical-only cells: masters of LEF classCORE SPACER,CORE WELLTAPorENDCAP*, or matching a PDKfill-cellspattern. A decap counts only when its class isCORE SPACERor it is listed infill-cells; sky130'sdecap_*cells are classCORE, so they count as routed unless listed.
The flow prints the last two after >>> Final reports as RB-CELL-COUNT: routed <n> physical <m>. If counting fails, both fields are absent and the run still passes.
A pass is qualified with pnr.no_wire_rc when the pre-route steps had no wire RC; see Set wire RC.
max_slew_violation_count, max_capacitance_violation_count and max_fanout_violation_count count the routed design's pins that break a Liberty or SDC max-slew, max-capacitance or max-fanout limit, over every corner, as ORFS's report_metrics counts them. The summary shows them as the Slew/Cap/Fanout column, pnr.log prints them as RB-ELECTRICAL: max_<check> <n> after >>> Final reports, and electrical.rpt lists the violating pins. A pass with any violator is qualified electrical N max-slew, ... violator(s) and logs the warning pnr.electrical_violators. A count OpenROAD cannot report is left out.
A run passes when OpenROAD exits 0 with no [ERROR ...] line and, under gds-mode: strict, the requested export was delivered complete. With fail-on-electrical: true a run with any electrical violator also fails, with no fail_stage, so xfail: can excuse it; it keeps its routed outputs and publishes no abstract. It skips when reglvl filters it out or tool: is unsupported. Timing violations and DRC counts are metrics only; gate signoff on them in your project.
On a multi-corner platform, wns_setup_ps, wns_hold_ps, tns_ps and tns_hold_ps are the worst across corners. The result also names the worst_setup_corner and worst_hold_corner and lists each corner's own four values under corners, so setup at the slow corner and hold at the fast corner can be read separately; pnr.log has them after >>> Per-corner timing.
Inspect artefacts
Outputs land under <pnr-dir>/artefacts/<run>/.
| File | Purpose |
|---|---|
pnr.log, pnr.tcl | OpenROAD output and generated flow |
<top>.def, <top>.routed.v, <top>.routed.sdc | Routed DEF, post-route netlist and constraints |
<top>.routed.odb | OpenROAD database read by post-P&R rb power |
<top>.routed.spef | Extracted parasitics; only when the PDK sets rcx-rules |
timing.rpt | Worst-path timing across all corners |
electrical.rpt | Max-slew, max-capacitance and max-fanout violators across all corners |
route.drc.rpt, route.maze.log | DRC summary and detailed-route log |
congestion.rpt | Overflowing global-route tiles; written only when global routing overflows |
<top>.gds, <top>.png, klayout.*.log | Optional KLayout outputs and logs |
export.provenance.json | What the last rb pnr-export read and produced |
checkpoints/, abstract/ | Optional stage checkpoints and hardened-block abstract |
Each run deletes the previous run's outputs first, and a run that fails after writing the routed database removes it again, so rb power never reads a stale one. pnr.log, pnr.tcl and congestion.rpt are kept from a failed run.
On failure, read pnr.log. If only KLayout failed, read the matching klayout.*.log, fix the installation and rerun with --gds or --png.
Configure the physical platform
Define PDK files once under cfg-pdks. Select a process and corner for P&R under cfg-pnr-platforms:
cfg-pnr-platforms:
- name: nangate45_typ
pdk: nangate45
corner: typ
cts-buffer: BUF_X4
routing-layers:
signal: metal2-metal8
clock: metal4-metal8
The PDK entry supplies Liberty, technology and macro LEF, cell GDS, site and other cell names. See Synthesis: Configure tools and the PDK and the root config schema.
Sign off at several corners
corners: in place of corner: analyses a list of the PDK's corners together. rb pnr and rb power read it; rb synth stays single-corner.
cfg-pdks:
- name: sky130hd
corners:
tt: pdk/sky130hd/lib/sky130_fd_sc_hd__tt_025C_1v80.lib
ss: pdk/sky130hd/lib/sky130_fd_sc_hd__ss_n40C_1v40.lib
cfg-pnr-platforms:
- name: sky130hd_mc
pdk: sky130hd
corners: [tt, ss] # the first entry is the primary corner
- All corners run in one OpenROAD session, so repair and the final worst-slack and TNS reports see every corner. Clock-tree synthesis uses the primary (first) corner.
cornerandcornersare mutually exclusive. The list must be non-empty, and each name must be declared in the PDK, appear once, and use only letters, digits,_,.and-. A one-entry list is the same run ascorner:. A platform that sets neither uses the PDK's first corner.- A macro with one Liberty in
lib-pathsis read into every corner and keeps the timing of the corner it was characterised at, so a slow corner can fail because of the macro (for exampleRSZ-0090from a TT SRAM Liberty). Give such a macro a Liberty per corner. OpenSTA logsSTA-1140 library ... already existsonce per extra corner; that is expected.
Tune the process-dependent steps
Placement, clock-tree and power-grid steps default to values calibrated for Nangate45. Another process declares its own. Every key below is optional.
cfg-pdks:
- name: sky130hd
# ...
placement:
density: 0.55 # default 0.7
padding: 2 # default 1, in sites
macro-halo: 30.0 # default 20.0, in microns
dont-use-cells: ["sky130_fd_sc_hd__probe*"]
pdn-config: pdk/sky130hd/pdn.tcl
rcx-rules: pdk/sky130hd/rcx_patterns.rules
cfg-pnr-platforms:
- name: sky130hd_tt
pdk: sky130hd
cts-buffer: [sky130_fd_sc_hd__clkbuf_4, sky130_fd_sc_hd__clkbuf_8]
placement:
density: 0.6 # this platform only; padding stays the PDK's 2
placement.densityis the global-placement target utilization, above 0 and at most 1.placement.paddingis cell padding in sites. A platform overrides either one field by field.placement.macro-halo(default 20.0 µm) is the channel kept between macros and between a macro and each core edge. Below about 19 µm on sky130hd,pdngenfails withPDN-0179. Raise it for a coarser grid; lower it only when macros do not fit.placement.macro-cell-halo(default 1.0 µm) keeps standard cells off macros with a hard blockage. Without it, the router can report a Metal Spacing violation at a pin on a hardened block's edge.0disables it.placement.tie-separation(default 0 µm) is how far from its load each per-load tie cell is placed; see Flow steps.placement.reference-hpwl(positive number, unset by default) passes-reference_hpwltoglobal_placement. Unset, the placer's density-penalty controller derives its reference HPWL from the design size, which on a large design spreads cells less and ends routability inflation early, so global route can overflow. A fixed value such as446000000spreads cells further and can clear the overflow.placement.routability-driven(defaultfalse) adds-routability_driventoglobal_placement: the placer estimates congestion, inflates cells in congested regions and re-spreads them. Turn it on for a congested block whose global route overflows.placement.routability-use-grt(defaultfalse) adds-routability_use_grt, which estimates that congestion with the global router instead of RUDY; slower, closer to the real route. It takes effect only in routability mode, so a platform whose resolved settings enable it withoutroutability-drivenfails to load. Like the otherplacement.*keys, each is set on the PDK and overridden per platform.cts-buffertakes a name or a list. With a list, the first entry is the root buffer.port-buffer(unset by default) is the cellbuffer_portsinserts on each port of a run withbuffer-portson, passed as-buffer_cell; unset lets OpenROAD choose. See Harden a block.cts-sink-clustering(defaulttrue) passes-sink_clustering_enableto CTS. Set it tofalsewhen CTS fails withCTS-0080 Sink not found, as it can on coincident clock pins or a large ASAP7 clock tree.cts-apply-ndr(unset by default) passes-apply_ndr(none,root_only,halforfull) to CTS; unset keeps OpenROAD's default,half. Setnonewhen the global router detours clock nets that carry non-default rules to several times their Manhattan length, which shows as large hold violations andGRT-0273 Disabled NDRin the log.max-fanout(positive integer, unset by default) addsset_max_fanout <n> [current_design]after eachread_sdcinrb pnrandrb power, replacing a design-level limit from the SDC. When neither the Liberty nor the SDC sets a limit,repair_designuses 50 and buffers every larger net; on a library without one, such as SKY130 HD, that costs setup timing. A large value such as100000stops repair buffering for fanout alone.post-cts-setup-repair(defaultfalse) runsrepair_timing -setupafter CTS, before hold repair. Turn it on when the post-CTS netlist misses setup; it adds buffers and resizes cells, so QoR changes.global-route-hold-repair(defaultfalse) repairs hold again after global route, onestimate_parasitics -global_routing, then legalizes and reroutes the new buffers before detail route, as ORFS does; see Set wire RC.routing-layer-adjustment(0 to 1, unset by default) withholds that fraction of each signal layer's capacity from the global router (set_global_routing_layer_adjustment, ORFSROUTING_LAYER_ADJUSTMENT, 0.25 on ASAP7). Raise it when detailed routing ends with DRCs in congested areas; unset leaves the router's default.pdn-configis a path, resolved fromroot_config.yaml, to a Tcl snippet that declares the power grid (add_global_connection,set_voltage_domain,define_pdn_grid,add_pdn_stripe,add_pdn_connect). The flow sources it after macro placement and callspdngenitself, so the snippet must not. Unset means no power grid. A run can replace it or its core grid; see Plan the power grid per run.dont-use-cellsandrcx-rulesare described below, and the platform Tcl hooks under Source platform Tcl hooks.
Exclude cells with dont-use-cells
dont-use-cells is a list of cell names or patterns on the PDK, read by both rb pnr and rb synth. P&R applies it before the floorplan, so no placement, repair or CTS step chooses a listed cell. Synthesis passes it to Yosys and, on the OpenROAD backend, to resynthesis.
- Write one pattern per entry. Only
*and?are allowed. An entry with whitespace or a Tcl metacharacter ([ ] { } $ " \ ;) is rejected when the config loads. - A synth or P&R platform can add its own list, appended to the PDK's. A platform's list reaches only its own flow; the PDK's reaches both.
With any cell excluded, the flow checks the routed design after detailed routing, before any output is written. It prints each offending instance to pnr.log as RB-DONT-USE-VIOLATION: <instance> <master> <pattern> and fails the run, which catches a cell the synthesis netlist already instantiated. An xfail: marker can excuse it.
A pattern that matches no Liberty cell excludes nothing. OpenROAD reports STA-0122 cell '<pattern>' not found and rb logs a warning naming the pattern.
Extract parasitics with rcx-rules
rcx-rules is a path, resolved like pdn-config, to an OpenRCX extraction-rules file (ORFS RCX_RULES). Its LayerCount must match the technology LEF's routing stack.
With it set, the flow extracts parasitics after fill insertion, writes <top>.routed.spef, and times the final reports on that SPEF instead of the global-route estimate. The summary's WNS and TNS come from the extracted parasitics, and a netlist-source: pnr power run reads the same SPEF. With the key unset there is no extraction.
Source platform Tcl hooks
Four optional PDK paths, resolved like pdn-config, name Tcl scripts the flow sources at the step where OpenROAD-flow-scripts (ORFS) sources its own. A platform taken from ORFS, such as ASAP7, needs them.
| Key | ORFS variable | Sourced |
|---|---|---|
platform-tcl | PLATFORM_TCL | Before the first read_liberty, so suppress_message lines cover the Liberty reads |
layer-rc-tcl | SET_RC_TCL | After read_sdc, so repair_design, CTS, hold repair and the final reports see wire RC |
tracks-tcl | MAKE_TRACKS | After initialize_floorplan, instead of the bare make_tracks |
tapcell-tcl | TAPCELL_TCL | After macro placement and the macro keep-outs, before the power grid |
cfg-pdks:
- name: asap7
# ...
platform-tcl: pdk/asap7/liberty_suppressions.tcl
layer-rc-tcl: pdk/asap7/setRC.tcl
tracks-tcl: pdk/asap7/make_tracks.tcl
tapcell-tcl: pdk/asap7/tapcell.tcl
- An unset key leaves the flow as it is:
make_trackswith the technology LEF's default tracks, no taps, and the LEF's layer RC, which is zero on ASAP7. - Each file is sourced as written, so it must not depend on ORFS environment variables. ORFS' ASAP7
openRoad/tapcell.tcltakes both its tap and endcap master from$::env(TAP_CELL_NAME)and its macro halo from$::env(MACRO_ROWS_HALO_X)and$::env(MACRO_ROWS_HALO_Y); copy it with those values filled in. - A configured file missing from disk fails the run at
setup, naming the key. rb powersourcesplatform-tclbefore its Liberty reads. The layer RC is session state that the routed ODB does not keep, so anetlist-source: pnrpower run also sourceslayer-rc-tclafterread_sdc.- The hooks are inputs to stage checkpoints and to a hardened block's abstract, so changing one makes the abstract stale.
Set wire RC
Set the PDK's layer-rc-tcl so clock-tree synthesis, placement parasitics and hold repair see wire resistance and capacitance. Without it they see only the technology LEF's layer RC, which is zero on most open PDKs. CTS then balances, and hold repair fixes, a design with no wires, and the routed timing can fail hold that repair never saw.
rb pnr warns with pnr.no_wire_rc when the PDK sets no layer-rc-tcl, or when pnr.log has OpenROAD's [WARNING EST-0018] (zero wire capacitance) or [WARNING CTS-0104] (zero clock wire RC). The warning is logged and appended to a passing run's description, naming the cause:
P&R passed; pnr.no_wire_rc: pre-route steps saw no wire RC (no layer-rc-tcl, EST-0018, CTS-0104); see rb docs show concepts/pnr#set-wire-rc
For sky130hd, copy the set_layer_rc lines of OpenROAD's test/sky130hd/sky130hd.rc and add the wire RC layers for signal and clock nets:
# pdk/sky130hd/setRC.tcl, named by cfg-pdks layer-rc-tcl
set_layer_rc -layer met1 -capacitance 1.72375E-04 -resistance 8.929e-04
# ... one line per layer and via, from sky130hd.rc
set_wire_rc -signal -layer met2
set_wire_rc -clock -layer met5
To also catch hold growth from wire lengths before detail route, set global-route-hold-repair: true on the platform. It runs repair_timing -hold on the global-route estimate, then legalizes the added buffers and reroutes their nets incrementally. It is off by default because it changes QoR. A global_route checkpoint holds the route before this repair.
Macro placement
A netlist that instantiates hard macros, such as an SRAM or a partition hardened by an earlier run, gets automatic macro placement before the power grid is built. To use OpenROAD's placer instead, see RTL-MP macro placement.
- The packer sorts macros tallest first, then widest, and packs them left to right into rows starting at the core's lower-left corner, or at the corner
floorplan.macro-anchornames. - It keeps
placement.macro-halobetween neighbours and at every core edge, and snaps each origin to the standard-cell site grid. - Placed macros are
FIRM; global and detailed placement leave them where they are. - To fix, orient or halo a single macro, see Place individual macros.
When macros do not fit, the run fails with N macros do not fit the W x H um floorplan core, followed by the largest footprint, the macro and core areas, and the smallest core at the run's aspect ratio that would fit. Lower the floorplan utilization, change its aspect ratio, or lower placement.macro-halo.
RTL-MP macro placement
floorplan.macro-placement: rtl-mp (default pack) hands macros to OpenROAD's hierarchical macro placer, rtl_macro_placer, instead of the packer:
floorplan:
utilization: 0.45
macro-placement: rtl-mp
RTL-MP places macros by connectivity and wirelength and may rotate them. Macros end up LOCKED on the track grid, and reports go to artefacts/<run>/rtlmp/. Use it for a flat netlist with many memories or when timing depends on macro placement; on the template's sky130hd assembly it gave about 0.8 ns better setup slack than the packer.
- It keeps
placement.macro-haloaround every macro and keeps macros out of every placement blockage, includingsoftandpartialones that the packer lets macros sit on. macro-cell-haloand the power grid apply afterwards as usual.- Setting
macro-anchorwithrtl-mpis a configuration error. Its result also changes when the netlist does, so use the packer to pin a macro whose neighbour's pin plan depends on its location. macro-placementis part of a hardened block's configuration, so switching placer makes the block's abstract stale.
Floorplan controls
Two optional floorplan keys steer where macros and standard cells may go.
floorplan:
utilization: 0.45
macro-anchor: upper-right # default lower-left
blockages:
- rect: [10, 10, 130, 60] # microns, die coordinates: x0 y0 x1 y1
- rect: [400, 300, 520, 420]
type: partial
max-density: 0.4
Macro anchor. macro-anchor is the core corner the macro packer starts from: lower-left (default), lower-right, upper-left or upper-right. Rows fill away from that corner, so the opposite edges stay clear of macros where possible. Constrain pins to those edges with floorplan.pins when a neighbouring block abuts one side. Set it per run, not per platform.
Placement blockages. Each blockages entry is rect: [x0, y0, x1, y1] in die microns plus a type:
type | Effect |
|---|---|
hard (default) | No standard cell is placed inside. The rows under it are cut before tap insertion, so no tap or endcap lands inside and the cut row ends get endcaps. The packer moves overlapping macros past it. |
soft | Global placement keeps cells out; later repair and legalization may use the area. Macros may sit on it, except under rtl-mp. |
partial | Global placement caps cell density at max-density (strictly between 0 and 1); legalization then treats the area as fully blocked. Macros may sit on it, except under rtl-mp. |
A row cut rounds outward to whole sites, so the remnants stay on the site grid. A remnant narrower than twice the row height is dropped. The power grid's followpins follow the cut rows.
Blockages need OpenROAD 26Q1 or newer; on an older build the run fails at setup. A malformed rectangle (x0 >= x1, y0 >= y1, a side under 0.001 µm, a negative coordinate, or not four numbers), an unknown type, or max-density on anything but partial fails when pnr.yaml loads.
Place individual macros
floorplan.macros gives individual macros a fixed location, an orientation or their own standard-cell halo. Each entry names an instance, by its full hierarchical name or a glob over the names:
floorplan:
macros:
- instance: u_inner/u_hist_mem/u_sram
location: [200, 290] # lower-left corner, die microns
orientation: MX
halo: [3, 3] # standard-cell keep-out, x and y, microns
- instance: "*/u_c*" # every matching macro
orientation: R180
| Key | Meaning |
|---|---|
instance | A macro instance name as OpenROAD reads it from the netlist, such as u_inner/u_hist_mem/u_sram. A name that is not an instance is matched as a glob, where * also crosses /. |
location | [x, y], the macro's lower-left corner in die microns, after orientation. The macro is fixed (LOCKED) before the packer or RTL-MP runs, and both leave it there. The pattern must match exactly one macro. |
orientation | R0, R180, MX or MY. Applied before placement, so a packed macro keeps it. |
halo | [x, y] in microns. Replaces placement.macro-cell-halo for the matched macros: no standard cell is placed within x of the macro's left and right edges or y of its top and bottom. [0, 0] removes the halo. |
- A location snaps to the nearest standard-cell site and row from the core's lower-left corner, the grid the packer also uses, so the macro's bottom edge lies on a row boundary. A macro that fits the core before snapping still fits after. The log shows
fixed <inst> at (x, y) um <orientation> (requested (x, y)). - A fixed macro must lie inside the core and must not overlap another fixed macro; either fails the run naming the entry. Overlap with a placement blockage is allowed.
- With the packer, fixed macros are keep-outs: the other macros stay
placement.macro-haloaway from them, as from each other. Withrtl-mp, RTL-MP treats them as fixed obstacles. - Rotations by 90 degrees (
R90,R270,MXR90,MYR90) are refused: they swap the macro's width and height off the site grid and turn its pins across the routing tracks. Underrtl-mp, anorientationwithout alocationis refused, because RTL-MP picks each placed macro's orientation itself. - A macro may be matched by several entries, but only one entry may set each key for it.
- At run time a pattern that matches no instance, or that matches a standard cell, fails the run:
floorplan.macros[i]: no instance matches '<pattern>'. floorplan.macrosis part of a hardened block's configuration. For a hardened block placed with alocation, check that its supply pins land on the parent's straps, and that a mirrored block's net order still fits the parent's grid.
Size the die and core
By default utilization, aspect and core-margin size the floorplan from the netlist's cell area. To give exact dimensions instead, set both rectangles in die microns:
floorplan:
die-area: [0, 0, 160, 160] # x0 y0 x1 y1
core-area: [20, 20, 140, 140]
core-cutouts:
- [80, 80, 140, 140] # an L-shaped core
die-areaandcore-areago together and runinitialize_floorplan -die_area -core_area. They excludeutilization,aspectandcore-margin; setting any of those with them is a load error. The core must lie inside the die. OpenROAD snaps the core edges inward onto the site grid and logsIFP-0028.core-cutoutscarves rectangles out of the core for an L, T or other rectilinear core. Each is a hard placement blockage whose rows are cut before tap insertion, like ahardblockage, and a keep-out for the macro packer. Withcore-areaset, a cut-out must lie inside it; at run time, one that does not overlap the core fails the run. It works with utilization sizing too, but the core is still sized for the whole cell area, so lowerutilizationto leave room.- The die stays rectangular: OpenROAD has no non-rectangular die. A cut-out keeps standard cells, rows, taps, followpin rails and macros out; it does not block routing. Core-grid straps still cross it and stay connected through the rows on either side, so
check_power_gridpasses. IO pins are still placed along the whole die edge; keep them off the edges next to a cut-out withfloorplan.pins. - All three keys are part of a hardened block's configuration when set.
Plan the power grid per run
The PDK's pdn-config declares the power grid for every run on it. A run can replace or reshape it:
pdn-config:on the run is a Tcl file, relative topnr.yaml, sourced instead of the PDK's. It follows the same contract: declare the grid, never callpdngen.pdn:on the run declares the core grid in YAML. It replaces the core grid of the pdn-config in effect (the run's or the PDK's), which must exist. Everything else in that file still runs: global connections, voltage domains and macro grids.
pdn:
ring: {layers: [met5, met4], width: 1.6, spacing: 1.7, offset: 2}
stripes:
- {layer: met1, width: 0.48, followpins: true}
- {layer: met4, width: 1.6, pitch: 20, offset: 5}
- {layer: met5, width: 1.6, pitch: 20, offset: 5}
# connect: [[met1, met4], [met4, met5]] # default: each stripe layer to the next
| Key | Meaning |
|---|---|
ring | Optional core ring: layers [horizontal, vertical], width, spacing between the power and ground rings, and offset from the core edge, all in microns (add_pdn_ring -core_offsets). It needs offset + 2 x width + spacing of room between core and die, which is checked against core-margin or the explicit areas when the run loads. With a ring, every stripe extends to it. |
stripes | The core grid's stripes, bottom layer first. A followpins: true entry gives the rails along the standard-cell rows and takes only width. Any other stripe needs pitch, and takes optional offset and spacing (add_pdn_stripe). |
connect | Optional [lower, upper] layer pairs to join with vias (add_pdn_connect). By default each stripe layer is joined to the next one listed. A ring on layers that also carry stripes needs nothing more; put other ring layers in connect. |
While it sources the pdn-config, the flow skips each define_pdn_grid that is neither -macro nor -existing, and that grid's stripes, rings and connects, and logs pdn: core grid '<name>' from the pdn-config replaced by the run's pdn: block. The run's grid keeps the skipped grid's name, voltage domains, -pins layers and starting net; without one it is rb_core on OpenROAD's default domain. Macro grids that connect to the core straps, as the template's -grid_over_boundary grids do, keep working only if pdn: still has straps on the layers they connect to.
pdn:with no pdn-config at all (for example on Nangate45) fails at setup: the global connections and voltage domain have to come from Tcl.- Malformed entries fail at load: a stripe without
pitch, two stripes wider than their pitch,pitchon a followpins entry, a ring without two layers, or a ring too wide for the core-to-die gap. - Switchable power domains are not supported: they need power-intent (UPF) support, which
rb pnrdoes not have. Use one always-on domain. pdn-configandpdnare part of a hardened block's configuration when set.
Constrain boundary pins
floorplan.pins constrains IO pins in pnr.yaml. Each entry names ports by name or glob in names, and either constrains them to an edge, keeps them together, or places one pin exactly:
floorplan:
pins:
- names: [req_*, clk] # the whole left edge
side: left
- names: rsp_*
side: right
start: 10 # microns, die y from 10 to 80
end: 80
- names: pwdata # a bus: every bit, together, in bit order
side: top
group: true
order: true
- names: irq
location: [0, 42.5] # pin centre, die microns
layer: met3 # optional
size: [0.3, 0.6] # optional, microns
| Key | Meaning |
|---|---|
names | Port names or globs, one string or a list. A bus name matches every bit. |
side | left, right, top or bottom: set_io_pin_constraint -region <side>:<start>-<end>. |
start / end | Optional range along the side in die microns: y for left and right, x for top and bottom. Either one alone leaves the other end open. Without both the whole edge is allowed. |
group / order | group: true keeps the matched pins next to each other (-group); order: true also keeps them in the order matched. With no side the group may land on any edge. |
location | [x, y] pin centre in die microns. Places exactly one pin with place_pin -force_to_die_boundary, so it moves onto the nearest die edge for its layer's direction and onto the nearest routing track; the log reports the requested and the final centre. The pin is fixed, and pin placement leaves it there. |
layer | With location only. Default: the PDK's horizontal pin layer on a left or right edge, its vertical one on a top or bottom edge, judged by the nearest edge. |
size | With location only. [width, height] in microns. Default: the layer's minimum width and area. |
- Exact pins are placed first, then the edge and group constraints, then the
pin-constraintsfile below, all just beforeplace_pins. A pin both placed and matched by a side keeps its location. - A name or glob that matches no port fails the run with
floorplan.pins[i]: no port matches '<pattern>', whereset_io_pin_constraintalone would only warn. Alocationentry whose name matches more than one port fails too. - A malformed entry fails when
pnr.yamlloads, namingfloorplan.pins[i]: an unknown side,startat or aboveend, a negative coordinate,startorendwithoutside,orderwithoutgroup,locationwithside,groupor more than one name,layerorsizewithoutlocation, or an entry that sets none ofside,groupandlocation. - A
harden: truerun places its pins the same way, and the published abstract carries them.floorplan.pinsis part of a hardened block's configuration, so changing it makes the block's abstract stale. - Keep clock pins on layers inside the platform's clock-routing range. To keep a pin edge clear of macros, anchor the packer at the opposite corner with
floorplan.macro-anchor.
For anything floorplan.pins does not cover, such as -mirrored_pins or exclude_io_pin_region, set pin-constraints: pins.tcl on the run. The path is relative to pnr.yaml and a missing file fails the run. The file is sourced just before pin placement, after macro placement, the power grid and floorplan.pins:
exclude_io_pin_region -region bottom:0-20
set_io_pin_constraint -mirrored_pins {a_in a_out}
Put these commands here, not in SDC, which is read before the die exists.
PDK setup notes
The same flow runs on any PDK the root config declares. Switching PDKs is a platform: change in the run YAMLs.
- Nangate45 (FreePDK45). All defaults are calibrated on it. Set
site, one Liberty corner,tech-lef,macro-lef, the tie and fill cells andcts-buffer: BUF_X4, and leave every process key unset. It has no PDN snippet, so runs have no power grid. For extraction, setrcx-rulesto ORFS'flow/platforms/nangate45/rcx_patterns.rules; its LEF has no via resistance, so vias extract as 0 Ω. - sky130hd. The template's
sky130hdPDK entry is a worked example, used by thedemo_tiny_alu_subsys_hierruns. Beyond Nangate45's fields it needspin-layers(met3/met2),routing-layersinmet*names, apdn-config,placement.density: 0.60, adont-use-cellslist for the probe andlpflowcells, and acts-bufferlist. Use ORFS'flow/platforms/sky130hs/rcx_patterns.rulesforrcx-rules. - ASAP7. Follow ORFS'
flow/platforms/asap7. Set the four platform Tcl hooks to itsliberty_suppressions.tcl,setRC.tcl,openRoad/make_tracks.tcland a copy ofopenRoad/tapcell.tclwithTAP_CELL_NAMEandMACRO_ROWS_HALO_X/_Yfilled in; withouttracks-tclthe run stops atmake_trackswithIFP-0039. List each corner's split Liberty files under that corner.macro-leftakes one path, so list the extra LEFs inlef-paths.
Follow routing progress
pnr.log is OpenROAD's output, written as it happens, so tail -f artefacts/<run>/pnr.log follows a running flow.
- Every global route runs with
-verbose. After it,pnr.loghas the per-layer resource and usage table ([INFO GRT-0096] Final congestion report:), which shows the congestion margin of a passing run. Withglobal-route-hold-repair, the incremental reroute prints the table again. - When global routing overflows, each overflowing tile, with its capacity, usage and nets, goes to
congestion.rpt, in the DRC-report format the OpenROAD GUI's DRC viewer loads. A route that fails withGRT-0116keeps it. - Detailed routing logs each optimization iteration (
[INFO DRT-0195] Start 1st optimization iteration.), its progress (Completing 30% with 88 violations.) and its result ([INFO DRT-0199] Number of violations = 16.), so a converging route can be told from a stalled one. Setdetailed-route-verbose:on the run to change the level:0silences the iterations; higher levels add more router detail.
runs:
- name: demo_pnr_nangate45
# ...
detailed-route-verbose: 0 # default 1
OpenROAD threads
OpenROAD runs on one thread unless the run asks for more. Reserving CPUs from a scheduler does not change that. Set threads: on the run:
runs:
- name: demo_pnr_nangate45
# ...
threads: 8 # or: auto
- Unset gives 1 thread. A positive integer gives that many.
autogives the CPUs of the allocation the run is in, or 1 with none; never the host's core count. - Zero, negative numbers, booleans, quoted numbers and other strings fail configuration loading.
rb pnr is not dispatched. Run it inside an allocation (srun -c 8 rb pnr ... or an sbatch script) to give OpenROAD CPUs. The allocation is SLURM_CPUS_PER_TASK (else SLURM_CPUS_ON_NODE) in a Slurm job, or a smaller CPU affinity mask on Linux. A container CPU quota (docker --cpus) is not visible.
A count above the allocation is clamped to it with an openroad.threads_capped warning naming both numbers. Outside an allocation an explicit count is used as given.
--machine output carries openroad_threads with the requested, effective and allocation values. The same rules apply to rb power and the OpenROAD stage of rb synth. Results are not guaranteed to be bit-identical across thread counts; compare DRC and slack if it matters.
Keep stage checkpoints
A routing run killed at a scheduler wall limit leaves only its log, because the DEF and ODB are written after detailed routing. Set checkpoints: on the run to keep a database at each stage boundary:
runs:
- name: demo_pnr_nangate45
# ...
checkpoints: true # or a stage, or a list: [cts, global_route]
| Stage | Written | Holds |
|---|---|---|
floorplan | before global_placement | floorplan, pins, tie cells, placed macros, PDN |
place | before clock_tree_synthesis | legalized global placement |
cts | before global_route | clock tree, hold repair, legalization |
global_route | after a successful global_route | the global route |
true keeps all four stages; a name or list keeps some; [] keeps only the progress file. Unset or false disables the feature. Each stage writes <NN>_<stage>.odb, .def and .sdc into artefacts/<run>/checkpoints/<timestamp>-<pid>/, and checkpoints/latest points at the newest run.
tail -f artefacts/<run>/checkpoints/latest/progress.jsonlfollows a running flow. After a kill, the laststep_beginwith nostep_endis the step the run was in.- A checkpoint is never final.
rb powerand a plainrb pnr-exportnever read it. - Old run directories are not pruned. Delete the ones you no longer need.
Resume from a checkpoint is not supported. To look at one, open its ODB in OpenROAD or stream it out with rb pnr-export --checkpoint.
Stream-out inputs
- Layout. Cell layouts come from the PDK's
cell-gds(one path or a list), then the run'sgds-paths. Put a hard macro's layout ingds-paths; an OpenRAM SRAM has its LEF inlef-pathsand its GDS ingds-paths. Paths resolve against the file that names them. - LEFs. The DEF reader gets the technology LEF, the PDK's macro LEF, then the run's
lef-paths. - Record.
def2stream.inputs.jsonin the artefact directory shows what a run streamed.
A configured input that is missing on disk stops the export before KLayout starts, with every missing path reported at once.
Stream-out completeness
A cell that the DEF instantiates but no GDS contains is streamed as an empty placeholder. Sometimes that is intended, such as an ORFS fakeram45 macro that exists only in LEF. Sometimes a real SRAM GDS was forgotten. gds-mode says which the run means:
runs:
- name: demo_pnr_signoff
# ...
gds-mode: strict # default: preview
gds-allow-empty: # cells that are empty on purpose
- fakeram45_*
preview(default) keeps the layout and reports what is missing: apnr.gds_incompletewarning naming every cell,GDS incomplete: …in the run description, andgds+png (incomplete: N missing)in the summary.--machineoutput hasgds_status: incompleteandgds_missing_cells. The run still passes.strictrefuses to publish an incomplete layout. Cells with no layout, a missing KLayout, a PDK with noklayout-tech, a missing input, any stream-out failure and a failed--pngrender each make the export aFAILwithfail_stage: export. The GDS, PNG and stream-out report are removed; the routed outputs stay.xfail:does not excuse an export failure.
gds-allow-empty takes cell names or case-sensitive fnmatch globs. A covered cell is not missing in either mode and is counted as (N empty by design) in the summary.
Export a saved result
KLayout, a PDK's GDS or the layer properties often arrive after P&R does. rb pnr-export streams an existing routed result out again, DEF to GDS to PNG, without rerunning synthesis or OpenROAD:
rb pnr-export demo_pnr_nangate45 -c pnr/demo/pnr.yaml --png --gds-mode strict
rb pnr-export demo_pnr_nangate45 -c pnr/demo/pnr.yaml --checkpoint cts --png
rb pnr-export demo_pnr_nangate45 -c pnr/demo/pnr.yaml --png-only --lyp dark.lyp --png-width 4096 --png-height 4096
Run selection, -c, -l and --gds-mode work as for rb pnr, and the same stream-out inputs and gds-allow-empty apply. The export needs the synth.yaml entry (for the design name) but none of its artefacts. rb tool-check --required-for pnr-export checks for KLayout only.
--def <path>exports a DEF from elsewhere, with platform and top taken from the run. It needs a single named run.--checkpointexports a stage checkpoint instead of the routed result. It needs a single named run and excludes--def. Give a stage (ctsor03_cts) for thelatestrun,<run-id>/<stage>for an older one, or a path to a checkpoint file. Outputs go tocheckpoints/<run-id>/export/<NN>_<stage>/and are marked not final.--png-onlyre-renders the PNG from the GDS already in the artefact directory, without stream-out.--lyp,--png-widthand--png-heightchange how it is drawn.
Before launching, the export checks that the routed DEF exists, is non-empty and declares the design the synth entry names; a mismatch fails with both names. A missing layer properties file, klayout-tech, input or KLayout stops it the same way. It clears only its own outputs (GDS, PNG, def2stream.*, export.provenance.json) and never touches the routed results, even when it fails.
The export is the whole verdict here. Everything requested on disk is PASS (exit 0), including a preview layout with the GDS incomplete: … qualifier. Anything else, in either mode, is FAIL with fail_stage: export and exit 1. export.provenance.json records the tool, the input DEF or GDS with its SHA-256, the render options and the outcome.
Harden a block
A block that a larger design instances as a hard macro needs three views of its routed result: an abstract LEF, a Liberty timing model and its layout. Set harden: true on the block's run to publish them to artefacts/<run>/abstract/ as <top>.lef, <top>.lib, <top>.gds and abstract.manifest.json:
runs:
- name: alu_block_pnr
# ...
platform: sky130hd_tt_block
harden: true
hardenimplies--gdsand forcesgds-mode: strict, so it needs KLayout.- The run also elaborates the block's top in Yosys, with the frontend, sources, defines and Liberty of the block's synthesis, and records the values of its parameters and localparams as
<top>.params.json. Only names the top declares in its own scope are recorded: a generate loop's genvar, or a localparam inside a generate block, function or procedural block, is not. Parents' overrides are checked against it (see Instance a parameterised block). The probe's script, log and output stay inparam_probe/of the artefact directory until the next run. If Yosys cannot elaborate the top, the abstract is published without the record and the run warnspnr.block_params_unrecorded. - The views are published together or not at all. If one cannot be produced, the run fails with
fail_stage: abstractand leaves noabstract/; the routed DEF and ODB stay. Each rerun removes the previous abstract first. hardenturns onbuffer-ports:buffer_ports -inputs -outputsruns after IO pin placement and before global placement, as ORFSglobal_place.tcldoes, andrepair_designsizes the buffers. Each input then drives one buffer instead of its internal loads, and each output is driven by one, so the abstract's pin capacitance and drive do not depend on the block's internals. Without it, an input fanning out to a thousand loads carries their whole capacitance into the Liberty model for the parent to drive. OpenROAD leaves clock, constant and special nets unbuffered. The platform'sport-buffernames the buffer cell; unset, OpenROAD picks one, and on sky130hd it picks the delay cellsky130_fd_sc_hd__clkdlybuf4s50_1, so setport-bufferthere (ORFS usessky130_fd_sc_hd__buf_4as its minimum buffer). Setbuffer-ports: falseto turn it off, orbuffer-ports: trueon a run that does not harden.buffer-portsis part of the block's configuration only when on, andport-bufferonly when it is set and ports are buffered, so an abstract hardened by an rtl_buddy without it is stale (config changed (buffer_ports)) until the block is re-hardened, and one hardened withbuffer-ports: falseis not.- An abstract carries one corner's timing model, so
hardenon a multi-corner platform is refused before OpenROAD starts. - The model has timing arcs only, so a parent's
rb powersees the block as drawing 0 W. Runrb poweron the block itself.
Choose the abstract's obstruction style
By default the abstract LEF is written with write_abstract_lef -bloat_occupied_layers: every layer the block uses is an obstruction over its whole footprint. A block with supply straps on met4 then blocks met4 everywhere, and a parent can cross it only on met5. abstract: on the hardened run chooses otherwise:
runs:
- name: alu_block_pnr
harden: true
abstract:
bloat: occupied # default; or a number, OpenROAD's -bloat_factor
feedthrough-layers: [met4] # these layers get the block's real shapes
bloat: <number>writes-bloat_factor <number>instead: the block's shapes grown by that factor, so a parent can route between them on every layer.0writes the real shapes.feedthrough-layerskeeps thebloatstyle on every other layer and writes the listed ones with the block's real shapes as obstructions. rtl_buddy writes a second LEF with-bloat_factor 0and splices its obstructions on those layers into<top>.lef. A listed layer the block does not use has no obstruction; one that appears in neither LEF, likely a misspelling, warnspnr.abstract_feedthrough_layers_unused.abstract:needsharden: true.- It is part of the block's configuration only when it differs from the default, so abstracts hardened before it existed stay current, and changing it makes the abstract stale (
config changed (abstract)) until the block is re-hardened. A hand-edited LEF is still stale. - With fewer obstructions a parent's router can route over the block. Check the parent's DRC and the block's pins and straps after changing the style.
Block power-grid convention
A parent ties a hardened block into its grid the way it ties in any macro: its top-level straps cross the block and drop vias onto the block's power pins. This works only if the block leaves the top layers free. rtl_buddy does not enforce the split, so give the block its own platform and PDK entry:
- The block owns the lower layers and the parent the top ones. On sky130hd the block routes and straps on met1 to met4 and exposes power pins on met4, and the parent's met5 straps run over it. A block strap on met5 blocks met5 over the whole block, so the parent's straps cannot cross it. A block that needs met5 is handled by joining its pins.
- Point a copy of the PDK entry at a block-level
pdn-configwhose straps stop below the parent's layers, and set the block platform'srouting-layersto the same range.
The template's pnr/sky130hd/pdn_block.tcl and sky130hd_tt_block platform show this split.
rb pnr ties each block instance's power and ground pins, as the abstract LEF declares them (USE POWER / USE GROUND), when the parent's pdn-config leaves them on no net. A pin goes to the parent supply net of the same name, else to the parent's only power or ground net; with several candidates the run fails and asks for an add_global_connection. The log shows rb: tied <inst>/<pin> to <net>.
Blocks with supply pins on the top strap layer
Some blocks cannot leave the parent's top layer free. A block with an OpenRAM sky130 SRAM is one: the SRAM obstructs met1 to met4 and its supply ring reaches a grid only through met5, so the block builds its own met5 straps and exposes VDD and VSS on met5. The parent's pdngen treats those pins as an obstruction and stops its met5 straps short of the block on both sides, so nothing reaches them.
rb pnr joins such pins after pdngen. For each block supply pin on the parent's top strap layer it finds the parent strap of the pin's net on the same track, on each side of the block, and adds one strap-shaped box along the pin that overlaps each strap end by a strap width. The log shows rb: joined N block supply pin(s) on met5 to the parent's straps (M shape(s)).
- Build the block on the parent's pitch. In the block's
pdn-config, expose its grid on the top layer (define_pdn_grid ... -pins {met5}) and give its straps there the parent's width, pitch and net order, so that each pin lands on a parent strap of its own net. The block's position and orientation decide the rest: a pin is on track only if the block's origin is a whole number of pitches from the parent's strap offset, and a mirrored copy swaps which net each track carries. - Keep the block out of the parent's macro grids. A macro grid connects met4 to met5 over a macro, and this block blocks both, so
pdngenwarnsPDN-0232for its grid and fails withPDN-0233. Define the parent's macro grids with-cellsor-instancesfor its other macros instead of-default. - Unjoinable pins fail the run. A supply pin with a shape on or above the parent's top strap layer, none of whose shapes can be joined, fails with
block power: <inst>/<pin> .... That covers a pin off the straps of its own net (is not on a parent <net> strap, naming the net whose track it is on and the nearest strap of its own net), a join that would touch another net's strap, as for a rotated block (cannot be joined), and a pin above the parent's top strap layer. Change the block's core margin or size, or the parent's floorplan, until the pins line up. A single stray shape of a pin whose other shapes are joined, such as a short strappdngenadds at a macro edge, is reported as a warning. - The block's other pins, on layers below the top one, are connected by
pdngenas usual.
Assemble hardened blocks
A top-level run instances hardened blocks by naming them under blocks:, in pnr.yaml and in the synth.yaml entry the run reads:
# pnr.yaml
runs:
- name: top_pnr
# ...
blocks:
- name: alu_block # module name as instanced in the top netlist
pnr: alu_block_pnr # a harden: true run
pnr-path: ../alu/pnr.yaml # default: this pnr.yaml
# synth.yaml
syntheses:
- name: top_synth
# ...
blocks:
- name: alu_block
pnr: alu_block_pnr
pnr-path: ../../pnr/alu/pnr.yaml # required here
P&R takes each block's LEF, Liberty and GDS from its abstract; synthesis takes the Liberty and LEF.
- Build the blocks first. A run never starts a block's run. With no abstract, the consuming run fails before its tool starts, naming the block and the
rb pnrcommand that builds it. The top's synthesis also needs the block hardened.rb pnrwith no run name orders this; see Run a whole hierarchy. - Same technology and corner. The block's technology LEF and corner Liberty must match the consuming run's, or the run fails with a platform/corner mismatch. A multi-corner platform cannot consume single-corner abstracts.
- Stale abstracts are refused. If an input of the block changed since it was hardened (RTL, netlist, SDC, Liberty, LEF, snippets, its
pnr.yamlor platform), the consuming run fails, naming the block, what changed and the command that re-hardens it.--accept-staleonrb pnrorrb synthconsumes it anyway and the result description saysstale block abstract(s) accepted: <names>. - Blackbox the module. The top's filelist must leave the block's module a blackbox, typically a port-only stub.
- Parameterised blocks. A hardened block has one parameter set, the
params:of its synthesis. A parent that overrides its parameters needs a stub that declares them; see Instance a parameterised block.
Instance a parameterised block
A parent instances a parameterised block with an override such as blk #(.W(16)) u_blk (...). Its stub declares the block's parameters, so the frontend accepts the override and sizes the ports:
// blk_bb.sv, on the parent's filelist. The block was synthesised with params: {W: 16}.
(* blackbox *)
module blk #(parameter int W = 8, localparam int AW = $clog2(W)) (
input logic clk, input logic [W-1:0] d, output logic [W-1:0] q);
endmodule
Yosys then writes the instance in a form OpenROAD cannot use:
- With
frontend: slangit writesblk #(.AW(32'd4), .W(32'd16)) u_blk (...). That is every parameter, localparams included, and OpenROAD rejects the syntax (STA-0171). - With the native
verilogfrontend it derives a copy of the blackbox,\$paramod\blk\W=s32'0...10000 u_blk (...), and OpenROAD has no master of that name. With many overrides Yosys names the copy by a hash,\$paramod$<sha1>\blk. The values cannot be read back from that name, so such an instance fails the run with a request to usefrontend: slang.
rtl_buddy rewrites each instance as blk u_blk (...) before OpenROAD reads the netlist:
rb synthwithblocks:rewritessynth_netlist.v. Yosys does not read the block's abstract Liberty when the filelist has a parameterised(* blackbox *)stub of the module, because the Liberty would replace the stub and lose its parameters.rb pnrrewrites any instances that remain, for example in a netlist from a synthesis withoutblocks:, and reads the result aspnr_netlist.vin its artefact directory.rb powertakes its blocks from the synthesis it reads, so it checks and rewrites its ownpower_netlist.vcopy only when that synthesis hasblocks:.
Each of these steps checks every instance of a block before rewriting it, so the rewrite does not hide a parent that disagrees with the hardened block. A failed check stops the run before OpenROAD reads the netlist, and its message names the instance, its line, and both values.
- Ports match the abstract. Every port the instance connects must be a pin or bus of the block's abstract LEF, with the same width (
port 'd' is connected to 16 bit(s), but the hardened block's pin is 8 bit(s) wide). Every signal pin of the abstract must be connected.- An unconnected input or inout fails; an unconnected output is a warning. Supply pins are not checked here.
- Widths come from the netlist's declarations, selects, concatenations and sized constants.
- A connection rb cannot size, a positional port list or an instance array fails with the connection named.
- This is the strongest check. Any parameter that changes a port's width is caught here, recorded or not.
- Overrides match the elaborated block, as far as the record goes. Every
harden: truerun elaborates the block's top once more in Yosys (scratch files inparam_probe/of the run's artefact directory) and records its parameter values as<top>.params.jsonin the abstract. Each override on an instance, named or positional, is compared with its recorded value as a whole number; two literals of the same width compare by their bits. A real, recorded as Yosys's string ("2.500000"), compares numerically with the netlist's2.500000. All instances of a block must also agree. What the record covers depends on the block's frontend:frontend: slang: the record lists the top's parameters and localparams, and no genvars or nested-scope localparams, so an override the block does not have fails. A real-valued parameter cannot be recorded; the abstract is then published without a record.- native
verilogfrontend: the record lists parameters but not localparams, so an override the record lacks is only a warning. Arealparameter is recorded only when the synthesis'sparams:sets it. - No record: an abstract hardened by an older rtl_buddy, or one whose top Yosys could not elaborate (warning
pnr.block_params_unrecorded). Only the synthesis'sparams:are compared, with a warning to re-harden. A YAML integer, or a quoted unsized number such as"-1", matches an override of width W when it fits W bits, signed or unsigned, and agrees modulo 2^W. - Warnings are reported once per block and parameter, with a count of the instances.
- The rewrite removes nothing else. Outside the rewritten statements the output is byte-identical to the input. Each rewritten statement, read back from the output, has the input's tokens minus the
#(...)list and with the master renamed, and needs no further rewrite. Anything else is an internal error, not a silent edit.
The record is an abstract view, so editing or losing it makes the abstract stale. Re-harden a block (rb pnr <block run>) to give it a record.
Run a whole hierarchy
rb pnr with no run name runs every entry in the pnr.yaml, each block's run before the runs that consume it. Without blocks:, runs go in file order.
- A block whose
pnr-pathis anotherpnr.yamlis pulled in ahead of its consumer and writes to its own suite'sartefacts/. - A run whose block failed is not attempted. It reports
FAILwithfail_stage: blockedand ablocked_bylist, even underxfail, and blocking propagates up the hierarchy. Independent runs still run. - A block skipped by
-ldoes not block its consumer, which uses the abstract already published. - A cycle in
blocks:(pnr blocks: cycle: a -> b -> a), a block naming a run itspnr.yamldoes not define, a missingpnr-path, or two runs that write the sameartefacts/<run>directory stop the command before anything runs.
Build a hierarchy from a clean tree
On a clean tree the top's synthesis needs the blocks' abstracts. --synth runs each run's upstream synthesis just before it:
rb pnr -c pnr/top/pnr.yaml --synth
- A synthesis runs once however many runs read it, regardless of its own
reglvl. All syntheses are resolved before the first run starts, so a misspeltsynth:stops the command up front. - A synthesis that does not pass fails its P&R run with
fail_stage: synth, which blocks the run's consumers.--accept-staleapplies to syntheses too. -j Nruns up toNP&R runs at once (default 1). A run starts as soon as every block it names has finished. Each run is a whole OpenROAD session with its ownthreads:, so size the two together.
Troubleshooting
pdn-config not foundor a missingrcx-rulesfile. Fix the path in the PDK entry. The run stops atsetup.- Blockages rejected at setup, or an older-OpenROAD warning. Upgrade OpenROAD (25Q1 minimum, 26Q1 for blockages).
[ERROR PDN-0179] Unable to repair all channels. Raiseplacement.macro-halo.N macros do not fit .... Lower utilization, change the aspect ratio or lowermacro-halo.RSZ-0090at a slow corner. A single-Liberty macro limits that corner. Give it a Liberty per corner.RB-DONT-USE-VIOLATION. The netlist instantiates an excluded cell. Change the RTL, synthesis or pattern.STA-0122 cell '<pattern>' not found. Adont-use-cellspattern matches nothing.openroad.threads_capped. Raise the reservation, lowerthreads:or useauto.GDS incompleteorpnr.gds_incomplete. Add the cells' GDS togds-paths, or list intentional gaps ingds-allow-empty.fail_stage: export. KLayout,klayout-tech, an input file or a render failed. Fix it and runrb pnr-export.fail_stage: abstract, orharden:refused. A view could not be produced (readpnr.log), or the platform has several corners.- A missing or stale abstract, or a platform/corner mismatch. Run the
rb pnrcommand the message names, runrb pnrwith no run name, or pass--accept-stale. fail_stage: blocked. A block the run consumes failed. Fix it first.block power: ... is not on a parent <net> strap,no single parent power net, orPDN-0233on a block's macro grid. A block's supply pins are off the parent's straps or cannot be tied. See Blocks with supply pins on the top strap layer.instance '<inst>' ... of block '<blk>' sets <P>=...,port '<p>' is connected to N bit(s),instances of block '<blk>' have different parameters, or... named by a hash. The parent instances a block with parameters or ports it was not hardened with. See Instance a parameterised block.fail_stage: error. The run crashed; the exception is in the row description. Its consumers are blocked and other runs still report.