rohd-rtl-gotchas — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited rohd-rtl-gotchas (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
ROHD (Rapid Open Hardware Development, Dart RTL) plus rohd_bridge and rohd_hcl have a handful of sharp edges that produce cryptic errors at build or sim setup, far from the actual cause. These are the ones that cost real hours on River and Harbor.
Core principle: Most of these surface as a hierarchy-rule violation or a "Bad state: No element" hang, and the fix is almost never where the error points. Match the symptom below to the cause directly instead of debugging the stack trace.
Sequential setupRegisterFile read returns data a cycle off from what the FPGA will doROHD's hierarchy checker rejects wiring two sibling modules through a shared bidirectional (inout/LogicNet) net that closes a loop (a SPI controller plus a behavioral SPI-flash model sharing spi_io). The error reads "should only communicate via inputs/inouts" and it may even invert the hierarchy (think the DUT contains its parent).
inout bus). The FSM, byte-order, and data-assembly logic is usually shared across modes, so you still cover it.LogicNet to both modules' addInOut (the ROHD inout-loopback idiom). Do not <= one onto the other. Two active drivers stay fragile; a parent harness beats top-level wiring.Two distinct causes, same message:
Sequential(someModuleOutput, ...) where the clock is a controller-generated, sometimes-static signal (a generated spi_clk) throws at sim setup. Fix: clock the model on the real system clock and detect edges of the derived clock manually (rising = clk & ~prevClk, register prevClk).tearDown between tests, never after build() and before Simulator.run(). The pattern is: build, inject resets, setMaxSimTime, unawaited(Simulator.run()), then await clock edges.module.input('clk').srcConnection! <= signal works. Inside a parent module it inverts the hierarchy unless you register the child first with parent.addSubModule(child).addSubModule only accepts a BridgeModule. A plain ROHD Module auto-parents via normal inference; do not addSubModule it.bus_STB, bus_CYC, bus_WE, bus_ADR, bus_DAT_MOSI, bus_SEL, bus_ACK, bus_DAT_MISO), not the bus.stb abstraction.Two instances of the same module class with different constructor params (two ROMs with different contents or widths) are different module definitions. ROHD uniquifies the name (Foo_0), but per-module SystemVerilog file emission can then collide or mismatch the reference ("RiverCore refs Foo, file defines Foo_0", reported as "module not part of the design").
Fix: give each a distinct stable definitionName plus reserveDefinitionName: true so the emitted file name and the instantiation agree.
When invoking yosys after module.generateSynth(): the top module name in the SV is the class/definitionName, not the instance name. Grep ^module to find the real top first.
Process.environmentDo not read Process.environment (or any process global) inside a module to switch a feature, a width, or a behavior. Pass a constructor parameter or a typed config object. The environment is legitimate only at the OUTERMOST generator or CLI entry point, parsed once into that config, which then flows down as parameters.
Process.environment is process-wide, so every instance of a module elaborated in one run reads the SAME value. You cannot build a fast variant and a slow variant, or two byte-lanes with different timing, or two SoC configs, in one process. Parallel test elaboration reads the one shared value too. Anything that can be multi-instance MUST take the setting as a parameter.silicon-grade-discipline: the design declares the capability, the generator reads it.Process.environment read deep in the PHY. Then the build records its setting and two lanes get tuned independently, which a single process-wide env var can never do.Process.environment['X'] in a module, add a constructor field with a sensible default, thread it from the generator's parsed config, and delete the env read. The knob becomes part of the module's declared interface, visible to callers and tests. See hdl-module-design for the typed-config-object shape.rohd_hcl RegisterFile read is combinational (readLatency 0). wrapReadForRegisterFile(..., readLatency: N) delays only valid, not data/done. For a real registered (BRAM) read you must register data yourself and craft the done/valid handshake to match.
If a consumer FSM holds its address until done, it tolerates +1 read latency for free (one fill cycle per probe). Verify by running the sim flop path at the SAME latency the FPGA BRAM will have, so the sim proves the actual hardware timing. See fpga-synthesis-fit for the flop-vs-BRAM latency-matching rule.
dart test parallelizes per file, not within a file.net-connect-and-bridgemodule-synth.md in this directory: the net_connect duplicate-port form that breaks yosys synth (and the fix), the srcConnection! sub-module wiring idiom, and the per-test Simulator wire-listener slowdown that reads as a hang.silent-width-and-instantiation-traps.md in this directory: the zeroExtend().signExtend() no-op that kills sign-extension, verifying a block is even instantiated (grep the netlist) before debugging its internals, sizing a sentinel or override field for the value not the lane count, the sum-needs-one-more-bit overflow, force-clearing a stale codegen snapshot after a path-dependency change, and confirming a debug register decodes the field you assume on the active build config.hdl-module-design, fpga-synthesis-fit, and differential-verification.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.