
简介这是基于BPSK调制解调与卷积编解码的FPGA工程源码采用Vivado2020.1开发并已通过测试适合通信相关专业学生、FPGA开发者以及需要搭建数字基带收发验证平台的工程师。压缩包共包含1691个文件约88.43MB其中除了7个Verilog源文件与5个IP核配置外还包括大量VHDL/Verilog源码、xdc约束、xci配置、do/sh/bat仿真脚本、log日志与txt说明等工程结构清晰便于直接打开、仿真与综合。目前已有433人学习下载说明该方案在通信系统课程设计与FPGA实现方面有一定关注度。工程内的模块划分明确配合博客中的方案介绍读者可以快速理解BPSK调制解调与卷积编解码在FPGA上的实现思路根据仿真脚本和约束文件进行复现也可针对具体参数做修改或二次开发适合课程设计、毕业设计及通信FPGA入门实践。1. 项目概述与设计思路1.1 这个工程解决什么问题最近在整理一套基于BPSK调制解调与卷积编解码的完整FPGA工程发射链路从随机数据源开始依次经过卷积编码、BPSK映射、基带成型、中频调制最终形成可供DAC输出的中频信号接收链路则从ADC采样数据出发依次完成数字下变频、匹配滤波、载波同步、定时同步、数据判决最后用Viterbi译码还原出原始信息比特。这条链路是数字通信物理层最经典、最基础的一条完整通路把BPSK调制解调和卷积编解码这两块独立的知识点真正串成了闭环。这个工程适合三类人看。一是正在做无线通信相关FPGA开发的工程师需要一套能完整复现的收发链路参考二是通信工程或电子工程专业的学生做课设或者毕业设计时可以直接在这个源码基础上改参数、加模块三是对同步算法、误码性能如何在硬件上落地感兴趣的同学。整个工程不依赖特定厂商的高级加密IP代码尽量手工RTL实现方便理解和二次修改这是这套源码最核心的定位。1.2 关键参数选型先给出整套系统的核心参数后续模块都围绕这些参数展开参数数值说明符号速率1 MspsBPSK一个符号对应一个编码后码元ADC/DAC采样率16 MSps16倍过采样方便滤波与同步环路工作中频频率4 MHz低中频方案规避零中频直流偏置问题卷积码(2,1,3)生成多项式[7,5]八进制约束长度3编码后码率2 Msps1/2码率卷积编码输出后符号速率翻倍成型滤波RRC滚降0.3收发端各一级根升余弦滤波器FPGA平台Artix-7 XC7A35T资源占用约15%适合入门板卡复现为什么不直接用更高阶的调制BPSK虽然频谱效率只有1bps/Hz但结构极其简单相位只有0和π两种状态判决域非常清晰用来验证卷积编解码、同步环路这些核心机制是最不会被调制复杂度干扰的路线。等这套链路跑通把映射级改成QPSK或者16QAM再对应改一下判决门限和误差提取方式就能平滑升级到高阶调制这点在后面扩展章节细说。参数里最值得留意的是中频和采样率的配比。4MHz中频、16MHz采样正好满足带通采样定理而且采样率是中频的4倍数字混频时NCO产生4MHz正弦波频率控制字正好是2^30这种整数硬件上极大简化了频率字计算。实际调试中这个整数关系能帮你快速排除NCO配置错误因为混频后频谱位置非常干净。1.3 为什么选BPSK卷积码这套组合BPSK与卷积码的组合本质上是很多实际通信系统的基础原型。卷积码是记忆型纠错码它不把输入比特独立编码而是通过移位寄存器把前后比特的约束关系编码到输出中因此抗突发噪声的能力比分组码更平滑。FPGA实现卷积码最大的优势在于编码器结构就是移位寄存器和异或门资源消耗几乎可以忽略译码端虽然Viterbi算法复杂度随约束长度指数增长但(2,1,3)这种约束长度只有4个状态在FPGA里跑起来非常轻松。另一个选择原因是调试友好。BPSK调制解调的所有中间信号都是窄位宽数据用ILA抓波形非常直观。卷积编码器输出可以直接和BPSK映射前的源数据对照Viterbi译码输出可以和发射端最原始的比特流逐位比对整个链路任何一个环节出问题都能通过波形快速定位到具体模块这对一个供学习参考的工程源码来说至关重要。2. 发射端实现编码与调制2.1 卷积编码器的RTL实现卷积编码器是发射链路数据进入信道前的第一步。(2,1,3)卷积码由三级移位寄存器、两个异或求和节点和一个串并变换输出组成。生成多项式[7,5]六进制对应二进制111和101意思是第一个输出码字由当前输入与前两级寄存器异或得到第二个输出码字由当前输入与第二级寄存器异或得到。寄存器每个符号周期移位一次每个输入比特产生两个输出码元码率自然就是1/2。RTL实现代码量很小核心就是三段式输入打拍、异或计算、输出拼接。但有一点必须提醒编码器启动前寄存器必须清零否则前几个符号的编码输出是无效的。连续帧传输时每帧开始前都要对编码器做一次复位操作。我在工程里习惯在编码器前面加一个帧头插入模块帧头采用固定PN序列这样接收端可以用帧头做同步捕获和相位翻转消除。帧头不参与卷积编码相当于已知序列调试时非常有用。编码器输出是2比特并行但后续BPSK映射按符号处理所以需要一个2比特转1符号的缓冲。可以直接把两个码元并行送到映射模块每拍处理一个码元对应一个BPSK符号速率等于码元速率2Msps也可以先做并串转换再按符号时钟处理。我用的是后一种方案同时引入随路有效信号valid标示数据有效后续所有模块都遵守AXI4-Stream握手约定。这种接口风格虽然看起来比简单valid-enable多几个信号但模块之间天然支持反压后续插入FIFO、调试模块或者替换成DMA都非常顺。2.2 BPSK映射与成型滤波BPSK映射就是把1比特数据映射成双极性电平。我采用的映射关系是编码输出0映射为1编码输出1映射为-1。这里要注意映射关系本身不影响系统性能只需要发射端和接收端的判决规则保持一致即可。如果映射反了接收端要么全部判反要么译码输出全错排查起来也简单看解调前星座图两簇点是否与理论位置匹配就行。成型滤波选择根升余弦滤波器滚降系数0.3。接收端再接一级相同的根升余弦滤波器两级级联等效为升余弦滤波器实现无码间干扰传输。滤波器阶数我选了40阶16倍过采样下每符号16个采样点加上滤波器自身的抽头数整体运算量不大。FPGA实现有两种路线一种直接调用Xilinx FIR Compiler IP另一种手工用乘累加器实现转置结构FIR。如果目标是学习源码和二次开发建议手工实现这样能彻底搞清抽头系数怎么量化、信号位宽怎么增长后续换成定制滤波器也能绕开IP。滤波器系数先用MATLAB的rcosdesign函数生成然后转成Q定点格式。系数定点化之后一定要重新看频响特性幅度波动一般控制在0.1dB以内即可。滤波输出的信号位宽会随着抽头累加而增长需要根据仿真结果做截位把有效位保留下来防止信号饱和或者量化噪声恶化。这里有个经验截位前先看滤波输出波形峰峰值再对应选择保留位宽不要盲目截断低位。2.3 中频调制与DDS载波生成基带成型后的信号是16MSps采样率的基带波形需要搬到4MHz中频。数字上变频的标准做法是产生一个4MHz的NCO正弦载波与基带信号相乘。由于16MHz采样率正好是4MHz的4倍NCO相位累加器每四个采样点走过一个完整周期相位步进正好是2^30硬件实现非常直接。我用32位相位累加器频率控制字通过公式freq f_carrier * 2^32 / f_s计算4MHz载波代入后正好是2^30。乘法器这里有一个关键点基带信号和载波相乘后会产生2倍频分量这个分量在接收端下变频之后会被低通滤波器滤除但中间信号位宽必须扩到足够大否则乘法器输出溢出会导致整个信号失真。实际编码里我给了基带信号12bitNCO正余弦各12bit乘法输出截位到16bit送给DAC功耗和指标都比较均衡这也是很多通信板卡常用的位宽配比。中频调制完成后发射链路处理完毕。DAC芯片如果是16bit信号动态范围完全够用如果DA位宽不足可以做简单的MSB截位或者加噪声整形不过这个工程里14bit以上都无所谓调试时优先关注频谱是否干净、符号速率是否正确即可。3. 接收端实现同步与解调3.1 数字下变频与匹配滤波接收端ADC采样后的信号是4MHz中频数字信号第一步做数字混频把信号搬回零中频。混频需要一个与发射端同频同相的本地载波由Costas环产生。初始阶段先假设本地NCO频率接近4MHz后续通过环路锁定校正频偏和相偏。混频后的I/Q两路分别代表基带信号的实部和虚部BPSK信号此时能量主要集中在I路上。数字混频之后是低通滤波器主要滤除混频产生的和频分量同时起到抗混叠作用。这个低通滤波器我直接复用了发射端RRC匹配滤波器的硬件结构只是系数替换成低通模式。如果中频频率和采样率关系处理得好可以先在混频输出用CIC抽取滤波器把采样率降到适当倍数再用FIR做精细滤波能省不少乘法器资源。但这个工程里符号率只有1Msps直接16倍采样率跑FIR乘法器数量也在可接受范围内所以我没做抽取反而让后面同步环路的性能更好调试。匹配滤波放在混频之后将接收信号与发射端RRC进行匹配两级RRC级联为升余弦输出波形在采样点位置的信噪比达到最大。匹配滤波系数必须和发射端完全一致两个滤波器系数在工程里放在同一个头文件或常量定义中防止版本不一致。这个问题我踩过坑有一次发射端改系数只改了RTL文件没改仿真文件结果MATLAB模型和RTL仿真对不上排查了一个多小时。3.2 Costas载波同步环BPSK解调必须有载波同步否则本地载波和接收信号之间存在频率偏差和相位偏差解调结果会旋转误码率直线上升。我使用的是经典Costas环利用I/Q两路输出提取相位误差e sign(I) × Q。这个误差信号经过环路滤波器之后反馈控制本地NCO的频率字形成一个负反馈闭环。环路滤波器采用二阶结构包含比例项和积分项。比例系数Kp决定环路的快速响应能力积分系数Ki决定稳态精度。这两个系数需要根据环路带宽和符号速率来设定工程上常用公式Kp 2ξωnTs、Ki ωn²Ts²估算。我实测下来环路带宽取符号速率的1/100左右比较合适带宽太宽会引入噪声导致星座点抖动太窄则锁定时间过长测试耐心都要被磨没了。Costas环锁定之后本地NCO频率字就是接收信号载波频率的精确估计可以直接读出来做频率显示或校准。这里必须点一下BPSK特有的相位模糊问题Costas环稳定在0度和180度两个收敛点0度时星座正确180度时解调输出完全反相。工程上最简单的解决方法是在编码前做差分编码接收端判决后做差分译码代价是引入一定误码扩散但换来绝对的相位鲁棒性。这个工程里我先用帧头独特字检测判断是否反相如果反相则在判决输出端对符号取反再通过FIFO对齐到正确帧起始位置。两种方案都可以差分编码对连续业务更稳独特字方案对突发帧更友好。3.3 Gardner定时同步与判决载波同步解决了相位问题但采样时刻不一定落在符号峰值点所以还需要定时同步。我用的是Gardner算法它不需要额外导频利用每个符号的两个采样点和中间点做误差提取误差公式为e mid × (late - early)。这里的early和late分别表示当前符号峰值前的采样点和下一符号峰值前的采样点mid是二者中间位置。Gardner环路的符号定时NCO控制插值滤波器的分数间隔输出符号率数据流给判决模块。插值滤波器我用四次多项式插值比线性插值精度高很多资源消耗也不大。环路滤波同样用二阶结构系数设计和Costas环类似只是输入信号不同。调试时有一个直观现象环路锁定时误差信号的平均值接近零同时眼图张开最大从ILA里看I路波形就能判断定时是否准确。定时同步完成之后输出数据送到判决模块。BPSK判决就是根据I路符号的正负决定输出0还是1非常直接。如果接收链路想再做完整一点这里还可以加AGC或者信道均衡但在室内短距离、信噪比足够的情况下不加也不影响链路演示。工程里我保留了AGC接口预留位后续接真实射频前端时可以直接补上。4. Viterbi译码器从理论到RTL4.1 译码器架构与状态管理Viterbi译码是整个工程里计算量最大的部分。(2,1,3)卷积码有4个状态每个状态对应移位寄存器前两级寄存器的取值组合。译码过程就是在一张时间为横轴、状态为纵轴的网格图上寻找一条累计度量最小的幸存路径。FPGA实现Viterbi译码器首先要确定判决方式。硬判决把解调输出量化成0或1分支度量用汉明距离软判决则保留多比特软信息分支度量用欧氏距离。软判决在AWGN信道下相对硬判决有大约2dB的编码增益代价是分支度量和路径度量的位宽增加。这个工程里我实现了软判决版本解调器判决模块只做符号到软信息的映射不直接输出硬比特软信息位宽选4bit兼顾性能和资源。实际测试下来4bit软信息和8bit软信息的性能差距小于0.3dB但资源省了不少。状态度量存储是Viterbi的核心问题。每级路径度量需要存储4个状态的值并且度量值会持续增长需要归一化处理。工程上用模2^M自动溢出的办法让两个并发路径之差保持在较小范围内省去额外归一化电路前提是M选得足够大真实路径度量差不发生翻转。我这里是10bit度量位宽实测没出过问题。如果约束长度升级到7状态数变成64度量位宽也需要同步加大。4.2 ACS单元与路径回溯的工程实现Viterbi译码器的核心是加比选单元每个状态完成三步把前级两个状态的路径度量加上对应分支度量比较大小选择较小值作为当前状态路径度量同时记录幸存路径信息。四个状态就是四个并行的ACS单元。因为(2,1,3)结构简单我直接展开成4个ACS单元每个单元工作在一个符号周期内16MHz时钟处理2Msps码元速率运算量不是瓶颈流水线控制在3级以内就能收时序。路径回溯长度一般取约束长度的5到8倍约束长度3时回溯深度取32足够。回溯实现有寄存器交换法和回溯法两种。寄存器交换法实时性高但寄存器消耗大回溯法用RAM存储幸存路径信息每解码一个符号需要回溯几拍。我用的是回溯法状态幸存路径写入双口RAM回溯时从当前最优状态倒推。这里有个容易忽略的细节回溯输出的顺序和编码顺序是反的需要做一个反序FIFO修正否则译码结果完全不可用。整个Viterbi译码模块的流水线节拍设计很有讲究。ACS、幸存路径写入、回溯、反序输出四个阶段用四级流水并行吞吐率就能稳定跟上符号率。如果以后要升级成约束长度7的(2,1,7)码状态数变成64个ACS单元可以复用同一个硬件结构做时分复用代价是时钟频率要求更高工作量主要在状态映射和RAM深度的修改上。5. 工程源码结构、仿真与板上验证5.1 源码模块划分这个工程采用典型的层次化设计顶层模块下面挂发射和接收两条子链路收发之间在顶层直接相连做成一个自环模式方便在单板上完成全链路验证。模块划分如下模块功能top_bpsk_conv顶层例化收发链路连接时钟复位tx_data_gen伪随机数据源用于误码统计tx_conv_encoder卷积编码器tx_bpsk_modBPSK映射、成型滤波、中频调制rx_nco_mixer本地NCO、数字混频rx_match_filterRRC匹配滤波rx_costasCostas载波同步rx_gardnerGardner定时同步rx_viterbiViterbi译码rx_bit_align帧对齐、反相补偿、误码统计所有模块都使用AXI4-Stream接口传输数据tvalid和tready握手tlast表示帧尾。这种接口风格虽然比简单valid-enable多几个信号但模块之间天然支持反压后续在模块间插入FIFO、调试模块或者把某一段替换成DMA都非常顺。顶层连接时也做了跨时钟域处理ADC时钟和系统逻辑如果不同域在接口处加异步FIFO同步避免亚稳态问题。5.2 Testbench设计与仿真观测点Testbench是整个工程调试的第一道防线。我写tb的时候重点不是把所有信号都拉出来看而是盯几个关键节点发射端的原始数据、编码输出、调制波形接收端的混频I/Q波形、环路误差信号、定时误差信号、软信息输出、Viterbi译码输出。在Vivado Simulator里跑完仿真直接把发射端原始数据和接收端Viterbi译码后数据做逐位比对统计出误码率。仿真里加噪声是必要测试项。我在发射端中频输出处叠加高斯白噪声模块用Box-Muller法生成高斯随机数噪声功率按SNR设定换算。不同SNR下跑误码率曲线可以验证编码增益是否正常。BPSK卷积码(2,1,3)在10dB以上基本零误码8dB左右会有少量误码这个结果和理论曲线对得上说明链路实现正确。如果发现误码率明显偏大优先查定时同步是否锁定准确其次查Costas误差符号是否正确。5.3 板级调试经验板级调试最大的问题不是算法本身而是如何高效观察内部信号。建议优先把ILA核挂在三个位置混频后I路、Costas误差信号、Viterbi处理前的软信息。这三个点能快速判断载波同步是否锁定、定时同步后的眼图是否张开、软信息幅度是否正常。VIO核用来动态调整环路系数和复位信号省去每次改参数都要重新综合的麻烦调试效率提升非常明显。另外一点自环模式下发射和接收都在同一块FPGA里不考虑空口衰减和多径板子能跑通只代表功能正确不代表真实信道性能。如果需要逼近实际信道场景可以在中频自环路径上加可调衰减器或者人为加噪声让接收端实际感受到信号幅度变化再观察AGC和同步环路的表现。如果要在两个板卡之间做无线收发测试那就要额外考虑射频前端和天线的匹配问题这个工程里暂时没有包含。6. 常见问题与排错经验6.1 载波同步锁不死的调试Costas环锁不定的现象是星座点在I/Q平面上始终旋转或者锁定之后相位抖动很大。排查顺序一般是先看NCO频率字初值是否准确再看环路滤波器系数是否合理最后检查误差信号极性。误差极性反了的话负反馈会变正反馈环路必然发散。常见原因是在写sign(I)的时候把符号逻辑写反排查方法很简单把环路断开手动给一个固定相偏看误差信号能否收敛到零点附近。6.2 相位模糊与误码统计自环状态下如果收发同源相位模糊往往不明显因为发射端和接收端默认同相。一旦链路里出现模拟器件或者真实空口相位模糊就会显现。排查技巧是看Viterbi译码前的硬判决比特流是否整体取反如果误码率接近50%大概率是180度相位翻转。解决方法就是前面提到的差分编码或独特字检测。用独特字检测时要注意帧同步状态机的稳定性别把误码引起的假独特字当成真同步否则帧同步会反复跳变误码统计结果完全不可信。6.3 时序收敛与资源优化整套工程在Artix-7上跑16MHz时钟没有压力但如果把采样率抬高到100MSps以上FIR滤波器和Viterbi回溯路径就需要认真做流水线设计。我的建议有两条一是所有跨模块信号统一遵守AXI-Stream握手在长组合逻辑路径中间插寄存器二是FIR滤波器系数用对称结构乘法器数量减半。Viterbi译码如果资源紧张可以把并行ACS改成时分复用用一个ACS单元串行处理所有状态用更高时钟频率换面积这个思路对未来扩展更高速率的卷积码很有用。还有一个容易被忽略的点RRC成型滤波器输出位宽裁剪。滤波器增益会带来二进制位增长如果直接截掉低位信号量化噪声会明显增加如果保留过多低位后续乘法器和加法器位宽全被撑大。正确做法是仿真观察滤波输出信号峰峰值确定有效位范围后裁位并补上直流偏置防止信号削波。6.4 调试顺序建议最后分享一个老套但非常有效的调试顺序。先验证环回链路把发射端调制部分直接接到接收端解调部分不经过同步环路确认卷积编码和Viterbi译码功能完全正确再加入载波同步不加定时同步确认Costas环能稳定锁定再加入定时同步确认Gardner环正确工作最后把AGC、帧同步这些完整功能打开做全链路联合调试。每加一个模块就回归一次误码率问题出现时大概率就集中在最后一次新增的模块里。按照这个顺序调试这套BPSK卷积码工程一共花了两天中间踩的最大的坑反而是仿真时忘记给复位信号做同步释放导致仿真波形前几百拍全是无效状态。把复位同步器加上之后一切都顺了。工程源码里保留了完整的约束文件和仿真脚本拿到直接跑能看到一个从编码到译码全通的自环演示。这里也提醒一句仿真通过只是第一步上板调试时时钟约束一定要如实填写否则时序收敛问题会掩盖算法问题到时候查起来会非常头疼。本文还有配套的精品资源点击获取