6b2e6724a23c94ba6bd01aced4c5d76cc25c5152
55 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6b2e6724a2 |
Fix the 0/8 timing failure the ring-geometry change caused
The sweep after honouring BP/RHBP came back clk 16.5-17.6 MHz, 0/8 seeds
passing against the 24 MHz target -- from ~29 MHz before. The build was
unusable.
Cause: the sanitised bounds were combinational, so
bp/rhbp -> comparators -> Mux -> subtract -> pg_total -> add -> raw
-> compare -> free -> compare -> have_room
became ONE path where it had been constant-folded. Two fixes were needed, and
the first alone was not enough:
1. Register pg_first/pg_last/pg_total and rrp_s. They change only when the GC
writes BP/RHBP/RRP, so a cycle of staleness costs nothing, and `free`
already reaches the FSM through the registered have_room. This got it to
~20 MHz, still failing.
2. Narrow them from 8 bits to 4. Ring pages are 1-15. An 8-bit variable-vs-
variable comparison is a carry chain where variable-vs-CONSTANT folds into
a couple of LUTs -- precisely what making the bounds dynamic had thrown
away. geom_ok guarantees bp < rhbp <= 0x0F, which makes the narrow slice
safe.
Now 26.90 and 28.50 MHz on seeds 1-2, against 28.00/29.21 for the build
without the geometry change.
Attribution needed an A/B: the register-space decode (item 4) landed in the
same stretch and had never been swept on its own. Measured separately it
closes 28.0-29.2 MHz and costs nothing, so the whole regression was the
assembler. The critical-path report named rx_frame_assembler.py:215 -- the
rrp clamp -- which is what pointed at the comparator width rather than at the
arithmetic everyone would suspect first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
147ef8fcfc |
Honour the GC's ring geometry (BP / RHBP) instead of hardcoding it
Compat TODO item 2, the last silent-corruption risk. RXFrameAssembler took the RX ring as a compile-time constant (pages 1-15), so a driver configuring a SMALLER ring was ignored and we kept writing pages it would never read, with nothing reporting an error. The bounds now come from bp/rhbp, wired in BBATop from the GC's BP (0x0A/0B) and RHBP (0x1A/1B) registers, and both the page wrap and the free-space calculation use them. Garbage is rejected rather than obeyed: the driver's values are used only when bp >= 1 (page 0 is the TX buffer), rhbp <= 0x0F and rhbp > bp. Otherwise the hardcoded layout stands and geom_honoured goes low, so a mismatch is observable. Without that check BP=0 or an RHBP below BP gives a zero or negative ring size and the assembler writes outside it. ⚠️ Recorded as a known limitation: a ring must be LARGER than _MAX_FRAME_PAGES (6) or nothing is ever stored, because a frame's length is unknown until EOF so the worst case (1518+4 = 6 pages) is reserved up front. Found the honest way -- the test was written with RHBP=4 first and passed VACUOUSLY, storing nothing at all. Making that work needs a length-aware reservation, which is a bigger change than this one. T7 (8-page ring honoured, frames only in pages 1-8, and at least one actually stored) and T7b (all three garbage cases rejected, fallback still stores). Mutation-tested: wrapping at the hardcoded page 15 writes pages 9-11 and fails T7; removing the sanity check fails all four T7b assertions. Item 1 (RX filtering) remains the only open gap. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4a455bbe88 |
Decode NCRB and the registers with non-zero hardware defaults
Compat TODO items 3 and 4. Every register libogc2 writes now stores writes and reads back, with the documented hardware reset defaults: NCRB (0x01), BP (0x0A/0B), TLBP (0x0C/0D), RXINTT (0x14/15), RHBP (0x1A/1B), NWAYC (0x30, default 0x84), GCA (0x32), MISC (0x3D, default 0x3C), MISC2 (0x50). BP and RHBP reset to OUR layout (page 1, page 15) so a driver reading them back without writing sees something consistent with where the ring actually is. Previously all of these read 0x00 and dropped writes, so a driver doing read-modify-write or sanity-checking a default saw zero -- and libogc2 does read NWAYC before writing it. Deliberately still reading 0x00: LTPS, LRPS, TWP, TRP, TXFIFOCNT. Those are STATUS registers, not configuration, so storing what was written to them would be a lie rather than a default. Drivers read LRPS/LTPS only on error bits and 0x00 reads as "no error", which is truthful for a device that does not detect them. NCRB, BP and RHBP are also exported as BBARegisterFile.ncrb/.bp/.rhbp for the RX-filtering work (item 1) and the ring-geometry check (item 2). Nothing consumes them yet -- the decode lands first so the behaviour that uses it is a separate, testable change. T15 checks the defaults, replays libogc2's twelve init writes in its order and reads every one back, and asserts the NCRB export is 0x52 with PR=0, PM=0, AB=1 -- i.e. that the real BBA delivers own-MAC + broadcast only, which is the fact item 1 has to act on. Mutation-tested: dropping NWAYC's 0x84 default fails it, as does dropping NCRB writes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7116bc1ecc |
Implement the command registers libogc2 actually touches
Previously every command register except 0x02/0x03 read 0x00 and dropped writes. Real software will hit them, so they are now implemented, enumerated from libogc2's gcif.c rather than guessed at random (Swiss's patch driver touches only 0x02 and 0x03): 0x01 read revision ID -> priv->revid 0x02 write interrupt mask (IRMASKALL / IRMASKNONE) 0x03 read / w1c interrupt status 0x04 write 2 bytes priv->devid = 0xD107 0x05 write priv->acstart = 0x4E 0x08 read 4 bytes challenge | device-initiated path, 0x09 write 4 bytes response | gated on status bit 0x10 0x0B read challenge/response status 0x0F read (slow) in __bba_reset(), result DISCARDED 0x01 returns 0x00 and is read-only. That is a real-hardware value, not a guess: the BBA never received a functional firmware revision that incremented it. libogc2's priv->revid = 0xf0 is its PRE-INIT PLACEHOLDER, overwritten by whatever this read returns, so it must not be mistaken for the device value. revid/devid/acstart feed only bba_calc_response(), whose path is device-initiated, so they are functionally inert for us. 0x04 and up are plain read/write storage, which IS a guess: both drivers treat them as write-only and never read them back, so read-what-you-wrote is the least surprising behaviour and lets our tests confirm the write landed. Ignoring them (the old behaviour) is strictly worse -- the write vanished. Registers above 0x0F still read 0x00 so a stray high register cannot alias onto a real one. Noted for later: bba_cmd_in8_slow(0x0F) sits in __bba_reset() between two 10 ms delays, holds CS for 200 us and discards the result. A read whose value is thrown away but whose timing is deliberate looks like a reset strobe; we treat it as an ordinary register, and the NCRA reset follows immediately in that sequence, so ignoring the side effect costs nothing today. T14 covers the whole table and is mutation-tested: making 0x01 plain storage fails it, and dropping the 0x04+ writes fails it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0d100eb84f |
Implement the COMMAND-level interrupt: without it no driver sees a frame
The BBA has two interrupt levels and we implemented only one. Both drivers' handlers are identical in shape -- libogc2's bba_event_handler and Swiss's exi_coroutine: status = bba_cmd_in8(0x03); /* COMMAND level */ bba_cmd_out8(0x02, BBA_CMD_IRMASKALL); if (status & 0x80) bba_interrupt(); /* ONLY then the MAC level */ and bba_interrupt() is what reads register-space IR/IMR and calls bba_start_rx(). Bit 7 of command register 0x03 means "a MAC interrupt is pending". r_cmd_ir was write-1-clear with nothing ever setting it, so a command read of 0x03 returned 0x00, every branch of both handlers fell through, and NEITHER DRIVER WOULD EVER HAVE PROCESSED A RECEIVED FRAME. Our own tooling could not have caught it: gc_bba_test and netboot poll RWP/IR directly and never use the interrupt path, so the whole command-level mechanism was untested and unused. Green suite, dead interface. cmd_ir[7] is now a live level equal to (IR & IMR) != 0, and INT_N is (cmd_ir & cmd_imr) != 0. A level rather than a latch because both drivers clear the MAC IR inside bba_interrupt() and only then write-1-clear command 0x03: a latched bit would be cleared by that write even if a new frame had arrived in between, while a level stays asserted as long as the condition does. INT_N honouring the command-level mask is what stops the line re-asserting while a handler runs (IRMASKALL on entry, IRMASKNONE on exit). r_cmd_imr resets to 0xF8 since the real power-on value is unknown and both drivers set it explicitly first thing. T13 and T13b cover it, both mutation-tested: reading the latched bits only leaves 0x00 after an RI, and ignoring the command mask leaves INT_N asserted through IRMASKALL. Also records in CLAUDE.md which driver actually talks to us, since Swiss has several back-ends and it is easy to read the wrong one: cube/patches/bba/ has one file per chip (bba.c is ours; enc28j60.c, w5500.c, w6100.c, w6300.c are other adapters), Swiss's menu networking goes through libogc2's gcif.c, and cube/patches/bba/bba.c is the in-game patch driver. Detection is by EXI device ID, so returning 04 02 02 00 is what gets us handed to the BBA path at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b3685237b2 |
Support BOTH command-space forms: Swiss's 4-byte and libogc's 2-byte
Reading Swiss's own driver (cube/patches/bba/bba.c) shows the previous commit
fixed libogc and BROKE Swiss. The two disagree, and both are real:
Swiss bba_cmd_out8: exi_imm_read_write(0x40<<24 | reg<<24 | val, 4)
-> ONE 4-byte transfer [0x40|reg, 00, 00, val], and bba_cmd_in8
returns the LOW byte, so a read response must be in BYTE 3.
libogc2 bba_cmd_outsnosel: req = (reg<<8)|0x4000;
EXI_ImmEx(&req, 2, WRITE); EXI_ImmEx(val, len, WRITE)
-> THREE bytes, value in byte 2, and a read takes byte 2.
stock libogc: identical to libogc2 (read out of libbba.a).
They share bytes 0 and 1 and differ only in how many bytes follow, so one
decoder serves both: take the LAST data byte before CS rise as the write value
(Swiss sends [00, val], libogc sends [val] -- last is val either way), and push
a read response TWICE so bytes 2 and 3 both carry it. Swiss clocks 4 bytes and
pops both; libogc clocks 3 and leaves one, which the next transaction's header
pops drain -- a 2-byte header gives exactly one drain slot, so the leftover is
bounded at one and cannot accumulate.
Critically this means NO auto-increment of cmd_reg across the data phase: that
applied Swiss's padding 00 to the register and val to reg+1, which is precisely
how the libogc-only version broke Swiss.
T12 covers the Swiss form and asserts bytes 2 AND 3; T10 keeps the libogc form.
Both mutation-tested: re-adding the auto-increment fails T12 with 0x00 in both
slots, and pushing the response once fails T12's byte 3. gc_bba_test gains
"cmd-space Swiss" alongside "cmd-space write", with bba_cmd_write4/read4.
Also corrects two things in CLAUDE.md now checkable against three drivers:
- NO driver writes TWD -- not Swiss, not libogc, not libogc2. The "GC sets
TWD before streaming" note was self-confirming: t_tx writes TWD because we
wrote t_tx. The counted TXDATA length is the primary path, not a fallback.
- Swiss's exi_dma_write rounds the length UP to 32 bytes (OSRoundUp32B), so
TXDATA gets up to 31 trailing bytes past the frame and a counted length sends
that much extra. Real hardware must do the same since Swiss works on it;
trailing padding past the IP total length is ignored. A 1514-byte frame
rounds to 1536 though, over the 1518 maximum.
Both drivers trigger TX with NCRA ST1, which the existing any-ST test accepts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
71ec6a2b37 |
TX: count the TXDATA bytes, because libogc never writes TWD
Third contract checked against libogc rather than our notes. __bba_link_tx is four transactions -- read NWAYS, ONE register-space write of the whole frame to 0x0048, read NCRA, write NCRA = 0x04 -- with no TWD_LO/TWD_HI anywhere. So the device has to count the bytes it was handed. The NCRA ST fallback queued tx_frame_len, which only TWD sets, so any driver that skips TWD would have transmitted a stale or zero length. BBARegisterFile now keeps txdata_cnt, incremented per byte accepted at 0x0048 and cleared whenever a frame is queued from either path, and the ST path queues that. T11 drives TX in libogc's shape with no TWD write and checks both the queued length and the drained bytes. Mutation-tested: switching the ST path back to tx_frame_len queues length 0, which is exactly the bug. gc_bba_test gains the hardware counterpart, "TX without TWD", which deliberately omits the TWD writes that t_tx makes. The TWD path stays and is still preferred when present: writing the length before streaming is what lets TXFrameDrain consume concurrently and keeps tx_bytes a rate-matching buffer rather than whole-frame storage. Swiss writes TWD, libogc does not, so both are supported. Also recorded: libogc streams the payload with EXI_Dma (32-byte-aligned bulk after PPCDCacheStoreAsync, then EXI_ImmEx for the remainder). CLAUDE.md said EXI DMA writes are avoided for the GC's 1-bit-shift bug; that is true of Swiss, not libogc. Nothing in the gateware changes -- a DMA write is just bytes clocked in -- but the path will be exercised, and if that bug is real it appears as corrupted TX frames that are not fixable here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0aa1fa884a |
Fix command-space writes, and replay libogc's real init sequence
Second protocol mismatch found the same way as the descriptor: by disassembling libogc's own driver instead of trusting our notes. COMMAND SPACE IS A 2-BYTE HEADER + DATA PHASE, not a single 4-byte transfer. CLAUDE.md said [0x40|reg, 00, 00, data] and the decoder followed it, waiting for a fourth byte and consuming the DATA byte as header byte 2 -- so every command-space WRITE was silently dropped. libogc's __bba_init writes command register 2 (its interrupt mask) at both the start and the end of init, so none of that took effect. What it actually sends: EXI_Imm (len=2, write) -> 42 00 the whole header, 0x4000 | (reg << 8) EXI_Sync EXI_ImmEx(len=1, write) -> 00 the data Three bytes between CS fall and CS rise. The device-ID read survived the wrong decode only because SPIMode3Slave answers it on a fast path independent of this FSM -- which is precisely why no test caught it: the only command-space traffic under test bypassed the decoder being tested. CHDR_2/CHDR_3 are replaced by CMD_WRITE, which takes the data byte straight after header byte 1 and auto-increments the command register so a multi-byte command write behaves like the register-space path. cmd_wreg latches the target because the write applies a cycle later, by which time cmd_reg has moved on. bba_register_file T10 writes command register 2 and reads it back, and is mutation-tested: restoring the 4-byte decode fails it. Also replaces the GUESSED init sequence in CLAUDE.md with the real transaction list. The old one was wrong in three ways that matter: libogc READS the MAC out of PAR0-5 rather than writing one (so our reset value is what the console uses), IMR is 0xDF not 0x86, and it writes RWP (0x0016/17) and RHBP (0x001A/1B) which the register file does not accept. The latter is harmless today -- RHBP is 0x000F, matching our hardcoded 15-page ring -- but a driver configuring a smaller ring would be ignored while the assembler kept writing outside it. New gc_bba_test suite 7 "libogc init" replays the sequence on hardware: cmd-space write (2-byte header, read back), MAC readable (one 6-byte PAR read, Nintendo OUI), and init replay (all 30 writes in order, then NWAYS/HIPR/NCRA.SR must still read correctly -- the old guessed header format turned a NWAYS read into an NCRA reset, so a misdecoded write is a real risk). bba.c gains bba_cmd_write/bba_cmd_read in libogc's shape. Every module testbench and bba_top at EXI_HALF=1/2/8 pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d83f7beef8 |
Fix the RX ring descriptor to the format libogc actually parses
The assembler wrote 00 00 len[11:8] len[7:0] with bytes 0 and 1 zero. The real format, read out of libogc's own BBA driver, is a PACKED 32-BIT LITTLE-ENDIAN word: next_packet_ptr in bits 0-11, packet_len in bits 12-23, status in bits 24-31. Under that driver our descriptor for a 342-byte frame decoded as a 12-byte frame with a next-pointer of 0 into the reserved page, so Swiss and anything else built on libogc would have initialised perfectly and then misparsed every received frame. Found by disassembling /opt/devkitpro/libogc/lib/cube/libbba.a (gcif.o, bba_interrupt) rather than by guessing: lwbrx r9,0,r9 # 4 bytes as a little-endian word rlwinm r4,r9,20,20,31 # packet_len = (word >> 12) & 0xFFF addi r4,r4,-4 # payload = packet_len - 4 cmpwi r4,1540 srwi r9,r9,24 # status = word >> 24 and. r9,r9,r18 # r18 = 0x..84 -> errors are 0x80 | 0x04 and the low 12 bits are written straight back to RRP (0x0018/0x0019), which is why next_packet_ptr has to be there at all -- it is the driver's read pointer. The length semantics and addressing were already correct (descriptor at page<<8, body at +4, length including the descriptor); only the packing was wrong and next_packet_ptr was never written. RXFrameAssembler now computes the next page BEFORE writing the descriptor (CALC_NEXT, formerly ADVANCE_RWP, moved ahead of the backfill) and writes bytes 0-3 as WRITE_D0..WRITE_D3. WRITE_D3 fixes a latent bug this exposed: WRITE_PLACEHOLDER issues four writes but the first lands twice, so desc_base+3 was NEVER written. That was harmless while the length write covered byte 3. As the status byte it would have been whatever the previous frame left in that cell, and a stale 0x80 or 0x04 makes the driver discard the frame. Readers updated to decode identically: netboot (which also now takes RRP from the descriptor's own next_packet_ptr, as libogc does, instead of recomputing the page span) and t_dma, including its page map. netboot's +0/+1 offset probe is removed -- the underrun that shifted descriptors is fixed and confirmed on console, and under a packed format a +1 read is not a shifted descriptor but garbage. The assembler's testbench now decodes with libogc_decode(), reproducing the instruction sequence above, so the tests check the DRIVER'S algorithm rather than our own packing. Every module testbench and bba_top at EXI_HALF=1/2/8 pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
abab26232b |
Fix the +1 descriptor offset: drop the arbiter's response register
SPRAMArbiter latched the SPRAM word into rsp_buf and raised exi_rsp_valid a cycle later. BBARegisterFile already took the response the cycle it arrived, so the buffer bought nothing and cost a 24 MHz cycle (~42 ns) inside a deadline that was short by about that much. Both are now combinational in the cycle SPRAM's output is valid. The data sampling is unchanged -- the old code read spram_dout in that same cycle, it just registered it before handing it over -- so only the handoff moved. bba_top T9 goes from 1/6 to 6/6 byte-exact at EXI_HALF=1, and stays 6/6 at 2 and 8. The whole suite passes at the hardware rate. Nothing else changed: BBARegisterFile is untouched. Found by measuring instead of guessing, after four falsified attempts. A counter in ExiCapture recording tx_load & ~tx_fifo.r_rdy, with a shift-register history of which byte boundary underran, showed good and bad reads BOTH underrun at pop 3 (harmless, mid-header) while a bad read adds pop 4 -- the pop that loads data slot 0. So the late byte is the FIRST data byte, which rules out every "prefetch further ahead" idea, because byte 0's request cannot be issued before RHDR_LO where the low address byte arrives. That makes the deadline arithmetic with no contention in it: rx CDC ~125ns + arbiter 42 + SPRAM 42 + rsp_buf 42 + tx CDC ~110 = ~361ns against a 296ns byte time. Over budget idle, which is why fair arbitration changed nothing -- ETH contention was only what pushed a marginal path over on some frames. Two further changes were implemented, measured and then removed as unnecessary: issuing byte 1's request during the pad byte, and two requests in flight with a 3-deep response hold. With rsp_buf gone, T9 is 6/6 with both removed, with either alone, and with the register file reverted completely. They were real pipelining but not the constraint, and they cost logic on sync, the binding timing domain. CLAUDE.md records them as do-not-re-add, along with the trap that sank the earlier 2-outstanding attempt: incrementing `outstanding` in the FSM and decrementing it in the response handler are two m.d.sync assignments that Amaranth resolves by PRIORITY, not arithmetic, so the increment is silently discarded when both fire in one cycle. Arbiter TB updated (the response is a cycle earlier) and mutation-tested: re-registering the response fails T1/T2/T4, swapping the byte lanes fails T1/T2. New T4 pins exi_rsp_valid to exactly one cycle. Not yet confirmed on hardware; netboot's reader still probes both offsets. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
bd4c69c5f2 |
Establish the +1 offset mechanism: tx FIFO underrun, not a stale byte
Reading the frame body at 0x104 (first byte a known 0xDE) instead of the
descriptor at 0x100 (first byte 0x00, indistinguishable from a stale byte)
shows what actually happens:
good: DE AD BE EF 01 02
bad: DE DE AD BE EF 01 <-- the FIRST BYTE IS DUPLICATED
So it is a tx FIFO UNDERRUN, not an inserted byte. When the FIFO runs dry for
one byte-time the bit engine reloads the stale FIFO head and sends the previous
byte again, shifting the rest of the read.
The register file must sustain one byte per 296 ns at 27 MHz. The round trip
is request -> spram_req SyncFIFO -> arbiter (ETH has absolute priority and
preempts it) -> SPRAM -> response -> push -> tx AsyncFIFO CDC (2-3 EXI cycles).
That is marginal even idle and over budget during a frame write, and only ONE
request may be in flight so the latency is never hidden.
THREE FIXES TRIED AND FALSIFIED, recorded in CLAUDE.md so they are not
repeated:
- Fair round-robin arbitration instead of strict ETH priority: still 2/6.
Bounds the wait to ~2 cycles but does not close the gap, and breaks
spram_arbiter T3. Reverted.
- Deepen tx_fifo 2 -> 8 for cushion: 0/6 with leading zeros. The engine
drains leftovers with ONE POP PER HEADER BYTE, so a deeper FIFO leaks
stale bytes into the data phase. tx_depth=2 is deliberate. Reverted.
- Allow 2 outstanding requests to pipeline: 0/6 at EVERY rate, including
HALF=8 which previously passed. With no response FIFO, responses must be
consumed the cycle they are valid; two in flight loses bytes. The
existing comment was right. Reverted.
Tree is back to the T9-reproduction baseline: 6/6 at HALF=8, 1-2/6 at HALF=1.
PROMISING DIRECTION, not attempted: SPRAM is 16 BITS WIDE and the arbiter
reads a word then returns one byte selected by address bit 0. Returning both
bytes per request would push two bytes per round trip, doubling the budget to
592 ns and making it comfortable. That is the only approach identified that
addresses the constraint rather than working around it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
2eba68a013 |
Reproduce the +1 descriptor offset in simulation (bba_top T9)
First time one of this session's RX faults has been reproducible on this side
rather than inferred from a console.
T9 performs EXI DMA reads from the SPRAM ring WHILE the ethernet side writes
frames into it. No earlier test did: T6 waits for the frame to complete before
reading, so reads and writes never overlapped -- and ETH has ABSOLUTE priority
in SPRAMArbiter, so only under that overlap can an EXI read be preempted.
EXI_HALF=8 6/6 byte-exact
EXI_HALF=2 6/6 byte-exact
EXI_HALF=1 2/6 byte-exact <-- the hardware rate
2-of-6 matches the console ratio closely (desc@+0=100 +1=343, 23% good), and
the corruption is exactly the hardware symptom -- ONE EXTRA BYTE inserted at
the front, shifting everything:
expected: 00 00 00 0C DE AD BE EF
got: 00 00 00 00 0C DE AD BE
That it is INSERTED rather than wrong is the useful part: an extra push into
the tx FIFO is a COUNT mismatch in the request/response path, not corrupted
SPRAM data. It only shows at 27 MHz because the read has to complete inside
the protocol's turnaround window while ETH preempts it.
This is the bug to chase first, ahead of the residual ACK retries: netboot
only works because its reader probes both offsets. Swiss does not probe -- it
would read a frame length of 1 -- so real networking on this adapter is broken
while our own tooling reports success.
bba_top now FAILS at EXI_HALF=1, deliberately. The failure is real and the
test is the reproduction; do not relax T9 to make the suite green. HALF=8 and
HALF=2 still pass for unrelated work.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
ada9378628 |
Restore the TX-done clears to the TX handler; shorten the ACK timeout
TIMING MEASUREMENT, which is the useful part. Raising the host timeout
0.1 -> 0.3 barely moved the retry count (110 -> 95) but tripled the cost of
each one: 16.9 s -> 40.1 s. If those were LATE ACKs, three times the deadline
would have all but eliminated them. They are LOST ACKs, so a SHORT timeout is
correct, not a long one -- the GC re-ACKs a duplicate block, so retrying early
costs nothing. Host default is now 0.05.
REGRESSION, mine, from
|
||
|
|
d67f111019 |
Don't advance the write address across TXDATA; deepen tx_bytes to 2048
Two coupled fixes, both exposed by batching TXDATA writes in the test app. 1. eff_addr advanced on EVERY data byte of a register-space write. But TXDATA (0x48) is a FIFO PORT, not a register: a whole frame is written to that one address. So an 8-byte write put byte 0 in TXDATA and bytes 1..7 into 0x49+, which the switch silently ignores. The frame was an eighth of its declared length, TXFrameDrain blocked waiting for the rest, and TI never set (hardware: txto=16, "no ACK after 20 tries"). SWISS WOULD HAVE HIT THIS. It writes an entire frame with bba_outs to BBA_WRTXFIFOD -- one transaction, one address -- so its TX would have delivered ONE BYTE per frame. The path was never exercised because the test app wrote TXDATA one byte per EXI transaction, giving every byte a fresh header at 0x48. Every other register must still advance, so that bba_outs can write PAR0-5 or MAR0-7 as a block; the exception is TXDATA only. 2. tx_bytes was 16 deep. SPI cannot back-pressure -- there is no way to tell the GC to wait -- so BBARegisterFile silently drops bytes when w_rdy is low, which makes the depth the only thing between a drain stall and a corrupted frame, not a buffering preference. 16 bytes is ~4.7 us at the EXI byte rate, and TXFrameDrain shares the W5100 bus arbiter with RX and the socket-3 UDP test, so any longer stall truncated a frame mid-flight. With (1) fixed, Swiss streams up to 1514 bytes in a single transaction, so 16 could never have held. Now 2048: a whole maximum-size frame plus margin, so a frame cannot be truncated even if the drain makes no progress for the entire transaction. Costs 3 of 27 free BRAMs (ICESTORM_RAM 3/30 -> 6/30, LC 68% -> 70%). Dennis spotted that the 8-byte workaround could not survive whole-frame writes, which is what prompted fixing the depth rather than shipping it. bba_tx_bytes() consequently streams 64-byte chunks (bba_write()'s buffer limit), so a 60-byte ACK is ONE transaction instead of sixty. T8 covers both and is mutation-tested: restoring the increment gives "1/8 bytes MISMATCH -- got [17], want [17, 34, 51, ...]". It now writes 64 bytes so it exercises the deepened FIFO too. --seeds 8: clk 27.6-29.4 MHz, EXI 51.2-64.7 MHz, 8/8 PASS. Best seed 1 (clk 29.35, EXI 60.80), flashed. STILL NO FLOW CONTROL on the TX path: a frame larger than 2048 would drop silently rather than error. Fine for ethernet's 1518 ceiling; revisit if the multiworld socket work ever wants larger payloads. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e9b1a75bfe |
Discard SPRAM responses with no request outstanding
SPRAMArbiter gives ETH writes ABSOLUTE priority -- eth_wr_ready is asserted
combinationally whenever eth_wr_valid is high, so the write port is never
stalled. Two consequences worth recording:
- The T5 stall model added to rx_frame_assembler does NOT reflect hardware.
Its value was eliminating a hypothesis, not modelling reality.
- EXI READS are what get preempted, so a read response can be delayed --
including past the end of the transaction that asked for it.
The register file did not guard against that. The response handler pushed
whatever arrived, and HEADER0 reset `outstanding` and `sp_req` but not
`sp_pending`. So a late response was pushed into tx_fifo anyway, a held byte
leaked across the transaction boundary, and `outstanding - 1` from zero
underflowed a 4-bit counter to 15 -- which then broke the prefetch bound for
the rest of that transaction.
Now guarded on (outstanding != 0), and sp_pending is cleared in HEADER0.
WHAT THIS DOES NOT CLAIM. I cannot demonstrate that this causes the +1
descriptor offset seen on hardware (17 of 22 frames, varying frame to frame).
A test written in bba_register_file passes with AND without the guard --
checked both ways -- because that testbench models TX as a capture list per
transaction, with no persistent tx FIFO and no header drain pops, which is
precisely the mechanism by which a leaked byte would shift the next read. A
test that cannot fail is worse than no test, so it is deleted rather than
banked, and the gap is documented where it was.
THE MISSING TEST, recorded in bba_register_file.py: a bba_top-level test that
performs an EXI DMA read from the SPRAM ring WHILE the ethernet side writes
frames into it. Only under that overlap can a response be delayed past its
transaction. No simulation does this today, which is why the offset is still
formally unexplained -- the fix here is a real latent bug, not a proven cause.
--seeds 8: clk 28.1-30.3 MHz, EXI 54.9-62.3 MHz, 8/8 PASS. Best seed 7
(clk 30.26, EXI 61.09), flashed. Gateware only -- .dol unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
2aa50a7cc3 |
Reset the assembler's own write pointer on NCRA reset too
Hardware: "timeout: no match (0 seen, 0 bad desc, 0 lapped)". Zero frames
reached the ring at all -- a regression from
|
||
|
|
f69b4d084a |
Model write-port stalls in the assembler testbench (T5)
eth_wr_ready was held constantly HIGH by every test this file ever had, which
is a serious blind spot: on hardware the SPRAM arbiter stalls that port
constantly, because the GC reads the ring over EXI while frames are written
into it. Any address bookkeeping that depends on ready timing is invisible
until ready actually goes low.
T5 repeats T2's 300-byte frame with ready asserted 1 cycle in 3 and requires
the descriptor and body offset to be IDENTICAL to the unstalled case.
Result: contention does NOT reproduce the one-byte body offset seen on
hardware. Under stalls the descriptor is still [0,0,1,48] and all 300 body
bytes land at base+4. So that hypothesis is eliminated, which was the point
of writing it.
Two harness faults found and fixed while building it, both of which produced
false results rather than no result:
- rx_irq and rx_overflow are 1-cycle pulses, and rx_overflow fires on the
very tick that consumes the EOF byte -- inside send_frame. Polling for it
afterwards missed it entirely, so a correctly DROPPED frame looked like a
stall. Both are now latched in the tick helper.
- send_frame read rx_ready BEFORE tick() applied the next eth_wr_ready.
rx_ready is combinational on it, so the loop was a cycle out of phase with
the accept the DUT actually made: it waited on a byte already taken, and
reported "rx_ready never asserted" for a frame that had stored correctly.
Ready is now applied, then read, then clocked.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
92f7d716d9 |
Add RBFI ring-overflow protection: drop frames instead of overwriting
RXFrameAssembler ignored RRP entirely, so it lapped the reader: a traffic
burst overwrote pages the GC had not read, and the "descriptor" the driver
then parsed was frame data from whatever had replaced it. That is where the
hardware's 00 00 00 01 came from -- not a dropped write.
This is a product bug, not just a netboot one. Until now ANY burst could
corrupt a frame Swiss was midway through reading, with nothing to indicate it
had happened.
The assembler now takes the GC's read pointer and computes free space as
(rrp - rwp - 1) mod 15. On a frame start with fewer than 6 free pages -- the
worst case for 1518 bytes plus a 4-byte descriptor -- it enters DROP_FRAME,
discards to EOF, and pulses rx_overflow, which sets IR[7] (RBFI). That is the
bit the GC's IMR = 0x86 already enables, so a driver that loses a frame is
told so instead of reading corruption.
Deliberate details:
- rx_ready stays HIGH while dropping. Stalling the ethernet master instead
would back-pressure into the W5100's own buffer and turn a dropped frame
into a wedged receiver.
- Six pages are reserved up front because a frame's length is not known
until EOF. Starting with less risks running into unread data partway
through, which is worse than refusing cleanly.
- have_room is REGISTERED, not combinational. Feeding a mod-15 subtract and
comparator into the FSM branch cost real depth in `sync`, the domain with
the least margin: clk fell 27.8-32.2 -> 27.0-28.0 MHz. Registering it
restored 28.4-31.4. One cycle of staleness is harmless -- rwp moves only
at end-of-frame and rrp only when the GC writes it.
T4 proves the property that matters, and the negative control REPRODUCES THE
HARDWARE FAULT IN SIMULATION: with the check removed the unread page reads
back as [245, 248, 251, 254, ...] -- frame data where a descriptor belongs.
First time one of today's RX faults has been reproducible on this side rather
than inferred from the console. T4 also checks the receiver un-latches once
RRP advances, so a full ring cannot kill RX permanently.
Fixed a test artifact in the same pass: rx_overflow fires on the very tick
that consumes the EOF byte, inside send_frame, so polling for a 1-cycle pulse
afterwards missed it and a correctly-dropped frame looked like a stall. Both
pulses are now latched in the tick helper.
netboot also clears IR (including RBFI) at init so a stale bit is not
inherited from an earlier run.
--seeds 8: clk 28.4-31.4 MHz, EXI 55.2-62.7 MHz, 8/8 PASS. Best seed 4
(clk 31.39, EXI 61.85), flashed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
86cd5d1abd |
Fix two dropped-write bugs in RXFrameAssembler's SPRAM handshake
Both from one mistake: eth_wr_valid is scheduled with m.d.sync and so is LOW
for the whole first cycle in a state, while eth_wr_ready was tested
immediately -- and `ready` is usually already high on entry. Both
valid.eq(1) and valid.eq(0) then fired in the same cycle, the later won, the
write was never issued, and it was dropped SILENTLY.
1. WRITE_LEN_HI dropped the descriptor's HIGH length byte, leaving it at its
placeholder 0x00. Hardware read a descriptor of 00 00 00 01 -- a 1-byte
frame length, impossible for ethernet -- and rejected the ring.
2. WRITE_PLACEHOLDER counted a phantom first write, advancing wr_addr without
issuing one. Only THREE placeholder bytes were written and wr_addr ended
at desc_base+3, so the frame body started at descriptor byte 3 and EVERY
RECEIVED FRAME WAS SHIFTED ONE BYTE EARLY. The GC's ethertype test then
read the wrong offset and silently dropped every frame: the host saw "no
ACK" while the ring filled and RWP advanced normally.
Both now advance on `valid & ready`, and the body offset is set explicitly
(desc_base + 4) rather than relying on an incremented pointer, so it cannot
drift with the handshake again.
WHY NO TEST CAUGHT THIS, which is the more important half:
- The testbench drove the RX stream OPEN-LOOP -- one byte per tick, never
checking rx_ready, which is low through IDLE and the whole placeholder
write. It silently lost bytes, including the SOF byte. A real master
must hold each byte until accepted.
- It only ever asserted on rwp and rx_irq. It declared a spram_writes list
and never filled it, so NOTHING had ever checked what was written.
- Every frame it sent was short, and a short frame's high length byte is
legitimately zero -- so a dropped high byte was indistinguishable from a
correct one.
The testbench now respects back-pressure, records every write, and checks the
descriptor bytes and the body offset. T2 sends a 300-byte frame specifically
so the high length byte is non-zero. Mutation-tested: restoring either bug
fails T2 with got [0,0,0,48] want [0,0,1,48] -- the same shape as the
hardware's 00 00 00 01 -- while T1 (short frame) still passes, which is
exactly the blind spot that let this ship.
Bring-up's 342-byte DHCP frame read a correct 00 00 01 5A only because the
arbiter happened to hold `ready` low on that entry cycle; the domain merge
and the SPRAM response-FIFO removal changed that timing.
--seeds 8: clk 27.8-32.2 MHz, EXI 54.9-64.5 MHz, 8/8 PASS. Best seed 8
(clk 32.22, EXI 62.25), flashed. Gateware only -- the .dol is unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
83a62d60a0 |
Reset the register file's ring pointers on an NCRA reset
An NCRA reset returns RXFrameAssembler's write pointer to page 1, but the
register file's COPY of RWP was never reset -- it kept whatever the last frame
left. So after any reset the GC reads a stale RWP pointing at a page the
assembler is not using, and its first RX after a reset reads the wrong page.
CLAUDE.md has stated the intent all along ("NCRA = 0x01 ... resets W5500 +
SPRAM ptrs"); the register file simply never did it.
This is NOT only a netboot problem. Any driver that resets and then trusts
RWP hits it, Swiss included. It stayed invisible because the suite's RX test
only checks that RWP CHANGES, never that it points at the right page, and
t_dma used to read a hardcoded 0x100 so it never consulted RWP either. It
surfaced through netboot, whose RRP = RWP sync landed on a stale page and
found garbage where a descriptor should be.
T1c covers it, and BOTH halves have teeth. My first version did not: RWP read
[1] with or without the fix because nothing in the testbench had moved it. It
now drives the assembler's RWP update path to page 9 and writes RRP to 7
first, so the negative control fails on both:
T1c RWP after NCRA reset: expected [1], got [9]
T1c RRP after NCRA reset: expected [1], got [7]
A test that passes whether or not the fix is present is worse than no test.
Gateware only -- the test app needs no change and the existing .dol is
compatible: after its reset it reads RWP, now gets 1, and writes RRP = 1.
--seeds 8: clk 28.5-32.2 MHz, EXI 51.3-63.4 MHz, 8/8 PASS. Best seed 2
(clk 32.19, EXI 63.23), flashed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
3db4ab0851 |
Reset both SPRAM ring pointers to page 1, not 0
The DMA descriptor test reported "RRP page 0 outside ring pages 1-15" on a board whose RX path was working (t_rx passed: RWP advanced and RI fired), so the pointer itself was wrong. RXFrameAssembler's write pointer resets to _RX_PAGE_FIRST = page 1 -- the ring is pages 1-15 and page 0 holds the register region -- but the register file's r_rwp_lo and r_rrp_lo were plain Signal(8) and reset to 0. Two consequences: the register file and the assembler disagreed about where the ring starts, and a driver reading its first frame from RRP=0 gets the REGISTER page instead of the frame, which is at page 1. It hid because RRP == RWP == 0 looks self-consistently empty, so nothing complained until something actually read the pointer. RRP is driver-owned (the GC writes it after consuming frames) but its RESET value is ours, and RRP == RWP == 1 is the correct initial state: empty, pointing at the ring start. T1b locks down both reset values AND imports the assembler's constant to assert it matches, because two copies of a page number in two modules is exactly how the off-by-one got in. --seeds 8: clk 28.x-32.2 MHz, EXI 56-60 MHz, 8/8 PASS. Best seed 8, flashed. NOT fixed here, and worth separate attention: nothing in the gateware consumes RRP, so RXFrameAssembler can overwrite pages the GC has not read yet -- there is no ring-overflow protection (the real BBA has RBFI for this). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7587636b48 |
Answer the device-ID query from the bit engine, not the register file
bba_devid fails on a console with 00 04 02 02 -- the ID correct but one byte
late -- and it fails in its REAL shape, with the 16 us gap and exactly four
bytes clocked, so this is not the t_raw_id probe artefact. The register suite
passes, so register-space reads are fine; only command space is affected.
Cause is latency, not phase. A command-space header is two bytes, so the
reply must be in the tx FIFO by the LAST RISING EDGE of the header transfer:
one byte time, 296 ns at 27 MHz. The round trip is rx AsyncFIFO (~125 ns) +
a sync cycle + tx AsyncFIFO (~110 ns) = 280-320 ns, marginal by construction.
HEADER0 already pushed in the same cycle it consumed byte 0, so the FSM side
was already as early as it can be.
The inter-transfer gap cannot rescue it: the load happens on that last rising
edge and the clock is PAUSED afterwards, so no edge exists during the gap for
anything to update. That is why bba_devid's 16 us gap makes no difference.
Pre-pushing the ID at CS-fall does not work either -- the engine's
unconditional per-byte pops would eat it, and the header length is not known
until byte 0 has been decoded, which is the latency in question.
So take the round trip out of the path. The device ID is a hardwired constant
and belongs to the physical interface rather than to the register file, and
byte 0 == 0x00 identifies the query exactly (bit 7 clear = command space, bit
6 clear = read, reg 0). SPIMode3Slave now recognises it and answers
04 02 02 00 from the EXI clock domain, with no CDC at all. The only thing it
needs from sync is id_phase, a single bit that changes once, crossed with an
FFSynchronizer.
The register file's ID path is REMOVED rather than kept as a fallback: left
in, it would push bytes that a 2-byte header has too few pops to drain, which
is exactly how an earlier version made the NEXT enumeration come back a slot
late.
Tests follow the responsibility:
- spi_mode3_slave T7: the query returns 04 02 02 00 with the tx FIFO
deliberately feeding 0x00, so only the fast path can be answering.
- spi_mode3_slave T8: and it is GATED -- with id_arm low, 0x5A comes
through from the FIFO. Without this the fast path would hijack address 0
forever and the GC could never read NCRA back after its own reset.
- bba_register_file T1: retargeted to assert the FSM pushes NOTHING for an
ID query, which is the property that stops stale bytes corrupting the
following transaction.
Note what these tests do NOT establish: bba_top already passed the device ID
at EXI_HALF=1 before this change, because simulation cannot reproduce a
296 ns window miss -- the sync domain's latency relative to the EXI clock in
sim does not match silicon closely enough. T7/T8 prove the fast path works;
the evidence that it is NECESSARY is the hardware result plus the paused-clock
argument above.
--seeds 8: clk 28.4-31.0 MHz, EXI 56.7-64.4 MHz, 8/8 PASS. Best seed 2
(clk 30.98, EXI 59.47), flashed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
7ac0cbe66c |
Launch MISO a full cycle early instead of chasing delay
PHASE_METER on a console settled what two rounds of delay-cutting could not.
Per-speed dump, first two slots per EXI speed index, with the register file
and both byte FIFOs out of the MISO path:
0:8080 1:8080 2:8080 3:8080 4:8080 5:C040
Perfect to 16 MHz, one bit late at 27 MHz. The 8-slot dump at 27 MHz read
C0 40 40 40 C0 40 40 40 -- a UNIFORM one-bit lag (ones at bits 1, 9, 17, 25,
33, ...), with extra highs at bits 0 and 32 where libogc splits the read into
two 4-byte transfers and MISO tri-states to its pull-up in the gap. The ID
dump agreed exactly.
Decisive detail: ~9 ns had already been removed from the fabric output path
(the EXI_J4 pad merge, then the CS mux, then the entire fabric route by moving
the output into an SB_IO register) and the hardware result stayed BYTE
IDENTICAL each time. Nine nanoseconds out of an 18.5 ns budget cannot change
nothing if propagation were the whole story. What is left is the part no RTL
change can reach: input pad + GLOBAL CLOCK NETWORK INSERTION + IOB clock-to-Q
+ output pad, plus the host's own setup. There was no fabric delay left to
cut, so cutting delay was the wrong move.
So double the budget instead. MISO is now launched on the RISING edge
carrying the bit the host will sample at the NEXT rising edge, so the whole
path gets a FULL period (37 ns at 27 MHz) rather than half. This is safe in
both directions: the insertion delay that caused the problem now covers the
host's hold time on the previous bit, and a full period covers its setup.
- tx_out is clocked on the rising edge and loads at rx_bit == 7, one
bit-time earlier than a falling-edge shifter loading at rx_bit == 0.
- tx_hold and the entire exiclk_n domain are gone: the engine now has ONE
clock edge and ONE phase reference.
- synth.py's SB_IO output register moves to NEG_TRIGGER=0.
- Byte alignment is unchanged -- T2/T5 still land A5 and 3C in the same
slots. Only the sub-bit launch timing moved.
Testbench consequence, and it matters: bba_top's master sampled MISO AFTER
raising the clock. With a zero-delay simulation and a rising-edge launch that
reads the NEXT bit, so the suite would have reported everything one bit early
while hardware was correct. Both masters now sample in the CLK-LOW phase --
the value the host latches ON the rising edge -- which is also exactly what
the hardware notes say a probe must do.
T2 slot 0 now reads 0xFF (the CS-reset parking value that keeps MISO high
while deselected) instead of 0x00; it is a header byte the host ignores.
T6 re-anchored to the rising edge and still proves miso_next predicts
spi_miso bit for bit, so the SB_IO register stays equivalent to the fabric
model rather than an untested divergence.
--seeds 8: clk 28.9-32.1 MHz, EXI 62.1-65.4 MHz, 8/8 PASS. The EXI numbers
rise sharply (41.6-49.7 -> 62.1-65.4) because the setup path into the IOB
register is now posedge->posedge against a FULL period instead of
posedge->negedge against a half -- the same doubling, visible internally.
Best seed 3 (clk 32.09, EXI 62.78), flashed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
2f081a3e69 |
Drive MISO from an SB_IO output register
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> |
||
|
|
5f7f20db43 |
Cut the MISO output path: drop EXI_J4 and the CS mux; rank seeds by it
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>
|
||
|
|
d35688390e |
Take the TX bit phase from the rising-edge counter, not a falling-edge one
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>
|
||
|
|
91c3bf0ece |
Clock the EXI bit engine from the EXI pin: timing closes on all 8 seeds
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>
|
||
|
|
02239466fb |
Merge the exi and sync domains into one
bba_top wired ClockSignal("sync") to ClockSignal("exi"), so the two "domains"
were always the same physical clock net. Everything between them -- six
AsyncFIFOs and three PulseSynchronizers -- was pure latency and logic for a
crossing that did not exist.
* the six AsyncFIFOs become plain SyncFIFOs
* the three PulseSynchronizers become wires
* BBARegisterFile and ExiCapture move from m.d.exi to m.d.sync
* the exi ClockDomain is gone; HFOSC drives sync directly
The SPRAM request write port is also combinational now. A registered w_en
costs a 24 MHz cycle, and the protocol's turnaround byte (~296 ns at 27 MHz)
has no cycles to spare.
Passing: bba_register_file, spram_arbiter, rx_frame_assembler, tx_frame_drain,
w5100_parallel_master, uart_shell, status_panel. bba_top at EXI_HALF=1
(27 MHz, the hardware rate) passes device ID, PAR read/write, NWAYS, RWP and
the 34-byte TX frame.
STILL OPEN: the SPRAM/DMA read is one byte late. Removing the CDC was not
enough -- the remaining round trip is the rx crossing (exiclk->sync, a REAL
CDC, ~125 ns) plus FSM decode plus the spram_req FIFO, arbiter, SPRAM access
and spram_rsp FIFO, which still exceeds the turnaround byte. Next step: now
that it is all one domain, drop the two SPRAM FIFOs entirely and give the
register file a direct request/grant port on the arbiter -- that should take
the round trip to ~2-3 cycles and fit.
exi_capture and exi_j4 testbenches still assert removed behaviour (the CS-fall
flush and the old MISO slot timing) and need rewriting for the clocked engine.
Still WIP -- do not flash.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
da21a080d9 |
TX passes at 27 MHz; remaining DMA offset traced to needless exi/sync CDC
The TX failure was a TESTBENCH artifact, not a design bug. Between
transactions the bench raised CS for one HP -- 18.5 ns at EXI_HALF=1 -- but
cs_active reaches the exi domain through a 2-stage FFSynchronizer at 24 MHz,
so the register file never saw one transaction end and swallowed the next
one's header as write data. A real host leaves microseconds between
exi_deselect() and the next exi_select(). With a realistic gap the 34-byte TX
frame now matches byte-for-byte at the hardware rate.
At EXI_HALF=1 (27 MHz) bba_top now passes device ID, PAR read/write, NWAYS,
RWP and TX. The one remaining failure is the SPRAM/DMA stream, offset by
exactly one byte.
Root cause of that offset, and it is not architectural: bba_top.py:171 does
m.d.comb += ClockSignal("sync").eq(ClockSignal("exi"))
so exi and sync are LITERALLY THE SAME CLOCK NET. Every AsyncFIFO between
them is pure overhead. The SPRAM round trip pays two needless CDC crossings
(~6 cycles, ~250 ns) on top of the arbiter and SPRAM access, giving ~375 ns
against the ~170 ns the protocol's turnaround byte actually leaves once the rx
crossing is subtracted. Register reads are unaffected because their response
is immediate; only SPRAM is late.
Fix direction: merge the two domains and replace the exi<->sync AsyncFIFOs and
PulseSynchronizers with plain SyncFIFOs/signals. That removes the latency,
drops a lot of logic, and cannot change behaviour because the clocks are
already identical.
Known failing, both testing removed behaviour rather than new bugs:
* exi_capture's own testbench asserts the CS-fall flush and the old MISO
slot timing; the flush no longer exists (header pops drain the FIFO).
* exi_j4's T3 re-enumerate is off by a byte.
Still WIP -- do not flash.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
a3b3e052bc |
WIP rewrite: clock the EXI bit engine from the EXI clock pin
Replaces 54 MHz oversampling with a slave clocked by EXI CLK itself:
exiclk posedge sample MOSI (Mode 3 samples on edge 2)
exiclk_n negedge drive MISO (must settle between the master's samples)
CS_N async reset for both
EXI CLK (pin 44) is at die (6,0) = global buffer net 6 on the 5k-sg48, so it
can drive a real clock network with no board change.
This removes the capture domain entirely. Bit-level timing no longer
crosses a clock boundary -- MISO is clock-to-Q instead of 3 ticks of a foreign
clock -- and the only crossings left are the byte FIFOs, which have a whole
byte-time (~296 ns) of slack rather than a fraction of a bit.
AsyncFIFO needs a posedge clock for its internal reset CDC, so the FIFO sides
use reset-less POSEDGE domains on the same pin; the engine pops on the posedge
and hands the byte to the negedge output stage through a register. The FIFOs
must not see CS: resetting one side of an AsyncFIFO desynchronises its gray
pointers.
Two bugs found and fixed along the way:
* one pop per byte boundary is all that is needed -- pops during the header
drain whatever the previous transaction left (replacing the CS-fall flush)
and the turnaround-byte push lands in data slot 0. No slot-threshold or
header-length logic at all.
* rx_byte/rx_valid had to become COMBINATIONAL. Registered, they need one
more clock edge, and after a transaction's final bit the clock stops and
CS rises -- so the last byte of every transaction was lost (a 4-byte write
landed as DE AD BE 00, a 34-byte TX as 15 bytes).
At the HARDWARE rate (EXI_HALF=1, 27 MHz) the device ID, NWAYS, PAR
read/write and the W5100->SPRAM RX path now all pass -- none of which has ever
worked at 27 MHz before. Testbenches now drive the SPI clock with real
delays, since there is no capture domain to count ticks against.
STILL BROKEN: the SPRAM/DMA stream is offset by one byte (its round trip
exceeds the single turnaround byte), and TX lands 15 of 34 bytes. Do not
flash this yet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
afc10d7e5c |
fix(spi): sample MOSI on the RISING edge -- real Mode 3, not Mode 2
The docs call this 'Mode 3 (CPOL=1, CPHA=1)' and then describe sampling MOSI
on the FALLING edge. Idle-high plus sample-on-falling is Mode 2. Real Mode 3
samples on the second edge of each cycle, which with an idle-high clock is the
RISING one, and that is what the GameCube does.
Sampling on the same edge the GC drives MOSI latched the PREVIOUS bit, so
every byte arrived one bit late in both directions, at every clock speed:
the 0x80 register-space header byte was read as 0x00, which decodes as a
command-space device-ID query -- so NWAYS reads returned the device ID and the
speed sweep failed at every index. Hardware raw dump, first transaction after
an FPGA reset:
00 00 02 01 01 00 00 00 02 01 01 00 = 04 02 02 00 >> 1
The Pi probe never caught it because that script was written to match this
device, and every simulation testbench drives MOSI many capture ticks before
toggling the clock, so MOSI is stable at BOTH edges and the convention cannot
be distinguished. The bba_top master now models a true Mode 3 master (drive
on falling, sample on rising) so the suite can tell the difference.
SPIMode3Slave(mode2=True) restores the old behaviour for the Pi/J4 probe.
Known remaining issue: at EXI_HALF=1 (27 MHz, 2x oversampling) reads are still
shifted, because the 2-stage input FFSynchronizer costs 2 capture ticks which
at 27 MHz is a whole bit. That is a separate fault and only bites at 2x.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
7df0c97c78 |
sim: make the EXI clock rate configurable; default was 8x too slow
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> |
||
|
|
399fbf02a4 |
fix(exi_capture): TX pop threshold must follow the header length
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> |
||
|
|
2458884160 |
Rework the EXI decoder to the real GC<->BBA protocol
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>
|
||
|
|
5f60c011cb |
fix(w5100): clear stale Sn_IR bits when INT_N asserts with no RX data
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> |
||
|
|
e80b7485d5 |
fix(w5100): actually drive RSTn on init so a wedged chip can recover
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> |
||
|
|
b42dc65796 |
fix(w5100): wait 83ms after software reset, not 1ms
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> |
||
|
|
80957f35db |
fix(tx): queue the frame length at TWD_HI so the drain runs concurrently
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> |
||
|
|
9a7eb54801 |
exi_j4: shared AND-merge helper + enumeration testbench
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> |
||
|
|
c0abd45518 |
synth: optional EXI mirror on the J4 debug header (EXI_J4=1)
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> |
||
|
|
3d37e4ba6d |
Hardware bring-up: first UDP packet transmitted and captured
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> |
||
|
|
394b677c8f |
fix(w5100): revert buffer base to 0x4000/0x6000 — IDM decodes only 15 bits
Reverts the 0x8000/0xC000 change from |
||
|
|
5d9880f9c5 |
Add W5100 read-only register-dump probe (bring-up)
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> |
||
|
|
9c4bc7a61c |
fix(w5100): unlock NETLCKR before writing GWR/SUBR/SHAR/SIPR (W5100S)
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> |
||
|
|
59948812f3 |
Bring-up: W5100 bus self-test, eFuse OVLO bug, Pi SPI EXI host
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> |
||
|
|
162dbf7d5f |
fix(w5100): W5100S TX/RX buffer base is 0x8000/0xC000, not 0x4000/0x6000
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> |
||
|
|
009a643698 | Moved all components to F.Cu | ||
|
|
c16afb6eea |
Add integrated UART UDP bring-up shell (socket 3) + capture critical-path fix
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> |
||
|
|
7d7ab34bff | Fixes review comments. | ||
|
|
5828d6a388 | Finished the board including the connector and mousebites |