FPGA读写AD9708与AD9280的ADDA回环实验:从时序到Verilog实现
2026/9/15 23:33:26 网站建设 项目流程

简介:这套FPGA读写AD9708+AD9280的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位并行接口,但读写两侧的约束方向完全相反:对AD9708,FPGA是主设备,要在时钟锁存边沿之前把数据和地址稳定住;对AD9280,FPGA是从设备,要清楚转换启动后数据何时有效。方向搞反,后面仿真和上板都是白做。

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 <= 8'd128; // 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地址;8'd128是DAC中点,AD9708单极性输出时对应模拟0V,双极性偏置时中点要换成对应码值。多出来的代价是输出相位延迟一个时钟周期,回环实验不在意,但系统级对齐时需要给AD侧补偿同样拍数。

2.2 AD9280的转换启动与输出流水延迟

AD9280是流水线结构ADC,ENCODE上升沿触发一次采样,但输出引脚不会同一时刻更新,要等固定的流水延迟,典型值约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 <= 3'd0; ad_d_valid <= 1'b0; end else begin if (ad_wait_cnt == 3'd4) begin // 等满5拍后采数据 ad_data <= ad_bus; ad_d_valid <= 1'b1; ad_wait_cnt <= 3'd0; end else begin ad_wait_cnt <= ad_wait_cnt + 1'b1; ad_d_valid <= 1'b0; end end end

逻辑说明:ad_wait_cnt从0计到4,跳过第1到第4拍,在第5个时钟沿采ad_busad_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框架,分三块:相位累加器生成正弦波查表地址驱动AD9708;AD9280采样启动与读取状态机;把发送样本和接收样本放进同一份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 <= 10'd0; else phase <= phase + 10'd1; // 步进为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 <= 8'd128; else sine_out <= rom_q; // 打一拍出去 end endmodule

逻辑说明:phase是10位计数器,每拍自增1;取phase[9:2]作为ROM地址,等效波形点数256。sine_out对ROM输出再寄存一次,满足AD9708建立时间。参数说明:若DA时钟50 MHz,256点正弦波输出约195 kHz;要改频率就把自增改成步进值phase <= phase + phase_inc;,频率公式为f_out = f_clk * phase_inc / 2^N,N是累加器位宽。

phase_inc输出频率(50 MHz,N=10)每周采样点数
148.8 kHz1024
297.7 kHz512
4195.3 kHz256

表格里的每周采样点数由N和phase_inc共同决定,当点数少于32时,重建正弦波会看到明显台阶,回环误差随之增大,此时应该提高DAC时钟而不是继续加大步进。

3.2 AD侧读取状态机:从启动转换到数据入FIFO

AD9280的ENCODE可连续触发转换,读侧只需要等待5拍后取数。为了不让读取阻塞主时钟,把取到的数据写进同步FIFO,FIFO可以用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 <= 1'b0; wait_cnt <= 3'd0; sample_valid <= 1'b0; fifo_wr <= 1'b0; end else begin encode <= 1'b1; // 持续转换 if (sample_valid) begin fifo_wdata <= ad_bus; // 采到的数据入FIFO fifo_wr <= 1'b1; sample_valid <= 1'b0; end else if (wait_cnt == PIPE_DELAY[2:0] - 3'd1) begin wait_cnt <= 3'd0; sample_valid <= 1'b1; end else begin wait_cnt <= wait_cnt + 1'b1; 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_dataad_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里看slack,DA时钟路径如果出现负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 = 1'b0; reg rst_n = 1'b0; wire [7:0] ad_bus; always #10 clk = ~clk; // 50 MHz initial begin #100 rst_n = 1'b1; // 先复位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 = 1'b1:如果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调小并重新综合,再测一轮直到三个频率点错误率在同一量级。这个流程做下来,能过滤掉九成“代码看着没问题但上板不对”的情况,剩下的原因通常回到模拟链路幅度匹配和电源纹波上。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询