简介这套FPGA读写AD9708AD9280的ADDA实验工程面向数字电路与FPGA学习者提供完整的Verilog逻辑源码与Quartus工程文件覆盖ADC采样、DAC波形生成、VGA显示与网格/波形叠加等典型综合设计可帮助读者快速理解数据采集链路与视频时序控制。压缩包共163个文件包含v、qsf、sdc等源码与约束文件qpf/sof等工程与烧录文件以及readme、pdf等文档说明包体仅5.33MB目录分层清晰便于按模块查阅。已有945人学习下载适合作为Cyclone IV E系列EP4CE6F17C8平台上的入门参考。资源内含AD9280数据缓存、AD9708信号发生、VGA驱动等逻辑源码并附带工程编译报告、管脚分配与使用说明可直接基于Quartus 17.1打开运行节省环境搭建与调试时间便于边学边练。1. FPGA读写AD9708和AD9280做ADDA回环实验先把整体链路认识清楚很多工程师拿到带AD9708和AD9280的开发板第一反应是找现成例程烧进去看到波形就认为完成了。实际区分度在三个位置DA输出到AD输入之间是物理层延迟与噪声FPGA内部从相位累加器到DA数据总线是数字路径延迟AD数据总线到内部缓存又要迁就芯片流水延迟。把链路拆成这三段来写Verilog才知道哪里该打拍、哪里该上FIFO、哪里要靠约束保证。AD9708是ADI的8位125 MSPS电流输出DAC需要外部电阻把电流转成电压AD9280是8位32 MSPS并行输出ADC带内部采样保持。两者组合常见于信号发生器、波形采集前端和通信教学板。不管你是刚接触FPGA还是做数字信号处理方案选型把这对芯片在Quartus里跑熟得到的是一个可复用的“写DA、读AD、回环比对”框架之后换任何并行DAC或ADC都只需要改时序参数核心结构不用推翻。2. AD9708与AD9280的接口时序并行总线到底要迁就什么写Verilog之前先把时序图在脑子里过一遍。两个芯片都是8位并行接口但读写两侧的约束方向完全相反对AD9708FPGA是主设备要在时钟锁存边沿之前把数据和地址稳定住对AD9280FPGA是从设备要清楚转换启动后数据何时有效。方向搞反后面仿真和上板都是白做。2.1 AD9708的数据建立时间与锁存边沿AD9708手册给出关键时序参数数据建立时间典型值2 ns保持时间典型值1.5 ns最高更新速率125 MSPS。这意味着FPGA输出的D[7:0]必须在CLK上升沿前至少2 ns稳定。常见误用是直接把查表ROM的输出接到数据总线但同步ROM读出需要一个时钟周期相位累加器的进位链和片内布线延迟会叠加进这2 ns预算时序收敛报告里红色路径往往都集中在这里。常见做法是把DA发送寄存器单独打一拍让ROM读出结果再寄存一次彻底隔离组合逻辑与总线reg [7:0] dac_data_reg; always (posedge dac_clk or negedge rst_n) begin if (!rst_n) dac_data_reg 8d128; // DAC单极性中点 else dac_data_reg sin_rom[phase_acc[9:0]]; // 寄存ROM输出 end代码逻辑每个dac_clk上升沿把查表结果锁存进dac_data_reg该寄存器直接连到芯片D[7:0]引脚。参数说明phase_acc是相位累加器的高10位作为ROM地址8d128是DAC中点AD9708单极性输出时对应模拟0V双极性偏置时中点要换成对应码值。多出来的代价是输出相位延迟一个时钟周期回环实验不在意但系统级对齐时需要给AD侧补偿同样拍数。2.2 AD9280的转换启动与输出流水延迟AD9280是流水线结构ADCENCODE上升沿触发一次采样但输出引脚不会同一时刻更新要等固定的流水延迟典型值约5个时钟周期。不少人仿真里一拍就读结果读到的是上一轮数据就是没算这段延迟。工程上常用的读时序列是启动后等待固定拍数再取总线同时把有效标志往后顺延reg [2:0] ad_wait_cnt; reg [7:0] ad_data; reg ad_d_valid; always (posedge ad_clk or negedge rst_n) begin if (!rst_n) begin ad_wait_cnt 3d0; ad_d_valid 1b0; end else begin if (ad_wait_cnt 3d4) begin // 等满5拍后采数据 ad_data ad_bus; ad_d_valid 1b1; ad_wait_cnt 3d0; end else begin ad_wait_cnt ad_wait_cnt 1b1; ad_d_valid 1b0; end end end逻辑说明ad_wait_cnt从0计到4跳过第1到第4拍在第5个时钟沿采ad_bus。ad_d_valid作为数据有效标志供后续FIFO写使能。参数说明3位计数器最大计数7这里只用0到4如果实测芯片流水延迟不是5拍改判断值即可AD9280工作在不同采样率时输出延迟会有微小变化这就是为什么上板后要留余量而不是把计数器卡死在边界。2.3 双芯片共用一个时钟域还是分开处理回环实验里把DAC_CLK和ENCODE接到同一个PLL输出最省事。模拟链路延迟固定DA波形一旦稳定AD采样点相对波形相位也可预测找一次相位就够了不用处理频差。真实采集系统如果时钟来自两个晶振则必须引入异步FIFO和握手同步。方案优点代价同源时钟结构简单相位固定模拟延迟无法数字消除只能调采样相位独立晶振更接近真实系统必须加异步FIFO复杂度上升PLL产生多频率可分别优化采样率多时钟域间需要同步和约束分组回到板上现实多数ADDA实验板把DAC输出与ADC输入直接跳线连通这时还有一个模拟幅度匹配问题AD9708满幅电流经电阻转电压后通常要经运放衰减到AD9280输入范围典型单端1 Vpp。幅度不匹配会让回环数据满量程削波波形看起来像梯形波却往往被人当成代码问题排查这个坑排错时务必放在模拟端一起看。3. Verilog逻辑源码在Quartus里把DA写、AD读的最小模块搭出来这一章给出可直接落进Quartus工程的Verilog框架分三块相位累加器生成正弦波查表地址驱动AD9708AD9280采样启动与读取状态机把发送样本和接收样本放进同一份FIFO做回环比对。工程里建议用MIF文件初始化ROM而不是在Verilog里写一长串case因为波形点数或频率改动只需要重新生成MIF不用重新整理代码结构。3.1 相位累加器生成正弦波查表ROM看频率分辨率怎么算ADDA回环最常用的激励是正弦波频谱单一混叠和削波一眼能看出来。最小可运行代码如下module dds_rom ( input wire clk, input wire rst_n, output reg [7:0] sine_out ); reg [9:0] phase; always (posedge clk or negedge rst_n) begin if (!rst_n) phase 10d0; else phase phase 10d1; // 步进为1 end wire [7:0] rom_q; sin_rom_256x8 u_rom ( .address(phase[9:2]), // 取高8位做地址 .clock(clk), .q(rom_q) ); always (posedge clk or negedge rst_n) begin if (!rst_n) sine_out 8d128; else sine_out rom_q; // 打一拍出去 end endmodule逻辑说明phase是10位计数器每拍自增1取phase[9:2]作为ROM地址等效波形点数256。sine_out对ROM输出再寄存一次满足AD9708建立时间。参数说明若DA时钟50 MHz256点正弦波输出约195 kHz要改频率就把自增改成步进值phase phase phase_inc;频率公式为f_out f_clk * phase_inc / 2^NN是累加器位宽。phase_inc输出频率50 MHzN10每周采样点数148.8 kHz1024297.7 kHz5124195.3 kHz256表格里的每周采样点数由N和phase_inc共同决定当点数少于32时重建正弦波会看到明显台阶回环误差随之增大此时应该提高DAC时钟而不是继续加大步进。3.2 AD侧读取状态机从启动转换到数据入FIFOAD9280的ENCODE可连续触发转换读侧只需要等待5拍后取数。为了不让读取阻塞主时钟把取到的数据写进同步FIFOFIFO可以用Quartus的IP也可以在代码里用双口RAM加读写指针。最小读模块如下module ad_read #( parameter PIPE_DELAY 5 ) ( input wire clk, input wire rst_n, input wire [7:0] ad_bus, output reg encode, output reg [7:0] fifo_wdata, output reg fifo_wr ); reg [2:0] wait_cnt; reg sample_valid; always (posedge clk or negedge rst_n) begin if (!rst_n) begin encode 1b0; wait_cnt 3d0; sample_valid 1b0; fifo_wr 1b0; end else begin encode 1b1; // 持续转换 if (sample_valid) begin fifo_wdata ad_bus; // 采到的数据入FIFO fifo_wr 1b1; sample_valid 1b0; end else if (wait_cnt PIPE_DELAY[2:0] - 3d1) begin wait_cnt 3d0; sample_valid 1b1; end else begin wait_cnt wait_cnt 1b1; end end end endmodule代码逻辑encode拉高后持续转换wait_cnt计满PIPE_DELAY-1后产生采样有效标志下一拍把ad_bus写入FIFO。参数PIPE_DELAY对应实际流水延迟调到实测值即可。注意wait_cnt是3位PIPE_DELAY不能超过7否则计数器回绕产生重复触发这个限制要写进模块注释不然换参数的人很容易踩坑。3.3 回环比对把发送样本和接收样本用移位寄存器对齐回环实验的核心指标是发出去的正弦波和采回来的正弦波是否一致。常见做法是发送样本和接收样本各写进一个FIFO读端同时弹出做差更省资源的是只存发送序列本次取值接收侧用移位寄存器打拍对齐。因为DA/AD时钟同源且延迟固定接收数据相对发送样本的偏移在上板后是常数所以只需手动调一个对齐拍数localparam LOOP_DELAY 8; // 实测后调整DA输出到AD数据的延迟拍数 reg [7:0] send_shift [0:LOOP_DELAY]; always (posedge clk) begin send_shift[0] dac_data_reg; for (genvar i 1; i LOOP_DELAY; i i 1) send_shift[i] send_shift[i-1]; // 逐级打拍对齐 end wire [7:0] send_aligned send_shift[LOOP_DELAY]; wire [7:0] error_val ad_data_fifo - send_aligned;这段代码用了SystemVerilog的genvar循环结构在Quartus里需要把文件扩展名设为.sv或者改写成generate for块否则旧版Quartus II会语法报错。逻辑说明send_shift数组把发送样本一级级往后移直到与AD返回数据对齐error_val是逐点误差理想回环时接近0实际上因DAC量化会有一个正负1到2的抖动抖动范围超过5就要怀疑采样点落在了DAC输出上升沿附近本文第5章会讲如何用直方图定位这类问题。4. Quartus工程配置引脚约束、时钟分配与ModelSim仿真排错代码写完只完成三分之一Quartus工程里引脚分配、时序约束、仿真配置三件事做不对上板端口接错或时序违约照样跑不出波形。这一章按工程建立、SDC约束、仿真排错的顺序来每一步都按最小可用的方式给全。4.1 用Tcl引脚约束把AD9708与AD9280引脚分配清楚Quartus支持在分配编辑器里手动点引脚但用Tcl脚本可以版本化管理换板卡或换工程版本时直接改文本。最小引脚约束如下set_location_assignment PIN_N23 -to dac_clk set_location_assignment PIN_N25 -to dac_data[0] set_location_assignment PIN_P25 -to dac_data[1] set_location_assignment PIN_M22 -to ad_encode set_location_assignment PIN_L25 -to ad_bus[0] set_location_assignment PIN_M25 -to ad_bus[1]以上是示意引脚实际编号以板卡原理图为准。关键点是时钟引脚必须接到FPGA专用时钟输入上比如Cyclone IV的CLK0/CLK1引脚否则PLL无法锁定后续所有时钟路径都会出问题。dac_data和ad_bus作为总线逐位写才能精确控制每根线的位置不建议写成set_location_assignment PIN_N25 -to dac_data[*]这种通配形式Quartus有时会把总线位序映射错逐位列出更可靠。4.2 时序约束给DA和AD设置输出延迟与输入延迟TimeQuest里DA侧要约束输出延迟AD侧要约束输入延迟。一份最小SDC如下create_clock -name sys_clk -period 20.000 [get_ports {clk}] # DA输出延迟FPGA内部寄存器到DAC数据引脚的路径 set_output_delay -clock sys_clk -max 2.0 [get_ports {dac_data[*]}] set_output_delay -clock sys_clk -min 1.5 [get_ports {dac_data[*]}] # AD输入延迟ADC输出引脚到FPGA内部寄存器的路径 set_input_delay -clock sys_clk -max 5.0 [get_ports {ad_bus[*]}] set_input_delay -clock sys_clk -min 0.0 [get_ports {ad_bus[*]}]说明-max 2.0表示DA数据最大外部延迟2 ns对应AD9708建立时间要求-min 1.5表示最小外延延迟1.5 ns对应保持时间。AD侧-max 5.0来自AD9280输出延迟典型值-min 0.0是数据无效窗口很短的保守设定。这些数值是起点上板后数据不稳定时优先调这里的窗口不要一上来改代码。SDC约束对应芯片参数初始值上板异常时调整方向set_output_delay -maxAD9708 tSu2.0 ns采样不稳则调小set_output_delay -minAD9708 tH1.5 ns保持时间不足则调大set_input_delay -maxAD9280 tOd5.0 ns读数出错则调小这个对应关系要写在工程注释里团队协作时别人改约束不会只看数字乱调。综合后在TimeQuest里看slackDA时钟路径如果出现负slack优先查ROM输出到dac_data_reg的组合逻辑而不是急着调时钟频率。4.3 ModelSim仿真常见报错与处理仿真报错是新手卡最久的地方两个高频问题都和Nativelink配置有关。第一个是Quartus II 17.1启动ModelSim时报“failure to obtain a Verilog simulation license”环境变量LM_LICENSE_FILE没有指向ModelSim的许可证文件或者被其他EDA软件的环境变量覆盖。第二个是“cannot launch ModelSim”常见原因是安装路径含中文或空格以及Tools Options EDA Tool Options里填的路径与实际安装目录不一致。处理顺序固定为先确认ModelSim安装目录下有win32aloem子目录再检查系统环境变量里有没有多个LM_LICENSE_FILE只保留ModelSim那一个最后回到Quartus里重新选择一次ModelSim路径并应用。仿真波形里数据全是X态大多数不是代码问题而是testbench复位没有拉低再拉高或者ROM没有初始化。给一个最小testbench模板timescale 1ns/1ps module tb_adda; reg clk 1b0; reg rst_n 1b0; wire [7:0] ad_bus; always #10 clk ~clk; // 50 MHz initial begin #100 rst_n 1b1; // 先复位100ns再释放 #2000 $finish; end // 用行为模型模拟AD9280: 数据相对encode延迟5拍 adda_top u_dut ( .clk (clk), .rst_n (rst_n), .ad_bus (ad_bus) ); endmodule关键点在#100 rst_n 1b1如果testbench不复位所有寄存器保持不定态ROM初始值也不会被正确加载。仿真正确通过后再用SignalTap上板上板异常时先回到这一节查复位和时钟不要直接怀疑芯片。5. 上板验证用SignalTap和直方图检查AD9708与AD9280回环数据质量最后要验证的不再是波形像不像正弦而是数据质量是否足够好。把SignalTap采样时钟设为DA时钟触发条件配成接收FIFO非空抓满1024个样本导出CSV用脚本做直方图统计。DAC中点设为128时输入正弦波的直方图应为标准U形最高点在0和255两端中央最低。若直方图在某个码值附近出现明显缺齿或者某一段计数接近0说明存在丢码或偶发漏采。缺齿定位的顺序先看AD数据有无复位残留即是否有固定值持续多拍再看fifo_wr是否存在单周期毛刺毛刺会把真实数据覆盖最后回去调整输入输出延迟约束。缺齿集中在0或255端说明模拟信号削波应衰减模拟幅度而不是改Verilog这里最容易做无用功。更精确的对齐技巧是用脉冲序列代替正弦波发送一个只在第512点有脉冲、其余全部为128的测试序列回环后用SignalTap搜这个脉冲的位置两者相减得到发送到接收的总延迟拍数把LOOP_DELAY填成这个精确值。这一步做完正弦波回环对齐就变成确定性操作不需要靠肉眼在波形图上数格子。最后用频率扫描验证系统稳定性把DA时钟分别设为10 MHz、25 MHz、50 MHz每个频率点跑5分钟并记录错误点数。高频时错误率显著上升大概率是建立时间或保持时间余量不足回到SDC把set_output_delay -max调小并重新综合再测一轮直到三个频率点错误率在同一量级。这个流程做下来能过滤掉九成“代码看着没问题但上板不对”的情况剩下的原因通常回到模拟链路幅度匹配和电源纹波上。本文还有配套的精品资源点击获取
