目录
[1.1、 DHCP 报文格式](#1.1、 DHCP 报文格式)
[1.2、 DHCP Options(选项字段)](#1.2、 DHCP Options(选项字段))
[1.3DHCP 的核心:DORA 交互流程](#1.3DHCP 的核心:DORA 交互流程)
[(1). Discover(发现阶段)](#(1). Discover(发现阶段))
[(2). Offer(提供阶段)](#(2). Offer(提供阶段))
[(3). Request(请求阶段)](#(3). Request(请求阶段))
[(4). Acknowledge(确认阶段)](#(4). Acknowledge(确认阶段))
[1.4、 租约维护(Lease)](#1.4、 租约维护(Lease))
[2. 基于 FPGA 的主机 IP 自动配置方案设计](#2. 基于 FPGA 的主机 IP 自动配置方案设计)
[2.1 方案一:嵌入式 DHCP 服务器硬件逻辑实现 (DHCP Server Logic)](#2.1 方案一:嵌入式 DHCP 服务器硬件逻辑实现 (DHCP Server Logic))
[offer 和 Acknowledge阶段的代码:](#offer 和 Acknowledge阶段的代码:)
[核心区别:siaddr vs Server Identifier (Option 54)](#核心区别:siaddr vs Server Identifier (Option 54))
[在Verilog 代码中如何实现](#在Verilog 代码中如何实现)
[2.2 方案二:IPv4 Link-Local (最简单的纯硬件方案)](#2.2 方案二:IPv4 Link-Local (最简单的纯硬件方案))
[2.2.1. 核心原理:RFC 3927 标准](#2.2.1. 核心原理:RFC 3927 标准)
[2.2.2. FPGA 端的实现步骤](#2.2.2. FPGA 端的实现步骤)
[第一步:固化 IP 和 MAC 地址](#第一步:固化 IP 和 MAC 地址)
[第二步:必须实现 ARP 响应(关键!)](#第二步:必须实现 ARP 响应(关键!))
[第三步:简单的 UDP 监听/发送](#第三步:简单的 UDP 监听/发送)
[3. 为什么大家都说它有"潜规则"?](#3. 为什么大家都说它有“潜规则”?)
[1. 协议的"盲目性" (Standardization)](#1. 协议的“盲目性” (Standardization))
[2. 为什么物理上"一根线"也可能有多个服务器?](#2. 为什么物理上“一根线”也可能有多个服务器?)
[3. 在 Request 报文中,电脑是如何"挑人"的?](#3. 在 Request 报文中,电脑是如何“挑人”的?)
[对你 FPGA 实现的启示](#对你 FPGA 实现的启示)
[拓展二:电脑端的行为:双重机制并行(DHCP + Link-Local)](#拓展二:电脑端的行为:双重机制并行(DHCP + Link-Local))
[1. 抓包证据分析](#1. 抓包证据分析)
[2. 为什么电脑会"坚持不懈"?](#2. 为什么电脑会“坚持不懈”?)
[1. 端口的三大分类](#1. 端口的三大分类)
[2. 为什么你的 DHCP 必须用 67 和 68?](#2. 为什么你的 DHCP 必须用 67 和 68?)
[3. FPGA 开发中的自由度](#3. FPGA 开发中的自由度)
[💡 调试总结](#💡 调试总结)
前言
针对科研探测设备与上位机直连的通信需求,本文提出了基于 FPGA 的两种主机 IP 自动配置方案:DHCP 协议与 IPv4 链路本地地址(Link-Local Address)。该设计有效规避了传统静态 IP 手动配置的局限性,显著提升了系统部署的灵活性与自动化水平。
1.DHCP协议
DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)是应用层协议,旨在为网络中的主机自动分配 IP 地址、子网掩码、默认网关以及 DNS 服务器。其在以太网帧的位置如下:

其中各层协议的组成如下:




1.1、 DHCP 报文格式
在 FPGA 中手写 DHCP,你需要关注的是这个巨大的 UDP 载荷(通常固定为 300 字节 以上)。
| 字段名称 | 长度 (Bytes) | 描述 | FPGA 实现要点 |
|---|---|---|---|
| Message Type (op) | 1 | 1=Request, 2=Reply | 作为 Server,你发送时该位恒为 0x02 |
| Hardware Type | 1 | 以太网通常为 0x01 |
恒定值 |
| Hardware Length | 1 | MAC 地址长度,恒为 0x06 |
恒定值 |
| Hops | 1 | 跳数,通常为 0x00 |
恒定值 |
| Transaction ID (xid) | 4 | 关键:随机传输 ID | 你发出的 Offer 必须拷贝 Discover 里的这个 ID |
| Seconds | 2 | 客户端开始请求后的秒数 | 通常设为 0x0000 |
| Flags | 2 | 广播标志位 | 0x8000 表示要求服务端广播回复 |
| Client IP (ciaddr) | 4 | 客户端当前 IP | 初始请求时为 0.0.0.0 |
| Your IP (yiaddr) | 4 | 分配给电脑的 IP | 这是你告诉电脑"给你这个 IP"的地方 |
| Server IP (siaddr) | 4 | DHCP 服务器 IP | 填入你给 FPGA 设定的 IP |
| Client MAC (chaddr) | 16 | 客户端 MAC 地址 | 前 6 字节填入电脑的 MAC,后 10 字节补 0 |
| Magic Cookie | 4 | 固定为 0x63, 0x82, 0x53, 0x63 |
协议识别标志,不可写错 |
| Options | 变长 | 协议扩展选项 | 见下表 |
1.2、 DHCP Options(选项字段)
Options 是 DHCP 的精华,采用 Type-Length-Value (TLV) 格式。在 FPGA 中,你需要手动拼接这些字节。
常见的 Options:
-
Option 53 (Message Type) :长度 1 字节。
1代表 Discover,2代表 Offer,3代表 Request,5代表 Ack。 -
Option 51 (Lease Time) :长度 4 字节。告知电脑 IP 能用多久(例如
3600秒)。 -
Option 1 (Subnet Mask) :长度 4 字节。子网掩码(如
255.255.255.0)。 -
Option 54 (Server Identifier):长度 4 字节。服务器自己的 IP 地址。
-
Option 255 (End) :长度 1 字节。固定为
0xFF,代表 Options 结束。
1.3DHCP 的核心:DORA 交互流程
DHCP 建立在 UDP 之上,服务端监听 67 端口,客户端监听 68 端口。其核心交互被称为 DORA 流程。
(1). Discover(发现阶段)
-
客户端
服务端(全网广播)。
-
电脑(客户端)插上网线后,不知道谁是 DHCP 服务器,于是发送一个目的 IP 为
255.255.255.255的广播包,寻找网络中的服务器。
| 字段层级 | 关键字段 | 值 (Hex/描述) | 硬件处理要点 |
|---|---|---|---|
| MAC 层 | 目的 MAC | FF:FF:FF:FF:FF:FF |
广播帧。 |
| IP 层 | 源 / 目的 IP | 0.0.0.0 / 255.255.255.255 |
此时电脑还没 IP。 |
| UDP 层 | 源 / 目的端口 | 68 / 67 |
电脑客户端到服务端。 |
| DHCP 层 | op | 0x01 |
代表这是一个请求 (Request)。 |
| xid | 0x12345678 (随机) |
FPGA 必须存下这个 ID,后续回包要用。 | |
| chaddr | 电脑的 MAC 地址 | FPGA 必须存下这个 MAC,作为回复的目标。 | |
| Option 53 | 0x35 0x01 0x01 |
消息类型:Discover。 |
(2). Offer(提供阶段)
-
服务端
客户端(单播或广播)。
-
DHCP 服务器收到请求后,从自己的地址池中选出一个未分配的 IP(例如
192.168.1.100),并携带租期、掩码等信息回发给客户端。
| 字段层级 | 关键字段 | 值 (Hex/描述) | 硬件处理要点 |
|---|---|---|---|
| MAC 层 | 目的 MAC | 电脑的 MAC | 如果设置了广播位,也可以发 FF...FF。 |
| IP 层 | 源 / 目的 IP | FPGA IP / 255.255.255.255 |
此时电脑还没 IP,只能发广播或根据 MAC 寻找。 |
| DHCP 层 | op | 0x02 |
代表这是一个回复 (Reply)。 |
| xid | 同 Discover 的 xid |
匹配事务,否则电脑会丢弃。 | |
| yiaddr | 192.168.1.100 |
这是你打算分给电脑的 IP。 | |
| Option 53 | 0x35 0x01 0x02 |
消息类型:Offer。 | |
| Option 1 | 0x01 0x04 FF FF FF 00 |
告诉电脑子网掩码是 255.255.255.0。 |
(3). Request(请求阶段)
-
客户端
服务端(全网广播)。
-
电脑可能收到多个服务器的 Offer,它会选择其中一个,并再次广播:"我接受了来自服务器 A 分配的
192.168.1.100,其他的 Offer 我拒绝了。" -
注意:这里依然用广播,是为了告诉其他服务器它们可以收回之前提供的 IP。
| 字段层级 | 关键字段 | 值 (Hex/描述) | 硬件处理要点 |
|---|---|---|---|
| MAC 层 | 目的 MAC | FF:FF:FF:FF:FF:FF |
全网广播。 |
| DHCP 层 | xid | 同上 | 事务 ID 保持不变。 |
| Option 53 | 0x35 0x01 0x03 |
消息类型:Request。 | |
| Option 50 | 192.168.1.100 |
电脑明确说:"我要刚才那个 IP"。 | |
| Option 54 | FPGA 的 IP 地址 | 核心! 这是电脑选中的服务器 ID。如果不是你的 IP,说明它选了别人。 |
对这部分的理解在拓展1。
(4). Acknowledge(确认阶段)
-
服务端
客户端(单播或广播)。
-
服务器正式确认该 IP 租借给客户端,并告知最后的配置细节。
| 字段层级 | 关键字段 | 值 (Hex/描述) | 硬件处理要点 |
|---|---|---|---|
| IP 层 | 源 / 目的 IP | FPGA IP / 255.255.255.255 |
最后的广播确认。 |
| DHCP 层 | op | 0x02 |
回复。 |
| yiaddr | 192.168.1.100 |
再次确认分配的 IP。 | |
| Option 53 | 0x35 0x01 0x05 |
消息类型:ACK。 | |
| Option 51 | 0x33 0x04 0x00 0x00 0x8C A0 |
租期:例如设置为 36000 秒。 |
1.4、 租约维护(Lease)
DHCP 分配的 IP 不是永久的,而是"租"给电脑的。
-
T1 定时器 :当租期达到 50% 时,电脑会向 FPGA 发送单播 Request,尝试续约。
-
T2 定时器 :当租期达到 87.5% 时,电脑会发送广播 Request 寻找任何可用的服务器。
对 FPGA 的简化建议:在你的 VLF 信号处理应用中,为了简化逻辑,你可以给出一个极长的租期(例如 7 天),甚至忽略 T1/T2 的续约逻辑。只要网线没断,电脑通常不会主动放弃 IP。
2. 基于 FPGA 的主机 IP 自动配置方案设计
在科研探测设备与上位机直连的场景下,为了摆脱手动配置静态 IP 的繁琐过程,本系统设计了硬件协议栈级的自动寻址逻辑。
2.1 方案一:嵌入式 DHCP 服务器硬件逻辑实现 (DHCP Server Logic)
该方案通过在 FPGA 内部构建一个轻量级的 应用层 DHCP 服务器状态机,主动为主机分配指定网段的 IP 地址。交互过程如1.3小结所示。对应的 DHCP Server 逻辑如下:
-
UDP 解析 :只要收到目标端口为 67 的 UDP 包。
-
状态机跳转:
-
收到 Discover
发送 Offer(把你的固定 IP 段扔过去)。
-
收到 Request
发送 Ack。
-
-
报文拼接技巧:
由于大部分报文字节是固定的,你可以把一个完整的 Offer 包(约 342 字节,包含 Ethernet/IP/UDP 头)存入 FPGA 的 ROM 中。
发送时,只需实时动态替换以下几个关键点:
-
以太网帧的目的 MAC。
-
DHCP 报文里的
xid(Transaction ID)。 -
重新计算 IP 和 UDP 的 Checksum(校验和)。
-
代码如下:
Discover和Request的代码:
`default_nettype none
module dhcp_server_request(
input wire clk,
input wire rst_n,
// UDP RX Interface
input wire udp_rx_busy,
input wire [15:0] udp_r_dst_port,
input wire [7:0] udp_r_data,
input wire udp_r_data_valid,
// User Interface
output reg [31:0] out_xid,
output reg [47:0] out_chaddr,
output reg out_is_discover,
output reg out_is_request,
output reg out_valid
);
localparam S_IDLE = 4'h1;
localparam S_PAYLOAD = 4'h2;
localparam S_OPTIONS = 4'h4;
localparam S_DONE = 4'h8;
reg [3:0] state, next_state;
reg [15:0] cnt;
// 1. 状态寄存器 (Sequential)
always @(posedge clk) begin
if (!rst_n) state <= S_IDLE;
else state <= next_state;
end
// 2. 下一状态逻辑 (Combinational)
always @(*) begin
case (state)
S_IDLE: next_state = (udp_rx_busy && udp_r_dst_port == 16'd67) ? S_PAYLOAD : S_IDLE;//不需要判断op,因为FPGA作为服务端
S_PAYLOAD: next_state = (udp_r_data_valid && cnt == 16'd239) ? S_OPTIONS : S_PAYLOAD;
S_OPTIONS: next_state = (udp_r_data_valid && udp_r_data == 8'h35) ? S_DONE :
(~udp_rx_busy ? S_IDLE : S_OPTIONS);
S_DONE: next_state = S_IDLE;
default: next_state = S_IDLE;
endcase
end
// 3. 输出逻辑与寄存器更新 (Sequential)
always @(posedge clk) begin
case (state)
S_IDLE: begin
cnt <= 16'd0;
out_valid <= 1'b0;
end
S_PAYLOAD: begin
if (udp_r_data_valid) begin
cnt <= cnt + 1'b1;
// 提取 xid (Offset 4-7)
if (cnt == 16'd4) out_xid[31:24] <= udp_r_data;
if (cnt == 16'd5) out_xid[23:16] <= udp_r_data;
if (cnt == 16'd6) out_xid[15:8] <= udp_r_data;
if (cnt == 16'd7) out_xid[7:0] <= udp_r_data;
// 提取 Client MAC (Offset 28-33)
if (cnt >= 16'd28 && cnt <= 16'd33) out_chaddr <= {out_chaddr[39:0], udp_r_data};
end
end
S_OPTIONS: begin
if (udp_r_data_valid) begin
// 记录 Option 53 之后的一个字节内容
out_is_discover <= (udp_r_data == 8'h01);
out_is_request <= (udp_r_data == 8'h03);
end
end
S_DONE: begin
out_valid <= 1'b1; // 触发发送模块脉冲
end
endcase
end
endmodule
需要记录xid、电脑的MAC地址以及判断请求的类型即可。
offer 和 Acknowledge阶段的代码:
`default_nettype none
module dhcp_server_reply(
input wire clk,
input wire rst_n,
// UDP TX Interface
output reg udp_tx_req,
input wire udp_tx_busy,
output wire [15:0] udp_t_data_len,
input wire udp_t_data_ready,
output reg udp_t_data_valid,
output reg [7:0] udp_t_data,
// Control Interface
input wire [31:0] in_xid,
input wire [47:0] in_chaddr,
input wire in_is_discover,
input wire in_is_request,
input wire start
);
localparam S_IDLE = 4'h1;
localparam S_START = 4'h2;
localparam S_SEND = 4'h4;
localparam S_WAIT = 4'h8;
reg [3:0] state, next_state;
reg [15:0] cnt;
assign udp_t_data_len = 16'd300; // 固定包长度
// 1. 状态寄存器
always @(posedge clk) begin
if (!rst_n) state <= S_IDLE;
else state <= next_state;
end
// 2. 下一状态逻辑
always @(*) begin
case (state)
S_IDLE: next_state = start ? S_START : S_IDLE;
S_START: next_state = udp_tx_busy ? S_SEND : S_START;
S_SEND: next_state = (udp_t_data_ready && cnt == udp_t_data_len - 1) ? S_WAIT : S_SEND;
S_WAIT: next_state = ~udp_tx_busy ? S_IDLE : S_WAIT;
default: next_state = S_IDLE;
endcase
end
// 3. 输出逻辑
always @(posedge clk) begin
case (state)
S_IDLE: begin
udp_tx_req <= 1'b0;
udp_t_data_valid <= 1'b0;
cnt <= 16'd0;
end
S_START: begin
udp_tx_req <= 1'b1;
end
S_SEND: begin
udp_tx_req <= 1'b0;
if (udp_t_data_ready) begin
udp_t_data_valid <= 1'b1;
cnt <= cnt + 1'b1;
// DHCP 字节拼接逻辑
case (cnt)
16'd0: udp_t_data <= 8'h02; // op: Reply
16'd1: udp_t_data <= 8'h01; // htype: Ethernet
16'd2: udp_t_data <= 8'h06; // hlen
// Transaction ID
16'd4: udp_t_data <= in_xid[31:24];
16'd5: udp_t_data <= in_xid[23:16];
16'd6: udp_t_data <= in_xid[15:8];
16'd7: udp_t_data <= in_xid[7:0];
// Your IP (yiaddr): 10.0.0.203
16'd16: udp_t_data <= 8'd10;
16'd17: udp_t_data <= 8'd0;
16'd18: udp_t_data <= 8'd0;
16'd19: udp_t_data <= 8'd203;
// Client MAC (chaddr)
16'd28: udp_t_data <= in_chaddr[47:40];
16'd29: udp_t_data <= in_chaddr[39:32];
16'd30: udp_t_data <= in_chaddr[31:24];
16'd31: udp_t_data <= in_chaddr[23:16];
16'd32: udp_t_data <= in_chaddr[15:8];
16'd33: udp_t_data <= in_chaddr[7:0];
// Magic Cookie
16'd236: udp_t_data <= 8'h63;
16'd237: udp_t_data <= 8'h82;
16'd238: udp_t_data <= 8'h53;
16'd239: udp_t_data <= 8'h63;
// Option 53: Offer(2) or ACK(5)
16'd240: udp_t_data <= 8'h35;
16'd241: udp_t_data <= 8'h01;
16'd242: udp_t_data <= in_is_discover ? 8'h02 : 8'h05;
// End Option
16'd243: udp_t_data <= 8'hFF;
default: udp_t_data <= 8'h00;
endcase
end
end
S_WAIT: udp_t_data_valid <= 1'b0;
endcase
end
endmodule
提供和确认阶段需要向电脑提供应答op,硬件类型,硬件长度,xid(用的是电脑提供的),yiaddr(给电脑配置的IP地址),CHAddr(电脑的MAC地址),Magic Cookie(固定字节),Option 53: Offer(2) or ACK(5)。
其他默认为0即可。对应的解释如下:
在 FPGA DHCP Server 实现中,
siaddr(下一个服务器 IP 地址)完全可以"省略" (即填充为0.0.0.0),也可以设置为 FPGA 自己的 IP 地址。对于直连电脑并分配 IP 的简单场景,这个字段通常是不需要的。
核心区别:
siaddrvsServer Identifier(Option 54)在 DHCP 协议中,这两个 IP 地址字段的功能完全不同,你只需要保证 Option 54 正确即可:
siaddr(Next Server IP Address) :位于 DHCP 固定首部。它仅在客户端需要进行网络启动(如 PXE 启动)并从另一台服务器(如 TFTP 服务器)获取启动文件时才使用。如果你的 FPGA 只是给电脑分个 IP,将其设为32'h0000_0000是最标准、最省事的做法。
Option 54(Server Identifier) :位于 DHCP 选项区(Options)。这个字段必须填写 FPGA 自己的 IP 地址。它是电脑用来识别到底是哪台服务器给它发了 Offer 的关键标志。在Verilog 代码中如何实现
参考你提供的代码片段:
yiaddr(Your IP Address) :你已经实现在了偏移16'd16到16'd19的位置,这是分给电脑的 IP。
siaddr(Next Server IP Address) :它紧随其后,对应计数器cnt为16'd20到16'd23的位置。目前的实现 :由于你的代码中
default分支将数据设为了8'h00,这意味着siaddr已经被你默认"省略"为了0.0.0.0。这对你目前的 VLF 信号探测项目来说是完全正确的。建议配置表
如果你想让报文看起来更"专业"一点,可以参考下表进行填充:
字段 对应 cnt (Hex/Dec) 建议填充值 作用 yiaddr16'd16~16'd1910.0.0.203给电脑用的地址 siaddr16'd20~16'd230.0.0.0可省略(填零) Option 5416'd243附近(需计算)FPGA 自己的 IP 告诉电脑"我是谁" 温馨提示 :除非你打算让这台电脑通过网线从你的 FPGA 里"远程启动"一个操作系统(这对于 VLF 系统来说确实有点炫技了),否则没必要折腾
siaddr。
代码调试
在 Windows、Linux 或 macOS 系统中,可以通过命令行或图形界面强制触发 DHCP 客户端重新发送请求。这在调试 FPGA 的 DHCP Server 逻辑时非常有用,因为你不需要反复拔插网线。
我使用的是 Windows 环境,可以使用以下命令:
- 重新获取 IP(发送 DHCP Discover) :
ipconfig /renew这是最关键的一步。执行该命令后,Windows 会立即发送 DHCP Discover 广播包。如果FPGA 逻辑正确且硬件通路已通,就能在 Wireshark 中看到响应过程。

调试成功现象:
依次执行四个交互过程,并获取里面的IP。

调试经验:
(1)状态机需要正确,idx,交互阶段的类型需要判断正确;
(2)UDP的源端口和目的端口是固定的需要注意。
调试过程的记录见资源"DHCP调试过程.doc"
2.2 方案二:IPv4 Link-Local (最简单的纯硬件方案)
IPv4 Link-Local(链路本地地址) ,通常被称为 Auto-IP 或 APIPA (Automatic Private IP Addressing)。在 Windows 系统中,当你看到网卡显示"未识别的网络"并分配了一个 169.254.x.x 的 IP 时,就是它在起作用。
对于 FPGA 开发者来说,这是实现"零协议成本"通信的最快路径。以下是该方案的深层运作机制和实现细节:
2.2.1. 核心原理:RFC 3927 标准
当一个网络设备(比如你的电脑)配置为"自动获取 IP",但物理链路接通后一段时间内没有收到任何 DHCP Offer 报文时,它会认为网络中没有 DHCP 服务器。为了不让通信完全中断,它会根据 RFC 3927 协议从 169.254.1.0 到 169.254.254.255 的地址池中随机挑选一个 IP。
-
冲突检测 :电脑选定 IP 后会发出一个 ARP Probe(探测),如果没有收到回应,说明这个 IP 没人用,它就正式启用该 IP。
-
子网掩码:固定为 255.255.0.0。
2.2.2. FPGA 端的实现步骤
在 FPGA 中,你不需要写任何复杂的自动协商逻辑,只需要"伪装"成一个已经在该网段内的静态设备。
第一步:固化 IP 和 MAC 地址
在你的 Verilog 代码(UDP/IP 协议栈模块)中,直接硬编码以下参数:
-
FPGA IP :
169.254.1.10(建议选一个简单的,避免冲突)。 -
FPGA MAC :
00:0A:35:00:01:02(自定义,确保唯一即可)。 -
Subnet Mask :
255.255.0.0。
第二步:必须实现 ARP 响应(关键!)
这是很多同学实验失败的原因。即使你给 FPGA 设了 IP,电脑在发送 UDP 数据包之前,依然会发一个 ARP 请求问:"谁是 169.254.1.10?"
-
逻辑实现 :当 FPGA 收到以太网帧类型为
0x0806(ARP) 且目标 IP 匹配时,立即构造一个 ARP Reply 包,把 FPGA 的 MAC 地址告诉电脑。 -
只有 ARP 通了,电脑的网卡驱动才会把上层 UDP 数据包通过物理层发出来。
第三步:简单的 UDP 监听/发送
实现一个最基础的 UDP 模块,监听特定端口(如 8080),并处理来自电脑的数据流。
3. 为什么大家都说它有"潜规则"?
虽然它实现起来最简单,但有几个工程上的坑需要你提前知道:
| 现象 | 原因 | 影响 |
|---|---|---|
| 启动慢 | Windows 默认会花 30-60 秒 疯狂寻找 DHCP Server。 | 插上网线后,你需要盯着任务栏,直到那个"小圆圈"变成"黄色感叹号"或"未识别的网络",通信才真正开启。 |
| IP 不固定 | 电脑端的 IP 是随机生成的(例如这次是 .12.34,下次是 .56.78)。 |
如果你的 FPGA 需要主动给电脑发数据,你不能在代码里写死电脑的 IP。 |
| 路由受限 | 该地址无法通过路由器进入外网。 | 仅限于点对点(直连)或交换机组网。 |
该方法的确会自动配置一个IP地址,但需要将FPGA的地址也设置为169.254.x.x才能通信。

拓展1:DHCP请求和确认阶段的必要性
这是一个非常敏锐的观察!在你的 FPGA 直连电脑(点对点)场景下,物理上确实只有一台"服务器",但 DHCP 协议作为一种通用标准,它的设计初衷是处理复杂的局域网环境。
要理解为什么要"多此一举"地广播,我们可以从以下三个维度来看:
1. 协议的"盲目性" (Standardization)
你的电脑(作为 DHCP 客户端)在发送 Request 报文时,它的操作系统协议栈(Windows/Linux)并不知道网线的另一头是一个简单的 FPGA,还是一个连接着数十台服务器的交换机。
统一流程:为了保证在任何网络环境下都能正常工作,电脑必须严格遵守 RFC 标准。标准规定 Request 必须是广播,这样可以确保:
被选中的服务器:知道客户接受了它的 Offer。
落选的服务器:知道客户拒绝了它的 Offer,从而立即释放掉那个被占用的临时 IP。
2. 为什么物理上"一根线"也可能有多个服务器?
虽然你现在是直连,但在实际工程应用中,这根线可能接入了:
物理交换机:交换机上可能挂载了多个开启了 DHCP 服务的设备。
虚拟网桥/虚拟机:如果你的电脑开启了 VMware 或 Hyper-V,电脑内部可能会运行多个虚拟 DHCP 服务,它们都会通过这根网线争抢分配 IP。
多径冗余:在工业或深空探测的地面站网络中,为了可靠性,通常会有主、备两台 DHCP 服务器同时运行。
3. 在 Request 报文中,电脑是如何"挑人"的?
既然是广播,大家都能收到。那么 FPGA 怎么知道电脑是选了自己,还是选了别人呢?
这取决于 DHCP Request 报文内部的一个关键字段:Option 54 (Server Identifier)。
如果电脑接受了 FPGA 的 Offer,它会在 Option 54 填入 FPGA 的 IP 地址。
FPGA 的逻辑处理:
收到广播的 Request。
检查 Option 54。
如果是自己的 IP
发送 Ack(确认)。
如果不是自己的 IP
放弃,回到 IDLE(说明电脑选了别人)。
对你 FPGA 实现的启示
在纯 Verilog 实现时,这个机制其实简化了你的设计,而不是增加了难度:
无需关心目标 MAC :由于电脑发出的 Request 是广播包(目的 MAC 为
FF:FF:FF:FF:FF:FF),你的 FPGA 逻辑层不需要识别 MAC 地址就能直接抓到这个包进行解析。"懒人"实现技巧 :在你的点对点实验中,既然确定只有 FPGA 一个服务器,你可以甚至不去校验 Option 54。只要收到 Request 包,就默认电脑选的是你,直接回发 Ack 即可。
冲突规避:如果未来你的设备要接入复杂的实验室网络,记得在状态机里加上对 Option 54 的校验,防止 FPGA 错误地截获了电脑给实验室路由器的请求。
总结
广播 Request 就像是在一个喧闹的房间里大喊:"A 先生,我用你给我的那个号码了!" 这样 A 先生(FPGA)可以确认,而原本也想给你号码的 B 先生、C 先生听到后就会默默地把号码收回去给别人用。
拓展二:电脑端的行为:双重机制并行(DHCP + Link-Local)
电脑在自动配置了 169.254.x.x 地址后,仍然会持续发送 DHCP 请求。
从Wireshark 抓包图中可以清楚地观察到这一行为:

1. 抓包证据分析
- 现状确认 :你的电脑已经由于 DHCP 超时而启用了 Auto-IP (APIPA) 机制,给自己分配了
169.254.8.89这个地址(见下图中的ipconfig结果)。

- 持续请求 :在抓包记录的第 27 行,你可以看到一条 DHCP Discover 报文,其源 IP 依然为
0.0.0.0。这证明了即使电脑有了临时 IP,它仍然在不断"寻找"真正的 DHCP 服务器。
2. 为什么电脑会"坚持不懈"?
-
临时性与局限性 :
169.254.x.x只是一个链路本地地址,它只能在直连的小范围内通信,无法访问外网或跨网段。 -
后台重试机制 :操作系统的网络服务会保持 DHCP 客户端后台运行。它会定期发送广播(通常是每隔几秒或几十秒),一旦你的 FPGA 响应了请求并成功分配了 IP(如你代码中设置的
10.0.0.203),电脑会立刻丢弃169.254.x.x地址并切换到正式 IP。
拓展三:UDP的端口选取
在普通应用开发中,UDP端口号选择可以有很大的自由度;但在实现标准协议(如 DHCP)时,端口是绝对不能随意取的。
端口号的范围是。根据国际标准(IANA),它们被划分为三个"阶级",了解这些规则对你调试 FPGA 协议栈至关重要。
1. 端口的三大分类
| 范围 | 类别 | 说明 | 常见例子 |
|---|---|---|---|
| 知名端口 (Well-known) | 预留给标准服务,不可随意占用。 | DHCP (67/68)、DNS (53)、HTTP (80) | |
| 注册端口 (Registered) | 用于用户自定义的应用或特定软件。 | 你之前工程用的 8001 | |
| 动态端口 (Dynamic) | 操作系统随机分配给客户端临时使用的。 | 浏览器访问网页时分配的随机端口 |
2. 为什么你的 DHCP 必须用 67 和 68?
这就好比收音机:如果广播电台在 90.0MHz 发射信号,你的收音机必须调到 90.0MHz 才能听到。
-
服务端(FPGA)必须监听 67 端口 :电脑发出的 Discover 报文是广播包,它会敲响网络中所有设备 67 号端口的大门。如果你的 FPGA 没在这个端口"守着",包就丢了。
-
客户端(电脑)必须监听 68 端口 :电脑在发包时告诉网络,"我在 68 号端口等回信"。因此你的 FPGA 发送 Offer 时,目的端口必须填 68。
遇到的问题:
在你之前的抓包 中,端口变成了 0 。在网络协议中,端口 0 是一个保留值,通常代表"无效"或"未指定"。电脑的防火墙和协议栈一看到端口是 0,会认为这是一个非法数据包直接过滤掉。
3. FPGA 开发中的自由度
虽然标准协议很死板,但在后续回传 VLF 探测数据 时,你是完全自由的:
-
自定义协议:你可以像之前的工程一样,定义源端口为 8001,目的端口为电脑上的上位机监听端口(比如 8002)。
-
避坑指南 :虽然可以选
之间的任何数字,但尽量避开 3389 (远程桌面)、443 (HTTPS) 等常用端口,防止和电脑上的其他软件"撞车"。
💡 调试总结
对于 DHCP Server 调试,请死死记住这一组配对:
-
FPGA 发包时: 源端口 = 67 ,目的端口 = 68。
-
FPGA 收包时: 只处理目的端口为 67 的 UDP 包。
只要这一组数字填对了,Wireshark 就会立刻把你的包从绿色的"UDP"识别成蓝色的"DHCP"。
以上就是本次笔记的内容。