Quirks & Known Issues
Find limitations and workarounds by workflow, from CDC and simulation through dispatch, synthesis, formal, FPGA and tools.
XPM CDC macros require rtl-buddy-cdc 0.4 or later
rtl-buddy-cdc 0.3.x treats xpm_cdc_* instances as dual-clock blackboxes. It reports CDC-BBX and drops their crossings from the report and domain map. Waivers hide the finding but do not recover the crossings. Upgrade with uv tool install -U rtl-buddy-cdc.
Filelist +incdir+ needs rtl-buddy-cdc 0.7 or later
rb cdc and the hub's domain-map build pass each +incdir+ to rtl-buddy-cdc as --incdir, but only when rtl-buddy-cdc lint --help lists that option (rtl-buddy-cdc#311, released after 0.6.0). An older analyzer gets no include directories, and the run warns cdc.filelist_incdirs_unsupported, naming the directories. A header that resolves only through one of them then fails with Cannot find include file. Upgrade with uv tool install -U rtl-buddy-cdc, write the `include path relative to the including file, or use the vivado cdc tool.
Coverage totals are per elaboration by default
Verilator scores each coverage point once per module elaboration. If a suite builds the same RTL under different defines or parameters, a block can look short on coverage when it is fully covered once the copies are collapsed.
rb cov summary,--coverage-dir-summaryand merged totals report the per-elaboration figure.- For the collapsed figure, read
source_totals: therun (source)row ofrb cov summary,rb cov summary --by-source,--coverage-source-summaryontestandregression, or the/covpane'sfigurespicker.
See Coverage.
Coverage uses the platform builder
Coverage collection and labels use the platform-selected builder, even when a suite or test selects another builder:. A mismatch can mislabel or misparse coverage. Pass --builder <name> for the run, or make that builder the platform default. See YAML Formats.
xezim coverage figures differ from xezim's own report
rtl_buddy scores a xezim toggle bit as hit only when it both rose and fell, while xezim counts each direction as a point, so T: reads lower than xezim's toggle percentage. Line points are statements, so a line holding two statements counts twice. LCOV and HTML hold line and branch only. See Collect coverage with xezim.
Coverage merging runs in the submitting process without a Slurm backend
Under --dispatch slurm the coverage tail (merge, model build, LCOV exports and manifest) runs as one job sized by cfg-dispatch.coverage (see Run the coverage tail as a job). Without dispatch, and under --dispatch local-parallel, it runs in the process that invoked rb.
- A few hundred inputs can peak in the gigabytes. A memory cap on that host can kill the merge; coverage then reports
FAILand the command exits 1 (see Read a failed merge). - The merge has no time limit unless
cfg-coveragesetsmerge-timeout, so a hung merge hangs the run.
Without Slurm, run the coverage command on a host with the memory it needs.
An orphaned coverage job is only found within its own artefact tree
Under --dispatch slurm a coverage tail job whose head died is recorded in the artefact root's .dispatch/coverage/ and found by the next Slurm run there (see Interrupted runs). cov_dir/ is shared by every --run-tag, so a job orphaned under another tag, or untagged, is not seen and can still write cov_dir/ during this run. Runs without a Slurm backend, including every rb randtest -r replay, do not check at all. Cancel a leftover rb:coverage job by hand (scancel) before re-running coverage in that directory.
Verilator randomized runs may not reproduce
Verilator can behave differently for the same random seed. When reproducibility matters, use VCS with -xlrm hier_inst_seed and give instances stable explicit names. If VCS does not write HierInstanceSeed.txt in the simulation directory, rtl_buddy warns sim.hier_seed_missing and cannot record the seed. The verdict is unchanged.
Tool-path fallback can select another installation
A configured tool directory wins only when it contains the requested executable. Otherwise rtl_buddy may use a matching executable on PATH and warns once per process. After changing root_config.yaml, .rtl-buddy/.env or the environment, restart long-running rb hub and rb mcp processes.
Hook scripts are not normal standalone scripts
sweep and preproc hooks run with the invocation directory as CWD and __name__ == "__rtl_buddy_hook__". Use the injected suite_dir, artifact_dir and run_artifact_dir paths, and do not put required logic behind if __name__ == "__main__":.
Python print() output is captured as hook.stdout events, but child-process output bypasses the capture, so capture it and print it. See Plugins.
toplevel: must name the testbench in a plain SystemVerilog test
For a plain SystemVerilog testbench, toplevel: becomes Verilator --top-module, VCS -top or Icarus and xezim -s. It must name the testbench, not the DUT. cocotb and SystemC testbenches need no review.
- A
toplevel:naming the DUT compiles, then the simulation exits at once and the test endsNAwithno PASS/FAIL markers found in .../test.log; result is NA. Nothing in that output namestoplevel:. A DUT with unconnected interface ports fails with%Error-UNSUPPORTED: Interfaced port on top level moduleinstead. - Without
toplevel:, the top is chosen from filelist order, not the testbenchname. - A top pinned in the builder's
compile-timeopts overridestoplevel:. When they differ, the run warnscompile.toplevel_conflictand uses the pinned top.
An error after PASS fails the test only through the exit status
A test whose transcript prints PASS and then reports an error fails because the simulator exits nonzero. When the simulator exits 0 after the error, the test stays PASS:
- VCS exits 0 after
$error,$fataland failed assertions. - Verilator exits 0 when
+verilator+error+limit+<n>lets the simulation continue past the error.
Set assertions: true so Verilator and xezim assertion failures fail the test, or have the testbench print FAIL when its own checks fire.
Narrow VCS access flags suppress cocotb defaults
For cocotb, rtl_buddy adds VPI access unless a configured compile option already starts with -debug_access or +acc. A narrower flag suppresses the full default and can block signal writes. Remove it, or configure sufficient access such as -debug_access+all and +acc+rw.
VCS license waits pause the simulation timeout
While VCS prints its license-queue banner, rtl_buddy pauses sim_timeout for up to one hour, so a queued run can outlive its timeout. An unrecognized newer banner resumes the clock too early; a timeout next to license messages in test.err indicates this. Set the builder's extra-sim-timeout as a backstop.
A timeout kill can leave test.log with an unflushed tail
When sim_timeout expires, the simulator can be killed before it flushes output, so test.log can end mid-line and its last bytes do not locate the stop. Follow the timeout triage order before raising the limit.
Deeply nested expressions can exhaust the elaboration stack on macOS
rb elab analyses on threads with the platform default stack: 512 KiB on macOS, 8 MiB on Linux. About 400 nested conditional expressions on macOS kill the worker with no diagnostic, and the run reports elaboration worker did not produce a result. max_parse_depth does not help. Elaborate such generated RTL on Linux, or reduce the nesting. See Model Elaboration.
Artifact locking is per tree and per host
Artifact-writing commands take <artifact_root>/.rtl-buddy.lock and fail at once if another process on the same host holds it.
- A lock left by a dead process on this host is reclaimed by the next run with the warning
artifact_lock.reclaimed. A lock from another host is never reclaimed; clear it by hand once that machine is idle. - The lock does not coordinate different NFS hosts. Dispatched worker jobs skip it, so do not start another command against a tree with a dispatch run in flight.
- A filesystem that cannot
flockfails withcannot lock this artefact tree.
Give concurrent runs of one suite a --run-tag so each locks its own tree.
--run-tag covers the test and P&R flows, not every reader
--run-tag is accepted by rb test, rb randtest, rb regression, rb pnr, rb pnr-export, the dispatch job commands and rb graph results. Other commands do not see tagged runs:
rb wave,rb cov,rb physand other flow commands read the flatartefacts/<test>/. Open a tagged trace by path:artefacts/.runs/<tag>/<test>/dump.fst.rb powerreads a P&R run's untaggedartefacts/<run>/, andrb synthhas no tag, so a taggedrb pnrreads the untagged synthesis netlist.- The hub, the MCP server and
rb graph queryread the untaggedartefacts/graph/results-overlay.json. Publish a tagged run there withrb graph results --run-tag <name> -o artefacts/graph. - A relative path in
compile-timebuilder opts resolves one directory deeper under a tag. Write${RTL_BUDDY_PROJECT_ROOT}/.... - Merged coverage is not namespaced:
--coverage-merge*writes<command_root>/cov_dir/whatever the tag, so merge in only one concurrent run.
Tool flows delete their previous outputs before running
rb cdc, rb synth, rb fpga, rb pnr and rb power remove the outputs they are about to write before invoking the tool, and rb hub clears the view.json and domain map it caches under .rtl-buddy/cache/. A failed rerun therefore leaves no output instead of the previous run's. Copy out anything you want to compare first.
- A failed
rb synthleaves no netlist, sorb pnrandrb powertell you to runrb synthfirst.rb fpgawithout--bitstreamremoves a previously built<top>.bit. - A rerun that fails early, on a filelist or config error, also leaves nothing. With the backend tool missing, only
rb synthandrb pnrstill clear. - A previous output that cannot be deleted (permissions, or a directory where the file belongs) fails the run with a fatal error. Fix the permissions or remove it by hand.
- A
pnrrun failed bygds-mode: strictremoves the GDS, PNG and report but keeps the routed DEF, netlist, SDC and ODB.
Runs of different commands with the same name share one artifact directory. Give an FPGA run and a power run different names; both write power.rpt.
local-parallel enforces only the job count
The local-parallel backend ignores CPU, memory, time and array settings. -j or cfg-dispatch.jobs is the only limit, so size it for the heaviest test's memory. SIGKILL of the head can orphan rb _test-job children; find and stop them. See Run on one host.
Oversized resource groups are split into several arrays
A resource group larger than the cluster's array limit is submitted as several arrays, and max-jobs-per-array throttles each one, so peak concurrency is the throttle times the number of slices. With several clusters selected, or no scontrol on the submit host, the limit is unknown and sbatch refuses an oversized group; set cfg-dispatch.max-array-size. See Split large groups into several arrays.
Quote dispatch time values
YAML 1.1 reads an unquoted time: 4:00:00 as an integer, and rtl_buddy rejects it. Quote every time value in resources:, compile: and modes: blocks.
Dispatch build jobs cover the whole suite
One build job compiles every build of a suite. compile.parallel runs that many builds at once, but only cpus is scaled for you: size compile.mem for the concurrent builds and compile.time for the longest batch. Each build job reserves cpus times min(parallel, planned tests), so set parallel to the expected number of distinct builds. See Compile several builds at once and Split verilation from the C++ build.
Preprocessors run in every dispatched job
Under dispatch, sweep runs once on the head. preproc runs in the build job and again in every simulation job; where verilation is split off, the verilate job runs it for every config and the build job only for one config per build it still has to make. Make preproc idempotent, write shared generated files atomically to artifact_dir, and write run-dependent files to run_artifact_dir.
With compile.parallel above 1, preproc for later configs runs while earlier builds compile, so no config's preproc may modify an input that another config compiles. Configs with the same compile key share one build, and a hook that rewrites an input per config on one key makes every config after the first fail with build_job.group_input_drift. A hook should skip the write when a shared input would not change: a build whose inputs were rewritten while it compiled, even with identical bytes, is compiled again (build_job.compile_inputs_moved).
Simulation jobs reuse the build stamp
A simulation job whose build stamp does not validate, against a build the build job recorded as built, fails with compile.build_stamp_rejected instead of recompiling. The usual cause is a preproc that writes different bytes on the simulation node. build_job.group_leader_unstamped means the first config of a build wrote no stamp, so the others compiled again; fix the directory as for compile.stamp_write_failed. There is no per-test opt-out of the shared directory under --dispatch. See Recover when a gated job cannot use the build.
Design compile errors under dispatch are CompileFail
A design compile error is CompileFail; infrastructure failures stay DispatchFail. Simulation jobs do not recompile a config the build job recorded as failed, and the row carries the build job's error and logs. Fix the design or the build reservation, not the simulation one.
Slurm retry reuses artifact paths
A retry overwrites the first attempt's simulation capture and per-job rtl_buddy log. Only slurm-<tag>-retry<N>.log stays per attempt, so diagnose retries from the scheduler logs. max-wait applies to each attempt. A --begin in sbatch-args overrides the retry delay, so remove it when you use retry backoff.
Slurm memory advice depends on accounting samples
rtl_buddy requests one-second task accounting unless sbatch-args sets --acctg-freq. When the longest run ends within the sampling interval, MaxRSS is unreliable and memory advice is suppressed. Reduction advice needs at least 25% savings and floors of five minutes and 128 MB, so a small reservation gets none.
Generated run.f files are checkout-specific
rtl_buddy writes sources, +incdir+ and -y directories as absolute paths. Do not commit or copy run.f between checkouts, and spell a checkout's path the same way every time, because path spelling affects compile keys.
- An include directory whose path contains
+stays relative, because filelist parsers read+incdir+a+bas two directories. rtl_buddy warnsfilelist.incdir_unrepresentable. Remove the+from the path. - A path containing whitespace is quoted, which Icarus's
-fparser does not understand. Keep whitespace out of checkout paths.
Mount paths must match across cluster nodes
On a cluster with different mount paths per node, a build stamp from one node may not validate on another. Outside --dispatch the result is a recompile. Under --dispatch the simulation job fails with compile.build_stamp_rejected naming the path that differed (see Simulation jobs reuse the build stamp). Spell the project root the same way on every node. shared-build-root removes the dependence on the root's spelling, but paths outside it, such as the toolchain, must still match.
Shared-build stamps track dependencies differently per simulator
A build stamp decides whether a shared build can be reused.
- Verilator tracks the headers, libraries, standard includes and binary it reports consuming. Inputs under the project root are compared by content hash; inputs outside it, and any single input above 64 MB, by size and mtime.
- VCS, Icarus and xezim report no dependencies, so their stamps list every file in each
+incdir+and-ydirectory and compare content. Adding, removing or editing a file there rebuilds, even one nothing includes. Verilator also compares that listing by file name.
The listing skips dot-directories, artefacts/, obj_dir*, a coverage run's cov_dir/ and HTML/Coverview reports, and rtl_buddy's own outputs, including every rtl_buddy.log, so a project directory with one of those names under an include path is not tracked. An +incdir+ on a large tree slows every reuse check.
Do not point +incdir+ at a directory a simulator or tool writes into: its scratch files change the listing and every run recompiles.
Environment variables, undeclared tool inputs and symlinked subdirectories are not tracked. For VCS, Icarus and xezim, an include resolved relative to the including file is also untracked. Force a compile with --rebuild after editing any of these.
xezim builds read sources at run time
A xezim simv is a wrapper that re-reads the sources each run, through a copy of run.f kept in the build directory. With shared-build-root, a build compiled in one checkout reads that checkout's sources when another checkout reuses it, so removing the first checkout breaks the reuse. Pass --rebuild after moving or deleting a checkout.
Shared-build locking
An advisory flock on <shared directory>/.rb-build.lock serialises concurrent processes populating one shared build. A waiting process logs compile.build_lock_wait every few minutes.
- On an NFS mount with
nolock,local_lock=flockorlocal_lock=all, the lock is process-local and succeeds without warning, so it does not protect across nodes. - Delete a shared build tree between runs, never during one; the lock file lives inside it.
- Where the filesystem cannot lock, the run warns
compile.build_lock_unavailableand compiles unserialised.
Unshared builds have no lock, so do not run such a suite twice at once.
Slurm serialises build jobs of the same suite
Build jobs of one suite run one at a time per user, job tag and cluster, so an unrelated run of the same suite under the same tag, or both untagged, waits for an earlier one (dispatch.build_job_deduped). --run-tag does not separate them. Two users, or two runs under different job tags, sharing a tree rely on the flock. A federation with DependencyParameters=disable_remote_singleton does not serialise across clusters; pin one with -M. See Troubleshoot builds for a build job that stays PENDING.
A different rtl_buddy version does not reuse shared builds
Build stamps written by another rtl_buddy version do not validate, so each build directory recompiles once, and repeatedly while hosts of one cluster run different versions. Use --rebuild to compile regardless, not a manual delete of artefacts/.shared-builds/.
Yosys-backed flows do not support whitespace in paths
Yosys scripts split on whitespace and treat # as a comment, and quoting does not group a path. Keep design and artifact paths for synthesis and FPV free of whitespace. fpv.yaml parameter validation also rejects whitespace, ; and #. String-valued parameter overrides need SystemVerilog quotes inside the YAML scalar.
Static-lifetime functions corrupt the netlist under the slang frontend
A function or task outside a class declared without automatic has one shared storage location per formal, and yosys-slang models this literally. Calls can alias their arguments or leave a net with conflicting drivers, which folds to x and can drop a register and everything downstream. Simulation is unaffected, so the defect can go unnoticed.
rb synth scans the filelist's sources and included headers before Yosys runs:
- With
frontend: slang,static-functions: error(the default) fails the run. Theverilogfrontend only warns. - Add
automaticto the declaration, or setstatic-functions: warnto stage a migration. - Yosys
multiple conflicting driverswarnings fail the run unlessconflicting-drivers: allowis set. Tristate buses are not counted.
The scan reports declarations, so a subroutine with one call site can still fail, and it misses declarations produced by macros or in -y directories. A synth.yaml defines: that overrides a filelist +define+ warns synth.filelist_defines_overridden. See Synthesis.
read_verilog drops an interface instance's own port connections
Yosys's read_verilog cannot bind an interface instance to a child module's interface port. It warns Could not find interface instance for `<inst>' in `<module>' and exits 0, and the instance's own port connections are lost: bus_if b (.clk(clk)); leaves \b.clk undriven, so every flop clocked from it loses its clock, with plausible area and timing numbers.
unresolved-interfacesgates the warning.warn(the default) logssynth.unresolved_interfaceper instance,errorfails the run, andallowskips the scan.frontend: slangbinds the instance properly, but an interface port on the synthesis top itself needs a flat-port wrapper.
See Synthesis.
An overridden blackbox needs a LEF and Liberty master under OpenROAD
The OpenROAD synthesis backend passes a (* blackbox *) module that has no LEF or Liberty master to OpenROAD as a port-only stub. An instance that overrides its parameters keeps its #(...) list, which OpenROAD's read_verilog rejects (STA-0171). Give the module a LEF and Liberty, as blocks: does for a hardened block, or instance it with its defaults. See Instance a parameterised block.
Unknown synthesis overrides are ignored after a warning
synth.yaml tool_overrides uses snake_case keys such as plugin_path and single_unit, unlike the kebab-case names under cfg-synth-tools.opts. An unknown key logs the warning synth_tool_config.unknown_override and the default is used. A non-mapping block, or a non-boolean single_unit or best_effort_hierarchy, is fatal. See Synthesis.
The mapped-run ABC script leaves out two steps of Yosys' default
The default ABC script of a Liberty-mapped Yosys run is Yosys' own default Liberty script without &fraig -x and dc2. ABC's &fraig -x SAT sweep does not finish on deep arithmetic, such as a combinational floating-point datapath, and dc2 rebuilds the log-depth adders, negates and incrementers that techmap produces as ripple chains. To run Yosys' default, set abc-script: "strash; &get -n; &fraig -x; &put; scorr; dc2; dretime; strash; &get -n; &dch -f; &nf {D}; &put". rtl_buddy#973 tracks returning to it. See Synthesis.
Wide adders map as ripple chains unless synth-args has -noabc
Yosys synth runs its generic abc pass before the mapped-run ABC step, and that pass's script includes dc2, which rebuilds log-depth adders, negates and incrementers as ripple chains. The mapped-run default script omits dc2, but it cannot restore depth the earlier pass removed. Add -noabc to the effort's synth-args for timing-critical datapaths. See Synthesis.
Prefix adders off the critical path ripple under abc-script: default
With the default mapped-run script, &dch -f choices and &nf area recovery under the module's one global required time rebuild Kogge-Stone and other +/choices/ adders off the critical path as ripple chains. A run whose synth-args request a +/choices/ map uses the delay preset unless abc-script is set. See Synthesis.
OpenROAD's legalizer blocks a rotated macro's unrotated footprint
OpenROAD's default legalizer treats a fixed macro turned by 90 degrees (R90, R270, MXR90, MYR90) as covering its unrotated width and height from its lower-left corner (OpenROAD#11735). Cells in that footprint but outside the macro are moved out, some far. By default rb pnr blocks the footprint during global placement, so the area stays empty. placement.rotated-macro-footprint: diamond-legalizer uses the diamond legalizer, which sees the real box, instead; none does neither. The workaround acts only when a macro entry turns a macro or a hook before global placement could.
repair_design does not repair the slew at an output port
OpenROAD's repair_design checks a net that ends at an output port only at its driver, so a long net to an output pin keeps its slew even when the SDC sets set_max_transition on the port. A port has no limit unless the SDC sets one, so without it the electrical counts do not list the pin either. Set the limit in the SDC and buffer the outputs with buffer-ports: {outputs: true, hold-outputs-at-pins: true}; see Check and repair slew at output ports.
A pin-constraints file must not constrain a port that has an exact pin
rb leaves exact pins out of the floorplan.pins side and group rules but does not read the pin-constraints file. A set_io_pin_constraint -region there that holds an exact pin outside the region fails place_pins with PPL-0107, reported on another pin. When every signal port has an exact pin, place_pins is skipped and the file's constraints have no effect. See Place IO pins.
Session state does not cross a P&R stage boundary
Each stage of a staged run, and each post-route repair round and the finish stage of a run with post-route-repair, is a new OpenROAD session. It reads the previous stage's database, SDC and saved flow variables. Anything an earlier session held only in memory is gone: Tcl variables, procedures and rename wrappers, set_wire_rc, set_layer_rc and set_dont_use from a hook, STA settings outside the SDC, and the resizer's state. rb warns pnr.hook_commands_not_kept when a hook redefines an existing command, and logs pnr.hook_commands_added for new ones, which later sessions do not have.
Move such state into a session hook, which runs at the start of every session. It then also applies to the steps before its original point: a set_wire_rc moved there from after-cts also changes placement. See Run Tcl hooks.
A staged P&R run can differ from a one-session run
A staged run runs the same flow commands as a one-session run and gives identical results at the same thread count, except where OpenROAD carries in-memory state across a stage boundary:
- The resizer keeps state from
repair_designandbuffer_ports. A hook that runs one of them in an earlier stage, such asrepair_design -pre_placementinafter-pins, can change a later repair. rb's ownbuffer_ports(floorplan) andrepair_design(place) also run in different stages; the sky130hd template routes identically either way, which does not guarantee it for every design. - OpenSTA keeps the supply ports
pdngencreates bidirectional in the session that ranpdngen(OpenROAD#11850). Thefinishsession re-asserts them as supply ports, so<top>.routed.vand a hardened<top>.libdo not depend on it, but a one-session hook that runs afterpdngensees them as signal ports. - OpenROAD writes the supply ports after the buses in the session that ran
pdngenand before them in a session that read the database. rb moves them last, sorted by name, in<top>.routed.pwr.vand a hardened<top>.lib, so neither depends on it. A file it cannot read that way keeps OpenROAD's order, and the run warnspnr.supply_order_unverifiednaming the file. rtl_buddy#924 tracks removing the rewrite once OpenROAD writes one order. - Without
rcx-rules,finishtimes on the global route restored from the route guides, which can miss a few pins (EST-0026), so slack can differ slightly. Withrcx-rulesboth time the same SPEF.
Compare a staged run with a one-session run at the same threads: before relying on it to reproduce one.
Post-route repair works around OpenROAD routing failures
Each post-route repair round runs in fresh sessions and orders its steps to avoid these OpenROAD 26Q4 failures:
- A repair in the session that ran detailed route makes the next detailed route fail with
DRT-0218, so each round reads the routed database in a new session. - On a routed database,
repair_timingswitches to detailed-routing parasitics and builds buffer trees from global routes that a fresh session rebuilds incompletely, failing withRSZ-0074(OpenROAD#11852). Anecoround therefore replaces OpenROAD'sdesign_is_routedwith one that answers 0 during its repairs, keeping them on placement parasitics. Whenrepair_timingdoes not call that procedure, rb warnspnr.post_route_eco_unguarded; if the round then failsRSZ-0074, it is set aside and the result names the fix,post-route-repair.reroute: full. global_route -start_incrementalaround a resizer command that reroutes by itself delivers each netlist change twice, and a later rip-up fails withGRT-0229andusage=65535(OpenROAD#11851). Afullround and the global-route repair run their repairs first and open the incremental route around the legalization only, as ORFS does. Anecoround, whose repairs do not reroute, opens it before the repairs.repair_timingon global-route parasitics failsRSZ-0074on netsrepair_designsplit until they are rerouted, so a repair that runs both legalizes and reroutes between them.
Post-route repair_design sizes its buffering on the placement estimate
A post-route round's repair_design, the electrical round's included, finds max-slew and max-capacitance violators on the extracted SPEF, but on OpenROAD 26Q4 it sizes the buffers that repair them on Steiner trees at the placement wire RC. A net whose extraction is far above that estimate is left as it is when the estimate is within the limit less the margin, so a larger margin does not help. It can resize a driver that has a stronger variant, which the timer judges on the SPEF, so a driver at its strongest size, such as a buf_16 driving a long chain, is the usual case. Running the repair on detailed-routing parasitics instead would build its buffer trees from the global routes, which a fresh session rebuilds incompletely (RSZ-0074, see Post-route repair works around OpenROAD routing failures).
When the electrical round is kept, its extraction lists each violator still on its SPEF that the estimate judges within the limit less electrical-repair's margin, each at the scene where its extracted violation is worst; rb warns pnr.electrical_round_unseen and records them in post_route_repair.electrical_round.unseen. The list leaves out the violators repair_design does not repair on any parasitics: ports, and pins on constant-driven, clock, dont_touch, special or abutment nets. Raise post-route-repair.repair-design.wire-rc toward the RC the extraction shows, or keep such nets short from the start with placement.repair-design.max-wire-length. See Repair electrical violators after routing.
Corners share one SDC mode on an OpenROAD without modes
rb pnr gives each corner its own SDC mode only on an OpenROAD that can switch back to an earlier mode, 26Q4 or newer. On an older one, such as 26Q2, every corner is timed in one mode and rb warns pnr.corner_modes_unsupported:
- A generated clock's latency comes from one corner at every corner, so a forwarded clock can be timed for hold at a fast corner on a slow corner's latency.
- A per-corner
constraints:fails setup withpnr.corner_constraints_unsupported. STA-1140 library ... already existsis logged once per extra corner for a macro with one Liberty. Per-corner Liberty files of one macro that share alibraryname log it in every session, with or without modes; it is harmless there, because each file is named only by its own corner's scene.- Every
<top>.<corner>.routed.sdcholds that one mode, and checkpoints write no per-corner SDC.
Upgrade OpenROAD, or run each corner on a single-corner platform. See Sign off at several corners.
rb power times every corner in one SDC mode
On a multi-corner platform rb power reads one SDC, by default the P&R run's <top>.routed.sdc (the primary corner's), and the routed SPEF once for all corners. Every corner then uses the primary corner's generated-clock latencies, and the corners after the first reuse the first corner's reduced parasitics. For one corner's own figures, run rb power on a single-corner platform of that corner with constraints: set to <top>.<corner>.routed.sdc.
An unconfirmed dispatched P&R run blocks later runs
While an earlier dispatched run's stage jobs may still be queued, a new rb pnr of the same configuration refuses to start. It also refuses when it cannot confirm the jobs have left: while squeue does not answer, on a host without squeue while that run's dispatch/ record changed within the total time its jobs asked for, and for up to 11 minutes while an sbatch submission has no answer. Run rb pnr where squeue answers, or, once the jobs have ended, remove the checkpoints/<run id>/dispatch/ directory the refusal names.
rb phys module reports no power for an RTL module
The physical model's synthesis half holds RTL module names and its power half holds the Liberty cell of each leaf instance, so rb phys module u_cpu reports cell count and area with no instances and no power. Use rb phys instance u_cpu, which sums the leaf rows under the instance path. See Physical Metrics.
Phys pane and schematic selections cross only within one hierarchy
Selections between the /phy pane and the /sch schematic are paths rooted at the physical model's top. A /sch showing a testbench around the DUT, or another design, selects nothing, with no message. Open the schematic on the synthesis top:. See Physical Metrics.
Graph-pane heat attributes a leaf to the nearest instance the graph knows
The /gph heat overlay rolls per-instance power up to the enclosing RTL module using the graph's design tier, so it is only as fine as that tier is complete.
- If the run's
top:is a wrapper the graph was not built for, or the graph was narrowed withrb graph build --model, no leaf power can be attributed. The pane paints cells and area only. - A row whose path runs through a level the graph lacks goes to the deepest level it has, which over-attributes that module.
- A module's power is summed over every instantiation, while its cells and area are counted once.
Graph coverage source changes attribution
A merged LCOV .info attributes coverage by file, so every module declared in one file gets the same totals. For per-module attribution use the default --coverage auto, or --coverage model.
A cocotb test over an opted-out model keeps a dangling DUT edge
graph: false in models.yaml withdraws every config-tier edge into the model's hierarchy, but the cocotb edge to module:<toplevel> still names the DUT and appears in the merge.dangling list of graph-meta.json. Opt out only models that no cocotb test runs against, or give the model a top:.
Compilation-unit bind requires the slang frontend
Yosys's native verilog frontend does not resolve a top-level bind, so no formal cells elaborate. rtl_buddy fails a property-based proof that would otherwise pass vacuously. Set frontend: slang and configure the yosys-slang plugin. Inline assertions do not need this. See Formal Property Verification.
Verify that anyconst elaborates
Some yosys-slang builds drop (* anyconst *) without producing a $anyconst cell. The signal then varies freely each cycle and can invalidate symbolic-index proofs. Check the elaborated design before relying on it:
yosys -p 'read_slang ...; prep -top dut; select -assert-min 1 t:$anyconst'
Use a behavioral reference model when portable data-integrity checking matters.
FPV COI analysis is best-effort
A cone-of-influence Yosys failure logs fpv coi_yosys_failed, omits COI data, and does not fail a successful proof. If COI numbers disappear, read artefacts/<name>/coi.log and check cfg-fpv-tools[].opts.plugin-path or RTL_BUDDY_SLANG_PLUGIN.
A simulation-only mutation campaign ignores top
Only the FPV oracle elaborates a top; the simulation oracle runs the suite's own testbenches. A mut.yaml that declares top: without verify.fpv_config warns mut_config.top_override_unused and scores every mutant unchanged. Remove the field, or add the FPV oracle it is meant to root. See Mutation Testing.
FPGA bitstream generation relaxes two I/O DRCs
Before write_bitstream, rtl_buddy downgrades Vivado DRCs NSTD-1 and UCIO-1 so designs without a complete pinout can produce a bitstream. For real hardware, treat either violation as blocking and add the missing IOSTANDARD and LOC constraints.
FPGA timing is optional unless gated
A completed routed run reports PASS even with negative slack. Read timing_met, wns_ns and failing_paths for closure work, or set require-timing-met: true in fpga.yaml to fail the run on a miss. A timing_met: null cannot trigger the gate.
pywellen must stay within 0.25.x
rb wave annotations and rb saif depend on pywellen's API, which changes on each pre-1.0 minor release. The supported range is >=0.25.6,<0.26. Outside it, the command fails with pywellen.api_missing, naming the installed version and the range. Install a version in the range.
rb nvim-install requires git and network access
The default install clones a pinned rtl-buddy-nvim revision. On an air-gapped system, pass a local checkout with rb nvim-install --source /path/to/rtl-buddy-nvim --ref <ref>. The pinned plugin must speak the hub protocol shipped by rtl_buddy.
The viewer distribution and executable have different names
Install the rtl-buddy-sch distribution; rtl_buddy invokes its rtl-buddy-view executable:
uv tool install rtl-buddy-sch
rb tool-check --explain rtl-buddy-sch accepts the alias but reports the tool key rtl-buddy-view. rb graph build needs viewer 0.4.0; other viewer-backed commands need 0.3.0, and rb tool-check marks only rb graph as outdated below 0.4.0.
Verible lint findings are on stderr
verible-verilog-lint writes findings to stderr and signals findings through its exit code. A pipeline that reads only stdout sees nothing. Capture stderr, or use rb lint, which scans both streams.
rb release keeps every name a clear file uses
Verible's obfuscator renames a spelling everywhere it appears, so a name used by any file that ships unobfuscated, the release testbench included, is kept in every obfuscated file too. A testbench local called count keeps every design signal called count. rb release refuses a testbench that names a design module, package or interface unless testbench.allow-design-refs lists it, and reports the rest in the internal manifest's forced_clear_units. Keep the release testbench to the preserved interface and a package published for it. See Customer releases.
rb release cannot obfuscate token-pasted macro names
Verible renames the pieces of a token-pasted name ( `define NXT(a) a_nxt ``) separately, so the pasted result no longer matches its declaration, and a macro string quote is renamed while the string is not. rb release refuses both in any file it obfuscates. Rewrite the macro, exclude the file with obfuscate: false and a reason, or set obfuscation.token-paste: preserve to keep every name the paste can form.
Verible picks new obfuscated names on every run
verible-verilog-obfuscate has no seed: two runs on the same input give different names. rb release keeps names stable by starting each release from the previous release's map, and reproduces a past release only from that release's own map with --reproduce. Keep every maps/<version>.map.