Blog
[counts measured through the GitHub API on 2026-10-02; merged means merged by the openXC7 maintainer, not shipped in a release; bitstream checks are cavearr's Vivado bench and our XC7A200T board] Since 1 September we opened 12 pull requests in repositories we do not own: 11 in openXC7, the open toolchain for Xilinx 7-series, and 1 in inngest. 5 are merged and 7 are open. Most follow one thread: a design with a regional clock buffer (BUFR) on an XC7A200T did not build through the open flow, and fixing it took the placer, the router, the bitstream writer and the bit database. A t27 spec of the database rows then found a parser bug in the t27 compiler, which we fixed in our own repository.
Since 1 September we opened 12 pull requests in repositories we do not own: 11 in openXC7, the open toolchain for Xilinx 7-series, and 1 in inngest. 5 are merged and 7 are open. Most of them follow one thread, and the last step of that thread led back into our own language.
A design on the ALINX AX7203 (XC7A200T) did not build through the open flow if it used BUFR, the regional clock buffer. The cause was the same at every layer: the open bit database, prjxray-db, was missing rows, and the tools built on it either could not route the clock or wrote a bitstream without the bits that switch it on. The work was discussed in openXC7/nextpnr#23 and #24 together with cavearr, who runs a Vivado bench and measured each change against Vivado's own bitstreams.
| PR | Problem | Fix | State |
|---|---|---|---|
| nextpnr-xilinx#170 | A pad-fed BUFR was placed on a site its pad cannot reach. On a 35T this went unnoticed; on the 200T the router failed with Failed to route arc 0 of net 'clk_ibuf' | The BUFR is constrained to its pad's dedicated site, as #168 already did for BUFIO | merged |
| nextpnr-xilinx#171 | A BUFR drives only its own clock region, but the placer did not know that and put flip-flops across the die | The reachable region is read from the routing graph, and the BUFR's sinks are kept inside it | merged |
| nextpnr-xilinx#205 | The bitstream placed the BUFR but did not set its enable bit | The BUFRCLK enables are written. This waited for the rows to exist; cavearr found they existed for artix7 only and added the other families in prjxray-db#22 | merged |
| prjxray-db#30 | The CLK_PERF mux rows were incomplete. Reading a bitstream (bit2fasm), one row fired falsely in 204 cases; writing one (fasm2frames), the enable bit was not set | Each row now carries its enable bit and its 2-bit source code, and the HCLK rows are added for kintex7, spartan7 and zynq7. Checked in both directions on all 608 of cavearr's bitstreams | open |
After #170 and #171 merged, cavearr ran their 23-design regression suite over both: no status, LUT/FF count or Fmax moved, and all 36 canonical FASM outputs were byte-identical.
openXC7 is moving from the old nextpnr-xilinx to nextpnr with the himbaechel engine. Some fixes from the old tool were never ported, so old crashes came back. Five of these PRs are ports, some of them corrected on the way.
| PR | Before | After | State |
|---|---|---|---|
| nextpnr#53 | A negative MMCM CLKFBOUT_MULT_F indexed past the end of a table: a segfault on x86 that did not reproduce on arm64, because the (int) cast is undefined behaviour | A range check, 1..63. The old fix allowed 64, which reads one row past the table | open |
| nextpnr#54 | An IDELAYCTRL with no IDELAY was an error and stopped the build | A warning, as in Vivado | open |
| nextpnr#55 | Swapped P/N on a differential pair crashed with std::out_of_range and no message | An error that names the port, the pin and the fix | open |
| nextpnr#56 | RAM32X2S and RAM32X1S were silently dropped by the packer, and the placer then crashed | Both are packed. RAM32X1S was missing even in the old engine | open |
| nextpnr#57 | A typo in a BEL name (an unknown tile) crashed the tool | A named error with the cell's name; the same for wires and pips | open |
| nextpnr#58 | An IDDR behind an IDELAYE2 got the wrong mux, so the delay never reached the flip-flop | The state Vivado writes. After the merge cavearr checked it on their bench: both bits now match Vivado | merged |
| demo-projects#22 | Nothing stopped a later refactor from losing RAM32X2S/X1S again | Two regression designs for #56 | merged |
The one change the maintainer, Hans Baier, asked for across these PRs was a style rule: every if condition becomes a named boolean declared right before it. We now write openXC7 code that way by default. The revisions for #53 to #57 were pushed on 30 September and are waiting for his review.
inngest/inngest#4860: on a self-hosted inngest with a signing key, every MCP tool that calls REST API v2 answered 401, because the internal request lost the Authorization header. The fix forwards it.
prjxray-db#30 changes 146 database rows by rules that were checked by hand: which bits a mux row must carry, and which rows the HCLK companions add. We rebuilt those rows from one t27 spec. The spec holds the rules and the measured bits; a small driver compiles it, renders the rows and compares them with the database.
At first the spec could not say what it meant. assert (a & b) == c parsed as assert(a & b) followed by == c. The typecheck said ok, and only zig test caught it. We reported it as gHashTag/t27#5593 and fixed it in gHashTag/t27#5594. Over all 1146 specs in the t27 corpus, the fix changes the output of exactly 3 specs and removes every Zig error the bug caused there, without adding a new one. With the fix, the spec's original assert (x & y) == x passes 7/7.
Every fix in this list closes one crash or one wrong bit, and each was found on a real XC7A200T or against a Vivado bitstream. The rows that took longest to get right were the ones written by hand for four families. As a spec they are checked by a compiler and by their own tests, and that check found a compiler bug on its first day.
Work with me
I audit RTL and build independent, bit-exact models, then take the result through synthesis and, when useful, onto an Artix-7 board. The first conformance module is free.