ARTICLE DETAIL

资讯详情

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

Zynq-7000上DDS Compiler IP核实战:从Vivado仿真到板上实测

Zynq-7000上DDS Compiler IP核实战:从Vivado仿真到板上实测 先说一句标题里的DDS是Direct Digital Synthesizer直接数字频率合成用来生成正弦波、扫频信号的那套东西不是ROS2里那个Data Distribution Service中间件。两个DDS是完全不同的领域身边不止一个朋友在群里把这两者搞混上来就问“你的DDS用哪个端口通信”我只能哭笑不得。这篇文章要聊的是前者而且是实打实地在Zynq-7000上把Xilinx官方DDS Compiler IP核跑起来从Vivado里的仿真波形一路做到板上实测顺便把大家最关心的资源消耗也拉出来看一遍。这个内容适合谁对DDS原理有一定了解但没在Xilinx平台上完整实操过的FPGA工程师、准备用Zynq做信号发生器或软件无线电原型的同学、以及被各种教程绕得晕头转向、只想看一条能直接走通路径的动手派。我会尽量把从IP配置、testbench设计、Block Design搭建、PS端写寄存器到最后用ILA抓波形的每个环节都讲清楚你跟着做就能复现。另外先打个预防针网上关于DDS IP核的教程很多但大多停在“仿真波形长这样”就结束了。仿真只是万里长征第一步真正上板之后你会发现复位、时钟、AXI总线地址、数据格式每个环节都可能有坑。这篇文章就是想把“从仿真到实测”中间这段没人好好讲的路替你踩一遍。1. 先把方案捋清楚为什么是DDS IP核 Zynq-70001.1 从项目需求反推选型DDS IP核到底解决什么问题如果你只是要在FPGA里产生一个固定频率的正弦波最快的办法是自己写一个相位累加器一个ROM查找表不到二十行Verilog就搞定。我早期做信号源原型时就这么干过功能确实能跑但做到后面就开始头疼了——想要扫频、想要调相、想要改善杂散、想要多通道同步代码复杂度立刻上去了而且性能和资源占用还未必可控。Xilinx的DDS Compiler IP核本质上是把相位累加器、相位截断、抖动处理、正弦查找表、以及AXI4-Stream接口这些成熟方案都封装好了。你在GUI里把参数一填它自动帮你处理相位截断带来的杂散问题自动生成查找表或CORDIC算法自动把AXI4-Stream握手时序管理好。对你来说最重要的工作就变成了两件事把频率控制字算对把配置时序给对。这比自己从头造轮子省心得多。在Zynq-7000上用这个IP核还有个特别明显的优势PL侧负责高速生成波形PS侧用ARM Cortex-A9通过AXI总线动态改频率。纯PL方案想改频率得重新综合这在调试和实际产品里都不可接受。PSPL方案意味着你可以像打电话一样随时改输出频率而且还能配合Linux驱动程序做成一个真正可用的信号发生设备。1.2 整体架构PL生成波形PS负责配置我这次工程的整体数据通路是这样的PS端通过AXI总线写一个AXI GPIOGPIO的输出接到一个自己写的“配置桥接逻辑”上桥接逻辑负责把GPIO数据转换成DDS核需要的AXI4-Stream配置时序DDS核输出正弦波数据一部分送给外部引脚用于示波器或DAC观察一部分同时接到ILA核用于板级调试。为什么中间要加一个“配置桥接逻辑”因为DDS核的S_AXIS_CONFIG_TDATA接口是AXI4-Stream协议而PS端AXI总线上挂的是AXI-Lite从设备两者协议不匹配。最省事的做法就是用AXI GPIO 简单的RTL状态机来搭桥PS把频率控制字写到GPIO同时把某个控制位拉高桥接逻辑检测到这个上升沿后把频率控制字锁存到S_AXIS_CONFIG_TDATA上并拉高TVALID持续8个时钟周期完成一次配置。这套方案代码量不大但把PS和PL的协作关系体现得很完整。1.3 硬件环境的假设与准备我没有办法给所有板卡都写一份专属教程所以下面的流程基于最经典的Zynq-7000平台假设你用ZedBoard或类似国产Zynq-7020开发板PL侧提供100MHz时钟DDS的输出通过PMOD引脚或板上扩展口引出。如果没有外接DAC直接看ILA抓出来的数字正弦波形也完全能验证功能。如果你的板卡PL时钟不是100MHz记得把后文所有公式里的f_clk替换成你的实际时钟频率这是新手最容易忽略的问题。环境方面Vivado 2019.1到2023.x都适用越新版本对DDS Compiler的界面越友好。如果你还在用Vivado 2015.4那个年代的SDK那套老的SDK和Vivado的安装卸载确实容易让人崩溃新项目建议直接上VivadoVitis的统一流程会省掉很多环境层面的麻烦。2. 深入DDS IP核参数不是拍脑袋定出来的2.1 核心参数逐项拆解相位累加器位宽、输出位宽、通道数打开Vivado IP Catalog搜索DDS Compiler双击后会看到一堆参数。很多人看到这个界面就发怵其实核心参数就那几个。第一个是Phase Width也就是相位累加器位宽。这个值直接决定频率分辨率。计算公式是Δf f_clk / 2^N。比如100MHz时钟相位累加器用32位频率分辨率就是100MHz除以2的32次方算下来约0.023Hz。什么概念你设定输出10MHz和10.0000001MHz都能精确区分这在精密信号源里非常重要。第二个是Output Width也就是波形数据位宽决定输出正弦波的幅度量化精度。通常设14位到16位比较合适过小波形台阶感明显过大会白白消耗资源。第三个是通道数DDS核原生支持多通道输出通道间可以做精确相位同步。单通道和两通道在资源上差异不大但如果做到四通道以上Block RAM的资源占用会明显上升。这几个参数之间要配合着看。相位累加器位宽设得很大但ROM查找表的地址位宽不可能用满32位通常会做相位截断只取高十几位作为ROM地址。截断会引入杂散DDS核提供了抖动Dither机制来把杂散能量扩散到整个频带实际效果就是SFDR指标更好看。这些选项在GUI里对应Noise Shaping或Dither相关配置我建议直接用默认的Auto除非你有明确的频谱指标要求再去手动调。2.2 频率控制字计算公式和实例DDS的核心公式是这个FTW f_out × 2^N / f_clk其中FTW是频率控制字N是相位累加器位宽。我这次工程的配置是N32、f_clk100MHz目标f_out5MHz。代入公式FTW 5e6 × 4294967296 / 100e6算出来是214748364.8取整后得到214748365十六进制就是0x0CCCCCCD。把0x0CCCCCCD写进DDS核实际输出频率是多少反过来验证一下f_out FTW × f_clk / 2^N 214748365 × 100e6 / 4294967296等于4.99999998MHz左右误差不到0.02Hz。这就是32位相位累加器的优势每步频率控制都极其细腻。你可以顺便记两个常用值方便调试100MHz时钟下1MHz对应FTW约为0x028F5C2910MHz对应0x1999999A不用每次重新算。需要注意FTW最终写入时要和你在IP核里选择的“相位增量模式”匹配。DDS核的S_AXIS_CONFIG_TDATA接口有很多种配置模式比如只配置相位增量或同时配置相位增量和相位偏移。如果勾选了相位偏移可编程Tdata的数据布局会变复杂新手建议先选只配置相位增量的模式等基本功扎实了再去研究相位偏移。2.3 配置接口时序要点TVALID要拉多少个周期DDS核的配置接口是AXI4-Stream协议但它的TVALID时序和普通AXI外设不太一样并不只是拉高一个周期那么简单。按照Xilinx官方文档PG141DDS Compiler的S_AXIS_CONFIG通道要求TVALID至少保持TCG个时钟周期TCG与配置通道的位宽和具体配置有关在默认配置下通常是8个周期。这意味着你的testbench和上板逻辑里配置数据必须稳定保持至少8拍否则DDS核可能根本没接收这次配置。另一个需要注意的信号是S_AXIS_CONFIG_TREADY。DDS核在准备好接收配置时会把TREADY拉高但你不能只在TREADY为高后的下一个周期就立刻撤销TVALID。稳妥的做法是拉高TVALID等TREADY拉高后再数8拍然后才撤销。我在实际项目中见过不少人只在TREADY拉高后保持了一两个周期结果DDS核偶尔工作正常、偶尔输出频率不变排查起来极其痛苦。3. 从零搭建工程仿真波形先跑起来3.1 在Vivado中添加DDS IP核的完整步骤在Vivado里新建RTL工程之后左侧IP Catalog里搜索DDS双击DDS Compiler进入配置界面。我这次的配置参数建议这样选Component Name填dds_compiler_0Configuration选Sine or CosineChannels填1Mode选Phase Increment OnlyPhase Width填32Output Width填14Phase Increment Programming选Programmable其他选项保持默认。关于Output Width有人喜欢填16位觉得精度更高。但你要考虑到如果后面接DAC12位或14位DAC比较常见输出16位还得自己截断反而容易出问题。14位是一个通用性很好的选择。配置完成后在IP Sources里能看到生成的例化模板Instantiation Template。点开之后顶层文件里就能看到标准的端口列表。各版本Vivado生成的端口名可能略有差异但核心就是那几组时钟复位、S_AXIS_CONFIG四个信号、M_AXIS_DATA四个信号。建议先照着例化模板敲一遍不要直接复制粘贴整个模板这样对端口的印象会深很多。3.2 Testbench应该怎么设计才贴近真实硬件DDS核的仿真核心目标只有一个验证配置时序正确且输出正弦波形正确。我习惯自己手写testbench结构非常清晰代码如下module tb_dds_top(); reg aclk; reg aresetn; reg s_axis_config_tvalid; reg [31:0] s_axis_config_tdata; wire s_axis_config_tready; wire m_axis_data_tvalid; wire [13:0] m_axis_data_tdata; initial begin aclk 0; forever #5 aclk ~aclk; end initial begin aresetn 0; s_axis_config_tvalid 0; s_axis_config_tdata 32h0; #100; aresetn 1; #20; s_axis_config_tvalid 1; s_axis_config_tdata 32h0CCCCCCD; // 5MHz 100MHz repeat(8) (posedge aclk); // 保持至少8个周期 s_axis_config_tvalid 0; end dds_compiler_0 u_dds( .aclk (aclk), .aresetn (aresetn), .s_axis_config_tvalid (s_axis_config_tvalid), .s_axis_config_tdata (s_axis_config_tdata), .s_axis_config_tready (s_axis_config_tready), .m_axis_data_tvalid (m_axis_data_tvalid), .m_axis_data_tdata (m_axis_data_tdata) ); endmodule这段testbench的逻辑很简单先给复位等复位释放后把FTW放到Tdata上并把Tvalid拉高保持8个时钟周期后拉低。这里面有个关键点为什么我不用TREADY做握手而固执地数8拍因为我吃过亏在个别版本的DDS核里TREADY的行为没有你想象的那么规范严格按照文档保持8拍是最稳妥的。3.3 仿真波形解读什么时候才算配置成功跑完仿真打开Vivado Simulator的波形窗口把M_AXIS_DATA_TDATA按有符号十进制显示。正常情况下你应该看到一串从0开始、先升后降再升的正弦序列波形轮廓很平滑幅值大概在正负8192之间摆动。这里有个最常见的认知误区如果你用无符号十进制显示会看到一个从0到16383来回的锯齿状波形然后怀疑自己哪里配置错了。其实这不是错而是DDS输出是有符号二进制补码你需要把Waveform窗口里的Radix切到Signed Decimal再看。我自己第一次烧录时也栽在这个小坑上当时以为是IP核配置问题折腾了好久才发现是显示设置。另外一个要观察的信号是M_AXIS_DATA_TVALID它应该周期性地拉高。DDS核输出数据是连续流式的每个时钟周期都有效所以TVALID通常会一直为高。如果这个信号一直为低说明配置没有生效回头检查复位和配置时序。4. 硬件实测把信号从PS写到DDS4.1 Block Design搭建AXI GPIO地址分配与桥接逻辑仿真通过后开始搭建上板工程。在Vivado里新建Block Design依次添加ZYNQ7 Processing System、AXI GPIO和DDS Compiler。ZYNQ7 PS块可以用Run Block Automation让Vivado自动配置DDR和UART把PL时钟设置为100MHz并打开UART1用于打印调试信息。AXI GPIO的宽度设为32方向设为Output其他默认。因为DDS核的S_AXIS_CONFIG不是AXI-Lite接口不能直接挂在AXI总线上所以需要写一小段RTL桥接逻辑。这段逻辑的核心功能是检测GPIO最高位bit 31的上升沿把GPIO低32位中的FTW字段锁存再驱动DDS核的配置接口。module dds_cfg_bridge #( parameter FTW_WIDTH 32 )( input wire aclk, input wire aresetn, input wire [31:0] gpio_data, output reg s_axis_config_tvalid, output reg [FTW_WIDTH-1:0] s_axis_config_tdata ); reg gpio_data_d; reg [3:0] cnt; reg [1:0] state; reg [FTW_WIDTH-1:0] ftw_lock; localparam IDLE 2d0, PULSE 2d1, WAIT 2d2; wire start gpio_data[31] !gpio_data_d; always (posedge aclk or negedge aresetn) begin if (!aresetn) begin gpio_data_d 1b0; state IDLE; cnt 4d0; ftw_lock {FTW_WIDTH{1b0}}; s_axis_config_tvalid 1b0; s_axis_config_tdata {FTW_WIDTH{1b0}}; end else begin gpio_data_d gpio_data[31]; case (state) IDLE: begin s_axis_config_tvalid 1b0; if (start) begin ftw_lock gpio_data[FTW_WIDTH-1:0]; s_axis_config_tdata gpio_data[FTW_WIDTH-1:0]; s_axis_config_tvalid 1b1; cnt 4d0; state PULSE; end end PULSE: begin s_axis_config_tvalid 1b1; s_axis_config_tdata ftw_lock; if (cnt 4d8) begin s_axis_config_tvalid 1b0; state WAIT; end else begin cnt cnt 1b1; end end WAIT: begin state IDLE; end default: state IDLE; endcase end end endmodule这段逻辑看起来很短但已经把“边沿检测”“锁存”“8拍保持”这几个关键点全都覆盖了。也许你会觉得直接在PS里控制GPIO电平来模拟手工时序不就行了理论上可以但PS通过AXI写GPIO的时序抖动很大很难保证恰好满足8个周期的精确宽度用状态机来做才靠谱。如果你不想用AXI GPIO桥接也可以写一个简单的AXI-Lite从机把寄存器地址0x00映射到FTW字段把0x04映射成触发位这样接口更简洁。但AXI-Lite从机的手写代码量比较大新手容易出错在产品原型阶段用GPIO搭桥完全够用而且调试时用Vivado的Hardware Manager直接改GPIO值看现象也特别方便。4.2 Vitis/SDK代码通过AXI寄存器写入频率控制字BD里把AXI GPIO、DDS、自定义桥接逻辑用Create HDL Wrapper整合到顶层再完成综合、实现、生成比特流。综合和实现的等待过程中正好把Vitis部分写好。在Vitis里创建基于Hello World模板的工程添加XGpio驱动支持然后写这样的代码#include xgpio.h #include xparameters.h #define GPIO_BASEADDR XPAR_AXI_GPIO_0_BASEADDR #define GPIO_DATA_OFFSET 0x0 void dds_set_frequency(XGpio *gpio_inst, unsigned int ftw) { Xil_Out32(GPIO_BASEADDR GPIO_DATA_OFFSET, ftw); Xil_Out32(GPIO_BASEADDR GPIO_DATA_OFFSET, 0x80000000 | ftw); Xil_Out32(GPIO_BASEADDR GPIO_DATA_OFFSET, ftw); } int main() { XGpio gpio; XGpio_Initialize(gpio, XPAR_AXI_GPIO_0_DEVICE_ID); XGpio_SetDataDirection(gpio, 1, 0x0); // 设置为5MHzFTW 0x0CCCCCCD dds_set_frequency(gpio, 0x0CCCCCCD); while (1) { // 保持程序运行 } return 0; }这段代码的关键在于dds_set_frequency函数第一次写入纯FTW让GPIO输出低控制位第二次把bit31置1桥接逻辑检测到上升沿后自动完成配置第三次把bit31清零为下一次配置做准备。这比一直置高bit31更稳妥因为万一桥接逻辑调试不当时至少上升沿触发是可靠的。如果你用的Vitis版本比较老也可以用XGpio_DiscreteWrite替代Xil_Out32效果一样。地址0x0是AXI GPIO通道1的数据寄存器这是Xilinx的标准寄存器映射确认无误。4.3 用ILA抓板级波形以及没有DAC时怎么看正弦波在Block Design里添加ILA核把DDS的M_AXIS_DATA_TDATA和M_AXIS_DATA_TVALID接进去采样深度建议设成4096或8192触发条件设为M_AXIS_DATA_TVALID上升沿。生成比特流后先在Hardware Manager里下载再打开Vivado的Waveform窗口。重点来了Vivado的Waveform窗口支持把总线数据切换成模拟曲线显示。在波形视图里选中M_AXIS_DATA_TDATA总线右键选择Waveform Style切到Analog。你会看到一条近乎完美的正弦曲线这就是ILA从FPGA内部抓到的最真实的运行数据比任何仿真波形都有说服力。这个方法在没有示波器、没有DAC的条件下也能完成硬件验证属于调试的杀手锏。如果你有示波器可以把DDS输出引脚直接或经过一级RC低通滤波后接到示波器探头。DDS输出的数字正弦波频率很高时直接引脚输出会有明显台阶但只要经过简单滤波就能看到模拟正弦波。不过要注意Zynq-7000的IO驱动能力有限建议经过一个跟随器或缓冲器再接示波器否则波形可能会变形。接DAC芯片的朋友要特别注意数据格式DDS输出的是二进制补码多数DAC输入需要偏移二进制或二进制补码不同DAC要求不一样。比如常见的AD9708这类芯片通常用偏移二进制你需要把补码信号加上一个直流偏置把最高位取反然后才能送进DAC。这个问题在仿真里看不出来一定是上板接DAC后才会暴露。5. 资源消耗分析LUT、FF、BRAM都去哪了5.1 一份典型的Utilization报告长什么样综合完成后打开Report Utilization你会看到一份资源使用报告。我这次工程的DDS部分资源消耗大致如下资源类型数量占Z-7020总量比例说明Slice LUTs约132约0.25%相位累加器、控制逻辑、地址计算Slice Registers约110约0.10%流水线寄存器、状态机寄存器Block RAM Tile1约0.7%正弦查找表DSP48E100%未使用DSP单元这个表是“相位累加器32位、输出14位、单通道、正弦查找表用BRAM实现”的典型数据。不同Vivado版本和不同器件型号会有些差别但量级基本就是这范围。DDS核本身资源开销很小在Zynq-7020这种规模的芯片里完全属于“洒洒水”级别。为什么BRAM只用了1个因为14位输出、大约16位相位地址的正弦表数据量也就是65536乘14bit约1Mbit左右单个Block RAM的容量已经足够装下。如果输出位宽继续提高或者要求极低的量化噪声BRAM数量会增加到2个或4个。如果你看综合报告里BRAM为0那说明IP核实现时选择了用分布式RAMLUTRAM来实现查找表这是IP核根据你的配置自动做的权衡不一定是我们这边的配置有问题。5.2 不同参数配置下的资源差异和优化取舍在DDS IP核的GUI里改几个参数资源消耗会发生很有意思的变化。相位累加器位宽从32位减小到16位LUT会略微减少但频率分辨率从0.02Hz左右变成1500Hz左右对很多应用而言这个精度就不够了。输出位宽从14位增加到16位BRAM使用量可能翻倍因为ROM地址不变但每个存储单元变宽了。通道数从1增加为2DDS核会尝试共享查找表资源增长不一定线性而是看IP核能否把两个通道的查表运算合并到同一块BRAM里。最有意思的是算法选择。DDS核内部可以用查找表方案也可以选CORDIC算法。查找表方案吃BRAM但算得快CORDIC方案省BRAM但吃LUT和DSP还会引入额外的流水线延迟。对高频信号生成来说查找表方案更直接如果BRAM资源特别紧张再考虑CORDIC。我给出一个非常实用的优化建议如果只是做原型验证不用一上来就追求极致参数把相位累加器设成32位、输出设成14位、通道数按照实际需求来填跑通了再说。资源优化的前提是功能正确没有任何优化比“先跑起来”更重要。5.3 资源分析背后的思路预算很重要做资源分析不只是为了写汇报而是为了给自己留出余量。Zynq-7020总共大概有5万多LUT、10万多FF、140个BRAM和220个DSP。你的DDS核用掉100多个LUT不算什么但如果你后面还要挂FFT、滤波器、以太网协议栈每一块资源都要提前规划。我习惯在工程最开始就定一个资源预算表比如DDS预留200个LUT、2个BRAMFFT预留3000个LUT、8个BRAM即使IP核实际消耗比预算少很多心理上也有底。这种习惯是从项目管控角度养成的等到资源不够再回头优化架构代价远大于一开始预留点余量。6. 常见问题与排查技巧实录6.1 仿真正常、上板不出波先查这五个地方第一种情况ILA里面什么都抓不到M_AXIS_DATA_TVALID一直为低。大概率是DDS核没有收到有效配置。从这几个方向查先用Hardware Manager确认AXI GPIO的地址有没有写对对照xparameters.h里的XPAR_AXI_GPIO_0_BASEADDR这步经常有人看错地址再确认GPIO数据方向寄存器设置成了输出XGpio_SetDataDirection的第三个参数0x0不能填错最后检查桥接逻辑有没有放进综合很多人升级顶层之后忘了把自定义RTL包含进去。第二种情况配置写进去了ILA也能看到波形但频率和预期差得离谱。先算一遍FTW公式确认f_clk用的是不是真实的PL时钟频率。我曾经遇到过PL时钟设成了100MHz但实际板卡上晶振是125MHz导致所有频率都偏了25%。另外检查是否误把十六进制写成了十进制比如0x0CCCCCCD写成了120795955十进制功能没错但频率不对这种错误很隐蔽。第三种情况波形轮廓是出来了但看起来有毛刺或者周期性跳变。优先检查时钟余量如果是时序违例导致的问题直接看Implementation中有没有Timing Violation的红色告警。也检查一下ILA的采样时钟是否和DDS用同一个时钟域跨时钟域的ILA采样会出现很多假毛刺。第四种情况PS端程序跑起来后DDS没反应但GPIO电平确实变了。最快捷的定位方法是保持程序运行的同时用Vivado Hardware Manager在线读取GPIO寄存器值确认bit31有上升沿出现过。如果GPIO输出正常但桥接逻辑没动作那就是桥接逻辑的复位问题检查一下aresetn是否连到了PS的复位输出或者PL侧生成的复位模块。第五种情况如果你用的是外部DAC芯片发现输出幅度很小或者直流偏置不对。八成都出在数据格式上。DDS的输出是补码范围是-8192到8191如果DAC需要0到4095的偏移二进制你需要把最高位取反并适当移位而不只是简单截断低几位。建议画一张数据转换表把正弦波几个关键采样点的数值手算一遍再写代码比上板后瞎猜高效得多。6.2 频率不准和杂散变差的原因频率不准最常见的原因是FTW四舍五入的误差。虽然32位累加器已经把误差压得很低但如果相位累加器位宽只有16位FTW量化误差可能会让输出频率偏差达到百分之几这在窄带系统里是要命的。解决思路很简单在资源允许的情况下尽量用够32位相位累加器频率精度这种东西一句话就是“用位宽容错”。杂散变差的原因就比较专业了。相位截断会在频谱上产生固定的杂散线DDS核的抖动选项能把杂散能量均匀扩散到整个频带从而提升无杂散动态范围SFDR。如果你在做频谱纯净度要求高的项目千万不要为了省那几个LUT把抖动关掉。我自己实测过相同配置下关掉抖动后SFDR能下降十几个dB这在很多射频场景里是不可接受的。如果你用示波器或频谱仪看到输出频率附近的相位噪声偏大也检查一下PL时钟的质量。Zynq-7000的PS可以产生PL时钟但这个时钟经常是从DDR或其他外设参考时钟分频出来的噪声指标一般。如果项目对相位噪声有要求尽量用板载独立时钟芯片或高精度晶振直接给PL提供参考时钟。6.3 经验笔记三条能少走弯路的习惯第一个习惯是分步验证。不要一上来就PSPL全端联调。我的做法是先用固定FTW常量在纯PL工程里跑通DDS和ILA验证确认PL侧没问题后再加PS和AXI部分。这样出了问题你能很清楚地定位是PL的错还是PS的错否则两个子系统同时引入问题时光是排查“为什么复位不对”就能耗掉半天。第二个习惯是在代码里把FTW做成宏定义而不是散落一地的数字。每个频率值都配上目标频率和实际频率的注释比如这样#define FTW_1M 0x028F5C29 // 1MHz, 实际1.000000Hz #define FTW_5M 0x0CCCCCCD // 5MHz, 实际4.9999998MHz #define FTW_10M 0x1999999A // 10MHz, 实际9.9999996MHz这个习惯看起来不起眼但在你后续做扫频测试、多频段切换时会省下大量调试时间。曾经我在一个项目里需要用户从20个预设频点里做选择如果当时所有频点都是直接写在程序里的裸数字后面维护基本就是灾难。第三个习惯是保存好每一版的utilization报告。综合报告是文本格式Git完全能追踪差异。每次改动参数后对比前后资源变化你能很快看出哪些选项对资源的影响最大很多经验是通过这种对比积累出来的。不要等到项目口粮紧张了才去找谁吃了资源早点存档早点心里有数。6.4 最后再分享两个小技巧关于DDS输出的正弦波形如果你发现ILA里显示的波形有轻微的非线性或毛刺先把采样深度调大再看。ILA的采样深度太小波形显示会失真这不是DDS本身的问题。采样深度调到8192之后再观察基本上能还原比较真实的波形轮廓。关于频率切换DDS核支持在运行中改写FTW但切频瞬间会有短暂的相位不连续。我在做扫频信号源时通常会在PS端做一个简单的等待机制先写入目标频率延时几个时钟周期让DDS完成内部流水线更新再做下一个动作。这个延时不用很精确几十个周期就行刚好卡在DDS核内部流水线建立时间内。如果你做了更精细的相位连续扫频需求就得认真去研读PG141里关于配置与输出之间延迟的表格了不能只靠这种粗略等待。关于调试载板上的DDS输出引脚如果示波器测到的波形不是平滑正弦线而是带毛刺的阶梯线这是正常的因为FPGA输出的是数字正弦波每100MHz时钟才刷新一次。只要你能在阶梯线的轮廓上看出正弦趋势就说明DDS工作正常。想看到真正平滑的模拟正弦必须外接DAC和重建滤波器。这个认知能避免很多无谓的紧张。
返回列表