Skip to content

fix(hw): intercard_link width-guard fires at elaboration (was simulation-only)#14

Merged
marcos-mendez merged 1 commit into
mainfrom
feat/stream-2/pr-12-width-guard-elaboration-enforcement
May 6, 2026
Merged

fix(hw): intercard_link width-guard fires at elaboration (was simulation-only)#14
marcos-mendez merged 1 commit into
mainfrom
feat/stream-2/pr-12-width-guard-elaboration-enforcement

Conversation

@marcos-mendez

Copy link
Copy Markdown
Member

Summary

Closes #12.

PR #11 placed the MAST #14 width-contract guard inside initial $error
blocks. Verilator --lint-only does not execute initial blocks, so a
future violation of INTERCARD_LANES * INTERCARD_LANE_WIDTH == 128 would
have lint-passed silently. Today the contract is satisfied, so the gap was
masked.

Option chosen: A (bare $error in generate body)

The issue offered three options (A: bare $error in generate, B: negative-
width wire trick, C: switch lint script to --binary). I tested Option A
against Verilator 5.048 first because it's the cleanest IEEE 1800-2012
construct and required no script changes.

Verilator 5.048 accepts $error directly inside a generate-if body
without --assert, surfaces it as %Warning-USERERROR, and exits
non-zero. Verified — no need to fall back to B or C.

Applied to BOTH sites the issue identified:

  • src/intercard_link.sv — production module's internal width-guard
    (lines ~80-92, formerly initial begin if (...) $error ... end)
  • verif/intercard_link/test_widths.sv — testbench's elaboration-time
    guard (lines ~59-72, formerly initial $error in generate)

The "OK" branch in test_widths.sv keeps initial $display because
that path is purely observational (only meaningful under --binary,
and under --lint-only it's a harmless no-op).

Verification (mandatory per issue)

1. Default-parameter case must still PASS

$ bash verif/intercard_link/run_lint.sh
[run_lint] verilator Verilator 5.048 2026-04-26 rev v5.048
[run_lint] linting .../src/intercard_link.sv with top .../test_widths.sv
- V e r i l a t i o n   R e p o r t: Verilator 5.048 2026-04-26 rev v5.048
- Verilator: Built from 0.050 MB sources in 3 modules ...
[run_lint] PASS — intercard_link elaborates with INTERCARD_BUS_WIDTH = 128
EXIT=0

2. Contract violation must FAIL (the crisp proof issue #12 asked for)

I created verif/intercard_link/test_widths_violation.sv (NOT committed,
deleted before commit) instantiating intercard_link #(.INTERCARD_LANES(8), .INTERCARD_LANE_WIDTH(32))8 * 32 = 256 != 128, contract broken.

$ verilator --lint-only --top-module test_widths_violation -Wall \
    -Wno-DECLFILENAME -Wno-UNUSED \
    src/intercard_link.sv verif/intercard_link/test_widths_violation.sv
%Warning-USERERROR: src/intercard_link.sv:89:13: intercard_link: INTERCARD_LANES (8) * INTERCARD_LANE_WIDTH (32) = 256, expected 128 (MAST #14 contract).
                                               : ... note: In instance 'test_widths_violation.u_violation'
   89 |             $error("intercard_link: INTERCARD_LANES (%0d) * INTERCARD_LANE_WIDTH (%0d) = %0d, expected 128 (MAST #14 contract).",
      |             ^~~~~~
                    ... For warning description see https://verilator.org/warn/USERERROR?v=5.048
%Error: Exiting due to 1 warning(s)
EXIT=1

Captured violation evidence:

  • Exit code: 1 (non-zero — would fail CI)
  • First error line: %Warning-USERERROR: src/intercard_link.sv:89:13: intercard_link: INTERCARD_LANES (8) * INTERCARD_LANE_WIDTH (32) = 256, expected 128 (MAST #14 contract).

The temp file was deleted before commit; not in the diff.

Out of scope (per issue)

No changes to: connector pinout, SV stub body, ADRs, KiCad files,
run_lint.sh. Only the two files with the broken guard.

Test plan

  • bash verif/intercard_link/run_lint.sh exits 0 on default params
  • verilator --lint-only exits non-zero when contract is violated
    (LANES=8) — captured above
  • SPDX headers preserved on both files
  • DCO sign-off present
  • No changes to out-of-scope artifacts
  • CI green (will appear after PR creation)

🛠 Stream 2 / Agent 2 (FPGA Hardware)

…ion-only)

PR #11 placed the MAST #14 width-contract guard inside `initial $error`
blocks. Verilator `--lint-only` does not execute `initial` blocks, so a
future violation of LANES * LANE_WIDTH == 128 would have lint-passed
silently. Today the contract is satisfied, so the gap was masked.

Fix: lift `$error` out of `initial` and place it directly in the generate
body (Option A from issue #12). This is an IEEE 1800-2012 elaboration-time
construct that Verilator enforces under `--lint-only`. Applied to both
sites:

  - src/intercard_link.sv  (production module's internal width-guard)
  - verif/intercard_link/test_widths.sv  (testbench elaboration guard)

Verilator 5.048 verification:

  Default params (LANES=4, LANE_WIDTH=32 → 128):
    bash verif/intercard_link/run_lint.sh  → exit 0, "PASS" banner

  Violation (LANES=8, LANE_WIDTH=32 → 256, via temp violation TB):
    verilator --lint-only ...              → exit 1
    %Warning-USERERROR: src/intercard_link.sv:89:13: intercard_link:
      INTERCARD_LANES (8) * INTERCARD_LANE_WIDTH (32) = 256, expected
      128 (MAST #14 contract).

The "OK" branch in test_widths.sv still uses `initial $display` (purely
observational under --binary; harmless under --lint-only).

closes #12

Authored by Agent 2 (FPGA Hardware).

Signed-off-by: Marcos <m@pop.coop>
@marcos-mendez marcos-mendez added stream-2 FPGA Hardware (Agent 2) — KiCad, Stays primary review-pending PR awaiting reviewer agent (R) labels May 6, 2026
@marcos-mendez

Copy link
Copy Markdown
Member Author

Review by Agent R

Verdict: APPROVE
Severity counts: CRITICAL=0 HIGH=0 MEDIUM=0 LOW=0

Pre-review gates (5d5fbb6e)

  • CI: Verilator + cocotb tests SUCCESS
  • Mergeable: yes
  • DCO sign-off + SPDX preserved
  • 21+/7- (small surgical change to 2 files)

Independent verification by Agent R

  • Default lint: bash verif/intercard_link/run_lint.shPASS — intercard_link elaborates with INTERCARD_BUS_WIDTH = 128 (Verilator 5.048)
  • Violation case: Built a tmp TB with intercard_link #(.INTERCARD_LANES(8)) and ran verilator --lint-only. Output:
    %Warning-USERERROR: src/intercard_link.sv:89:13: intercard_link: INTERCARD_LANES (8) * INTERCARD_LANE_WIDTH (32) = 256, expected 128 (MAST #14 contract).
    
    Verilator surfaces USERERROR as fatal by default → exit non-zero (Agent 2 captured exit 1 without -Wno-fatal). The guard now genuinely fires at elaboration, not just at simulation. Issue [hw] intercard_link width-guard does not fire under verilator --lint-only #12's gap is closed.

Option A choice rationale (accepted)

Bare $error directly in generate-if body without initial wrapper is the IEEE 1800-2012 elaboration-time form. Cleanest of the three options proposed in #12 — no negative-width tricks, no --binary switch needed.

Useful agent gotcha (worth memory)

Agent 2 noted Verilator's pragma scanner is text-based — wrapped // comments containing /* verilator …-style sequences trip BADVLTPRAGMA. Worth keeping in mind for future hw doc-comments. Not memory-worthy on its own (Verilator-specific, low-recurrence) but useful context.

Action

Merging via two-step. Forgejo sync follows.

Authored by Agent R (Reviewer).

@marcos-mendez marcos-mendez merged commit a3e7934 into main May 6, 2026
1 check passed
@marcos-mendez marcos-mendez deleted the feat/stream-2/pr-12-width-guard-elaboration-enforcement branch May 6, 2026 05:18
@marcos-mendez marcos-mendez restored the feat/stream-2/pr-12-width-guard-elaboration-enforcement branch May 6, 2026 05:19
marcos-mendez added a commit that referenced this pull request May 6, 2026
 #13) (#15)

PR #11 declared `clk_p`/`clk_n` as unconditionally `output` on the
single `intercard_link` module. Per docs/hw/intercard-connector-pinout.md
§2.1 §6, the forwarded source-synchronous clock is driven by the
upstream card and consumed by the downstream card — so the original
single-module surface only modeled the upstream role. A downstream-card
instantiation could not compile against it without a wrapper.

Apply Option C from issue #13 (least disruptive):

  - git-rename src/intercard_link.sv → src/intercard_link_upstream.sv
    (preserves blame; clk_p/clk_n stay output, prsnt_n stays input)
  - add src/intercard_link_downstream.sv with clk_p/clk_n declared as
    input and prsnt_n as output (downstream pulls low so upstream sees
    logic-0 = neighbour present)
  - update verif/intercard_link/test_widths.sv to instantiate BOTH
    role variants in the same elaboration unit
  - add verif/intercard_link/test_two_card_pair.sv: cross-wires
    upstream → downstream over the connector signal names. Verilator's
    multi-driver / undriven-output checks catch any future regression
    that flips CLK direction. Local sanity check confirmed: forcing
    downstream clk_p/clk_n back to output yields
    `%Warning-UNDRIVEN: 'clk_p'`, exit 1.
  - run_lint.sh now performs two Verilator passes (widths + 2-card pair)
  - docs/hw/intercard-connector-pinout.md §2.1 §4 §6: CLK and PRSNT_N
    direction explicitly tied to card role, with cross-references to
    ADR-003 from the per-pin table
  - docs/adr/0003-intercard-link-role-split.md: rationale, alternatives
    A/B/C/no-op, consequences, and a downstream-caller table

Verilator 5.048 evidence (local):

  $ bash verif/intercard_link/run_lint.sh
  [run_lint] verilator Verilator 5.048 2026-04-26 rev v5.048
  [run_lint] (1/2) widths: ...test_widths.sv      → exit 0
  [run_lint] (2/2) two-card pair: ...two_card_pair.sv → exit 0
  [run_lint] PASS — intercard_link_upstream + intercard_link_downstream
  elaborate with INTERCARD_BUS_WIDTH = 128 (CLK direction split per
  ADR-003) and the two-card pair wires up cleanly.

closes #13
refs #11, #14

Authored by Agent 2 (FPGA Hardware).

Signed-off-by: Marcos <m@pop.coop>
Co-authored-by: Marcos <m@pop.coop>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-pending PR awaiting reviewer agent (R) stream-2 FPGA Hardware (Agent 2) — KiCad, Stays primary

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[hw] intercard_link width-guard does not fire under verilator --lint-only

1 participant