Pcap2HDL
Pcap2HDL — Packet traces to HDL simulation

Release 0.4.0

Packet traces to cycle-accurate HDL

Stream a captured .pcap into Verilator through DPI-C. Hardware under test sees the same Ethernet frames a NIC would, on an AXI-Stream bus, with simulation time frozen for debug.

0.4.0 · Bring your own NIC · MIT · Linux / Verilator 5.032+

Bring your own NICmake NIC=1 · nic_rx slave beside observers
C vs HDLRSS Toeplitz and IPv4 checksum, mis=0
RoCEv2BTH, PSN tracker, ICRC on complete frames
BPFmatched / skipped, unread tail not counted

Built for early network silicon bring-up

Use it when an FPGA or ASIC packet pipeline needs real traffic before a live port exists. Classification, DPI, RoCE-aware paths, and RSS can be gated on traces you already captured.

DPI-C + libpcap

Offline replay with wire length, timestamps, and DLT. FILTER= compiles BPF and reports match versus skip counts.

Bring your own NIC

Optional AXIS slave: make NIC=1 compiles nic_rx. It drives s_tready. Parser, TCP/RoCE, RSS, and checksum snoop the same beats — they are not a FIFO after the NIC.

C vs HDL scoreboard

RSS Toeplitz hash on tuser and IPv4 header checksum are computed in C and checked in SystemVerilog. Gate mis=0.

TCP next-seq

Handshake HS_DONE, then payload length from IHL and data offset. SYN/FIN consume one. In-order data is SEQ_OK.

Soft-RoCEv2

UDP/4791 BTH, PSN tracker, and ICRC on the last four bytes of a complete frame. Truncated captures are skipped, not failed.

ARP and VXLAN

EtherType 0x0806 is classified as ARP. UDP dest 4789 walks the overlay to the inner Ethernet type and VNI.

Dump round-trip

DUMP=replay.pcap writes accepted beats through pcap_dump. Replaying that file must match the original DUT summary. Wireshark still opens it as Ethernet.

Coverage print

End-of-run [COV] bins: size class, TCP SYN/ACK/FIN/RST, RoCE send vs ACK. Verilator 5.032 cannot compile covergroup.

From pcap to DUT

Sources sit under hdl/ and dpi/. Traces stay on the machine that runs the sim (gitignored). Signal-level notes: Architecture. Bring your own NIC: AXIS slave slot.

.pcaplibpcap
BPFoptional FILTER
DPI-Cpcap_reader.c
AXI-Streamtb_pcap_dpi
observers · nic_rxbeside, not after

DUT modules and plusargs

Each HDL block is isolated. Runtime knobs are plusargs; changing PCAP or MAX_PACKETS does not rebuild. Changing AXIS_W does.

ModuleRole
pkt_size_filterRunt (<64) / standard / jumbo (>1500) from tkeep popcount
pkt_header_parserMAC, EtherType, IPv4 TTL/iplen, L4 ports, TCP flags/seq, RoCE BTH, ARP, VXLAN VNI
pkt_tcp_tracker4-tuple CAM: SYN / SYN-ACK / HS_DONE / next-seq (plen) / FIN / RST
pkt_roce_trackerPSN per {src,dst,qp}, MSG_DONE, reverse ACK_OK, next-message PSN_GAP
pkt_roce_icrcSeed 0xdebb20e3, masked CRC32 vs last 4 bytes; truncated frames skipped
pkt_rss40-byte Microsoft/Intel key; 4-tuple TCP/UDP or 2-tuple IPs; qid = hash % 4
pkt_ip_csumRFC 1071 fold of the IHL words; compared with DPI-C (CSUM mis=0)
nic_rxAXIS slave; make NIC=1 only. Drives s_tready
Plusarg / makeMeaning
WiresharkUser-provided .pcap (save from Wireshark or tcpdump); the bench replays that file
PCAP=Capture file (default traffic.pcap)
MAX_PACKETS=Stream cap (default 100). BPF counts matches, not file order
FILTER=libpcap expression; pcap_offline_filter so skip is counted
DUMP=Write accepted AXI-Stream bytes back to a pcap (same DLT)
BP=1 / BP=2Extra stall AND-ed with nic_rx s_tready
NIC=1Compile nic_rx and print [NIC] rx= (rebuilds)
NIC_PAUSE=1With NIC=1: nic_rx toggles bubble so s_tready is 50%. Not the same as BP (ready_mask). See Bring your own NIC.
PACE=1IFG from pcap timestamps, capped by PACE_MAX_US (default 100)
AXIS_W=8|64Bits on tdata; 64 uses tkeep lanes
Wireshark showing eight Ethernet frames from a dump round-trip
Dump round-trip: DUMP=replay.pcap as Ethernet in Wireshark.
GTKWave showing RoCE header signals
GTKWave: hdr_is_roce, dest port 0x12b7. Run make wave.

Gates

These summaries are what a green run looks like. Compact handshake logs live under examples/ with MAX_PACKETS=8.

CommandExpect
make MAX_PACKETS=8ipv4=8 tcp=8 hs=1 seq_ok=5 seq_err=0, RSS and CSUM mis=0
make FILTER='tcp port 5201'BPF matched=100 skipped=0 (1000-pkt TCP file, cap 100)
make FILTER='udp'streamed 0, skipped=1000 on a TCP-only capture
make PCAP=soft_roce.pcap MAX_PACKETS=8roce=8 msg=1 ack=1 icrc ok=8
make PCAP=soft_roce.pcap MAX_PACKETS=16msg=3 ack=3, next Send continues PSN
python3 scripts/gen_pcap.py ci.pcap && make PCAP=ci.pcap BP=2arp=1 vxlan=1 CSUM mis=0, streamed 4

Build and run

Ubuntu is the reference environment. Install Verilator 5.032 or later and libpcap-dev. GTKWave is optional (make wave). Soft-RoCE capture additionally needs rdma-core, ibverbs-utils, and tcpdump (docs/soft_roce_veth.md).

sudo apt install build-essential libpcap-dev
git clone https://github.com/khademullah/Pcap2HDL.git
cd Pcap2HDL
make MAX_PACKETS=8
python3 scripts/gen_pcap.py ci.pcap
make PCAP=ci.pcap MAX_PACKETS=8 BP=2
make FILTER='tcp port 5201'
make FILTER='udp'
make PCAP=soft_roce.pcap MAX_PACKETS=8
make PCAP=soft_roce.pcap MAX_PACKETS=16
make DUMP=replay.pcap
make AXIS_W=64 PCAP=soft_roce.pcap

traffic.pcap and soft_roce.pcap are local (gitignored). Default cap is 100 packets. tuser carries the RSS hash; tuser_err is checksum-fail sideband and does not overwrite the hash.

Contribute

Aimed at digital design, verification, and network-hardware engineers. Clocked handshakes and L2–L4 still matter. Keep isolation: a change in pkt_rss.sv should not require editing the RoCE tracker.

Parked (good first PRs)

  • IPv6 in pkt_header_parser.sv, keep IPv4/RoCE gates green
  • 802.1Q VLAN: EtherType 0x8100, then inner type; untagged captures must not break
  • More example logs (failed handshake, malformed frames); do not commit .pcap files

Out of scope unless CHANGELOG says otherwise

  • Live sniff, host CSR map, Ethernet FCS
  • Replacing the DPI-C bench with a UVM environment
  • A GUI around the simulator

0.4.0 landed: Bring your own NIC (make NIC=1), IPv4 checksum scoreboard, ARP/VXLAN, random BP=2, scripts/gen_pcap.py, GitHub Actions.