Skip to content

ci: green the smoke/heavy matrix on the nextpnr engine - #18

Merged
hansfbaier merged 8 commits into
mainfrom
ci/toolchain-nextpnr-fixes
Sep 25, 2026
Merged

hansfbaier merged 8 commits into
mainfrom
ci/toolchain-nextpnr-fixes

Conversation

@hansfbaier

@hansfbaier hansfbaier commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Makes the smoke and heavy matrices green again against the openXC7/nextpnr engine.
The run at 373b764 (smoke #81) failed 9 jobs for five distinct reasons; all of
them are addressed here and the whole matrix was rebuilt locally on the pinned
toolchain before pushing (results below).

What failed, and why

jobs symptom cause
6 LiteX DDR SoCs ERROR: Hold/min time violation the port added hold analysis and makes it fatal; the fork had none and only ever printed Info: Max frequency … FAIL for the same designs (run #80's logs). nextpnr-xilinx#165 records the same finding and that the FASM is identical with --timing-allow-fail.
same + ddr3-test-arty-s7, litex-sata ERROR: Max frequency … FAIL the derived MMCM constraints are now enforced; the designs never met them (kc705 56→116 MHz but 125 required, qmtech-artix7 79→83 vs 100).
litex-sata fasm2frames … FasmLookupError: GTP_COMMON.GTXE2_COMMON.IBUFDS_GTE2.CLKSWING_CFG not found engine bug: the COMMON segment prefix was hardcoded to the GTX name on GTP tiles. Fixed in openXC7/nextpnr#45.
ddr3-test-arty-s7 unrecognised option '--pre-place' toolchain-nix@e3df10c builds with BUILD_PYTHON=OFF, so the fork's python hooks are gone; and the _SING-tile pinning they did is obsolete (the current prjxray-db routes the design, ISERDES on LIOI3_SING_X0Y149 and all).
same, after dropping the hook ISERDESE2 '…ISERDESE2_train' has disconnected D input engine bug: the OFB-loopback exemption only covered D fed by the OSERDESE2's OFB, not the DDR3 example's shape (OFB input fed, D/DDLY open). Fixed in openXC7/nextpnr#45.
2 enclustra XDCs ERROR: found multiple properties 'SLEW' … (on line 713) the XDC sets SLEW FAST and SLEW SLOW on ddram0_reset_n; the new xdc.cc errors on a repeated property. Every other board XDC has FAST only.

Commits

  1. assemble with fpga-as, refresh the goldens — the openXC7.mk migration that
    373b764 left half done (it still called fasm2frames, which the engine's GTP
    FASM defeats), with the seven blinky goldens regenerated by the pinned fpga-as.
  2. adapt the failing demos — --timing-allow-fail on the seven SoC demos that
    miss their derived clocks, ddr3-test-arty-s7's hooks dropped (with the two
    scripts), the stray SLEW SLOW removed from both enclustra XDCs.
  3. pin toolchain-nix @a1408fb — toolchain: pin nextpnr 1436b96f, fpga-as reference parity, chipdb part-name symlinks toolchain-nix#20, which pins
    xilinx: fix the GTP common segment name and OFB-fed ISERDESE2 packing nextpnr#45, the fpga-as reference-parity revision and the chipdb
    part-name symlinks.

The rev pins are pre-merge commits so this PR's CI exercises the whole stack;
#45 is merged; this branch now pins nextpnr's merge commit 5a0b7e41 through
toolchain-nix 3fa6f49 (derivation-identical -- identical store paths, nothing
re-derives). Re-pin to the toolchain-nix merge commit once #20 lands.

Verification (local, with the pinned toolchain + the engine patches)

  • 7 golden-gated blinky jobs: build + normbit.py compare → byte-identical
    to the committed goldens, and the engine patches are therefore golden-neutral.
  • determinism job (blinky-arty/qmtech/zybo, ddr3-test-arty-s7, two fresh builds):
    deterministic.
  • the other 13 smoke projects + litex-ddr-hpcstore-k420t (heavy): all build
    to .bit
    , litex-sata now assembling GTP_COMMON_X97Y127.GTPE2_COMMON….
  • regression/run.sh: 16 ok / 2 skip (zynq7 chipdb is not uploaded), unchanged.

Not touched: the working tree's litex-ddr-arty-s7 regeneration (--sys-clk-freq 110e6 and its ~20 MB of regenerated netlist), which is a QoR experiment of its
own — the committed 60 MHz design still builds.

Assisted-by: deepseek/deepseek-flash

Summary by Sourcery

Restore green smoke and heavy FPGA CI matrices on the updated openXC7 toolchain.

Bug Fixes:

  • Restore smoke and heavy FPGA project matrices by allowing known timing-failing designs to complete and removing obsolete or conflicting board constraints.
  • Fix bitstream generation failures by migrating projects from the fasm2frames/xc7frames2bit pipeline to fpga-as.
  • Remove obsolete DDR3 placement hooks and conflicting Enclustra slew constraints.

Enhancements:

  • Improve FPGA CI chip database artifact handling by preserving symlinks in family archives and unpacking them for downstream jobs.

Build:

  • Update the pinned toolchain revision to the corrected nextpnr/fpga-as and chip database toolchain.

CI:

  • Update smoke and heavy workflows to use fpga-as and reliably package and distribute chip database artifacts.

Documentation:

  • Update workflow and build comments to describe the fpga-as bitstream pipeline and chip database archive handling.

Fix-up after the first CI run: re-pin to 07b9721

The chipdb stage of this PR's own first CI run failed on every family -- not in
the demos, but in the toolchain-nix pin:

ln: failed to create symbolic link '.../xc7a100tcsg324.bin': File exists

The chipdb derivation installed the per-part names as symlinks with ln -s,
and the speed grades of one footprint (xc7a100tcsg324-1, -2, -2L, -3)
all collapse to the same part name, so the second link aborted the build. The
cp it replaced overwrote silently. Fixed with ln -sf in the toolchain-nix
PR (07b9721), and this branch now pins that revision; the pin commit is
therefore superseded by the later re-pin commits.

All five chipdb families were built locally with the fix and every part name
these two matrices use was checked against the resulting directory.

Review fixes

fpga-as parity comment (openXC7.mk) -- claimed "23 designs, five
families". The designs here span four families (artix7, kintex7, spartan7,
zynq7) and the figures were not reproducible, so the check was re-run using
fpga-as's own frame dump:

fpga-as --dump_frames_file=<out> --prjxray_db_path=... --part <part> x.fasm

against fasm2frames output for every design with a .fasm:

  • 20 of 21: frames identical. fpga-as dumps only the frames it sets (286
    for blinky-digilent-arty) where fasm2frames also lists the all-zero ones
    (4974); every frame the two share is word-for-word equal (101-word frames,
    value-diff=0).
  • litex-sata-alientek-davincipro (the only GTP design): two transceiver
    frames (0x2129e, 0x2129f) differ in two words each (77/78). Unexplained, and
    the only frame-content difference found anywhere in the repo.
  • That design's .fasm in a dirty tree can predate the GTP common fix, in
    which case the reference pair cannot read it at all (FasmLookupError on
    GTP_COMMON.GTXE2_COMMON.IBUFDS_GTE2.CLKSWING_CFG); regenerated with the
    pinned engine it emits GTP_COMMON_X97Y127.IBUFDS_GTE2_Y1.* and
    fasm2frames accepts it -- independent confirmation of the merged fix.

The comment now names the four families and the exception; the speed range was
re-measured the same way (9x-120x on 20 designs, previously 8x-112x).

Chipdb artifacts carried dereferenced copies (raised on the toolchain-nix
PR): the chipdb jobs staged with cp "$OUT"/*.bin, which dereferences the
per-part symlinks -- artix7 staged 1011 MB for 123 MB of data, kintex7 1.2 GB
for 252 MB. Both workflows now archive the member list with tar and the
four consumers extract it, so the round trip no longer depends on
upload-artifact's symlink handling (v4.3.5 changed it and broke pipelines:
actions/upload-artifact#590).

CI on the previous head

a15593e ran smoke green: 29/29 jobs -- 4 chipdb, 4 determinism,
regression, all 21 projects, and the 7 golden comparisons. The run at
a15593e is the one that matters for the original failure report: every job
that failed at 373b764 passes.

openXC7.mk now runs fpga-as instead of fasm2frames + xc7frames2bit: one
process, no .frames intermediate, and unlike prjxray 0.9.2's fasm2frames it
can assemble what the engine emits for an Artix-7 GTP (litex-sata's
IBUFDS_GTE2 swing setting, now emitted under the segment name the database
uses).

The seven blinky goldens are regenerated with the fpga-as revision pinned in
the toolchain (hansfbaier/fpga-assembler@cf0e3f0, lromor/fpga-assembler#47).
That revision reproduces xc7frames2bit's header and bit choices, so the
bitstreams match the reference build apart from the date/time stamp that
.github/scripts/normbit.py blanks; the previous fpga-as revision wrote "fasm"
in place of the part name and left seven bits set that the reference clears.

Assisted-by: deepseek/deepseek-flash
…behaviour

The port enforces what the fork only reported: hold analysis is new and fatal,
and the derived MMCM clock constraints are actually checked.  Every LiteX DDR
SoC here failed its derived clock under 0.9.7 as well -- the run #80 logs show
the same designs reporting "Info: Max frequency ... FAIL", non-fatal -- and the
engine's own parity report (nextpnr-xilinx#165) records that the FASM is
byte-identical with and without --timing-allow-fail, so these demos now declare
it explicitly, like litex-sata, litex-ddr-hdmi-* and litex-minimal already do.

ddr3-test-arty-s7 loses its --pre-place/--pre-route hook: the port is packaged
with BUILD_PYTHON=OFF, so the fork's python hooks no longer exist, and they are
not needed any more -- the current prjxray-db has the _SING-tile PIPs and the
design routes without either pinning (it now places an ISERDES on
LIOI3_SING_X0Y149, which the constraints.py workaround existed to avoid).

The two enclustra XDCs set SLEW both FAST and SLOW on ddram0_reset_n; the new
xdc.cc counts a repeated property as an error and stops, and every other board
XDC in the tree has FAST only.  Drop the stray SLOW.

Assisted-by: deepseek/deepseek-flash
Brings the GTP common segment fix (litex-sata), the OFB-fed ISERDESE2 pack
fix (ddr3-test-arty-s7), the fpga-as reference-parity revision the goldens
were regenerated with, and the chipdb part-name symlinks.

The rev is the pre-merge commit from openXC7/toolchain-nix#20 and the engine
rev inside it from openXC7/nextpnr#45; re-pin to the merge commits once those
land.

Assisted-by: deepseek/deepseek-flash
@sourcery-ai

sourcery-ai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

The PR restores the smoke and heavy matrices on the pinned openXC7/nextpnr toolchain by switching all projects to fpga-as assembly, pinning the toolchain containing nextpnr fixes, and updating affected DDR demos and constraints for stricter timing, routing, and XDC validation.

Sequence diagram for project bitstream assembly

sequenceDiagram
    participant Make as Makefile
    participant Synth as Yosys
    participant PNR as nextpnr-xilinx
    participant Assembler as fpga-as
    participant CI as CI matrix

    Make->>Synth: synthesize project
    Synth-->>Make: project.json
    Make->>PNR: nextpnr-xilinx --json --xdc --fasm
    PNR-->>Make: project.fasm
    Make->>Assembler: fpga-as --prjxray_db_path --part
    Assembler-->>Make: project.bit
    Make-->>CI: build result
    CI->>CI: compare golden bitstream
Loading

Flow diagram for the restored CI smoke and heavy matrix

flowchart TD
    Pin["Resolve pinned toolchain"] --> ChipDB["Build or download chipdb"]
    ChipDB --> Build["Build project"]
    Build --> Timing{"Timing failure allowed?"}
    Timing -->|yes| Bit["Assemble .bit with fpga-as"]
    Timing -->|no| Bit
    Bit --> Golden{"Golden comparison required?"}
    Golden -->|yes| Compare["Compare regenerated bitstream"]
    Golden -->|no| Pass["Matrix job passes"]
    Compare --> Pass
Loading

File-Level Changes

Change Details Files
Migrate bitstream assembly from the two-stage fasm2frames/xc7frames2bit flow to fpga-as.
  • Replace the intermediate .frames target and assembly commands with a direct FASM-to-bitstream recipe.
  • Update workflow documentation and comments to describe fpga-as and retain deletion of failed build outputs.
openXC7.mk
.github/workflows/README.md
.github/workflows/smoke.yml
Pin CI to a toolchain revision containing the required nextpnr, fpga-as, and chipdb fixes.
  • Update the smoke and heavy matrix toolchain revision to a1408fb1.
  • Keep the revision synchronized across both workflows.
.github/workflows/smoke.yml
.github/workflows/heavy.yml
Adapt affected demo projects to stricter timing and constraint validation.
  • Allow timing failures for DDR designs whose derived clocks do not meet requirements.
  • Remove obsolete ddr3-test-arty-s7 pre-place/pre-route hooks and their scripts.
  • Remove the duplicate SLEW property from both Enclustra XDC files.
ddr3-test-arty-s7/Makefile
ddr3-test-arty-s7/constraints.py
ddr3-test-arty-s7/show_bels.py
litex-ddr-enclustra-kx2/Makefile
litex-ddr-enclustra-kx2/enclustra_mercury_kx2.xdc
litex-ddr-hdmi-enclustra-kx2/enclustra_mercury_kx2.xdc
litex-ddr-hpcstore-k420t/Makefile
litex-ddr-kc705/Makefile
litex-ddr-qmtech-artix7/Makefile
litex-ddr-qmtech-kintex7/Makefile
litex-ddr-stlv7325/Makefile

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="openXC7.mk" line_range="72" />
<code_context>
+# fpga-as assembles the FASM into the bitstream in one process, where
+# fasm2frames and xc7frames2bit needed two plus the .frames file between them.
+# It assembles the same configuration as that pair on every design in this
+# repo (23 designs, five families, compared frame by frame) and is 8x to 112x
+# faster doing it.
+${PROJECT}.bit: ${PROJECT}.fasm
</code_context>
<issue_to_address>
**nitpick:** The new validation comment claims that the repository covers five FPGA families, but the Makefiles and CI matrices cover only Artix-7, Kintex-7, Spartan-7, and Zynq-7. This overstates the scope of the frame-by-frame equivalence check and can mislead future changes about which device families are protected by the assembler parity claim.

**Suggested fix:** Correct the comment to state the actual number of families, or name the families explicitly.

```suggestion
# repo (23 designs, four families, compared frame by frame) and is 8x to 112x
```
</issue_to_address>

Sourcery assessment

Needs a human reviewer. A faulty fpga-as/toolchain transition or the new timing-allow-fail settings could produce bitstreams that configure successfully but fail on hardware, and removing the SLEW constraint could alter DDR3 signal behavior. Reverting restores the previous build path, while any already-flashed bad bitstreams would require a bounded reflash.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread openXC7.mk Outdated
The chipdb derivation in the previous pin aborted on the second speed grade of
a footprint -- xc7a100tcsg324-1, -2, -2L and -3 all strip to xc7a100tcsg324 --
so every chipdb matrix job of this PR failed:

    ln: failed to create symbolic link '.../xc7a100tcsg324.bin': File exists

The copy that the symlink replaced overwrote silently, which is why this only
surfaced on the first real build.  Fixed in the toolchain-nix PR by linking
with -f; re-pinning supersedes the previous pin commit on this branch.

Assisted-by: deepseek/deepseek-flash
The chipdb derivation installs the per-part names as relative symlinks to the
die database, but the chipdb jobs staged the store output with

    cp "$OUT"/*.bin "$GITHUB_WORKSPACE/stage/"

which dereferences them: artix7 staged 1011 MB of copies for 123 MB of data
(kintex7: 1.2 GB for 252 MB), and that is what the artifact carried.  Review
comment on the toolchain-nix PR.

Tar the member list instead -- tar keeps the symlinks as symlinks -- upload the
single archive, and extract it in the three consumers (project, determinism,
regression) and in heavy.yml's project job.  A tar member list is exactly what
the derivation installed, so the round trip no longer depends on how
upload-artifact treats symlinks either (v4.3.5 changed that behaviour and broke
pipelines).

Verified: artix7 tar is 123 MB for the same 25 entries, 0 broken symlinks after
extraction, every part name the two matrices use resolves, and
blinky-digilent-arty built from the extracted directory matches the committed
golden's normalized hash (ee4159f5...).

Assisted-by: deepseek/deepseek-flash
The toolchain revision carries the chipdb header correction and now tracks
nextpnr's merge commit 5a0b7e41 on main instead of the #45 branch tip.  The
merge brought nothing else (empty tree diff) and the source hash is unchanged,
so every derivation is unchanged: the chipdb and nextpnr store paths are the
same as the previous pin's, nothing re-derives, and the chipdb cache stays warm.

Assisted-by: deepseek/deepseek-flash
The comment claimed "23 designs, five families" compared frame by frame.  The
designs here span four families (artix7, kintex7, spartan7, zynq7), and the
figures were not reproducible.  Re-checked with fpga-as's own frame dump:

    fpga-as --dump_frames_file=<out> --prjxray_db_path=... --part ... x.fasm

against fasm2frames' output, for every design that has a .fasm here:

- 20 of 21 designs: the frames are identical.  fpga-as dumps only the frames
  it sets (286 for blinky-digilent-arty) where fasm2frames lists the all-zero
  ones too (4974), but every frame the two have in common is word-for-word
  equal (value-diff=0, all 101-word frames).
- litex-sata-alientek-davincipro, the only GTP design: two transceiver frames
  (0x2129e, 0x2129f) differ in two words each (77 and 78).  Not explained; the
  other 4688 frames agree, and nothing else in the repo shows a difference.
- its stale .fasm here predates the GTP common fix, so the reference pair
  cannot read it at all (FasmLookupError on
  GTP_COMMON.GTXE2_COMMON.IBUFDS_GTE2.CLKSWING_CFG); regenerating it with the
  pinned engine emits GTP_COMMON_X97Y127.IBUFDS_GTE2_Y1.* and fasm2frames
  accepts it.  Independent confirmation that the merged fix is the right one.

Speed re-measured on 20 designs (assembler only, fasm2frames+xc7frames2bit vs
fpga-as): 9x (blinky-digilent-zybo) to 120x (blinky-ypcb003381p1, 60.6s vs
0.5s), where the comment said 8x to 112x.

The comment now names the families and the exception instead of the stale
counts.

Assisted-by: deepseek/deepseek-flash
@hansfbaier

Copy link
Copy Markdown
Collaborator Author

Comment addressed in cf51c2a -- and the underlying claim did not survive the
re-check, so thank you for pushing on it.

The comment said "23 designs, five families". The designs in this repo span
four families (artix7, kintex7, spartan7, zynq7; 26 project directories, 16
distinct PROJECT values), so "five families" was wrong, and I could not
reproduce "23 designs" either.

Rather than re-word it from memory I re-ran the comparison the claim is about,
using fpga-as's own frame dump (--dump_frames_file, which writes exactly the
text format fasm2frames does) against fasm2frames output, for every design
here with a .fasm:

  • 20 of 21 designs: frame-for-frame identical. fpga-as's dump is sparse --
    286 frames for blinky-digilent-arty where fasm2frames lists 4974 -- but
    the extra ones are all-zero, and every frame the two share is word-for-word
    equal (101-word frames, no coordinate or value differences).
  • litex-sata-alientek-davincipro, the only design that instantiates GTP:
    two transceiver frames (0x2129e, 0x2129f) differ in two words each (words 77
    and 78). This is the only frame-content difference I found in the repo and I
    have not explained it; it is called out in the comment rather than papered
    over.
  • That design's .fasm in a dirty tree can predate the GTP common fix, in
    which case the reference pair cannot read it at all -- fasm2frames raises
    FasmLookupError: ... GTP_COMMON.GTXE2_COMMON.IBUFDS_GTE2.CLKSWING_CFG not found. Regenerated with the pinned engine it emits
    GTP_COMMON_X97Y127.IBUFDS_GTE2_Y1.* and fasm2frames accepts it, which is
    independent confirmation that the merged nextpnr fix is the right one.

The comment now names the four families, states the comparison method so it can
be re-run, and records the one exception; the speed figure was re-measured the
same way and is 9x to 120x (20 designs, assembler only) instead of 8x to 112x.

#20 is merged, so track main's merge commit instead of the PR-branch head.
Nothing changes in what gets built: both trees are f86d564b7db47b5f7c0c8873cf41fd9c7894e5f,
so the flake evaluates to identical outputs -- the chipdb and nextpnr store
paths are unchanged and the chipdb caches stay warm.

Assisted-by: deepseek/deepseek-flash

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sourcery assessment

Approved.

@hansfbaier
hansfbaier merged commit 1e707de into main Sep 25, 2026
5 checks passed
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.

1 participant