Skip to content

fpga(ax7203): DDR3 head read measured at 2769 MB/s -- own_model cell 26 grounded as a draft (Closes #10170) - #10248

Merged
gHashTag merged 2 commits into
masterfrom
claude/ddr3-head-bandwidth
Oct 18, 2026
Merged

gHashTag merged 2 commits into
masterfrom
claude/ddr3-head-bandwidth

Conversation

@gHashTag

Copy link
Copy Markdown
Owner

Closes #10170. Refs #10024 (own-model cell 26, "Full model on FPGA, DDR3").

The DDR3 head-read top for the ALINX AX7203. It is written in t27 (specs/fpga/ddr3_head_bandwidth.t27), lowered by t27b verilog around a LiteDRAM core, routed with openXC7 and measured on the board. The gate is a head read of 2.0 GB/s or more, with the kill at 1.5 GB/s. The board read the head at 2,769 MB/s with every beat correct. Cell 26 of specs/igla/own_model.t27 is grounded from that receipt, as a draft at 10 percent.

Everything hand-written is .t27. No Verilog, constraint file, Python or C was written by hand: the pins are PIN ATTRIBUTEs (#10209), the top is t27b verilog output, and litedram_core.v is litedram_gen output.

RTL written

specs/fpga/ddr3_head_bandwidth.t27 passes 11/11 tests with 0 vacuous on the Railway lab (t27c test-report, zig 0.16.0). Seal: .trinity/seals/fpga_FpgaDdr3HeadBandwidth.json, spec sha256 5ae8e395....

The run, step by step:

  • Wait for the core's init_done.
  • Write 325,000 beats of 32 bytes (10,400,000 bytes, the head: 2.6M fp32). Each beat is four 64-bit lanes of a pattern that no two beats or lanes share.
  • Read the beats back at full rate, counting the clocks from the first read command to the last beat in.
  • Compare every beat with its pattern and fold it into an order-sensitive checksum.
  • A 32-step serial divider turns the clock count into MB/s.
  • Give the verdict:
    • FAIL on any bad beat;
    • otherwise PASS at 2000 MB/s or more, KILL below 1500 MB/s, and SLOW in between.
  • Repeat the receipt on the UART (115200 baud on N15).

Three fixes since the first bitstream:

  • The checksum was degenerate. The four lanes of a pattern beat xor to zero, so the plain-xor fold gave S=0 C=0 on the board. Lane k is now rotated left by 16*k before the xor; that is wiring and adds no gate.
    • A new test pins the head's sum at FFB7236C398EE555.
    • Another test asserts that a beat's lanes xor to 0, so the reason for the rotation is checked too.
  • The verdict multiplier was on the critical path.
    • The verdict is now taken from the divider's quotient. verdict_by_rate is proved against verdict_of on the boundary rows.
    • The numerator gains 3200 per beat instead of a multiply.
  • Read data went from the PHY straight into the compare. The read beat is now latched and checked one clock later. The read tests were re-derived for the latch: rcyc, MB/s, verdict and divider clocks.

Synthesised and routed (Railway lab)

  • Tools: yosys synth_xilinx -flatten, then nextpnr-xilinx for xc7a200tfbg484-2, then fasm2frames and xc7frames2bit.
  • Timing: not closed.
    • nextpnr estimates the 100 MHz user clock at 90.9 MHz (FAIL at 100).
    • Across 8 placement seeds it estimated 85.4–94.8 MHz.
    • The worst path is now inside the LiteDRAM core: the ZQCS timer into refresh logic, 2.3 ns of logic and 8.7 ns of routing. My logic is off it.
    • Before the fixes, the user logic was the limit: 56.8 MHz from the multiply, then 70.1 MHz from the PHY-to-compare path.
  • Calibration depends on the route. Of 9 routes flashed, 3 pass the LiteX BIOS memtest and run; the other 6 fail the memtest or print nothing. The receipts below are from routes that calibrate. A timing-closed flow for the core is a follow-up, not claimed here.
  • VREF: nextpnr-xilinx writes VREF.V_675_MV for the SSTL bank (MT41J256M16 wants 750 mV). Calibration passed anyway.

Measured (AX7203, openFPGALoader -c digilent_hs2 --busdev-num 1:3, UART CP2102N)

Bitstream sha256 e6915eaf461e755a1831a29632ee496e699df9328ca6f54727419b800111523a.

BIOS:

  • Read leveling passed on m0–m3.
  • Memtest OK.
  • BIOS memspeed: 995.8 MiB/s write, 1003.9 MiB/s read (the CPU's wishbone path, not this harness).

Receipts from two flashes of the same bitstream (the L= line counter dropped):

T27 DDR3 N=000000000004F588 W=000000000005D388 R=000000000005BB02 M=0000000000000AD1 S=FFB7236C398EE555 C=FFB7236C398EE555 E=0000000000000000 PASS
T27 DDR3 N=000000000004F588 W=000000000005D38E R=000000000005BAFC M=0000000000000AD1 S=FFB7236C398EE555 C=FFB7236C398EE555 E=0000000000000000 PASS
field value
beats 325,000 (10,400,000 bytes)
write clocks 381,832 / 381,838
read clocks 375,554 / 375,548
read rate 2,769 MB/s (86.5% of the 3,200 MB/s peak; gate 2,000)
bad beats 0
write checksum = read checksum FFB7236C398EE555, the value the spec's test predicts
verdict PASS

Negative control. A lab-only mutant checks each beat against the pattern of beat ri+1. It is not committed. Its seed-5 route (sha256 40a9fe42...) calibrated, and it reported:

T27 DDR3 N=000000000004F588 W=000000000005D39E R=000000000005BAFB M=0000000000000AD1 S=FFB7236C398EE555 C=FFB7236C398EE555 E=000000000004F588 FAIL

Every beat counts as bad, 325,000 of 325,000, and the checksum still matches because the data are right. So E=0 on the real build is a real compare, and so is the checksum match.

Cell 26 in specs/igla/own_model.t27

  • SOURCES names this spec and its receipt. MISSING is cleared.
  • GROUNDED is true, and STATUS becomes 3 (draft). Step 26 is a DELIVER step and the engine asks a review for that, so it is not verified.
  • PCT is 10. This is a sequential read of a pattern: no weight on the board and no compute from DDR3.
  • The MEASURED note records the receipt, the control and the open timing.
  • Recount: 1 published, 16 verified, 6 drafts, 4 gaps. The harness lens is 38%; overall stays 54%.
  • own_model_board.t27 follows: MSG, LENS_PCT, CELL_STATUS, CELL_PCT and METRICS_BYTES 626 → 627.
  • Lab test reports: own_model 9/9, own_model_board 6/6, 0 vacuous. t27b verilog exits 0 on both.

Not claimed

  • Timing closure at 100 MHz, and a reliable build. 3 of 9 routes calibrate.
  • Any model running. No weights are on the board and nothing computes from DDR3.
  • Any access pattern other than this one: a sequential write, then a sequential read, of one 10.4 MB region.
  • VREF at 750 mV.
  • Native t27b. It was not run here: the lab is x86_64 and the t27b JIT is arm64-only. The ledger row is checked by CI.

🤖 Generated with Claude Code

gHashTag and others added 2 commits October 18, 2026 11:47
… written and sealed

The board top around a LiteDRAM core: write 325,000 beats of 32 bytes,
read them back at full rate, count the clocks, divide into MB/s, and send
the receipt line on the UART. 10/10 tests, 0 vacuous, on the Railway lab.
RTL written and synthesised; not yet routed or measured.

Refs #10024, #10170

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…as a draft

The checksum folded the four lanes of a beat with a plain xor, and the
pattern's lanes xor to zero, so the first bitstream reported S=0 C=0.
Each lane is now rotated by 16*k before the xor (wiring only); the
spec's test predicts the head's sum, FFB7236C398EE555, and the board
reports exactly that for both the write and the read pass.

The read beat is latched and checked one clock later, and the divider
works on a per-beat numerator and its quotient instead of a multiply,
which takes the user logic off the critical path (56.8 -> 90.9 MHz in
nextpnr's estimate; the worst path is now inside the LiteDRAM core).

Board receipt (AX7203, two flashes): 325000 beats in 375548..375554
read clocks, M=2769 MB/s, E=0, PASS. A mutant checking beat ri+1
reported E=325000 FAIL. own_model cell 26 is grounded at 10 percent as
a DELIVER draft; the tally is 1 published, 16 verified, 6 drafts, 4 gaps.

Refs #10024

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

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-18 05:56:17 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 48
PRs with All Checks Green 2
READY 0
FAILING 48
PENDING 0
NO CHECKS YET 0

These columns do not partition: 0 + 48 + 0 + 0 = 48, and there are 50 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=0f8c66b9ed0e != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@gHashTag
gHashTag enabled auto-merge (squash) October 18, 2026 06:00
@gHashTag
gHashTag merged commit 16117db into master Oct 18, 2026
35 of 39 checks passed
gHashTag added a commit that referenced this pull request Oct 18, 2026
Cell 26 is grounded as a draft at 10 percent (DDR3 head read), cells 8,
24 and 25 keep this branch's evidence. Tally 1 published, 17 verified,
8 drafts, 1 gap, 61 percent; own_model 9/9 and own_model_board 6/6 on
the lab, 0 vacuous.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

fpga(ax7203): DDR3 head-read bandwidth for own-model cell 26 -- blocked on inout and >64-bit ports

1 participant