目录
一、ARP协议的作用
ARP(Address Resolution Protocol,地址解析协议)是用来将IP地址解析为MAC地址的协议。主机或三层网络设备上会维护一张ARP表,用于存储IP地址和MAC地址的映射关系,一般ARP表项包括动态ARP表项和静态ARP表项。
为什么需要ARP?
在局域网中,当主机或其它三层网络设备有数据要发送给另一台主机或三层网络设备时,需要知道对方的网络层地址(即IP地址)。但是仅有IP地址是不够的,因为IP报文必须封装成帧才能通过物理网络发送,因此发送方还需要知道接收方的物理地址(即MAC地址),这就需要一个通过IP地址获取物理地址的协议,以完成从IP地址到MAC地址的映射。地址解析协议ARP即可实现将IP地址解析为MAC地址。
ARP 工作的基本流程
ARP 工作流程分为两个阶段,一个是 ARP 请求过程,另一个是 ARP 响应过程。
工作流程如下所示。
在上面图片中,主机 A 的 IP 地址为 192.168.1.1,主机 B 的 IP 地址为 192.168.1.2。
主机 A 与主机 B 进行通信,需要获取其 MAC 地址,基本流程如下:
- 主机 A 以广播形式向网络中所有主机发送 ARP 请求,请求包中包含了目标 IP 地址 192.168.1.2。
- 主机 B 接收到请求,发现自己就是主机 A 要找的主机,返回响应,响应包中包含自己的 MAC 地址
ARP报文格式
ARP请求和应答报文格式
报文的长度是42字节。前14字节的内容表示以太网首部,后28字节的内容表示ARP请求或应答报文的内容。报文中相关字段的解释如下图所示。
注意:标准以太网帧的最小长度为64字节,上述全部的字节数为42字节,所以在发送ARP报文的时候需要补充0。
二、代码实现
模块的主要作用
ARP_RX模块的主要作用:
ARP_RX接收ARP请求报文或者ARP的响应报文,对于所有的报文,将ARP中相应的的IP、MAC地址写入ARP Table中。对于请求报文,通知ARP_TX模块发送本机的IP、MAC地址。
ARP_TX模块的主要作用:
1、是在接收到ARP_RX模块发送过来的目的IP、目的MAC后回复关于本节点的ARP响应报文
2、当接收到本机的ARP激活信号后,发送ARP请求报文
ARP_Table模块的主要作用:
利用寄存器简单实现一个ARP表,包括一个IP表和MAC表,两者之间的表项是一一映射的关系。当收到ARP_RX发送过来的IP和MAC地址时,会更新ARP表中的内容。
代码实现
参考FPGA奇哥网课
ARP_RX模块
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2024/05/16 09:29:41
// Design Name:
// Module Name: ARP_RX
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// ARP_RX接收ARP请求报文或者ARP的响应报文,对于所有的报文,将ARP中相应的的IP、MAC地址写入ARP Table中
// 对于请求报文,通知ARP_TX模块发送本机的IP、MAC地址
// 0x0806属于ARP的帧类型
//
module ARP_RX(
input i_clk ,
input i_rst ,
input [63:0] s_axis_data ,
input [79:0] s_axis_user ,//16'dlen,48'dsource_mac,16'dtype
input [7 :0] s_axis_keep ,
input s_axis_last ,
input s_axis_valid ,
output [47:0] o_target_mac ,
output [31:0] o_target_ip ,
output o_target_valid ,
output o_replay_valid
);
reg [63:0] rs_axis_data ;
reg [79:0] rs_axis_user ;
reg [7 :0] rs_axis_keep ;
reg rs_axis_last ;
reg rs_axis_valid ;
reg [47:0] ro_target_mac ;
reg [31:0] ro_target_ip ;
reg ro_target_valid ;
reg ro_replay_valid ;
reg r_arp ;
reg [15:0] r_cnt ;
reg [15:0] r_op ;
assign o_target_mac = ro_target_mac ;
assign o_target_ip = ro_target_ip ;
assign o_target_valid = ro_target_valid ;
assign o_replay_valid = ro_replay_valid ;
always@(posedge i_clk,posedge i_rst)begin
if(i_rst)begin
rs_axis_data <= 'd0;
rs_axis_user <= 'd0;
rs_axis_keep <= 'd0;
rs_axis_last <= 'd0;
rs_axis_valid <= 'd0;
end
else begin
rs_axis_data <= s_axis_data ;
rs_axis_user <= s_axis_user ;
rs_axis_keep <= s_axis_keep ;
rs_axis_last <= s_axis_last ;
rs_axis_valid <= s_axis_valid ;
end
end
//通过USER字段判断是否为ARP包
always@(posedge i_clk,posedge i_rst)begin
if(i_rst)
r_arp <= 'd0;
else
if(s_axis_valid && !rs_axis_valid && s_axis_user[15:0] == 16'h0806)
r_arp <= 1'b1;
else if(s_axis_valid && !rs_axis_valid && s_axis_user[15:0] != 16'h0806)
r_arp <= 1'b0;
else
r_arp <= r_arp;
end
always@(posedge i_clk,posedge i_rst)begin
if(i_rst)
r_cnt <= 'd0;
else
if(r_cnt == 2)
r_cnt <= 'd0;
else if(r_arp || r_cnt)
r_cnt <= r_cnt + 1;
else
r_cnt <= r_cnt ;
end
//解析出操作类型字段
always@(posedge i_clk,posedge i_rst)begin
if(i_rst)
r_op <= 'd0;
else
if(r_arp && rs_axis_valid && r_cnt == 0)
r_op <= rs_axis_data[15:0];
else
r_op <= r_op;
end
//不管是不是响应报文,当接收到ARP报文时,都去更新ARP表
always@(posedge i_clk,posedge i_rst)begin
if(i_rst)
ro_replay_valid <= 'd0;
else
if(r_arp && rs_axis_valid && r_cnt == 1)
ro_replay_valid <= 1'b1;
else
ro_replay_valid <= 'd0;
end
//如果是响应报文,则需要将源IP地址和源MAC地址解析出来
always@(posedge i_clk,posedge i_rst)begin
if(i_rst)
ro_target_mac <= 'd0;
else
if(r_cnt == 1 && rs_axis_valid) //不管是不是ARP包都解出此字段,如果是ARP包去控制valid拉高就可以了
ro_target_mac <= rs_axis_data[63:16];
else
ro_target_mac <= ro_target_mac;
end
//将源IP地址取出
always@(posedge i_clk,posedge i_rst)begin
if(i_rst)
ro_target_ip <= 'd0;
else
if(r_cnt == 1 && rs_axis_valid)
ro_target_ip <= {rs_axis_data[15:0],s_axis_data[63:48]};
else
ro_target_ip <= ro_target_ip;
end
//如果是ARP请求包则拉高valid,通知ARP_TX模块请求端的ip、mac地址已经解析出来
always@(posedge i_clk,posedge i_rst)begin
if(i_rst)
ro_target_valid <= 'd0;
else
if(r_arp && r_cnt == 1 && rs_axis_valid && r_op == 1)
ro_target_valid <= 1'b1;
else
ro_target_valid <= 'd0;
end
endmodule
ARP_TX模块
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2024/05/16 09:29:41
// Design Name:
// Module Name: ARP_TX
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// ARP_TX模块有两个功能,1、是在接收到ARP_RX模块发送过来的目的IP、目的MAC后回复关于本节点的ARP响应报文
// 2、当接收到本机的ARP激活信号后,发送ARP请求报文
//
module ARP_TX#(
parameter P_SOURCE_IP = {8'd192,8'd168,8'd100,8'd100} ,
parameter P_SOURCE_MAC = 48'h00_01_02_03_04_05
)(
input i_clk ,
input i_rst ,
input [47:0] i_set_source_mac ,
input i_set_smac_valid ,
input [31:0] i_set_source_ip ,
input i_set_sip_valid ,
input i_arp_active ,//主动请求ARP报文
input [47:0] i_target_mac ,
input [31:0] i_target_ip ,
input i_target_valid ,
output [63:0] m_axis_data ,
output [79:0] m_axis_user ,//16'dlen,48'dsource_mac,16'dtype
output [7 :0] m_axis_keep ,
output m_axis_last ,
output m_axis_valid
);
reg [47:0] ri_set_source_mac ;
reg [31:0] ri_set_source_ip ;
reg [47:0] ri_target_mac ;
reg [31:0] ri_target_ip ;
reg ri_target_valid ;
reg ri_arp_active ;
reg [63:0] rm_axis_data ;
reg [79:0] rm_axis_user ;
reg [7 :0] rm_axis_keep ;
reg rm_axis_last ;
reg rm_axis_valid ;
reg [15:0] r_cnt ;
reg [15:0] r_op ;
assign m_axis_data = rm_axis_data ;
assign m_axis_user = rm_axis_user ;
assign m_axis_keep = rm_axis_keep ;
assign m_axis_last = rm_axis_last ;
assign m_axis_valid = rm_axis_valid ;
always@(posedge i_clk,posedge i_rst)begin
if(i_rst)
ri_set_source_mac <= P_SOURCE_MAC;
else
if(i_set_smac_valid)
ri_set_source_mac <= i_set_source_mac;
else
ri_set_source_mac <= ri_set_source_mac;
end
always@(posedge i_clk,posedge i_rst)begin
if(i_rst)
ri_set_source_ip <= P_SOURCE_IP;
else
if(i_set_sip_valid)
ri_set_source_ip <= i_set_source_ip;
else
ri_set_source_ip <= ri_set_source_ip;
end
always@(posedge i_clk,posedge i_rst)begin
if(i_rst)begin
ri_target_mac <= 'd0;
ri_target_ip <= 'd0;
end
else begin
if(i_target_valid)begin
ri_target_mac <= i_target_mac ;
ri_target_ip <= i_target_ip ;
end
else begin
ri_target_mac <= ri_target_mac;
ri_target_ip <= ri_target_ip ;
end
end
end
always@(posedge i_clk,posedge i_rst)begin
if(i_rst)
ri_arp_active <= 'd0;
else
ri_arp_active <= i_arp_active;
end
always@(posedge i_clk,posedge i_rst)begin
if(i_rst)
ri_target_valid <= 'd0;
else
ri_target_valid <= i_target_valid;
end
//当接收到i_target_valid时说明有请求包到来,需要发送应答包
//当i_arp_active时说明需要发送请求包
always@(posedge i_clk,posedge i_rst)begin
if(i_rst)
r_cnt <= 'd0;
else
if(r_cnt == 5)
r_cnt <= 0;
else if(ri_target_valid || ri_arp_active || r_cnt)
r_cnt <= r_cnt + 1;
else
r_cnt <= r_cnt ;
end
//判断操作类型
always@(posedge i_clk,posedge i_rst)begin
if(i_rst)
r_op <= 'd0;
else
if(ri_target_valid)
r_op <= 16'd2;
else if(ri_arp_active)
r_op <= 16'd1;
else
r_op <= r_op;
end
//发送数据
//以太网最小帧应该为64字节,除去以太网帧头和FCS,数据段应该为46字节
always@(posedge i_clk,posedge i_rst)begin
if(i_rst)
rm_axis_data <= 'd0;
else
case(r_cnt)
0 : rm_axis_data <= ri_target_valid ?{16'd1,16'h0800,8'd6,8'd4,16'd2}:{16'd1,16'h0800,8'd6,8'd4,16'd1};
1 : rm_axis_data <= {ri_set_source_mac,ri_set_source_ip[31:16]};
2 : rm_axis_data <= r_op == 1 ? {ri_set_source_ip[15:0],48'h00_00_00_00_00_00} :{ri_set_source_ip[15:0],ri_target_mac};
3 : rm_axis_data <= r_op == 1 ? {64'h00_00_00_00_00_00_00_00} : {ri_target_ip,32'h00_00_00_00};
4 : rm_axis_data <= {64'h00_00_00_00_00_00_00_00};
5 : rm_axis_data <= {64'h00_00_00_00_00_00_00_00};
default : rm_axis_data <= 'd0;
endcase
end
//控制keep信号
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
rm_axis_keep <= 8'b1111_1111;
else
if(r_cnt == 5)
rm_axis_keep <= 8'b1111_1100;
else
rm_axis_keep <= 8'b1111_1111;
end
//控制User信号
always@(posedge i_clk,posedge i_rst)begin
if(i_rst)
rm_axis_user <= 'd0;
else
rm_axis_user <= {16'd6,48'hff_ff_ff_ff_ff_ff,16'h0806};//MAC地址写入广播地址
end
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
rm_axis_last <= 'd0;
else if(r_cnt == 5)
rm_axis_last <= 'd1;
else
rm_axis_last <= 'd0;
end
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
rm_axis_valid <= 'd0;
else if(rm_axis_last)
rm_axis_valid <= 'd0;
else if(ri_target_valid || ri_arp_active)
rm_axis_valid <= 'd1;
else
rm_axis_valid <= rm_axis_valid;
end
endmodule
ARP_Table
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2024/05/16 14:47:41
// Design Name:
// Module Name: ARP_Table
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 简单的ARP表
// 表采用触发器实现
//
module ARP_Table(
input i_clk ,
input i_rst ,
input [47:0] i_write_mac ,
input [31:0] i_write_ip ,
input i_write_valid ,
input [31:0] i_query_ip ,//不能打拍,因为想一次就读出来
input i_query_valid ,
output [47:0] o_read_mac ,
output o_read_valid
);
reg [47:0] ri_write_mac ;
reg [31:0] ri_write_ip ;
reg ri_write_valid ;
reg r_rewrite ;
reg r_rewrite_1d ;
reg [2 :0] r_readdr ;
reg [2 :0] r_write_addr ;
reg [2 :0] r_write_addr_ed ;
reg [2 :0] r_read_addr ;
reg [31:0] r_ram_ip[0:7] ;
reg [47:0] r_ram_mac[0:7] ;
reg [47:0] ro_read_mac ;
reg ro_read_valid ;
assign o_read_mac = ro_read_mac ;
assign o_read_valid = ro_read_valid ;
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst) begin
ri_write_mac <= 'd0;
ri_write_ip <= 'd0;
ri_write_valid <= 'd0;
r_rewrite_1d <= 'd0;
end else begin
ri_write_mac <= i_write_mac ;
ri_write_ip <= i_write_ip ;
ri_write_valid <= i_write_valid;
r_rewrite_1d <= r_rewrite;
end
end
//查看是否需要重新更新IP地址对应的MAC地址
//并获取IP所在的表项的地址
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
r_readdr <= 'd0;
else if(r_ram_ip[0] == i_write_ip && i_write_valid)
r_readdr <= 'd0;
else if(r_ram_ip[1] == i_write_ip && i_write_valid)
r_readdr <= 'd1;
else if(r_ram_ip[2] == i_write_ip && i_write_valid)
r_readdr <= 'd2;
else if(r_ram_ip[3] == i_write_ip && i_write_valid)
r_readdr <= 'd3;
else if(r_ram_ip[4] == i_write_ip && i_write_valid)
r_readdr <= 'd4;
else if(r_ram_ip[5] == i_write_ip && i_write_valid)
r_readdr <= 'd5;
else if(r_ram_ip[6] == i_write_ip && i_write_valid)
r_readdr <= 'd6;
else if(r_ram_ip[7] == i_write_ip && i_write_valid)
r_readdr <= 'd7;
else
r_readdr <= 'd0;
end
//查看是否需要重新更新IP地址对应的MAC地址
//并获取重写信号
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
r_rewrite <= 'd0;
else if(r_ram_ip[0] == i_write_ip && i_write_valid)
r_rewrite <= 'd1;
else if(r_ram_ip[1] == i_write_ip && i_write_valid)
r_rewrite <= 'd1;
else if(r_ram_ip[2] == i_write_ip && i_write_valid)
r_rewrite <= 'd1;
else if(r_ram_ip[3] == i_write_ip && i_write_valid)
r_rewrite <= 'd1;
else if(r_ram_ip[4] == i_write_ip && i_write_valid)
r_rewrite <= 'd1;
else if(r_ram_ip[5] == i_write_ip && i_write_valid)
r_rewrite <= 'd1;
else if(r_ram_ip[6] == i_write_ip && i_write_valid)
r_rewrite <= 'd1;
else if(r_ram_ip[7] == i_write_ip && i_write_valid)
r_rewrite <= 'd1;
else
r_rewrite <= 'd0;
end
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
r_write_addr <= 'd0;
else if(r_rewrite) //如果需要重新写入表项,则将表项的地址给到写地址
r_write_addr <= r_readdr;
else if(!r_rewrite && r_rewrite_1d)//返回之前地址指针所在的位置
r_write_addr <= r_write_addr_ed;
else if(ri_write_valid) //当需要更新表项时,进行地址自加1
r_write_addr <= r_write_addr + 1;
else
r_write_addr <= r_write_addr;
end
always@(posedge i_clk,posedge i_rst)//暂时存储目前的地址指针
begin
if(i_rst)
r_write_addr_ed <= 'd0;
else
r_write_addr_ed <= r_write_addr;
end
genvar i;
generate for(i = 0;i < 8;i = i + 1)
begin
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
r_ram_ip[i] <= 'd0;
else if((i == r_write_addr && ri_write_valid && !r_rewrite) || (r_rewrite_1d && i == r_write_addr))//进行ARP IP表项更新
r_ram_ip[i] <= ri_write_ip;
else
r_ram_ip[i] <= r_ram_ip[i];
end
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
r_ram_mac[i] <= 48'hff_ff_ff_ff_ff_ff;
else if((i == r_write_addr && ri_write_valid && !r_rewrite) || (r_rewrite_1d && i == r_write_addr))//进行ARP MAC表项的更新
r_ram_mac[i] <= ri_write_mac;
else
r_ram_mac[i] <= r_ram_mac[i];
end
end
endgenerate
//获取查询IP对应的MAC地址
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
ro_read_mac <= 'd0;
else if(i_query_valid && i_query_ip == r_ram_ip[0])
ro_read_mac <= r_ram_mac[0];
else if(i_query_valid && i_query_ip == r_ram_ip[1])
ro_read_mac <= r_ram_mac[1];
else if(i_query_valid && i_query_ip == r_ram_ip[2])
ro_read_mac <= r_ram_mac[2];
else if(i_query_valid && i_query_ip == r_ram_ip[3])
ro_read_mac <= r_ram_mac[3];
else if(i_query_valid && i_query_ip == r_ram_ip[4])
ro_read_mac <= r_ram_mac[4];
else if(i_query_valid && i_query_ip == r_ram_ip[5])
ro_read_mac <= r_ram_mac[5];
else if(i_query_valid && i_query_ip == r_ram_ip[6])
ro_read_mac <= r_ram_mac[6];
else if(i_query_valid && i_query_ip == r_ram_ip[7])
ro_read_mac <= r_ram_mac[7];
else
ro_read_mac <= ro_read_mac;
end
//标识当前的输出有效
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
ro_read_valid <= 'd0;
else if(i_query_valid)
ro_read_valid <= 'd1;
else
ro_read_valid <= 'd0;
end
endmodule
三、功能仿真
仿真步骤:
1、例化两个ARP模块,其中一个模块发送ARP请求,查看另外一个模块的应答
2、读取ARP_Table中其中一个IP地址的MAC
发送ARP请求包
ARP应答
查询一个IP的MAC地址