Skip to content

feat(mii): store-and-forward TX path on a single packet FIFO + HW-validated deadlock fixes - #1

Merged
lcapossio merged 9 commits into
mainfrom
feat/mii-tx-store-forward
Jul 13, 2026
Merged

feat(mii): store-and-forward TX path on a single packet FIFO + HW-validated deadlock fixes#1
lcapossio merged 9 commits into
mainfrom
feat/mii-tx-store-forward

Conversation

@lcapossio

Copy link
Copy Markdown
Owner

Summary

Reworks the MII transmit path onto a store-and-forward, single packet FIFO
(mii_tx_saf), replacing the old cut-through mii_if TX. The framer only starts
a frame once the whole frame is committed to the FIFO, so it can never underrun
and the AXIS input may bubble (deassert tvalid mid-frame) with no wire error.

Along the way this adds the on-hardware debug path used to root-cause two TX
deadlocks on an Arty A7-100T, fixes both, and reclaims the now-dead mii_if TX
FIFO BRAM. gmii_cdc (RGMII path) is likewise unified onto an EOF-sideband
packet FIFO with the separate length FIFO removed.

What's included

New RTL

  • rtl/mii_tx_saf.v — store-and-forward MII TX core: one async frame FIFO
    ({tlast,data}) + media-clock framer (preamble / SFD / CRC-32 / pad / FCS /
    IFG). Gray-coded committed-frame counter gates frame start → no underrun,
    tolerates AXIS bubbles.
  • rtl/axil_arb2.v — 2:1 AXI-Lite arbiter for the JTAG debug path.
  • gmii_cdc — unified TX on an EOF-sideband packet FIFO; dropped the length
    FIFO.

Bug fixes (both found and reproduced on hardware)

  1. Committed-frame counter wrap (d79d1d0). A 4-bit counter aliased to a
    false "equal" once 16 frames backed up in the 4 KB FIFO, parking the framer
    in idle. Fixed by widening it to FIFO_ADDR_WIDTH+1 bits. Regression
    tb_mii_tx_saf_burst_stall.
  2. Oversized / uncommitted-frame wedge (e6d8a95). The write side committed
    only on tlast with no bound on the uncommitted run. A no-tlast run
    reaching the FIFO depth (an oversized frame, or frames merged by a dropped
    tlast upstream) filled the FIFO with uncommitted data: frame_pending never
    rose, the framer parked in idle, wr_full stuck, and the whole TX path
    deadlocked permanently. Fixed by capping the in-flight run at MAX_FRAME
    (force a synthetic EOF, drop the runaway tail) — since MAX_FRAME < FIFO
    depth, a permanent wedge is now structurally impossible. Regression
    tb_mii_tx_saf_oversize.

Debug infrastructure (dc1b307)

  • IP_ADDR CSR (0x40) — demo L3 IPv4 made AXI-writable (retarget the board's
    IP over JTAG without a rebuild).
  • SAF_DBG CSR (0x94, RO) — mirrors the mii_tx_saf framer/FIFO state into
    the sys domain (committed/drained frame counts, rd_empty, framer state) for
    on-hardware diagnosis. This is what pinned down bug verify: cocotb datapath suite + 13 RTL fixes across gmii_cdc / eth_mac_rx / mii_tx_saf #2.
  • EJTAG-AXI bridge + axil_arb2 — CSR access over JTAG (USER4) alongside the
    ELA/EIO.

Resource cleanup (67b71d1)

  • mii_if gains a TX_ENABLE parameter (default 1). The MII branch in
    eth_mac_sys sets TX_ENABLE=0 (there mii_tx_saf drives the pins), so the
    dead mii_if TX FIFO is no longer instantiated. Block RAM 6.5 → 5.5 tiles;
    the TX path now has exactly one physical FIFO.

Testing

  • Simulation: full regression green — 40 test groups, iverilog -Wall and
    Verilator lint clean. New TBs: tb_mii_tx_saf, tb_mii_tx_saf_burst_stall,
    tb_mii_tx_saf_oversize, tb_axil_arb2.
  • Hardware (Arty A7-100T, 10/100 MII):
    • ARP + ICMP + UDP-stats replies verified; ELA-confirmed TX pin waveform.
    • The 64-byte UDP echo flood that previously wedged the board at 300 Mbps
      (echo loss ~99%, TX frozen) now sustains 300–400 Mbps at <1% loss, with
      SAF_DBG returning to clean idle after every burst and RX_ERR=0.
    • Timing met, WNS +0.360 ns.

Scope / notes

  • Default TX_ENABLE=1 keeps the legacy eth_mac.v wrapper and all MII TX
    testbenches unchanged.
  • No functional change to the RGMII datapath's external behavior; its TX FIFO was
    unified internally.
  • Docs updated: docs/registers.md (new CSRs), docs/missing_features.md
    (Known Issues → resolved), README.md, fpga/arty_a7/README.md, CHANGELOG.md.

lcapossio added 7 commits July 9, 2026 19:09
Replace the TX length FIFO + accumulator with a single 9-bit packet FIFO
carrying {EOF, byte}, gated by a gray-coded committed-frame counter. This is
the same store-and-forward scheme mii_if and gmii_cdc's own RX path already
use, so both PHY adapters share one boundary mechanism.

The paced drain FSM is preserved byte-for-byte (start_delay prefetch intact);
the frame now ends on the EOF bit. Resource-neutral: BRAM unchanged (EOF uses
the spare 9th bit).

Lint clean; full sim regression green incl. GMII-CDC 1G/100M/10M. Not HW-tested
(Arty is MII-only).
New self-contained MII transmit path: one async frame FIFO carrying
{tlast, data} feeds a media-side framer (preamble/SFD/CRC/pad/FCS/IFG + nibble
output). It starts only once a whole frame is committed, so it cannot underrun:
the AXIS input may bubble freely with no wire underrun and no gmii_tx_er,
unlike cut-through eth_mac_tx.

Validated by tb_mii_tx_saf (MII-TX-SAF): byte-exact framing and recomputed FCS
under gaps and a mid-frame stall. Regression green.
The MII path now transmits through mii_tx_saf (single frame FIFO + media-clk
framer) instead of the cut-through eth_mac_tx, so a bubbling TX AXIS source can
no longer underrun the wire. eth_mac_tx is kept on the RGMII path.

TX framing moves per-PHY into the generate branches; eth_stats byte/frame
counts, STATUS.tx_active, the TX-done IRQ, and inbound-PAUSE TX gating are
preserved (wire-byte semantics unchanged) via muxed tx_byte_ev/tx_frame_done_ev
and mii_tx_saf.tx_start_ok. The csum TB reconstructs the frame from the MII
nibbles. Full regression + both lint phases green.
IP_ADDR CSR (0x40) exposes the demo L3 IPv4 over AXI (default 192.168.137.200), so the host retargets the demo IP at runtime with no rebuild.

The FCAPZ_DEBUG build adds an fpgacapZero EJTAG-AXI bridge (JTAG USER4) behind a verified 2:1 AXI-Lite arbiter (axil_arb2) for live CSR read/write over JTAG; the ELA now also probes the MII TX/RX pins.

Used this to validate mii_tx_saf store-and-forward TX on Arty A7 end to end (ARP/ICMP/UDP replies, RX_ERR=0). Adds axil_arb2 and IP_ADDR tests; docs updated.
The 4-bit committed-frame counter wrapped once 16 frames backed up in the 4 KB FIFO, so frame_pending read 0 and the framer deadlocked with a full FIFO and TX frozen. Widen the counter and gray CDC to FIFO_ADDR_WIDTH+1 bits so it cannot wrap before the FIFO backpressures. New regression tb_mii_tx_saf_burst_stall (4/20 -> 20/20). A separate load-dependent FIFO-desync wedge remains; see docs/missing_features.md.
The write side committed only on tlast, with no bound on the uncommitted run. A no-tlast run reaching the FIFO depth (an oversized frame, or frames merged by a dropped tlast) filled the FIFO with uncommitted data: frame_pending never rose, the framer parked in idle, and TX deadlocked permanently.

Cap the in-flight run at MAX_FRAME (force a synthetic EOF, drop the runaway tail); MAX_FRAME < FIFO depth so the framer always progresses. Add regression tb_mii_tx_saf_oversize and a read-only SAF_DBG CSR (0x94) mirroring framer/FIFO state for on-hardware diagnosis.
On the MII branch mii_tx_saf drives the TX pins and mii_if is used only for RX, but its TX data FIFO was still instantiated and Vivado kept the XPM FIFO's RAMB36 even with the write side tied off.

Add a TX_ENABLE parameter (default 1, so eth_mac.v and the MII TX testbenches are unchanged) guarding only the u_tx_fifo instantiation, and set TX_ENABLE=0 in eth_mac_sys's MII branch. The framer, IOB and debug taps stay at module level and self-drive to idle. Reclaims one RAMB36 (6.5 to 5.5 tiles), timing still met (WNS +0.360 ns).
@lcapossio lcapossio self-assigned this Jul 11, 2026
SAF_DBG (0x94) was undocumented and the C header was missing both IP_ADDR (0x40) and SAF_DBG. Add both to registers.md, README, and emaczero.h, with the SAF_DBG bit layout.

CHANGELOG: drop the stale note claiming the mii_if TX FIFO is only tied off (it is now compiled out via TX_ENABLE=0, reclaiming the BRAM), and record the two mii_tx_saf TX-deadlock fixes plus the IP_ADDR/SAF_DBG CSRs and axil_arb2.
TX path now shows eth_mac_tx (RGMII) / mii_tx_saf (MII); the PHY interface block shows MII: mii_tx_saf + mii_if vs RGMII: gmii_cdc + rgmii_if, reflecting mii_if now being RX-only on MII. Regenerated the SVG from the JSON spec via the hdldiagzero renderer.
@lcapossio
lcapossio merged commit 15d0052 into main Jul 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant