尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

AD7616 FPGA驱动设计与仿真验证:从SPI时序到工程落地

AD7616 FPGA驱动设计与仿真验证:从SPI时序到工程落地 简介面向FPGA开发者的AD采集设计资源以16位DAS芯片CL1616与AD7616为核心覆盖硬件模式下的双路同步采样、并行数据输出、±10V模拟输入范围以及62.5MHz工作时钟配置。工程采用Block模块化设计提供实测可用的AD转换工程与读取AD采集值的仿真工程适合需要快速搭建多通道同步采集系统的读者参考。压缩包共467个文件大小87.71MB主要包含Verilog/VHDL源码v、vhd、vhdl、Vivado工程文件xpr、xci、dcp、约束文件xdc、仿真脚本tcl、sh、bat以及CL1616数据手册和详细设计说明目录按功能划分便于查找。已有296人浏览学习实测工程可直接用于硬件验证仿真工程有助于理解采集时序适合在FPGA平台上开展高精度多通道数据采集开发的工程师。1. 给AD7616这类ADC写FPGA驱动最容易翻车的不是RTL逻辑而是仿真准备很多工程师拿到AD7616或CL1616第一反应就是照着数据手册里的时序图写SPI状态机综合过了就赶紧上板。结果ILA一抓数据要么整体偏移一位要么配置的量程波段根本没生效更常见的是所有通道读回来的值一模一样只能在波形里一个bit一个bit地数时钟沿一个晚上就这么搭进去。这类问题如果提前准备一套和RTL工程同步的仿真工程大半都可以在综合之前暴露出来。本文要讲的是一条能直接落地的FPGA AD采集设计路径从AD7616和CL1616的SPI接口时序入手把AD采集工程文件的模块划分、仿真工程的搭建方式、时序约束和排错手段串起来。适合刚接触FPGA数据采集的入门者也适合那些“仿真过了但上板就挂”的工程师对照排查。2. 先啃数据手册AD7616与CL1616的接口时序和管脚规划2.1 决定RTL结构的五组关键参数AD7616是16通道、16位、双极性输入的同步采样ADC支持4线SPI从机模式也支持并行接口CL1616在多数国产替代方案里和AD7616管脚兼容时序模型基本对齐。写RTL之前不需要背完整本手册但下面五组参数必须从手册时序图里确认它们直接决定状态机怎么写。参数典型值量级对RTL的影响通道数16通道差分/单端决定突发读模式下的帧数分辨率16 bit决定单通道移位寄存器的位宽最大SCLK频率数十MHz量级以手册为准决定分频系数和约束周期转换时间数微秒量级状态机里需要插入等待周期配置寄存器格式16 bit指令决定通道路由和量程配置的编码方式确认这些参数时有个常见误区只看表格里的最大值不看时序图里的建立保持时间。AD7616这类ADC在串行模式下数据是在SCLK下降沿更新的FPGA在上升沿采样那么板级走线的长度差异会直接吃掉建立时间余量。这个余量在第5章的set_input_delay约束里会体现但现在就要在管脚规划阶段给数据线留出走线等长空间否则后期时序收敛会很痛苦。2.2 串行读的三种时序模式单通道、突发、菊花链AD7616的SPI接口支持三种读法工程里用哪种取决于你要采集的通道数。单通道读是最基础的模式CS_n拉低FPGA发出通道配置指令然后连续发SCLK把该通道的16位转换结果移出来。这种模式适合只采集特定几路信号的场景但每次读通道都要重新配置效率不高。突发读是AD采集工程里最常用的模式。CS_n一次只拉低一次配置好起始通道后连续发16个SCLK读出一个通道再发16个SCLK读出下一个通道直到把16个通道全部移完。这里要注意AD7616突发读的通道顺序是按编号递增的如果只要其中8个通道也必须按完整突发帧读然后在上层把不需要的通道丢弃FPGA侧不能中途把SCLK停掉否则后续通道数据错位。菊花链模式则适合多片ADC级联。CL1616如果支持菊花链可以把一片的DOUT接到下一片的DIN最后只留一根数据线回到FPGA。这样能省IO但代价是每帧数据的延迟累加。做FPGA资源评估时菊花链模式对DSP资源几乎没有影响但状态机的移位寄存器深度会变成“通道数乘以16乘以级联片数”这个在工程文件里要提前预留否则后续扩展通道数时RTL要大改。2.3 Bank电压和管脚规划FPGA IO的第一道坎AD7616的数字IO通常在3.3V电平FPGA侧如果选的是HP bank1.8V默认直接连接就有电平风险。工程文件里管脚规划这一步我一般按下面的表来约束信号方向管脚规划要点SCLKFPGA输出选普通IO驱动强度调到8mA左右走线尽量短CS_nFPGA输出普通IO和SCLK保持等长DOUTADC输出约束输入延时走线不和高速并行总线并排DINFPGA输出只在配置阶段使用对时序不敏感电平匹配上最简单可靠的做法是加一颗电平转换芯片或者把FPGA的对应bank电压配到3.3V。如果选的是国产FPGA高云、安路这类bank电压配置方式和Xilinx略有差异要提前在管脚约束文件里确认IO standard否则综合过了布局布线也会在IO planning阶段报错。这个问题在“fpga入门”阶段特别常见因为开发板上的ADC往往已经帮你做了电平转换自己画板子时才会暴露。确认完这些RTL的边界条件才算清晰。接下来就可以动手做工程文件拆分了。3. 工程文件怎么组织从顶层到SPI状态机的模块划分3.1 六层结构从FPGA管脚到数据上报一个能复用的AD采集FPGA工程我不会只放一个SPI主控模块而是会按数据流方向拆成六层。这种结构的好处是换ADC型号时只动SPI控制器换处理器接口时只动寄存器桥不用全盘重写。层次模块名职责顶层ad7616_top管脚声明、时钟复位分发、模块例化时钟复位clk_rst_gen生成SPI工作时钟和模块复位时序寄存器桥reg_bridge维护配置寄存器向SPI控制器下发指令SPI控制器spi_master产生CS_n/SCLK收发DIN/DOUT数据缓冲data_fifo缓存转换结果处理跨时钟域上报接口user_interfaceAXI-Lite或自定义并口供处理器读取实际工程文件里每一层对应一到两个文件命名上建议带上模块前缀例如ad7616_spi_master.v、ad7616_data_fifo.v。这样“fpga项目”的层次一眼能看清后续做代码评审和资源评估也容易按模块统计LUT和FF的占用。3.2 写一个可综合的SPI状态机五个状态够用SPI控制器是AD采集工程的核心我用一个五状态状态机来覆盖“配置读数据”两个阶段module ad7616_spi_master #( parameter CHANNEL_NUM 16, parameter DATA_WIDTH 16 )( input wire clk, input wire rst_n, // 用户侧接口 input wire start, input wire [15:0] cfg_data, output wire busy, output wire spi_done, // SPI物理接口 output reg spi_cs_n, output reg spi_sclk, output reg spi_din, input wire spi_dout ); localparam IDLE 3d0; localparam CFG 3d1; localparam WAIT 3d2; localparam READ 3d3; localparam DONE 3d4; reg [2:0] state, next_state; reg [4:0] bit_cnt; reg [4:0] ch_cnt; reg [15:0] shift_reg; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; spi_cs_n 1b1; spi_sclk 1b1; end else begin state next_state; end end always (*) begin next_state state; case (state) IDLE: if (start) next_state CFG; CFG: if (bit_cnt 5d16) next_state WAIT; WAIT: if (wait_cnt WAIT_CYCLES) next_state READ; READ: if (ch_cnt CHANNEL_NUM bit_cnt DATA_WIDTH) next_state DONE; DONE: if (!start) next_state IDLE; endcase end这段代码的核心设计点有两个。第一状态机采用三段式输出逻辑留给组合电路单独处理方便后续加时序例外约束。第二WAIT状态用来覆盖AD7616的转换时间配置寄存器更新后不能立即发起读否则读到的是上一次转换的陈旧数据。参数方面CHANNEL_NUM和DATA_WIDTH都做成了parameter换用CL1616或者调整采集路数时不用改状态机主体。bit_cnt计数到16时完成一帧移位ch_cnt计数到16时说明16个通道全部读完。如果只想读8个通道不要试图在这个模块里截断而是在DONE之后配置成只回读部分通道如果ADC本身不支持部分通道突发读那就在上层丢弃多余帧。3.3 寄存器表不是所有项目都要上软核很多工程师一接到“FPGA AD采集”就想挂一个MicroBlaze或RISC-V软核上去用处理器轮询SPI。这在通道少、速率低的场景下确实好用但AD7616的16通道突发读一次就有256 bit数据软核搬数据的速度跟不上时瓶颈就变成了总线带宽。更常见的做法是直接硬逻辑处理再用一组寄存器桥暴露给外部处理器。寄存器表我一般这样规划偏移地址读/写位宽描述0x00RW16全局控制bit0软复位bit1采样启动0x04RW16通道配置对应16路通道的使能位0x08RW16量程配置每2 bit对应一路通道的增益档位0x10RO256采样结果按通道编号顺序排列寄存器桥本身也是标准写法地址译码、写寄存器堆、读多路选择器。写完SPI状态机和寄存器桥RTL主体就完成了一大半。但在这时候先别急着上板接下来要搭的是标题里提到的另一块内容——独立的仿真工程。4. 仿真工程从零搭一套SPI从机模型和激励4.1 为什么仿真工程必须和RTL工程分开数据采集工程的仿真和普通逻辑仿真不一样。普通逻辑仿真是FPGA自己和自己玩输入激励是人为给定的理想信号AD采集仿真是FPGA和一个“虚拟ADC”在通信必须有一个行为模型来模拟AD7616的SPI从机行为包括CS_n有效后的最小延时、SCLK边沿移出数据的方式。如果只写一个简单的testbench把spi_dout当成常数或者RAM里预存的数组一股脑丢出去那只能验证SPI移位寄存器能不能把数据收进来验证不了时序关系的正确性。所以我会在工程目录里单开一个sim文件夹RTL文件用filelist/include方式引用仿真文件独立维护。4.2 搭一个能应答的SPI从机模型testbench代码下面是一个简化的AD7616 SPI从机模型核心机制是检测到CS_n下降沿后在每个SCLK的有效边沿把预置数据的一位送到spi_dout上。module ad7616_slave_model #( parameter DATA_WIDTH 16 )( input wire spi_cs_n, input wire spi_sclk, input wire spi_din, output reg spi_dout ); reg [DATA_WIDTH-1:0] mem [0:15]; // 虚拟ADC的16个通道数据 reg [4:0] bit_cnt; reg [4:0] ch_idx; reg [DATA_WIDTH-1:0] shift_buf; initial begin for (int i 0; i 16; i i 1) mem[i] 16h1000 i * 16h0110; // 预置一组递增pattern end always (posedge spi_sclk or negedge spi_cs_n) begin if (!spi_cs_n) begin bit_cnt 0; ch_idx 0; spi_dout mem[0][DATA_WIDTH-1]; end else begin spi_dout mem[ch_idx][DATA_WIDTH-1-bit_cnt]; bit_cnt bit_cnt 1b1; if (bit_cnt DATA_WIDTH - 1) begin bit_cnt 0; ch_idx ch_idx 1b1; if (ch_idx 15) ch_idx 0; end end end endmodule这个模型的行为和真实ADC不完全一样真实ADC在CS_n拉低后还需要等待t2时间才开始输出模型里默认CS有效后第一个SCLK上升沿就有数据。两者的差别正好用来验证FPGA端状态机在CS_n之后有没有额外等待。如果FPGA端设计正确真实ADC的时间参数会更宽松模型反而更苛刻仿真过了上板时序至少不会在这一点上翻车。testbench里例化DUT和这个从机模型启动一次start信号然后等待spi_done拉高。仿真完成后把收到的数据用$display打印出来和预置的递增pattern做对比。这一步能快速确认三个问题移位寄存器方向是否反了、高低字节是否颠倒、突发读的通道顺序有没有错位。4.3 从仿真波形到在线验证仿真怎么跑、结果怎么复核Vivado环境下仿真工程挂在sim_1 fileset里RTL文件用source文件组引入。跑仿真用下面的命令set_property top tb_ad7616 [get_filesets sim_1] launch_simulation -mode behavioral run -all open_wave_config ad7616_wave.wcfg跑完之后不要只看spi_dout那根线的波形那根线一根线跳来跳去看不出对错。打开波形后把数据总线和状态机状态信号一并加入按spi_done信号做标记对比每一次突发读周期内16个通道的数据是否符合预期。这种验证方式在“fpga应用”的调试里叫总线级验证比单纯看单bit波形高效得多。仿真通过了再生成比特流上板。上板之后用ILA抓SPI总线和用户侧数据把上板抓到的数据包和仿真波形对比。如果完全一致说明RTL时序和真实器件对齐了如果不一致优先查时钟域和管脚约束而不是回头改SPI状态机。5. 时序约束、校准与三个隐藏排错点5.1 输入延时约束set_input_delay怎么给SPI总线的SCLK是FPGA内部生成的所以对DOUT这条输入路径做时序约束不能直接引用系统时钟要定义一个虚拟时钟来描述SCLK的周期。create_clock -name vclk_adc -period 40 set_input_delay -clock vclk_adc -max 6.0 [get_ports spi_dout] set_input_delay -clock vclk_adc -min 1.0 [get_ports spi_dout] set_output_delay -clock vclk_adc -max 5.0 [get_ports {spi_sclk spi_cs_n spi_din}] set_output_delay -clock vclk_adc -min 0.0 [get_ports {spi_sclk spi_cs_n spi_din}]上面是简单的虚拟时钟约束。由于SCLK到AD7616再到DOUT回FPGA路径上存在PCB走线延时set_input_delay的max值要包含这些因素。max 6.0、min 1.0是经验初值具体数值由板级长度算出来。min值尤其不能填0如果填0可能把输入端的hold violation掩盖掉。5.2 上板校准偏置、增益、有效位三件事设计跑通之后AD采集还有一个绕不开的步骤校准。先把ADC输入端短接到GND记录偏移量再输入一个已知幅值的直流电压算增益误差最后用正弦波源测有效位。工程文件里要预留这三项的寄存器位否则每次换板子都要改RTL重新综合。做有效位测试时要注意AD7616是16位ADC但有效位受前端调理电路影响很大一般能跑到13到14位就算不错标称16位是没意义的。5.3 三个不容易查的坑第一个坑CS_n拉低后立刻启动SCLK。AD7616从CS有效到数据真正出现在DOUT上有最小延时要求如果FPGA端CS和SCLK严格同时发出读到的首bit其实是无效位。解决方法是CS_n拉低后空等数个SCLK周期再开始采集这个等待在仿真模型里可能正好被掩盖因为模型是理想输出。第二个坑配置通道后没有等待转换时间。寄存器配置位宽、量程档位生效需要时间紧跟着读数据读到的还是上一次转换结果。这个坑在调试里的表现是“所有通道读数都正常但都慢了一拍”。第三个坑仿真和上板结果对不上怀疑FPGA逻辑有X态传播。这大概率是反馈信号没有寄存器打拍或者FIFO复位时序不对。AD采集工程的复位建议用异步复位同步释放并给SPI控制器单独做一个复位延时保证上电时SCLK不会在CS_n无效时乱翻。这三个点都排掉之后AD采集工程的基本盘就稳了。后续如需扩展通道数再按本文第3章的模块边界去加例化即可。本文还有配套的精品资源点击获取
返回列表