简介:这是基于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 Msps | BPSK一个符号对应一个编码后码元 |
| ADC/DAC采样率 | 16 MSps | 16倍过采样,方便滤波与同步环路工作 |
| 中频频率 | 4 MHz | 低中频方案,规避零中频直流偏置问题 |
| 卷积码 | (2,1,3) | 生成多项式[7,5]八进制,约束长度3 |
| 编码后码率 | 2 Msps | 1/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倍频分量,这个分量在接收端下变频之后会被低通滤波器滤除,但中间信号位宽必须扩到足够大,否则乘法器输出溢出会导致整个信号失真。实际编码里我给了基带信号12bit,NCO正余弦各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译码器:从理论到RTL
4.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_mod | BPSK映射、成型滤波、中频调制 |
| rx_nco_mixer | 本地NCO、数字混频 |
| rx_match_filter | RRC匹配滤波 |
| rx_costas | Costas载波同步 |
| rx_gardner | Gardner定时同步 |
| rx_viterbi | Viterbi译码 |
| 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+卷积码工程一共花了两天,中间踩的最大的坑反而是仿真时忘记给复位信号做同步释放,导致仿真波形前几百拍全是无效状态。把复位同步器加上之后,一切都顺了。工程源码里保留了完整的约束文件和仿真脚本,拿到直接跑,能看到一个从编码到译码全通的自环演示。这里也提醒一句:仿真通过只是第一步,上板调试时时钟约束一定要如实填写,否则时序收敛问题会掩盖算法问题,到时候查起来会非常头疼。
本文还有配套的精品资源,点击获取