From babce69bd0029601a1e93f7eb15a127b704d5514 Mon Sep 17 00:00:00 2001 From: Alex Forencich Date: Wed, 6 Aug 2025 14:49:22 -0700 Subject: [PATCH] zircon: Add RX parser module and testbench Signed-off-by: Alex Forencich --- src/zircon/rtl/zircon_ip_rx_parse.sv | 1147 +++++++++++++++++ src/zircon/tb/zircon_ip_rx_parse/Makefile | 54 + .../test_zircon_ip_rx_parse.py | 536 ++++++++ .../test_zircon_ip_rx_parse.sv | 56 + 4 files changed, 1793 insertions(+) create mode 100644 src/zircon/rtl/zircon_ip_rx_parse.sv create mode 100644 src/zircon/tb/zircon_ip_rx_parse/Makefile create mode 100644 src/zircon/tb/zircon_ip_rx_parse/test_zircon_ip_rx_parse.py create mode 100644 src/zircon/tb/zircon_ip_rx_parse/test_zircon_ip_rx_parse.sv diff --git a/src/zircon/rtl/zircon_ip_rx_parse.sv b/src/zircon/rtl/zircon_ip_rx_parse.sv new file mode 100644 index 0000000..68c76b5 --- /dev/null +++ b/src/zircon/rtl/zircon_ip_rx_parse.sv @@ -0,0 +1,1147 @@ +// SPDX-License-Identifier: CERN-OHL-S-2.0 +/* + +Copyright (c) 2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +*/ + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * Zircon IP stack - RX parser + */ +module zircon_ip_rx_parse # +( + parameter logic IPV6_EN = 1'b1, + parameter logic HASH_EN = 1'b1 +) +( + input wire logic clk, + input wire logic rst, + + /* + * Packet header input + */ + taxi_axis_if.snk s_axis_pkt, + + /* + * Packet metadata output + */ + taxi_axis_if.src m_axis_meta +); + +// Metadata output (64 bit blocks): +// 00: flags / payload len, pkt sum +// 01: RSS hash / header and payload offsets +// 02: vlan tags / dscp, ecn +// 03: eth dst +// 04: eth src, ethtype +// 05: protos/hdrs with offsets +// 06: protos/hdrs with offsets +// 07: ip id/fl / protocol, ttl/hl +// 08: ipv6 dst +// 09: ipv6 dst +// 10: ipv6 src +// 11: ipv6 src +// 12: l4 ports / tcp flags +// 13: tcp wnd/urg +// 14: tcp seq/ack +// 15: + +localparam DATA_W = s_axis_pkt.DATA_W; +localparam META_W = m_axis_meta.DATA_W; + +// check configuration +if (DATA_W != 32) + $fatal(0, "Error: Interface width must be 32 (instance %m)"); + +if (s_axis_pkt.KEEP_W * 8 != DATA_W) + $fatal(0, "Error: Interface requires byte (8-bit) granularity (instance %m)"); + +if (META_W != 64) + $fatal(0, "Error: Interface width must be 64 (instance %m)"); + +if (m_axis_meta.KEEP_W * 8 != META_W) + $fatal(0, "Error: Interface requires byte (8-bit) granularity (instance %m)"); + +localparam [15:0] + ETHERTYPE_IPV4 = 16'h0800, + ETHERTYPE_ARP = 16'h0806, + ETHERTYPE_VLAN_C = 16'h8100, + ETHERTYPE_VLAN_S = 16'h88A8, + ETHERTYPE_IPV6 = 16'h86DD, + ETHERTYPE_PBB = 16'h88E7, + ETHERTYPE_PTP = 16'h88F7, + ETHERTYPE_ROCE = 16'h8915; + +localparam [7:0] + PROTO_IPV6_HOPOPT = 8'd0, + PROTO_ICMP = 8'd1, + PROTO_IGMP = 8'd2, + PROTO_IPIP = 8'd4, + PROTO_TCP = 8'd6, + PROTO_UDP = 8'd17, + PROTO_IPV6 = 8'd41, + PROTO_IPV6_ROUTE = 8'd43, + PROTO_IPV6_FRAG = 8'd44, + PROTO_GRE = 8'd47, + PROTO_ESP = 8'd50, + PROTO_AH = 8'd51, + PROTO_IPV6_ICMP = 8'd58, + PROTO_IPV6_NONXT = 8'd59, + PROTO_IPV6_OPTS = 8'd60, + PROTO_HIP = 8'd139, + PROTO_SHIM6 = 8'd140, + PROTO_253 = 8'd253, + PROTO_254 = 8'd254; + +localparam + FLG_VLAN_S = 1, + FLG_VLAN_C = 2, + FLG_IPV4 = 3, + FLG_IPV6 = 4, + FLG_FRAG = 5, + FLG_ARP = 6, + FLG_ICMP = 7, + FLG_TCP = 8, + FLG_UDP = 9, + FLG_AH = 10, + FLG_ESP = 11, + FLG_L3_OPT_PRSNT = 16, + FLG_L4_OPT_PRSNT = 17, + FLG_L3_BAD_CKSUM = 24, + FLG_L4_BAD_LEN = 25, + FLG_PARSE_DONE = 31; + +localparam [4:0] + STATE_IDLE = 5'd0, + STATE_ETH_1 = 5'd1, + STATE_ETH_2 = 5'd2, + STATE_ETH_3 = 5'd3, + STATE_VLAN_1 = 5'd4, + STATE_VLAN_2 = 5'd5, + STATE_IPV4_1 = 5'd6, + STATE_IPV4_2 = 5'd7, + STATE_IPV4_3 = 5'd8, + STATE_IPV4_4 = 5'd9, + STATE_IPV4_5 = 5'd10, + STATE_IPV4_6 = 5'd11, + STATE_IPV6_1 = 5'd12, + STATE_IPV6_2 = 5'd13, + STATE_IPV6_3 = 5'd14, + STATE_IPV6_4 = 5'd15, + STATE_IPV6_5 = 5'd16, + STATE_IPV6_6 = 5'd17, + STATE_IPV6_7 = 5'd18, + STATE_IPV6_8 = 5'd19, + STATE_IPV6_9 = 5'd20, + STATE_IPV6_10 = 5'd21, + STATE_EXT_HDR_1 = 5'd22, + STATE_TCP_1 = 5'd23, + STATE_TCP_2 = 5'd24, + STATE_TCP_3 = 5'd25, + STATE_TCP_4 = 5'd26, + STATE_TCP_5 = 5'd27, + STATE_UDP_1 = 5'd28, + STATE_UDP_2 = 5'd29, + STATE_FINISH_1 = 5'd30, + STATE_FINISH_2 = 5'd31; + +logic [4:0] state_reg = STATE_IDLE, state_next; + +logic frame_reg = 1'b0, frame_next; +logic run_reg = 1'b0, run_next; +logic [31:0] flag_reg = '0, flag_next; +logic [7:0] next_hdr_reg = '0, next_hdr_next; +logic [8:0] hdr_len_reg = '0, hdr_len_next; +logic [7:0] offset_reg = '0, offset_next; +logic [7:0] l3_offset_reg = '0, l3_offset_next; +logic [7:0] l4_offset_reg = '0, l4_offset_next; +logic [7:0] payload_offset_reg = '0, payload_offset_next; +logic [15:0] payload_len_reg = '0, payload_len_next; + +logic [20:0] ip_hdr_cksum_reg = '0, ip_hdr_cksum_next; +logic [23:0] corr_cksum_reg = '0, corr_cksum_next; + +logic s_axis_pkt_tready_reg = 1'b0, s_axis_pkt_tready_next; + +// metadata RAM +localparam META_AW = 5; + +logic [31:0] meta_ram_a[2**META_AW]; +logic [31:0] meta_ram_a_wr_data; +logic [3:0] meta_ram_a_wr_strb; +logic [META_AW-1:0] meta_ram_a_wr_addr; +logic meta_ram_a_wr_en; +logic [31:0] meta_ram_a_rd_data_reg = '0; +logic [META_AW-1:0] meta_ram_a_rd_addr; +logic meta_ram_a_rd_en; + +logic [31:0] meta_ram_b[2**META_AW]; +logic [31:0] meta_ram_b_wr_data; +logic [3:0] meta_ram_b_wr_strb; +logic [META_AW-1:0] meta_ram_b_wr_addr; +logic meta_ram_b_wr_en; +logic [31:0] meta_ram_b_rd_data_reg = '0; +logic [META_AW-1:0] meta_ram_b_rd_addr; +logic meta_ram_b_rd_en; + +logic [1:0] meta_wr_slot_reg = '0, meta_wr_slot_next; +logic [1:0] meta_rd_slot_reg = '0, meta_rd_slot_next; +logic [META_AW-1-1:0] meta_rd_ptr_reg = '0, meta_rd_ptr_next; + +logic meta_rd_data_valid_reg = 1'b0, meta_rd_data_valid_next; +logic meta_rd_data_last_reg = 1'b0, meta_rd_data_last_next; + +wire meta_empty = meta_wr_slot_reg == meta_rd_slot_reg; +wire meta_full = meta_wr_slot_reg == (meta_rd_slot_reg ^ 2'b10); + +assign s_axis_pkt.tready = s_axis_pkt_tready_reg; + +assign m_axis_meta.tdata = {meta_ram_b_rd_data_reg, meta_ram_a_rd_data_reg}; +assign m_axis_meta.tkeep = '1; +assign m_axis_meta.tstrb = m_axis_meta.tkeep; +assign m_axis_meta.tid = '0; +assign m_axis_meta.tdest = '0; +assign m_axis_meta.tuser = '0; +assign m_axis_meta.tlast = meta_rd_data_last_reg; +assign m_axis_meta.tvalid = meta_rd_data_valid_reg; + +// offset data for proper 4-byte alignment after Ethernet header +wire [31:0] pkt_data = {s_axis_pkt.tdata[15:0], pkt_data_reg}; +wire [31:0] pkt_data_be = {pkt_data[7:0], pkt_data[15:8], pkt_data[23:16], pkt_data[31:24]}; +logic [15:0] pkt_data_reg = 0; + +// Toeplitz flow hash computation +logic hash_reset; +logic hash_step; +wire [31:0] hash_value; + +if (HASH_EN) begin : rss_hash + + logic [31:0] key_rom[10] = '{ + 32'h6d5a56da, 32'h255b0ec2, 32'h4167253d, 32'h43a38fb0, 32'hd0ca2bcb, + 32'hae7b30b4, 32'h77cb2da3, 32'h8030f20c, 32'h6a42b73b, 32'hbeac01fa + }; + + logic [3:0] key_ptr_reg = '0; + logic [63:0] key_reg = '0; + logic [31:0] hash_reg = '0; + logic hash_rst_reg = 1'b0; + + assign hash_value = hash_reg; + + function [31:0] hash_toep32(input [31:0] data, input [63:0] key); + hash_toep32 = '0; + for (integer i = 0; i < 32; i = i + 1) begin + if (data[31-i]) begin + hash_toep32 = hash_toep32 ^ key[32-i +: 32]; + end + end + endfunction + + always @(posedge clk) begin + if (hash_step) begin + hash_reg <= hash_reg ^ hash_toep32(pkt_data_be, key_reg); + end + + if (hash_rst_reg || hash_step) begin + key_reg[63:32] <= key_reg[31:0]; + key_reg[31:0] <= key_rom[key_ptr_reg]; + key_ptr_reg <= key_ptr_reg + 1; + if (key_ptr_reg != 0) begin + hash_rst_reg = 1'b0; + end + end + + if (hash_reset) begin + hash_reg <= '0; + key_ptr_reg <= '0; + hash_rst_reg = 1'b1; + end + end + +end else begin + + assign hash_value = '0; + +end + +// handle ethertype +logic [4:0] eth_type_state; +logic [31:0] eth_type_flags; + +always_comb begin + eth_type_flags = '0; + if (pkt_data_be[15:0] == ETHERTYPE_VLAN_S) begin + // S-tag + eth_type_state = STATE_VLAN_1; + end else if (pkt_data_be[15:0] == ETHERTYPE_VLAN_C) begin + // C-tag + eth_type_state = STATE_VLAN_2; + end else if (pkt_data_be[15:0] == ETHERTYPE_ARP) begin + // ARP + eth_type_flags[FLG_ARP] = 1'b1; + eth_type_state = STATE_FINISH_1; + end else if (pkt_data_be[15:0] == ETHERTYPE_IPV4) begin + // IPv4 + eth_type_state = STATE_IPV4_1; + end else if (pkt_data_be[15:0] == ETHERTYPE_IPV6) begin + // IPv6 + eth_type_state = STATE_IPV6_1; + end else begin + eth_type_state = STATE_FINISH_1; + end +end + +// handle next header +logic [4:0] next_hdr_state; +logic [31:0] next_hdr_flags; + +always_comb begin + next_hdr_flags = '0; + case (next_hdr_reg) + PROTO_IPV6_HOPOPT: begin + next_hdr_flags[FLG_L3_OPT_PRSNT] = 1'b1; + next_hdr_state = STATE_EXT_HDR_1; + end + PROTO_IPV6_ROUTE: begin + next_hdr_flags[FLG_L3_OPT_PRSNT] = 1'b1; + next_hdr_state = STATE_EXT_HDR_1; + end + PROTO_IPV6_FRAG: begin + next_hdr_flags[FLG_FRAG] = 1'b1; + next_hdr_state = STATE_EXT_HDR_1; + end + PROTO_IPV6_OPTS: begin + next_hdr_flags[FLG_L3_OPT_PRSNT] = 1'b1; + next_hdr_state = STATE_EXT_HDR_1; + end + PROTO_IPV6_NONXT: next_hdr_state = STATE_FINISH_1; + PROTO_IPV6_ICMP: begin + next_hdr_flags[FLG_ICMP] = 1'b1; + next_hdr_state = STATE_FINISH_1; + end + PROTO_ICMP: begin + next_hdr_flags[FLG_ICMP] = 1'b1; + next_hdr_state = STATE_FINISH_1; + end + PROTO_AH: begin + next_hdr_flags[FLG_AH] = 1'b1; + next_hdr_state = STATE_EXT_HDR_1; + end + PROTO_ESP: begin + next_hdr_flags[FLG_ESP] = 1'b1; + next_hdr_state = STATE_FINISH_1; + end + PROTO_HIP: next_hdr_state = STATE_EXT_HDR_1; + PROTO_SHIM6: next_hdr_state = STATE_EXT_HDR_1; + PROTO_253: next_hdr_state = STATE_EXT_HDR_1; + PROTO_254: next_hdr_state = STATE_EXT_HDR_1; + default: begin + if (flag_reg[FLG_FRAG]) begin + // fragmented packet, do not parse further + next_hdr_state = STATE_FINISH_1; + end else begin + case (next_hdr_reg) + PROTO_TCP: next_hdr_state = STATE_TCP_1; + PROTO_UDP: next_hdr_state = STATE_UDP_1; + default: next_hdr_state = STATE_FINISH_1; + endcase + end + end + endcase +end + +always_comb begin + state_next = STATE_IDLE; + + hash_reset = 1'b0; + hash_step = 1'b0; + + frame_next = frame_reg; + run_next = run_reg; + flag_next = flag_reg; + next_hdr_next = next_hdr_reg; + hdr_len_next = hdr_len_reg; + offset_next = offset_reg; + l3_offset_next = l3_offset_reg; + l4_offset_next = l4_offset_reg; + payload_offset_next = payload_offset_reg; + payload_len_next = payload_len_reg; + + ip_hdr_cksum_next = ip_hdr_cksum_reg; + corr_cksum_next = corr_cksum_reg; + + s_axis_pkt_tready_next = s_axis_pkt_tready_reg; + + meta_ram_a_wr_data = pkt_data; + meta_ram_a_wr_strb = '1; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd0}; + meta_ram_a_wr_en = 1'b0; + meta_ram_a_rd_addr = {meta_rd_slot_reg[0], meta_rd_ptr_reg}; + meta_ram_a_rd_en = 1'b0; + + meta_ram_b_wr_data = pkt_data; + meta_ram_b_wr_strb = '1; + meta_ram_b_wr_addr = {meta_wr_slot_reg[0], 4'd0}; + meta_ram_b_wr_en = 1'b0; + meta_ram_b_rd_addr = {meta_rd_slot_reg[0], meta_rd_ptr_reg}; + meta_ram_b_rd_en = 1'b0; + + meta_wr_slot_next = meta_wr_slot_reg; + meta_rd_slot_next = meta_rd_slot_reg; + + meta_rd_ptr_next = meta_rd_ptr_reg; + meta_rd_data_valid_next = meta_rd_data_valid_reg && !m_axis_meta.tready; + meta_rd_data_last_next = meta_rd_data_last_reg; + + if (s_axis_pkt.tready && s_axis_pkt.tvalid) begin + if (hdr_len_reg != 0) begin + hdr_len_next = hdr_len_reg - 1; + end + offset_next = offset_reg + 1; + if (payload_len_reg != 0) begin + payload_len_next = payload_len_reg - 4; + end + + ip_hdr_cksum_next = ip_hdr_cksum_reg + 21'(pkt_data_be[15:0] + pkt_data_be[31:16]); + corr_cksum_next = corr_cksum_reg + 24'(pkt_data_be[15:0] + pkt_data_be[31:16]); + + frame_next = !s_axis_pkt.tlast; + if (s_axis_pkt.tlast) begin + s_axis_pkt_tready_next = 1'b0; + end + end + + if (run_reg && frame_reg && (!s_axis_pkt.tready || !s_axis_pkt.tvalid)) begin + // hold + state_next = state_reg; + end else begin + case (state_reg) + // Ethernet header + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Destination MAC address | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Destination MAC address | Source MAC address | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Source MAC address | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Ethertype | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // Note: input data is shifted by 2 bytes to align subsequent headers + STATE_IDLE: begin + // store dest MAC + meta_ram_a_wr_data = {pkt_data[15:0], pkt_data[31:16]}; + meta_ram_a_wr_strb = 4'b0011; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd3}; + meta_ram_a_wr_en = !meta_full; + + hash_reset = 1'b1; + flag_next = '0; + offset_next = 1; + l3_offset_next = '0; + l4_offset_next = '0; + payload_offset_next = '0; + payload_len_next = '0; + + ip_hdr_cksum_next = '0; + corr_cksum_next = '0; + + s_axis_pkt_tready_next = frame_reg || !meta_full; + + if (s_axis_pkt.tready && s_axis_pkt.tvalid && !frame_reg) begin + run_next = 1'b1; + state_next = STATE_ETH_1; + end else begin + state_next = STATE_IDLE; + end + end + STATE_ETH_1: begin + // store dest MAC + meta_ram_b_wr_data = {pkt_data[15:0], pkt_data[31:16]}; + meta_ram_b_wr_strb = 4'b0011; + meta_ram_b_wr_addr = {meta_wr_slot_reg[0], 4'd3}; + meta_ram_b_wr_en = 1'b1; + meta_ram_a_wr_data = {pkt_data[15:0], pkt_data[31:16]}; + meta_ram_a_wr_strb = 4'b1100; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd3}; + meta_ram_a_wr_en = 1'b1; + + ip_hdr_cksum_next = '0; + corr_cksum_next = '0; + + state_next = STATE_ETH_2; + end + STATE_ETH_2: begin + // store source MAC + meta_ram_a_wr_data = pkt_data; + meta_ram_a_wr_strb = 4'b1111; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd4}; + meta_ram_a_wr_en = 1'b1; + + ip_hdr_cksum_next = '0; + corr_cksum_next = '0; + + state_next = STATE_ETH_3; + end + STATE_ETH_3: begin + // store source MAC and ethertype + meta_ram_b_wr_data = pkt_data; + meta_ram_b_wr_strb = 4'b1111; + meta_ram_b_wr_addr = {meta_wr_slot_reg[0], 4'd4}; + meta_ram_b_wr_en = 1'b1; + + l3_offset_next = offset_reg; + payload_offset_next = offset_reg; + + ip_hdr_cksum_next = '0; + corr_cksum_next = '0; + + flag_next = flag_reg | eth_type_flags; + state_next = eth_type_state; + end + // VLAN tags + STATE_VLAN_1: begin + // store S-TAG + meta_ram_a_wr_data = pkt_data; + meta_ram_a_wr_strb = 4'b0011; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd2}; + meta_ram_a_wr_en = !flag_reg[FLG_VLAN_S]; + + // store ethertype + meta_ram_b_wr_data = pkt_data; + meta_ram_b_wr_strb = 4'b1100; + meta_ram_b_wr_addr = {meta_wr_slot_reg[0], 4'd4}; + meta_ram_b_wr_en = 1'b1; + + l3_offset_next = offset_reg; + payload_offset_next = offset_reg; + ip_hdr_cksum_next = '0; + + flag_next = flag_reg | eth_type_flags; + state_next = eth_type_state; + + flag_next[FLG_VLAN_S] = 1'b1; + end + STATE_VLAN_2: begin + // store C-TAG + meta_ram_a_wr_data = {pkt_data[15:0], pkt_data[31:16]}; + meta_ram_a_wr_strb = 4'b1100; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd2}; + meta_ram_a_wr_en = !flag_reg[FLG_VLAN_C]; + + // store ethertype + meta_ram_b_wr_data = pkt_data; + meta_ram_b_wr_strb = 4'b1100; + meta_ram_b_wr_addr = {meta_wr_slot_reg[0], 4'd4}; + meta_ram_b_wr_en = 1'b1; + + l3_offset_next = offset_reg; + payload_offset_next = offset_reg; + ip_hdr_cksum_next = '0; + + flag_next = flag_reg | eth_type_flags; + state_next = eth_type_state; + + flag_next[FLG_VLAN_C] = 1'b1; + end + // IPv4 header + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |Version| IHL |Type of Service| Total Length | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Identification |Flags| Fragment Offset | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Time to Live | Protocol | Header Checksum | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Source Address | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Destination Address | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Options | Padding | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // from https://www.ietf.org/rfc/rfc791.txt + STATE_IPV4_1: begin + hdr_len_next = 9'(pkt_data_be[27:24]-1); + payload_len_next = pkt_data_be[15:0] - 4; + + // store DSCP and ECN + meta_ram_b_wr_data = {pkt_data_be[15:0], pkt_data_be[31:16]}; + meta_ram_b_wr_strb = 4'b0001; + meta_ram_b_wr_addr = {meta_wr_slot_reg[0], 4'd2}; + meta_ram_b_wr_en = 1'b1; + + if (pkt_data_be[31:28] == 4'd4) begin + flag_next[FLG_IPV4] = 1'b1; + state_next = STATE_IPV4_2; + end else begin + state_next = STATE_FINISH_1; + end + end + STATE_IPV4_2: begin + // store IP ID + meta_ram_a_wr_data = {pkt_data_be[15:0], pkt_data_be[31:16]}; + meta_ram_a_wr_strb = 4'b0011; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd7}; + meta_ram_a_wr_en = 1'b1; + + if (pkt_data_be[13] || pkt_data_be[12:0] != 0) begin + // MF bit set or nonzero fragment offset + flag_next[FLG_FRAG] = 1'b1; + end + + state_next = STATE_IPV4_3; + end + STATE_IPV4_3: begin + // store TTL and protocol + meta_ram_b_wr_data = {pkt_data_be[15:0], pkt_data_be[31:16]}; // TODO check this + meta_ram_b_wr_strb = 4'b0011; + meta_ram_b_wr_addr = {meta_wr_slot_reg[0], 4'd7}; + meta_ram_b_wr_en = 1'b1; + + next_hdr_next = pkt_data_be[23:16]; + + state_next = STATE_IPV4_4; + end + STATE_IPV4_4: begin + // store source IP + meta_ram_a_wr_data = pkt_data; + meta_ram_a_wr_strb = 4'b1111; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd10}; + meta_ram_a_wr_en = 1'b1; + + hash_step = 1'b1; + + corr_cksum_next = corr_cksum_reg; + + state_next = STATE_IPV4_5; + end + STATE_IPV4_5: begin + // store dest IP + meta_ram_a_wr_data = pkt_data; + meta_ram_a_wr_strb = 4'b1111; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd8}; + meta_ram_a_wr_en = 1'b1; + + hash_step = 1'b1; + + corr_cksum_next = corr_cksum_reg; + + flag_next = next_hdr_flags | flag_reg; + state_next = next_hdr_state; + + flag_next[FLG_L3_BAD_CKSUM] = (ip_hdr_cksum_next[15:0] ^ 16'(ip_hdr_cksum_next[20:16])) != 16'hffff; + + l4_offset_next = offset_reg; + payload_offset_next = offset_reg; + + if (hdr_len_reg > 1) begin + state_next = STATE_IPV4_6; + end + end + STATE_IPV4_6: begin + flag_next = next_hdr_flags | flag_reg; + state_next = next_hdr_state; + + flag_next[FLG_L3_OPT_PRSNT] = 1'b1; + flag_next[FLG_L3_BAD_CKSUM] = (ip_hdr_cksum_next[15:0] ^ 16'(ip_hdr_cksum_next[20:16])) != 16'hffff; + + l4_offset_next = offset_reg; + payload_offset_next = offset_reg; + + if (hdr_len_reg > 1) begin + state_next = STATE_IPV4_6; + end + end + // IPv6 header + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |Version| Traffic Class | Flow Label | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Payload Length | Next Header | Hop Limit | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | | + // + + + // | | + // + Source Address + + // | | + // + + + // | | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | | + // + + + // | | + // + Destination Address + + // | | + // + + + // | | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // from https://www.ietf.org/rfc/rfc2460.txt + STATE_IPV6_1: begin + // store flow label + meta_ram_a_wr_data = pkt_data_be; + meta_ram_a_wr_strb = 4'b0111; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd7}; + meta_ram_a_wr_en = 1'b1; + + // store DSCP and ECN + meta_ram_b_wr_data = {24'd0, pkt_data_be[27:20]}; + meta_ram_b_wr_strb = 4'b0001; + meta_ram_b_wr_addr = {meta_wr_slot_reg[0], 4'd2}; + meta_ram_b_wr_en = 1'b1; + + if (pkt_data_be[31:28] == 4'd6) begin + flag_next[FLG_IPV6] = 1'b1; + state_next = STATE_IPV6_2; + end else begin + state_next = STATE_FINISH_1; + end + end + STATE_IPV6_2: begin + // store next header, hop limit + meta_ram_b_wr_data = pkt_data; // TODO check this + meta_ram_b_wr_strb = 4'b0011; + meta_ram_b_wr_addr = {meta_wr_slot_reg[0], 4'd7}; + meta_ram_b_wr_en = 1'b1; + + payload_len_next = pkt_data_be[31:16]; + next_hdr_next = pkt_data_be[15:8]; + + state_next = STATE_IPV6_3; + end + STATE_IPV6_3: begin + // store source IP + meta_ram_a_wr_data = pkt_data; + meta_ram_a_wr_strb = 4'b1111; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd10}; + meta_ram_a_wr_en = 1'b1; + + hash_step = 1'b1; + + payload_len_next = payload_len_reg; + corr_cksum_next = corr_cksum_reg; + + state_next = STATE_IPV6_4; + end + STATE_IPV6_4: begin + // store source IP + meta_ram_b_wr_data = pkt_data; + meta_ram_b_wr_strb = 4'b1111; + meta_ram_b_wr_addr = {meta_wr_slot_reg[0], 4'd10}; + meta_ram_b_wr_en = 1'b1; + + hash_step = 1'b1; + + payload_len_next = payload_len_reg; + corr_cksum_next = corr_cksum_reg; + + state_next = STATE_IPV6_5; + end + STATE_IPV6_5: begin + // store source IP + meta_ram_a_wr_data = pkt_data; + meta_ram_a_wr_strb = 4'b1111; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd11}; + meta_ram_a_wr_en = 1'b1; + + hash_step = 1'b1; + + payload_len_next = payload_len_reg; + corr_cksum_next = corr_cksum_reg; + + state_next = STATE_IPV6_6; + end + STATE_IPV6_6: begin + // store source IP + meta_ram_b_wr_data = pkt_data; + meta_ram_b_wr_strb = 4'b1111; + meta_ram_b_wr_addr = {meta_wr_slot_reg[0], 4'd11}; + meta_ram_b_wr_en = 1'b1; + + hash_step = 1'b1; + + payload_len_next = payload_len_reg; + corr_cksum_next = corr_cksum_reg; + + state_next = STATE_IPV6_7; + end + STATE_IPV6_7: begin + // store dest IP + meta_ram_a_wr_data = pkt_data; + meta_ram_a_wr_strb = 4'b1111; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd8}; + meta_ram_a_wr_en = 1'b1; + + hash_step = 1'b1; + + payload_len_next = payload_len_reg; + corr_cksum_next = corr_cksum_reg; + + state_next = STATE_IPV6_8; + end + STATE_IPV6_8: begin + // store dest IP + meta_ram_b_wr_data = pkt_data; + meta_ram_b_wr_strb = 4'b1111; + meta_ram_b_wr_addr = {meta_wr_slot_reg[0], 4'd8}; + meta_ram_b_wr_en = 1'b1; + + hash_step = 1'b1; + + payload_len_next = payload_len_reg; + corr_cksum_next = corr_cksum_reg; + + state_next = STATE_IPV6_9; + end + STATE_IPV6_9: begin + // store dest IP + meta_ram_a_wr_data = pkt_data; + meta_ram_a_wr_strb = 4'b1111; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd9}; + meta_ram_a_wr_en = 1'b1; + + hash_step = 1'b1; + + payload_len_next = payload_len_reg; + corr_cksum_next = corr_cksum_reg; + + state_next = STATE_IPV6_10; + end + STATE_IPV6_10: begin + // store dest IP + meta_ram_b_wr_data = pkt_data; + meta_ram_b_wr_strb = 4'b1111; + meta_ram_b_wr_addr = {meta_wr_slot_reg[0], 4'd9}; + meta_ram_b_wr_en = 1'b1; + + hash_step = 1'b1; + + payload_len_next = payload_len_reg; + corr_cksum_next = corr_cksum_reg; + + l4_offset_next = offset_reg; + payload_offset_next = offset_reg; + + flag_next = next_hdr_flags | flag_reg; + state_next = next_hdr_state; + end + // IPv6 extension header + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Next Header | Hdr Ext Len | | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + // | | + // . . + // . Content . + // . . + // | | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // from https://www.ietf.org/rfc/rfc2460.txt + STATE_EXT_HDR_1: begin + if (hdr_len_reg <= 1) begin + next_hdr_next = pkt_data_be[31:24]; + hdr_len_next = {pkt_data_be[23:16], 1'b1}; + end + + l4_offset_next = offset_reg; + payload_offset_next = offset_reg; + + flag_next = next_hdr_flags | flag_reg; + state_next = next_hdr_state; + + if (hdr_len_reg > 1) begin + state_next = STATE_EXT_HDR_1; + end + end + // TCP header + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Source Port | Destination Port | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Sequence Number | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Acknowledgment Number | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Data | |U|A|P|R|S|F| | + // | Offset| Reserved |R|C|S|S|Y|I| Window | + // | | |G|K|H|T|N|N| | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Checksum | Urgent Pointer | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Options | Padding | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | data | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // from https://www.ietf.org/rfc/rfc793.txt + STATE_TCP_1: begin + // store ports + meta_ram_a_wr_data = pkt_data_be; + meta_ram_a_wr_strb = 4'b1111; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd12}; + meta_ram_a_wr_en = 1'b1; + + hash_step = 1'b1; + + corr_cksum_next = corr_cksum_reg - 24'(payload_len_reg); + + flag_next[FLG_TCP] = 1'b1; + + state_next = STATE_TCP_2; + end + STATE_TCP_2: begin + // store sequence number + meta_ram_a_wr_data = pkt_data_be; + meta_ram_a_wr_strb = 4'b1111; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd14}; + meta_ram_a_wr_en = 1'b1; + + corr_cksum_next = corr_cksum_reg - 24'(PROTO_TCP); + + state_next = STATE_TCP_3; + end + STATE_TCP_3: begin + // store ack number + meta_ram_b_wr_data = pkt_data_be; + meta_ram_b_wr_strb = 4'b1111; + meta_ram_b_wr_addr = {meta_wr_slot_reg[0], 4'd14}; + meta_ram_b_wr_en = 1'b1; + + corr_cksum_next = corr_cksum_reg; + + state_next = STATE_TCP_4; + end + STATE_TCP_4: begin + // store flags + meta_ram_b_wr_data = {pkt_data_be[15:0], pkt_data_be[31:16]}; + meta_ram_b_wr_strb = 4'b0001; + meta_ram_b_wr_addr = {meta_wr_slot_reg[0], 4'd12}; + meta_ram_b_wr_en = 1'b1; + + // store window + meta_ram_a_wr_data = pkt_data_be; + meta_ram_a_wr_strb = 4'b0011; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd13}; + meta_ram_a_wr_en = 1'b1; + + hdr_len_next = 9'(pkt_data_be[31:28]); + + corr_cksum_next = corr_cksum_reg; + + state_next = STATE_TCP_5; + end + STATE_TCP_5: begin + // store urgent pointer + meta_ram_a_wr_data = {pkt_data_be[15:0], pkt_data_be[31:16]}; + meta_ram_a_wr_strb = 4'b1100; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd13}; + meta_ram_a_wr_en = 1'b1; // TODO BROKEN + + payload_offset_next = offset_reg; + + corr_cksum_next = corr_cksum_reg; + + if (hdr_len_reg > 5) begin + flag_next[FLG_L4_OPT_PRSNT] = 1'b1; + state_next = STATE_TCP_5; + end else begin + state_next = STATE_FINISH_1; + end + end + // UDP header + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Source Port | Destination Port | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Length | Checksum | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // from https://www.ietf.org/rfc/rfc768.txt + STATE_UDP_1: begin + // store ports + meta_ram_a_wr_data = pkt_data_be; + meta_ram_a_wr_strb = 4'b1111; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd12}; + meta_ram_a_wr_en = 1'b1; + + hash_step = 1'b1; + + corr_cksum_next = corr_cksum_reg - 24'(payload_len_reg); + + flag_next[FLG_UDP] = 1'b1; + flag_next[FLG_L4_BAD_LEN] = {s_axis_pkt.tdata[23:16], s_axis_pkt.tdata[31:24]} != payload_len_reg; + + state_next = STATE_UDP_2; + end + STATE_UDP_2: begin + corr_cksum_next = corr_cksum_reg - 24'(PROTO_UDP); + + payload_offset_next = offset_reg; + + state_next = STATE_FINISH_1; + end + // Done parsing + // Either we got to the payload, an unknown or invalid header, or a fragmented packet + STATE_FINISH_1: begin + flag_next[FLG_PARSE_DONE] = run_reg; + + // store flags + meta_ram_a_wr_data = flag_next; + meta_ram_a_wr_strb = 4'b1111; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd0}; + meta_ram_a_wr_en = 1'b1; + + // store offsets + meta_ram_b_wr_data[7:0] = l3_offset_reg; + meta_ram_b_wr_data[15:8] = l4_offset_reg; + meta_ram_b_wr_data[23:16] = '0; + meta_ram_b_wr_data[31:24] = payload_offset_reg; + meta_ram_b_wr_strb = 4'b1111; + meta_ram_b_wr_addr = {meta_wr_slot_reg[0], 4'd1}; + meta_ram_b_wr_en = 1'b1; + + payload_len_next = payload_len_reg; + corr_cksum_next = 24'({8'd0, corr_cksum_reg[23:16]} + corr_cksum_reg[15:0]); + + run_next = 1'b0; + + state_next = STATE_FINISH_2; + end + STATE_FINISH_2: begin + // store hash + meta_ram_a_wr_data = hash_value; + meta_ram_a_wr_strb = 4'b1111; + meta_ram_a_wr_addr = {meta_wr_slot_reg[0], 4'd1}; + meta_ram_a_wr_en = 1'b1; + + // store payload len and full-packet sum + meta_ram_b_wr_data[15:0] = payload_len_reg; + meta_ram_b_wr_data[31:16] = {8'd0, corr_cksum_reg[23:16]} + corr_cksum_reg[15:0]; + meta_ram_b_wr_strb = 4'b1111; + meta_ram_b_wr_addr = {meta_wr_slot_reg[0], 4'd0}; + meta_ram_b_wr_en = 1'b1; + + payload_len_next = payload_len_reg; + corr_cksum_next = 24'({8'd0, corr_cksum_reg[23:16]} + corr_cksum_reg[15:0]); + + meta_wr_slot_next = meta_wr_slot_reg + 1; + + state_next = STATE_IDLE; + end + default: begin + state_next = STATE_IDLE; + end + endcase + end + + // force parser into finish state at end of header + if (run_reg && !frame_reg) begin + if (state_reg != STATE_FINISH_1) begin + state_next = STATE_FINISH_1; + end + end + + // read out metadata + if (!meta_empty) begin + meta_ram_a_rd_addr = {meta_rd_slot_reg[0], meta_rd_ptr_reg}; + meta_ram_b_rd_addr = {meta_rd_slot_reg[0], meta_rd_ptr_reg}; + if (!meta_rd_data_valid_reg || m_axis_meta.tready) begin + meta_ram_a_rd_en = 1'b1; + meta_ram_b_rd_en = 1'b1; + meta_rd_data_valid_next = 1'b1; + meta_rd_data_last_next = 1'b0; + meta_rd_ptr_next = meta_rd_ptr_reg + 1; + + if (&meta_rd_ptr_reg) begin + meta_rd_ptr_next = '0; + meta_rd_data_last_next = 1'b1; + meta_rd_slot_next = meta_rd_slot_reg + 1; + end + end + end +end + +always_ff @(posedge clk) begin + if (meta_ram_a_wr_en) begin + for (integer i = 0; i < 4; i = i + 1) begin + if (meta_ram_a_wr_strb[i]) begin + meta_ram_a[meta_ram_a_wr_addr][i*8 +: 8] = meta_ram_a_wr_data[i*8 +: 8]; + end + end + end + if (meta_ram_a_rd_en) begin + meta_ram_a_rd_data_reg <= meta_ram_a[meta_ram_a_rd_addr]; + end + + if (meta_ram_b_wr_en) begin + for (integer i = 0; i < 4; i = i + 1) begin + if (meta_ram_b_wr_strb[i]) begin + meta_ram_b[meta_ram_b_wr_addr][i*8 +: 8] = meta_ram_b_wr_data[i*8 +: 8]; + end + end + end + if (meta_ram_b_rd_en) begin + meta_ram_b_rd_data_reg <= meta_ram_b[meta_ram_b_rd_addr]; + end +end + +always_ff @(posedge clk) begin + state_reg <= state_next; + + frame_reg <= frame_next; + run_reg <= run_next; + flag_reg <= flag_next; + next_hdr_reg <= next_hdr_next; + hdr_len_reg <= hdr_len_next; + offset_reg <= offset_next; + l3_offset_reg <= l3_offset_next; + l4_offset_reg <= l4_offset_next; + payload_offset_reg <= payload_offset_next; + payload_len_reg <= payload_len_next; + + ip_hdr_cksum_reg <= ip_hdr_cksum_next; + corr_cksum_reg <= corr_cksum_next; + + s_axis_pkt_tready_reg <= s_axis_pkt_tready_next; + + if (s_axis_pkt.tready && s_axis_pkt.tvalid) begin + pkt_data_reg <= s_axis_pkt.tdata[31:16]; + end + + meta_wr_slot_reg <= meta_wr_slot_next; + meta_rd_slot_reg <= meta_rd_slot_next; + meta_rd_ptr_reg <= meta_rd_ptr_next; + + meta_rd_data_valid_reg <= meta_rd_data_valid_next; + meta_rd_data_last_reg <= meta_rd_data_last_next; + + if (rst) begin + state_reg <= STATE_IDLE; + + frame_reg <= 1'b0; + run_reg <= 1'b0; + + s_axis_pkt_tready_reg <= 1'b0; + + meta_wr_slot_reg <= '0; + meta_rd_slot_reg <= '0; + meta_rd_ptr_reg <= '0; + + meta_rd_data_valid_reg <= 1'b0; + end +end + +endmodule + +`resetall diff --git a/src/zircon/tb/zircon_ip_rx_parse/Makefile b/src/zircon/tb/zircon_ip_rx_parse/Makefile new file mode 100644 index 0000000..11c3cd4 --- /dev/null +++ b/src/zircon/tb/zircon_ip_rx_parse/Makefile @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: CERN-OHL-S-2.0 +# +# Copyright (c) 2025 FPGA Ninja, LLC +# +# Authors: +# - Alex Forencich + +TOPLEVEL_LANG = verilog + +SIM ?= verilator +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +RTL_DIR = ../../rtl +LIB_DIR = ../../lib +TAXI_SRC_DIR = $(LIB_DIR)/taxi/src + +DUT = zircon_ip_rx_parse +COCOTB_TEST_MODULES = test_$(DUT) +COCOTB_TOPLEVEL = test_$(DUT) +MODULE = $(COCOTB_TEST_MODULES) +TOPLEVEL = $(COCOTB_TOPLEVEL) +VERILOG_SOURCES += $(COCOTB_TOPLEVEL).sv +VERILOG_SOURCES += $(RTL_DIR)/$(DUT).sv +VERILOG_SOURCES += $(TAXI_SRC_DIR)/axis/rtl/taxi_axis_if.sv + +# handle file list files +process_f_file = $(call process_f_files,$(addprefix $(dir $1),$(shell cat $1))) +process_f_files = $(foreach f,$1,$(if $(filter %.f,$f),$(call process_f_file,$f),$f)) +uniq_base = $(if $1,$(call uniq_base,$(foreach f,$1,$(if $(filter-out $(notdir $(lastword $1)),$(notdir $f)),$f,))) $(lastword $1)) +VERILOG_SOURCES := $(call uniq_base,$(call process_f_files,$(VERILOG_SOURCES))) + +# module parameters +export PARAM_DATA_W := 32 +export PARAM_META_W := 64 +export PARAM_IPV6_EN := 1 +export PARAM_HASH_EN := 1 + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v))) +else ifeq ($(SIM), verilator) + COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v))) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + VERILATOR_TRACE = 1 + endif +endif + +include $(shell cocotb-config --makefiles)/Makefile.sim diff --git a/src/zircon/tb/zircon_ip_rx_parse/test_zircon_ip_rx_parse.py b/src/zircon/tb/zircon_ip_rx_parse/test_zircon_ip_rx_parse.py new file mode 100644 index 0000000..dea355f --- /dev/null +++ b/src/zircon/tb/zircon_ip_rx_parse/test_zircon_ip_rx_parse.py @@ -0,0 +1,536 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: CERN-OHL-S-2.0 +""" + +Copyright (c) 2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +""" + +import ipaddress +import logging +import os +import socket +import struct + +from enum import IntFlag + +import scapy.config +import scapy.utils +import scapy.pton_ntop +from scapy.layers.l2 import Ether, Dot1Q, Dot1AD, ARP +from scapy.layers.inet import IP, ICMP, UDP, TCP +from scapy.layers.inet import IPOption_MTU_Probe +from scapy.layers.inet6 import IPv6, ICMPv6ND_NS +from scapy.layers.inet6 import IPv6ExtHdrFragment, IPv6ExtHdrHopByHop, RouterAlert + +import cocotb_test.simulator + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge +from cocotb.regression import TestFactory + +from cocotbext.axi import AxiStreamBus, AxiStreamSource, AxiStreamSink, AxiStreamFrame + + +# don't hide ports +scapy.config.conf.noenum.add(TCP.sport, TCP.dport) +scapy.config.conf.noenum.add(UDP.sport, UDP.dport) + + +hash_key = [ + 0x6d, 0x5a, 0x56, 0xda, 0x25, 0x5b, 0x0e, 0xc2, + 0x41, 0x67, 0x25, 0x3d, 0x43, 0xa3, 0x8f, 0xb0, + 0xd0, 0xca, 0x2b, 0xcb, 0xae, 0x7b, 0x30, 0xb4, + 0x77, 0xcb, 0x2d, 0xa3, 0x80, 0x30, 0xf2, 0x0c, + 0x6a, 0x42, 0xb7, 0x3b, 0xbe, 0xac, 0x01, 0xfa +] + + +def hash_toep(data, key=hash_key): + k = len(key)*8-32 + key = int.from_bytes(key, 'big') + + h = 0 + + for b in data: + for i in range(8): + if b & 0x80 >> i: + h ^= (key >> k) & 0xffffffff + k -= 1 + + return h + + +def tuple_pack(src_ip, dest_ip, src_port=None, dest_port=None): + src_ip = ipaddress.ip_address(src_ip) + dest_ip = ipaddress.ip_address(dest_ip) + data = b'' + if src_ip.version == 6 or dest_ip.version == 6: + data += src_ip.packed + data += dest_ip.packed + else: + data += src_ip.packed + data += dest_ip.packed + if src_port is not None and dest_port is not None: + data += src_port.to_bytes(2, 'big') + dest_port.to_bytes(2, 'big') + return data + + +class ParserFlags(IntFlag): + FLG_VLAN_S = 2**1 + FLG_VLAN_C = 2**2 + FLG_IPV4 = 2**3 + FLG_IPV6 = 2**4 + FLG_FRAG = 2**5 + FLG_ARP = 2**6 + FLG_ICMP = 2**7 + FLG_TCP = 2**8 + FLG_UDP = 2**9 + FLG_AH = 2**10 + FLG_ESP = 2**11 + FLG_IP_OPT_PRSNT = 2**16 + FLG_TCP_OPT_PRSNT = 2**17 + FLG_L3_BAD_CKSUM = 2**24 + FLG_L4_BAD_LEN = 2**25 + FLG_PARSE_DONE = 2**31 + + +class TB: + def __init__(self, dut): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + cocotb.start_soon(Clock(dut.clk, 3.2, units="ns").start()) + + self.source = AxiStreamSource(AxiStreamBus.from_entity(dut.s_axis_pkt), dut.clk, dut.rst) + self.sink = AxiStreamSink(AxiStreamBus.from_entity(dut.m_axis_meta), dut.clk, dut.rst) + + async def reset(self): + self.dut.rst.setimmediatevalue(0) + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + self.dut.rst.value = 1 + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + self.dut.rst.value = 0 + await RisingEdge(self.dut.clk) + await RisingEdge(self.dut.clk) + + +async def run_test(dut): + + tb = TB(dut) + + await tb.reset() + + test_pkts = [] + + payload = bytearray(range(64)) + + ip_id = 0 + + l2hdrs = [] + + # Ethernet + eth = Ether(src='5A:51:52:53:54:55', dst='DA:D1:D2:D3:D4:D5') + l2hdrs.append(eth) + + # Ethernet with 802.1Q VLAN + eth = Ether(src='5A:51:52:53:54:55', dst='DA:D1:D2:D3:D4:D5') + vlan = Dot1Q(vlan=123) + l2hdrs.append(eth / vlan) + + # Ethernet with 802.1Q QinQ + eth = Ether(src='5A:51:52:53:54:55', dst='DA:D1:D2:D3:D4:D5') + vlan = Dot1AD(vlan=456) + l2hdrs.append(eth / vlan) + + # Ethernet with 802.1Q QinQ and VLAN + eth = Ether(src='5A:51:52:53:54:55', dst='DA:D1:D2:D3:D4:D5') + vlan = Dot1AD(vlan=456) / Dot1Q(vlan=123) + l2hdrs.append(eth / vlan) + + for l2hdr in l2hdrs: + + # Raw ethernet + test_pkts.append(l2hdr / payload) + + # ARP + arp = ARP(hwtype=1, ptype=0x0800, hwlen=6, plen=4, op=2, + hwsrc='5A:51:52:53:54:55', psrc='192.168.1.100', + hwdst='DA:D1:D2:D3:D4:D5', pdst='192.168.1.101') + test_pkts.append(l2hdr / arp) + + l3hdrs = [] + + # IPv4 + ip = IP(src='10.1.0.1', dst='10.2.0.1', id=ip_id) + l3hdrs.append(ip) + + # IPv4 (fragmented) + ip = IP(src='10.1.0.1', dst='10.2.0.1', flags=1, id=ip_id) + l3hdrs.append(ip) + + # IPv4 with options + ip = IP(src='10.1.0.1', dst='10.2.0.1', id=ip_id, options=[IPOption_MTU_Probe()]) + l3hdrs.append(ip) + + # IPv6 + ip6 = IPv6(src='fd12:3456:789a:1::1', dst='fd12:3456:789a:2::1', fl=ip_id) + l3hdrs.append(ip6) + + # IPv6 with extensions (fragmented) + ip6 = IPv6(src='fd12:3456:789a:1::1', dst='fd12:3456:789a:2::1', fl=ip_id) + frag = IPv6ExtHdrFragment() + l3hdrs.append(ip6 / frag) + + # IPv6 with extensions + ip6 = IPv6(src='fd12:3456:789a:1::1', dst='fd12:3456:789a:2::1', fl=ip_id) + hbh = IPv6ExtHdrHopByHop(options=[RouterAlert()]) + l3hdrs.append(ip6 / hbh) + + # IPv6 with extensions 2 + ip6 = IPv6(src='fd12:3456:789a:1::1', dst='fd12:3456:789a:2::1', fl=ip_id) + hbh = IPv6ExtHdrHopByHop(options=[RouterAlert(), RouterAlert(), RouterAlert(), RouterAlert()]) + l3hdrs.append(ip6 / hbh) + + for l3hdr in l3hdrs: + + l3hdr = l3hdr.copy() + if IP in l3hdr: + l3hdr.id = ip_id + if IPv6 in l3hdr: + l3hdr.fl = ip_id + + # IP (empty) + if IP in l3hdr: + hdr = l3hdr.copy() + hdr.proto = 59 + test_pkts.append(l2hdr / hdr) + else: + test_pkts.append(l2hdr / l3hdr) + + # IP (unsupported protocol) + if IP in l3hdr: + hdr = l3hdr.copy() + hdr.proto = 59 + test_pkts.append(l2hdr / hdr / payload) + else: + test_pkts.append(l2hdr / l3hdr / payload) + + if IP in l3hdr: + # ICMP + icmp = ICMP(type=8) + test_pkts.append(l2hdr / l3hdr / icmp / payload) + + if IPv6 in l3hdr: + # ICMPv6 / NDP + ns = ICMPv6ND_NS(tgt='::') + test_pkts.append(l2hdr / l3hdr / ns) + + # UDP (empty) + udp = UDP(sport=ip_id, dport=0x1000+ip_id) + test_pkts.append(l2hdr / l3hdr / udp) + + # UDP + udp = UDP(sport=ip_id, dport=0x1000+ip_id) + test_pkts.append(l2hdr / l3hdr / udp / payload) + + # TCP (empty) + tcp = TCP(sport=ip_id, dport=0x1000+ip_id, seq=54321, ack=12345, window=8192) + test_pkts.append(l2hdr / l3hdr / tcp) + + # TCP with options (empty) + tcp = TCP(sport=ip_id, dport=0x1000+ip_id, seq=54321, ack=12345, window=8192, options=[('Timestamp',(0,0))]) + test_pkts.append(l2hdr / l3hdr / tcp) + + # TCP + tcp = TCP(sport=ip_id, dport=0x1000+ip_id, seq=54321, ack=12345, window=8192) + test_pkts.append(l2hdr / l3hdr / tcp / payload) + + # TCP with options + tcp = TCP(sport=ip_id, dport=0x1000+ip_id, seq=54321, ack=12345, window=8192, options=[('Timestamp',(0,0))]) + test_pkts.append(l2hdr / l3hdr / tcp / payload) + + ip_id += 1 + + for pkt in test_pkts: + tb.log.info("Packet: %r", pkt) + + pkt_b = pkt.build() + hdr = pkt_b[0:128] + + rx_csum = ~scapy.utils.checksum(bytes(pkt_b[14:])) & 0xffff + + await tb.source.send(AxiStreamFrame(hdr)) + + meta = await tb.sink.recv() + + tb.log.info("Metadata: %r", meta) + + flags, payload_len, pkt_sum = struct.unpack_from('HHB', meta.tdata, 16) + + tb.log.info("VLAN S-tag: 0x%04x", s_tag) + tb.log.info("VLAN C-tag: 0x%04x", c_tag) + tb.log.info("DSCP/ECN: 0x%02x", dscp_ecn) + + eth_dst = meta.tdata[24:30] + eth_src = meta.tdata[32:38] + eth_type = struct.unpack_from('>H', meta.tdata, 38)[0] + + tb.log.info("Eth dest: %s (%r)", scapy.utils.str2mac(eth_dst), eth_dst) + tb.log.info("Eth src: %s (%r)", scapy.utils.str2mac(eth_src), eth_src) + tb.log.info("Eth type: 0x%04x", eth_type) + + assert scapy.utils.mac2str(pkt[Ether].dst) == eth_dst + assert scapy.utils.mac2str(pkt[Ether].src) == eth_src + + ref_type = pkt[Ether].type + + # VLAN tags + if Dot1AD in pkt: + tag = struct.unpack_from('>H', meta.tdata, 16)[0] + tb.log.info("VLAN S-tag: 0x%04x", tag) + ref_type = pkt[Dot1AD].type + + assert ParserFlags.FLG_VLAN_S in flags + assert pkt[Dot1AD].vlan == tag & 0x3ff + else: + assert ParserFlags.FLG_VLAN_S not in flags + + if Dot1Q in pkt: + tag = struct.unpack_from('>H', meta.tdata, 18)[0] + tb.log.info("VLAN C-tag: 0x%04x", tag) + ref_type = pkt[Dot1Q].type + + assert ParserFlags.FLG_VLAN_C in flags + assert pkt[Dot1Q].vlan == tag & 0x3ff + else: + assert ParserFlags.FLG_VLAN_C not in flags + + assert ref_type == eth_type + + # IPv4 + if IP in pkt: + ip_dst = meta.tdata[64:68] + ip_src = meta.tdata[80:84] + + tb.log.info("IPv4 dest: %s (%r)", scapy.utils.inet_ntoa(ip_dst), ip_dst) + tb.log.info("IPv4 src: %s (%r)", scapy.utils.inet_ntoa(ip_src), ip_src) + + assert ParserFlags.FLG_IPV4 in flags + assert ParserFlags.FLG_L3_BAD_CKSUM not in flags + + assert scapy.utils.inet_aton(pkt[IP].src) == ip_src + assert scapy.utils.inet_aton(pkt[IP].dst) == ip_dst + + if TCP in pkt and not (pkt[IP].flags & 1 or pkt[IP].frag): + hash_val = hash_toep(tuple_pack(pkt[IP].src, pkt[IP].dst, pkt[TCP].sport, pkt[TCP].dport), hash_key) + assert hash_val == rss_hash + elif UDP in pkt and not (pkt[IP].flags & 1 or pkt[IP].frag): + hash_val = hash_toep(tuple_pack(pkt[IP].src, pkt[IP].dst, pkt[UDP].sport, pkt[UDP].dport), hash_key) + assert hash_val == rss_hash + else: + hash_val = hash_toep(tuple_pack(pkt[IP].src, pkt[IP].dst), hash_key) + assert hash_val == rss_hash + else: + assert ParserFlags.FLG_IPV4 not in flags + assert ParserFlags.FLG_L3_BAD_CKSUM not in flags + + # IPv6 + if IPv6 in pkt: + ip_dst = meta.tdata[64:80] + ip_src = meta.tdata[80:96] + + tb.log.info("IPv6 dest: %s (%r)", scapy.pton_ntop.inet_ntop(socket.AF_INET6, ip_dst), ip_dst) + tb.log.info("IPv6 src: %s (%r)", scapy.pton_ntop.inet_ntop(socket.AF_INET6, ip_src), ip_src) + + assert ParserFlags.FLG_IPV6 in flags + assert ParserFlags.FLG_L3_BAD_CKSUM not in flags + + assert scapy.pton_ntop.inet_pton(socket.AF_INET6, pkt[IPv6].src) == ip_src + assert scapy.pton_ntop.inet_pton(socket.AF_INET6, pkt[IPv6].dst) == ip_dst + + if TCP in pkt and IPv6ExtHdrFragment not in pkt: + hash_val = hash_toep(tuple_pack(pkt[IPv6].src, pkt[IPv6].dst, pkt[TCP].sport, pkt[TCP].dport), hash_key) + assert hash_val == rss_hash + elif UDP in pkt and IPv6ExtHdrFragment not in pkt: + hash_val = hash_toep(tuple_pack(pkt[IPv6].src, pkt[IPv6].dst, pkt[UDP].sport, pkt[UDP].dport), hash_key) + assert hash_val == rss_hash + else: + hash_val = hash_toep(tuple_pack(pkt[IPv6].src, pkt[IPv6].dst), hash_key) + assert hash_val == rss_hash + else: + assert ParserFlags.FLG_IPV6 not in flags + assert ParserFlags.FLG_L3_BAD_CKSUM not in flags + + # ARP + if ARP in pkt: + assert ParserFlags.FLG_ARP in flags + else: + assert ParserFlags.FLG_ARP not in flags + + if ParserFlags.FLG_FRAG not in flags: + # TCP + if TCP in pkt: + dp, sp, tcp_flags = struct.unpack_from('