T27.AI

Blog

179 features that change no bits, and one routing choice that did

2026-08-31 · 7 min read

A parity report between two Xilinx place-and-route trees ranked its own work items backwards: the 179 missing BRAM features were zero-codepoints, and the blocker that kept a bitstream off a board produced no feature-count delta at all.

Three-panel engraved illustration for: 179 features that change no bits, and one routing choice that did
View the complete triptych at full size
#openXC7#FPGA#Bitstream#Upstream#Measurement

On 2026-08-27 a parity report landed in openXC7/nextpnr-xilinx#165: two place-and-route trees — the himbaechel port and the released nextpnr-xilinx 0.9.3 — run over the same netlists, the same XDC, the same prjxray-db, the same yosys and the same back end, so that the only variable is the P&R binary. The report named its most concrete finding without hedging: 179 BRAM configuration features that 0.9.3 emits and the port does not, "the most concrete bitstream-parity item and the one I would fix first".

One day later the same author retracted that sentence. Three days after that, three bitstreams ran on a physical board. Neither of those measurements is mine — I read the artefacts, I did not reproduce a number in them — and together they make one point worth writing down: the difference you can count is not necessarily the difference that matters.

The 179 features that change no bits

The missing features are real in the FASM text: RAMB18.READ_WIDTH_{A,B}_1, WRITE_WIDTH_{A,B}_1 and RSTREG_PRIORITY_{A,B}_RSTREG, 179 of them in the LiteX design, with RSTREG_PRIORITY_* going 88 → 0, WRITE_WIDTH_B_1 44 → 5 and READ_WIDTH_B_1 40 → 7 between the arms. They are also, all of them, zero-codepoints: their segbits are entirely negated.

BRAM_L.RAMB18_Y0.READ_WIDTH_A_1  !27_35 !27_36 !27_37
BRAM_L.RAMB18_Y0.RSTREG_PRIORITY_A_RSTREG  !27_124

Every bit in the definition is a negation, so writing the feature and not writing it produce the same frame. The retraction rests on three checks that fail independently of each other:

So the work item that had been ranked first would have moved nothing in the bitstream for anything LiteX exercises. It was a difference in the text that describes the configuration, not in the configuration.

The blocker nobody had counted

The thing that actually stopped a bitstream from reaching a board was a routing choice, and it produced no feature-count delta at all. Building a blinky for the lowRISC Sonata (xc7a50tcsg324, board clock on P15 = LIOI3_X0Y23), the port takes the regional clock network out of the pad:

pad → HCLK_IOI3_X1Y26.HCLK_IOI_IO_PLL_CLK3_DMUX ← I2IOCLK_BOT1
    → RCLK3 → BUFR divider bypass → CLK_HROW … CK_BUFRCLK_L1 → BUFG

Deterministically, over eight seeds, with router2 and with the default router. 0.9.3 takes the dedicated CCIO → HCLK_CMT → CLK_HROW → BUFG backbone instead. The fork knows to do this explicitly: Arch::routeClock() treats a single-user net feeding BUFGCTRL.I0 as a global (to_bufg_input, xilinx/arch.cc:1812), with a comment saying that left to the general router the BUFG output is dead and the design freezes. The port’s XilinxImpl::route_clocks() has five forms and none of them is that one.

What made this hard to see is that the same route fails in two different-looking ways depending on which database revision you have. With the db revision 0.9.3 ships (ab1fc60) that DMUX pip has no segbits entry at all, so fasm2frames rejects the design with a FasmLookupError — which reads like a database gap. With current db master (77e52f10, after prjxray-db#7 merged 2026-08-26) the same key is an all-zero default row, so it assembles cleanly and yields a bitstream — which reads like success.

…but whether that regional path delivers a working clock to the BUFG is unknown.

That sentence, written on 2026-08-28, is the honest state of a bitstream that assembles without complaint. Nothing short of silicon could settle it.

Three bitstreams, one board

On 2026-08-30 the loop closed. Jonathan (jrrk2) ran three Sonata blinky bitstreams on a physical Sonata — same netlist, same XDC, same prjxray back end, only the P&R differing — and all three blink.

armtreerouterclock route
Anextpnr-xilinx 0.9.3 (68aeeb39, released package)router2dedicated CCIO → CMT → HROW → BUFG
Bhimbaechel 2212c004router1dedicated path (router1 avoids the BUFR datapath)
Chimbaechel + openXC7/nextpnr#1 (2bf0e1f9)router2dedicated path (the PR's predicate refuses the BUFR datapath)

Arm C is the one that carries information. That arm previously produced either a FasmLookupError or a clock of unknown liveness, depending on the database; with the merged predicate from openXC7/nextpnr#1 it now blinks with the default router. The claim about what that means for the port was made with its hedge attached, and it is worth quoting rather than paraphrasing:

B and C are, to our knowledge, its first board-verified bitstreams from an A/B kit against the fork.

One reproduction note for anyone with the same board: the Sonata is programmed by dropping a UF2 on its bootloader volume, not over JTAG, so the .bit needs a UF2 wrap with family id 0x6ce29e6b.

What the fix actually is

It is worth being exact about what closed and what did not. openXC7/nextpnr#1 makes a route through the BUFR datapath oblige a BUFR — that is, it teaches the router to refuse a path it cannot honestly assemble, so placement falls back to the dedicated backbone. The regional path itself was not made to work. The related placement problem, putting a pad-fed BUFIO on a site its pad can actually reach, is openXC7/nextpnr-xilinx#168 and is still open; so is #149, the issue that started this line, where BUFIO is not packed and the HCLK_L enables are never emitted.

The pattern the week produced: a counted difference of 179 features was cosmetic, and an uncounted difference of one routing decision was the thing standing between a port and a board. Feature diffs are cheap to compute and that makes them attractive as a work queue. This one ranked its own items exactly backwards, and the correction came from segbits, a round trip and a Vivado golden — not from a bigger diff.

What this does not settle

Receipts

Work with me

Need an FPGA/RTL problem taken to measured hardware?

I work contract and part-time on hardware-AI, FPGA/RTL and ML systems — from specification and open toolchains to reproducible measurements.