Skip to content

verify: cocotb datapath suite + 13 RTL fixes across gmii_cdc / eth_mac_rx / mii_tx_saf - #2

Merged
lcapossio merged 8 commits into
mainfrom
feat/cocotb-verification
Jul 29, 2026
Merged

verify: cocotb datapath suite + 13 RTL fixes across gmii_cdc / eth_mac_rx / mii_tx_saf#2
lcapossio merged 8 commits into
mainfrom
feat/cocotb-verification

Conversation

@lcapossio

@lcapossio lcapossio commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a directed + randomized cocotb verification suite for the MAC datapath, wired into CI and build_and_test.py as its own phase. The suite is language-neutral (sources passed via sources=), so it retargets to a VHDL port later without rewriting the testbenches. Building it out found — and this PR fixes — six real RTL bugs across three modules, every one mutation-proven by the suite that caught it.

What's covered

Module Tests Checks
mii_tx_saf directed boundaries, heavy-bubble, randomized MII wire payload + recomputed FCS vs a commit/truncate/drop reference model
eth_mac_rx filtering, error paths, backpressure, randomized AXIS output + per-frame stat pulses vs an RX filter/error/stats model
gmii_cdc TX + RX across 1G/100M/10M, counter burst probes, randomized byte-exact CDC (identity model); paced-TX segmentation; committed-counter wrap

Bugs fixed (all mutation-checked)

gmii_cdc TX (paced store-and-forward):

  • Phantom-frame stall — in 100M/10M the read pointer parked on a frame's EOF word, so the next paced frame emitted a stale EOF byte, ended after one cycle, and orphaned the real frame in the FIFO — dropping every frame after the first. Close-out now advances past EOF in the paced modes only.
  • Committed-counter wrap — a 4-bit counter aliased at 16 backed-up frames, deasserting the store-and-forward start gate and wedging the media side before the 16 K FIFO filled. Widened to ADDR_WIDTH+1 (15) bits.

gmii_cdc RX (unpaced readout):

  • Multi-frame byte-drop — the first-word-fall-through FIFO's readout returned to idle at each EOF and re-ran its cold-start "align" pre-consume on the next frame, dropping byte 0 of every frame after the first. It now stays in the reading state across the EOF marker.
  • rx_frames_pending wrap — same class as above; a 4-bit counter aliased at 16 buffered frames and stalled the readout under a slow sys drain. Widened to ADDR_WIDTH+1 (13) bits.

Found by a follow-up module audit (all three mutation-checked):

  • eth_mac_rx multicast-hash filter gated on the wrong bit — the hash-admit term tested mac_chk[0] (LSB of the last dst octet) instead of mac_chk[40] (the I/G bit), so under MCAST_HASH_FILTER=1 a genuine group address with an even last octet was rejected and a unicast with an odd last octet + colliding bucket was admitted. New eth_mac_rx_mcast build/suite.
  • gmii_cdc TX error input droppedgmii_tx_er_in was never captured and gmii_tx_er_out was hard-wired 0. Added a per-byte error lane (9→10-bit FIFO word) that re-drives gmii_tx_er_out, symmetric with the RX rx_er path.
  • gmii_cdc paced last byte held 1 cycle — at 100M/10M the final byte occupied a single media cycle instead of the full pace interval; now it closes out at the next pace_tick, so the last byte gets its full period.

Robustness hardening (eth_mac_rx_robust suite, all mutation-checked):

  • RX-FIFO overflow no longer breaks framing — the readout reserves headroom so a frame's SOF and closing TLAST are never the dropped words. An overrun frame is truncated + terror'd but always starts and terminates cleanly, so it can't merge into the next frame.
  • Runt frames (< 64 wire bytes) are delivered with terror instead of as a clean frame with a garbage FCS.
  • byte_cnt saturates at 0x3FFF — a > 16383-byte frame can no longer wrap the counter and inject a phantom SOF that corrupts the following frame.
  • rx_er on a preamble/SFD byte is now reported (terror + stat_err_align); it was only sampled in S_DATA.
  • mii_tx_saf elaboration guard ($finish if MAX_FRAME >= FIFO_DEPTH) — this caught a real latent bug: eth_mac_sys fixed the MII SAF FIFO at 4096 while MAX_FRAME=9018, so a 4096–9018 byte MII TX frame would hit the permanent wedge the oversize cap exists to prevent. eth_mac_sys now derives that FIFO width from MAX_FRAME ($clog2), so it holds one whole frame and a standard (1518) build stays small — jumbo cost is paid only when jumbo is built.

(The two mii_tx_saf fixes the suite also re-validates — committed-counter wrap and the oversize/uncommitted-frame wedge — landed earlier; it now guards them as regressions. Also updated tb_gmii_cdc_10m, whose pacing assertion hard-coded the pre-fix 1-cycle last byte.)

Verification

  • All suites green: mii_tx_saf 5/5, eth_mac_rx 5/5, eth_mac_rx_robust 4/4, eth_mac_rx_mcast 3/3, gmii_cdc 10/10; plus the full legacy Icarus phase.
  • Every fix has a mutation proof: reintroducing each bug fails exactly the test that catches it (e.g. narrowing a widened counter back to 4 bits wedges its burst probe at the modulo boundary; reverting the RX re-align drops byte 0 of frame feat(mii): store-and-forward TX path on a single packet FIFO + HW-validated deadlock fixes #1).
  • iverilog -Wall clean on the changed RTL.
  • Runs in CI (PHASE 2 of sim.yml, with cocotb>=2.0 installed) and locally via python sim/cocotb/run.py.

Notes

  • Complements the existing Icarus regression rather than replacing it.
  • GHDL/VHDL execution is deferred until the source port exists; the Python side is already backend-agnostic.

Backend-agnostic cocotb layer beside the existing Icarus TBs. Reusable lib: AXIS master with random bubbles + dropped-tlast, MII monitor with FCS check, boundary-weighted random frame generator, and a store-and-forward reference model + scoreboard. Pilot covers mii_tx_saf boundaries, merges, and oversized runs with seed-logged random cases. Mutation-checked: reintroducing the oversize wedge makes it fail. Wired into build_and_test.py as PHASE 2 (skips if cocotb absent). Uses cocotb's language-neutral sources= so the same Python retargets GHDL for the VHDL port.
The sim workflow already installs iverilog (cocotb's backend) and runs build_and_test.py, but without cocotb the new cocotb phase skipped silently. Add a pip install "cocotb>=2.0" step so the directed+randomized suite actually executes on push/PR.
Second module on the framework. New RX lib: GMII driver (FCS-corrupt + rx_er), backpressuring AXIS sink, and an eth_mac_rx reference model for filtering, terror, delivery-vs-drop, and stats. Directed + seed-logged random. Mutation-checked. run.py drives both suites.
Suite drives contiguous GMII frames on the sys clock and checks the paced
media output byte-for-byte across 1G/100M/10M, plus a small-frame burst that
stresses the committed-frame counter.

It found two real bugs, now fixed:
- paced-TX phantom frame: read pointer parked on the EOF word, so the next
  100M/10M frame emitted a stale EOF byte and orphaned the real frame. Close-out
  now advances past EOF in the paced modes only.
- committed-counter wrap: 4-bit counter aliased at 16 backed-up frames, wedging
  the media side. Widened to ADDR_WIDTH+1 bits (same class as mii_tx_saf).

Both are mutation-checked. Docs updated.
RX tests: byte-exact delivery, rx_er alignment, slow-clock burst probe. Found
and fixed two bugs: a multi-frame byte-drop (readout re-ran its cold-start align
at each EOF, dropping byte 0 of later frames) and an rx_frames_pending 4-bit
counter wrap. Both mutation-checked.
All mutation-checked:
- eth_mac_rx: multicast hash admit used mac_chk[0], not mac_chk[40] (the I/G
  bit), so wrong frames passed under MCAST_HASH_FILTER=1. New mcast suite.
- gmii_cdc: gmii_tx_er_in was dropped; added a per-byte error lane re-driving
  gmii_tx_er_out.
- gmii_cdc: paced last byte held 1 cycle instead of a full interval.
@lcapossio lcapossio changed the title verify: cocotb datapath suite (mii_tx_saf, eth_mac_rx, gmii_cdc) + 4 gmii_cdc fixes verify: cocotb datapath suite (mii_tx_saf, eth_mac_rx, gmii_cdc) + 7 RTL fixes Jul 26, 2026
The paced last-byte-hold fix makes the final byte occupy a full pace interval,
so 10M tx_en spans N*K = 8*100 = 800 cycles, not the old (N-1)*K+1 = 701. The
legacy TB hard-coded 701 (the bug); update it to ~800, matching the module's
own design comment. The 100M TB already tolerated this (range covers 320).
@lcapossio lcapossio changed the title verify: cocotb datapath suite (mii_tx_saf, eth_mac_rx, gmii_cdc) + 7 RTL fixes verify: cocotb datapath suite + 13 RTL fixes across gmii_cdc / eth_mac_rx / mii_tx_saf Jul 26, 2026
@lcapossio
lcapossio merged commit 10b99ca into main Jul 29, 2026
1 check passed
@lcapossio
lcapossio deleted the feat/cocotb-verification branch August 4, 2026 04:13
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