FPGA Implementation of ARP Protocol Using MII Interface

Role of ARP in Network Communication

When a host initiates network communication, the application layer is aware of the destination IP address and port number but lacks knowledge of the target's hardware (MAC) address. Since network interface cards process incoming packets at the physical layer first, any frame with a mismatched destination MAC address is discarded immediately. The Address Resolution Protocol (ARP) bridges this gap by dynamically discovering the hardware address associated with a specific IP address.

ARP Operation Mechanism

The source host broadcasts an ARP request packet onto the local network segment, inquiring about the MAC address corresponding to a particular IP. This broadcast frame uses the destination address FF:FF:FF:FF:FF:FF. Upon receiving the broadcast, the host matching the queried IP address responds with a unicast ARP reply containing its MAC address.

ARP Packet Structure

The ARP payload is encapsulated within an Ethernet frame. Key fields include:

  • Destination MAC Address: 48-bit broadcast address for requests
  • Source MAC Address: 48-bit hardware address of the sender
  • Ethernet Frame Type: 0x0806 indicating an ARP packet
  • Hardware Type: 0x0001 for Ethernet
  • Protocol Type: 0x0800 for IPv4
  • Hardware Address Length: 6 bytes for MAC addresses
  • Protocol Address Length: 4 bytes for IPv4 addresses
  • Operation Code: 0x0001 for request, 0x0002 for reply
  • Sender Hardware Address: MAC of the originator
  • Sender Protocol Address: IP of the originator
  • Target Hardware Address: MAC being resolved (zero for requests)
  • Target Protocol Address: IP being queried

Ethernet Frame Structure

The MAC layer encapsulation includes:

  • Preamble: Seven bytes of 0x55 for clock synchronization
  • Start Frame Delimiter (SFD): 0xD5 marking frame start
  • Frame Check Sequence (FCS): 4-byte CRC-32 appended at the end

Verilog Implementation

The following design implements an ARP request transmitter using a state machine approach for the MII interface:

module arp_request_tx(
    input wire tx_clk,
    input wire reset_n,
    output reg mii_tx_en,
    output reg [3:0] mii_tx_d,
    output wire phy_rst_n
);

    // ARP Packet Constants
    localparam DEST_MAC_ADDR = 48'hFF_FF_FF_FF_FF_FF;
    localparam SRC_MAC_ADDR = 48'h00_0A_35_01_FE_C0;
    localparam ETH_TYPE_ARP = 16'h0806;
    localparam HW_TYPE_ETH = 16'h0001;
    localparam PROTO_TYPE_IP = 16'h0800;
    localparam OP_REQUEST = 16'h0001;
    localparam SENDER_IP = 32'hC0A80002; // 192.168.0.2
    localparam TARGET_IP = 32'hC0A80003; // 192.168.0.3

    // Transmit States
    localparam IDLE_ST = 4'd0;
    localparam PRE_ST = 4'd1;
    localparam SFD_ST = 4'd2;
    localparam DST_MAC_ST = 4'd3;
    localparam SRC_MAC_ST = 4'd4;
    localparam ETH_TYPE_ST = 4'd5;
    localparam ARP_HW_TYPE_ST = 4'd6;
    localparam ARP_PROTO_TYPE_ST = 4'd7;
    localparam ARP_HW_LEN_ST = 4'd8;
    localparam ARP_PROTO_LEN_ST = 4'd9;
    localparam ARP_OP_ST = 4'd10;
    localparam SEND_MAC_ST = 4'd11;
    localparam SEND_IP_ST = 4'd12;
    localparam TGT_MAC_ST = 4'd13;
    localparam TGT_IP_ST = 4'd14;
    localparam PAD_ST = 4'd15;
    localparam CRC_ST = 4'd16;
    localparam DONE_ST = 4'd17;

    reg [4:0] tx_state;
    reg [3:0] nibble_idx;
    reg [31:0] timer_cnt;
    reg [7:0] pad_count;
    wire tx_trigger;
    wire [31:0] crc_out;
    reg crc_init;
    reg crc_en;

    assign phy_rst_n = 1'b1;

    // Periodic transmission trigger (approximately 700ms interval)
    always @(posedge tx_clk or negedge reset_n) begin
        if (!reset_n)
            timer_cnt <= 32'd0;
        else if (timer_cnt >= 32'd17_500_000)
            timer_cnt <= 32'd0;
        else
            timer_cnt <= timer_cnt + 1'b1;
    end

    assign tx_trigger = (timer_cnt == 32'd17_500_000);

    // CRC Module
    crc32_calc u_crc(
        .clk(tx_clk),
        .rst_n(reset_n),
        .init(crc_init),
        .enable(crc_en),
        .data_in(mii_tx_d),
        .crc_result(crc_out)
    );

    // Main transmission state machine
    always @(posedge tx_clk or negedge reset_n) begin
        if (!reset_n) begin
            tx_state <= IDLE_ST;
            mii_tx_en <= 1'b0;
            mii_tx_d <= 4'd0;
            nibble_idx <= 4'd0;
            pad_count <= 8'd0;
            crc_init <= 1'b0;
            crc_en <= 1'b0;
        end else begin
            case (tx_state)
                IDLE_ST: begin
                    mii_tx_en <= 1'b0;
                    if (tx_trigger) begin
                        tx_state <= PRE_ST;
                        nibble_idx <= 4'd0;
                    end
                end

                PRE_ST: begin
                    mii_tx_en <= 1'b1;
                    mii_tx_d <= 4'h5;
                    crc_en <= 1'b0;
                    nibble_idx <= nibble_idx + 1'b1;
                    if (nibble_idx == 4'd13)
                        tx_state <= SFD_ST;
                end

                SFD_ST: begin
                    mii_tx_d <= 4'hD;
                    nibble_idx <= 4'd0;
                    crc_init <= 1'b1;
                    tx_state <= DST_MAC_ST;
                end

                DST_MAC_ST: begin
                    crc_init <= 1'b0;
                    crc_en <= 1'b1;
                    case (nibble_idx)
                        4'd0: mii_tx_d <= DEST_MAC_ADDR[43:40];
                        4'd1: mii_tx_d <= DEST_MAC_ADDR[47:44];
                        4'd2: mii_tx_d <= DEST_MAC_ADDR[35:32];
                        4'd3: mii_tx_d <= DEST_MAC_ADDR[39:36];
                        4'd4: mii_tx_d <= DEST_MAC_ADDR[27:24];
                        4'd5: mii_tx_d <= DEST_MAC_ADDR[31:28];
                        4'd6: mii_tx_d <= DEST_MAC_ADDR[19:16];
                        4'd7: mii_tx_d <= DEST_MAC_ADDR[23:20];
                        4'd8: mii_tx_d <= DEST_MAC_ADDR[11:8];
                        4'd9: mii_tx_d <= DEST_MAC_ADDR[15:12];
                        4'd10: mii_tx_d <= DEST_MAC_ADDR[3:0];
                        4'd11: mii_tx_d <= DEST_MAC_ADDR[7:4];
                    endcase
                    nibble_idx <= nibble_idx + 1'b1;
                    if (nibble_idx == 4'd11)
                        tx_state <= SRC_MAC_ST;
                end

                SRC_MAC_ST: begin
                    case (nibble_idx)
                        4'd0: mii_tx_d <= SRC_MAC_ADDR[43:40];
                        4'd1: mii_tx_d <= SRC_MAC_ADDR[47:44];
                        4'd2: mii_tx_d <= SRC_MAC_ADDR[35:32];
                        4'd3: mii_tx_d <= SRC_MAC_ADDR[39:36];
                        4'd4: mii_tx_d <= SRC_MAC_ADDR[27:24];
                        4'd5: mii_tx_d <= SRC_MAC_ADDR[31:28];
                        4'd6: mii_tx_d <= SRC_MAC_ADDR[19:16];
                        4'd7: mii_tx_d <= SRC_MAC_ADDR[23:20];
                        4'd8: mii_tx_d <= SRC_MAC_ADDR[11:8];
                        4'd9: mii_tx_d <= SRC_MAC_ADDR[15:12];
                        4'd10: mii_tx_d <= SRC_MAC_ADDR[3:0];
                        4'd11: mii_tx_d <= SRC_MAC_ADDR[7:4];
                    endcase
                    nibble_idx <= nibble_idx + 1'b1;
                    if (nibble_idx == 4'd11)
                        tx_state <= ETH_TYPE_ST;
                end

                ETH_TYPE_ST: begin
                    case (nibble_idx)
                        4'd0: mii_tx_d <= ETH_TYPE_ARP[11:8];
                        4'd1: mii_tx_d <= ETH_TYPE_ARP[15:12];
                        4'd2: mii_tx_d <= ETH_TYPE_ARP[3:0];
                        4'd3: mii_tx_d <= ETH_TYPE_ARP[7:4];
                    endcase
                    nibble_idx <= nibble_idx + 1'b1;
                    if (nibble_idx == 4'd3)
                        tx_state <= ARP_HW_TYPE_ST;
                end

                ARP_HW_TYPE_ST: begin
                    case (nibble_idx)
                        4'd0: mii_tx_d <= HW_TYPE_ETH[11:8];
                        4'd1: mii_tx_d <= HW_TYPE_ETH[15:12];
                        4'd2: mii_tx_d <= HW_TYPE_ETH[3:0];
                        4'd3: mii_tx_d <= HW_TYPE_ETH[7:4];
                    endcase
                    nibble_idx <= nibble_idx + 1'b1;
                    if (nibble_idx == 4'd3)
                        tx_state <= ARP_PROTO_TYPE_ST;
                end

                ARP_PROTO_TYPE_ST: begin
                    case (nibble_idx)
                        4'd0: mii_tx_d <= PROTO_TYPE_IP[11:8];
                        4'd1: mii_tx_d <= PROTO_TYPE_IP[15:12];
                        4'd2: mii_tx_d <= PROTO_TYPE_IP[3:0];
                        4'd3: mii_tx_d <= PROTO_TYPE_IP[7:4];
                    endcase
                    nibble_idx <= nibble_idx + 1'b1;
                    if (nibble_idx == 4'd3)
                        tx_state <= ARP_HW_LEN_ST;
                end

                ARP_HW_LEN_ST: begin
                    mii_tx_d <= (nibble_idx == 0) ? 4'h6 : 4'h0;
                    nibble_idx <= nibble_idx + 1'b1;
                    if (nibble_idx == 1)
                        tx_state <= ARP_PROTO_LEN_ST;
                end

                ARP_PROTO_LEN_ST: begin
                    mii_tx_d <= (nibble_idx == 0) ? 4'h4 : 4'h0;
                    nibble_idx <= nibble_idx + 1'b1;
                    if (nibble_idx == 1)
                        tx_state <= ARP_OP_ST;
                end

                ARP_OP_ST: begin
                    case (nibble_idx)
                        4'd0: mii_tx_d <= OP_REQUEST[11:8];
                        4'd1: mii_tx_d <= OP_REQUEST[15:12];
                        4'd2: mii_tx_d <= OP_REQUEST[3:0];
                        4'd3: mii_tx_d <= OP_REQUEST[7:4];
                    endcase
                    nibble_idx <= nibble_idx + 1'b1;
                    if (nibble_idx == 4'd3)
                        tx_state <= SEND_MAC_ST;
                end

                SEND_MAC_ST: begin
                    case (nibble_idx)
                        4'd0: mii_tx_d <= SRC_MAC_ADDR[43:40];
                        4'd1: mii_tx_d <= SRC_MAC_ADDR[47:44];
                        4'd2: mii_tx_d <= SRC_MAC_ADDR[35:32];
                        4'd3: mii_tx_d <= SRC_MAC_ADDR[39:36];
                        4'd4: mii_tx_d <= SRC_MAC_ADDR[27:24];
                        4'd5: mii_tx_d <= SRC_MAC_ADDR[31:28];
                        4'd6: mii_tx_d <= SRC_MAC_ADDR[19:16];
                        4'd7: mii_tx_d <= SRC_MAC_ADDR[23:20];
                        4'd8: mii_tx_d <= SRC_MAC_ADDR[11:8];
                        4'd9: mii_tx_d <= SRC_MAC_ADDR[15:12];
                        4'd10: mii_tx_d <= SRC_MAC_ADDR[3:0];
                        4'd11: mii_tx_d <= SRC_MAC_ADDR[7:4];
                    endcase
                    nibble_idx <= nibble_idx + 1'b1;
                    if (nibble_idx == 4'd11)
                        tx_state <= SEND_IP_ST;
                end

                SEND_IP_ST: begin
                    case (nibble_idx)
                        4'd0: mii_tx_d <= SENDER_IP[27:24];
                        4'd1: mii_tx_d <= SENDER_IP[31:28];
                        4'd2: mii_tx_d <= SENDER_IP[19:16];
                        4'd3: mii_tx_d <= SENDER_IP[23:20];
                        4'd4: mii_tx_d <= SENDER_IP[11:8];
                        4'd5: mii_tx_d <= SENDER_IP[15:12];
                        4'd6: mii_tx_d <= SENDER_IP[3:0];
                        4'd7: mii_tx_d <= SENDER_IP[7:4];
                    endcase
                    nibble_idx <= nibble_idx + 1'b1;
                    if (nibble_idx == 4'd7)
                        tx_state <= TGT_MAC_ST;
                end

                TGT_MAC_ST: begin
                    mii_tx_d <= 4'h0;
                    nibble_idx <= nibble_idx + 1'b1;
                    if (nibble_idx == 4'd11)
                        tx_state <= TGT_IP_ST;
                end

                TGT_IP_ST: begin
                    case (nibble_idx)
                        4'd0: mii_tx_d <= TARGET_IP[27:24];
                        4'd1: mii_tx_d <= TARGET_IP[31:28];
                        4'd2: mii_tx_d <= TARGET_IP[19:16];
                        4'd3: mii_tx_d <= TARGET_IP[23:20];
                        4'd4: mii_tx_d <= TARGET_IP[11:8];
                        4'd5: mii_tx_d <= TARGET_IP[15:12];
                        4'd6: mii_tx_d <= TARGET_IP[3:0];
                        4'd7: mii_tx_d <= TARGET_IP[7:4];
                    endcase
                    nibble_idx <= nibble_idx + 1'b1;
                    if (nibble_idx == 4'd7)
                        tx_state <= PAD_ST;
                end

                PAD_ST: begin
                    mii_tx_d <= 4'h0;
                    pad_count <= pad_count + 1'b1;
                    if (pad_count >= 8'd36)
                        tx_state <= CRC_ST;
                end

                CRC_ST: begin
                    crc_en <= 1'b0;
                    case (nibble_idx)
                        4'd0: mii_tx_d <= crc_out[27:24];
                        4'd1: mii_tx_d <= crc_out[31:28];
                        4'd2: mii_tx_d <= crc_out[19:16];
                        4'd3: mii_tx_d <= crc_out[23:20];
                        4'd4: mii_tx_d <= crc_out[11:8];
                        4'd5: mii_tx_d <= crc_out[15:12];
                        4'd6: mii_tx_d <= crc_out[3:0];
                        4'd7: mii_tx_d <= crc_out[7:4];
                    endcase
                    nibble_idx <= nibble_idx + 1'b1;
                    if (nibble_idx == 4'd7)
                        tx_state <= DONE_ST;
                end

                DONE_ST: begin
                    mii_tx_en <= 1'b0;
                    nibble_idx <= 4'd0;
                    pad_count <= 8'd0;
                    tx_state <= IDLE_ST;
                end
            endcase
        end
    end

endmodule

Testbench

`timescale 1ns / 1ps

module arp_request_tx_tb;

    reg clk;
    reg rst_n;
    wire tx_en;
    wire [3:0] tx_data;
    wire phy_rst;

    arp_request_tx dut(
        .tx_clk(clk),
        .reset_n(rst_n),
        .mii_tx_en(tx_en),
        .mii_tx_d(tx_data),
        .phy_rst_n(phy_rst)
    );

    initial begin
        clk = 1'b0;
        forever #20 clk = ~clk;
    end

    initial begin
        rst_n = 1'b0;
        #200;
        rst_n = 1'b1;
        #500000;
        $finish;
    end

endmodule

Implementation Notes

When deploying this design, ensure proper timing constraints are applied to meet the 25MHz MII clock requirements. If the host computer does not respond to the ARP request, verify that the target IP address matches an interface configured on the receiving device.

Tags: FPGA ARP Protocol Verilog Ethernet MII Interface

Posted on Fri, 08 May 2026 04:57:53 +0000 by Smee