3、片上逻辑分析仪架构:核心模块、数据路径与时钟域

好,咱们今天聊聊片上逻辑分析仪的内部骨架。说白了,就是搞清楚它由哪几块拼起来的,数据怎么在里面跑,以及最让人头疼的时钟域问题。

我刚开始接触这个架构时,觉得不就是个抓波形的工具嘛,能有多复杂?结果第一次自己搭触发逻辑,就被跨时钟域的问题折腾得够呛。嗯,咱们一步步来拆解。

3.1 三大核心模块:触发、存储、控制

一个片上逻辑分析仪,不管多花哨,核心就三个模块:触发单元、存储单元和控制单元。它们各司其职,配合起来才能完成一次完整的捕获。

模块 核心职责 我踩过的坑
触发单元 实时监测信号,匹配预设条件,产生触发信号 触发条件写得太复杂,导致时序收敛困难
存储单元 循环采样并存储数据,触发后保存有效记录 深度不够,抓不到关键时序窗口
控制单元 管理状态机、配置寄存器、协调读写操作 状态机漏写了空闲态,仿真时直接跑飞

3.1.1 触发单元

触发单元是分析仪的“眼睛”。它时刻盯着你指定的信号,一旦满足条件,就喊一声“停!”。

我个人习惯把触发单元分成两级:

  • 一级触发(简单匹配):比较器直接对比信号值与预设值。比如检测到 data_bus == 0xA5。
  • 二级触发(序列/计数器):支持多级事件序列。比如“先等A信号拉高,再等B信号出现下降沿,最后计数到10次后触发”。
我的小技巧:触发条件尽量用组合逻辑快速判断,别在触发路径上插太多寄存器。我曾经为了省资源,把触发判断做成了多级流水,结果触发时刻比实际事件晚了两个时钟周期,抓到的数据全偏了。

3.1.2 存储单元

存储单元就是分析仪的“记忆”。它通常是一个环形缓冲器(Ring Buffer),一直在写,直到触发信号到来才停止。

这里有个关键参数:存储深度。深度越大,能捕获的连续样本就越多。但代价是面积和功耗。

// 典型的环形缓冲器控制逻辑(伪代码)
always @(posedge clk) begin
    if (wr_en) begin
        mem[wr_ptr] <= sampled_data;
        wr_ptr <= wr_ptr + 1;
        if (wr_ptr == DEPTH-1)
            wr_ptr <= 0;
    end
    if (triggered) begin
        // 记录触发时的写指针位置
        trig_pos <= wr_ptr;
        // 停止写入,或继续写入指定数量的后样本
    end
end
注意:存储单元的读写时钟域必须统一。如果采样时钟和存储时钟不同,一定要做异步处理。我见过一个项目,因为偷懒没做跨时钟域同步,结果抓到的数据里偶尔会出现“毛刺”或“跳变”,排查了整整两天才发现是亚稳态问题。

3.1.3 控制单元

控制单元是分析仪的“大脑”。它管理着整个捕获流程:空闲、等待触发、触发后捕获、读出数据。

我建议用三段式状态机来写控制逻辑,清晰且不易出错。

// 状态机示例
typedef enum logic [1:0] {
    IDLE,
    ARM,
    CAPTURE,
    READOUT
} state_t;

state_t current_state, next_state;

// 状态转移
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) current_state <= IDLE;
    else        current_state <= next_state;
end

// 次态逻辑
always @(*) begin
    case (current_state)
        IDLE:    next_state = arm_signal ? ARM : IDLE;
        ARM:     next_state = trigger_hit ? CAPTURE : ARM;
        CAPTURE: next_state = capture_done ? READOUT : CAPTURE;
        READOUT: next_state = read_done ? IDLE : READOUT;
        default: next_state = IDLE;
    endcase
end

3.2 数据路径:信号怎么流

数据路径就是信号从被测节点到存储单元的“高速公路”。我把它画成了一张图,你一看就明白。

片上逻辑分析仪数据路径 被测信号 采样寄存器 触发单元 存储单元 控制单元 触发信号 写使能/地址 数据路径说明: 1. 被测信号先被采样寄存器同步到采样时钟域 2. 触发单元实时监测采样后的信号,产生触发信号 3. 控制单元根据触发信号,控制存储单元的写入与停止

你看,数据路径其实不复杂。关键点在于:采样寄存器是第一个关卡,它决定了你能看到什么质量的信号。

3.3 时钟域:最容易翻车的地方

时钟域处理,是片上逻辑分析仪设计里最容易出bug的地方。没有之一。

你想想看,分析仪要捕获的信号,可能来自不同的时钟域。比如CPU总线是100MHz,外设接口是50MHz,还有一个异步的GPIO信号。这些信号要统一送到分析仪里,怎么处理?

3.3.1 单时钟域 vs 多时钟域

方案 优点 缺点 适用场景
单时钟域 设计简单,无亚稳态风险 只能捕获同频信号,灵活性差 小规模、同频系统
多时钟域(异步FIFO) 可捕获任意频率信号 需要同步器,面积大,延迟不确定 复杂SoC、混合信号系统
过采样+同步器 实现简单,通用性强 对高频信号可能漏采 中低速信号、调试阶段

3.3.2 我的推荐做法

我个人习惯的做法是:所有输入信号先经过两级同步器,再进入分析仪内部。虽然会多两个时钟周期的延迟,但能彻底消除亚稳态。

// 两级同步器模板
module sync_2ff #(
    parameter WIDTH = 1
)(
    input  wire             clk,
    input  wire             rst_n,
    input  wire [WIDTH-1:0] async_in,
    output wire [WIDTH-1:0] sync_out
);

reg [WIDTH-1:0] sync_reg1, sync_reg2;

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        sync_reg1 <= {WIDTH{1'b0}};
        sync_reg2 <= {WIDTH{1'b0}};
    end else begin
        sync_reg1 <= async_in;
        sync_reg2 <= sync_reg1;
    end
end

assign sync_out = sync_reg2;

endmodule
核心原则:永远不要用组合逻辑直接采样异步信号。哪怕仿真跑一万次都没问题,流片回来也可能翻车。亚稳态不是概率问题,是时间问题。

3.3.3 跨时钟域的数据传输

如果分析仪需要把捕获到的数据跨时钟域读出(比如从采样时钟域传到调试接口时钟域),那就得用异步FIFO了。

我记得有一次,我为了省事,直接用握手信号做跨时钟域传输。结果在高速采样时,握手信号本身出现了亚稳态,导致数据丢失。后来老老实实换成了异步FIFO,问题才解决。

异步FIFO的核心是格雷码指针,它能保证指针在跨时钟域时最多只有一位变化,从而避免多比特同步的亚稳态问题。

// 格雷码转换
function [WIDTH-1:0] bin2gray;
    input [WIDTH-1:0] bin;
    bin2gray = (bin >> 1) ^ bin;
endfunction

3.4 小结

这一章咱们把片上逻辑分析仪的骨架搭起来了。触发单元负责“看”,存储单元负责“记”,控制单元负责“管”。数据路径要清晰,时钟域要谨慎。

嗯,这些基础打牢了,后面咱们才能聊更高级的触发策略和调试技巧。你先把这三个模块的关系理清楚,最好能在FPGA上搭一个最简单的版本跑一跑。纸上得来终觉浅,绝知此事要躬行嘛。