Skip to content

fpga(build): skip t27b-only bitstream specs in fpga-build, not teach gen-verilog a second lowering (rc_filter_ax7203, ddr3_head_bandwidth) #10300

Closed

Description

@gHashTag

Decision on the red fpga-lint and elaboration ratchet that rc_filter_ax7203 and ddr3_head_bandwidth bring to master (follow-up of #10294 / PR #10296; the ddr3 spec itself belongs to epic #10024 and is not touched).

Measured (Railway lab, t27c from fix/rc-filter-lint-10276)

t27c fpga-build --smoke, then the CI loop yosys -p "read_verilog -sv -DSIMULATION f.v; hierarchy -top M" over build/fpga/generated/*.v: 54 of 56 modules pass, ddr3_head_bandwidth and rc_filter_ax7203 fail ("Non-constant function call in constant expression" at localparam clkbuf = IBUFDS(...)). tools/check_elab_ratchet.py: 278 errors against a baseline of 214; rc_filter_ax7203 is 8 (a recorded KNOWN GAP) and ddr3_head_bandwidth is NEW at 64.

Cause: both specs are written in t27b's bitstream idiom. Pins are const x : bool = undefined; plus x_LOC / x_IOSTANDARD string attributes, and primitives are empty-bodied extern fns returning a struct (const clkbuf : IbufdsOut = IBUFDS(a, b);). t27b verilog (specs/tri/t27b/verilog_plan.t27) lowers that to ports and module instances, and its output is what the board-verified bitstreams are built from. t27c gen-verilog has no lowering for it and prints localparam lines.

The decision

Two fixes were on the table.

(a) t27c gen-verilog learns pins (as ports) and empty-bodied struct-returning extern fns (as module instances).

(b) t27c fpga-build skips the specs that are t27b-only bitstream specs, says so on its output, and the two baseline rows go away.

Chosen: (b). Reasons:

  1. The shipped RTL of these cells is t27b verilog output, never t27c gen-verilog output. Making the second one parse does not make the first one any better; it adds a second, unused lowering.
  2. (a) cannot be small. To produce an instance that yosys accepts, gen-verilog has to copy t27b's conventions: port naming for pins, the btn_O name of an instance output read from on_clock, parameter-by-name binding for the PLL, clock and reset wiring. That is one rule living in two homes, the recorded defect of this codebase family, and it is hundreds of lines of hand-written Rust, and the foreign-budget gate allows no net growth. The ddr3 spec is the hard case: its 64 errors are one class (unbound parameter, cascading from the pins and the litedram_core instance), but closing them means lowering a 60-odd-port extern correctly, which only t27b has been checked against on a board.
  3. (b) is a few lines in run_fpga_build, paid for by folding two identical blocks there, so the Rust debt shrinks, and does not touch either spec.

Cost, stated plainly: the t27c-generated Verilog of these two specs is no longer linted. That output was never the shipped RTL; the shipped RTL is covered by the t27b gates and the board receipt (rc_filter 2130/2130 PASS). If t27b verilog ever runs in CI, its output is what should be linted, and that is the follow-up.

How a spec is recognised as t27b-only: it declares at least one pin attribute, a top-level const <name>_LOC : str = "<ball>";. That is t27b's own idiom for a pin, so no spec has to change or be resealed. wasm_board_ax7203 (also KIND = "board-cell", no pins) is not skipped and keeps its 0 row.

Goal

fpga-lint and the elaboration ratchet are green again for rc_filter_ax7203 and ddr3_head_bandwidth without teaching t27c a second lowering, and the skip is visible, not silent.

Boundary

  • bootstrap/src/main.rs :: run_fpga_build
  • tools/elab_baseline.txt

User Scenarios

  • Given a spec under specs/fpga/ with a _LOC pin attribute, when t27c fpga-build --smoke runs, then it prints SKIP <name> (t27b bitstream spec) and writes no <name>.v.
  • Given a spec without pins such as wasm_board_ax7203, when the same command runs, then it is generated exactly as before.
  • Given the CI yosys loop and tools/check_elab_ratchet.py over the result, when they run, then both pass with no NEW row.

Requirements

  • FR-001: run_fpga_build MUST skip a spec that contains a top-level _LOC pin attribute, and MUST print one SKIP line naming it and the reason.
  • FR-002: run_fpga_build MUST NOT skip any spec that has no pin attribute, and MUST still fail when gen-verilog fails on a spec it does not skip.
  • FR-003: tools/elab_baseline.txt MUST drop the rc_filter_ax7203 row and MUST NOT gain a ddr3_head_bandwidth row, with the notes rewritten to say why.
  • FR-004: No .t27 spec, seal or workflow file is changed.

Success Criteria

  • t27c fpga-build --smoke exits 0 and generates 53 modules plus the wrapper, with 2 SKIP lines.
  • The CI yosys loop reports 0 failing modules.
  • tools/check_elab_ratchet.py exits 0 with 0 NEW rows and a total of 206 errors, the master baseline.
  • bootstrap/src/main.rs ends with fewer hand-written lines than it started with (the foreign-budget gate allows no growth): the skip adds 8, and the two identical yosys script blocks in run_fpga_build are folded into one.

Close criteria

  • The PR merges and fpga-lint and the elaboration ratchet no longer name either module.

Activity

  1. added 3 commits that reference this issue on Oct 18, 2026
    873d9b3
    18c0384
    30b4363
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions