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 数据路径:信号怎么流
数据路径就是信号从被测节点到存储单元的“高速公路”。我把它画成了一张图,你一看就明白。
你看,数据路径其实不复杂。关键点在于:采样寄存器是第一个关卡,它决定了你能看到什么质量的信号。
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上搭一个最简单的版本跑一跑。纸上得来终觉浅,绝知此事要躬行嘛。