简介:使用VIVADO完成FPGA信号调制与解调的完整工程资料,面向通信电子类课程设计、FPGA数字信号处理入门及电子竞赛备赛人群。工程覆盖载波1M-10MHz、调制信号1k-10kHz的AM调制解调设计,支持调制深度0-1.0步进0.1,包含VIO动态调节、ILA实时观测、MATLAB联合验证等关键环节,贴近实践任务书要求。压缩包共1338个文件,约95.58MB,以.v/.vhd/.vhdl硬件源码、.xdc约束文件、.coe/.mif系数文件、.do/.bat/.tcl仿真与运行脚本、.xpr工程文件等为主体,结构完整,便于直接打开工程或按脚本复现流程。已有3272人学习下载。资源可作为完整方案参考,帮助理解AM调制解调原理、参数精度控制、IP核配置及FPGA-MATLAB协同验证思路,节省从零搭建工程的时间。 从去年开始,我一直在做一套基于FPGA的通信信号处理平台,最核心的一块就是用VIVADO在FPGA上把调制解调这块跑通。从一开始用STM32配合DDS芯片做简单正弦波,到后来直接在Xilinx的FPGA上用逻辑实现从ASK、FSK、PSK到QAM的调制与解调,这条路走下来踩了不少坑,也积累了一些比较实用的经验。如果你正准备用VIVADO做FPGA的调制解调,或者正在纠结是买现成的调制解调芯片还是在FPGA里用逻辑实现,这篇文章应该能帮你省掉不少摸索时间。文章的内容主要是基于Xilinx 7系列FPGA和VIVADO 2018.3版本,原理部分对其他系列和其他版本同样适用。
1. 为什么非要用FPGA做调制解调
很多人第一反应是:调制解调不是有现成的芯片吗?DAC加模拟乘法器,或者直接买ADI的AD9361、TI的AFE系列射频收发芯片,人家内部都集成好了,何必在FPGA里自己折腾。
这话对了一半。如果你做的产品是标准通信协议,比如WiFi、LTE、蓝牙,当然应该用专用芯片,功耗、性能、成本都最优。但如果你做的是非标系统,比如自定义的遥测链路、水声通信样机、科研用的验证平台、或者某种特殊的扩频通信方案,专用芯片反而会成为最大的束缚。这类系统的特点是调制方式、帧格式、速率随时都在改,今天用BPSK,明天想换16QAM,后天又要在解调端加个自适应均衡,专用芯片根本跟不上这种迭代速度。
FPGA做调制解调的核心价值在于:可重构的数字信号处理链路。所有调制解调本质上就是数学运算,而数学运算在FPGA里就是乘法器、加法器、查找表的组合。只要资源够,你可以在同一片FPGA里同时部署多种调制方式的发射机和接收机,切换只是换一个配置字的事。
另外一个被很多人忽略的优势是确定性延迟。调制解调在MCU或者DSP上用软件循环实现,最大的问题是执行时间不确定,中断一多延迟就飘。而FPGA是纯硬件流水线,从数据进来到最后输出是固定时钟周期,这个特性在需要精确同步的通信系统里非常关键。
当然FPGA方案也不是没有代价。开发周期长、调试困难、数字信号处理的数值精度需要自己把控,这些是绕不开的。但如果你想真正把通信信号处理吃透,FPGA几乎是必经之路。
2. DDS载波生成:整个调制解调的地基
2.1 DDS的基本原理
调制的本质简单说就是用待传输的信号去改变载波的某个参数——幅度、频率或者相位。所以无论做什么调制,你首先得在FPGA里产生一个频率可控、相位可控的正弦载波。这就是DDS(直接数字频率合成)干的活。
DDS最常见的实现方式叫相位累加器加查找表。核心逻辑就是用一个N位寄存器不停累加一个频率控制字,每个时钟周期加一次,溢出后自然回绕。累加器的高M位作为ROM查找表的地址,ROM里存着一个周期正弦波的采样值。输出频率由这个公式决定:
[ f_{out} = \frac{f_{clk} \times \Delta}{2^N} ]
其中( f_{clk} )是系统时钟,( \Delta )是频率控制字,( N )是相位累加器位数。比如系统时钟100MHz,累加器32位,想要输出1MHz正弦波,算出来的频率控制字就是:
[ \Delta = \frac{1 \times 10^6 \times 2^{32}}{100 \times 10^6} = 42949673 ]
2.2 在VIVADO里用DDS Compiler IP
自己写一个DDS也不难,大概四五十行Verilog就搞定了,但工程上我更推荐直接用VIVADO自带的DDS Compiler IP核,稳得多。它在资源利用和噪声性能上做了优化,还支持频率调制和相位调制输入端口,做调制非常方便。
配置DDS Compiler时,有几个参数要特别说下。一个是SFDR(无杂散动态范围),它会决定查找表的容量。SFD R设成90dB和120dB,ROM资源能差出几倍。如果后面的调制链路对杂散要求不高,90dB就挺合适,没必要追求过高的指标。另一个是频率分辨率,由相位累加器位数决定,32位精度在100MHz时钟下分辨率大约是0.02Hz,常规场合完全够用。
我在调试中习惯把DDS的相位累加器位数设为32,输出位宽设为14到16位,这样和DAC的位数匹配比较自然。DDS Compiler还支持多通道模式,如果你要同时产生I/Q两路正交载波,可以配置成两个通道,分别输出正弦和余弦,避免自己做正余弦查找表。
2.3 DDS调试中的常见坑
DDS在VIVADO仿真里看起来完美,一到板子上就出问题,这是很多初学者常见的困惑。我踩过的最典型的一个坑是:仿真里波形非常干净,但用示波器看DAC输出,发现正弦波上叠了一层毛刺。
原因出在数据同步上。DDS输出的数据和DAC的时钟如果没有对齐,或者DAC接口的建立保持时间不满足,就会出现这种问题。解决办法是把DDS输出先用FIFO或寄存器打拍同步,再用DAC的时钟域把数据锁存出去。另外还要检查DDS输出的数据格式,VIVADO的DDS Compiler默认输出二进制的补码格式,而很多DAC芯片接口期望的是偏移二进制码,需要在外面上一个转换电路,千万别想当然地直连。
3. 调制端的实现:从最简的ASK到实用的QAM
3.1 ASK和FSK:入门必做,工程上也够用
ASK就是把数字信号0和1映射到载波的幅度上,实现起来最简单。用DDS生成载波,后面接一个乘法器,把数据位(0或1)作为乘数之一,输出就得到调幅波。在VIVADO里就是一个乘法器IP的事。但ASK的抗噪性能最差,实际工程里单独用的不多,往往是用在简单的遥控遥测场合。
FSK稍微高级一点,用数字信号控制载波频率切换。DDS Compiler正好有一个频率调制输入端口(PHASE_INCREMENT端口),你只需要在不同码元时间里把对应的频率控制字送到这个端口就行。这里有个很关键的细节:频率切换的瞬间可能导致相位不连续,表现在频谱上就是产生额外的杂散分量。要避免这个问题,就得保证切换前后载波相位连续,这叫连续相位FSK(CPFSK)。
3.2 PSK调制:相位累加器初值的设计
PSK调制对初学者来说最容易犯迷糊。很多人一开始想着把正弦查找表的地址直接偏移,这种方式虽然也能实现相位跳变,但工程上更规范的做法是改变DDS相位累加器的初值。
相位累加器的工作原理决定了,即使两个码元使用不同的初值,只要累加步进(频率控制字)相同,输出正弦波频率就是一样的,只是相位差一个固定角度。所以BPSK的调制实现非常简单:数据0给相位累加器初值0,数据1给初值(\pi/2)(对应相位偏移(\pi))。这个(2^{N-1})的偏移量,因为N位相位累加器总共代表(2\pi),所以180度就是一半。
VIVADO的DDS Compiler有一个PHASE_OFFSET端口,专门用来做这个事。把要偏移的相位值按时钟送进去就行,但注意相位偏移值是实时生效的,需要和码元同步信号对齐,否则会出现相位跳变沿落在符号中间的情况。这个同步问题我在实际调试中遇到过,最后是在基带侧先把数据对齐到码元时钟,再和DDS的PHASE_OFFSET端口同步握手才解决。
3.3 QAM调制:IQ双路幅度加权的工程实现
QAM的原理是在同一载波频率上,用两路正交载波(cos和sin)分别承载两路独立数据,幅度加权后再相加。16QAM就是把星座图的16个点映射成两路4电平基带信号。
在FPGA里实现QAM需要双路DDS(或一个双通道DDS),一路输出cos作为I路载波,一路输出sin作为Q路载波。基带处理侧先把比特流按照映射表转换为I值和Q值,然后分别和对应的载波相乘,最后用加法器合成输出。
这里最值得注意的就是映射表的编码方式。QAM星座图有两种常见映射:格雷码映射和自然码映射。格雷码映射下相邻星座点之间只有1比特不同,在信噪比不太高的情况下,误码往往发生在相邻点之间,格雷码能保证这种情况下只错1个比特,对系统误比特率有明显改善。实际工程中几乎都用格雷码。
资源消耗方面,16QAM调制端的核心资源就是两个乘法器加一个加法器。在7系列的FPGA上,乘法器直接用DSP48E1硬核,几乎不占用通用逻辑资源。
4. 解调端的实现:非相干和相干两条路线
解调比调制难,难在收端不知道发送端载波的准确频率和相位。接收信号一路经过信道、变频、滤波,频率有偏移、相位有随机抖动,如果不处理就直接乘本地载波解调,输出信号会剧烈起伏,根本误码率爆炸。
4.1 非相干解调:包络检波和过零检测
非相干解调不需要恢复载波相位,实现简单,适合ASK和FSK这类调制方式。包络检波的基本思想是,把接收信号通过一个低通滤波器,提取幅度包络,然后和判决门限比较。数字域实现更是容易,把采样信号取绝对值再低通就行。VIVADO里用几个移位寄存器加一个FIR Compiler就能搭出来。
FSK的非相干解调通常是过零检测或者鉴频法。过零检测的原理是统计信号的过零率来判别不同频率,实现简单但对噪声敏感。另一种常见做法是双通道滤波,用两路窄带滤波器分别在两个频率点提取能量,然后比较能量大小判断当前收到的是哪个符号。这个方法抗噪性能比过零检测好一些,但需要更复杂的滤波器设计。
4.2 相干解调:乘法器加低通滤波器的标准结构
对于PSK和QAM这类调相信号,非相干解调基本不适用,必须用相干解调。结构上就是一个正交乘法器加上低通滤波器,原理就是三角函数积化和差公式:
[ \cos(\omega_c t) \times \cos(\omega_c t + \varphi) = \frac{1}{2}[\cos(\varphi) + \cos(2\omega_c t + \varphi)] ]
乘完以后高频分量(\cos(2\omega_c t + \varphi))被低通滤掉,剩下的(\cos(\varphi))就是和相位偏移有关的基带信号。
问题的关键来了:这里假设本地载波和接收信号的载波是同频同相的。如果频率不一致,(\varphi)就不是常数,而是一个随时间线性增长的相位,(\cos(\varphi))就会变成低频振荡。所以必须做一个载波同步环,把本地载波的频率和相位锁到接收信号上,这就是锁相环的用途。
4.3 载波同步的工程实现:Costas环和判决反馈环
在FPGA里实现载波同步,最经典的方案是Costas环。对于BPSK信号,Costas环的结构是把接收信号分别乘以同相和正交两路本地载波,两路输出分别低通,然后把两路相乘得到一个误差信号,这个误差信号经过环路滤波器后去调整NCO的频率控制字。
环路滤波器是整个锁相环设计的核心,它决定同步速度、跟踪精度和抗噪性能。在离散数字域里,环路滤波器就是一个比例积分控制器,系数由环路带宽和阻尼因子决定。我在实际工程里一般把环路带宽设在符号速率的1/100到1/50之间,既能跟上频率漂移,又不会让噪声破坏环路的稳定性。如果环路带宽太宽,解调出来的星座图就是一层雾,信噪比会明显变差。
QAM的载波同步比BPSK又复杂一个量级,因为QAM信号本身没有纯粹的载波分量,需要用到判决反馈环或者广义Costas环。实际实现时,还要在载波同步之前加上定时同步(符号同步),否则采样点落在符号边缘,眼图是张不开的。不少第一次做QAM解调的人会在这一步卡上很久,我就是其中之一,后面会专门说这个。
5. VIVADO工程搭建的关键细节和定点化处理
5.1 用AXI-Stream接口串联算法模块
在VIVADO里设计调制解调系统,我强烈建议算法模块之间的数据通路统一使用AXI-Stream接口。这不仅仅是为了和IP核对接方便,更重要的是让模块间传递握手信号,解决数据有效性和背压问题。
调制解调链路里,每个模块的处理延迟不一样,如果直接用寄存器直连,时序很容易出问题。AXI-Stream协议里面有tvalid和tready信号,发送方拉高tvalid表示数据有效,接收方拉高tready表示可以接收,两路都拉高才算完成一次传输。用这个机制做数据同步,就不需要自己设计复杂的跨时钟域逻辑了。
我自己的工程里固定按照这样的数据链路来组织代码:基带映射 -> AXIS FIFO -> 成形滤波(FIR) -> 乘法调制 -> DDS -> DA数据拼接。每两个模块中间都插一个AXI-Stream Register Slice,这个IP其实就是打了几拍寄存器,能有效打断组合逻辑的长路径,避免时序不收敛。
5.2 定点数处理
FPGA不像DSP或者MCU那样用浮点数方便,小数运算几乎都要用定点数实现。我吃过不少亏才开始重视这个环节,早期的项目里解调性能不稳定,后来发现就是截位处理没做好,导致信号噪声比被计算精度拖累了。
定点数设计里最关键的是Q格式,比如Q1.15表示1位符号位、15位小数位,能表示-1到0.999969的范围。设计调制解调算法,第一步就要确定各个节点用多少位宽、多少小数位,这直接决定了信号动态范围会不会溢出、量化噪声会不会过大。
数据位宽怎么定,可以用一个简单的原则:在仿真里把各个节点的信号幅度峰值统计出来,预留20%到30%的余量,同时保证有效分辨率不低于12位。DAC之前的数据最好保持14位以上,因为如果这个节点上精度丢得太多,DAC动态范围的余量就会白白浪费,输出的信噪比上不去。
乘法器是另一个需要仔细处理的点。两个16位数据相乘得到32位结果,但下一级模块往往不需要32位,需要截位。截位不是简单地砍掉低位,而是要加上四舍五入处理。VIVADO的乘法器IP核都支持配置输出截位模式,一般选择Round to nearest这种模式,直接丢掉低位会引入直流偏置,星座图会整体偏移,这个问题在QAM解调时最明显。
5.3 时序约束:调制解调系统的生命线
VIVADO工程能不能上板跑稳,时序约束占到一半的成败。很多新手从仿真转到上板时出现问题,经常是没写约束或者乱写了约束。
调制解调系统至少应该包括这几类约束:主时钟约束(create_clock)、输入输出延迟约束(set_input_delay / set_output_delay)、异步时钟域之间的伪路径或者异步FIFO处理。其中最容易被忽略的是DAC接口的输出延迟约束,如果你不给DAC芯片的时钟和数据走线设置合适的output delay,VIVADO会默认数据在时钟沿处精确对齐,但实际芯片的时序要求不是这样,结果就是DAC采集到的数据可能跑偏。我自己就遇到过一连上真实DA芯片,输出波形就抖动,找了一下午才发现是漏了set_output_delay。
如果遇到时序不收敛,也别上来就加大约束放宽。先看VIVADO的时序报告,找到关键路径是从哪里到哪里,大多数情况下是某个模块的组合逻辑链太长,需要在中间插入流水寄存器把它打断。比如FIR滤波器的数据通路,如果抽头数很多,直接做成单级组合逻辑乘法累加,时序肯定收不了,必须要进行流水切分。
6. 仿真和板级调试:从ModelSim到ILA,完整链路验证
6.1 做仿真的正确姿势
VIVADO自带的XSim仿真器,对于中小规模的调制解调仿真完全够用。不过有一点千万记住:仿真激励尽量用文件来驱动,而不是在Testbench里用代码生成复杂的测试信号。我习惯的做法是先用MATLAB或者Python生成包含特定调制方式的二进制码流或者IQ采样数据,存成文本文件或二进制文件,然后在Testbench里用$readmemh或者$fscanf读取。这样仿真输入和实际工作的数据特征一致,更重要的是可以和MATLAB的计算结果做个差分对比,算法正确性一眼就能看出来。
仿真时要注意观察的不光是时域波形,频域也要看。XSim本身没有频谱分析功能,但你可以把I/Q两路仿真数据dump成文件,再用Python做FFT看频谱。我在调试滤波器和调制链路时,经常用这种方式来确认频谱纯净度,比只盯时域波形靠谱得多。
6.2 ILA抓真实信号:问题往往藏在仿真覆盖不到的地方
仿真做得再充分,板级调试还是有新问题。VIVADO的集成逻辑分析仪ILA就是我板级调试的耳朵和眼睛。把ILA核挂到内部信号上之后,就可以在VIVADO Hardware Manager里实时看到内部节点的波形,再用系统的触发条件捕捉特定事件的时序。
我最常用的几个ILA监控点是:DDS输出的载波数据、调制后的基带信号、解调端的低通滤波输出、以及载波同步环的NCO控制字。只要把ILA采样深度设成足够大,触发条件设为数据帧头出现,就能把调制到解调整条链路的波形完整截下来。
有一次调试QAM解调,星座图散成一团,怎么调整环路参数都没用。后来用ILA抓了定时同步模块的前后数据,发现眼图完全是闭合的,才定位到问题是符号同步没有收敛,不是载波环的问题。如果靠示波器捅引脚,这种内部信号根本看不到,猜谜游戏能玩一整个星期。
6.3 上板调试的几个经验
第一块板子比较容易出问题的是DAC/ADC接口电气时序。PCB走线长度、端接阻抗、电平标准都会影响数据采样。检查顺序先看输出端的DAC数据口是否有稳定波形,再看输入端的ADC采样是否正确,最后才查信号处理链路,不要一上来就调试算法。
第二是时钟问题。调制解调系统对时钟抖动非常敏感,如果板载时钟源质量差,解调性能会显著劣化。尽量使用板上低抖动晶振,并且通过MMCM或PLL生成各模块时钟,别直接用外部时钟采集数据。
第三是电源纹波。FPGA内核电压的纹波在高速调制解调时会被调制到输出信号上,频谱上表现为底噪抬高。我在调试一个FSK系统时就发现发射频谱两边长了草,排查到最后是电源模块的纹波超标,加了一级LC滤波就干净了。
7. 从仿真到上板之后:我最后的几点实在话
真到了项目交付阶段,有几件事是文档里不会告诉你的。
第一,模块化设计比追求代码简洁重要得多。调制解调系统里面环环相扣,每个模块单独测试都非常良好,连起来就不对的情况太多了。我的习惯是一个功能一个模块,一个模块一个仿真Testbench,仿真验证通过后再把单独的模块整合。调试时优先怀疑接口信号,而不是算法本身——接口的位宽不匹配是高频问题,数据总线错位一比特,什么都白搭。
第二,记得留观察点。在写代码的时候就要规划好哪些信号要拉到ILA观察,提前把这些信号提取到一个顶层调试模块里,避免后期为了看一个信号翻遍整个工程重新综合。有些版本优化还会把信号优化掉,你需要在综合属性里对要观察的信号加KEEP属性,不然综合完之后ILA里根本找不到这个信号。
第三,也是反复摔出来的经验:依次检查时钟、复位、接口时序、位宽对齐、数值溢出。我将近70%的解调性能问题最后定位出来都不是高深的算法问题,而是时钟没拉通,或者某个中间数据溢出导致符号错反。先把这些基础检查做好,再去调环路参数,效率会高很多。
调制解调在FPGA上这件事,入门不难,做精很难。从DDS生成载波到载波同步环锁住信号,每一层都有值得细抠的点。如果你的目标只是交个实验报告,按照前面的步骤做到仿真通过就足够了。如果你希望真正做个能用的系统,那就要多花时间在定时同步、环路滤波器的系数整定、以及定点化精度分析上,这些才是决定产品能不能稳定跑起来的真正门槛。
本文还有配套的精品资源,点击获取