ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

USRP B210 FPGA二次开发:Verilog自定义信号处理与TDC时间测量实战

USRP B210 FPGA二次开发:Verilog自定义信号处理与TDC时间测量实战

简介:这份资源是USRP B210软件定义无线电平台的FPGA源码包,面向从事SDR开发、硬件加速与无线通信研究的工程师及研究人员。B210作为Ettus USRP系列中广泛使用的型号,其FPGA逻辑承担着射频信号收发、数字上下变频与高速接口等核心功能,源码对理解整机工作机制、进行自定义修改与功能扩展具有直接参考意义。压缩包共约2000个文件,整体31.45MB,以Verilog源文件(1145个.v)为主体,辅以VHDL、Tcl脚本、XDC约束、MIF/COE初始化数据、XCI/XCO IP核配置及仿真测试文件,覆盖综合、实现、仿真与板级配置的完整流程,并包含FIR、FFT、CORDIC等信号处理模块的示例工程。目前已有903人学习下载,适合希望深入USRP FPGA开发、开展硬件加速优化或通信协议定制的读者研读与二次开发。

1. 从 b210_usrp 这个压缩包说起:USRP B210 的 FPGA 里到底装了什么

手里拿到一个叫fpga-b210-verilog.zip的包,解压出来是b210_usrp目录,里面一堆 Verilog 和工程脚本——这大概是很多做 USRP B210 二次开发的工程师都会遇到的场景。B210 是 Ettus 家很经典的一款软件无线电前端,AD9361 射频收发芯片配上 Xilinx Spartan-6 FPGA,出厂固件里跑的是 UHD(USRP Hardware Driver)配套的 FPGA 镜像。但出厂镜像只暴露了标准采样收发通路,一旦你想在 FPGA 里插自己的逻辑——比如做实时频谱检测、自定义触发、滑动窗口滤波、高速 ADC 数据预处理——就必须自己改 FPGA 源码重新综合烧录。

这个标题指向的正是这件事:一份 B210 的 Verilog FPGA 工程,让你能在 USRP 的 FPGA 里塞进自己的信号处理逻辑。它解决的核心问题是「UHD 框架下 FPGA 不可编程」的痛点,适合已经会用 GNU Radio 或 UHD 做上位机开发、但发现主机端处理延迟太大或吞吐不够的从业者。热词里 usrp x410 怎么发射信号、fpga 高速 adc 采样、滑动窗口滤波 verilog 这些诉求,本质上都指向同一个方向:把处理下沉到 FPGA。下面我从工程结构、编译链路、关键模块改造到避坑,把这条路走一遍。

2. b210_usrp 工程结构与 Spartan-6 编译链路拆解

2.1 目录里有什么:从顶层到 AD9361 接口

一份典型的 B210 FPGA 工程(基于 UHD 的uhd/fpga/usrp3或早期usrp2分支裁剪而来)大致长这样:

目录/文件作用
top/b210/顶层模块,例化 radio、DSP、host 接口
radio/AD9361 接口逻辑,含 LVDS 收发、SPI 配置
dsp/抽取/内插、CIC、半带滤波器链
host/与上位机的 USB3 或以太网接口逻辑
settings/时钟、复位、PLL 配置
Makefile/build/ISE 或 Vivado 综合脚本

B210 用的是 Spartan-6 XC6SLX75(部分版本是 LX150),所以综合工具通常是 Xilinx ISE 14.7。这一点很关键——ISE 对 SystemVerilog 支持有限,工程里基本都是纯 Verilog-2001 风格,模块例化用位置映射或命名映射,别指望用logic、always_ff这些。

顶层模块一般叫b210_top或usrp_b210_top,里面最核心的两条数据通路是 RX(AD9361 → FPGA → host)和 TX(host → FPGA → AD9361)。AD9361 和 FPGA 之间走的是 LVDS 差分对,RX 有 12 位数据加帧/时钟信号,TX 同理。热词里 fpga 的 lvds 接收 在这里就是实打实要面对的:DATA_CLK、RX_FRAME、RX_D[11:0] 这几组信号的时序约束写不对,采样直接错位。

2.2 编译链路:从 Verilog 到 bin 文件

B210 的 FPGA 镜像最终要转成.bin给 UHD 加载。完整链路是:

# 1. 进入工程目录 cd b210_usrp/top/b210 # 2. 用 ISE 命令行综合(假设已装 Xilinx ISE 14.7) xtclsh build_b210.tcl # 3. 生成 bit 文件后转 bin promgen -w -b -p bin -o b210_fpga.bin -u 0 b210_top.bit # 4. 拷贝到 UHD 镜像目录 cp b210_fpga.bin /usr/local/share/uhd/images/usrp_b210_fpga.bin # 5. 用 uhd_image_loader 烧录 uhd_image_loader --args="type=b200" --fpga-path="b210_fpga.bin"

promgen那一步是把 Xilinx bit 流转换成 UHD 能识别的 raw bin,-u 0表示不填充起始地址偏移。很多人第一次翻车就翻在这里:直接拿.bit丢给 UHD,加载报错RuntimeError: FPGA image is invalid。原因是 UHD 读的是纯二进制流,bit 文件头部有 Xilinx 的 ASCII 头。

参数上要注意build_b210.tcl里的器件型号必须和板子实际 FPGA 一致。B210 早期批次是 XC6SLX75,后期有 XC6SLX150,选错了综合能过但烧进去不启动。用uhd_usrp_probe看FPGA那一行的型号,或者直接看板子上丝印。

2.3 时钟与复位:别小看这两个信号

B210 的 FPGA 主时钟来自 AD9361 的 DATA_CLK,典型 4 MHz 到 61.44 MHz 可配。工程里一般会用一个 MMCM 把它倍频到 100 MHz 以上给内部逻辑用。复位信号不是简单的外部引脚——B210 没有独立的复位按键,复位来自 UHD 通过 SPI 写的软复位寄存器,或者上电时 PLL 锁定信号。

热词里 fpga 有固定的复位脚吗 这个问题在 B210 上答案是:没有传统意义的复位脚,复位是软逻辑生成的。所以你在自己加的模块里做复位同步时,必须用工程里已有的reset_sync模块,别自己写一个always @(posedge clk or negedge rst_n)就完事,否则上电时序对不上,逻辑跑飞。

提示:改任何模块前,先用uhd_usrp_probe确认当前 FPGA 镜像版本和 UHD 版本匹配。UHD 版本和 FPGA 镜像有兼容矩阵,跨版本烧录可能直接不识别。

3. 在 B210 FPGA 里插入自定义信号处理模块

3.1 找到插入点:RX 通路的 dsp 链

B210 的 RX 数据从 AD9361 进来后,先过 LVDS 解串,然后进 DSP 链做抽取和滤波,最后打包给 host。你要插自己的逻辑,最自然的插入点是 LVDS 解串之后、DSP 抽取之前——这里数据率最高,但也是最能体现 FPGA 价值的地方。

以滑动窗口滤波为例(热词里滑动窗口滤波 verilog 的诉求),假设 AD9361 配成 30.72 MSPS,12 位 I/Q 交织。你要在 RX 通路上加一个 8 点滑动平均:

// 8 点滑动窗口平均,输入 12 位有符号 I 或 Q module sliding_avg8 ( input wire clk, input wire rst_n, input wire signed [11:0] din, input wire din_valid, output reg signed [11:0] dout, output reg dout_valid ); reg signed [11:0] shift_reg [0:7]; reg signed [14:0] sum; // 12 位 + 3 位防溢出 integer i; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin for (i = 0; i < 8; i = i + 1) shift_reg[i] <= 12'sd0; sum <= 15'sd0; dout <= 12'sd0; dout_valid <= 1'b0; end else if (din_valid) begin // 移位 for (i = 7; i > 0; i = i - 1) shift_reg[i] <= shift_reg[i-1]; shift_reg[0] <= din; // 求和:减去最老,加上最新 sum <= sum - shift_reg[7] + din; // 除以 8 即右移 3 位,保留符号 dout <= sum[14:3]; dout_valid <= 1'b1; end else begin dout_valid <= 1'b0; end end endmodule

这段代码的关键点:sum用 15 位是因为 8 个 12 位数相加最大需要 15 位,少一位就溢出,波形上表现为周期性削顶。sum[14:3]是算术右移,Verilog 里对有符号数右移会自动符号扩展,但前提是sum声明为signed。很多人在这里翻车:声明成reg [14:0] sum然后右移,负数就变成大正数。

参数怎么改:窗口长度改 8 为 16,sum位宽要加到 16 位,右移位数改成 4。但注意 B210 的 LVDS 数据率下,16 点滑动平均的时序余量会紧,综合后要看 timing report 的 WNS(Worst Negative Slack),低于 -0.5 ns 就得考虑流水线化。

3.2 例化到顶层:别动 UHD 的握手逻辑

把模块例化进b210_top的 RX 通路时,最容易犯的错是改了 UHD 原有的 valid/ready 握手。B210 的 host 接口逻辑对数据连续性有要求,你插的处理模块如果引入不确定延迟,host 端会丢包。

正确做法是:你的模块只做数据变换,不改变 valid 的节奏。上面那个滑动平均每个din_valid周期出一个dout_valid,延迟固定 1 拍,这样握手逻辑不用动。如果你做的是 CIC 抽取这种会改变速率的模块,必须在后面加一个 FIFO 做跨时钟域缓冲,并且把 FIFO 的almost_full接到 UHD 的流控信号上。

// 在 b210_top 的 RX 通路里例化 sliding_avg8 u_rx_i_avg ( .clk (rx_clk), .rst_n (rx_rst_n), .din (rx_i_data), .din_valid (rx_i_valid), .dout (rx_i_filtered), .dout_valid (rx_i_filtered_valid) );

例化时端口名必须和模块定义完全一致,Verilog 不像 VHDL 有默认绑定。rx_clk用 LVDS 解串后的时钟,别用 MMCM 倍频后的系统时钟,否则跨时钟域没处理,数据全是亚稳态。

3.3 时序约束:LVDS 和内部逻辑分开写

Spartan-6 的时序约束用 UCF 文件。B210 工程里通常有一个b210.ucf,里面已经写好了 LVDS 的输入延迟约束。你加了自己的逻辑后,如果新逻辑的时钟域和 LVDS 不同,要补一条TIMESPEC:

# LVDS 接收约束(工程原有,别删) NET "rx_data<0>" LOC = "P123" | IOSTANDARD = LVDS_25; TIMEGRP "rx_lvds" OFFSET = IN 2.0 ns VALID 4.0 ns BEFORE "rx_clk" RISING; # 你新增逻辑的时钟域约束 NET "rx_clk" TNM_NET = "rx_clk_grp"; TIMESPEC "TS_rx_clk" = PERIOD "rx_clk_grp" 32.55 ns HIGH 50%;

OFFSET = IN 2.0 ns VALID 4.0 ns这两个数不是拍脑袋写的,要查 AD9361 数据手册的 LVDS 时序参数,结合 PCB 走线延迟算。改错了表现是采样数据随机错位,频谱上出现镜像。热词里 fpga 高速 adc 采样 的坑基本都在这里。

注意:ISE 的时序分析报告里,如果 LVDS 输入路径的 slack 是负的,先别急着改逻辑,检查 UCF 里的 IOSTANDARD 是否和原理图一致。B210 的 LVDS 是 2.5V,写成 LVCMOS33 直接烧不进去。

4. 避坑与排查:B210 FPGA 改造的 5 个血泪教训

4.1 烧录后 UHD 不识别,uhd_find_devices空列表

现象:uhd_image_loader显示烧录成功,但uhd_find_devices找不到设备,USB 灯不亮。

原因:FPGA 镜像的 bin 文件生成时promgen参数不对,或者顶层模块的器件型号和实际不符。B210 的 FPGA 配置通过 USB 走的是 FX3 芯片,FPGA 没正常启动时 FX3 不会枚举。

解决:先用lsusb看有没有 Cypress FX3 的设备 ID(04b4:00f0 之类)。有的话说明 FX3 活着但 FPGA 没起来,重新用promgen -w -b -p bin生成,确认-u 0没漏。还是没有就检查build_b210.tcl里的-part参数。

4.2 频谱出现规律性尖峰,间隔等于采样率除以窗口长度

现象:加滑动平均后,频谱上每隔固定频率出现尖峰。

原因:滑动平均的移位寄存器初值没清零,上电后前 8 个采样是随机值,导致输出有暂态。或者sum位宽不够,中间结果溢出后回绕。

解决:复位时把shift_reg全部清零,sum也清零。位宽按输入位宽 + log2(窗口长度)算,12 位输入 8 点窗口就是 15 位,别省。

4.3 综合报错Multi-source in unit或Signal has multiple drivers

现象:ISE 综合到一半报错,指向你新加的模块。

原因:Verilog 里在多个always块里对同一个reg赋值,或者例化时输出端口接了同一个 wire。

解决:一个reg只能在一个always块里赋值。如果是三模冗余(热词里三模冗余 verilog 的场景),三个副本的输出要用不同的 wire,最后在输出级做多数表决,别把三个输出短接。

4.4 时序报告 WNS 为负,但功能仿真正常

现象:ModelSim 仿真波形全对,烧到板子上跑几分钟就出错。

原因:仿真没有时序信息,综合后的布线延迟导致建立时间违例。B210 的 Spartan-6 速度等级是 -3,跑 100 MHz 以上要小心。

解决:看 ISE 的Timing Report,找到 slack 为负的路径,插入一级流水线寄存器。滑动平均的求和那一步是组合逻辑最长的,可以在sum计算和dout输出之间加一拍。

4.5 改了 FPGA 后 GNU Radio 流图报U或O

现象:GNU Radio 里出现U(underflow)或O(overflow)。

原因:你插入的处理模块引入了额外延迟,host 端的缓冲深度不够。或者模块的吞吐率跟不上 LVDS 数据率。

解决:在模块前后加 FIFO 缓冲,深度至少 1024。如果是吞吐问题,把组合逻辑改成流水线,或者降低 AD9361 的采样率。用uhd_usrp_probe看RX Streamer的overflows计数,持续增长就是吞吐不够。

5. 进阶:用进位链 TDC 在 B210 上做高精度时间测量

前面讲的都是数据通路改造,但 B210 FPGA 还有一个被低估的能力:Spartan-6 的进位链可以做 TDC(Time-to-Digital Converter),测量精度能到几十皮秒。热词里使用 fpga 进位链 tdc 测量时间 在 B210 上完全可行,而且因为 B210 有外部触发输入(PPS 或外部信号),做时间间隔测量很自然。

原理是利用 Spartan-6 的CARRY4原语,把信号在进位链里传播,用抽头延迟线采样传播位置。一个CARRY4有 4 个抽头,级联 32 个就是 128 个抽头,每个抽头延迟约 50-80 ps(取决于温度和电压)。代码骨架:

// 简化的进位链 TDC 抽头采样 module tdc_carry ( input wire clk, input wire start_pulse, input wire stop_pulse, output reg [127:0] tap_thermometer ); wire [127:0] carry_chain; // 用 CARRY4 级联构成延迟线 genvar i; generate for (i = 0; i < 32; i = i + 1) begin : gen_carry CARRY4 u_carry ( .CO(carry_chain[i*4+3 : i*4]), .O(), .CI(i == 0 ? start_pulse : carry_chain[i*4-1]), .CYINIT(1'b0), .DI(4'b0000), .S(4'b1111) ); end endgenerate // stop_pulse 到来时锁存进位链状态 always @(posedge stop_pulse) begin tap_thermometer <= carry_chain; end endmodule

这段代码的关键:CARRY4的S输入全接 1,让进位信号以最快速度传播。stop_pulse作为锁存时钟,把当前进位链的状态抓下来。温度计码里 1 的个数乘以单级延迟就是时间差。单级延迟需要标定——用已知频率的时钟做参考,统计多次测量的码值分布,取平均。

标定方法:B210 的 AD9361 可以输出一个已知频率的测试音,把它接到外部触发输入,测出的时间差应该等于周期的整数倍。偏差就是延迟值。温度变化会影响延迟,所以标定要在工作温度下做,或者加温度传感器做补偿。

这个方案的边界:Spartan-6 的进位链延迟一致性一般,DNL(微分非线性)可能到 0.5 LSB 以上,做绝对时间测量精度有限,但做相对时间差测量(比如脉冲间隔)够用。如果你要更高精度,得换 Artix-7 或 Kintex-7,它们的CARRY4一致性更好。

我自己在这个方向上踩过的坑是:一开始没做温度标定,实验室 25 度调好的参数,拿到室外 40 度环境跑,测量值偏了 15%。后来在 FPGA 里加了一个简单的温度补偿查找表,用板载温度传感器读数查表修正延迟值,才稳定下来。做 FPGA 信号处理,时序和物理层的坑永远比逻辑本身多,别指望仿真过了就万事大吉。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进