Implementing Gigabit Ethernet Communication with UDP on FPGA

Overview of the UDP Protocol

The User Datagram Protocol (UDP) is a fundamental transport layer protocol in the OSI model, offering an unreliable, connectoinless communication service. Defined in IETF RFC 768, UDP operates over IP and uses a simple header format. Its protocol number in IP packets is 17 (0x11).

Packet Strucutre

In Ethernet communication, data flows through multiple layers:

  1. Ethernet Frame: Contains MAC addresses and payload.
  2. IP Packet: Encapsulates the UDP segment.
  3. UDP Segment: Carries application data along with port information.
  4. Application Data: The actual content, such as "Hello, welcome to FPGA!".

The ancapsulation sequence is: Ethernet frame → IP packet → UDP segment → Application data.

UDP Packet Format

Fields Description

  1. Preamble:

    • 8'h55 repeated seven times followed by 8'hd5.
  2. Ethernet Header:

    • Destination MAC address.
    • Source MAC address.
    • EtherType field set to 0x0800 for IPv4.
  3. IP Header (20 bytes):

    • Version (4 bits)
    • Header Length (4 bits)
    • Type of Service (8 bits)
    • Total Length (16 bits)
    • Identification (16 bits)
    • Flags and Fragment Offset (16 bits)
    • TTL (8 bits)
    • Protocol (8 bits)
    • Header Checksum (16 bits)
    • Source IP Address (32 bits)
    • Destination IP Address (32 bits)

    The IP header checksum is calculated using one's complement arithmetic:

    • Set the checksum field to zero.
    • Split the header into 16-bit words.
    • Sum all words using one's complement addition.
    • Take the one's complement of the result.
  4. UDP Header:

    • Source Port (16 bits)
    • Destination Port (16 bits)
    • Length (16 bits)
    • Checksum (16 bits) — can be zero if not required.
  5. CRC:

    • Calculated starting from the preamble until the end of the frame.
    • Uses a standard CRC32 algorithm.

Implementation Details

// Time: 2020.04.11 21:22
// Description: UDP test module

module udp_test(
    rst_n,
    mii_tx_clk,
    mii_tx_en,
    mii_tx_er,
    mii_tx_data,
    phy_rst_n
);

input rst_n;
input mii_tx_clk;
output mii_tx_en;
output mii_tx_er;
output reg [3:0] mii_tx_data;
output phy_rst_n;

assign phy_rst_n = 1'b1;

parameter des_mac     = 48'hc4_54_44_97_c5_d7;
parameter src_mac     = 48'h00_0a_35_01_fe_c0;
parameter type_length = 16'h08_00;
parameter data_total_len = 16'd22;

parameter src_port = 16'd5000;
parameter des_port = 16'd6000;

parameter ver        = 4'h4;
parameter hdr_len    = 4'h5;
parameter tos        = 8'h00;
parameter id         = 16'h0000;
parameter offset     = 16'h0000;
parameter ttl        = 16'h40;
parameter protocol   = 8'h11;
parameter src_ip     = 32'hc0_a8_00_02;
parameter dst_ip     = 32'hc0_a8_00_03;

wire [15:0] ip_total_len;
wire [15:0] udp_total_len;
assign ip_total_len = data_total_len + 16'd28;
assign udp_total_len = data_total_len + 16'd8;

wire [31:0] CRC_Result;
reg [7:0] lsm_cnt;
wire CRC_EN;
assign CRC_EN = (lsm_cnt >= 17 && lsm_cnt <= 144);

crc32_d4 u0 (
    .Clk(mii_tx_clk),
    .Rst_n(rst_n),
    .Data(mii_tx_data),
    .Enable(CRC_EN),
    .Initialize(~mii_tx_en),
    .Crc(),
    .CrcError(),
    .Crc_eth(CRC_Result)
);

wire [31:0] sum;
wire [15:0] ip_checksum;
assign sum = {ver,hdr_len,tos} + ip_total_len + id + offset + {ttl,protocol} + src_ip[31:16]+ src_ip[15:0] + dst_ip[31:16] + dst_ip[15:0];
assign ip_checksum = ~(sum[31:16] + sum[15:0]);

wire tx_go;
reg en_tx;
reg [28:0] cnt;
always @(posedge mii_tx_clk or negedge rst_n)
    if (!rst_n)
        cnt <= 28'd0;
    else if (cnt == 28'd1000)
        cnt <= 28'd0;
    else
        cnt <= cnt + 1'b1;

assign tx_go = (cnt == 28'd1000) ? 1'b1 : 1'b0;

always @(posedge mii_tx_clk or negedge rst_n)
    if (!rst_n)
        en_tx <= 1'd0;
    else if (tx_go)
        en_tx <= 1'd1;
    else if (lsm_cnt >= 153)
        en_tx <= 1'd0;

always @(posedge mii_tx_clk or negedge rst_n)
    if (!rst_n)
        lsm_cnt <= 8'd0;
    else if (en_tx) begin
        if (lsm_cnt == 8'd153)
            lsm_cnt <= 8'd0;
        else
            lsm_cnt <= lsm_cnt + 1'b1;
    end
    else
        lsm_cnt <= 8'd0;

always @(*) begin
    case (lsm_cnt)
        1: mii_tx_data <= 4'h5;
        2: mii_tx_data <= 4'h5;
        3: mii_tx_data <= 4'h5;
        4: mii_tx_data <= 4'h5;
        5: mii_tx_data <= 4'h5;
        6: mii_tx_data <= 4'h5;
        7: mii_tx_data <= 4'h5;
        8: mii_tx_data <= 4'h5;
        9: mii_tx_data <= 4'h5;
        10: mii_tx_data <= 4'h5;
        11: mii_tx_data <= 4'h5;
        12: mii_tx_data <= 4'h5;
        13: mii_tx_data <= 4'h5;
        14: mii_tx_data <= 4'h5;
        15: mii_tx_data <= 4'h5;
        16: mii_tx_data <= 4'hd;
        17: mii_tx_data <= des_mac[43:40];
        18: mii_tx_data <= des_mac[47:44];
        19: mii_tx_data <= des_mac[35:32];
        20: mii_tx_data <= des_mac[39:36];
        21: mii_tx_data <= des_mac[27:24];
        22: mii_tx_data <= des_mac[31:28];
        23: mii_tx_data <= des_mac[19:16];
        24: mii_tx_data <= des_mac[23:20];
        25: mii_tx_data <= des_mac[11:8];
        26: mii_tx_data <= des_mac[15:12];
        27: mii_tx_data <= des_mac[3:0];
        28: mii_tx_data <= des_mac[7:4];
        29: mii_tx_data <= src_mac[43:40];
        30: mii_tx_data <= src_mac[47:44];
        31: mii_tx_data <= src_mac[35:32];
        32: mii_tx_data <= src_mac[39:36];
        33: mii_tx_data <= src_mac[27:24];
        34: mii_tx_data <= src_mac[31:28];
        35: mii_tx_data <= src_mac[19:16];
        36: mii_tx_data <= src_mac[23:20];
        37: mii_tx_data <= src_mac[11:8];
        38: mii_tx_data <= src_mac[15:12];
        39: mii_tx_data <= src_mac[3:0];
        40: mii_tx_data <= src_mac[7:4];
        41: mii_tx_data <= type_length[11:8];
        42: mii_tx_data <= type_length[15:12];
        43: mii_tx_data <= type_length[3:0];
        44: mii_tx_data <= type_length[7:4];
        45: mii_tx_data <= 4'h5;
        46: mii_tx_data <= 4'h4;
        47: mii_tx_data <= 4'h0;
        48: mii_tx_data <= 4'h0;
        49: mii_tx_data <= ip_total_len[11:8];
        50: mii_tx_data <= ip_total_len[15:12];
        51: mii_tx_data <= ip_total_len[3:0];
        52: mii_tx_data <= ip_total_len[7:4];
        53: mii_tx_data <= 4'h0;
        54: mii_tx_data <= 4'h0;
        55: mii_tx_data <= 4'h0;
        56: mii_tx_data <= 4'h0;
        57: mii_tx_data <= 4'h0;
        58: mii_tx_data <= 4'h0;
        59: mii_tx_data <= 4'h0;
        60: mii_tx_data <= 4'h0;
        61: mii_tx_data <= 4'h0;
        62: mii_tx_data <= 4'h4;
        63: mii_tx_data <= 4'h1;
        64: mii_tx_data <= 4'h1;
        65: mii_tx_data <= ip_checksum[11:8];
        66: mii_tx_data <= ip_checksum[15:12];
        67: mii_tx_data <= ip_checksum[3:0];
        68: mii_tx_data <= ip_checksum[7:4];
        69: mii_tx_data <= 4'h0;
        70: mii_tx_data <= 4'hc;
        71: mii_tx_data <= 4'h8;
        72: mii_tx_data <= 4'ha;
        73: mii_tx_data <= 4'h0;
        74: mii_tx_data <= 4'h0;
        75: mii_tx_data <= 4'h2;
        76: mii_tx_data <= 4'h0;
        77: mii_tx_data <= 4'h0;
        78: mii_tx_data <= 4'hc;
        79: mii_tx_data <= 4'h8;
        80: mii_tx_data <= 4'ha;
        81: mii_tx_data <= 4'h0;
        82: mii_tx_data <= 4'h0;
        83: mii_tx_data <= 4'h3;
        84: mii_tx_data <= 4'h0;
        85: mii_tx_data <= src_port[11:8];
        86: mii_tx_data <= src_port[15:12];
        87: mii_tx_data <= src_port[3:0];
        88: mii_tx_data <= src_port[7:4];
        89: mii_tx_data <= des_port[11:8];
        90: mii_tx_data <= des_port[15:12];
        91: mii_tx_data <= des_port[3:0];
        92: mii_tx_data <= des_port[7:4];
        93: mii_tx_data <= udp_total_len[11:8];
        94: mii_tx_data <= udp_total_len[15:12];
        95: mii_tx_data <= udp_total_len[3:0];
        96: mii_tx_data <= udp_total_len[7:4];
        97: mii_tx_data <= 4'h0;
        98: mii_tx_data <= 4'h0;
        99: mii_tx_data <= 4'h0;
        100: mii_tx_data <= 4'h0;
        101: mii_tx_data <= 4'h8;
        102: mii_tx_data <= 4'h4;
        103: mii_tx_data <= 4'h5;
        104: mii_tx_data <= 4'h6;
        105: mii_tx_data <= 4'hc;
        106: mii_tx_data <= 4'h6;
        107: mii_tx_data <= 4'hc;
        108: mii_tx_data <= 4'h6;
        109: mii_tx_data <= 4'hf;
        110: mii_tx_data <= 4'h6;
        111: mii_tx_data <= 4'hc;
        112: mii_tx_data <= 4'h2;
        113: mii_tx_data <= 4'h7;
        114: mii_tx_data <= 4'h7;
        115: mii_tx_data <= 4'h5;
        116: mii_tx_data <= 4'h6;
        117: mii_tx_data <= 4'hc;
        118: mii_tx_data <= 4'h6;
        119: mii_tx_data <= 4'h3;
        120: mii_tx_data <= 4'h6;
        121: mii_tx_data <= 4'hf;
        122: mii_tx_data <= 4'h6;
        123: mii_tx_data <= 4'hd;
        124: mii_tx_data <= 4'h6;
        125: mii_tx_data <= 4'h5;
        126: mii_tx_data <= 4'h6;
        127: mii_tx_data <= 4'h0;
        128: mii_tx_data <= 4'h2;
        129: mii_tx_data <= 4'h4;
        130: mii_tx_data <= 4'h7;
        131: mii_tx_data <= 4'hf;
        132: mii_tx_data <= 4'h6;
        133: mii_tx_data <= 4'h0;
        134: mii_tx_data <= 4'h2;
        135: mii_tx_data <= 4'h6;
        136: mii_tx_data <= 4'h4;
        137: mii_tx_data <= 4'h0;
        138: mii_tx_data <= 4'h5;
        139: mii_tx_data <= 4'h7;
        140: mii_tx_data <= 4'h4;
        141: mii_tx_data <= 4'h1;
        142: mii_tx_data <= 4'h4;
        143: mii_tx_data <= 4'h1;
        144: mii_tx_data <= 4'h2;
        145: mii_tx_data <= CRC_Result[27:24];
        146: mii_tx_data <= CRC_Result[31:28];
        147: mii_tx_data <= CRC_Result[19:16];
        148: mii_tx_data <= CRC_Result[23:20];
        149: mii_tx_data <= CRC_Result[11:8];
        150: mii_tx_data <= CRC_Result[15:12];
        151: mii_tx_data <= CRC_Result[3:0];
        152: mii_tx_data <= CRC_Result[7:4];
        153: mii_tx_data <= 4'd0;
        default: mii_tx_data <= 4'd0;
    endcase
end

assign mii_tx_en = ((lsm_cnt >= 1) && (lsm_cnt <= 153)) ? 1'b1 : 1'b0;

endmodule

CRC Module Implementation

`timescale 1ns/1ns
module crc32_d4 (Clk, Rst_n, Data, Enable, Initialize, Crc, CrcError, Crc_eth);

parameter Tp = 1;

input Clk;
input Rst_n;
input [0:3] Data;
input Enable;
input Initialize;

output [31:0] Crc;
output [31:0] Crc_eth;
output CrcError;

reg [31:0] Crc;
wire [31:0] CrcNext;

assign CrcNext[0] = Enable & (Data[0] ^ Crc[28]);
assign CrcNext[1] = Enable & (Data[1] ^ Data[0] ^ Crc[28] ^ Crc[29]);
assign CrcNext[2] = Enable & (Data[2] ^ Data[1] ^ Data[0] ^ Crc[28] ^ Crc[29] ^ Crc[30]);
assign CrcNext[3] = Enable & (Data[3] ^ Data[2] ^ Data[1] ^ Crc[29] ^ Crc[30] ^ Crc[31]);
assign CrcNext[4] = (Enable & (Data[3] ^ Data[2] ^ Data[0] ^ Crc[28] ^ Crc[30] ^ Crc[31])) ^ Crc[0];
assign CrcNext[5] = (Enable & (Data[3] ^ Data[1] ^ Data[0] ^ Crc[28] ^ Crc[29] ^ Crc[31])) ^ Crc[1];
assign CrcNext[6] = (Enable & (Data[2] ^ Data[1] ^ Crc[29] ^ Crc[30])) ^ Crc[2];
assign CrcNext[7] = (Enable & (Data[3] ^ Data[2] ^ Data[0] ^ Crc[28] ^ Crc[30] ^ Crc[31])) ^ Crc[3];
assign CrcNext[8] = (Enable & (Data[3] ^ Data[1] ^ Data[0] ^ Crc[28] ^ Crc[29] ^ Crc[31])) ^ Crc[4];
assign CrcNext[9] = (Enable & (Data[2] ^ Data[1] ^ Crc[29] ^ Crc[30])) ^ Crc[5];
assign CrcNext[10] = (Enable & (Data[3] ^ Data[2] ^ Data[0] ^ Crc[28] ^ Crc[30] ^ Crc[31])) ^ Crc[6];
assign CrcNext[11] = (Enable & (Data[3] ^ Data[1] ^ Data[0] ^ Crc[28] ^ Crc[29] ^ Crc[31])) ^ Crc[7];
assign CrcNext[12] = (Enable & (Data[2] ^ Data[1] ^ Data[0] ^ Crc[28] ^ Crc[29] ^ Crc[30])) ^ Crc[8];
assign CrcNext[13] = (Enable & (Data[3] ^ Data[2] ^ Data[1] ^ Crc[29] ^ Crc[30] ^ Crc[31])) ^ Crc[9];
assign CrcNext[14] = (Enable & (Data[3] ^ Data[2] ^ Crc[30] ^ Crc[31])) ^ Crc[10];
assign CrcNext[15] = (Enable & (Data[3] ^ Crc[31])) ^ Crc[11];
assign CrcNext[16] = (Enable & (Data[0] ^ Crc[28])) ^ Crc[12];
assign CrcNext[17] = (Enable & (Data[1] ^ Crc[29])) ^ Crc[13];
assign CrcNext[18] = (Enable & (Data[2] ^ Crc[30])) ^ Crc[14];
assign CrcNext[19] = (Enable & (Data[3] ^ Crc[31])) ^ Crc[15];
assign CrcNext[20] = Crc[16];
assign CrcNext[21] = Crc[17];
assign CrcNext[22] = (Enable & (Data[0] ^ Crc[28])) ^ Crc[18];
assign CrcNext[23] = (Enable & (Data[1] ^ Data[0] ^ Crc[29] ^ Crc[28])) ^ Crc[19];
assign CrcNext[24] = (Enable & (Data[2] ^ Data[1] ^ Crc[30] ^ Crc[29])) ^ Crc[20];
assign CrcNext[25] = (Enable & (Data[3] ^ Data[2] ^ Crc[31] ^ Crc[30])) ^ Crc[21];
assign CrcNext[26] = (Enable & (Data[3] ^ Data[0] ^ Crc[31] ^ Crc[28])) ^ Crc[22];
assign CrcNext[27] = (Enable & (Data[1] ^ Crc[29])) ^ Crc[23];
assign CrcNext[28] = (Enable & (Data[2] ^ Crc[30])) ^ Crc[24];
assign CrcNext[29] = (Enable & (Data[3] ^ Crc[31])) ^ Crc[25];
assign CrcNext[30] = Crc[26];
assign CrcNext[31] = Crc[27];

always @(posedge Clk or negedge Rst_n)
begin
    if (!Rst_n)
        Crc <= #1 32'hffffffff;
    else
    if (Initialize)
        Crc <= #Tp 32'hffffffff;
    else if (Enable)
        Crc <= #Tp CrcNext;
end

assign Crc_eth = ~{
    CrcNext[28], CrcNext[29], CrcNext[30], CrcNext[31],
    Crc[24], Crc[25], Crc[26], Crc[27],
    Crc[20], Crc[21], Crc[22], Crc[23],
    Crc[16], Crc[17], Crc[18], Crc[19],
    Crc[12], Crc[13], Crc[14], Crc[15],
    Crc[8], Crc[9], Crc[10], Crc[11],
    Crc[4], Crc[5], Crc[6], Crc[7],
    Crc[0], Crc[1], Crc[2], Crc[3]};

assign CrcError = Crc[31:0] != 32'hc704dd7b;

endmodule

Testbench

`timescale 1ns/1ps
module udp_test_tb;

reg rst_n;
reg mii_tx_clk;
wire mii_tx_en;
wire mii_tx_er;
wire [3:0] mii_tx_data;
wire phy_rst_n;

udp_test u0 (
    .rst_n(rst_n),
    .mii_tx_clk(mii_tx_clk),
    .mii_tx_en(mii_tx_en),
    .mii_tx_er(mii_tx_er),
    .mii_tx_data(mii_tx_data),
    .phy_rst_n(phy_rst_n)
);

initial
    mii_tx_clk = 1'b0;
    always #20 mii_tx_clk = ~mii_tx_clk;

initial
begin
    rst_n = 1'b0;
    #100;
    rst_n = 1'b1;
    #100000;
    $stop;
end

endmodule

Tags: FPGA Ethernet udp Verilog CRC

Posted on Tue, 18 Aug 2026 16:57:20 +0000 by Shit4Brains