第五十四章:基于双路AD的以太网波形传输实验(初学者详解)
本章在讲什么?(先看这个)
这是实战篇的收官实验,也是一次"大型组装":把前面学过的两大块技能拼在一起——
- 双路 AD 模块(双路 AD 实验学过):把信号发生器输出的模拟波形转成数字信号;
- 以太网 UDP 传输(第 46~49 章学过):把数字波形实时发到 PC 上位机显示。
实验任务:双路 AD 模块采集信号发生器的波形 → FPGA 采集数据 → 通过以太网 UDP 发给上位机 → 上位机实时绘制波形。整个数据链路是:
信号发生器 → 双路AD模块 → FPGA(位宽转换+封装+异步FIFO) → 千兆以太网 → 上位机显示波形
开始前先做一个重要的带宽预算,这也是本实验架构设计的依据:
- 双路 AD 模块采样时钟最大 50MHz;
- 千兆以太网理论速率 1000Mbit/s,但扣除帧头、CRC 校验、帧间隙(IFG)等开销后,实际可用速率低于 1000Mbit/s。
结论:尽管实际速率打折,实时传输 50MHz 采样率的 AD 波形依然没有压力,所以不需要外接存储器缓存波形——数据先进异步 FIFO 缓冲,攒够一包就发。这种"先算带宽再定架构"的思路值得记住。
核心概念拆解
1. 系统架构:五个模块各司其职

| 模块 | 职责 |
|---|---|
| clk_wiz_0(时钟模块) | 输出三路时钟:50MHz(AD 驱动)、50MHz 偏移 250°(AD 采样)、200MHz(IDELAYCTRL 参考) |
| start_transfer_ctrl(开始传输) | 解析上位机 UDP 命令:1=传通道一、2=传通道二、0=停止 |
| ad_10bit_to_16bit(位宽转换) | 10 位 AD 数据高位补零扩成 16 位,按 ctrl 选通道 |
| img_data_pkt(数据封装) | 异步 FIFO(写 16 位/读 8 位)+ 攒够 1200 字节触发一包 UDP 发送 |
| eth_top(以太网顶层) | ARP/ICMP/UDP 收发,复用 UDP 测试实验 |
总体模块框图:

关于那个 250° 相位偏移的采样时钟要特别说一下:AD 驱动时钟和采样时钟频率相同但相位相反(180°)是常规做法——在数据稳定的中点采样。但如果排除硬件原因后波形仍有毛刺,可以通过微调采样时钟相位来消除,本实验实测调到 250° 毛刺消失。这是很实用的调试经验:采样相位是可调的调试自由度,不是固定值。
2. 开始传输控制模块:命令从"1/0"升级为"1/2/0"
与 OV5640/OV7725 视频传输实验里的同名模块相比,本实验多了双通道选择。命令集:
| 收到的字节 | 含义 | transfer_flag | ctrl |
|---|---|---|---|
8'd1 |
开始传通道一 | 1 | 2'b01 |
8'd2 |
开始传通道二 | 1 | 2'b10 |
8'd0 |
停止传输 | 0 | 2'b00 |
| 其他 | 无效,停止 | 0 | 2'b00 |
parameter START_1 = 8'd1; //通道一开始命令
parameter STOP = 8'd0; //停止命令
parameter START_2 = 8'd2; //通道二开始命令
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
transfer_flag <= 1'b0;
ctrl <= 2'b0;
end
//只有"单包接收完成 且 字节数为1"时才解析命令
else if(udp_rec_pkt_done && udp_rec_byte_num == 1'b1) begin
if(udp_rec_data == START_1) begin
transfer_flag <= 1'b1;
ctrl <= 2'b01;
end
else if(udp_rec_data == START_2) begin
transfer_flag <= 1'b1;
ctrl <= 2'b10;
end
else begin //0或其它值:一律停止
transfer_flag <= 1'b0;
ctrl <= 2'b00;
end
end
end
注意触发条件是 udp_rec_pkt_done && udp_rec_byte_num == 1:必须等一包数据接收完成、且包长恰好 1 字节才解析,防止把误包、长包的中间字节当成命令。
3. 数据位宽转换模块:10bit → 16bit,为什么补高位
双路 AD 输出 10 位数据,以太网按字节(8 位)传输。为了方便打包,先把 10 位扩成 16 位:
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
ad_out <= 16'd0;
else if( sel == 2'b01)
ad_out <= {6'd0, ad_in1}; //高位补0,为了适应上位机
else if( sel == 2'b10)
ad_out <= {6'd0, ad_in2};
else
ad_out <= 16'd0;
end
两个细节:
- 高位补零而非低位补零:代码注释写明"这样补 0 为了适应上位机"——上位机按 16 位无符号数解析波形幅值,AD 是无符号输出,高位补零数值不变;如果格式不匹配,上位机画出来的波形会错乱;
- 通道选择在这一层完成:
sel就是开始传输模块输出的ctrl,选中的通道数据才输出,未选中时输出 0——不传的数据根本不会进 FIFO,这是对带宽的第二重保护。
4. 数据封装模块:位宽转换 FIFO + 深度触发的发包

这是本章的核心模块,干三件事:跨时钟域、位宽转换、控制发包节奏。
(1)一包发多少字节:1200
always @(posedge eth_tx_clk or negedge rst_n) begin
if(!rst_n)
udp_tx_byte_num <= 16'd0;
else
udp_tx_byte_num <= 16'd1200; //单包1200字节
end
为什么是 1200?UDP 数据段最大 1472 字节(1500 MTU − 20 IP 头 − 8 UDP 头),1200 留了余量且是偶数——16 位数据转 8 位后,一个采样点恰好占 2 字节,1200 字节 = 600 个采样点,数据边界不会错位。
(2)攒够就发:tx_busy_flag 防止重复触发
always @(posedge eth_tx_clk or negedge rst_n) begin
if(!rst_n) begin
udp_tx_start_en <= 1'b0;
tx_busy_flag <= 1'b0;
end
//上位机未发送"开始"命令时,不发送数据
else if(transfer_flag == 1'b0) begin
udp_tx_start_en <= 1'b0;
tx_busy_flag <= 1'b0;
end
else begin
udp_tx_start_en <= 1'b0;
//空闲 且 FIFO攒够1200字节 → 触发一包发送
if(tx_busy_flag == 1'b0 && fifo_rdusedw >= 1200) begin
udp_tx_start_en <= 1'b1;
tx_busy_flag <= 1'b1;
end
else if(udp_tx_done) //一包发完,解除忙标志
tx_busy_flag <= 1'b0;
end
end
节奏控制闭环:FIFO 深度 ≥ 1200 → 拉高 udp_tx_start_en(一拍)→ 以太网模块发 udp_tx_req 请求数据 → FIFO 读出 → 发送完成 udp_tx_done → tx_busy_flag 清零 → 等待下一次攒够。tx_busy_flag 保证上一包没发完时不会重复触发。
(3)异步 FIFO:写 16 位、读 8 位,复位接 transfer_flag 取反
async_fifo_2048x8b u_async_fifo_2048x8b (
.rst (~transfer_flag ), //停止传输时立刻清空FIFO
.wr_clk (clk ), //写侧:50MHz_deg(AD采样时钟域)
.rd_clk (eth_tx_clk ), //读侧:以太网发送时钟域
.din (img_data ), //16位写入
.wr_en (~full ),
.rd_en (udp_tx_req && ~empty ),
.dout (udp_tx_data ), //8位读出
.full (full ),
.empty (empty ),
.rd_data_count(fifo_rdusedw )
);
三个精妙之处:
- 位宽不对称 FIFO:写 16 位读 8 位,FIFO 控制器自动完成"一个 16 位采样点拆成两个 8 位字节",位宽转换和跨时钟域一步到位,不需要额外逻辑;
rst接~transfer_flag:上位机在传输中途发"停止"命令时,FIFO 被立即复位清空——不会残留半包数据导致下次传输的波形错位。这是用传输使能反向控制缓存复位的常见手法;- 读使能 =
udp_tx_req && ~empty:以太网请求数据且 FIFO 非空才读,双保险防止读空。
5. 顶层模块:三路时钟 + 环环相扣的握手
顶层 ad_udp_pc 要点:
(1)时钟分配:
assign ad_clk_1 = clk_50m; //两路AD共用50MHz驱动时钟
assign ad_clk_2 = clk_50m;
assign ad_oe_1 = 1'b0; //输出使能固定有效
assign ad_oe_2 = 1'b0;
//clk_wiz_0 输出:clk_50m / clk_50m_deg250(相位偏移250°) / clk_200m
位宽转换模块和封装模块的写侧都用 clk_50m_deg250——整个 AD 数据通路从采样到写 FIFO 都在采样时钟域,出 FIFO 才进以太网时钟域,时钟域边界只有 FIFO 一处,非常干净。
(2)MAC/IP 参数集中定义(与前面以太网章节一致):
parameter BOARD_MAC = 48'h00_11_22_33_44_55;
parameter BOARD_IP = {8'd192,8'd168,8'd1,8'd10};
parameter DES_MAC_DEFAULT = 48'hff_ff_ff_ff_ff_ff;
parameter DES_IP_DEFAULT = {8'd192,8'd168,8'd1,8'd102};
(3)跨模块握手信号全链路:上位机命令(UDP 接收域)→ start_transfer_ctrl 产出 transfer_flag/ctrl → ctrl 回到 AD 采样域选通道 → transfer_flag 控制封装模块发包 → eth_top 的 udp_tx_req/udp_tx_done 回调封装模块。一个跨三个时钟域的控制信号(transfer_flag),靠各模块在本地时钟域打拍同步,这是多时钟域系统里控制信号传播的典型方式。
6. 下载验证(简述)
硬件连接:
- 网线连接开发板 GE_PL 网口与电脑网口;
- 双路 AD 模块插开发板 J3 扩展口;
- 用 SMA 转 BNC 线把信号发生器接到 AD 模块模拟输入,设置信号发生器输出指定频率/幅值的正弦波。
上位机用 ATK-XADC(A 盘 6_常用软件/ATK-XADC 文件夹):打开软件 → 选择采样通道、采样方式、采样数量 → 点"开始传输"(即向上位机约定发送 ASCII "1"/"2")→ 上位机界面实时显示对应通道的波形。若要切通道先发停止("0")再发新通道命令。
验证要点:波形形状与信号发生器设置一致、无毛刺(有毛刺可微调采样时钟相位)、切换通道后显示内容随之切换。
初学者容易踩的坑
-
带宽没算就开干:如果换成更高速率的 AD(比如 125MHz 采样),千兆网实际有效带宽可能不够,就必须降采样或先缓存再传。本实验 50MHz 采样 + 16bit/点 = 800Mbps 原始数据率,实测能跑通但余量不大——发包字节数和 FIFO 深度都是围绕带宽预算定的,改动前先重新算账。
-
FIFO 位宽选错:封装模块的 FIFO 必须配成"写 16 位、读 8 位"。如果配成对称位宽再手动拆字节,拆出来的高低字节顺序很容易弄反,上位机波形会出现"锯齿状"的规律错乱。
-
忘了
rst接~transfer_flag:中途停止传输后 FIFO 里残留的半包数据不清空,下次开始传输时上位机会先收到一段"旧波形",画面跳变。 -
发包触发条件写错:触发必须是
tx_busy_flag == 0 && fifo_rdusedw >= 1200。漏掉 busy 标志会在同一包还没发完时反复触发udp_tx_start_en,导致包序错乱;阈值小于 1200 则会发出短包,字节计数错位。 -
命令解析不做包长过滤:只判断
udp_rec_data不判断udp_rec_byte_num == 1,可能把上位机其他长包数据的中间字节误认成命令,导致传输状态莫名切换。 -
采样时钟相位不调:照抄"180° 反相采样"的结论,遇到毛刺就怀疑硬件或 AD 芯片。记住采样时钟相位是可以微调的调试手段,本实验就是从 180° 调到 250° 才消除毛刺。
-
两个 AD 通道数据混淆:10bit 转 16bit 模块里
sel信号必须与上位机选择的通道严格对应(1=通道一2'b01,2=通道二2'b10)。写反了,上位机选通道一时显示的其实是通道二的波形。
小结 & 下一步
至此,本教程的实战篇全部收官。本章把"模拟信号采集"与"以太网传输"两大技能合体:带宽预算决定架构(50MHz 采样 + 千兆网 → FIFO 缓冲直传,无需片外存储)、三步数据整形(10bit→16bit 高位补零 → 16/8 位宽转换 FIFO → 1200 字节攒包触发)、命令协议升级(1/2/0 三命令 + 包长过滤 + 通道选择)。再加上两个可迁移的调试经验——采样时钟相位微调消毛刺、用传输使能反向复位 FIFO。
回看这 9 章的脉络:从 MDIO 管理接口、ARP/ICMP/UDP 三层协议,到 OV5640/OV7725 视频传输,再到 FFT 频谱分析、FIR 滤波,最后到双路 AD 波形回传——以太网是贯穿始终的主线,而跨时钟域 FIFO、AXI4-Stream 握手、命令解析、攒包发送这些手法反复出现。掌握了一个,就掌握了一类。建议把拓展训练(40MHz AD 时钟 + 单包 800 字节)亲手做一遍,检验一下带宽预算的账你会不会算。