Pcap2HDL

Home / Architecture / Bring your own NIC

Release 0.4.0

Bring your own NIC (NIC RX as an AXI-Stream slave)

Pcap2HDL 0.4.0 has a slot for a real NIC receive pipeline, not only the parser/scoreboard observers.

How it works: make NIC=1 compiles an AXI-Stream slave (nic_rx). It drives tready. The existing checks (pkt_snoop: parser, TCP/RoCE, RSS, checksum) sit beside it on the same bus — they do not sit after the NIC in a FIFO chain. Someone else swaps that slave instance for their RX, keeps the same pins (s_tdata / s_tvalid / s_tready / …), and still replays a .pcap through DPI-C into Verilator.

Hierarchy

The testbench is the AXI-Stream master. Packet checks live in one wrapper, pkt_snoop (u_snoop). That wrapper does not drive tready. With NIC=1, u_nic_rx is the slave: it produces s_tready on the same wires the snoops sample. Without the flag, the bench drives tready from BP only.

tb_pcap_dpi instance hierarchy tb_pcap_dpi AXIS master · DPI-C · plusargs u_snoop (pkt_snoop) [DUT] observers · tready is an input u_nic_rx (nic_rx) compile only with make NIC=1 u_filter u_parser u_tcp / u_tracker u_icrc u_rss u_csum s_tready out ready_mask = BP replace this instance always compiled ifdef EN_NIC
Labels sit on the modules, not on the arrows. u_snoop is not a packet FIFO: children snoop the same AXIS.

After instantiation

What exists after make NIC=1 instantiates.

Instance tree (GTKWave SST)

tb_pcap_dpi instance tree tb_pcap_dpi AXIS master + DPI-C u_nic_rx nic_rx u_snoop pkt_snoop u_filter u_parser u_tcp u_tracker u_icrc u_rss u_csum

Same AXIS after the port map (not a chain)

Shared AXIS after port map tb_pcap_dpi master tdata tkeep tvalid tstart tlast tuser AXI-Stream nets tready = u_nic_rx.s_tready u_nic_rx s_* in, s_tready out ready_mask = BP u_snoop same t* in tready is input s_tready

u_nic_rx and u_snoop are siblings. Beats do not go NIC then parser. Swap only u_nic_rx if you attach another RX.

AXIS pin template

Your NIC is a slave. The testbench is the master. Observers tap the bus; they must not drive tready. ready_mask is the bench BP bit. AND it inside the slave so a stalled BP cycle is not counted as a beat. Tie ready_mask to 1 only if your core is the sole ready source.

Bring your own NIC AXI-Stream pin template tb_pcap_dpi master tdata tvalid tstart tlast your RX / nic_rx slave · hdl/nic_rx.sv s_tdata s_tkeep s_tvalid in s_tstart s_tlast s_tuser in s_tready out ready_mask in (BP) pause_en in (optional) rx_pkt_valid rx_bytes u_snoop observers tready is an input payload + tvalid s_tready same beats
AMBA AXI-Stream has no tstart; this bench uses it as SOP. Sample only when s_tvalid && s_tready.

What nic_rx does

The in-tree template is ingress credits, not a 400GbE MAC. Occupancy rises on an accepted beat and falls one beat per cycle (host drain). s_tready drops when almost full, when pause_en toggles (NIC_PAUSE=1), or when ready_mask is 0. On s_tlast it pulses rx_pkt_valid with a byte count from tkeep. The testbench compares that length to pkt_size_filter.

nic_rx internal ready and credits ready_mask BP from TB credits DEPTH=16 almost full drain one beat / cycle s_tlast → rx_pkt_valid [NIC] rx= vs streamed
occ_max=1 on a line-rate drain is expected: the FIFO does not back up.

NIC_PAUSE=1

NIC_PAUSE=1 is a slave-side stall inside nic_rx. It only exists when you also make NIC=1.

Each clock, pause_en toggles bubble. s_tready is 0 whenever bubble is 1, so the NIC accepts a beat only every other cycle (50% ready), even if the FIFO is not full.

s_tready = rst_n && ready_mask && !bubble && !almost_full
KnobWho stallsTypical use
BP=1 / BP=2Bench ready_mask ANDed into the slaveExtra backpressure from the testbench
NIC_PAUSE=1nic_rx itself (bubble)Prove the DUT is the one driving tready

Default is NIC_PAUSE=0: bubble stays 0, no extra toggle.

make NIC=1 NIC_PAUSE=1

Packet counts and [NIC] … mis=0 should still match NIC_PAUSE=0 if the slave only advances on s_tvalid && s_tready. Your own RX can tie pause_en off.

Build and gate

make                  # [NIC] off — nic_rx.sv not compiled
make NIC=1            # u_nic_rx on the bus
make NIC=1 BP=2
make NIC=1 NIC_PAUSE=1

Changing NIC rebuilds. End of a NIC=1 run:

[NIC] rx=100  drop=0  byte_mis=0  csum_side=0  occ_max=1  mis=0

rx must equal streamed packets. byte_mis=0 means the same length as pkt_size_filter on each tlast. Per-packet lines stay [DUT] Classified; there is no per-packet [NIC] print.

Swap your RX

  1. Keep make NIC=1 (or add your file in the Makefile ifeq ($(NIC),1) block).
  2. Replace u_nic_rx inside ifdef EN_NIC in hdl/tb_pcap_dpi.sv.
  3. Ports: s_tdata, s_tkeep, s_tvalid, s_tready (out), s_tstart, s_tlast, s_tuser, s_tuser_err, ready_mask.
  4. Do not open the pcap in HDL. Use PCAP=.
  5. Leave u_snoop unless you are replacing the scoreboards too.