Skip to content

fix(cost-sweep): give nextpnr an XDC it can open, with real package pins - #897

Open
gHashTag wants to merge 2 commits into
mainfrom
fix/tnf-cost-sweep-xdc-path
Open

gHashTag wants to merge 2 commits into
mainfrom
fix/tnf-cost-sweep-xdc-path

Conversation

@gHashTag

@gHashTag gHashTag commented Oct 5, 2026

Copy link
Copy Markdown
Owner

Closes #896.

What was wrong

tnf-cost-sweep.yml has been green on main with no arm placed and routed since the per-arm XDC landed (W928, 8b165f5). Two defects, one behind the other:

  1. The XDC was written to the runner's /tmp; the container mounts only $PWD:/work, so nextpnr could not open it (failed to open XDC file, run 37263184079).
  2. With only IOSTANDARD in the XDC, nextpnr-xilinx auto-placed an output on a GTP pad and placement failed (No Bel named 'OPAD_X0Y11/IOB33/OUTBUF', run 34448747524, which already had defect 1 fixed on its branch).

Both runs ended success with every arm recorded as routing-pending, which reads like a fabric limit.

Change

  • The XDC is written under build/tnfcost/, which the container sees as /work/build/tnfcost/.
  • New tools/sweep_pin_xdc.py gives every port bit a PACKAGE_PIN from the pinned image's own xc7a200tfbg484-2/package_pins.csv: general-purpose IOB sites only, clk on an MRCC P pin, bank 34 (the AX7203 DDR3 bank) excluded, no pin used twice. --selftest: 13/13, run in the workflow before the arms.
  • An arm whose nextpnr log has neither Info: Routing.. nor Info: Running router2 is recorded as harness-failure and fails the job after all arms are tried. A real routing failure, after the router started, stays routing-pending and does not fail the job. refit already ran under if: always(), so a harness failure still leaves a report.

Verification

Run 37265666318 on this branch (6a9292434), pnr_arms=tnf_cost_e4m9_add_top,tnf_cost_e3m33_add_top, image eced1cdd as before:

arm pads pinned status LUT FF Fmax (post-route, target 50 MHz)
tnf_cost_e4m9_add_top 57 routed, attempt 1 (heap/router1/seed1) 536 18 532.20 MHz, see caveat below
tnf_cost_e3m33_add_top 123 routed, attempt 1 (heap/router1/seed1) 1632 170 28.41 MHz (fails 50 MHz)

Controls on the router-start marker the new gate uses:

  • positive: both logs above contain it (1 each) and contain neither failed to open XDC nor No Bel named (0 each);
  • negative: both failure logs in run 34448747524's tnfcost-pnr artifact (e4m9, e4m11) contain it 0 times, so that run would now fail the job instead of passing.

speed.py wflint (the build-speed loop's workflow linter) gained a check for this defect class: a host /tmp path written on the runner and then read by a docker run that does not mount /tmp. It flags main's file at the XDC line and does not flag this branch. Across all 122 workflows on main it fires only on this one.

Caveat on the Fmax column

The two numbers are not the same kind of measurement. Arms with M < 25 instantiate gf_adder_param with PIPELINE=0, so the adder datapath runs from input pads to the output register and is not an FF-to-FF path; nextpnr's clock Fmax does not time it. e4m9's critical path is 3 cells on the out_valid control logic. e3m33 (M >= 25, PIPELINE=1) has a 50-cell stage-2 datapath between registers, so its 28.41 MHz is a datapath number. Read the e4m9 Fmax as "not measured" for the adder. Making the narrow arms comparable would mean registering their inputs, which changes the arm definitions, so it is left out of this PR.

Left as they were

speed.py wflint still reports three bare ifs in this file (PNR_ARMS, the routed check and CLEAN). They predate this branch and are untouched here.

The image stays pinned to eced1cdd (W945). That image fails DSP multipliers with $PACKER_GND_NET (cross run 37264809032); the _add_top arms do not use a DSP. Repinning is the owner's call, see #851.

Not measured

🤖 Generated with Claude Code

tnf-cost-sweep has been green on main while no arm was ever placed and
routed. Two harness defects, one behind the other:

- The per-arm XDC (W928) was written to the runner's /tmp, which the
  container cannot see. Run 37263184079 put e4m9 in routing-pending on
  "failed to open XDC file", twelve attempts in seconds. The XDC now
  lives under build/tnfcost/, seen as /work/build/tnfcost/.
- With only IOSTANDARD set, nextpnr-xilinx auto-placed an output on a
  GTP pad: "No Bel named OPAD_X0Y11/IOB33/OUTBUF", placing failed (run
  34448747524, branch tnf-publication-readiness). The new
  tools/sweep_pin_xdc.py pins every port bit to a general-purpose IOB
  from the image's own package_pins.csv, clk on an MRCC P pin, bank 34
  (the AX7203 DDR3 bank) excluded. Selftest 13/13; on a locally
  synthesized e3m33 it pins 123 pads with no pin used twice.

An arm whose log never reaches the router (no "Routing.." or "Running
router2") is now recorded as harness-failure and fails the job, so a
sweep cannot be green with nothing routed. Routing failures proper stay
routing-pending and do not fail it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown

X Brain Health Check

Score: 100.0/100
Status: 🟢 HEALTHY
Threshold: 80/100

X Brain is above merge threshold

@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown

🧪 Brain Stress Test Results

Score: not measured (no stress test prints a Score: line)
Status: 🟢 PASS
Threshold: 270/300

X Stress test passed

The wns field grepped for 'Max delay|worst slack|Slack' and caught the
two "Info: Slack histogram:" headers, so every routed arm recorded
"wns=Info: Slack histogram:;Info: Slack histogram:;" and no number
(run 37266625622, all 5 arms). nextpnr-xilinx prints no worst-slack
line. Record the post-route critical path's logic/routing split
("11.2 ns logic, 44.5 ns routing" for e4m29), which is what the Fmax
line is computed from. Nothing reads wns: refit_cost_model.py reads
lc, status, luts and fmax only.

The comment above it named corona_decode_ax7203.xdc; the sweep writes
its own XDC with pins and IOSTANDARD and no create_clock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gHashTag

gHashTag commented Oct 5, 2026

Copy link
Copy Markdown
Owner Author

Two updates.

8d4ba98 fixes a second empty metric in the same step. wns= grepped for Slack and caught the two Info: Slack histogram: headers, so all five arms of run 37266625622 recorded wns=Info: Slack histogram:;Info: Slack histogram:; and no number. nextpnr-xilinx prints no worst-slack line, so the field is replaced by crit_delay: the logic/routing split of the post-route critical path, for example 11.2 ns logic, 44.5 ns routing for e4m29. Nothing read wns. A one-arm verify run is queued on this branch.

Conflict with #852. git merge-tree reports a content conflict in tnf-cost-sweep.yml. #852 replaces the actions/cache and "Build chipdb" steps with the ./.github/actions/openxc7-chipdb composite action, and this PR adds "Package pins from the pinned image" right after "Build chipdb". The resolution is mechanical: keep #852's composite-action step, then this PR's package-pins step. Whichever PR merges second needs a rebase, and I will do it once the first one lands.

The 5-arm results are on #896, and the refit defects they exposed are in #898.

🤖 Generated with Claude Code

@gHashTag

gHashTag commented Oct 5, 2026

Copy link
Copy Markdown
Owner Author

Verify run 37267724500 on 8d4ba987c, arm e4m9 only: job success, status=routed, and the new field reads crit_delay=0.5 ns logic, 1.4 ns routing, the same split the e4m9 log of run 37266625622 printed. LUT 536, FF 18 and Fmax 532.20 MHz match runs 37265666318 and 37266625622, so this is the third identical result for this arm on one seed.

No metric field in the artifact is empty now. Before this commit, wns held no number in 5 of 5 arms. crit_path is still populated but carries a source cell name rather than a delay; nothing reads it.

🤖 Generated with Claude Code

@tri-claw

tri-claw Bot commented Oct 10, 2026

Copy link
Copy Markdown

Summary by t27-bees

The PR fixes two harness defects that kept the cost-sweep workflow green while no arm actually routed: (1) the per-arm XDC was written to the runner’s /tmp, invisible to the container; (2) the XDC only had IOSTANDARD, so nextpnr placed outputs on GTP pads and failed. The fix writes XDCs under build/tnfcost/ (container-visible) and adds tools/sweep_pin_xdc.py to assign real PACKAGE_PINs from the part’s package_pins.csv (general-purpose IOBs only, clk on an MRCC P pin, bank 34 excluded, no pin reused). A new gate fails the job if nextpnr never reaches the router, distinguishing harness failures from real routing limits.

Worth a look

  • .github/workflows/tnf-cost-sweep.yml:159–162 – The package_pins.csv is copied from a hardcoded path inside the pinned container image (/nextpnr-xilinx/xilinx/external/prjxray-db/artix7/xc7a200tfbg484-2/package_pins.csv). If the image is ever repinned or rebuilt, this path may change and the step will fail silently (empty CSV → no pins → selftest passes but runtime assignment fails).
  • .github/workflows/tnf-cost-sweep.yml:210–212 – Router-reached detection uses grep -qE "Info: Routing\.\.|Info: Running router2". This depends on nextpnr-xilinx’s exact log wording; a version upgrade could change the strings and misclassify harness failures as routing-pending (or vice versa).
  • tools/sweep_pin_xdc.py:18 – CLOCK_PORTS = ("clk",) only recognizes a port literally named clk. Any top with a clock port named clk_in, clock, sys_clk, etc. will not get an MRCC-capable pin and may fail placement or get a non-clock-capable site.
  • tools/sweep_pin_xdc.py:24–28 – is_clock_pin parses pin_function assuming a _-delimited format where the second field ends with P for the P-side of a differential pair. If the prjxray CSV format changes (e.g., different field order or naming), clock pins may not be recognized.
  • tools/sweep_pin_xdc.py:12 – BANKS = ("15", "14", "16", "13", "35") hardcodes the allowed banks and excludes bank 34 for the AX7203 DDR3 bank. This is board-specific; running the sweep on a different board/part would require editing the script.

Read: 2 files, +244 -13, the whole diff. Head cf77fd2. Model: openai-compatible/nvidia/nemotron-3-ultra-550b-a55b. Commands: @t27-bees review | summary | help | pause | resume. Free for every repository.

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.

ci(cost-sweep): green on main with no arm ever placed and routed (XDC path + OPAD auto-placement)

1 participant