在高速数据采集系统中,ADC的灵活配置直接决定了系统能否稳定工作。TI的ADS54J60上电后需要严格按手册写入数十个寄存器,运行中还要根据频段切换奈奎斯特区配置。本文设计的adc_cfg_module以状态机+查找表为核心,配合可暂停计数、差分锁存防抖和VIO融合接口,实现了初始化、动态切换、写后校验与异常保护的一体化管理。
1. 模块顶层与接口
adc_cfg_module负责将上层控制意图转换为 SPI 总线时序,其顶层端口如下:
| 端口名 | 方向 | 位宽 | 功能说明 |
|---|---|---|---|
clk | I | 1 | 系统时钟 |
rst | I | 1 | 异步复位,高有效 |
sda_rx | I | 1 | SPI 数据输入(来自 ADC SDO) |
nyq_zone | I | 3 | 奈奎斯特区选择,3‘b000= 第1区 |
sck | O | 1 | SPI 时钟输出 |
csb | O | 1 | SPI 片选,低有效 |
sda_tx | O | 1 | SPI 数据输出(至 ADC SDI) |
tri_1i0o | O | 1 | 三态门方向控制:1=输入,0=输出 |
enable | O | 1 | 内部使能状态,异常时自动拉低 |
init_done | O | 1 | 初始化完成标志 |
参数定义(localparam):
parameter WIDTH_ADDR = 4‘d15, // 寄存器地址宽度 15bit WIDTH_DATA = 4’d8, // 数据宽度 8bit LEN_INIT = 8'd47, // 初始化序列长度 HEAD_NYQ_ZONE= LEN_INIT, // Nyquist区配置在LUT中的起始索引 LEN_NYQ_ZONE = 8'd3; // 每个Nyquist区寄存器个数2. 整体架构
模块内部由主状态机、配置查找表、SPI 控制器、差分锁存器及 VIO 调试电路组成,核心子模块的例化关系如下:
// 寄存器查找表,根据 cnt 输出 addr, data_wr, rw_ex, gap_clk define_ads54j60 #(...) define_ads54j60_u0 ( ... ); // SPI 读写控制器,支持可编程等待间隔 spi_interface #(...) spi_interface_u0 ( ... ); // nyq_zone 变化检测与锁存,配置期间锁定 lock_if_diff #(...) lock_if_diff_u0 ( ... ); // VIO 边沿检测(仅在 WITH_VIO 宏定义时生效) get_edge #(.SEL_1POS_0NEG(1'b1)) get_edge_u0 ( ... );顶层通过cfg_ing、temp_head、temp_len等组合逻辑动态切换当前任务:
assign cfg_ing = ~init_done | nyq_zone_ing; // 是否有配置任务在进行 assign temp_head = ~init_done ? 0 : HEAD_NYQ_ZONE; assign temp_len = ~init_done ? LEN_INIT : LEN_NYQ_ZONE;当init_done为低时,配置 0~46 号初始化寄存器;完成后若检测到nyq_zone变化,则配置索引 47~49 对应的 Nyquist 区寄存器。
3. 三状态主状态机
配置过程由一个简洁的 Moore 型状态机控制,状态定义如下:
parameter P_ST_IDLE = 3'b001; parameter P_ST_TX = 3'b010; parameter P_ST_CNT_INC = 3'b100;转移条件通过组合逻辑生成:
assign p_st_idle2p_st_tx_start = (state_c==P_ST_IDLE) && (idle && cfg_ing && ~temp_done && enable); assign p_st_tx2p_st_cnt_inc_start = (state_c==P_ST_TX) && (idle && cfg_ing && ~temp_done && enable); assign p_st_cnt_inc2p_st_idle_start = (state_c==P_ST_CNT_INC) && ((cnt == (temp_head+temp_len-1)) & ~cnt_suspend); assign p_st_cnt_inc2p_st_tx_start = (state_c==P_ST_CNT_INC) && (((cnt == (temp_head+temp_len-1)) & ~cnt_suspend) == 'd0);状态机工作流程:
- IDLE → TX:空闲且存在未完成任务时,启动一次 SPI 传输。
- TX → CNT_INC:立即进入计数递增状态(实际 SPI 传输由
spi_interface自行完成)。 - CNT_INC:若当前
cnt未达末尾或需暂停,则返回TX发送下一条命令;若达到末尾且无暂停,则回到IDLE,并通过temp_done脉冲指示一组任务完成。
4. 关键机制深度解析
4.1 地址计数器与暂停机制
计数器cnt在状态机控制下递增:
always @(posedge i_clk) begin if(i_rst) cnt <= 'd0; else if(idle && cfg_ing && temp_done == 'd0 && enable && state_c == P_ST_IDLE) cnt <= temp_head; // 启动时加载起始地址 else if(idle && cfg_ing && temp_done == 'd0 && enable && state_c == P_ST_CNT_INC && cnt_suspend == 'd0 && cnt != temp_head + temp_len - 'd1) cnt <= cnt + 'd1; // 正常递增 else cnt <= cnt; end暂停信号cnt_suspend的设计是整个模块的精髓,它支持两种复杂校验:
assign rd_not_equal = (data_rd != data_wr) & idle & (state_c == P_ST_CNT_INC) & rw; assign cnt_suspend = (if_rd_until & rd_not_equal) | (if_wr_check & ~check_ing); assign rw = rw_ex[0] | check_ing; assign if_wr_check = (rw_ex == 2'b10); // 写后需检查 assign if_rd_until = (rw_ex == 2'b11); // 读直到匹配rw_ex是寄存器查找表输出的扩展操作码,2’b10表示“写后检查”,2’b11表示“读直到相等”。- 对于写后检查,当
cnt_suspend生效且check_ing=0时,暂停计数并强制rw=1(读操作),同时将check_ing置 1,下一次状态机从 CNT_INC 跳回 TX 时便发起一次读操作,读回数据与期望值比较。若相等,rd_not_equal=0,暂停解除,check_ing清零并递增cnt。 - 对于读直到相等,逻辑更直接:只要读回数据不匹配,
cnt_suspend始终有效,状态机反复在同一地址发送读命令。
这一机制仅通过少量组合逻辑和寄存器,就在同一 SPI 控制器上实现了阻塞式校验,极大增强了配置的可靠性。
4.2 enable 信号与异常保护
enable不仅作为状态机使能条件,还兼任硬件自锁保护:
always @(posedge i_clk) begin if(i_rst) enable <= 1'd1; else if(idle && cfg_ing && temp_done == 'd0 && enable && state_c == P_ST_CNT_INC && addr == 15'h6001) enable <= (~rd_not_equal); // 关键寄存器校验失败则拉低 else enable <= enable; end当地址为15’h6001(例如 ADC 的某个状态寄存器)且读回值与写入期望值不一致时,enable被清零,状态机立即停摆,后续所有 SPI 事务全部终止。这实际上是一种“硬件看门狗”,可避免在配置出错时继续执行可能损害硬件的操作。
4.3 Nyquist 区动态切换与防抖
用户只需修改nyq_zone端口,模块即可自动完成对应寄存器组的重配置,核心由lock_if_diff模块实现:
lock_if_diff #(.WIDTH(3), .PARALLEL(1)) lock_if_diff_u0 ( .clk (clk), .rst (rst), .signal (nyq_zone), .lock_diff (nyq_zone_diff), // 变化指示 .signal_lock(nyq_zone_lock), // 稳定的锁存值 .unlock (temp_done & nyq_zone_ing) // 配置完成且处于nyq任务时才解锁 );当nyq_zone跳变且当前无其他配置任务(cfg_ing == 0)时,nyq_zone_ing拉高,状态机启动并索引 47~49 号寄存器。整个过程中nyq_zone_lock保持不变,直到temp_done脉冲到来,unlock才允许锁存器更新值,有效防止了输入抖动引起的多次重配。
5. VIO 在线调试的硬件融合
为使调试更灵活,模块通过宏WITH_VIO集成了 Vivado Virtual I/O,在spi_interface例化时进行动态选择:
spi_interface #(...) spi_interface_u0 ( ... `ifdef WITH_VIO .rw (EN_VIO ? RW_VIO : rw ), .addr (EN_VIO ? ADDR_VIO : addr ), .data_wr (EN_VIO ? DATA_WR_VIO : data_wr), `else .rw (rw), .addr (addr), .data_wr (data_wr), `endif ... );VIO 核提供EN_VIO、RW_VIO、ADDR_VIO、DATA_WR_VIO等虚拟端口,读回数据DATA_RD_VIO可实时观测。通过get_edge捕获EN_VIO上升沿,产生单周期valid_tx脉冲,即可在自动配置的间隙或完全掌控总线,手动读写任意寄存器。这种“自动+手动”双模设计在板级调试中极为高效。
6. 总结与复用建议
adc_cfg_module将复杂的 ADC 配置时序抽象为地址-数据-操作码表格,通过三状态机和灵活的暂停校验机制,轻松应对初始化、动态频段切换、异常保护等需求。在实际项目中,您只需根据 ADC 手册修改define_ads54j60中的查找表,并调整顶层参数,即可复用于其他 SPI 接口器件。
关键的设计亮点包括:
- 表驱动:所有寄存器操作序列固化在 LUT 中,维护便捷。
- 可暂停计数:用
cnt_suspend+check_ing实现了写后校验和轮询等待,无需额外状态。 - 硬件自锁:
enable信号在关键寄存器校验失败时自动关闭,提高安全性。 - VIO 共融:通过宏和选择器,在不影响主逻辑的前提下无缝加入手动调试通道。
希望本文的代码剖析能为从事高速数据采集的 FPGA 工程师提供可借鉴的参考,如有任何疑问或改进建议,欢迎在评论区交流!
附:WITH_VIO宏与在线调试机制
WITH_VIO是一个条件编译宏,通常在头文件with_vio.h中通过`define WITH_VIO进行声明。当该宏被定义时,预编译器会在spi_interface的输入路径中例化一个 Vivado Virtual I/O(VIO)核,并在地址、读写控制和写数据信号上插入选择器,实现 VIO 手动控制通路与自动状态机通路的切换。若WITH_VIO未被定义,所有 VIO 相关逻辑均被综合工具移除,系统仅保留自动配置功能。
VIO 核提供五个虚拟端口:EN_VIO(使能)、RW_VIO(读写选择)、ADDR_VIO(寄存器地址)、DATA_WR_VIO(写数据)以及DATA_RD_VIO(读数据)。其中DATA_RD_VIO用于实时观测从 ADC 读回的寄存器值。工作方式如下:在 Hardware Manager 中置高EN_VIO,VIO 内部边沿检测电路(get_edge)将生成一个周期的valid_tx脉冲,触发spi_interface根据RW_VIO、ADDR_VIO和DATA_WR_VIO的状态发起一次 SPI 读写操作;读回数据经spi_interface输出后直接连接至DATA_RD_VIO,供在线监测。
该设计在自动配置与手动旁路之间实现了硬件级无缝切换,无需反复修改 RTL 代码即可完成单寄存器调试或初始化序列验证,显著提升了板级调试效率。当调试结束,去除宏定义并重新综合,即可复原纯净的自动配置电路。