'highest good = 0' meant both 'only index 0 works' and 'nothing works', which
are opposite diagnoses -- the first says oversampling, the second rules it
out. Now reports the per-index map and says NONE explicitly.
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>
tx_fifo.r_en popped from txld_cnt >= 2, hard-coded for the old invented
2-byte header where the data phase began at slot 2. The real protocol's
register space uses a 4-byte header, so data starts at slot 4 -- two bytes
were being consumed before the GC clocked any data, and every register read
came back wrong. On hardware: HIPR and NWAYS both read 0x00, and the device
ID returned 01 80 c0 c0 (stale bytes, including our own header command bytes
0x80/0xC0 echoing back out of the FIFO).
The threshold is now derived from byte 0 bit 7, which is exactly what selects
the header form: register space -> 4 slots, command space -> 2 slots.
Also removes the pad-slot filler byte the register file was pushing. That was
compensating for this bug rather than fixing it -- it made the simulation pass
while hardware still failed, which is how it slipped through. With the pop
threshold correct the filler is wrong, and both the unit and integration
testbenches agree without it.
All suites pass: bba_register_file, bba_top, exi_capture, exi_j4,
spi_mode3_slave, tx_frame_drain, spram_arbiter, rx_frame_assembler,
w5100_parallel_master.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replaces the invented 2-byte header (addr split across both bytes, 2-bit
length field) with the protocol libogc and Swiss actually speak:
command (bit7=0, 6-bit reg) [reg, 00, 00, data] one 4-byte transfer
register (bit7=1, 16-bit reg) [0x80|w<<6, ah, al, 00] + data until CS
This was not an optimisation: the old decoder read Swiss's NWAYS read
(80 00 31 00) as 'write 0x31 to NCRA', and bit 0 of 0x31 is RESET, so every
register read would have reset the device.
Changes:
* 16-bit address; no length field -- register reads and writes now stream
until CS, like the SPRAM path already did
* RHDR_LO starts the SPRAM fetch as soon as the address completes, using
the trailing header byte as the prefetch window the protocol provides
* command space serves EXI regs 0x02/0x03 (interrupt mask/status), the only
two Swiss uses
* EXI_GetID gets its own path: it offers just ONE header byte of warning, so
the first ID byte is pushed the instant byte 0 identifies the query
Two timing subtleties, both found by simulation and commented in place:
* MISO is driven LIVE from the tx FIFO head, so a byte pushed while the pad
byte is on the wire appears in the pad slot. A filler byte is parked
there (MISO is don't-care through the header) so the first real response
lands in data slot 0.
* the prefetch must be one-shot; re-firing while waiting for the pad byte
consumed the register stream early and shifted reads by a byte.
All testbenches updated to the real format and passing: bba_register_file
(7), bba_top (8 incl. the full W5100->SPRAM->GC loop and TX), exi_j4,
exi_capture, spi_mode3_slave, tx_frame_drain, spram_arbiter,
rx_frame_assembler, w5100_parallel_master. gc_bba_test rebuilt against the
new protocol.
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>
First on-console run: the Identity suite failed with the device ID reading
00 00 00 10 instead of 04 02 02 00. Bit analysis shows that is an EXACT
match for the correct stream delayed by 22 bits -- the device answers
correctly, just too late for a 32-clock window to catch more than its first
set bit. Not corruption, and not a broken link: response latency.
At EXI index 5 (~27 MHz) 22 bit-times is ~815 ns, which is about what the CDC
chain costs (rx AsyncFIFO capture->exi, register-file decode, tx AsyncFIFO
exi->capture, SPI engine) at a 24 MHz exi clock. The design expects the GC to
hold the clock idle between the header write and the data read so the register
file can prefetch the response; libogc's inter-call overhead alone is not
enough at full speed.
Adds bba_gap_us (default 16 us, busy-waited with CS asserted since we hold the
EXI lock) and a Gap sweep test that reports the minimum clean gap at the
current speed -- which measures the latency rather than guessing at it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sets the PC to 10.0.0.50/24 so the GC test suite's RX test can pass: its TX
test ARPs for that address and the kernel answers ARP for an address it
actually holds, so no listener process is needed. Without it RX fails
legitimately for lack of traffic.
Handles the NetworkManager trap -- NM silently strips a hand-added address
within seconds, so the device is set unmanaged first and handed back on
'down'. Warns that this drops any existing connection on that NIC.
up / down / status / watch, with wired-interface auto-detection (prefers one
with a carrier) and a status readout that flags no-carrier along with the
likely cause: the W5100S rail is gated off GC_ON, so it is dead whenever the
console is off.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
libogc's EXI functions return '1 on success, <=0 on error', but every call
site tested '== 0'. A -1 error return therefore read as success, so a failed
EXI_Lock or EXI_Select would fall through and the subsequent transfer would
produce garbage reported as a misleading test failure rather than an EXI
error. All six call sites now test <= 0.
Also retries EXI_Lock for ~100 ms: it fails outright rather than blocking when
another subsystem holds the channel, and Swiss or a libogc driver may hold it
briefly -- without the retry a transient collision surfaces as a test failure.
Builds clean with devkitPPC 16.1.0 / GCC 16, no warnings: 172 KB .dol.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gc_bba_test/ builds a .dol that drives the adapter over raw EXI from the
console and reports pass/fail per test on the TV, navigable with the pad.
Uses libogc's EXI API against EXI0:2 (Serial Port 1) rather than its bba
network driver: driving the register file directly gives each check its own
verdict, so a failure points at a specific register instead of at
'networking'. Six suites - identity, registers, reset/control, EXI link
integrity (including an EXI clock-index sweep), ethernet TX/RX, SPRAM/DMA
streaming.
Encodes the hardware lessons from bring-up so the app cannot mislead:
* the device ID is a one-shot cleared by the first NCRA reset, so it is
reported SKIP on a re-run rather than FAIL
* TI means the frame reached the W5100, NOT the wire - the TX test says so
* register reads clock exactly the requested length (over-clocking
desynchronises the next transaction)
* TXDATA is written one byte per transaction (eff_addr auto-increments)
.devcontainer/Dockerfile gains the devkitPro gamecube-dev group. devkitPro
publishes no .deb on GitHub, so this uses their installer script against
apt.devkitpro.org -- which is 403 from inside the running container's egress
but reachable from a host-side docker build. Rebuild the container to get it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Regenerating the production files for the V2.1 eFuse rework surfaced two
steps that neither the skill nor CPL_ROTATIONS.md documented, both caught
only by diffing against the previously-shipped CPL:
- --exclude-dnp does not drop 'in_bom no' parts, so J4 and TP1-TP5 landed in
the raw export although the shipped CPL excluded them. Filter to
designators that have a BOM line; the two sets should end up equal.
- kicad-cli emits signed rotations while the correction step's % 360 only
normalises corrected parts. Mixing the two made the diff show 34 phantom
rotation changes (270.0 vs -90.0 is the same angle), burying the 2 real
ones.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The TPS25961's exposed pad is GND and the datasheet requires it be connected
to the GND plane (Table 5-1; package note 3 adds a mechanical requirement).
REVIEW.md claimed this was honoured because the footprint is the _ThermalVias
variant -- but the variant alone connects nothing. hardware/TPS25961.kicad_sym
defined pins 1-6 only, with no pin for the exposed pad, so KiCad had nothing to
map onto footprint pad 7 and left it netless: an isolated copper island under
the chip with its thermal vias going nowhere.
Adds pin 7 (GND, power_in, hidden, stacked on pin 4) to the symbol library, the
cached symbol in exi.kicad_sch, and the U13 instance.
PCB updated and verified: U13 pad 7 carries GND on all four elements (both SMD
rects and both thermal vias), R48 placed, EFUSE_OVLO routed, R49 gone. DRC
reports 0 violations, 0 unconnected pads and 0 schematic parity issues -- the
0-unconnected result is what confirms pad 7 is tied into the GND network rather
than merely labelled. The 4 DRC errors previously dismissed as zone-fill
phantoms on this pad are gone, though zones were refilled in the same pass so
that is not proof of cause either way.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
13 resistors carried Tolerance=5% while their MPN is a UNI-ROYAL 0402WG*F*
part -- F is the EIA code for +/-1% (J is 5%), and LCSC confirms the 1% rating.
The parts are 1%; only the metadata was wrong.
Worth fixing rather than leaving: the same part number appearing with two
different tolerances splits into two BOM lines under any grouping that
includes the field, and it misleads anyone sizing a divider. R48 in the eFuse
ladder was one of these (fixed in a9052c6) and there it was load-bearing --
at a genuine 5% the OVLO worst case reaches 17.05 V, over U3's 17 V limit,
versus 14.42-15.83 V at the correct 1%.
Metadata only; no circuit or part number changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Redraws the eFuse area with proper wires instead of labels-on-pins, and
simplifies the divider that 228ff03 added.
The first draft split R3 into 12 k + 2.2 k to hit 14.2 k, purely to preserve
the original ~8.9 V UVLO. That bought a fourth resistor and a named junction
net (EFUSE_R3MID) that nothing else touched. The ~9 V UVLO turned out to be
an assumption carried over from the original design rather than a requirement:
U3's minimum input is 4.5 V, so relaxing it to 6.78 V costs nothing and is in
fact less likely to chatter if the GC rail sags.
R46 620 k -> 100 k (C25741) R1: 12V_RAW -> EN/UVLO
R47 100 k -> 12.4 k (C11692) R2: EN/UVLO -> OVLO
R48 NEW 10 k (C25744) R3: OVLO -> GND
R49 removed
R45 100 k -> 33 k (C25779) ILIM 0.52 A -> 1.52 A
OVLO is unchanged at 15.18 V typ (14.47-15.78 worst case) against U3's 17 V
limit, so the simplification costs nothing on the parameter that matters.
Verified: 12V_RAW(R46.2) / EFUSE_EN(R46.1,R47.1,U13.5) / EFUSE_OVLO(R47.2,
R48.1,U13.2) / GND(R48.2,R45.2,U13.4); 152 -> 151 components with only R49
removed; ERC 0 violations.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Implements the values worked out in 6ce7657.
R45 100k -> 33k ILIM 0.52 A -> 1.52 A (clears the 621 mA inrush)
R46 620k -> 150k ladder R1: 12V_RAW -> EN/UVLO
R47 100k -> 10k ladder R2: EN/UVLO -> OVLO
R48 NEW 12k \ ladder R3 = 14.2k: OVLO -> GND
R49 NEW 2.2k /
U13 pin 2 (OVLO) taken off GND and onto the new EFUSE_OVLO net
Verified: netlist shows 12V_RAW(R46.1) / EFUSE_EN(R46.2,R47.1,U13.5) /
EFUSE_OVLO(R47.2,R48.1,U13.2) / EFUSE_R3MID(R48.2,R49.1) / GND(R49.2,R45.2,
U13.4); 150 -> 152 components with none dropped (the malformed-property trap
from the kicad-bom-edit skill); ERC 0 violations.
PCB placement and routing still to do.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Works the TPS25961 fix out from the datasheet rather than from memory, and
re-solves it against resistors already on the BOM.
OVLO: TI's 3-resistor ladder (eq 7/8) -> R46 620k->150k, R47 100k->10k, new
R48 12k + R49 2.2k (=14.2k). UVLO 8.93 V typ, unchanged from today; OVLO
15.21 V typ, 15.81 V worst case. The OVLO ceiling is set by U3 (TPS562201,
4.5-17 V input), not by the eFuse -- that rules out a tempting 3-resistor
solution tripping at 17.24 V.
ILIM: R45 100k -> 33k. 0.516 A sat BELOW the board's own 621 mA inrush, so
the part entered current limiting on every power-on (-> thermal shutdown ->
retries -> latch-off). Inrush control comes from the fixed 5.17 V/ms slew
rate, not ILIM, so raising ILIM costs no inrush protection; it is pure fault
protection at ~27x the measured 56 mA load.
Every value is already stocked and 620k loses its only user: one FEWER unique
part, +2 placements.
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>
RX_RSR_CHK returned to IDLE when RSR==0 without clearing Sn_IR. SEND_OK
(bit 4) fires after every successful transmit and the RX path only ever
cleared RECV (bit 2), so INT_N stayed asserted; because IDLE tests
~w5100_int_n BEFORE tx_valid, the FSM span IDLE->RX_CHECK->RX_RSR_CHK->IDLE
and TX was starved permanently. Observed on hardware as the first frame
transmitting and every later one never setting TI while RX kept working.
Now clears every Sn_IR bit except RECV, so a frame arriving between the RSR
read and the write still raises INT_N and is served next pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
w5100_rst_n was declared and never driven anywhere -- the pin sat at its init
value of 1 forever. The only reset the chip ever got was the MR software
reset, which arrives over the very bus a wedged chip has stopped answering, so
a wedged W5100 could not be recovered at all: not by NCRA reset, not by
reflashing, not even by reconfiguring the FPGA (RSTn just floats to its R36
pull-up while unconfigured). Only a 12 V power cycle worked.
Init now pulses RSTn low (1 ms, datasheet minimum is ~2 us) and waits the full
post-reset settle before touching the bus. RSTn low time is clamped to
reset_cycles so the testbenches' small values stay reachable by wait_ctr.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The init does MR=0x80 (software reset) then waits reset_cycles before writing
NETLCKR/SHAR/SIPR/socket registers. That wait was 24_000 cycles = 1 ms at
24 MHz, but the W5100S needs the datasheet's 60.3 ms -- our own bus self-test
needed ~85 ms before the chip answered at all.
1 ms is marginal rather than simply wrong, which is why this presented as
intermittent: an NCRA reset sometimes left the chip still resetting while the
init writes landed on it and were silently dropped, leaving it misconfigured
and the bus master blocked. Symptom was a W5100 that wedged after an NCRA
reset -- TX never completing and the UART shell's own 'udp unicast' hanging --
recoverable only by a 12 V power cycle. Note RST_N is never asserted by the
gateware; only the software reset is used, so this wait is the only settle
time the chip gets.
Default is now 2_000_000 = 83 ms. Testbenches pass small values explicitly and
are unaffected.
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>
TXFrameDrain sat in IDLE until it had a length, and the length only reached
tx_ctrl when the NCRA ST bits were written -- after the GC had streamed the
whole frame. So the entire frame had to fit in the 16-deep tx_bytes FIFO, and
BBARegisterFile silently drops bytes when w_rdy is low (SPI cannot
back-pressure). On hardware this truncated frames at exactly 16 bytes: a
captured ARP had 16 correct bytes followed by the frame restarting over itself.
The length is now handed to the drain one cycle after the TWD_HI (0x35) write,
which the GC issues BEFORE streaming data. The drain starts early and blocks
on tx_bytes_r_rdy, consuming bytes as they arrive, so tx_bytes becomes a
rate-matching buffer instead of whole-frame storage. The ST write remains a
fallback trigger for software that skips TWD, guarded by tx_queued so one frame
is never queued twice.
Adds BBATop T8: streams a 34-byte frame (> the 16-deep FIFO) over EXI and
checks the W5100 model received it byte-for-byte. Verified to FAIL without the
fix with exactly the hardware symptom -- 16/34 bytes, first bad byte at index
16. The gap that let this ship: TXFrameDrain's own tests drive the FIFO
signals directly, and the BBATop test only covered the RX loop.
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>
Verifies the harness before attempting a real EXI transfer. Keyed on the
EXI_J4 build's SB_IO pull-ups: an FPGA input with PULLUP=1 reads HIGH when
connected, follows the Pi's pull when open, and reads a hard 0 only when
shorted -- so all three cases are distinguishable from the Pi alone.
Includes a T0 control on known-unwired pins; without it a broken pull
mechanism would turn every 'connected' verdict into a false pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Factors the EXI_J4 pad merge out of synth.py so the synthesis path and the
test exercise the same code. Verifies the device ID (04 02 02 00) enumerates
over SP1 with J4 idle, over J4 with SP1 idle, and over SP1 again afterwards,
plus a register read via J4 and the full cs_n truth table.
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>
Lets a bench host drive EXI without the SP1 edge connector, for bring-up.
Merges a second set of EXI pads onto the existing ones rather than repinning,
so the SP1 connector stays live and the default build is untouched. The three
inputs are AND-merged, which is correct because every EXI input is inactive
HIGH (CPOL=1 clk, active-low cs_n, mosi only matters while cs_n is low): the
unplugged side idles at 1 through its SB_IO PULLUP and passes the other side
through, so no select signal is needed. Outputs drive both pads.
All five J4 pads are on the same bottom die edge as the EXI pads (x=13..18 vs
x=5..9), so the capture domain keeps its anchor. It still adds 3 LUTs ahead of
the input synchronisers, so EXI_J4 builds must sweep seeds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Anode-side pad at 3V3, cathode at 0V, LED dark — the full rail standing
across it means no current and no drop across R42, so Q2 and R42 are both
doing their job. Probe pressure on the anode pad lights it at full
brightness: the terminal is sitting on the pad unwetted. Reflow fixes it.
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>
Reverts the 0x8000/0xC000 change from 162dbf7, which was WRONG and actively
broke transmission. The W5100S datasheet pseudo-code quotes 0x8000 (TX) /
0xC000 (RX) for the memory blocks, but the PARALLEL/indirect (IDM) interface
only decodes a 15-bit offset. Bench-proven with an address-aliasing probe:
writing distinct values to each candidate base and reading them back showed
0xC000 and 0x4000 are the SAME cell (0xC000 & 0x7FFF == 0x4000) while 0x6000
holds its own value.
Consequence of the bad base: every payload byte written to 0x9800+i aliased
down onto 0x0000+i, landing on SHAR (0x09-0x0E) and SIPR (0x0F-0x12) and
overwriting them with the payload. The chip then transmitted frames with a
garbage source MAC/IP, which the peer NIC drops without counting -- the
"SEND_OK but rx_packets=0" symptom, and the bogus SIPR0 readback (0x50 was
literally payload byte 15, 'P').
The NETLCKR unlock from 9c4bc7a is a genuine W5100S requirement and stays.
Also:
- w5100_udptest.py: self-contained configure+send+readback probe (and the
address-aliasing probe that found this), reports over UART.
- scripts/build-flash.sh: sweep N seeds and flash the best (capture timing is
seed-dependent, so the sweep is mandatory).
- scripts/uart-console.py: interactive UART console, plus --send/--read
one-shot modes for scripting.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reads socket-3 / status registers over the indirect bus and streams them on the
UART, without resetting the chip, to inspect what a UDP send left behind. First
hardware run was inconclusive (socket regs read 0x00 but SIPR/PHYSR didn't match
either "state persisted" or "clean reset") -- the FPGA reflash appears to glitch
RST_N, so a self-contained configure+send+readback is the reliable next step.
Committed as a starting point for that.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Second W5100S-vs-W5100 gotcha found at bring-up: the W5100S has a Network Lock
Register (NETLCKR, 0x0071) the original W5100 lacks. It defaults to 0x00 (locked),
and while locked, ALL writes to GWR/SUBR/SHAR/SIPR are silently dropped -- so the
chip has no source IP/MAC and the UDP/MACRAW send builds nothing (chip reports
SEND_OK but transmits nothing; socket registers aren't locked, which is why an
indirect write/readback of Sn_PORT succeeded). Fix: write the unlock value 0x3A
to NETLCKR right after MR enables indirect mode, before the network-config writes.
On the bench this flipped the board from silent to actually keying frames onto
the wire (magjack activity LED now blinks).
Also:
- bba_top.py test model: RX buffer base 0x6000 -> 0xC000 to match the W5100S
buffer-base fix (the model hardcoded the old W5100 address).
- w5100_selftest.py: added an indirect-mode (IDM) write/readback probe of a
socket register + IND= field in the UART report, to isolate direct-vs-indirect
access (confirmed indirect works on the real chip).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
From the physical V2 bring-up session (2026-09-20):
- exi_bba/w5100_selftest.py (NEW): standalone FPGA<->W5100 bus diagnostic.
Clock-independent data-bus walk (direct-mode GAR0 read-back, all 8 lines) +
MR software-reset self-clear (needs the 25 MHz crystal). Reports 4 verdicts on
D6/D7 LEDs AND over UART (115200): GOOD / data-line-N / clock-dead / no-response.
Boot wait is 85 ms (> the W5100S 60.3 ms init). On this board: GOOD -- bus +
all 8 data lines + 25 MHz clock all verified (unlike V1's broken D3).
- REVIEW.md / TODO.md: eFuse U13 OVLO is tied to GND, which on the TPS25961
selects the internal FIXED ~6 V overvoltage cutoff -- so it blocks the 12 V
rail entirely (bench-confirmed: 12V_EXI=0, GC_ON=0, W5100 unpowered). FAB-
BLOCKER for V2.1: fix with an OVLO divider (~1.1M/100k -> trip ~15 V). Bench
workaround: lift U13 pin 2 (OVLO) off GND. Also closed the eFuse-placement,
GND-via/3V3-zone, and CC-ESD TODO items.
- hardware/re-bba-rb/exi_devid_spi.py (NEW): Pi hardware-SPI (~25 MHz) EXI
device-ID host, companion to the bit-bang exi_devid_rpi.py.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The W5100ParallelMaster used the original W5100's TX (0x4000) / RX (0x6000)
memory-block base addresses. This board's chip is a W5100S, whose TX/RX memory
blocks live at 0x8000 / 0xC000 (datasheet: TX 0x8000-0xBFFF, RX 0xC000-0xFFFF).
Consequence found at hardware bring-up (2026-09-20): every frame was written to
dead address space, so SEND transmitted the (empty) real TX buffer -- the chip
reported "sent" but nothing valid reached the wire (peer rx_packets stayed 0,
link up). The sim bus-model used these same constants, so it never caught it --
exactly the "register addresses are datasheet-from-memory, confirm at bring-up"
risk. All socket register offsets were verified correct against the W5100S
datasheet; only the two memory bases were wrong. Fixes both the socket-N UDP
test and the socket-0 MACRAW (GC) path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hardware bring-up correction (Dennis, 2026-08-22): writing the FT2232H product
string to the undersized 93LC46B (U7) does NOT harmlessly fail — the partial,
mirrored write lands a checksum-valid-but-garbage config, and the FT2232H then
FAILS USB enumeration entirely (dead silent, no dmesg attach). It does NOT fall
back to ROM defaults; only a blank/checksum-invalid EEPROM does. The earlier
claim that "a bad EEPROM always falls back and still enumerates" was WRONG.
Recovery (confirmed working): power on with U7 CLK shorted to GND so the FT2232H
can't read a valid config -> forces ROM defaults -> enumerates -> then erase U7.
Updated REVIEW.md, TODO.md, the flash-re-bba-rb skill (do-not-program warning +
recovery, native-Linux troubleshooting), and flash_ftdi_eeprom.py comments/help
to state the soft-brick reality. The script already refuses the write on
mirroring detection; --force now documented as "reproduce the soft-brick".
Co-Authored-By: Claude Opus 4.8 <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>
All eFuse/RExt/EE-CS parts (U13, R43-R47, C66, R44, D13) are picked with
MPN+LCSC in the live BOM and placed+routed on the PCB - the TODO/REVIEW
notes calling their fields 'intentionally blank' were written when the
parts were first added and never updated. Corrected to reflect the
committed state.
Also documents the kicad-cli DRC gotcha that caused a false alarm: it
does not refill zones, so it reports phantom GND-via-in-3V3-pour and
thermal-pad clearance errors (actual 0.000mm) on a board the GUI reports
clean. Added a warning at the top of TODO and in REVIEW section 7 so no
future session mistakes it for a real defect.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>