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>
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>
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>
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>
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>