1.ARP协议简介
ARP(Address Resolution Protocol),即地址解析协议,是根据IP地址(逻辑地址)获取MAC地址的一种TCP/IP协议。在以太网通信中,数据是以"帧"的格式进行传输的,帧格式里面包含目的主机的MAC地址。
源主机的应用程序知道目的主机的IP地址,却不知道目的主机的MAC地址。而目的主机的MAC地址直接被网卡接收和解析,当解析到目的MAC地址非本地MAC地址时,则直接丢弃该包数据,因此在通信前需要先获得目的MAC地址,而ARP协议正是实现了此功能。
ARP协议的基本功能是通过目的主机IP地址,获取目的主机的MAC地址,以保证通信的顺利进行。MAC地址在网络中表示网卡的ID,每个网卡都需要并有且仅有一个MAC地址。在获取到目的MAC地址之后,将目的MAC地址更新至ARP缓存表中,称为ARP映射。下次通信时,可以直接从ARP缓存表中获取,而不用重新通过ARP获取MAC地址。但一般ARP缓存表会有过期时间,过期后需要重新通过ARP协议进行获取。
ARP映射是指将IP地址和MAC地址映射起来,分为静态映射和动态映射。
ARP 工作流程如下:
假设主机A和B在同一个网段,主机 A 要向主机B 发送信息,具体的地址解析过程如下:
(1) 主机A首先查看自己的 ARP 表 ,确定其中是否包含有主机B对应的ARP表项。如果找到 了对应的MAC 地址 ,则主机A直接利用ARP表中的MAC地址,对IP数据包进行帧封装 ,并将数据包发送给主机B。
(2) 如果主机A在ARP表中找不到 对应的MAC 地址 ,则将缓存该数据报文,然后以广播方式发送一个ARP请求报文。
ARP请求报文中的发送端 IP 地址和发送端MAC 地址 为主机 A 的IP 地址和MAC 地址 ,目标 IP 地址和目标MAC 地址 为主机 B 的IP 地址和全0 的MAC 地址。
由于ARP请求报文以广播方式发送,该网段上的所有主机都可以接收到该请求,但只有被请求的主机(即主机B)会对该请求进行处理。
(3) 主机B比较自己 IP 地址和ARP请求报文中的 目标IP 地址,当两者相同 时进行如下处理:将 ARP 请求报文中的发送端(即主机A )的IP 地址和MAC 地址存入自己的ARP 表中。
之后以单播方式发送ARP响应报文给主机A,其中包含了自己的MAC地址。
(4) 主机A收到ARP响应报文后,将主机B的MAC地址加入到自己的ARP表中以用于后续报文的转发,同时将IP数据包进行封装后发送出去。
2.ARP协议实现
2.1接收部分:
接收部分采用一个状态机来实现,其状态转换图如下所示:
开始状态机处于IDLE状态,当检测到第一个8'h55的时候进入PREAMBLE状态,即接收前导码和SFD,与此同时我们对接收数据进行计数,当计数器计数到6时检测此时接收的数据是不是8'hd5,如果是进入接收帧头状态;否则,进入接收终止状态。接收帧头状态的跳转也和PREAEBLE状态的跳转情况差不多,只不过此时需要检测接受到的以太网类型,而以太网类型是两字节,因此需要先接收高位,再进行对比。ARP_DATA状态的跳转则需要检测接受到的目的ip地址与开发板的ip地址是否相同并且还要检测操作码是否有效。当接收完单包数据后下一个时钟周期跳转到IDLE状态。
整体的实现还是比较简单的,利用一个状态机就能完成,但是需要对以太网帧格式比较熟悉。具体实现代码如下:
`timescale 1ns / 1ps
module ARP_RX#(
parameter BOARD_MAC = 48'h00_11_22_33_44_55 , //开发板MAC地址
parameter BOARD_IP = {8'd192,8'd168,8'd1,8'd10} //开发板IP地址
)(
//system
input clk , //系统时钟
input rst_n , //系统复位
//input
input gmii_rx_dv , //接收数据有效信号
input [7:0] gmii_rxd , //接收数据
//output
output reg arp_rx_done , //ARP接收完成信号
output reg arp_rx_type , //ARP接收类型0:请求 1:应答
output reg [47:0] src_mac , //输出接收到的源MAC地址
output reg [31:0] src_ip //输出接收到的源IP地址
);
parameter IDLE = 5'b0_0001 ; //空闲状态
parameter PREAMBLE = 5'b0_0010 ; //接收前导码和SFD
parameter ETH_HEAD = 5'b0_0100 ; //帧头
parameter ARP_DATA = 5'b0_1000 ; //接收ARP数据
parameter RX_DONE = 5'b1_0000 ; //接收CRC校验数据
parameter ETH_TPYE = 16'h0806 ; //以太网帧类型
reg state_en ; //状态跳转使能信号
reg [4:0] cur_state ; //现态
reg [4:0] next_state ; //次态
reg error_flag ; //错误指示信号
reg [47:0] des_mac_t ; //目的MAC地址
reg [31:0] des_ip_t ; //目的ip地址
reg [47:0] src_mac_t ; //源MAC地址
reg [31:0] src_ip_t ; //源ip地址
reg [4:0] cnt ; //解析数据计数器
reg [15:0] eth_type ; //以太网类型
reg [15:0] op_data ; //操作码
//三段式状态机第一段
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cur_state <= IDLE;
else
cur_state <= next_state;
end
//三段式状态机第二段
always @(*) begin
next_state = IDLE;
casex (cur_state)
IDLE:begin
if(state_en)
next_state = PREAMBLE;
else if(error_flag)
next_state = RX_DONE;
else
next_state = IDLE;
end
PREAMBLE:begin
if(state_en)
next_state = ETH_HEAD;
else if(error_flag)
next_state = RX_DONE;
else
next_state = PREAMBLE;
end
ETH_HEAD:begin
if(state_en)
next_state = ARP_DATA;
else if(error_flag)
next_state = RX_DONE;
else
next_state = ETH_HEAD;
end
ARP_DATA:begin
if(state_en)
next_state = RX_DONE;
else if(error_flag)
next_state = RX_DONE;
else
next_state = ARP_DATA;
end
RX_DONE:begin
if(state_en)
next_state = IDLE;
else
next_state = RX_DONE;
end
default: next_state = IDLE;
endcase
end
//三段式状态机第三段
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
state_en <= 1'b0;
cnt <= 6'd0;
error_flag <= 1'b0;
des_mac_t <= 48'd0;
des_ip_t <= 32'd0;
src_mac_t <= 48'd0;
src_ip_t <= 32'd0;
eth_type <= 16'd0;
op_data <= 16'd0;
arp_rx_done <= 1'b0;
arp_rx_type <= 1'b0;
src_mac <= 48'd0;
src_ip <= 32'd0;
end
else begin
state_en <= 1'b0;
arp_rx_done <= 1'b0;
error_flag <= 1'b0;
case (next_state)
IDLE:begin//接收到第一个8'h55
if((gmii_rx_dv == 1'b1) && (gmii_rxd == 8'h55))
state_en <= 1'b1;
else;
end
PREAMBLE:begin
if(gmii_rx_dv)begin
cnt <= cnt + 1'b1;
if((cnt < 5'd6) && (gmii_rxd !=8'h55))
error_flag <= 1'b1;
else if(cnt == 5'd6)begin
cnt <= 5'd0;
if(gmii_rxd == 8'hd5)
state_en <= 1'b1;
else
error_flag <= 1'b1;
end
end
else;
end
ETH_HEAD:begin
if(gmii_rx_dv)begin
cnt <= cnt + 1'b1;
if(cnt < 5'd6)
des_mac_t <= {des_mac_t[39:0],gmii_rxd};//接收源MAC地址
else if(cnt == 5'd6)begin
if((des_mac_t != BOARD_MAC) && (des_mac_t != 48'hff_ff_ff_ff_ff_ff))
error_flag <= 1'b1;
else;
end
else if(cnt == 5'd12)
eth_type[15:8] <= gmii_rxd;
else if(cnt == 5'd13)begin
cnt <= 5'd0;
eth_type[7:0] <= gmii_rxd;
if(eth_type[15:8] == ETH_TPYE[15:8] && gmii_rxd == ETH_TPYE[7:0])
state_en <= 1'b1;
else
error_flag <= 1'b1;
end
else;
end
else;
end
ARP_DATA:begin
if(gmii_rx_dv)begin
cnt <= cnt + 1'b1;
if(cnt == 5'd6)
op_data[15:8] <= gmii_rxd;
else if(cnt == 5'd7)
op_data[7:0] <= gmii_rxd;//操作码
else if(cnt >= 5'd8 && cnt < 5'd14)
src_mac_t <= {src_mac_t[39:0],gmii_rxd};//源MAC地址
else if(cnt >= 5'd14 && cnt < 5'd18)
src_ip_t <= {src_ip_t[23:0],gmii_rxd};//源ip地址
else if(cnt >= 5'd24 && cnt < 5'd28)
des_ip_t <= {des_ip_t[23:0],gmii_rxd};//目的ip地址
else if(cnt == 5'd28)begin
cnt <= 5'd0;
if(des_ip_t == BOARD_IP)begin//判断目的ip地址和操作码
if((op_data == 16'd1) || (op_data == 16'd2))begin
state_en <= 1'b1;
arp_rx_done <= 1'b1;
src_ip <= src_ip_t;
src_mac <= src_mac_t;
src_mac_t <= 48'd0;
src_ip_t <= 32'd0;
des_ip_t <= 32'd0;
des_mac_t <= 48'd0;
if(op_data == 16'd1)
arp_rx_type <= 1'b0;//ARP请求
else
arp_rx_type <= 1'b1;//ARP应答
end
else
error_flag <= 1'b1;
end
else
error_flag <= 1'b1;
end
end
end
RX_DONE:begin
cnt <= 5'd0;
//单包数据接收完成
if(gmii_rx_dv && state_en)
state_en <= 1'b1;
else;
end
default: ;
endcase
end
end
endmodule
2.2发送部分:
发送部分也可以采用一个状态机来完成,状态转换图如下所示:
发送部分的状态转换比接收部分的状态转换还简单一些,我们需要定义一个计数器,每个时钟的上升沿发送一字节数据并且计数器加1,即可很容易的控制状态机的状态转换,需要注意的是,进入ARP_DATA状态之后,我们将cnt计数器的值清零,并引入一个新的计数器用来计数前28字节数据。之所以这样是因为ARP_DATA状态我们总共需要发送46字节数据,而这里面的前28字节数据为ARP数据段,后18字节为填充段,为了将二者分开因此再定义一个计数器对ARP数据段进行计数。
发送部分的具体实现代码如下所示:
`timescale 1ns / 1ps
module ARP_TX(
//system
input clk , //系统时钟
input rst_n , //系统复位
//input
input arp_tx_en , //ARP发送使能信号
input arp_tx_type , //ARP发送类型0:请求 1:应答
input [31:0] des_ip , //目的ip地址
input [47:0] des_mac , //目的MAC地址
input [31:0] crc_data , //CRC校验数据
input [7:0] crc_next , //CRC下次校验数据
//output
output reg tx_done , //发送完成信号
output reg gmii_tx_en , //GMII发送使能信号
output reg[7:0] gmii_txd , //GMII发送数据
output reg crc_en , //CRC校验使能信号
output reg crc_clr //CRC清零系信号
);
parameter DES_IP = {8'd192,8'd168,8'd1,8'd102} ; //目的ip地址即PC端ip地址
parameter DES_MAC = 48'hff_ff_ff_ff_ff_ff ; //目的MAC地址
parameter BOARD_IP = {8'd192,8'd168,8'd1,8'd10} ; //开发板ip地址
parameter BOARD_MAC = 48'h00_11_22_33_44_55 ; //开发板MAC地址,用户可自行设置
parameter ETH_TPYE = 16'h0806 ; //以太网帧类型 ARP
parameter HD_TYPE = 16'h0001 ; //硬件协议类型
parameter PROTOCOL_TYPE = 16'h0800 ; //上层协议类型为ip协议
localparam IDLE = 5'b0_0001 ; //空闲状态
localparam PREAMBLE = 5'b0_0010 ; //发送前导码+SFD
localparam ETH_HEAD = 5'b0_0100 ; //发送帧头
localparam ARP_DATA = 5'b0_1000 ; //发送ARP数据
localparam CRC = 5'b1_0000 ; //发送CRC校验数据
localparam MIN_DATA_NUM = 6'd46 ; //最小计数值,不满则填充数据
//reg type
reg arp_tx_en_r0 ; //对ARP发送使能信号打拍以检测上升沿
reg arp_tx_en_r1 ;
reg arp_tx_en_r2 ;
reg [4:0] cur_state ; //现态
reg [4:0] next_state ; //次态
reg state_en ; //状态转移使能信号
reg [5:0] cnt ;
reg [4:0] data_cnt ; //发送数据计数器
reg tx_done_t ; //发送完成信号暂存
reg [7:0] preamble [7:0] ; //前导码+SFD
reg [7:0] eth_head [13:0] ; //帧头
reg [7:0] arp_data [27:0] ; //ARP数据
//wire type
wire posedge_arp_tx_en ; //ARP发送使能信号上升沿
assign posedge_arp_tx_en = ~arp_tx_en_r2 && arp_tx_en_r1 ;
//对使能信号打拍以检测上升沿
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
arp_tx_en_r0 <= 1'b0;
arp_tx_en_r1 <= 1'b0;
arp_tx_en_r2 <= 1'b0;
end
else begin
arp_tx_en_r0 <= arp_tx_en;
arp_tx_en_r1 <= arp_tx_en_r0;
arp_tx_en_r2 <= arp_tx_en_r1;
end
end
//三段式状态机第一段,状态转移
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cur_state <= IDLE;
else
cur_state <= next_state;
end
//三段式状态机第二段
always @(*) begin
next_state = IDLE;
case (cur_state)
IDLE:begin
if(state_en)
next_state = PREAMBLE;
else
next_state = IDLE;
end
PREAMBLE:begin
if(state_en)
next_state = ETH_HEAD;
else
next_state = PREAMBLE;
end
ETH_HEAD:begin
if(state_en)
next_state = ARP_DATA;
else
next_state = ETH_HEAD;
end
ARP_DATA:begin
if(state_en)
next_state = CRC;
else
next_state = ARP_DATA;
end
CRC:begin
if(state_en)
next_state = IDLE;
else
next_state = CRC;
end
default:next_state = IDLE;
endcase
end
//三段式状态机第三段,判读数据输出结果
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_done_t <= 1'b0;
state_en <= 1'b0;
cnt <= 6'd0;
gmii_tx_en <= 1'b0;
gmii_txd <= 7'd0;
data_cnt <= 5'd0;
crc_en <= 1'b0;
//前导码
preamble[0] <= 8'h55;
preamble[1] <= 8'h55;
preamble[2] <= 8'h55;
preamble[3] <= 8'h55;
preamble[4] <= 8'h55;
preamble[5] <= 8'h55;
preamble[6] <= 8'h55;
//SFD
preamble[7] <= 8'hd5;
//帧头
eth_head[0] <= DES_MAC[47:40];//目的MAC地址
eth_head[1] <= DES_MAC[39:32];
eth_head[2] <= DES_MAC[31:24];
eth_head[3] <= DES_MAC[23:16];
eth_head[4] <= DES_MAC[15:8];
eth_head[5] <= DES_MAC[7:0];
eth_head[6] <= BOARD_MAC[47:40];//开发板MAC地址
eth_head[7] <= BOARD_MAC[39:32];
eth_head[8] <= BOARD_MAC[31:24];
eth_head[9] <= BOARD_MAC[23:16];
eth_head[10] <= BOARD_MAC[15:8];
eth_head[11] <= BOARD_MAC[7:0];
eth_head[12] <= ETH_TPYE[15:8];//以太网帧类型
eth_head[13] <= ETH_TPYE[7:0];
//ARP数据
arp_data[0] <= HD_TYPE[15:8];//硬件类型
arp_data[1] <= HD_TYPE[7:0];
arp_data[2] <= PROTOCOL_TYPE[15:8];//上层协议类型
arp_data[3] <= PROTOCOL_TYPE[7:0];
arp_data[4] <= 8'h06;//硬件地址长度
arp_data[5] <= 8'h04;//协议地址长度
arp_data[6] <= 8'h00;//
arp_data[7] <= 8'h01;//OP操作码 01:request 02:ack
arp_data[8] <= BOARD_MAC[47:40]; //发送端(源)MAC地址
arp_data[9] <= BOARD_MAC[39:32];
arp_data[10] <= BOARD_MAC[31:24];
arp_data[11] <= BOARD_MAC[23:16];
arp_data[12] <= BOARD_MAC[15:8];
arp_data[13] <= BOARD_MAC[7:0];
arp_data[14] <= BOARD_IP[31:24]; //发送端(源)IP地址
arp_data[15] <= BOARD_IP[23:16];
arp_data[16] <= BOARD_IP[15:8];
arp_data[17] <= BOARD_IP[7:0];
arp_data[18] <= DES_MAC[47:40]; //接收端(目的)MAC地址
arp_data[19] <= DES_MAC[39:32];
arp_data[20] <= DES_MAC[31:24];
arp_data[21] <= DES_MAC[23:16];
arp_data[22] <= DES_MAC[15:8];
arp_data[23] <= DES_MAC[7:0];
arp_data[24] <= DES_IP[31:24]; //接收端(目的)IP地址
arp_data[25] <= DES_IP[23:16];
arp_data[26] <= DES_IP[15:8];
arp_data[27] <= DES_IP[7:0];
end
else begin
state_en <= 1'b0;
crc_en <= 1'b0;
tx_done_t <= 1'b0;
gmii_tx_en <= 1'b0;
case (next_state)
IDLE:begin
if(posedge_arp_tx_en)begin
state_en <= 1'b1;
if((des_ip != 32'd0) || (des_mac != 48'd0))begin
eth_head[0] <= des_mac[47:40];
eth_head[1] <= des_mac[39:32];
eth_head[2] <= des_mac[31:24];
eth_head[3] <= des_mac[23:16];
eth_head[4] <= des_mac[15:8];
eth_head[5] <= des_mac[7:0];
arp_data[18] <= des_mac[47:40];
arp_data[19] <= des_mac[39:32];
arp_data[20] <= des_mac[31:24];
arp_data[21] <= des_mac[23:16];
arp_data[22] <= des_mac[15:8];
arp_data[23] <= des_mac[7:0];
arp_data[24] <= des_ip[31:24];
arp_data[25] <= des_ip[23:16];
arp_data[26] <= des_ip[15:8];
arp_data[27] <= des_ip[7:0];
end
else;
if(arp_tx_type == 1'b0)
arp_data[7] <= 8'h01;
else
arp_data[7] <= 8'h02;
end
else;
end
PREAMBLE:begin//进入PREAMBLE状态之后拉高gmii_tx_en信号表示开始发送数据,并且每次时钟周期发送8位数据总共发送7个8'h55和一个8'hd5
gmii_tx_en <= 1'b1;
gmii_txd <= preamble[cnt];
if(cnt == 6'd7)begin
state_en <= 1'b1;
cnt <= 6'd0;
end
else
cnt <= cnt + 1'b1;
end
ETH_HEAD:begin//发送帧头
gmii_tx_en <= 1'b1;
crc_en <= 1'b1;
gmii_txd <= eth_head[cnt];
if(cnt == 6'd13)begin
state_en <= 1'b1;
cnt <= 6'd0;
end
else
cnt <= cnt + 1'b1;
end
ARP_DATA:begin
gmii_tx_en <= 1'b1;
crc_en <= 1'b1;
if(cnt == MIN_DATA_NUM - 1'b1)begin
state_en <= 1'b1;
cnt <= 6'd0;
data_cnt <= 5'd0;
end
else
cnt <= cnt + 1'b1;
if(data_cnt <= 5'd27)begin
data_cnt <= data_cnt + 1'b1;
gmii_txd <= arp_data[data_cnt];
end
else
gmii_txd <= 8'd0;//填充
end
CRC:begin//发送CRC校验值
gmii_tx_en <= 1'b1;
cnt <= cnt + 1'b1;
if(cnt == 6'd0)
gmii_txd <= {~crc_next[0], ~crc_next[1], ~crc_next[2],~crc_next[3],
~crc_next[4], ~crc_next[5], ~crc_next[6],~crc_next[7]};
else if(cnt == 6'd1)
gmii_txd <= {~crc_data[16], ~crc_data[17], ~crc_data[18],
~crc_data[19], ~crc_data[20], ~crc_data[21],
~crc_data[22],~crc_data[23]};
else if(cnt == 6'd2) begin
gmii_txd <= {~crc_data[8], ~crc_data[9], ~crc_data[10],
~crc_data[11],~crc_data[12], ~crc_data[13],
~crc_data[14],~crc_data[15]};
end
else if(cnt == 6'd3) begin
gmii_txd <= {~crc_data[0], ~crc_data[1], ~crc_data[2],~crc_data[3],
~crc_data[4], ~crc_data[5], ~crc_data[6],~crc_data[7]};
tx_done_t <= 1'b1;
state_en <= 1'b1;
cnt <= 1'b0;
end
else;
end
default:;
endcase
end
end
//发送ARP发送完成信号和CRC复位信号
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_done <= 1'b0;
crc_clr <= 1'b0;
end
else begin
tx_done <= tx_done_t;
crc_clr <= tx_done_t;
end
end
endmodule
仿真结果如下所示:
发送部分
发送使能信号拉高,并且在打拍之后检测到时钟上升沿,代表一次发送开始。
发送数据与发送数据计数。
单包数据发生完成。
接收部分
检测到第一个8'h55,状态机的状态进行跳转。
接收数据与接收数据计数。
接收到的MAC地址和ip地址。
上板测试:
按下按键后,利用wireshark抓取数据包结果如下图所示:
开发板发送请求信号,PC端进行应答。
开发板MAC地址与ip地址。
代码参考正点原子。