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出厂固件里跑的是 UHDUSRP 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里面最核心的两条数据通路是 RXAD9361 → FPGA → host和 TXhost → 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 --argstypeb200 --fpga-pathb210_fpga.binpromgen那一步是把 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 MSPS12 位 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] 12sd0; sum 15sd0; dout 12sd0; dout_valid 1b0; 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 1b1; end else begin dout_valid 1b0; end end endmodule这段代码的关键点sum用 15 位是因为 8 个 12 位数相加最大需要 15 位少一位就溢出波形上表现为周期性削顶。sum[14:3]是算术右移Verilog 里对有符号数右移会自动符号扩展但前提是sum声明为signed。很多人在这里翻车声明成reg [14:0] sum然后右移负数就变成大正数。参数怎么改窗口长度改 8 为 16sum位宽要加到 16 位右移位数改成 4。但注意 B210 的 LVDS 数据率下16 点滑动平均的时序余量会紧综合后要看 timing report 的 WNSWorst 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_data0 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 的设备 ID04b4: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 里出现Uunderflow或Ooverflow。原因你插入的处理模块引入了额外延迟host 端的缓冲深度不够。或者模块的吞吐率跟不上 LVDS 数据率。解决在模块前后加 FIFO 缓冲深度至少 1024。如果是吞吐问题把组合逻辑改成流水线或者降低 AD9361 的采样率。用uhd_usrp_probe看RX Streamer的overflows计数持续增长就是吞吐不够。5. 进阶用进位链 TDC 在 B210 上做高精度时间测量前面讲的都是数据通路改造但 B210 FPGA 还有一个被低估的能力Spartan-6 的进位链可以做 TDCTime-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*43 : i*4]), .O(), .CI(i 0 ? start_pulse : carry_chain[i*4-1]), .CYINIT(1b0), .DI(4b0000), .S(4b1111) ); 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 信号处理时序和物理层的坑永远比逻辑本身多别指望仿真过了就万事大吉。希望帮到你。本文还有配套的精品资源点击获取