ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于FPGA的DVB-S2 16-APSK发射器设计:从星座映射到工程实践

基于FPGA的DVB-S2 16-APSK发射器设计:从星座映射到工程实践 简介适用于BladeRF平台的16-APSK DVB-S2发射器完整工程源码面向软件定义无线电SDR开发者、FPGA设计工程师及卫星通信方向学习者。项目以Verilog/VHDL实现DVB-S2标准中的16-APSK调制链路包含与BladeRF硬件交互的控制逻辑可帮助用户理解高阶调制算法并落地到实际射频收发实验。资源包共741个文件约5.32MB主要包含VHDL源码114个vhd、C语言驱动与嵌入式代码90个c、69个h、Verilog及SystemVerilog文件61个v、31个sv以及Quartus工程配置、仿真脚本和文档等覆盖从编译、仿真到上板运行的完整流程。目前已有90人学习。压缩包内含工程主目录bladerf-dvbs2-master解压后可按文档搭建工具链生成比特流并加载到BladeRF设备。对于想掌握DVB-S2标准、研究16-APSK调制实现或基于BladeRF做SDR开发的读者这是一份难得的完整参考源码。1. 一套现成的 16-APSK DVB-S2 发射器源码值得拆开看的点在哪乍看 DVB-S2 发射器第一反应是去 GNU Radio 里找 dvbs2 模块调几个参数就能出波形。但卫星链路上的 16-APSK 是嵌套环星座涉及 LDPC 编码、物理层帧头、加扰和成形滤波要在通用处理器上实时跑 20 Msps 以上的发射链路资源占用和调度抖动都会非常难看。这个压缩包给的是另一种思路把调制链搬进 FPGA。工程在 BladeRF 上实现 16-APSK DVB-S2 发射器源码同时涉及 Verilog 和 VHDL还保留了 NIOS II 软核控制射频前端的完整工程痕迹适合手里有 BladeRF 的 SDR 工程师也适合正在做 DVB-S2 发射端 FPGA 设计的人当参考骨架。解包之后重点不是找到某个能跑的比特流而是看懂文件之间的分工、约束文件的坑以及前端射频芯片和 FPGA 的数据通路是怎么对齐的。2. DVB-S2 物理层与 16-APSK 星座映射的工程取舍2.1 为什么 16-APSK 比 8PSK 和 32APSK 更难“直接照抄”DVB-S2 的调制方式不是随便选个星座图就能用。标准里的 16-APSK 确切地说是一个 412 APSK即内环 4 个点、外环 12 个点内环和外环的半径比值随编码率变化不是固定比例。这与普通 QAM 的最大区别是QAM 星座点的判决门限全是直线而 APSK 的判决边界一部分是圆弧、一部分是径向线。FPGA 里实现时如果按直角坐标直接量化误差向量幅度对环半径比会非常敏感量化步长稍微大一点外环 12 个点之间的相位裕度就不够用了。工程实现里最值得看的不是 QPSK 那种查表即可的映射而是两件事16-APSK 的比特到符号映射表以及映射之前比特到幅度和相位的重新排列。DVB-S2 是在 FECFRAME 的比特序列上做串并转换每次取 4 个比特构成一个符号。标准规定外环相位偏置为 π/12内环则为 π/4这个细节很容易被忽略。如果直接把外环第一个点放在 0 度得到的是一个被旋转过的星座接收端解调时所有符号都会判错。提示编码率 2/3 和 9/10 使用的环半径比相差近 20%修改工程参数时必须同时改发射端 ROM 系数和接收端解映射表只改一侧会导致判决边界完全错位。2.2 编码率对应的环半径比和相位偏置参数表在写 RTL 之前先把 DVB-S2 标准里的 16-APSK 参数表拉出来看。下面的参数来自 ETSI EN 302 307工程里要做的是把这些值换算成归一化 IQ 坐标编码率环半径比 γ内环点数外环点数外环相位偏置2/33.15412π/123/42.85412π/124/52.75412π/125/62.70412π/128/92.60412π/129/102.57412π/12环比率只是相对值。实际发送时为了不让发射平均功率随调制方式变化需要把星座图按能量归一化。设外环半径为 R_o内环半径 R_i R_o / γ再按 4·R_i² 12·R_o² 16 的关系缩放。R_i 和 R_o 在硬件里只能以定点数表示工程里一般取 8 到 12 bit 带符号数量化步长直接决定 EVM 上限。有经验的 FPGA 工程师会直接把归一化好的 I/Q 值预生成 ROM 初始化文件放进 block RAM而不是在硬件里用除法器现场计算。BladeRF 的 FPGA 资源有限16 个符号 × 16 bit 的 ROM 就够用。想自己改参数的话用脚本算好再转定点比在 RTL 里做除法靠谱得多。2.3 用 Python 生成归一化 16-APSK 星座点我自己动手的第一步是把上面的表格变成一排定点 I/Q 数据再去对照 RTL 里的 case 语句。下面是生成 16-APSK 星座点并做能量归一化的参考实现import numpy as np def dvbs2_16apsk(code_rate, bits_iq12): # 编码率对应的环半径比来自 DVB-S2 标准 gamma {2/3: 3.15, 3/4: 2.85, 4/5: 2.75, 5/6: 2.70, 8/9: 2.60, 9/10: 2.57}[code_rate] inner_n 4 outer_n 12 # 内环起始相位 pi/4外环起始相位 pi/12 inner_ph np.pi / 4 np.arange(inner_n) * 2 * np.pi / inner_n outer_ph np.pi / 12 np.arange(outer_n) * 2 * np.pi / outer_n R_o 1.0 R_i R_o / gamma # 平均功率归一化让 16 个点总能量等于 16 avg_power (inner_n * R_i**2 outer_n * R_o**2) / (inner_n outer_n) scale np.sqrt(1.0 / avg_power) pts [] for idx in range(inner_n): pts.append((scale * R_i * np.cos(inner_ph[idx]), scale * R_i * np.sin(inner_ph[idx]), idx)) for idx in range(outer_n): pts.append((scale * R_o * np.cos(outer_ph[idx]), scale * R_o * np.sin(outer_ph[idx]), inner_n idx)) max_val max(abs(p[0]) for p in pts) iq_scale (2**(bits_iq - 1) - 1) / max_val print(f// 16-APSK {code_rate}, {bits_iq}-bit signed I/Q) for i, q, sym in pts: i_int int(round(i * iq_scale)) q_int int(round(q * iq_scale)) print(fiq_rom[{sym:2d}] 16sd{i_int:5d}; fiq_rom[{sym 16:2d}] 16sd{q_int:5d};)脚本里inner_ph和outer_ph决定星座点的起始相位。内环按 π/4 起转外环按 π/12 起转这一点与 DVB-S2 的星座图定义一致。scale做能量归一化让不同编码率拥有相同的平均发射功率。最后打印的iq_rom可以直接复制进 Verilog 的 ROM 初始化块I、Q 各自占 16 个地址映射器查表时用sym_in取 I用sym_in 16取 Q。2.4 为什么说原工程的模块划分是合理的原本看到libhal_bsp.a、nios_system.bsf这些文件容易误以为发射器的调制全靠软件。实际上 NIOS II 只是配置 LMS6002D 的寄存器、设置发射频率和增益、管理数据包元数据不会参与逐符号的 16-APSK 调制。把调制放到 FPGA 里的直接收益是确定性FPGA 侧时钟由晶振直接驱动不存在操作系统调度抖动。USB 3.0 通路在软件侧有缓冲但缓冲溢出在 GNU Radio 方案里会造成符号中断FPGA 内的tx_meta_fifo以硬件 FIFO 吸收这种突发射频输出不会出现间隙。这个架构划分是 DVB-S2 发射器能长期稳定工作的前提。3. 源码结构、文件职责与 bitstream 编译流程3.1 压缩包里各文件到底承担什么角色解压后扫一眼文件列表能看出这不是单一厂商的干净工程包而是带移植痕迹的完整工程。先把文件归类再决定从哪里下手文件语言/格式职责与判读bladerf-hosted.vhd.bakVHDLBladeRF hosted 模式顶层包含 USB 接口和 FX3 握手的实体.bak 表示改动前的备份bladerf.sdc.bakSDC 时序约束定义 DAC/ADC 时钟和 FPGA 与 LMS6002D 之间的 I/Q 接口约束nios_system.bsfQuartus 符号NIOS II 软核系统与外设连接的框图把射频前端控制寄存器映射给软件fir_filter.bsf模块符号成形滤波器对应 DVB-S2 的基带脉冲整形tx_meta_fifo.bsf模块符号TX 元数据 FIFO隔离 host 与符号率时钟域settings.bspNIOS II BSP 配置定义 NIOS II 外设驱动、堆栈和中断参数libhal_bsp.a预编译库NIOS II HAL 感知库配合settings.bsp使用pkt_legacy.cC 源码旧版元数据包格式的兼容处理alt_log_printf.cC 源码日志输出重定向到 JTAG UARTdevices.cC 源码列出 NIOS II 系统中各外设的基地址和中断号.bsf是 Quartus 的块符号文件在顶层原理图里代表一个模块。用文本编辑器打开能看到WIRE和PORT它本身不包含逻辑只表达连接关系。真正重建 bitstream 时.bsf对应的底层 HDL 文件必须在工程里否则 Quartus 会报找不到符号。看源码时我习惯直接用 VSCode 打开.v和.vhd配合 Verilog-HDL 插件做语法检查比在 Quartus 自带编辑器里翻页快很多。3.2 用 Quartus Prime 命令行完成编译建议先不开图形界面直接用命令行把流程跑通换机器也能复现。以下命令以 Quartus Prime 标准版为例# 假设解压后的工程目录名是 bladerf-dvbs2-master cd bladerf-dvbs2-master # 1) 分析与综合 quartus_map --read_settings_fileson --write_settings_filesoff bladerf_dvbs2_top # 2) 布局布线 quartus_fit bladerf_dvbs2_top # 3) 生成 bitstream quartus_asm bladerf_dvbs2_top # 4) 时序分析验证 bladerf.sdc 里的约束有没有满足 quartus_sta bladerf_dvbs2_top四个命令对应完整 FPGA 流程。quartus_map里的--read_settings_fileson让工具读取已有工程配置文件如果目录里缺.qsf需要先打开 Quartus 新建工程再把源码和.sdc加进来。quartus_fit最耗时布线后时序不满足时报告会写在bladerf_dvbs2_top.sta.rpt里重点看 reg-to-reg 路径的 slack。quartus_sta这步最容易暴露问题。很多网上工程里的.sdc时钟名和顶层端口对不上报Cant find clock。这里尤其注意bladerf.sdc.bak只是备份后缀是.bak不代表能用。遇到这种情况先用get_ports查顶层端口名再把create_clock的 target 改正确否则时序约束等于没写。3.3 移植到 Xilinx 平台时要改哪些东西如果你手上没有 BladeRF或者只有 Vivado 许可证这个工程不能原样编进 Xilinx 器件。需要改的是四类文件约束、时钟原语、NIOS II 替代逻辑、FIFO 的 IP 例化方式。约束方面Xilinx 用 XDC 而不是 SDC基本语法兼容主要把get_ports {adc_clk}改成get_ports adc_clk再删掉 Altera 特有的set_false_path写法。时钟原语上Altera 的altpll要换成MMCME2_BASE参数和复位时序都不一样。NIOS II 在这里承担的任务不重只是配置 SPI 寄存器。如果不想在 Xilinx 里再造一个 MicroBlaze可以用一个简单的状态机把devices.c里的配置序列搬过来再把寄存器地址映射改成 AXI-Lite 地址省掉软核的启动时间。3.4 加载到 BladeRF 的两种方式拿到.sof之后加载命令有两种# 方式一下载到 FPGA 配置 SRAM重启后失效 bladeRF-cli -l bladerf_dvbs2_top.sof # 方式二烧写到 SPI flash掉电保存 bladeRF-cli -f bladerf_dvbs2_top.rbf -x方式一适合调试重启 BladeRF 后恢复出厂配置方式二适合现场部署。烧写 flash 前需要先把.sof转成.rbf用quartus_cpf -c bladerf_dvbs2_top.sof bladerf_dvbs2_top.rbf即可。转换后文件比.sof小很多烧写时间也更短。4. 调制器与 TX 数据通路的 RTL 实现细节4.1 数据通路host 包如何变成 I/Q 符号BladeRF hosted 模式下host 通过 FX3 的 USB 3.0 把基带数据包送到 FPGAFPGA 侧入口就是tx_meta_fifo。数据包头带有元数据数据体是待发射比特流。必须加 FIFO 的原因是 USB 传输有突发性host 可能一次写入几千字节然后短暂停顿而调制器必须按固定符号率取数两者速率不匹配要用异步 FIFO 把读时钟域和写时钟域隔开。FIFO 之后是加扰器和 FEC 编码器。压缩包源码中 FEC 对应 DVB-S2 的 BCH 外码和 LDPC 内码。LDPC 在 FPGA 里通常实现为部分并行结构完整并行会消耗太多 LUT。这部分如果不需要改建议当成黑盒。重点注意 16-APSK 映射器前后的时序对齐因为 LDPC 输出和调制输入之间有流水线延迟没对齐会出现符号错位。4.2 可综合的 16-APSK 映射器模块下面是一个可直接综合的 16-APSK 映射模块用 case 语句或 ROM 查表完成比特到 I/Q 转换。数值来自 2.3 节脚本对 3/4 编码率的计算结果module apsk16_mapper #( parameter IQ_WIDTH 16 )( input wire clk, input wire rst_n, input wire sym_valid, input wire [3:0] sym_in, output reg [IQ_WIDTH-1:0] i_out, output reg [IQ_WIDTH-1:0] q_out, output reg sym_valid_out ); // 16-APSK 412按符号索引存放归一化定点 IQ // 数据来自 16-APSK 3/4 编码率生成脚本12bit 定点扩展到 16bit reg [IQ_WIDTH-1:0] iq_rom [0:31]; initial begin // 内环 4 点相位偏置 pi/4半径 650/2047 iq_rom[0] 16sd461; iq_rom[16] 16sd461; // 内环点0 iq_rom[1] -16sd461; iq_rom[17] 16sd461; // 内环点1 iq_rom[2] -16sd461; iq_rom[18] -16sd461; // 内环点2 iq_rom[3] 16sd461; iq_rom[19] -16sd461; // 内环点3 // 外环 12 点相位偏置 pi/12半径 2047/2047 iq_rom[4] 16sd1977; iq_rom[20] 16sd530; // 外环点0 iq_rom[5] 16sd1448; iq_rom[21] 16sd1448; // 外环点1 iq_rom[6] 16sd530; iq_rom[22] 16sd1977; // 外环点2 // ... 其余外环点由脚本生成 end always (posedge clk or negedge rst_n) begin if (!rst_n) begin sym_valid_out 1b0; i_out {IQ_WIDTH{1b0}}; q_out {IQ_WIDTH{1b0}}; end else begin // 延迟一拍输出和后续成形滤波器对齐 i_out iq_rom[sym_in]; q_out iq_rom[sym_in 16]; sym_valid_out sym_valid; end end endmodule实现有两个关键点。第一把 I 和 Q 放进同一个 ROM用sym_in和sym_in 16作为两个地址综合时 Quartus 或 Vivado 能推断为 block RAM而不是一堆 LUT 选择器。第二输出打了一拍让sym_valid_out和 IQ 数据在流水线上保持同步后面接成形滤波器时不会错位。外环剩余点的系数建议直接用脚本生成不要手动填。手动填 12 个外环点的最大问题是相位偏置容易算错尤其 π/12 换算成带符号定点数后一旦符号位处理错星座会整体旋转 15 度接收端很难纠正这种固定旋转。4.3 成形滤波器与上变频接口的时序调制器输出的是符号速率的 IQ 采样点BladeRF 的 DAC 工作频率更高中间必须插入成形滤波器。工程里的fir_filter.bsf承担的就是这个角色。DVB-S2 推荐滚降系数为 0.20、0.25 或 0.35默认通常取 0.20。不同滚降系数对应的占用带宽和符号率约束如下滚降系数20 MHz 采样下最大符号率占用带宽建议 DAC 回退0.2016.67 MSps20 MHz7-8 dB0.2516.00 MSps20 MHz7-8 dB0.3514.81 MSps20 MHz6-7 dB成形滤波器在实现上有两种做法多相 FIR 或查找表插值。多相 FIR 的系数可以在软件里按滚降系数重新计算工程里用的是 FIR 结构系数放在 ROM。符号率变化时NIOS II 软件更新系数但更新期间调制器必须停止否则输出符号率会不连续。接入 DAC 之前还要处理增益和直流偏置。BladeRF 的 LMS6002D 在 16-APSK 这种高峰均比波形下必须留足够回退。如果 DAC 满刻度对应 -10 dBmIQ 幅度建议设置在满刻度的 -6 dB 到 -8 dB防止 7 dB 以上峰均比造成削波。工程里没有现成寄存器调这个需要在应用层调整发射增益。4.4 工程里容易踩的时序坑第一是复位顺序。nios_system.bsf里的 NIOS II 软核有cpu_reset信号它只复位 CPU 和外设寄存器不会复位tx_meta_fifo。如果整个系统共用全局复位FIFO 读指针和写指针可能出现初始状态不一致表现为首包数据丢失。处理方式是在软件初始化射频前端之后再单独拉高 FIFO 复位。第二bladerf.sdc.bak里的时钟约束可能是旧版 PLL 的时钟名和当前工程不一致时Quartus 会忽略约束并给 warning。此时输出频谱会出现明显杂散因为时序约束没生效。建议删掉旧create_clock重新从原理图找 PLL 输出时钟名。第三异步 FIFO 的读写指针建议用三段式状态机按各自时钟域独立控制。直接照搬单时钟 FIFO 代码读时钟域会出现亚稳态。BladeRF 工程里 FIFO 两侧时钟频率不同这个问题一旦出现表现为偶发的符号错位很难用逻辑分析仪直接抓到。5. 从 ILA 到 bladeRF-cli实时验证 16-APSK 发射链路的技巧5.1 先抓芯片内部信号别急着看频谱把信号送上天线之前先在 FPGA 内部挂逻辑分析仪。BladeRF 工程里建议抓三个信号tx_meta_fifo的空满标志、调制器输入sym_valid、成形滤波器输出i_out。逻辑分析仪触发条件设为tx_meta_fifo_empty 0 sym_valid 1一次抓 2048 个点。这一步能看出符号流是否连续。如果sym_valid有毛刺或 FIFO 空信号频繁拉高问题在 host 数据供应端不在调制器。5.2 利用 bladeRF-cli 捕获 IQ 并做离线 EVM 分析波形稳定输出后在 TX 口和 RX 口之间接一根短射频线做回环采集# -s 设置采样率-n 设置样本数-f 指定输出文件 bladeRF-cli -f loopback_iq.sc16q11 -s 20M -n 16Msc16q11是 BladeRF 的定点 IQ 格式I/Q 各 16 bit 有符号数11 bit 有效位。采集后用 Python 读取做匹配滤波和星座图绘制。注意回环时 RX 增益不要开太高否则 LNA 饱和会让外环 12 个点向外扩张看起来像 EVM 很差其实是接收链路削波。5.3 判断结果对错的三个指标第一看星座点是否落在两个环上。16-APSK 的 I/Q 平面里内环 4 个点半径较小外环 12 个点半径较大。如果看到 16 个点挤成一群说明环比率参数没生效。第二看是否整体旋转。内环应起于 π/4外环应起于 π/12旋转超过 10 度就要检查 ROM 系数。第三计算 EVM。无噪声回环条件下 16-APSK 的 EVM 应低于 1%如果超过 5% 先查成形滤波器滚降系数和 DAC 回退量。回环验证时RX 路径的直流偏置会叠加在信号上。校验发射机时可以直接减去均值但真实卫星链路里的直流偏置需要用接收端专门估计不能简单地减均值了事。调试到这一步发射链路从符号映射到射频回环的验证就算闭环了接下来就可以直接接上真实码流做端到端的 TS 层测试。本文还有配套的精品资源点击获取
返回列表