Pcap2HDL

Home / Architecture

Release 0.4.0

From pcap to DUT

The capture file is never opened in SystemVerilog. libpcap reads it in C. The testbench is an AXI-Stream master. With make NIC=1, nic_rx is the AXIS slave (it drives tready). Other DUT modules snoop the same beats; they do not pass frames to one another. Trackers and RSS sit on parser metadata (hdr_valid and decoded fields), not on AXIS.

Overview

Pcap2HDL is a Verilator bench for early network-silicon bring-up. A Wireshark or tcpdump .pcap is read in C with libpcap, streamed through DPI-C, and driven onto an AXI-Stream so HDL sees the same Ethernet bytes a MAC would, with simulation time frozen. Default DATA_W is 8 (one byte per cycle); make AXIS_W=64 packs eight bytes with tkeep. A beat is legal only when tvalid && tready. Default make uses bench BP as tready. make NIC=1 lets nic_rx drive ready (with BP as ready_mask). Template diagrams: Bring your own NIC.

The figure is the whole design. Top to bottom is capture → C → testbench → bus. pkt_size_filter, pkt_header_parser, pkt_roce_icrc, and pkt_ip_csum are not a chain: they snoop the same AXIS in the same cycles. Only pkt_header_parser fans down to pkt_tcp_tracker, pkt_roce_tracker, and pkt_rss. pkt_roce_icrc stays on AXIS and also takes is_roce and ip_tot_len from pkt_header_parser. pkt_size_filter and pkt_ip_csum have no downward arrows; they never feed the trackers.

Pcap2HDL data path .pcap on disk Wireshark / tcpdump dpi/pcap_reader.c libpcap + optional BPF tb_pcap_dpi.sv AXIS master + tready slave AXI-Stream tdata tkeep tvalid tready tstart tlast · tuser tuser_err pkt_size_filter pkt_header_parser pkt_roce_icrc pkt_ip_csum pkt_tcp_tracker pkt_roce_tracker pkt_rss DPI-C: open_pcap, fetch_next_packet, get_packet_byte, dump_* hdr_valid + fields hdr_valid + BTH hdr_valid + 4-tuple is_roce, ip_tot_len
AXIS broadcast to pkt_size_filter, pkt_header_parser, pkt_roce_icrc, and pkt_ip_csum. Parser metadata from pkt_header_parser only. pkt_roce_icrc also samples parser is_roce / ip_tot_len. Optional DUMP= writes accepted beats back to a pcap.

Capture and DPI-C

The top of the figure is the only path that touches the filesystem. make PCAP=traffic.pcap plusargs into tb_pcap_dpi.sv. Imports are implemented in dpi/pcap_reader.c. BPF skip happens here: skipped frames never become beats. C also computes RSS and IPv4 checksum for the scoreboard (get_rss_hash, get_ip_csum). Optional DUMP= writes accepted beats back (dump_put_byte / dump_packet) so Wireshark can open the round-trip.

ImportRole
open_pcapOpen capture; fail the sim if missing
set_pcap_filterCompile BPF; skip is counted, not silent
fetch_next_packetNext matched frame length, or 0 at EOF
get_packet_byteOne captured byte for the current frame
get_wire_len / get_ts_* / get_datalinkWire length, timestamps, DLT
get_rss_* / get_ip_csum_*C golden for HDL scoreboard
open_pcap_dump / dump_put_byte / dump_packetRound-trip pcap of accepted beats

Skipped BPF frames never raise tvalid. Truncated captures (captured length < wire length) still stream; ICRC may skip rather than fail.

AXI-Stream

The wide bar in the figure is the only packet interface. For each matched frame the testbench puts bytes on the bus. Clock is 10 ns (always #5 clk = ~clk). Reset is active-low. There is no FIFO between DUT modules; the same wires go to pkt_size_filter, pkt_header_parser, pkt_roce_icrc, and pkt_ip_csum. tuser is the C RSS hash and tuser_err is C checksum-fail; pkt_rss and pkt_ip_csum do not read them.

Beat

A beat is one AXI-Stream handshake: one clock where tvalid and tready are both 1. That is the only cycle the DUT may take tdata / tkeep. In the HDL this is fire (tvalid && tready).

DATA_W=8 → one beat is one byte. make AXIS_W=64 → one beat is up to eight bytes; tkeep marks which lanes are valid. tstart is the first beat of the frame, tlast the last. If tready is 0 (BP=1 / BP=2), there is no beat that cycle even if tvalid is 1. A 64-byte frame is 64 beats at 8 bits, or 8 beats at 64 bits (the last beat may have a short tkeep).

The testbench drives tdata/tkeep on negedge, sets tvalid, then waits posedge until tready.

SignalWidthDirection (DUT)Meaning
clk / rst_n1inSynchronous design; async assert of reset in the TB
tdataDATA_WinPayload; little-endian lanes when DATA_W=64
tkeepDATA_W/8inValid bytes in the beat; all 1s except a short last beat
tvalid1inMaster has a beat
tready1out*From nic_rx (ready_mask is bench BP)
tstart1inFirst beat of the frame (not in AMBA AXIS; explicit here)
tlast1inLast beat of the frame
tuser32inC RSS hash when valid; for a custom NIC DUT
tuser_err1inC checksum fail; does not overwrite tuser

*Observers input tready. The in-tree nic_rx outputs s_tready, which already includes bench BP as ready_mask. Confirm fire in the wave before trusting parse.

Bring your own NIC

Hierarchy, AXIS pin template, and make NIC=1 are on the Bring your own NIC page. hdl/nic_rx.sv is the in-tree slave; observers stay under u_snoop. Without the flag the log prints [NIC] off.

Gate with the slave compiled: [NIC] rx= equals streamed packets, drop=0, byte_mis=0, mis=0.

Streaming DUTs

pkt_size_filter, pkt_header_parser, pkt_roce_icrc, and pkt_ip_csum all sample tvalid && tready. They are observers, not a chain. pkt_size_filter and pkt_ip_csum stop on AXIS. Only pkt_header_parser continues downward. pkt_roce_icrc stays on AXIS and also takes parser is_roce and ip_tot_len.

ModuleOn AXISProduces
pkt_size_filtertkeep popcountpkt_done, pkt_bytes, runt / standard / jumbo
pkt_header_parserbyte index through Ethernet / IP / L4 / BTHhdr_valid and header fields
pkt_ip_csumown IHL walkcsum_valid, csum, ok / err / skip vs C
pkt_roce_icrcsame beats + is_roce, ip_tot_lenicrc_valid, ICRC ok / err / skip

C vs HDL

DPI-C and SystemVerilog both fold the IPv4 header checksum (RFC 1071). They do not share an implementation: C runs in dpi/pcap_reader.c when the frame is fetched; pkt_ip_csum walks AXIS beats. tb_pcap_dpi latches the C result, then on csum_valid compares csum and csum_ok to get_ip_csum / get_ip_csum_ok. A mismatch increments n_csum_mis and prints [CSUM] … MISMATCH. The gate is mis=0.

C versus HDL IPv4 checksum same IPv4 frame in the pcap fetched once by DPI-C, then streamed on AXIS dpi/pcap_reader.c get_ip_csum · RFC 1071 in C pkt_ip_csum.sv same fold on AXIS beats c_csum, c_csum_ok latched in tb_pcap_dpi at fetch csum, csum_ok valid when csum_valid tb_pcap_dpi compare → [CSUM] mis=0 mismatch if values or ok-flag differ DPI-C AXI-Stream
RSS uses the same pattern: get_rss_hash in C versus pkt_rss on parser metadata; the testbench prints [RSS] and counts mis.

Parser metadata

NIC pipelines call this parser metadata (P4: a packet header vector). pkt_tcp_tracker, pkt_roce_tracker, and pkt_rss hang only off pkt_header_parser. They never see tdata. When hdr_valid is high for a cycle, the fields below are that metadata. The testbench compares HDL RSS and checksum to C (mis=0).

GroupSignals
Classifyis_ipv4, is_non_ipv4, is_truncated, is_tcp, is_udp, is_roce, is_arp, is_vxlan
L2dst_mac, src_mac, ethertype (0480, 0806, …)
IPv4src_ip, dst_ip, ip_ttl, ip_tot_len, ip_proto, is_len_mismatch
L4src_port, dst_port; TCP seq, ack, flags, plen
RoCE BTHopcode, pkey, ackreq, dest_qp, psn
VXLANvxlan_vni, inner type via the same L2/L3 walk
ConsumerUses
pkt_tcp_trackerhdr_valid, is_tcp, 4-tuple, seq, ack, flags, plen
pkt_roce_trackerhdr_valid, is_roce, IPs, opcode, dest_qp, psn
pkt_rsshdr_valid, is_ipv4, truncated, proto, 4-tuple
pkt_roce_icrcAXIS plus is_roce, ip_tot_len (not hdr_valid alone)
tb scoreboardrss_hash vs get_rss_hash; csum vs get_ip_csum

One packet

Walk the figure once around the clock. DPI returns length and bytes; C RSS and checksum are latched. Beats run on AXIS from tstart to tlast; pkt_size_filter, pkt_header_parser, pkt_roce_icrc, and pkt_ip_csum update every fire. When the header is complete (often before tlast on a long frame), hdr_valid pulses and pkt_tcp_tracker, pkt_roce_tracker, and pkt_rss fire. On tlast, pkt_size_filter pulses pkt_done and pkt_roce_icrc checks the last four bytes if RoCE and not truncated. Then tvalid drops; PACE may insert IFG from pcap timestamps (capped by PACE_MAX_US), plus three idle cycles by default.

In the wave, start with tvalid / tready / tstart / tlast / tdata on one packet. Then hdr_valid and ethertype, then tcp_evt or trk_evt. If AXIS is wrong, parser and trackers will lie even if the .pcap is fine. Run a sim, then make wave (GTKWave on simulation_trace.vcd). Logs: [HDR], [TCP], [TRK], [RSS], [CSUM], [DUT].

Sources

PathRole
hdl/tb_pcap_dpi.svDPI imports, AXIS master, plusargs, logs
hdl/pkt_snoop.svObserver hierarchy under u_snoop
hdl/pkt_size_filter.svRunt / standard / jumbo from tkeep popcount
hdl/pkt_header_parser.svStreaming L2–L4, ARP, VXLAN, Soft-RoCEv2 BTH
hdl/pkt_tcp_tracker.svHandshake and next-seq CAM
hdl/pkt_roce_tracker.svPSN / message / reverse ACK
hdl/pkt_roce_icrc.svMasked CRC32 vs last 4 bytes
hdl/pkt_rss.svToeplitz; same key as C
hdl/pkt_ip_csum.svIPv4 header checksum; same fold as C
hdl/nic_rx.svAXIS slave; compiled only with make NIC=1
dpi/pcap_reader.clibpcap, BPF, dump, C RSS and csum