a2e8e5c batched TXDATA writes into 64-byte chunks and broke ACKs outright --
"no ACK after 20 tries". My regression, and CLAUDE.md warns about exactly
this: tx_bytes is 16 deep and BBARegisterFile silently drops bytes when
w_rdy is low, because SPI cannot back-pressure.
Within a single transaction the bytes arrive back-to-back at EXI speed, far
faster than TXFrameDrain starts consuming, so a 64-byte chunk overran the FIFO,
the frame went out truncated, and no valid ACK ever reached the host.
Byte-by-byte worked only because the per-byte EXI overhead happened to pace
the drain -- which is also why it was slow.
Chunk is now 8, half the FIFO depth: it cannot overflow even if the drain
consumes nothing for the whole transaction, while still cutting a 60-byte ACK
from ~60 transactions to 8. Noted in both the source and CLAUDE.md not to
raise it without deepening tx_bytes or giving the TX path real back-pressure.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Self-update is proven end to end on console: 300076 bytes in 294 blocks,
verify OK, /boot.dol boots, and new builds are now delivered this way instead
of by SD card.
bba_tx_bytes() wrote TXDATA byte by byte with bba_w8(), i.e. a full
EXI_Lock / EXI_Select / header / EXI_Deselect / EXI_Unlock PER BYTE -- about
sixty transactions to send one 60-byte ACK. Hardware showed txto=0, so every
ACK did reach the W5100 with TI set; the ~1/3 of blocks the host retried were
not transmit failures but ACKs that missed the host's timeout. The data phase
of a register-space write runs until CS, so there was never a reason to pay
the per-byte cost. It now streams in 64-byte chunks, one transaction each
(64 is bba_write()'s buffer limit, not a protocol limit). t_tx() benefits
equally.
CLAUDE.md records the facility, including that it CANNOT be driven from the
devcontainer -- raw ethernet is link-local and the container has a veth, so
the sender must run on a host holding the physical NIC.
Also records the +1 descriptor offset as OPEN, with what has been ruled out:
not overflow (lapped=0 under RBFI), not the two dropped-write handshake bugs
(fixed and mutation-tested), not write-port contention (ETH has absolute
priority so the port is never stalled; T5 models a stall anyway and the layout
holds), and not the late-response leak (guarded; ratio unchanged at
desc@+0=100 +1=343).
And names the test nobody has written: a bba_top-level EXI DMA read from the
ring WHILE the ethernet side writes into it. ETH preempts EXI reads, so only
that overlap can delay a response past its transaction. Write it before
guessing again.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The whole gc_bba_test suite now passes on a real GameCube: Identity (device
ID 04 02 02 00, HIPR, NWAYS), Registers, Reset/control, EXI integrity (speed
sweep ok=[1,1,1,1,1,1], per-speed dump clean at all six indices, raw reg dump
0x17 in slot 0), Ethernet, and SPRAM/DMA -- at the real 27 MHz EXI rate.
The one reported failure was t_raw_id, and it was not a fault: an earlier
suite had already issued the NCRA reset that clears id_phase, after which
address 0 correctly reads back NCRA. t_devid already guarded for this; t_raw_id
did not, so a full run cried wolf. It now returns SKIP like t_devid, and both
messages say reflash/reset the FPGA rather than power-cycle the console, which
is what actually restores id_phase.
CLAUDE.md records the device-ID fix and, importantly, that simulation CANNOT
reproduce the bug it fixed -- bba_top passed the ID at EXI_HALF=1 beforehand,
because sim's sync-to-EXI latency ratio does not match silicon closely enough
to miss a 296 ns window. Anyone "simplifying" the ID back into the register
file will keep a green suite and break hardware.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Console confirms the full-cycle-early launch: speed sweep ok=[1,1,1,1,1,1],
per-speed dump 0:1700 through 5:1700, raw reg dump 0x17 in slot 0. Register
reads are correct at every EXI speed index including 27 MHz.
Records what the PHASE_METER measurement showed, why three rounds of
delay-cutting could not work (9 ns removed, byte-identical result each time,
so the remaining cost is pad + global clock insertion + IOB + pad, which no
RTL change reaches), and the testbench rule that falls out of it: a master
must sample MISO in the CLK-LOW phase or a zero-delay simulation reads the
NEXT bit and reports everything one bit early while hardware is correct.
Also records the one remaining failure as OPEN rather than fixed: the
command-space device-ID response is one byte late at 27 MHz because a 2-byte
header leaves only one byte time (296 ns) for a round trip that costs
~280-320 ns, and the inter-transfer gap cannot help because the clock is
paused across it. HEADER0 already pushes in the same cycle it consumes byte
0, so the FSM side is already as early as possible.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Removing ~2 ns from the fabric MISO path (EXI_J4 merge + the CS mux) did NOT
move the hardware result: the GC still returned a byte-identical 0B 80 at
27 MHz while reading correctly at 16 MHz and below. Identical rather than
intermittent means the path is well outside the window, not marginally inside
it, so trimming fabric delay further was not going to get there.
MISO is source-synchronous -- driven on the falling edge, sampled by the GC on
the next rising edge -- so the whole path must fit a HALF period, 18.5 ns at
27 MHz, minus global-buffer insertion, pad, connector and the GC's setup.
Driving from fabric spent 8.4-11.5 ns of that on routing alone.
An SB_IO output register sits in the IO tile: clock-to-pad is the IO flop's
own delay with no fabric routing. nextpnr confirms the path left the fabric
entirely -- miso_io.D_OUT_0 packs at die (9,0), the pad itself, SB_IO count is
unchanged at 24, and no negedge -> <async> path is reported any more. What
replaces it is a setup path INTO the IOB register (10.07 ns against a
half-period), which is internal and has nothing stacked on top of it.
PIN_TYPE was decoded from yosys's own SB_IO model rather than from memory,
because wrong bits there produce a silently dead or mistimed output:
[5:4]=10 tri-state, combinational OUTPUT_ENABLE
[3]=0,[2]=1 dout = dout_q_0, single-rate REGISTERED output
[1:0]=01 simple input (unused)
NEG_TRIGGER=1 moves the IO registers to the FALLING edge.
To keep simulation and silicon agreeing about MISO, the slave exposes
miso_next -- the value the shift register WILL hold after the falling edge --
and spi_mode3_slave T6 proves it predicts spi_miso bit for bit. Without that
the IOB register would be an untested sim/hardware divergence, which is the
exact class of bug that has cost this design the most time.
BEHAVIOUR CHANGE: MISO is now TRI-STATED when CS is high, held high by the
pad's PULLUP, instead of actively driven high. EXI MISO is a SHARED bus --
SP1 is on channel 0 alongside memory-card slot A -- so driving it while
deselected was wrong and could fight a card.
--seeds 8: clk 28.5-30.7 MHz, EXI 41.6-49.7 MHz, 8/8 PASS. The EXI numbers
fall (62 -> 49) because the posedge->negedge setup path into the IOB register
is now charged to that domain at a half-period constraint; 41.6 MHz worst case
is still 1.5x the 27 MHz target. Best seed 3 (clk 30.68, EXI 49.14), flashed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Remaining after the bit-phase fix was a SPEED-DEPENDENT failure -- the GC read
correctly at 1/2/4/8/16 MHz and failed only at index 5 (27 MHz), returning
0B 80 = 0x17 late by exactly ONE bit. Opposite signature to the phase slip it
was hiding under, and this one really is propagation delay.
MISO is source-synchronous: driven on the falling edge, sampled by the GC on
the next rising edge, so the whole path must fit a HALF period -- 18.5 ns at
27 MHz -- minus global-buffer insertion, pad, connector and the GC's setup.
The failing build measured ~10.5 ns of that internally. Critical path:
clk-to-q 1.39 tx_shift (3,12)
routing 3.60 tx_shift[7] (3,12) -> (5,5) to the CS mux
logic 1.21 Mux(cs_n, 1, ...) LUT
routing 4.79 miso (5,5) -> (17,0) to the J4 MISO pad
10.99 ns (2.60 logic, 8.39 routing)
Two removals, both of things that were pure cost here:
1. EXI_J4 is GONE entirely (exi_j4.py, the resource, the AND merge, the docs
section, scripts/pi-exi-wirecheck.py) -- recoverable from git history. The
AND put a LUT in the CLOCK path, denying the EXI clock pad its dedicated
global-buffer route, and MISO fanned out to the J4 pad at die (17,0) which
was the far end of the critical output path: 4.79 ns of routing to a header
with nothing plugged into it. 10.2-11.5 ns -> 9.3-9.6 ns.
2. The Mux(cs_n, 1, ...) LUT is gone from the MISO data path. It only parked
MISO high when deselected, which the flop's own reset value does for free:
tx_shift now has init=0xFF and CS is that domain's async reset, so MISO is
tx_shift[7] with nothing in between. Removes 1.21 ns of logic plus the
3.60 ns route feeding it and lets the placer sit the flop by the pad.
synth.py now PARSES AND RANKS BY the MISO clock-to-pad delay. Ranking by fmax
was how a board that failed at 27 MHz passed every seed at >=1.29x margin --
fmax was never the binding constraint for this design.
--seeds 8: clk 28.7-32.5 MHz, EXI 49.6-62.1 MHz, MISO 8.38-10.22 ns, 8/8 PASS.
Best seed 8 (clk 30.35, EXI 49.56, MISO 8.38 ns), flashed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The bit engine had TWO independent phase references: rx_bit counting rising
edges and tx_bit counting falling edges. Those only stay in step while the
host emits exactly as many falls as rises, and it does not -- a transfer that
begins on a RISING edge (CS asserted while CLK is already low, or the clock
gating between two back-to-back host transfers) delivers a rise with no
matching fall. tx_bit then slips one bit and never recovers.
RX never notices, because the rising count always equals the host's bit count.
So the header decodes perfectly, the right register is selected, and ONLY MISO
is wrong -- which is what made this look like an output-edge or output-delay
problem for two rounds of debugging.
Measured on a GameCube: every MISO byte shifted right by 2 bits, identically
at every EXI speed index. NWAYS read 05 C0 (= 0x17 with two leading zero
bits), device ID 01 00 80 80, speed sweep ok=[0,0,0,0,0,0].
Two diagnoses were wrong before this one, and both were ruled out by evidence
rather than argument:
- NOT propagation delay. Index 0 is 1 MHz, a 1000 ns bit time; no output
path fails there. Speed-independence is the tell.
- NOT the output edge. The old rising-edge build and the new falling-edge
build returned BYTE-IDENTICAL 05 C0. Flipping the edge would have moved
it; it did not, so the slip is upstream of the output stage.
What settled it was a measurement. PHASE_METER=1 builds a diagnostic,
non-functional bitstream whose engine transmits a constant 0x80 per byte
instead of the FIFO head, taking the register file and both byte FIFOs out of
the MISO path so the host reads the engine's byte PHASE directly: 0x80
aligned, 0x40 one bit late, 0x20 two bits late. Hardware read 0x20 in all
eight slots, localising the fault to the bit engine in a single run.
Fix: the shift register loads on rx_bit == 0. One phase reference, on the
edge the host counts, so the two cannot drift.
Regression test (spi_mode3_slave T5) asserts CS while CLK is LOW so the
transaction begins on a rising edge. The old counter fails it by exactly one
bit: A5 3C comes back as 52 9E. EVERY earlier testbench asserted CS with CLK
high, so falls and rises always matched and the slip was structurally
invisible -- the same blind spot as the Mode 2/3 mix-up, which was invisible
to a master that parked MOSI early. Vary the starting edge in SPI tests.
Timing after the fix, EXI_J4=1, --seeds 8: clk 26.9-31.1 MHz, EXI 45.8-61.7
MHz, 8/8 PASS. Best seed 4 (clk 31.07, EXI 56.70), flashed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replaces the PLL-fed 54 MHz `capture` domain, which oversampled the GC's
27 MHz EXI clock 2x and so put BIT-level timing across a CDC boundary: the
engine's view of the clock lagged the pin by 2 FFSynchronizer stages plus a
register (~55 ns) against a 37 ns bit time, so MISO came out ~2 bits late and
the GC read 0x17 as 05 C0. No amount of oversampling fixes that -- 54 MHz
gives only 2 ticks per bit and the part cannot close the 81 MHz that 3x needs.
The engine is now clocked by the pin itself (pin 44 = global buffer input net
6, no board change), making MISO clock-to-Q. ExiCapture creates four domains:
exiclk (posedge, samples MOSI), exiclk_n (negedge, drives MISO), both
async-reset by CS, plus reset-less exififo_w/exififo_r for the byte FIFO sides
-- reset-less because resetting one side of an AsyncFIFO desynchronises its
gray pointers, and posedge because AsyncFIFO has a posedge-only reset CDC
internally and cannot host a negedge side.
Also removes the SPRAM response FIFO. The DMA read was one byte late because
the round trip did not fit the protocol's turnaround byte; the arbiter already
exposed valid/ready, so the FIFO bought a cycle of latency and nothing else.
The response is handled outside the FSM (the reply lands in RHDR_PAD, not
SPRAM_STREAM) with a one-entry hold for when tx_fifo is momentarily full.
The PLL was left instantiated but unused after the rewrite; removing it frees
the hard macro and pin 35 (which stays unconnected only because the board is
already built that way). This is what lifted the pass rate:
domain target before after
clk (sync) 24 MHz 28.1-30.2 26.4-32.2 8/8
capture_clk 54 MHz 2 of 8 PASS (gone)
EXI pin domain 27 MHz -- 45.6-64.4 8/8
Best seed 3: clk 32.18 MHz, EXI 55.86 MHz. The binding constraint has FLIPPED
-- `clk` (1.10-1.34x) is now tighter than the EXI domain (1.69-2.39x) -- so
synth.py ranks seeds by worst RELATIVE margin, not by the EXI number.
synth.py could not have scored this design at all: it required the vanished
capture_clk, and its regex '(\w+)' cannot match nextpnr's exi_clk_$glb_clk
(no $ in \w). add_clock_constraint was also landing on the wrong net -- under
EXI_J4 the clock is the AND of two pads, not either pad -- so nextpnr silently
fell back to a 12 MHz default and under-optimised the whole EXI domain.
Testbenches:
- spi_mode3_slave had lost its testbench entirely in the rewrite. Restored
with a strict Mode 3 master (changes MOSI on the falling edge, samples MISO
in the clock-low phase) and MUTATION-TESTED: flipping either clock edge in
the RTL makes it fail. Both of those bugs previously reached silicon
against a lax master that parked MOSI well before the edge.
- exi_j4 T2/T3 returned [0,4,2,2]. Real bug: the engine latches the FIFO head
at each byte boundary, so a 2-byte command header leaves only ONE drain
slot. DEVID streamed zeros until CS and left two stale bytes -- one too
many -- so the NEXT enumeration came back a slot late. DEVID now pushes
exactly four bytes and stops. Register-space reads may still over-push:
their 4-byte header gives three drain slots.
- Dead ports removed from SPIMode3Slave (hdr_bytes, frame_start, cs_active,
byte_idx) and the mode2 parameter is gone -- there is one correct edge
convention and it is now enforced by tests.
All 14 module suites pass, plus bba_top at EXI_HALF=1/2/8.
CLAUDE.md: rewrote the clocking, CDC inventory and module tables; marked the
2026-09-30 capture_clk measurements historical; and corrected the line in
"Critical Protocol Notes" that still stated the INVERTED edge convention
("MOSI sampled on falling, MISO driven on rising" is Mode 2) -- the original
implementation followed that doc error.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hardware speed sweep after the Mode 3 fix: ok=[1,1,1,0,0,0] -- register reads
are CORRECT up to 4 MHz and fail from 8 MHz. The MOSI edge fix works and the
gateware is functionally correct; what remains is a fixed MISO output delay of
~3 capture ticks (~55 ns: 2 stages of input FFSynchronizer plus 1 tick to
register the shift). At 27 MHz that is ~2 bits, which is exactly the observed
NWAYS read of 05 C0 = 0x17 >> 2, and the 250ns-pass / 125ns-fail threshold
brackets the delay at 60-125 ns.
Also records that lowering the EXI clock is not available: Swiss hardcodes
EXI_SPEED_32MHZ in exi_select().
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bba_top derived its EXI clock from HALF, defaulting to 3.4 MHz against the
real GC's ~27 MHz. At that rate the whole CDC response latency fits inside
one bit time, so the suite passed green on a design that failed on console --
this is how both the wrong-header-format and the TX-pop-threshold bugs
reached hardware with everything green.
HALF is now EXI_HALF-configurable (default 8 for fast regression). At the
real rate, EXI_HALF=1, every read comes back shifted right by ONE BIT:
04 02 02 00 -> 02 01 01 00, NWAYS 0x17 -> 0x0B. That is a bit-level sampling
misalignment, not the byte-level latency chased earlier: at 54 MHz capture
there are only 2 ticks per EXI bit and the 2-stage FFSynchronizer on EXI CLK
costs 2 ticks, a whole bit.
Recorded as an open bug in CLAUDE.md with the measurements.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Read from the two drivers that will actually talk to this device: libogc
(libdb/uIP/bba.c) and Swiss (cube/patches/bba/bba.c). The implemented 2-byte
header with a 2-bit length field does not match either.
The real protocol has two address spaces selected by bit 7 of byte 0, and no
length field at all -- the data phase runs until CS:
COMMAND (6-bit reg) one 4-byte readwrite; response in byte 3
REGISTER (16-bit reg) [0x80|w<<6, reg>>8, reg&0xFF, 0x00] + data phase
NCRA, IR, NWAYS, RWP, RRP and PAR all live in the register space. The
mismatch is destructive rather than merely non-functional: Swiss reading
NWAYS sends 80 00 31 00, which the current decoder reads as 'write 0x31 to
NCRA' -- and bit 0 of 0x31 is RESET. Every register read would reset the
device. The device-ID query is the one case where the two formats coincide,
which is exactly why enumeration worked on hardware and nothing else would
have.
Also explains the 22-bit response latency measured on console: the trailing
0x00 byte in the real header is the device's prefetch window, and the current
format spends those bits on the length field, leaving zero slack. Adopting
the real format is required for compatibility anyway and recovers ~296 ns of
the ~815 ns deficit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The PC's replies are unicast to our own MAC, which it could only learn from
our requests, so the loop is closed independently of any packet capture.
Records the four W5100 fixes it took and the caveat that TI does not mean the
chip transmitted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TX: the TWD fix lands a full 60-byte ARP on the wire, byte-for-byte correct
(previously truncated at 16). TI sets.
RX: a real 342-byte DHCP broadcast from the PC was received in MACRAW mode,
assembled into the SPRAM ring and DMA-read back over EXI. The descriptor
length 0x015A cross-checks exactly against the frame's own IP total-length
field plus the ethernet header, so the assembler's length is confirmed, not
merely plausible.
Also replaces the full-path probe's 'is 08 and 06 somewhere in the buffer'
guess with a real descriptor decode -- it called a DHCP frame an ARP reply.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A captured frame proves the full EXI->FPGA->W5100->wire path works: the first
16 bytes are byte-perfect (broadcast dst, PAR0-5 source MAC, ARP ethertype).
It truncates at exactly 16 = the tx_bytes FIFO depth, because TXFrameDrain
waits for a length that only arrives at the ST trigger, after the whole frame
has been streamed.
Supersedes the previous note, which concluded it was NOT depth-related; that
came from a clean-reset run whose result was invalid because the W5100 was
already wedged, so nothing transmitted regardless of length.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The whole GC init sequence now runs over EXI on the real board: device ID,
NCRA reset self-clear, PAR0-5, MAR, ANALOG, NWAYC, IMR, NCRA SR, NWAYS 0x17.
GC->wire TX does not complete. Not an EXI fault and not tx_bytes depth --
8-byte frames fail too, and the UART shell's own UDP send hangs the same way,
so the W5100 bus master is stuck and TXFrameDrain is waiting on it.
Also records that id_phase is a one-shot cleared by the first NCRA reset, so
a re-run probe reads 00 00 00 00 for the device ID and looks broken.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
First silicon proof of the EXI capture path. A Pi bit-banging EXI into J4
reads the device ID 04 02 02 00 21/21, NWAYS 0x17, HIPR 0x01, writes and reads
back PAR0-3, writes IMR, and sees NCRA's reset bit self-clear.
Fixes two host-side bugs found getting there:
- exi_devid_rpi.py sampled MISO just after the rising edge, racing the slave's
own update and landing one bit early (04 came back as 08). Sample in the
CLK-low phase, as the simulation does.
- pi-exi-wirecheck.py's bridge scan drove HIGH against pull-downs, but the
FPGA's SB_IO pull-up out-pulls the Pi's internal pull-down, so every pin
reported as bridged. The aggressor now drives LOW against pull-ups.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4/8 seeds pass capture, best seed 8 = 63.09 MHz (+17% margin) — same pass
rate as the default build, so the AND-merge costs no measurable timing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The W5100S ethernet back-end now works end-to-end on real hardware: a complete,
correctly-checksummed UDP datagram (10.0.0.99:40000 -> 10.0.0.50:6464, 32-byte
payload) was transmitted by the FPGA-driven W5100S and captured on a PC, after a
full bidirectional ARP handshake.
Records the three W5100S-specific findings in CLAUDE.md (NETLCKR unlock, the
15-bit IDM address space putting the buffers at 0x4000/0x6000, and the 60.3 ms
reset-to-ready time), and notes that UDP unicast is the reliable bring-up path
while a 255.255.255.255 send sets SEND_OK without appearing on the wire.
w5100_udptest.py is the self-contained configure+send+readback probe that
isolated all of this.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Devcontainer now grants libusb access to the FT2232H so iceprog (bitstream) and
pyftdi (FT2232H EEPROM) run inside the container:
- devcontainer.json: --privileged + /dev/bus/usb bind mount
- Dockerfile: sudo (NOPASSWD for vscode; USB nodes are root-owned), libusb-1.0-0,
libftdi1-2, pyftdi
Add the flash-re-bba-rb skill documenting the procedure (bitstream via best swept
seed, optional EEPROM string, troubleshooting, guardrails) and
flash_ftdi_eeprom.py (pyftdi, backup-first, dry-run default) + ftdi_eeprom.conf
value spec.
Bring-up on the repaired V1 unit: bitstream flash VERIFY OK (iceprog
build/seed4/top.bin, capture 58.36 MHz). EEPROM product string CANNOT be
programmed on V1 — U7 is a 93LC46B (128 B), too small for the FT2232H, which
needs a 93LC56B (256 B); the 128 B chip mirrors and the config write fails
verify. This overturns the old REVIEW note "93LC46B = correct for FT2232H"
(that only checked ORG, not size). Board still works blank -> ROM defaults;
iceprog + UART unaffected. Documented in REVIEW.md + TODO.md (swap U7->93LC56B
for V2); flash_ftdi_eeprom.py detects the mirroring and refuses the write.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Board V1 ships as-is (capture closes full-speed on 4/8 seeds). Documents,
for a possible future push toward more capture margin (e.g. 64 MHz), that
the binding path is now the tx_fifo exi->capture Gray-code CDC pointer
loop (~6.4ns logic + ~10.7ns routing = 17.1ns / 58.4MHz), and the two
measured levers: a lightweight 2-deep ping-pong TX CDC (RTL, pinout-
independent, est. 67-80MHz - the reliable one) and EXI/W5100 pin
co-location on one die edge (respin, routing-half only, ~63MHz seen).
Both investigated but NOT applied.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An interactive UART command shell drives a UDP send/receive test on the
W5100's socket 3, running ALONGSIDE the live BBA (socket-0 MACRAW) with
EXI keeping bus priority. MACRAW+UDP coexistence is W5100S-datasheet
confirmed (S4.6 + "4 independent SOCKETs"). Now the default flash build
(--console selects the old event-log console).
New / changed gateware:
- uart_shell.py (new): rebbarb> shell over FT2232H channel B. Commands:
help; udp unicast <ip> [msg]; udp broadcast [msg]. After each send it
waits (bounded, else "timeout") for a reply and prints "rx <payload>".
Line buffer + message ROM live in block RAM with a sequential parser
(LC-efficient); 1-deep RX holding reg keeps pastes intact. 8 sim tests.
- w5100_parallel_master.py: configurable UDP socket (default 3) with UDP
send AND receive (IP-stack init, runtime dest IP, WIZnet UDP RX header
+ payload). Gated by enable_udp_test so the MACRAW path is unchanged
when off. Tests U1-U4 + MACRAW T1-T5.
- exi_capture.py: CAPTURE-DOMAIN CRITICAL-PATH FIX. The TX byte-FIFO
read-enable was gated by its own gray-coded ready
(r_en = ... | (flushing & r_rdy)), forming a consume_ptr -> gray ->
r_rdy -> flush -> r_en -> consume_ptr loop that capped capture_clk.
Replaced the r_rdy-based "drain until empty" flush with a fixed-length
drain counter (FIFO is only tx_depth deep), removing the pointer
feedback from r_en. Path 24.3 -> 19.6 ns; flush behavior preserved.
- bba_top.py: wire shell <-> W5100 UDP (send + rx); shell additive.
- synth.py: shell default build; env-var UDP network config; documents a
reverted PNR-timing-priority experiment.
Timing (--seeds 8, default shell build, 67% LC): capture closes on 4/8
seeds (best seed 4 = 58.36 MHz, +8%), clk passes on all. This is BETTER
than the pre-shell 2/8 baseline because the flush fix improved the
capture domain intrinsically. Flash build/seed4/top.bin.
Bring-up caveats (unchanged): W5100 socket register addresses / UDP
header format are datasheet-derived (confirm on hardware); UDP_SRC_IP /
subnet / gateway must match the LAN for unicast ARP.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Documents everything examined in the 2026-07-17/18 review - not just
findings but full coverage with verdicts, so future sessions know what
not to re-review: per-sheet verification against datasheets and the
WIZnet reference design, the settled look-wrong-but-correct list
(VPHY/VPLL on 3.3V, D4/D5 floating VBUS + flow-through, EN at 12V,
RSET 12.4k, no CT cap, DO/DI direction evidence), PCB/JLC/BOM audit
state, explicit not-reviewed list (bench/gateware items), and a binding
rule to keep the file updated in the same commit as any change to a
reviewed circuit. Referenced from CLAUDE.md alongside TODO.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SP1 DO/DI naming is console-perspective (verified against SD Gecko and
SD2SP2 wiring): pin 6 DO = GC data out = adapter MOSI input, pin 9 DI =
GC data in = adapter MISO output. The design doc's SP1 table had the
roles reversed and the schematic followed it, putting the FPGA's MISO
driver on the GC-driven DO line (bus contention).
Fix keeps all PCB routing identical:
- Swap net names EXI_MOSI(_RAW) <-> EXI_MISO(_RAW) across exi/Fpga/root
sheets and the PCB (also renames the D2 PUSB3AB6 channel assignments).
- Swap R21 (22R) <-> R25 (33R) identities between the two footprint
positions so 33R stays on the FPGA-driven MISO line, 22R on the MOSI
input.
- Correct the SP1 pin table + MISO-tristate note in
docs/gc_bba_fpga_design.md (root cause).
Resulting board pin map (for the future board PCF): EXI_MOSI = FPGA
pin 4 (IOB_8a, input), EXI_MISO = pin 3 (IOB_9b, output).
Verified: ERC 0, DRC 0 errors with schematic parity 0, netlist shows
J3.6(DO)->R21->pin 4 and J3.9(DI)->R25->pin 3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Remove stale lowercase power.kicad_sch (pre-rework draft; live sheet is
Power.kicad_sch). Both paths were git-tracked, which on a case-insensitive
Windows checkout makes them one file on disk - deleting the stale one also
deleted the live one, and restoring the live sheet resurrected the stale
path at the next commit. Removing the path from the index breaks the loop.
- Remove re-bba-rb.kicad_pcb.old.* leftovers (recoverable from history).
- Restore hardware/re-bba-rb/datasheets/ (deleted accidentally by 3547302)
and add the two missing datasheets: TPS22810 (power) and PUSB3AB6 (exi).
- Add hardware/re-bba-rb/TODO.md capturing the 2026-07-17 review findings
(EXI DO/DI swap, missing W5100S MDI termination, FTDI EEPROM resistors,
iceprog pin mismatch, PR1 margin, BOM/ordering notes); referenced from
CLAUDE.md.
Verified after cleanup: ERC 0 violations, netlist identical (107 components).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>