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>
This commit is contained in:
@@ -78,22 +78,33 @@ All modules elaborate without errors and pass their unit tests. The full design
|
||||
synthesizes, places and routes on the iCE40UP5K, but **capture-domain timing is
|
||||
seed-dependent and only closes on a minority of seeds** — you MUST sweep.
|
||||
|
||||
Measured 2026-07-31, full build (`BBATopSynth(status_panel=True,
|
||||
uart_console=True)`, 47% LC), `python -m exi_bba.synth --seeds 8`:
|
||||
Measured 2026-08, DEFAULT build (`BBATopSynth(status_panel=True,
|
||||
uart_shell=True)` — the interactive UART shell + socket-3 UDP test, ~67% LC),
|
||||
`python -m exi_bba.synth --seeds 8`:
|
||||
|
||||
| domain | target | result |
|
||||
|---|---|---|
|
||||
| `clk` (exi/sync) | 24 MHz | 35.4–38.4 MHz — **PASS on every seed** |
|
||||
| `capture_clk` | 54.02 MHz | 49.7–55.7 MHz — **PASS on only 2 of 8 seeds** (3 and 6) |
|
||||
| `clk` (exi/sync) | 24 MHz | 27–30 MHz — **PASS on every seed** |
|
||||
| `capture_clk` | 54.02 MHz | 49.6–58.4 MHz — **PASS on 4 of 8 seeds** (3, 4, 7, 8) |
|
||||
|
||||
Best seed 3 = 55.69 MHz, margin +1.67 MHz (3%). Seed 1 — the default when you
|
||||
run without `--seeds` — **FAILS at 53.08 MHz**. So `python -m exi_bba.synth`
|
||||
with no arguments produces a bitstream that does not meet timing; always pass
|
||||
Best seed 4 = 58.36 MHz, margin +4.3 MHz (8%). Still seed-dependent — `python -m
|
||||
exi_bba.synth` with no arguments (seed 1) FAILS capture (50.96 MHz); always pass
|
||||
`--seeds 8` (or more) and flash the reported best seed from
|
||||
`build/seed<N>/top.bin`.
|
||||
|
||||
There is essentially no margin: assume any added logic breaks capture timing
|
||||
until a sweep proves otherwise. The earlier "~70 MHz, both PASS" figure in this
|
||||
**Capture-domain critical-path fix (2026-08):** capture used to close on only
|
||||
2 of 8 seeds at ~55 MHz *without* the shell. The binding path was the TX byte
|
||||
FIFO's read-enable gated by its own gray-coded ready
|
||||
(`tx_fifo.r_en = ... | (flushing & r_rdy)`) — a `consume_ptr → gray → r_rdy →
|
||||
flush → r_en → consume_ptr` loop in `exi_capture.py`. Replacing the r_rdy-based
|
||||
"drain until empty" flush with a FIXED-length drain counter (the FIFO is only
|
||||
`tx_depth` deep) removed the pointer feedback from `r_en`, dropping the path
|
||||
from 24.3 → 19.6 ns and lifting capture to 4/8 passing (best 58.4) EVEN with the
|
||||
shell integrated. **Do not re-introduce any `tx_fifo.r_rdy` dependence into
|
||||
`r_en` or the flush deassert** — see the comment in `exi_capture.py`.
|
||||
|
||||
There is little margin: assume added logic in (or near) the capture domain may
|
||||
break capture timing until a sweep proves otherwise. The earlier "~70 MHz, both PASS" figure in this
|
||||
file was wrong — it came from a sweep that silently never ran (see the
|
||||
`_prepared` note in `synth.py`) and from misreading nextpnr's PRE-routing
|
||||
placement estimate, which runs ~8 MHz optimistic.
|
||||
@@ -109,9 +120,11 @@ placement estimate, which runs ~8 MHz optimistic.
|
||||
| `SPRAMArbiter` | `exi_bba/spram_arbiter.py` | ✅ 3 tests |
|
||||
| `RXFrameAssembler` | `exi_bba/rx_frame_assembler.py` | ✅ 3 tests |
|
||||
| `TXFrameDrain` | `exi_bba/tx_frame_drain.py` | ✅ 2 tests |
|
||||
| `W5100ParallelMaster` | `exi_bba/w5100_parallel_master.py` | ✅ 5 tests (init/TX/RX vs bus model, incl. ring wrap) — **default eth back-end** |
|
||||
| `W5100ParallelMaster` | `exi_bba/w5100_parallel_master.py` | ✅ MACRAW init/TX/RX (T1–T5) + socket-N UDP send/receive (U1–U4) vs bus model, incl. ring wrap — **default eth back-end** |
|
||||
| `W5500SPIMaster` | `exi_bba/w5500_spi_master.py` | ✅ init/TX/RX vs SPI-slave model (alt back-end) |
|
||||
| `StatusPanel` | `exi_bba/status_panel.py` | ✅ 6 tests (heartbeat, stretched activity LEDs, debounced buttons, freeze) |
|
||||
| `UARTConsole` | `exi_bba/uart_console.py` | ✅ 7 tests (event log + 'r' reinit) — event-logger console (`--console` build) |
|
||||
| `UARTShell` | `exi_bba/uart_shell.py` | ✅ 8 tests (help/unicast/broadcast/reply-print/timeout/badip/backspace) — interactive UDP bring-up shell (**default build**) |
|
||||
| `EEPROMModel` | `exi_bba/eeprom_model.py` | ✅ 4 tests |
|
||||
|
||||
**Bring-up status panel (optional):** `BBATop(status_panel=True)` adds a
|
||||
@@ -140,13 +153,47 @@ python -m exi_bba.bba_register_file
|
||||
python -m exi_bba.spram_arbiter
|
||||
python -m exi_bba.rx_frame_assembler
|
||||
python -m exi_bba.tx_frame_drain
|
||||
python -m exi_bba.w5100_parallel_master # 5 tests: init, TX(+wrap), RX(+wrap)
|
||||
python -m exi_bba.w5100_parallel_master # T1-5 MACRAW + U1-4 socket-N UDP tx/rx
|
||||
python -m exi_bba.w5500_spi_master
|
||||
python -m exi_bba.status_panel # 6 tests: heartbeat/activity/buttons
|
||||
python -m exi_bba.uart_console # 7 tests: event log + 'r' reinit
|
||||
python -m exi_bba.uart_shell # 8 tests: shell cmds + reply/timeout
|
||||
python -m exi_bba.eeprom_model
|
||||
python -m exi_bba.bba_top # end-to-end EXI integration test (W5100 RX loop)
|
||||
```
|
||||
|
||||
### UART shell + UDP bring-up test (default flash build)
|
||||
|
||||
`BBATop(uart_shell=True)` adds an **interactive UART command shell** that shares
|
||||
the FT2232H channel-B UART pins with (and replaces) the event-log console, and
|
||||
drives a **UDP send/receive test on a second W5100 socket** (default socket 3)
|
||||
alongside the BBA's socket-0 MACRAW path. `synth.py` builds it **by default**
|
||||
(pass `--console` for the old event-logger instead). EXI/BBA keeps priority: the
|
||||
UDP test is the lowest-priority branch in the W5100 master's bus arbiter, so a
|
||||
GC frame always preempts a queued send/poll.
|
||||
|
||||
Shell (115200 8N1, `rebbarb> ` prompt):
|
||||
- `help` — list commands
|
||||
- `udp unicast <ip> [msg]` — send a UDP datagram to `<ip>:dst_port` (the W5100
|
||||
ARPs the target), then wait (bounded) for a reply
|
||||
- `udp broadcast [msg]` — same to 255.255.255.255 (limited broadcast; no ARP)
|
||||
- after a send, the shell prints `sent`, waits up to `reply_timeout_cycles`
|
||||
(~1 s) for a UDP reply on the socket, and prints `rx <payload>` or `timeout`
|
||||
(so it never hangs). Example: `udp broadcast Hello World` → a responder
|
||||
unicasts back → `rx <reply>` on the console.
|
||||
|
||||
Config is build-time via `synth.py` env vars (the board's own IP identity —
|
||||
match your LAN): `UDP_SOCKET` (1–3, default 3), `UDP_SRC_IP`, `UDP_SUBNET`,
|
||||
`UDP_GATEWAY`, `UDP_DST_IP`, `UDP_SRC_PORT`, `UDP_DST_PORT`. Receive on the PC
|
||||
with `nc -ul <port>` or `sudo tcpdump -n udp port <port>`.
|
||||
|
||||
**MACRAW + UDP coexistence is datasheet-confirmed** (W5100S §4.6: *"MACRAW Mode
|
||||
SOCKET 0 does not receive any Data Packet for other SOCKET"*; feature list:
|
||||
*"Support 4 independent SOCKETs simultaneously"*). All 4 sockets get a 2 KB
|
||||
RX + 2 KB TX buffer at reset (`RMSR/TMSR=0x55`), so socket-3 frames are stored
|
||||
in the chip independently — no extra FPGA buffer needed. Register addresses are
|
||||
still datasheet-from-memory — **confirm at hardware bring-up**.
|
||||
|
||||
### Pending work
|
||||
- **Synthesis/timing**: ⚠️ partially — synthesizes and P&Rs, `clk` closes with
|
||||
wide margin, but `capture_clk` closes on only **2 of 8 seeds** and the default
|
||||
|
||||
Reference in New Issue
Block a user