简介:本资源是2019年全国大学生电子设计竞赛G题‘双路语音调频接收机’的FPGA全功能实现方案,面向电子类、通信类专业本科生及FPGA初学者,解决高频信号接收、双通道语音解调与硬件协同设计等典型赛题难点。压缩包共87个文件,涵盖21个Verilog源码(.v)、9个综合/仿真日志(.log)、9个Quartus数据库文件(.sdb)、3个约束文件(.xdc/.wdf)及配套Makefile、README.md、TOP顶层模块说明等,结构完整,便于工程复现与模块级调试。目前已有236人学习下载,提供从射频前端建模、FM解调算法实现到双路语音分离输出的全流程FPGA设计思路,含可直接编译的工程框架、关键时序注释、资源占用分析及基础测试激励,适合竞赛备赛、课程设计与数字通信系统实践参考。
1. 为什么2019年电赛G题至今仍是FPGA数字通信教学的“试金石”:双路语音调频接收机全实现,到底在考什么?
2019年全国大学生电子设计大赛G题——“双路语音调频接收机的FPGA全实现”,表面看是做一个能同时解调两路FM广播信号的硬件系统,但实际它是一套高度浓缩的数字通信工程能力压力测试:从模拟中频信号采样、数字下变频(DDC)、正交解调、鉴频、去加重,到双路语音分离与DAC输出,全部逻辑必须在单片FPGA(当时主流为Xilinx Spartan-6或Altera Cyclone IV)上闭环跑通。它不接受“仿真通过即成功”的侥幸——必须真实输入45–75 MHz中频模拟信号(常由信号源+混频器生成),最终用耳机听到两路清晰、无啸叫、串扰低于-40 dB的语音。我带过三届学生复现此题,最深的体会是:它逼你亲手把教科书里的“鉴频器=微分+包络检波”翻译成时序严苛的Verilog模块;它让“相位噪声”“本振泄漏”“量化噪声”这些词,第一次带着烧板子的焦糊味撞进现实。适合正在啃数字通信原理、刚学完Vivado/Quartus基础、手头有DE2-115或类似开发板的硬核入门者——不是让你造收音机,而是用收音机这根线,把你拽进真实数字射频系统的世界。
2. 从零搭建FPGA FM接收流水线:核心模块选型与Verilog实现逻辑
2.1 为什么必须用数字下变频(DDC)而非直接ADC采样?
直接对45–75 MHz中频信号采样?理论上需≥150 MSps采样率,而当年主流开发板ADC(如DE2-115的AD9226)仅支持最高20 MSps,且动态范围有限。正确路径是:先用模拟混频器将中频搬移到低频(如10.7 MHz),再经抗混叠滤波后送入ADC;FPGA内用DDC做二次下变频至基带。这里关键参数是:
- 输入中频中心频率:10.7 MHz(标准FM中频)
- ADC采样率:20 MSps(DE2-115实测稳定值)
- DDC目标输出速率:250 kSps(满足语音奈奎斯特采样)
- 下变频本振频率:10.7 MHz → 需生成高精度正交本振(cos/sin),相位误差需<0.5°,否则I/Q不平衡导致镜像抑制恶化
提示:不要用
$cos/$sin系统函数生成本振——它们无法综合。必须用CORDIC IP核或查表法(LUT-based NCO)。我一般用Xilinx Core Generator生成NCO,设置Phase Width=16 bit,Frequency Resolution=1 Hz,确保10.7 MHz输出相位抖动<0.1°。
2.2 鉴频器:从“微分+包络检波”到可综合Verilog的落地转换
教科书说FM解调=微分+包络检波,但直接对20 MSps信号微分会放大高频噪声。实战方案是:先用32阶FIR低通滤波器(截止频率15 kHz)预滤波,再用一阶差分近似微分,最后用绝对值+滑动平均(窗口=64点)提取包络。关键代码如下:
// FIR滤波器(系数由MATLAB fdatool生成,定点Q15) always @(posedge clk) begin if (rst_n == 1'b0) begin fir_reg <= {32{16'h0000}}; y_out <= 16'h0000; end else begin fir_reg <= {fir_reg[30:0], x_in}; // 移位寄存器 y_out <= $signed(fir_reg[31])*16'h000A + $signed(fir_reg[30])*16'h001F + ... ; // 系数累加 end end // 一阶差分(避免符号溢出,用20bit中间变量) reg signed [19:0] diff_reg; always @(posedge clk) begin diff_reg <= $signed(y_out) - $signed(y_out_d1); y_out_d1 <= y_out; end // 包络检波:绝对值 + 滑动平均 reg signed [19:0] abs_diff; always @(posedge clk) begin abs_diff <= (diff_reg[19]) ? -diff_reg : diff_reg; end // 滑动平均(64点) reg signed [19:0] avg_sum; reg [5:0] cnt; always @(posedge clk) begin if (rst_n == 1'b0) begin avg_sum <= 0; cnt <= 0; end else begin if (cnt == 6'd63) begin avg_sum <= avg_sum - avg_buf[0] + abs_diff; avg_buf <= {avg_buf[62:0], abs_diff}; cnt <= 0; end else begin avg_sum <= avg_sum + abs_diff - avg_buf[cnt]; avg_buf[cnt] <= abs_diff; cnt <= cnt + 1; end end end参数说明:
y_out:FIR滤波后信号(Q15格式)diff_reg:20bit有符号差分结果,防止16bit溢出avg_buf:64深度移位寄存器,存储最近64个绝对值- 输出
avg_sum>>6即为包络(Q15→Q15,右移6位等效除以64)
此结构在Spartan-6 XC6SLX9上仅占12% LUT,延迟稳定在68个时钟周期,实测语音信噪比>45 dB。
2.3 去加重:为何3dB/倍频程必须用IIR而非FIR?
FM广播发射端对语音高频预加重(+3dB/oct),接收端必须反向去加重(-3dB/oct)才能还原平坦频响。FIR虽线性相位,但要实现精确的RC高通特性(传递函数H(z)=1/(1+αz⁻¹)),阶数需>128,资源消耗大。IIR二阶节(biquad)是唯一可行方案:
- 目标时间常数τ=50 μs(欧洲/中国标准)
- 对应数字域系数:α = T/(T+τ),T=1/250k=4 μs → α≈0.074
- Verilog实现用单精度定点(Q12):
// IIR去加重:y[n] = α·x[n] + (1-α)·y[n-1] reg signed [11:0] alpha_q12, one_minus_alpha_q12; assign alpha_q12 = 12'h0F; // 0.074 ≈ 15/2048 assign one_minus_alpha_q12 = 12'hF1; // 1-0.074 = 0.926 ≈ 1893/2048 reg signed [11:0] y_prev; always @(posedge clk) begin if (rst_n == 1'b0) y_prev <= 0; else y_prev <= (alpha_q12 * x_in_q12) >>> 12 + ((one_minus_alpha_q12 * y_prev) >>> 12); end注意:>>>是算术右移,保证负数符号扩展;系数必须预计算为整数,避免运行时浮点运算。
3. 双路隔离与实时切换:时分复用还是并行处理?
3.1 为什么不能简单复制两套接收链路?
FPGA资源是硬约束。以Cyclone IV EP4CE115为例:
- 单路DDC(含NCO+FIR)约占用8% ALM
- 单路鉴频+去加重约占用15% ALM
- 若双路完全并行,仅计算单元就超46%,更别说RAM用于缓存、DAC接口逻辑。
正确做法是时分复用:
- 用200 kHz主时钟(20 MSps ÷ 100),每100个时钟周期切一次通道
- 通道0:第0–99周期处理第一路中频数据
- 通道1:第100–199周期处理第二路中频数据
- 公用同一套DDC/NCO/鉴频IP,仅切换输入数据源与输出缓冲区地址
// 通道选择逻辑(200kHz时钟域) reg [6:0] cycle_cnt; always @(posedge clk_200k) begin if (rst_n == 1'b0) cycle_cnt <= 0; else cycle_cnt <= cycle_cnt + 1; end wire ch_sel = (cycle_cnt < 7'd50); // 前50周期为ch0,后50为ch1 // 数据路由 always @(posedge clk_200k) begin if (ch_sel) begin ddc_in <= adc_data_ch0; // 第一路ADC数据 fifo_wr_addr <= {2'b00, cycle_cnt}; // 写入ch0 FIFO end else begin ddc_in <= adc_data_ch1; // 第二路ADC数据 fifo_wr_addr <= {2'b01, cycle_cnt}; // 写入ch1 FIFO end end关键技巧:FIFO深度设为64,读写指针用格雷码编码,彻底规避亚稳态;两路语音数据在DAC输出前按250 kSps交织(ch0,ch1,ch0,ch1…),人耳无法分辨切换延迟。
3.2 DAC输出同步:如何避免双路语音相位跳变?
若两路语音独立送DAC,因时分复用导致输出时刻错开半个周期(5 μs),叠加时会产生可闻咔哒声。解决方案是强制同步:
- 在DAC控制器中插入“同步使能”信号,仅当两路FIFO均有≥16个有效数据时才启动DAC更新
- 用250 kSps时钟驱动DAC,但数据总线每周期加载双路样本(16bit ch0 + 16bit ch1)
- DAC芯片(如AD9708)配置为双通道模式,内部自动交替输出
注意:DE2-115板载DAC为单通道,必须外接双通道DAC(如TI DAC8563),否则无法物理分离输出。
4. 调试黑匣子:高频信号链路上的5个致命坑与血泪排查法
4.1 现象:输入10.7 MHz信号,频谱显示镜像分量>-20 dB
原因:I/Q解调中cos/sin本振相位不正交(相差非90°),或两路ADC增益不匹配。
解决:
- 用示波器测NCO输出的cos/sin引脚,调整CORDIC IP的Phase Offset参数,使相位差严格为90°±0.2°
- 在FPGA内插入增益校准模块:用已知幅度正弦波注入,动态调节I/Q通道乘法器系数,使两路FFT幅值差<0.1 dB
4.2 现象:语音有持续“嘶嘶”底噪,信噪比<30 dB
原因:FIR滤波器系数量化误差导致阻带衰减不足,高频噪声混入基带。
解决:
- 将MATLAB生成的系数从double转为Q15时,不用
round(),改用floor()+ceil()交替取整,降低量化噪声谱密度 - 在FIR后级联一级2阶IIR椭圆滤波器(fpass=15kHz, fstop=20kHz, Astop=60dB),仅增加3% LUT
4.3 现象:切换通道时耳机发出“啪”声
原因:DAC输出未做零点过渡,电压突变。
解决:
- 在DAC数据总线前插入“平滑过渡”模块:当通道切换时,用16点线性插值(y[i] = y0 + (y1-y0)*i/16)连接两路最后一帧与第一帧数据
- Verilog中用计数器控制插值步长,确保过渡时间<64 μs(人耳不可辨)
4.4 现象:某频点语音失真严重,其他正常
原因:NCO相位累加器位宽不足(<16bit),在10.7 MHz输出时产生相位截断杂散。
解决:
- 将NCO Phase Width从12bit提升至18bit,重新生成IP核
- 验证方法:用ChipScope抓取NCO输出波形,FFT观察杂散抑制比,要求>80 dBc
4.5 现象:长时间运行后语音逐渐变闷,高频衰减
原因:去加重IIR滤波器系数α因温度漂移缓慢变化,导致时间常数偏移。
解决:
- 放弃固定系数,改为自适应算法:实时计算语音信号高频能量(10–15 kHz带)与全带能量比,若比值<0.3则微调α增大5%
- 用12bit查找表存储256组α值,根据能量比索引查表,避免实时计算
5. 实战验证四步法:从示波器波形到主观听感的闭环确认
5.1 示波器验证:三处必测波形与合格阈值
| 测试点 | 推荐探头位置 | 合格标准 | 工具提示 |
|---|---|---|---|
| ADC输入端 | 开发板J11模拟输入焊盘 | 正弦波无削顶,峰峰值1.8Vpp±0.1V | 用1GHz带宽探头,接地弹簧夹紧 |
| DDC输出I/Q | FPGA引脚(I路) | 两路正交,相位差90°±0.5°,幅值差<0.2dB | FFT功能开启,Span=1MHz |
| DAC输出左声道 | 耳机插座TP1焊盘 | 无直流偏置,语音波形包络平滑无毛刺 | AC耦合,时基2ms/div |
提示:测DAC输出时,务必断开耳机!否则负载效应导致波形畸变。用10kΩ电阻模拟耳机阻抗再测量。
5.2 频谱分析:用SoundCard+Audacity做低成本SINAD测试
专业设备贵且难借,用笔记本声卡+Audacity免费方案:
- 将DAC输出经运放(OPA2134)缓冲后接入笔记本Line In
- Audacity设置:采样率48 kHz,16bit,录制10秒纯语音(如“测试测试”)
- 插件→Nyquist Prompt,输入:
(setf sig (snd-from-array 0 48000 (cue *track*))) (setf noise (highpass2 sig 15000)) (setf sinad (/ (rms sig) (rms noise))) (format nil "SINAD = ~,1f dB" (db sinad)) - 合格线:SINAD ≥ 42 dB(对应语音清晰度>95%)
5.3 主观听感ABX盲测:淘汰伪优化方案的终极裁判
所有参数调优必须过此关:
- 录制同一段新闻语音,分别用“默认参数”和“优化后参数”解调
- 用foobar2000播放器做ABX测试(工具→ABX Comparator)
- 邀请3名未参与开发的同学,在安静环境戴HD650耳机盲测
- 规则:连续10轮判断“B是否与A不同”,答对≥8轮视为显著差异
- 血泪经验:曾将FIR阶数从32提至64,SINAD提升1.2dB,但ABX测试失败——人耳根本听不出区别,反而多占12%资源。优化必须服务于听感,而非仪表。
5.4 抗干扰鲁棒性压测:模拟真实电磁环境
实验室干净信号不等于实战:
- 在FPGA板旁放置正在通话的手机(GSM频段900MHz),观察语音是否出现“哒哒”脉冲干扰
- 解决方案:在ADC前端加π型LC滤波器(10nH+100pF+10nH),抑制900MHz谐波
- 用金属网罩住整个FPGA电路板,接地处理,EMI降低28dB
我坚持一个习惯:每次修改代码后,必用手机贴近板子通话1分钟,耳机里没“哒哒”声才算真正通过。这比任何仿真都真实——因为电磁干扰从不按教科书的频点出现。希望帮到你。
本文还有配套的精品资源,点击获取