资讯动态

基于FPGA的BPSK变频器电路设计:从NCO到ADC/DAC的完整实现

发布时间:2026/9/16 2:45:48 来源:尧图企业网站定制
简介面向基于FPGA的BPSK变频器电路设计需求该压缩包提供了一套从发射端数字上变频到接收端变频解调的完整工程参考。设计将1兆赫兹的BPSK基带信号变频到4兆赫兹中频载波覆盖直接数字频率合成、正交混频、载波生成等关键模块并包含多个DDS和IQ调制器相关IP核文件便于对照学习变频链路的硬件实现。资源包采用ZIP压缩格式共包含796个文件压缩后整体大小约9.48兆字节属于轻量级FPGA工程设计包。文件类型涵盖Verilog/VHDL硬件描述语言源文件、Xilinx ISE工程配置、IP核与约束文件、Tcl脚本及仿真日志并附有PDF和HTML说明文档方便梳理设计流程与参数设置。已有486人学习下载适合通信电子或数字信号处理方向的FPGA入门者、课程设计学生以及需要在此基础上进行二次开发和参数调试的工程师。1. 为什么“基于FPGA的BPSK变频器电路设计”值得自己做一遍很多人把BPSK当成QPSK去掉一路来理解但真要把一个基带码流变成可发射的中频信号或者把一个中频信号搬回基带往往会卡在变频级NCO相位步进算错一个位宽输出全是乱码滤波器系数定晚了眼图闭合一大半DAC时钟抖动偏大EVM面板上的数字直接让人不敢看。这个标题绕不开三件事BPSK映射与解调、数字变频结构、ADC/DAC周边电路。把这三件事在FPGA里串起来是一个典型的中频收发链路微缩工程既能作为fpga入门到进阶的分水岭也是通信板卡调测的常客。以下按“原理→代码→仿真→电路设计与调试”的顺序展开所有参数都按能复现的目标给出。2. BPSK变频器原理与“数字化之后先想清楚”的三个问题2.1 BPSK调制为什么是“乘±1”而不是“换相位表”BPSK的本质是用一个二进制符号控制载波相位取0°或180°反映到数字域就是一个实信号乘上“A”或“−A”的基带电平再与载波混频。很多fpga入门的同学会下意识地建一张相位查找表在符号到来时切换NCO输出的相位偏移这能做但非常浪费而且相位跳变容易产生毛刺。更常见的做法是把编码后的比特流映射成有符号电平比特1对应1比特0对应−1或按格雷码反相​。映射后的基带信号幅度只有两个值与正交载波相乘后就变成了载波同相信号的BPSK。解调侧用同一个载波做相干下变频再通过符号判决恢复原始比特。如果直接在FPGA内部做上变频不需要真正把载波搬到模拟域。常见做法是先用NCO产生数字载波再在数字域完成混频最后送入DAC。这一步的优势是载波频率由相位累加器精确控制不存在模拟本振的温漂和相位噪声问题。下述设计都基于这种全数字变频思路。2.2 数字变频的结构NCO、混频器和滤波器在数字域干什么数字上变频DUC的典型链路是“码流→BPSK电平映射→数字混频→DAC→模拟滤波器”。数字下变频DDC则反过来“ADC→数字混频→低通滤波→定时恢复→判决”。两套结构在FPGA中共享同一个NCO和乘法器只是数据流向相反。正交变频也不是必需的。BPSK的频谱只在载波一侧用实数混频就能实现。但工程上仍常保留I/Q两路结构原因有两个一是便于后续扩展QPSK或多电平调制二是解调侧用一个90°移相网络把镜像信号分离否则实数混频后正负频率会发生折叠。数字域混频公式非常直接x_IF[n] d[n] × A[n]其中A[n] cos(2π·f_c·n/f_s)。d[n]是±A的基带电平A[n]由NCO查找表给出。FPGA里乘法器一个时钟周期就能完成瓶颈反而在NCO的相位步进和累加位宽上。载波频率分辨率由相位累加器位宽决定Δf f_s / 2^N。若f_s50MHzN32分辨率约为0.0116Hz完全够用。位宽过小则无法精确表示10.7MHz这类非整数分频频率最终产生频率偏差。2.3 采样率、载波频率和位宽的选型以 50MSPS 和 10.7MHz 为例这里给出一组可以直接落地的参数表。该参数适配常见Xilinx ARTIX-7或Altera Cyclone V开发板也适合用黑金或正点原子等fpga开发板验证。参数取值选择理由系统时钟 f_clk50MHz与ADC/DAC采样率一致省去多时钟域采样率 f_s50MSPS满足10.7MHz中频信号f_cf_s/2中频载波 f_c10.7MHz常见中频带外抑制容易做NCO相位累加器位宽32bit频率分辨率0.0116HzNCO输出位宽16bit兼顾查找表存储量与EVM基带符号速率 R_b1Msps中频带宽约2MHz便于滤波基带电平位宽16bit与载波直接相乘后仍是16bit混频输出位宽16bit乘法后截位防溢出位宽的选择要留裕量混频乘法后位宽会翻倍一般先扩展到32bit再截回16bit。截位不能直接砍低几位否则直流偏置可能导致频谱出现载波泄漏。用“舍入饱和”策略比“直接截断”好很多。后面章节的代码默认使用这种策略。3. 用Verilog在FPGA上实现BPSK变频器的核心代码3.1 顶层模块与端口规划顶层模块把NCO、BPSK映射、混频和DAC接口拆开方便后续替换成更高阶调制。端口建议直接对应FPGA开发板上的DAC芯片引脚比如AD9708这类并行DAC也可以把数据写入FIFO由外部接口读取。module bpsk_duc_top #( parameter PHASE_WIDTH 32, parameter DATA_WIDTH 16, parameter LUT_WIDTH 16 )( input wire clk, input wire rst_n, input wire bit_in, // 1比特串行码流 output reg [DATA_WIDTH-1:0] dac_data, // 并行DAC数据 output wire dac_clk // DAC时钟可接DCM输出 ); wire [DATA_WIDTH-1:0] nco_sin; wire [DATA_WIDTH-1:0] nco_cos; wire signed [DATA_WIDTH-1:0] bpsk_lv; // BPSK电平映射1 - 120000 - -12000 assign bpsk_lv bit_in ? 16sd12000 : -16sd12000; // NCO产生载波 nco_dds #( .PHASE_WIDTH(PHASE_WIDTH), .DATA_WIDTH (LUT_WIDTH) ) u_nco_dds ( .clk (clk), .rst_n (rst_n), .sin_o (nco_sin), .cos_o (nco_cos) ); // 混频器常用 DSP48E 实现乘法 reg signed [DATA_WIDTH-1:0] mixed; always (posedge clk or negedge rst_n) begin if (!rst_n) mixed d0; else mixed $signed(bpsk_lv) * $signed(nco_sin); end // 截位到16bit同时饱和处理 always (posedge clk or negedge rst_n) begin if (!rst_n) dac_data d0; else begin if (mixed[31:16] 16h7fff) dac_data 16h7fff; else if (mixed[31:16] 16h8000) dac_data 16h8000; else dac_data mixed[23:8]; // 舍去低8位 end end assign dac_clk clk; endmodule代码要点说明bpsk_lv是BPSK映射后的有符号电平不使用查找表只做一个条件判断节省block RAM。NCO例化输出的nco_sin作为载波;nco_cos保留下来便于后续扩展正交调制或DDC解调。混频用Verilog内建乘法器。Xilinx综合时会自动推断为DSP48E1若在Altera上则会被映射为硬件乘法器不需要手动干预。截位选择高位mixed[23:8]既防溢出又保留足够动态范围。若直接取mixed[31:16]会丢失大量有效位输出频谱会有明显量化噪声。3.2 相位累加器与正弦余弦查找表NCONCO的代码是变频器核心。相位累加器每个时钟周期加一次频率控制字 frequency tuning wordFTW高16位作为查找表地址。查找表可以用ROM初始化文件也可以直接用块内存IP。module nco_dds #( parameter PHASE_WIDTH 32, parameter DATA_WIDTH 16, parameter LUT_DEPTH 4096 )( input wire clk, input wire rst_n, output reg [DATA_WIDTH-1:0] sin_o, output reg [DATA_WIDTH-1:0] cos_o ); localparam unsigned [PHASE_WIDTH-1:0] FTW $rtoi(10.7e6 / 50e6 * 2.0**PHASE_WIDTH); reg [PHASE_WIDTH-1:0] phase_acc; always (posedge clk or negedge rst_n) begin if (!rst_n) phase_acc d0; else phase_acc phase_acc FTW; end wire [LUT_DEPTH-1:0] idx phase_acc[PHASE_WIDTH-1 -: LUT_DEPTH]; // 以下为查找表逻辑省略真实系数表综合时替换为ROM IP reg [DATA_WIDTH-1:0] sin_table [0:LUT_DEPTH-1]; reg [DATA_WIDTH-1:0] cos_table [0:LUT_DEPTH-1]; always (posedge clk or negedge rst_n) begin if (!rst_n) begin sin_o d0; cos_o d0; end else begin sin_o sin_table[idx]; cos_o cos_table[idx]; end end endmodule参数说明FTW计算FTW f_c/f_s × 2^PHASE_WIDTH在Verilog里用$rtoi完成浮点转整数综合时是常数。查找表深度选4096输出位宽16bit。若想降低资源占用可以减到1024但杂散会升高。实际项目中不要手写几千行正弦表应调用Xilinx DDS Compiler IP或Altera NCO IP。IP核内部可以选择CORDIC算法或ROM查找表CORDIC比纯ROM省存储但在FPGA上DSP资源便宜优先选“ROM泰勒校正”模式。3.3 BPSK映射、上变频混频与DAC输出上变频的混频器在一拍内完成乘法但要注意符号位扩展。$signed()必须同时作用于两个操作数否则Verilog会按无符号数处理负电平被当成很大的正数。DAC接口通常是并行数据加时钟。许多高速DAC要求数据在时钟上升沿之前建立因此需要把混频结果打一拍再输出。这也是上面顶层代码里dac_data是寄存器输出的原因。直接使用组合逻辑输出会带来严重的时序问题。如果开发板上DAC数据位宽只有12bit则截位规则要改assign dac_data mixed[19:8];12bit位宽下满量程为±2048BPSK输出电平最好设在满量程的70%~80%给模拟后端留出余量。实际工程中可用“峰值回退”方式调节DAC幅度避免后级放大器饱和。这也是“变频器电路设计”里跟模拟电路对接时经常踩的坑数字域滿码不代表模拟域干净。3.4 数字下变频的复用镜像抑制与低通滤波同一套硬件可以复用做DDC核心是ADC采样得到中频信号乘以NCO的cos和sin分别得到I/Q两路再经过低通滤波器滤除2f_c分量。这时NCO频率要改成与发送端一致并且需要做载波同步。低通滤波器的实现选择常用的三种结构对比如下滤波器类型资源占用处理速度适用场景直接型FIR高乘法器多高无反馈系数少、阶数低半带滤波器中乘法器减半高抽取/内插2倍CIC积分梳状极低无需乘法器最高大抽取比、宽带信号BPSK只需要2MHz带宽若从10.7MHz中频变到基带建议先数字混频再用半带滤波器做2倍抽取最后用CIC做4倍或8倍抽取。CIC的优点是结构里没有乘法器功耗低代价是通带滚降明显需要在后续用FIR均衡。这一组合是fpga开发中DUC/DDC链路的经典搭配可以直接当作模板复用。4. 仿真验证用Testbench确认BPSK变频波形不再“玄学”4.1 写一个能让BPSK变频器自检的测试平台Testbench不复杂但有一点很关键把比特流结果和混频输出分别写入两个文件方便后处理。下面给出一个自检激励模拟发送端产生BPSK基带序列并同时观察中间信号。timescale 1ns / 1ps module tb_bpsk_duc; reg clk 0; reg rst_n 0; reg bit_in 0; wire signed [15:0] dac_data; wire dac_clk; always #10 clk ~clk; // 50MHz initial begin #100 rst_n 1; // 发送一段已知伪随机序列 repeat(20) begin bit_in $random; #100; // 1Msps end #1000 $finish; end initial begin integer f; f $fopen(dac_data.txt, w); // 在混频输出有效时写入文件采集点和系统时钟对齐 end always (posedge clk) begin if (rst_n) begin $fdisplay(f, %d, $signed(dac_data)); end end bpsk_duc_top dut ( .clk(clk), .rst_n(rst_n), .bit_in(bit_in), .dac_data(dac_data), .dac_clk(dac_clk) ); endmodule这段激励的逻辑说明时钟周期20ns50MHzbit_in每100ns跳一次模拟1Msps码率。$random产生随机比特覆盖连续同相码元的情况。$fdisplay把DAC输出按有符号数写入文本供Python或MATLAB读取做频谱分析。如果想仿真解调链路可以把dac_data再接入一个DDC模型验证解调后的码元与发送码元是否一致。4.2 用Vivado/ModelSim看波形眼图、星座图和频谱怎么检查跑完仿真后第一步看时域波形dac_data应表现为高频载波包络随基带电平翻转。BPSK时载波在符号边界处相位反转180°这正是“相位连续”和“相位突变”的分界点。如果载波包络没有随码流翻转说明映射或混频接错了。第二步看频谱。先在Vivado中运行行为仿真导出dac_data.txt再用Python离线画图import numpy as np import matplotlib.pyplot as plt data np.loadtxt(dac_data.txt) / 32768.0 fs 50e6 n len(data) freq np.fft.fftfreq(n, 1/fs) spec 20*np.log10(np.abs(np.fft.fft(data)) 1e-12) plt.figure(figsize(10, 4)) plt.plot(freq/1e6, spec) plt.xlim([0, fs/2/1e6]) plt.xlabel(Frequency (MHz)) plt.ylabel(Amplitude (dB)) plt.title(BPSK DUC Output Spectrum) plt.grid(True) plt.show()这个脚本的作用是快速验证混合后信号的主瓣是否在10.7MHz附近而不是在0Hz或21.4MHz。如果主瓣出现在21.4MHz说明混频后没有滤除镜像如果主瓣在0Hz附近多半是NCO的sin/cos接错把cos接到I路却用sin做了实数混频。眼图可以用Python把每个符号周期内的波形叠在一起画也可以直接看仿真波形。BPSK眼图在符号中点张开最大若眼图上眼皮和下眼皮都变厚说明载波有幅度噪声或DAC量化位宽不够。4.3 仿真中常见的三个坑无符号数、回环初值、异步复位仿真不过的时候优先查三件事几乎能覆盖八成问题第一个坑是Verilog乘法器操作数无符号化。bpsk_lv是负数时若乘法表达式里漏写$signed()仿真器按无符号数理解结果完全失真。排查方法是给复位后第一个码元打波形检查混频输出是否出现符号翻转。第二个坑是NCO初值。相位累加器复位置0没问题但查找表输出复用部队时机没对齐混频结果会延迟一拍导致仿真和实际板子对不上。在所有寄存器输出上统一打一拍能规避初始相位不确定的问题。第三个坑是异步复位信号在仿真里使用#5这类随机时间释放导致复位撤销时正好撞上时钟上升沿引起FF亚稳态。标准做法是用同步复位always (posedge clk) begin if (!rst_n) phase_acc d0; else phase_acc phase_acc FTW; end如果设计里既有异步复位又有同步复位仿真环境里所有约束都要跟真实时序对齐否则综合前后仿真行为不一致。5. 硬件调试与进阶给BPSK变频器“收尾”的技术细节5.1 时钟抖动与电源噪声在ADC/DAC电路板上的三个布局要点上板后最容易出问题的不是逻辑而是“模拟电路设计”。时钟抖动会让混频后的载波边带变宽电源噪声则直接抬高DAC输出的噪声底。常见的做法有三个方面一是给ADC/DAC提供独立的高质量时钟不要把FPGA内部逻辑时钟直接接到DAC建议用专用时钟Buffer并靠近DAC引脚放置二是DAC模拟电源和数字电源做单点连接或磁珠隔离GND层在器件下方完整铺铜而且不要在DAC正下方开槽走数字线三是混频器输出到DAC输入线保持等长、尽量短并加RC低通滤波到地。5.2 用ILA和VIO调试变频器从“仿真过了”到“板子能跑”板级调试时用Vivado的ILA抓DAC前一级数据设触发条件为bit_in变化沿。观察数据是否呈“正弦包络相位翻转”特征。VIO可以在线调整FTW验证频率调节是否准确进而确认NCO环路和DAC时钟频率。若ILA中数据正常但频谱仪上无信号检查DAC参考时钟和模拟输出匹配网络。这一步把fpga开发从代码阶段带入信号完整性验证阶段是回归现实电路的必由之路。5.3 进阶Simulink与FPGA联合仿真的思路、CIC优化后能省多少资源如果要对变频器做更完整的验证可以在Simulink中搭建变频链路模型生成激励数据导入FPGA仿真再把FPGA输出导回MATLAB对比误码率。Simulink的频谱分析工具可以直接观测BPSK频谱辅助确认滤波器阶数。对于CIC优化用4级CIC做8倍抽取替换等效FIR约能节省70%以上的DSP乘法器资源代价是带内容差需要在后续FIR中补偿。这个优化在资源紧张的项目中几乎是必做步骤。若进一步把CIC补偿和半带滤波器合并成一个多相FIR单路BPSK链路的DSP48E用量可以控制在个位数。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价