Skip to content

adilsondias-engineer/06-fpga-udp-parser-mii-v4

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MII Ethernet Receiver with IP Parser Integration (Phase 1D)

Integration of standalone IP header parser into full Ethernet receiver system. Completes Phase 1 MII receiver implementation with MAC and IP layer parsing, clock domain crossing, and hardware validation on Arty A7-100T.


Overview

Phase 1D integrates the IP parser module (developed and validated in Phase 1C/v3a) into the complete MII Ethernet receiver pipeline. The system now parses Ethernet frames through MAC and IP layers, extracting protocol fields and validating checksums in real-time. All processing occurs at line rate (100 Mbps) with proper clock domain crossing between the 25 MHz MII interface and 100 MHz system clock.

Trading Relevance:

  • Multi-layer protocol parsing (MAC → IP → UDP path toward market data)
  • Clock domain crossing patterns for multi-rate systems
  • Header validation and filtering at wire speed
  • Statistics aggregation for monitoring and diagnostics

Architecture

Module Hierarchy

mii_eth_top (100 MHz system clock, 25 MHz Ethernet RX clock)
├── clk_wiz_0 (PLL: 100 MHz → 25 MHz for PHY TX, 100 MHz buffered)
├── phy_reset_ctrl (20ms reset generation for DP83848J PHY)
├── mii_receiver (25 MHz domain)
│   ├── Nibble-to-byte assembly
│   ├── Preamble/SFD detection and stripping
│   └── Frame valid generation
├── mac_parser (25 MHz domain)
│   ├── Destination MAC filtering
│   ├── Frame parsing and statistics
│   └── Byte stream output for IP parser
├── ip_parser (25 MHz domain) -> NEW
│   ├── EtherType detection (0x0800 for IPv4)
│   ├── IP header extraction (src/dst IP, protocol, length)
│   ├── Checksum validation (16-bit one's complement)
│   └── Version/IHL validation
├── Clock Domain Crossing (25 MHz → 100 MHz)
│   ├── 2FF synchronizers for single-bit signals
│   └── Multi-bit sampling on stable pulse edges
├── stats_counter (100 MHz domain)
│   ├── Frame and IP packet counters
│   ├── Error aggregation
│   └── LED display with mode selection
├── mdio_master (100 MHz domain)
│   └── PHY register access for status/debug
└── uart_tx (100 MHz domain)
    └── Debug output for packet information

Data Flow

MII PHY (DP83848J)
    ↓ (25 MHz, 4-bit nibbles)
mii_receiver
    ↓ (25 MHz, bytes with frame_valid)
mac_parser
    ↓ (25 MHz, filtered byte stream + byte_counter)
ip_parser
    ↓ (25 MHz, IP fields + validation flags)
2FF Synchronizers
    ↓ (100 MHz, synchronized signals)
stats_counter / uart_tx
    ↓
LED Display / UART Output

Clock Domains

  1. eth_rx_clk (25 MHz) - Recovered from PHY, drives MII receiver

    • mii_receiver module
    • mac_parser module
    • ip_parser module (NEW - runs in same domain as MAC parser)
  2. clk (100 MHz) - System clock from PLL

    • stats_counter module
    • mdio_master module
    • uart_tx module
    • Button debouncing and LED control

Clock Domain Crossing:

  • Single-bit signals (ip_valid, error flags) use 2FF synchronizers
  • Multi-bit signal (ip_protocol) sampled on ip_valid pulse edge, then synchronized

Hardware Requirements

  • Board: Xilinx Arty A7-100T Development Board
  • FPGA: Artix-7 XC7A100T-1CSG324C
  • PHY: TI DP83848J (10/100 Mbps Ethernet, MII interface)
  • Tools: AMD Vivado Design Suite 2020.2 or later
  • Test Equipment:
    • USB-to-Ethernet adapter (or direct PC Ethernet connection)
    • Python 3 with Scapy library for packet generation

Pin Connections:

  • RJ-45 Ethernet jack (J11) to network
  • USB-UART (J10) for debug output at 115200 baud
  • LEDs (LD0-LD3) for statistics display
  • RGB LED (LD4) for link status and errors
  • Buttons (BTN0-BTN3) for reset and mode control

Quick Start

1. Build and Program

cd 06-udp-parser-mii-v4
build 06-udp-parser-mii-v4
prog 06-udp-parser-mii-v4

Or using Vivado directly:

vivado -mode batch -source build.tcl
vivado -mode batch -source program.tcl

2. Connect Hardware

  1. Connect Ethernet cable between PC and Arty board (J11)
  2. Connect USB cable for power and UART (J10)
  3. Observe LD4 RGB LED:
    • Green: Link established (100 Mbps)
    • Red: Errors detected

3. Run Hardware Tests

cd test
pip install scapy  # If not already installed
sudo python3 test_ip_ethernet.py

Expected Output:

============================================================
Test: Valid UDP Packet
============================================================
Packet summary: Ether / IP / UDP 192.168.1.10:12345 > 192.168.1.100:80 / Raw
IP: 192.168.1.10 -> 192.168.1.100
Protocol: 17 (udp)
Total Length: 38
Checksum: 0xB5E0
✓ Sent successfully!

Expected behavior:
  - LED increments
  - Protocol = 0x11 (UDP)
  - In Mode 2: LED shows 0x1 (0x11 & 0xF)

4. Cycle Display Modes

Press BTN3 to cycle through display modes:

  • Mode 0: Frame count (lower 4 bits) on LD0-LD3
  • Mode 1: MDIO register data (lower 4 bits) on LD0-LD3
  • Mode 2: IP protocol field (lower 4 bits) on LD0-LD3
    • UDP: 0x1 (protocol 0x11)
    • TCP: 0x6 (protocol 0x06)
    • ICMP: 0x1 (protocol 0x01)

Implementation Details

IP Parser Integration

Location: mii_eth_top.vhd:657-676

The IP parser instantiation receives MAC parser outputs directly:

ip_parser_inst: entity work.ip_parser
    port map (
        clk => eth_rx_clk,  -- Run in same domain as data (25 MHz)
        reset => mdio_rst,

        -- From MAC parser
        frame_valid => mac_frame_valid,
        data_in     => mac_data_out,
        byte_index  => to_integer(mac_byte_counter),

        -- Outputs (25 MHz domain)
        ip_valid        => ip_valid,
        ip_src          => ip_src,
        ip_dst          => ip_dst,
        ip_protocol     => ip_protocol,
        ip_total_length => ip_total_length,
        ip_checksum_ok  => ip_checksum_ok,
        ip_version_err  => ip_version_err,
        ip_ihl_err      => ip_ihl_err,
        ip_checksum_err => ip_checksum_err
    );

Design Rationale: Running IP parser in 25 MHz domain avoids clock domain crossing for high-bandwidth data stream. Only low-bandwidth outputs (flags and protocol field) are synchronized to 100 MHz system clock.

MAC Parser Modifications

Location: mac_parser.vhd

Added outputs to feed IP parser:

data_out     : out STD_LOGIC_VECTOR(7 downto 0);  -- Current byte for IP parser
bye_counter : out unsigned(10 downto 0)           -- Byte index in frame

These outputs provide the byte stream and position information required by the IP parser state machine.

Clock Domain Crossing

Location: mii_eth_top.vhd:692-729

Single-bit signals (ip_valid, checksum_ok, error flags) use 2-stage flip-flop synchronizers:

process(clk)
begin
    if rising_edge(clk) then
        -- 2FF synchronizer for single-bit signals
        ip_valid_sync1 <= ip_valid;
        ip_valid_sync2 <= ip_valid_sync1;

        ip_checksum_ok_sync1 <= ip_checksum_ok;
        ip_checksum_ok_sync2 <= ip_checksum_ok_sync1;
    end if;
end process;

Multi-bit signal (ip_protocol) sampled when ip_valid asserts, then synchronized:

-- Sample protocol when ip_valid pulses (stable multi-bit value)
if ip_valid = '1' then
    ip_protocol_sync1 <= ip_protocol;
end if;
ip_protocol_sync2 <= ip_protocol_sync1;

This avoids metastability on multi-bit busses by sampling only when data is stable.

Statistics Counter Integration

Location: stats_counter.vhd

Extended with IP packet statistics:

ip_valid_in        : in  std_logic;  -- Pulse on valid IP packet
ip_checksum_ok_in  : in  std_logic;  -- IP checksum validation
ip_protocol_in     : in  std_logic_vector(7 downto 0);  -- Protocol field

Tracks both MAC-level frame counts and IP-level packet counts separately.

Error Handling

Multiple error sources:

  1. rx_error - From mii_receiver (preamble, SFD, or length errors)
  2. stats_error - From stats_counter internal errors
  3. ip_checksum_err - IP checksum validation failures
  4. ip_version_err - IPv4 version field validation failures
  5. ip_ihl_err - IP header length validation failures

Aggregation:

led_rgb(2) <= rx_error or stats_error;  -- Red LED on any error

Separate error signals prevent multiple driver synthesis errors while allowing OR-based aggregation for LED output.

Display Modes

Location: mii_eth_top.vhd:746

led <= ip_protocol_sync2(3 downto 0) when debug_mode = "10" else
       current_reg(3 downto 0)        when debug_mode = "01" else
       frame_count_leds;

Mode selection uses 2-bit counter (debug_mode) toggled by BTN3 with debouncing.


Test Cases

The test script (test_ip_ethernet.py) validates IP parser functionality with six test scenarios:

Test Description Protocol Expected LED (Mode 2) Expected Behavior
1 Valid UDP 0x11 0x1 LED increments, checksum_ok='1'
2 Valid TCP 0x06 0x6 LED increments, checksum_ok='1'
3 Valid ICMP 0x01 0x1 LED increments, checksum_ok='1'
4 Invalid checksum 0x11 No increment, red LED asserts
5 Wrong MAC (broadcast) 0x11 Filtered by MAC parser
6 Burst (10 packets) 0x11 0x1 LED shows count = 0xA

Test Execution:

# Run all tests
sudo python3 test_ip_ethernet.py

# Run specific test
sudo python3 test_ip_ethernet.py --test 1

# Run burst with custom count
sudo python3 test_ip_ethernet.py --test 6 --burst 20

Bugs Fixed

Bug #1: Multiple Driver Nets for LED RGB Signal

Date: November 5, 2025 Location: mii_eth_top.vhd:654, mii_eth_top.vhd:759

Symptom:

Synthesis error:

Multiple Driver Nets: Net stats_counter_inst/led_rgb_OBUF[1] has multiple drivers:
  stats_counter_inst/led_rgb_OBUF[2]_inst_i_1/O, and
  mii_receiver/rx_error_reg/Q

Root Cause:

Signal rx_error had two drivers in conflicting hierarchy paths:

  1. Output port from mii_receiver module
  2. Connected to stats_counter led_error input port, which also drives led_rgb(2)

Port mapping led_error => rx_error created second driver through stats_counter logic.

Fix:

Created separate signal stats_error for stats_counter output:

-- Added new signal (line 293)
signal stats_error : std_logic;

-- Changed stats_counter port map (line 654)
led_error => stats_error  -- Use separate signal, not rx_error

-- Combined both error sources for LED (line 759)
led_rgb(2) <= rx_error or stats_error;

Verification:

Synthesis completes successfully. Both error types illuminate red LED correctly.

Lesson:

Avoid connecting output ports of different modules to the same signal when those modules also use the signal as an output. Use intermediate signals with explicit OR/AND logic for combining multiple error sources.


Bug #2: Scapy Packet Building - NoneType Format Error

Date: November 5, 2025 Location: test_ip_ethernet.py:118

Symptom:

Python test script crashes with format string error:

Total Length: None
Error: unsupported format string passed to NoneType.__format__

Root Cause:

Scapy does not calculate IP length and checksum fields until packet is explicitly built. Attempting to format None values (uncomputed fields) with format specifier caused TypeError.

Original code:

print(f"Total Length: {packet[IP].len}")  # len is None before building
print(f"Checksum: 0x{packet[IP].chksum:04X}")  # chksum is None

Fix:

Complete rewrite of packet building and display logic:

def send_test_packet(iface, description, packet):
    # Force packet construction to calculate auto-fields
    built_packet = packet.__class__(bytes(packet))

    if IP in built_packet:
        ip_layer = built_packet[IP]

        # Conditional formatting for potentially None fields
        if ip_layer.len is not None:
            print(f"Total Length: {ip_layer.len}")
        else:
            print(f"Total Length: auto")

        if ip_layer.chksum is not None:
            print(f"Checksum: 0x{ip_layer.chksum:04X}")
        else:
            print(f"Checksum: auto")

    # Send the built packet
    sendp(built_packet, iface=iface, verbose=False)

Also fixed string encoding: Raw(b"Hello FPGA") instead of Raw("Hello FPGA").

Verification:

All test cases run successfully with correct checksum values displayed.

Lesson:

Scapy auto-calculates fields lazily. Force packet building with packet.__class__(bytes(packet)) before accessing computed fields. Always check for None before formatting values.


Metrics

Development Status: Complete - Hardware verified Phase: 1D - IP Parser Integration Lines of Code:

  • mii_eth_top.vhd: ~800 lines
  • ip_parser.vhd: ~250 lines
  • mac_parser.vhd: ~200 lines
  • mii_receiver.vhd: ~150 lines
  • test_ip_ethernet.py: ~305 lines

FPGA Resource Utilization:

Resource Used Available Utilization
LUTs ~1,200 63,400 1.9%
FFs ~900 126,800 0.7%
Block RAM 0 135 0%
DSPs 0 240 0%

Timing:

  • WNS (Worst Negative Slack): 4.2ns (meets timing)
  • System Clock (100 MHz): 10ns period
  • Ethernet RX Clock (25 MHz): 40ns period

Development Time:

  • IP parser standalone development (Completed on Phase 1C(v3a)): ~10 hours
  • Integration into full system (Phase 1D): ~4 hours
  • Test script development and debugging: ~3 hours
  • Total: ~7 hours

Hardware Testing Results

Test Environment:

  • Arty A7-100T connected via USB Ethernet adapter
  • PC interface: enp0s20f0u1 (80:3f:5d:fb:17:63)
  • FPGA MAC: 00:0a:35:02:af:9a
  • Link speed: 100 Mbps full duplex

Test Results:

Test Packets Sent LED Count Mode 2 Display Status
Valid UDP 1 Increments 0x1 PASS
Valid TCP 1 Increments 0x6 PASS
Valid ICMP 1 Increments 0x1 PASS
Invalid checksum 1 No change PASS
Wrong MAC 1 No change PASS
Burst (10) 10 Shows 0xA 0x1 PASS

Performance:

  • Zero packet loss at 100 Mbps line rate
  • Checksum validation correct for all test cases
  • MAC filtering prevents invalid frames from reaching IP parser
  • Clock domain crossing stable with no metastability observed

Lessons Learned

1. Clock Domain Selection for Data Processing

Running IP parser in 25 MHz domain (same as MAC parser) avoids clock domain crossing for high-bandwidth byte stream. Only low-rate control signals and extracted fields cross to 100 MHz domain.

Trade-off: Processing throughput vs. CDC complexity. For line-rate parsing, minimize CDC on data paths.

2. Multi-bit Signal Synchronization

Multi-bit busses (ip_protocol) cannot use 2FF synchronizers directly due to bit skew. Solution: sample when stable (on ip_valid pulse edge), then synchronize the latched value.

3. Error Signal Aggregation

Multiple error sources require careful signal routing to avoid multiple drivers. Create separate signals for each module's error output, then explicitly combine with OR logic at top level.

4. Test Data Generation

Scapy's lazy field calculation requires explicit packet building before accessing computed values. Always call packet.__class__(bytes(packet)) to force field computation.

5. Hierarchical Module Testing

Standalone module development (Phase 1C) with comprehensive testbenches significantly simplified integration (Phase 1D). All IP parser bugs caught in simulation before hardware integration.


Future Work (Phase 1E)

UDP Parser

  • Extract UDP source/destination ports
  • Access UDP payload data
  • Port-based filtering and statistics

Packet Timestamping

  • Sub-microsecond arrival time capture
  • Compare PHY timestamp vs. FPGA timestamp
  • Jitter measurement and analysis

Enhanced Statistics

  • Per-protocol packet counts (UDP, TCP, ICMP)
  • Checksum error rates
  • Frame size histograms

MDIO Enhancements

  • Write capability for PHY configuration
  • Link speed/duplex negotiation control
  • Error counter readback from PHY

Dependencies

  • AMD Vivado Design Suite 2020.2 or later
  • Python 3.7+
  • Scapy library (pip install scapy)
  • Root privileges for raw packet transmission (Linux/Mac)
  • Administrator privileges for packet transmission (Windows)

References


Status: Complete - Hardware verified Created: November 5, 2025 Last Updated: November 6, 2025 Completed: November 6, 2025

Phase Progression:

  • Phase 1A: MII receiver with MAC parser
  • Phase 1B: MDIO interface
  • Phase 1C: IP parser standalone development
  • Phase 1D: IP parser integration (current)
  • Phase 1E: UDP parser and timestamping (planned)

About

UDP parser v4 - Async FIFO integration for CDC. Moving toward real-time architecture.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors