Repository navigation
fpga(ax7203): the SAR converter of the golden deck on the AX7203 -- 31 conversions, UART receipts equal to the spec's codes, a one-literal negative control (Closes #10910) - #10919
Merged
Conversation
… conversions, one UART receipt each, a one-literal negative control (Closes #10910) specs/fpga/sar_adc_ax7203.t27 (module FpgaSarAdc) is the board top of the M4e converter (specs/analog/sar_adc.t27): a machine that walks the spec's MSB-first sequence on the fabric (neg, diff, decide, ratio, K x VCM as two shifted adds, round, plate step), with the 31 held samples, the DAC's ten Vout ratios and the two pinned codes of each conversion as if-chain ROMs. It prints one line per conversion, "T27 SAR K=.. C=.. E=.. N=.. S=.. L=.. PASS|FAIL", PASS when the board's code equals the spec's fitted code (30 of the 31 are also ngspice's). Twelve tests, 0 vacuous: the ROMs equal the spec's tables word for word; a plate step is the spec's DAC event for every plate; the three-clock decision is decide(); the machine gives the spec's code for all 31 conversions and the same Vout voltage after every one of the eleven decisions; a conversion is 75 clocks; one more DAC unit on the LSB plate (one rom_k literal) fails exactly 16 of the 31 conversions, the spec's own bridge and Bit_8 controls fail all 31; the restart, the pins and 63 UART lines (two rounds and the wrap) decode. Nothing in specs/analog/ or its seals is touched. Bitstreams are built locally and not committed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
16 of 21 tasks
Contributor
This was referenced Oct 19, 2026
Merged
…o-bit-time gap; the board lost "T27 SAR " at the start of every line after the first (Closes #10910) Found on the board: every line after the first began with the same seven bytes 95 BA 02 9A 05 49 81 instead of "T27 SAR ". The cause: the transmitter set the restart request `fin` at the shift that starts the LF's stop bit (tx_left == 1), and the next conversion plus the next line's first start bit follow about 80 clocks later, 0.09 of a bit time at 868 clocks a bit. The LF's stop bit was cut to a tenth of its time, a terminal read a framing error, hunted for the next falling edge and took bit 4 of the "T" for a start bit. M1b has tens of thousands of clocks between lines, so it never showed; the 8-clocks-per-bit simulation had 10 bit times of compute between lines, so it did not show either. The fix: `fin` is set only after the last frame's stop bit has run its whole bit time and the line has been idle GAP_BITS (2) more bit times (one new register, `gap`, reset with the restart). Nothing else changes in the machine, the ROMs or the receipt. One new test (13 in all, 0 vacuous): the machine at the real divider 868 with a receiver that samples the middle of each bit reads line 0 and the first 12 bytes of line 1 with no framing error, the line was high for the stop bit and two more bit times before line 1's first start bit; with the old handshake it fails (4 framing errors). The simulation of the generated Verilog at 868 clocks a bit reads 34 lines, K = 00 .. 1E and the wrap, all equal to the expected ones. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Contributor
PR DashboardGenerated at: 2026-10-19 08:00:22 UTC
Summary
Seal Status
|
…h data bit of the "T" for a start bit (comment only, Closes #10910) Comment-only change in the spec header: the generated Verilog is byte for byte the one the _v2 bitstreams were built from (sha256 d8e31fdcc479...), and the seal's generated-code hashes are unchanged; only spec_hash moves. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
gHashTag
marked this pull request as ready for review
October 19, 2026 08:11
gHashTag
enabled auto-merge (squash)
October 19, 2026 08:11
This was referenced Oct 19, 2026
Merged
Contributor
PR DashboardGenerated at: 2026-10-19 08:24:47 UTC
Summary
Seal Status
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #10910. Part of #10524 (epic #10486). Board run done 2026-10-19 15:08 (below): 31 of 31 PASS on the board, the negative control fails exactly where predicted.
Board run, AX7203, 2026-10-19 (the fixed v2 bitstreams)
Flashed from the orchestrator's terminal:
openFPGALoader -c digilent_hs2 --busdev-num 000:004 <bit>, then one reader on/dev/cu.usbserial-130, 115200 8N1, 3 s per bitstream, port free before each capture. Each line is checked against^T27 SAR K=<2 hex> C=<3 hex> E=<3 hex> N=<3 hex> S=<16 hex> L=<4 hex> (PASS|FAIL)$and its payload (K, C, E, N, S, verdict) againstexpected_*_lines.txt.652c6a3daeeb4990720f67f0(one more DAC unit on the LSB plate)T27 SARprefix is intact on every line (the first build's 7-byte garbage prefix is gone).Nothing about speed or power is claimed; the blocks are behavioural with the two fitted numbers of M4e.
Update 2026-10-19: the board run found a defect, fixed in b76beea and 4b53ed6 (a comment only; the Verilog and so the bitstreams are unchanged) (this section replaces what the older ones below say about the bitstreams)
The orchestrator flashed both bitstreams of the first push and captured them for 6 s at 115200: the payload after
K=(K, C, E, N, S, verdict) was right on every line, all 31 K PASS for the top, the control FAILed on exactly the 16 predicted conversions. But every line after the first began with the same seven bytes95 ba 02 9a 05 49 81instead ofT27 SAR(8 bytes with the space), so the line did not match the grammar.The cause, in the generated machine. The transmitter set the restart request
finat the shift that STARTS the LF's stop bit:if (last and tx_left == 1) { fin = true; }in the spec,if ((last && (tx_left == 8'd1))) fin <= 1'b1;at line 237 of the first build's Verilog (sha256bc6a6299dd71...). Afterfinthe machine runs the next conversion (1 + 75 clocks), the verdict, and loads the next line's first frame: about 80 clocks, 0.09 of a bit time at 868 clocks a bit. So the next line's first start bit cut the LF's stop bit to a tenth of its time. A terminal reads that LF with a framing error, hunts for the next falling edge, and takes the fourth data bit of theT(0x54: data bits 0 0 1 0 1 0 1 0, low first) for a start bit: its first byte is then95(data bits 4 to 7 and the stop bit of theT, then the start bit and data bits 0 and 1 of the2: 1 0 1 0 1 0 0 1, low first), and it stays misaligned through the contiguous frames ofT27 SARuntil theK. M1b has tens of thousands of clocks between its lines and the 8-clocks-per-bit simulation of the first push had 10 bit times of compute between lines (80 / 8), so neither showed it: the ratio of compute clocks to bit time is the thing.Reproduced at the real bit period before fixing. The Verilog of the first push, simulated at 868 clocks a bit (no patch of
baud_div) and read by a receiver that samples the middle of each bit, gives line 0 right and then, after every line,\x95\xba\x02\x9a\x05I\x81followed byK=01 C=349 ...: the same bytes the board showed, with a framing error at every LF (12 in 227 bytes).The fix (
specs/fpga/sar_adc_ax7203.t27, one new registergap, constantGAP_BITS = 2).finis set only after the last frame's stop bit has run its whole bit time and the transmitter has been idle forGAP_BITSmore bit times: in the transmitter,else if (last)takes the place of loading a next frame, countsgapup on the baud ticks and setsfinwhen it reachesGAP_BITS. It is M1b's own handshake (finrestarts the machine), later. Nothing else changes: the machine, the ROMs, the receipt's fields, the pins.Proof at the real bit period.
T27 SAR K=01), with 0 framing errors, and the line was high for at least the LF's stop bit andGAP_BITSmore bit times (3 x 868 clocks, under 200 more) before line 1's first start bit. With the first push's handshake restored in a scratch copy the test fails (4 framing errors) and the other 12 pass.New bitstreams (the first push's two are superseded and deleted; the payload of every line is unchanged, so the expected lines below still stand, with the prefix now intact):
/tmp/claude-501/sarboard/sar_adc_top_v2.bit/tmp/claude-501/sarboard/sar_adc_neg_bit1_2units_v2.bit652c6a3daeebe897d5a5dfb89112d62a29b13582359ba7b497b21249abb32399(9730778 bytes)4990720f67f0bf0f3a7e942418ed98be28fe4c87cd9d354797372d2c09a4c02c(9730778 bytes)d8e31fdcc479e1f99df471b3fa5f68fca5ba3ac514301ef5b20199532116cfc266c1c155d5a39b3aae84bbdd3e631845934614f56f2d65630887d896ef30fc3cSame flow, default seed, as below. The fix cost 8 flip-flops (
gap); yosys counts 908 LUTs against 916 before, nextpnr 1972 LUT sites against 1959; the top now places at 108.27 MHz (the first push: 124.60), an 8 % margin over the constraint. The control is the fixed spec with the same one literal changed (if (j == 0) { return 16083; }to32166); its Verilog differs from the fixed top's in that one line, and it fails 5 of the 13 tests. Expected receipt lines, FAIL and PASS conversions of the control: unchanged, see below.Board steps (replaces the ones below): the same, with the
_v2filesThe lines are now followed by 3 bit times of high (the stop bit and two idle bits: 0.35 ms) before the next one; a capture that starts in the middle of a line may still cut its first line, as before.
What is in the PR
specs/fpga/sar_adc_ax7203.t27(moduleFpgaSarAdc, 13 tests, 0 vacuous) and its seal.trinity/seals/fpga_FpgaSarAdc.json(sealed by t27c,seal --verifyall MATCH, spec_hash prefix54c6f2c7e86c). The board top of the M4e converter (specs/analog/sar_adc.t27): it runs the 31 conversions of the spec's fitted behavioural converter on the 31 pinned held samples (V(VDP), V(VDN) of our own ngspice + PSP103 run of the golden deck) and prints one UART line per conversion..v,.json,.fasmor.bitis committed. Nothing inspecs/analog/is edited and noanalog_AnalogSar*seal is touched. No other spec is touched, nodocs/reports/t27b_expectations.jsonrow.use, no body is copied: fromanalog::linear(add_sm,pack,is_neg,round_back,fold,q_sm,NEG,MAG),analog::sar_cdac(UNITS,VCM,event_k,plate_step),analog::sar_adc(neg_word,decide,convert_sar,model_k,FIT_CODE,NG_CODE,S_VP,S_VN,CP_FIT,OFF_FIT_V) andfpga::rc_filter_ax7203withfpga::board_receipt(the PLL, the UART frame,hex_digit,pass_fail_char, the verdict and state constants).python3 -I tools/dupe_scan.py: no new duplicate body;tri types ratchet: RATCHET: CLEAN.The machine
One step a clock at 100 MHz, the same for every conversion, no loop that depends on data:
decidesplit over three clocks so that no clock chains more than oneadd_sm(a test proves the split equalsdecide, on all 31 samples both ways round and on a 10 x 10 grid of edge words)pack(rnd, b)andpack(rnd, !b))A conversion is 75 clocks from P_SMP to the verdict (1 + 10 x 7 + 4, pinned for all 31 samples). Net1 of the spec's DAC never reaches the comparator, so the board does not carry it; a test runs both nodes of both halves of the spec beside the machine, with M4a's own
event_k, and compares Vout of each half and the code so far before every one of the eleven decisions, for all 31 conversions.The restart is M1b's: the button and LOCKED pass M0's two registers, the request flags
finandhardset from them restart the machine, a finished line setsfinalone and the next conversion starts. After conversion 30 the board goes back to conversion 0, so the 31 lines repeat (L keeps counting).The ROMs are if-chains (
t27b verilogrefuses array indexing) and a test compares every word with the spec's own tables:q_sm(S_VP[k]),q_sm(S_VN[k]),FIT_CODE[k],NG_CODE[k],model_k(UNITS, 1.0, CP_FIT)[2 j + 1],q_sm(OFF_FIT_V).The receipt
115200 baud 8N1 on N15,
/dev/cu.usbserial-130, one line per conversion (63 characters, CR LF, 5.5 ms a line, 31 lines a round = 0.17 s):K the conversion (hex 00 .. 1E), C the board's 11-bit code, E the spec's fitted code for it (FIT_CODE), N the code of the ngspice + PSP103 run (NG_CODE), S the order-sensitive fold of the board's codes of the round so far (K = 0 .. this one), L the lines since the button. PASS when C equals E, else FAIL; N is printed and not judged, 30 of the 31 E equal N (K=19, conversion 25: 1841 against 1842, the miss the spec reports at 0.133 mV from a threshold). LEDs (active low) as M0: B13 a line is being sent, C13 FAIL, D14 PASS, D15 heartbeat; the button T6 resets.
Measured (local flow, nothing committed) -- of the FIRST push; the fixed numbers are in the update above
t27b verilogthen yosys 0.63 with M1b's script (synth_xilinx -family xc7 -top FpgaSarAdc -flatten -abc9 -nodsp -nosrl -run :coarse; techmap -map +/cmp2lut.v -map +/cmp2lcu.v -D LUT_WIDTH=6; alumacc; opt; memory -nomap; opt_clean; synth_xilinx (same flags) -run map_memory:), thennextpnr-xilinx --chipdb xc7a200tfbg484-2.bin --json .. --fasm .. --freq 100(default seed),fasm2frames.py --db-root prjxray-db/artix7 --part xc7a200tfbg484-2,xc7frames2bit.bc6a6299dd71ad4e4c2890dd85a00d8b81c8393b9cf33231a5ad6788f525040433fd87077b130ae0f145a805dc63439bfe3b658ee6716ec8eab44cba1fbfb9db94dc11837bd637b349bda53d1ee2444eed8d58b1a2c3a247055e590824846346(9730778 bytes)66cd7a60834c9f8d6186c63e879fcfda0341fd564f7dc91d5ab3c58cbca3acb8(9730778 bytes)The first push's bitstreams (
sar_adc_top.bit,sar_adc_neg_bit1_2units.bit) are superseded and deleted; the_v2files are the current ones. The bitstream header carries a file name and a time, so a rebuild of the same netlist has another sha256 (an earlier build of this very top gave5c3809c4...with the same frames count). Place-and-route is deterministic for one netlist and not stable under small netlist changes; both builds used nextpnr's default seed and closed 100 MHz in nextpnr's report. The top was placed at 124.60 MHz, a margin of 24 % over the constraint: more than M1b's 106.30.Simulation of the generated Verilog of the first push (iverilog 13, stub primitives, 8 clocks per bit instead of 868, a scratch testbench that is not committed; this is the simulation that could not show the defect, see the update above): both builds print 64 lines, and the first 63 equal, character for character, the lines an independent integer model of the converter (a scratch Python mirror, not committed, which also reproduces the spec's 31 FIT codes and its 30 of 31 matches) predicts, including S and L and the wrap from K=1E to K=00 with S restarting.
Expected receipt lines (the committed top)
Every complete line must match
^T27 SAR K=[0-9A-F]{2} C=[0-9A-F]{3} E=[0-9A-F]{3} N=[0-9A-F]{3} S=[0-9A-F]{16} L=[0-9A-F]{4} (PASS|FAIL)$. A capture starts at an arbitrary place, so the first and last line of it may be cut. For each K the fields C, E, N, S and the verdict are fixed (S folds from the round's K=00, so it does not depend on where the capture started); L is the line number since the button and counts on without a gap. The first round after the button is:All 31 PASS; after K=1E the board goes round again with K=00 (L=001F), S restarting from 0x3EE.
The negative control
One literal of
rom_kchanged in a scratch copy of the spec (if (j == 0) { return 16083; }to32166, plate 0 = Bit_1, the Vout ratio of 2 units instead of 1: one more DAC unit on the LSB plate); the generated Verilog differs from the top's in that one line and nothing else. The spec's own converter on that table gives the pinnedFAULT_CODEand 16 of the 31 conversions differ from E (FAULT_MASK), so the control prints FAIL on K = 03 06 09 0A 0B 0C 0E 10 11 13 14 15 16 17 1B 1D (decimal 3, 6, 9, 10, 11, 12, 14, 16, 17, 19, 20, 21, 22, 23, 27, 29) and PASS on the other 15 (K = 00 01 02 04 05 07 08 0D 0F 12 18 19 1A 1C 1E). Its S field differs from the top's from K=03 on. The changed spec fails 5 of its 13 tests, andt27c seal --savewould refuse it. The first round of the control:the 31 expected control lines
The tests also assert, for the spec's own two controls (the bridge of 2 units, and Bit_8 with 3 units, each with its whole table re-derived), that every one of the 31 conversions would print FAIL.
Tests (13, 0 vacuous;
t27c test-report,tri testandt27b test --checkagree, 3276 asserts under--check)prod(K, VCM)= two shifted adds;round_backof it =event_k's Vout from rest, both signs)decide(170)0xADF99975C7)baud_div = 1), every field decoded and compared with the spec: K, C, E, N, S, L, PASSBoard steps for the orchestrator (#10910)
Pass: 31 of 31 distinct K lines PASS for the top with C, E, N, S as in the table above; the control FAILs on the 16 predicted K and PASSes on the other 15, with its changed C values.
Not claimed
OFF_FIT_V,CP_FIT), so "30 of 31 equal ngspice's" describes this run and predicts nothing; the switch is the run's, not modelled; the SAR logic is a sequence, not gates (M4d).may_claim_speedupinspecs/analog/bench.t27stays no).Found on the way
A 8-clocks-per-bit simulation hides a line-end defect that the board shows. What a UART testbench must keep is the ratio of compute clocks to bit time, not only the line's content. The new spec test and the iverilog runs use the real divider and a receiver that counts framing errors.
t27b: a module-level
varmust not share a name with a local of any function a test pulls in throughuse.StmtLocal(shadows module var)onacc:analog::linearhas f64 helpers with a localacc, and my first draft of the machine had a registeracc. Renamedpacc. The error points at a line of the resolved file, not of the spec.The
useof three analog specs and M0 resolves every name the spec references:t27c genprints noUNRESOLVEDline, andt27b testandt27b verilogaccept the file. The names that two modules of the closure declare (fold,round_back) agree on their text, as in M1b.Why this session did not flash
The sandbox has no USB; flashing and the UART capture are the orchestrator's steps (above), as in #10565. The first run on the board is what found the defect fixed above.
🤖 Generated with Claude Code