ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

FPGA DDS正弦波发生器:相位累加器原理与MATLAB/FPGA位真对齐实践

FPGA DDS正弦波发生器:相位累加器原理与MATLAB/FPGA位真对齐实践

简介:这份资源面向数字信号处理学习者与FPGA开发者,聚焦DDS直接数字频率合成技术的双通道正弦波叠加实现与验证。包内以Verilog与VHDL源码、MATLAB脚本、Vivado工程文件及仿真脚本为主体,配合约束文件、综合报告与日志,完整呈现从算法建模到硬件落地的链路。资源共534个文件,涵盖v、vhd、m、xdc、tcl、rpt等类型,压缩包约16.36MB,目录按工程模块组织,便于按需检索。已有302人学习下载,适合希望打通MATLAB仿真与FPGA实现的中级读者参考。读者可从中获取双通道DDS相位累加器与查找表设计思路、MATLAB波形验证方法,以及综合实现与仿真排错线索,用于课程设计、实验复现或项目移植时对照学习。

1. DDS 正弦波发生器:为什么相位累加器是整条信号链的心脏

很多人第一次在 FPGA 上做 DDS 正弦波,都会盯着查找表容量和 DAC 位数反复调,结果波形还是不够干净。真正决定输出频率分辨率、相位连续性和杂散水平的,往往不是这些显性参数,而是那个只有几十位的相位累加器。它每来一个时钟就加上一个频率控制字,溢出位直接丢弃,剩下的高位去查正弦表——这个看似简单的循环,把“频率”变成了“相位增量”,把“时间”变成了“地址”。搞懂它,你才能明白为什么改一个控制字就能无跳变地切换频率,也才能解释 MATLAB 仿真和 FPGA 实测之间那点对不上的偏差到底出在哪。这份 DDS_2C1 资源把 MATLAB 建模和 FPGA 实现放在一起,正好是拆这条链路的合适样本。

2. 相位累加器与频率控制字:频率是怎么被“算”出来的

2.1 从连续相位到离散累加:一个积分器的数字化

模拟域里,正弦波是 $s(t)=\sin(2\pi f t)$,相位随时间线性增长。数字域没法表达连续时间,于是把相位也离散化:每个采样时钟周期,相位增加一个固定量 $\Delta\phi$。这个 $\Delta\phi$ 就是频率控制字(Frequency Tuning Word,FTW),它和输出频率的关系是:

$$f_{out} = \frac{FTW \cdot f_{clk}}{2^N}$$

其中 $N$ 是相位累加器位宽,$f_{clk}$ 是系统时钟。这个公式是整个 DDS 的第一性原理:频率分辨率等于 $f_{clk}/2^N$。一个 32 位累加器配 100 MHz 时钟,分辨率约 0.023 Hz,这就是为什么 DDS 能做到极细的频率步进——不是靠改晶振,而是靠改一个整数。

常见的实现思路是用一个 $N$ 位寄存器加一个 $N$ 位加法器构成累加器,每个时钟上升沿执行acc <= acc + FTW。溢出自然回绕,正好对应相位从 $2\pi$ 跳回 0,不需要任何取模运算。这一点很关键:硬件里“溢出”不是错误,而是设计的一部分。

// 32 位相位累加器,每个时钟累加一次频率控制字 reg [31:0] phase_acc; always @(posedge clk or negedge rst_n) begin if (!rst_n) phase_acc <= 32'd0; else phase_acc <= phase_acc + ftw; // 溢出自动回绕,等价于 mod 2^32 end

这段代码只有一行有效逻辑,但它决定了三件事:第一,频率切换时相位不清零,所以输出波形连续、没有相位跳变;第二,累加器位宽直接决定频率分辨率;第三,加法器的进位延迟会影响最高工作频率,位宽越大越难跑高频。我一般会先确认目标时钟和分辨率,再反推位宽,而不是上来就写 32 位。

2.2 频率控制字的计算与截断误差

实际工程里,FTW 往往不是整数。比如要输出 1 kHz,时钟 50 MHz,32 位累加器,算出来 $FTW = 1000 \times 2^{32} / 50\times10^6 \approx 85899.35$。取整成 85899 之后,实际频率是 999.992 Hz,误差约 8 mHz。这个误差在大多数场景可以忽略,但在需要严格频率对齐的场合(比如多通道相干系统),就得靠更宽的累加器或者分数分频来压。

MATLAB 建模阶段通常用浮点算 FTW,再量化成定点,这一步的舍入方式会影响后续频谱。资源里的 MATLAB 脚本一般会显式打印实际输出频率和理论值的偏差,方便你在上板前就判断量化误差是否可接受。血泪经验是:别在 MATLAB 里用round之后就不管了,把量化后的 FTW 代回公式反算一遍频率,和设计指标对一下,能提前发现位宽选窄了。

2.3 相位截断:查找表地址为什么只取高位

累加器有 32 位,但正弦表不可能存 $2^{32}$ 个点。常见做法是只取高 $M$ 位作为查找表地址,低 $N-M$ 位直接丢弃,这叫相位截断。截断会引入周期性的相位误差,在频谱上表现为杂散(spur)。截断位数越多,杂散越明显,但表也越小。

这里有个容易被忽略的取舍:截断误差不是随机的,它和 FTW 强相关。某些 FTW 下杂散会集中到特定频点,听起来像“玄学”,其实是可以算出来的。MATLAB 里做 FFT 看 SFDR(无杂散动态范围)时,换几个 FTW 试试,就能看到杂散分布随控制字变化。资源里如果带了不同截断位数的对比脚本,值得逐个跑一遍,比看文档直观得多。

提示:相位截断带来的杂散无法靠提高 DAC 位数消除,它发生在数字域。要压它,要么加表深度,要么上相位抖动(dithering),要么用泰勒级数校正。

3. 查找表与波形重建:正弦值从哪来、精度丢在哪

3.1 查找表的存储结构与量化位宽

查找表本质是一段 ROM,地址是截断后的相位,内容是正弦值的定点量化。位宽 $D$ 决定幅度量化噪声,理论 SNR 约 $6.02D + 1.76$ dB。12 位约 74 dB,14 位约 86 dB。但注意,这个 SNR 是理想情况,实际还要叠加相位截断杂散和 DAC 非线性。

存储结构上,常见两种:全表存 $[0, 2\pi)$ 一个周期,或者只存 $[0, \pi/2]$ 四分之一周期加符号/对称逻辑。后者省 3/4 存储,但要多一点地址译码逻辑。FPGA 的 Block RAM 通常够大,全表实现更简单、时序更干净,我一般优先全表,除非资源真的紧张。

// 取相位高 12 位作为地址,查 12 位量化的正弦表 wire [11:0] lut_addr = phase_acc[31:20]; wire signed [11:0] sin_val; sin_rom u_rom ( .clk(clk), .addr(lut_addr), .dout(sin_val) // 有符号定点,范围约 [-2047, 2047] );

地址取phase_acc[31:20]意味着截断掉低 20 位,表深 4096。这个映射关系要和 MATLAB 建模严格一致,否则仿真和实测对不上。参数说明:lut_addr位宽决定表深,sin_val位宽决定幅度精度,两者独立可调,但都受 Block RAM 配置约束。

3.2 从查找表到 DAC:重建滤波为什么不能省

DDS 输出的是离散采样序列,DAC 之后是阶梯波,频谱里除了基波还有大量镜像分量,集中在 $f_{clk} \pm f_{out}$、$2f_{clk} \pm f_{out}$ 附近。不做重建滤波,这些镜像会直接混进输出,示波器上看是毛刺,频谱仪上看是一排谐波。常见的实现思路是在 DAC 后加低通滤波器,截止频率设在 $f_{clk}/2$ 附近,把第一镜像压下去。

这里有个踩坑点:很多人以为 DDS 输出频率越低越干净,其实低到一定程度,镜像离基波很近,滤波器过渡带不够陡就压不干净。选输出频率时,尽量让 $f_{out}$ 落在 $f_{clk}$ 的 1/4 到 1/3 之间,给滤波器留过渡带。资源里的 MATLAB 脚本如果画了 DAC 输出频谱,重点看镜像位置和幅度,比只看时域波形有用。

3.3 MATLAB 定点建模与 FPGA 位真对齐

MATLAB 建模的价值在于位真(bit-true)对齐:用fi定点类型模拟累加器溢出、截断、量化,跑出来的序列和 FPGA 仿真逐点比对。做法是把 FTW、累加器位宽、表地址位宽、表数据位宽全部参数化,MATLAB 和 Verilog 用同一套参数。对齐之后,任何偏差都能定位到具体环节,而不是笼统地说“仿真和实测不一样”。

% 位真建模:32 位累加器,取高 12 位查表,12 位量化 N = 32; M = 12; D = 12; ftw = fi(85899, 0, N, 0); % 无符号定点 acc = fi(0, 0, N, 0); addr = zeros(1, L, 'uint16'); for k = 1:L acc = acc + ftw; % fi 类型自动处理溢出回绕 addr(k) = floor(double(acc) / 2^(N-M)); % 取高位 end sin_lut = fi(round(sind((0:2^M-1) * 360 / 2^M) * (2^(D-1)-1)), 1, D, 0); y = sin_lut(double(addr) + 1); % 查表输出

这段脚本的关键在fi的溢出模式默认是 saturate 还是 wrap,要显式设成 wrap 才能和硬件一致。floor(double(acc)/2^(N-M))对应 Verilog 里的位切片,两者必须严格等价。跑完把y和 Verilog 仿真输出逐点相减,残差应该全零;不为零就说明某一环的量化或截断方式不一致。

4. 杂散与频谱:DDS 输出为什么不只是“一个正弦”

4.1 三类杂散来源的区分

DDS 频谱上的杂散主要来自三处:相位截断杂散、幅度量化杂散、DAC 非线性杂散。相位截断杂散是确定性的,位置和 FTW 有关,幅度随截断位数增加而下降,大约每多保留一位地址位,杂散改善 6 dB。幅度量化杂散接近白噪声,均匀分布。DAC 非线性则表现为谐波,和具体器件相关。

排查时先看杂散是否随 FTW 变化:变化明显多半是相位截断,固定位置多半是谐波。这个判断方法很实用,能少走很多弯路。资源里如果带了频谱分析脚本,建议固定其他参数只改 FTW,观察杂散移动规律,比死记公式管用。

4.2 相位抖动(dithering)的取舍

压相位截断杂散的一个常用手段是相位抖动:在截断前给相位加一个小的伪随机数,把确定性杂散打散成噪声。代价是底噪抬升,SFDR 和 SNR 之间要做权衡。实现上就是在累加器输出加一个 LFSR 生成的小随机量,再截断。

这个手段不是万能的。如果系统对特定频点的杂散敏感(比如接收机的中频规划),抖动可能把杂散搬到更麻烦的位置。我一般会先在 MATLAB 里加抖动模型跑频谱,确认杂散分布可接受再上硬件,避免上板后才发现问题。

4.3 用 FFT 验证:窗函数与频谱泄漏

MATLAB 里看 DDS 频谱,直接fft会有泄漏,因为采样点数未必是信号周期的整数倍。加窗(Hamming 或 Blackman-Harris)能压泄漏,但会展宽主瓣,影响杂散幅度读数。做法是选相干采样(采样点数是周期整数倍)配合矩形窗,或者加窗后按窗函数修正幅度。两种方法结果对不上时,先怀疑是不是泄漏没处理好,而不是急着改设计。

5. 避坑与排查:DDS 上板后波形不对,先查这几处

5.1 输出频率偏差固定比例

现象:实测频率是设计值的整数倍或分数倍。原因:FTW 计算时位宽或时钟频率代错,或者 MATLAB 和 Verilog 用了不同的 $N$。解决:把量化后的 FTW 代回 $f_{out}=FTW\cdot f_{clk}/2^N$ 反算,和实测对比,偏差比例能直接指向是位宽错还是时钟错。

5.2 波形有周期性毛刺

现象:示波器上每个周期固定位置出现尖刺。原因:查找表地址截断导致相位误差周期性出现,或者 ROM 读延迟没对齐,地址和数据错拍。解决:先确认 ROM 是否加了一级输出寄存器、地址和数据是否同拍;再把截断位数加一位看毛刺是否减弱,能区分是截断还是时序问题。

5.3 频率切换时波形跳变

现象:改 FTW 瞬间输出相位不连续。原因:切换时把累加器清零了,或者 FTW 用了非寄存的组合逻辑直接进加法器。解决:FTW 打一拍再进累加器,切换时只改 FTW 不动累加器,相位自然连续。

5.4 高频段输出幅度衰减

现象:输出频率越高,幅度越小。原因:DAC 的零阶保持效应(sinc 滚降),或者重建滤波器截止频率设太低。解决:算一下 sinc 衰减 $|\sin(\pi f/f_{clk})/(\pi f/f_{clk})|$,在 $f_{clk}/4$ 处约 -0.9 dB,$f_{clk}/3$ 处约 -1.6 dB,属于正常;衰减过大就检查滤波器。

5.5 MATLAB 与 FPGA 结果对不上

现象:仿真波形趋势对,但逐点比对有残差。原因:定点舍入方式不一致(round vs floor)、溢出模式不一致(wrap vs saturate)、表初始化相位偏移不一致。解决:三处逐一核对,先保证累加器和截断完全一致,再查表内容和地址映射。

6. 设计取舍:位宽、表深与时钟,没有一项能单独最优

DDS 的参数选择是一个多目标权衡,不存在“全都拉满”的方案。累加器位宽决定频率分辨率,但加法器进位链随位宽增长,限制最高时钟;表地址位宽决定相位截断杂散,但表深受 Block RAM 容量约束;表数据位宽决定幅度量化噪声,但和 DAC 位数要匹配,超出 DAC 分辨率的部分没有实际收益。

我一般会按这个顺序定参数:先根据频率分辨率和目标时钟定累加器位宽,再根据 SFDR 指标定表地址位宽,最后根据 SNR 指标和 DAC 位数定数据位宽。三步之间如果冲突,优先保分辨率,因为相位截断杂散可以靠抖动或校正补救,分辨率不够则无法挽回。

参数主要影响增大代价
累加器位宽 N频率分辨率加法器延迟、时序压力
表地址位宽 M相位截断杂散Block RAM 容量
表数据位宽 D幅度量化 SNR存储位宽、DAC 匹配
系统时钟 f_clk输出上限、分辨率功耗、时序收敛难度

验证方法上,我习惯做两级比对:MATLAB 位真模型对 Verilog 仿真,逐点残差为零;Verilog 仿真对上板实测,看频谱而不是只看时域。时域波形“看起来像正弦”说明不了问题,频谱上的杂散位置和幅度才是硬指标。资源里 MATLAB 和 FPGA 两套东西放在一起,价值就在于能让你把这两级比对都走通,而不是各跑各的。

从那以后我每次做 DDS,都强制先把位真模型和 RTL 逐点对齐再上板,哪怕多花半天,也比上板后对着频谱猜原因强。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进