Skip to content

docs(fpga): rewrite the HDL-backend design against the generator as built - #777

Merged
0-jake-0 merged 1 commit into
mainfrom
claude/verilog-codegen-fpga-hft-3sd62o
Aug 10, 2026
Merged

docs(fpga): rewrite the HDL-backend design against the generator as built#777
0-jake-0 merged 1 commit into
mainfrom
claude/verilog-codegen-fpga-hft-3sd62o

Conversation

@0-jake-0

Copy link
Copy Markdown
Contributor

What this changes

docs/fpga-hdl-backend-decision.md was written before the software generator existed. Now that #769 has built it, the document's central claim — that a hardware backend reuses the codegen front-end wholesale — could be checked rather than assumed. It did not survive intact. This rewrites the doc around what actually carries over, and adds the two walls it was missing.

Docs only; no code changes.

Why

Refs #727 (this doc's tracking issue). Follows #769.

The question that prompted it: now that lightning-codegen has landed, is it actually useful for the Verilog work? The answer is yes, substantially — but not through the part the document led with.

The headline claim was wrong. §3 asserted that func! quotation is "exactly the bridge RHDL's no-closures kernel subset needs" and that tier-1 closures "translate nearly verbatim". One line of ops.rs settles it:

F: Fn(&A) -> B + 'static,          // map
F: Fn(&A, &B) -> C + 'static,      // join
F: Fn(&mut B, &A) + 'static,       // fold

Every op closure in the catalog takes references; rhdl forbids them. So a recorded body — |n: &u64| (n * size) as f64 — needs a signature rewrite and deref elision to become a #[kernel] fn, not a splice. fold's &mut accumulator isn't a rewrite at all: it's a state-update kernel, a different shape. The knock-on matters more than the rewrite — §3 hoped rhdl's frontend would double as the eligibility checker, and it can't, because what we hand rhdl isn't what the user wrote.

The clean carryovers turn out to be the unglamorous ones: NodeInfo traversal (active/passive edges are valid-gating vs plain sampling), the refusal machinery with #[track_caller] call sites, capture detection, and check_artifact.

The changes

New §2 — the carryover ledger. Per-asset audit of what the generator hands the hardware backend, including §2b (why quotation doesn't splice) and §2c: there is no nitro! equivalent for RTL, so the parity burden the software generator sidestepped lands squarely on the op twins.

New §5.4 — pipeline skew, the load-bearing wall. Absent from the original, whose worked example is a single path and therefore cannot exhibit it. Fan-in across paths of unequal pipeline depth combines values from different logical ticks, and NodeInfo carries no depth information to balance against. The one part of the backend the generator gives no head start on.

§5.1 corrected. Fixed-point is not something to introduce — adapters/market already ships Px/Qty. Their i128 width is the actual obstacle, and it's deliberate (an i64 at nine decimals caps at ±9.22 × 10⁹, making large-notional venues unrepresentable). The work is narrowing to a venue-scaled Fixed<I, F>, which has a software payoff of its own.

New §5.5Burst has no strobe (it serialises, which breaks the one-clock claim), and backpressure is mandatory in fabric while absent from software wingfoil.

New §6 — the host↔card interface. PCIe posted-vs-non-posted discipline (never read across it on a hot path), the DMA ring as an Activation::ALWAYS poll source, torn-parameter avoidance, wire timestamping, and the decision tap. Deliberately independent of everything else: it applies whether the gateware is generated, hand-written, or bought, which is why trading-roadmap.md item 8 already sequences it first.

§1 widened, and the backend split. Beyond rust-hdl/rhdl: CIRCT/FIRRTL, Spade, XLS, Veryl, Amaranth. Recommendation is rhdl for leaf kernels only, with wingfoil owning structural emission — which bounds the pre-release one-person dependency to the half that has a fallback. Under the original framing, an rhdl stall took the whole emitter with it.

§7's spike amended so it can fail. As written it was all single-path and scalar, so it could only succeed. Now includes doing the §2b rewrite by hand and wiring an unequal-depth join, with a note that the latter runs first if time is short.

How it was verified

Docs only — no Rust changed, so the code checklist doesn't apply. What was checked:

  • Every claim about the current tree verified against source, not memory — the Fn(&A) bounds in ops.rs/fluent.rs, Px/Qty as i128 at SCALE = 1e9 in adapters/market.rs, poll's Option<T> return, and the NodeInfo fields the ledger cites
  • Cross-references checked: no other doc links to this one by section number, so the renumbering is contained
  • Heading sequence and internal § references consistent after renumbering

Notes for the reviewer

Read §2 first — it is the section that answers "was the codegen work useful here", and the rest follows from it.

The doc keeps a revision note at the top recording what changed and why, rather than silently rewriting. §2b in particular contradicts the original §3, and the audit trail seemed worth more than a clean read.

#727's checklist is now stale — it cites "doc §5" and "doc §4" for the spike and the walls, both of which have moved (§7 and §5), and its three-item spike is the version that could only succeed. Not updated here; happy to do it in a follow-up.

Not folded in, deliberately: the review findings on #769 itself. The one I'd call a should-fix-before-merge is that #[wiring] and the Probe expansion hardcode ::wingfoil, which breaks for anyone renaming the dependency (wf = { package = "wingfoil" }) — proc-macro-crate is the usual fix. That belongs on #769, not here.

Time-sensitive item raised in §6c: capture detection already produces, per node, the exact set of values a closure depends on — which for hardware splits into frozen (baked into LUTs) and live (a control register). wired-graph-codegen-decision.md §3 names that fork as "a follow-up, not v1"; hardware makes it a requirement, since nobody resynthesises to change a size. Cheap to decide while #769's capture machinery is fresh, expensive once artifacts are checked in across the tree.

🤖 Generated with Claude Code

https://claude.ai/code/session_01P9wTP8Gyd9jFWpi6awakVP


Generated by Claude Code

…uilt

The FPGA backend doc was written before the software generator existed
(#769). Now that it does, its central claim — that the hardware backend
reuses the codegen front-end wholesale — could be checked rather than
assumed, and it did not survive intact.

New §2 audits what the generator actually hands the hardware backend.
Most of it carries; the part the document led with does not. Closure
quotation was advertised as the bridge to rhdl's no-closures kernel
subset, but every op closure in the catalog is bound `Fn(&A) -> B` and
rhdl forbids references, so recorded bodies need a signature rewrite and
deref elision rather than a verbatim splice — and eligibility cannot be
delegated to rhdl's frontend, because what we hand it is not what the
user wrote. The clean carryovers are the unglamorous ones: `NodeInfo`
traversal, the refusal machinery with `#[track_caller]` call sites,
capture detection, and `check_artifact`. Also records the asymmetry that
there is no `nitro!` equivalent for RTL, so the parity burden the
software generator sidestepped lands on the op twins.

New §5.4: pipeline skew, absent from the original, whose worked example
is a single path and so cannot exhibit it. Fan-in across paths of
unequal pipeline depth combines values from different logical ticks, and
`NodeInfo` carries no depth information to balance against — the one
part of the backend the generator gives no head start on.

§5.1 corrected: fixed-point is not something to introduce. `Px`/`Qty`
already ship, and their `i128` width — deliberate, so large-notional
venues stay representable — is the actual obstacle. The work is
narrowing to a venue-scaled `Fixed<I, F>`, not adding fixed-point.

New §5.5 (`Burst` has no strobe; backpressure is mandatory in fabric and
absent in software) and §6, the host↔card interface: PCIe discipline,
the DMA ring as an `Activation::ALWAYS` poll source, torn-parameter
avoidance, wire timestamping, and the decision tap. §6 applies whether
the gateware is generated, hand-written, or bought, which is why
`trading-roadmap.md` item 8 already sequences it first.

§1 widens the survey beyond rust-hdl/rhdl and splits the backend: rhdl
for leaf kernels only, wingfoil owning structural emission. That bounds
the pre-release dependency to the half with a fallback.

§7's spike is amended so that it can fail — the original was all
single-path and scalar, so it could only succeed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P9wTP8Gyd9jFWpi6awakVP
@0-jake-0
0-jake-0 merged commit 81052c0 into main Aug 10, 2026
9 checks passed
@0-jake-0
0-jake-0 deleted the claude/verilog-codegen-fpga-hft-3sd62o branch August 10, 2026 21:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants