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
endmoduleTestbench
`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
endmoduleImplementation 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.