ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Vivado DDS Compiler双通道信号生成实战:从原理到配置

Vivado DDS Compiler双通道信号生成实战:从原理到配置 刚接触Vivado里DDS Compiler的时候我相信大多数人都会被那一串配置项搞得云里雾里。Phase Increment、Phase Offset、SFDR、Noise Shaping、Independent/Joint通道模式……一个个术语看着都眼熟真到要配一个能用的双通道信号发生器出来却总是差那么一口气。这篇文章就用一个实战例子带着你把Vivado IP Catalog里的DDS Compiler从原理到配置、再到双通道信号生成的完整流程走一遍顺便把那些文档里不会明说的坑都给你指出来。这篇文章适合谁看已经会用Vivado建工程、写简单Verilog但还没怎么碰过DDS Compiler的FPGA开发者或者是项目里需要快速生成两路正弦波、需要I/Q正交信号、需要给相控阵通道提供可调相位基准的朋友。看完之后你应该能独立完成双通道DDS的配置、例化和仿真验证也知道换频率、换通道关系时该动哪些参数。1. DDS原理与需求的初步拆解1.1 DDS的基本原理相位累加器与查找表DDS全称Direct Digital Synthesizer直接数字频率合成器。FPGA里实现DDS核心就三样东西相位累加器Phase Accumulator、波形查找表LUT/ROM、以及后级的幅度量化输出。绝大多数情况下你不需要自己去写相位累加器和查找表直接用Xilinx官方IP即可但原理必须懂否则配置界面上的参数对你来说就是一堆随机数字。相位累加器可以理解成一个循环计数器每个时钟上升沿累加一次。这个累加器的位宽是N位所以它能表示的相位范围是0到2^N-1对应一个完整周期的360度。每个时钟沿累加的值就是频率控制字也叫Phase IncrementPINC。PINC越大累加器从0跑到溢出或者回绕的速度越快输出波形的频率就越高。输出频率的计算公式是f_out PINC × f_clk / 2^N其中f_clk是DDS的系统时钟频率。从公式可以直观看到PINC相当于每个时钟周期走多少度走得快波形就密。频率分辨率由Δf f_clk / 2^N决定。举个例子系统时钟100MHz累加器位宽32位那么频率分辨率就是100e6 / 4294967296 ≈ 0.0233Hz非常精细足够绝大多数应用使用。相位累加器产生的是相位值要变成正弦波还需要查表。把累加器的高M位作为地址去ROM里查出对应的正弦幅度值。但这里有个问题ROM的深度有限不可能把2^N个相位全部映射进去只能取高M位低N-M位被截断。这个截断就是DDS杂散Spurious的主要来源。表现出来就是在频谱上主信号旁边会出现不需要的小尖峰。为了抑制杂散常见的做法是加Dither抖动把杂散能量打散到噪声底或者用Taylor Series Correction泰勒级数校正来补偿截断误差。这个后面配置的时候会具体说。1.2 双通道信号生成的应用场景与典型需求为什么需要双通道信号生成应用场景太常见了。最典型的就是I/Q正交信号生成A通道输出余弦B通道输出正弦两路信号频率相同、相位差90度用于正交上变频、数字下变频、QPSK调制解调等。另一个常见场景是双音测试同时给被测系统输入两个不同频率的信号用来测互调失真、非线性特性。还有相控阵系统里每个阵元可能需要独立的相位控制多通道DDS可以方便地给不同通道设置相位偏移实现波束指向控制。甚至音频领域做立体声效果、左右声道不同频率扫频信号都能用上双通道DDS。双通道的关系通常分两种一是两个通道频率不同各自独立二是两个通道频率相同但相位关系固定尤其是0度/90度/180度等。搞清楚你要哪种关系直接决定DDS Compiler里Channel Selection选Independent还是Joint。这里有个重要的概念要提前说DDS Compiler的多通道输出是时分复用TDM的。也就是说两个通道的数据不是两个独立的并行数据流而是在同一个数据接口上轮流输出第一个时钟周期出通道0的数据第二个时钟周期出通道1的数据第三个周期又出通道0……以此类推。因此每个通道的有效采样率是系统时钟除以通道数。如果系统时钟100MHz、双通道那么每个通道的实际吞吐率是50MHz。这一点在做频率规划时必须提前算好否则输出频率超过25MHz就会违反奈奎斯特定理波形直接失真。2. Vivado IP Catalog中的DDS Compiler核心概念2.1 从IP Catalog找到DDS Compiler打开Vivado工程在左侧Flow Navigator里找到IP Catalog搜索框输入dds能看到一个叫DDS Compiler的IP。它的完整版本号一般是6.0以上不同Vivado版本可能细节略有差异但核心配置项是通用的。如果你习惯用Tcl也可以在Vivado Tcl Console里直接输入create_ip -name dds_compiler -vendor xilinx.com -library ip -version 6.0 -module_name dds_compiler_0这样IP会直接创建到工程里然后双击打开配置界面。我个人建议新手用GUI方式因为GUI里有大量的自动计算和提示能帮你搞明白各个参数之间的联动关系。2.2 关键配置项逐一解读Configuration与Output FrequencyDDS Compiler的配置界面左侧有多个标签页核心主要是Configuration和Output Frequency这两块。Configuration页里第一个要选的是Configuration类型有Phase Generator和Direct Digital SynthesizerDDS两种。Phase Generator只输出相位值不负责查表转换成正余弦DDS才是完整的信号发生器。大部分情况下选DDS。接着是Phase Increment Programmability也就是频率控制字能不能在运行时修改。可选Fixed固定或Programmable可编程。选Fixed频率在配置时写死IP内部自动处理不需要额外的输入接口选ProgrammableIP会多出一个S_AXIS_PHASE输入接口运行时要通过这个接口把PINC值送进去。同理Phase Offset Programmability控制相位偏移POFF是否可编程有None、Fixed、Programmable三种。Noise Shaping这里有None、Dither、Taylor Series Corrected三个选项。None就是不管相位截断的杂散Dither加抖动把杂散摊平到噪声底简单有效Taylor是泰勒级数校正进一步提升SFDR代价是资源更多。对一般的信号生成Dither是性价比最高的选择。Output Frequency页主要填这些内容System ClockDDS的工作时钟频率必须和实际接入的aclk一致否则IP算出来的频率全是错的。Number of Channels通道数这里填2。Channel SelectionIndependent或Joint后面单独讲。Frequency per Channel每个通道的期望输出频率。Spurious Free Dynamic RangeSFDR无杂散动态范围IP会根据你的输出位宽、相位位宽和噪声整形方式估算能达到的SFDR值。Frequency Resolution只读显示当前配置下的频率分辨率。2.3 模式选择的核心取舍Fixed/Programmable与Independent/JointPhase Increment选Fixed还是Programmable核心是是否需要在运行时改变频率。如果你只是生成两路固定频率的信号比如10MHz和5MHz那必须选Fixed省事、稳定、资源也少而且代码里完全不用关心PINC怎么算。如果系统需要扫频、跳频或者像相控阵那样动态调整通道相位那就得选Programmable运行时通过S_AXIS_PHASE接口喂数据。Channel Selection的Independent和Joint是另一个关键选择。Independent模式下每个通道有独立的相位增量和相位偏移通道之间完全独立。这意味着两个通道可以输出不同频率、不同相位的信号。Joint模式则恰好相反所有通道共享相位增量通道间只能配置各自不同的相位偏移而且有些版本Joint模式连POFF也是共享的具体看版本提示。Joint模式资源更省但灵活性差适合所有通道输出相同频率、只需要固定相位差的场景。这里必须提醒一个容易踩的坑很多人以为选了Independent模式IP内部就有两套完整的DDS硬件资源翻倍。其实不是。DDS Compiler的多通道实现是时分复用的两个通道共用一套相位累加器和查找表逻辑Independent模式只是多了存储每通道PINC/POFF的寄存器。所以多通道DDS比例化两个独立DDS IP核要省资源得多这也是推荐用多通道功能的原因。3. 双通道DDS配置的完整步骤3.1 配置前准备时钟、位宽、频率规划在动手配置之前先把需求定清楚。我这次以一个非常典型的例子来走流程系统时钟100MHz需要输出两个通道的正弦波通道0输出10MHz通道1输出5MHz。两个通道频率不同所以Channel Selection选Independent。输出位宽16bitNoise Shaping选DitherPhase Accumulator位宽取默认32bit。先验证频率规划是否合理双通道模式下每个通道的有效采样率是100MHz / 2 50MHz。通道0的10MHz远小于25MHz的奈奎斯特上限通道1的5MHz也没问题。如果需求是40MHz双通道按这个配置就废了因为40MHz超过了25MHz。这种时候要么降低通道数要么提高系统时钟要么改用单通道Sin/Cos同时输出两路信号Sin和Cos的采样率都等于系统时钟不受通道数折半影响。设计阶段先把这些算清楚能省后面好多事。Output Width选16bit是比较均衡的选择。位宽越大幅度量化噪声越低但查找表存储和后续处理资源也会上升。16bit在绝大多数测试和通信场景下都够用。3.2 DDS Compiler的GUI配置实操创建好IP后双击打开DDS Compiler配置界面按下面顺序设置。Configuration页面Component Name改成dds_compiler_0方便后续例化。Configuration选Direct Digital SynthesizerDDS。Phase Increment Programmability选Fixed。固定频率模式不用写S_AXIS_PHASE接口最简单。Phase Offset Programmability选Fixed。通道间不需要动态调相。Noise Shaping选Dither。往下看Output类型选Sine因为我们只要正弦波。如果需要正交信号可以选Sine and Cosine这样单通道就能同时输出正余弦两路信号那是另一种用法。Output Frequency页面System Clock填100单位MHz。Number of Channels填2。Channel Selection选Independent。Frequency per Channel这里会出现两个输入框通道0填10通道1填5单位MHz。填完后下面会自动显示计算出来的结果Phase Increment值、SFDR、频率分辨率等。这时你会看到两个通道的PINC值会被自动算出来。通道010MHz的PINC大约是0x1999999A通道15MHz的PINC大约是0x0CCCCCCD。如果频率不是整数关系IP会显示最接近的PINC值和对应的实际输出频率二者会有极其微小的误差通常可以忽略。其他标签页比如Implementation、Detailed Implementation新手可以直接保持默认。需要注意的是Phase Accumulator的实现方式默认Auto会让Vivado自己决定用LUT还是DSP48通常没问题。如果后续时序收敛困难再回来手动指定用DSP48实现累加器。配置完成后点击OK然后Generate Output Products等IP生成完毕。3.3 生成IP后的接口信号与关键端口IP生成后在Sources面板里找到dds_compiler_0右键选择Open IP Example Design可以打开官方例程。但更直接的是在顶层代码里手动例化。双击IP核在IP Sources里能看到例化模板Instantiation Template端口大概是这样的dds_compiler_0 u_dds ( .aclk (aclk), .aresetn (aresetn), .m_axis_data_tvalid (m_axis_data_tvalid), .m_axis_data_tdata (m_axis_data_tdata), .m_axis_data_tuser (m_axis_data_tuser) );aclk是系统时钟aresetn是异步复位低有效。m_axis_data_tvalid是输出数据有效标志m_axis_data_tdata是波形数据m_axis_data_tuser是通道指示信号。因为固定频率模式下没有S_AXIS_PHASE接口所以输入侧只需要时钟和复位。这里重点说TUSER。双通道时分复用输出下m_axis_data_tuser用来区分当前m_axis_data_tdata属于哪个通道。对2通道来说tuser为0时数据是通道0的为1时是通道1的。如果不把TUSER接口引出来后面解析数据时两个通道的波形叠在一起很难处理。所以配置IP时TUSER选项一定要开。有些版本默认是关的需要手动在Output Frequency或AXI4-Stream Options里勾上。4. 双通道信号生成的代码实现与仿真验证4.1 顶层模块例化固定频率模式固定频率模式下代码极其简单。只需要一个顶层文件例化DDS IP把TUSER和TDATA引到需要的地方即可。下面是一段可以直接用的代码module top_dds_dual( input wire clk_100m, input wire rst_n, output wire [15:0] dds_data, output wire [0:0] dds_channel ); wire m_axis_data_tvalid; wire [15:0] m_axis_data_tdata; wire [0:0] m_axis_data_tuser; dds_compiler_0 u_dds ( .aclk (clk_100m), .aresetn (rst_n), .m_axis_data_tvalid (m_axis_data_tvalid), .m_axis_data_tdata (m_axis_data_tdata), .m_axis_data_tuser (m_axis_data_tuser) ); assign dds_data m_axis_data_tdata; assign dds_channel m_axis_data_tuser; endmoduleTUSER拉出来后你在FPGA内部可以根据dds_channel的值把数据分别接入两个下游模块。比如dds_channel为0时把dds_data写入通道0的FIFO为1时写入通道1的FIFO。注意两个通道各占50%的有效时钟周期下游逻辑必须按这个节奏消费数据不能假设每个周期都有某个通道的数据。4.2 Programmable模式下的PINC计算与动态配置如果需求是动态改变频率那Phase Increment Programmability必须选Programmable。此时IP会多出S_AXIS_PHASE接口你需要手动计算PINC并按时分复用的时序送进去。PINC的计算公式前面已经提过PINC round(f_out × 2^N / f_clk)N是Phase Accumulator位宽默认32。以100MHz时钟、通道0输出10MHz为例PINC round(10e6 × 2^32 / 100e6) round(429496729.6) 429496730换算成十六进制就是0x1999999A。通道1输出5MHzPINC round(5e6 × 2^32 / 100e6) round(214748364.8) 214748365十六进制是0x0CCCCCCD。在Programmable模式下S_AXIS_PHASE接口也是时分复用的。双通道Independent模式每个通道的PINC要在自己对应的时隙写进去接口上同样有TUSER来标识当前写的是哪个通道。写配置的程序要比固定频率模式复杂一些而且IP的S_AXIS_PHASE接口有TREADY信号需要配合握手。我个人的建议是如果只是生成两个固定频率的信号就别折腾Programmable。真要动态调频优先考虑用AXI4-Lite接口通过软件配置寄存器而不是在硬件逻辑里手工去拼时序。4.3 testbench设计与仿真验证写完顶层代码就可以仿真了。下面是一个最基础的testbenchmodule tb_dds_dual; reg clk_100m; reg rst_n; wire [15:0] dds_data; wire [0:0] dds_channel; top_dds_dual u_top ( .clk_100m (clk_100m), .rst_n (rst_n), .dds_data (dds_data), .dds_channel(dds_channel) ); initial begin clk_100m 1b0; forever #5 clk_100m ~clk_100m; // 100MHz时钟周期10ns end initial begin rst_n 1b0; #100; rst_n 1b1; #5000; $stop; end endmodule跑仿真后在Vivado的波形窗口里添加m_axis_data_tdata和m_axis_data_tuser。你会看到tuser以0、1、0、1的节奏翻转每个tuser对应的波形各自是连续的正弦波。把游标放到tuser为0时的tdata数据点上数一下一个完整周期的采样点数。通道0输出10MHz有效采样率50MHz一个周期大约5个点能看到明显的正弦轮廓通道1输出5MHz一个周期大约10个点波形更平滑。如果波形看起来像一条直线或者只有几个离散值检查一下Output Width是否是16bit以及仿真时间是否足够长。用tuser区分通道的好处在这里体现得很明显一眼就能判断出两个通道的频率和幅度是否正常。如果有条件可以把仿真数据导出成文本用Python或MATLAB做FFT验证频谱纯度。不过日常使用波形窗口目测加游标测量就够了。5. 常见问题排查与实操经验5.1 常见问题速查表实际调试中我遇到过不少问题也帮别人排查过类似的坑整理成一张速查表供参考。现象可能原因解决办法输出波形频率和配置值不符System Clock配置值与实际时钟不一致检查IP配置里的System Clock必须和aclk实际频率一致两个通道的波形完全一样Channel Selection选成了Joint需要在配置里改成Independent波形看起来有毛刺或杂散大相位截断噪声、Noise Shaping选成None配置里改选Dither或Taylor Series Correctedm_axis_data_tuser一直为0或没有这个信号IP生成时未使能TUSER输出重新配置IP打开TUSER选项输出数据全部为0aresetn一直为低或固定频率模式下某个配置项没填完整检查复位信号重新打开IP确认PINC已填入仿真波形中有大量X态复位未正确释放或者未加aresetn信号在testbench里给复位一个延时后拉高双通道输出频率超过25MHz时波形畸变通道有效采样率只有50MHz输出频率接近或超过奈奎斯特上限提高系统时钟或改单通道Sin/Cos方式或降低输出频率时序报错DDS路径timing violation系统时钟过高或相位累加器实现方式不合理在Implementation里把相位累加器指定到DSP48或降低时钟频率5.2 关于有效采样率、SFDR与多通道数据解析的实操经验多通道DDS最容易忽视的就是有效采样率。很多人配完IP拿着100MHz的时钟想当然以为输出能到50MHz结果出来的波形稀烂。双通道时分复用决定了每个通道的有效采样率只有f_clk/2。所以输出频率规划时要按奈奎斯特定理重新算一遍。同样的道理如果用到4通道每个通道有效采样率只有f_clk/4能输出的最高频率更紧张。SFDR方面如果项目对频谱纯净度要求高比如通信系统中要混频的本地振荡器信号我建议Noise Shaping直接上Taylor Series Corrected虽然资源多一点但频谱改善效果明显。如果只是做测试信号源Dither就足够了没必要浪费资源。这里还要注意Output Width会制约SFDR上限16bit位宽就算相位处理得再好SFDR也上不到100dB以上那是位宽的极限决定的。最后再强调一下TUSER。我见过不止一个人配双通道DDS时没开TUSER然后看着示波器上的波形一头雾水不知道两个通道的数据到底怎么区分。时分复用输出是DDS Compiler多通道工作的核心特征做数据解析时必须依赖TUSER信号。在代码层面建议把TUSER引到顶层不要藏在IP内部不管。这套双通道DDS的配置流程我前后用在信号源、正交调制、相控阵相位校准等好几个项目里。本质上只要走通了双通道后面扩展到4通道、8通道或者切换到Joint模式、动态相位控制都只是改配置界面里几个选项的事。希望能帮你少走点弯路。
返回列表