GOWIN 逻辑分析仪实战:探测 FPGA 内部信号
在 Tang Nano 20K 上用 GOWIN GAO 逻辑分析仪,结合 Verilog 时钟与同步设计,观察 LED 脉冲和串口数据。
前几篇文章里,我们分别用 GOWIN 官方工具链和开源工具在 Tang Nano 20K 上做了尝试。开源流程的好处是能在下载到芯片前仿真、调试设计;而 GOWIN 工具里其实也藏着一个上手简单、能力不错的逻辑分析仪。这次先不急着点“运行”,而是把待测的 Verilog 设计拆开看看,再把它接到 GAO 上。
准备 GAO 配置文件
GAO 是 GOWIN 的逻辑分析仪配置。新建 GAO 文件时,工具会问不少选项,最终这个文件会在综合时把分析仪嵌进设计,并定义触发条件。经验规则如下:
- 工程里同时只能有一个 GAO 文件处于激活状态。
- 在 IDE 的 Design 标签里右键文件,可以把它禁用。被禁用的文件会带上红叉,不参与后续构建;需要时再启用。这样就能在多个 GAO 配置或多个 Verilog 源文件之间切换,比如测试版和发布版。
- 从零新建 GAO 文件时如果出现“Lite Mode”选项,基本可以忽略它,直接选 Standard Mode。通常也应使用 RTL 模式,这样看到的主要是自己定义的符号,而不是处理阶段插入的内部名字。
完整的 GAO 手册可以在 GOWIN 官网 找到;文末的视频则演示了配置和抓取过程。
用 Verilog 描述可探测的设计
Verilog 容易让人误入歧途:它看起来像编程语言,但实际上是在描述硬件。最关键的差别之一是时钟。大多数实用数字系统都靠时钟协调动作;异步逻辑并非不能做,但设计难度高得多,同步逻辑才是主流。
为什么需要时钟
理想情况下信号瞬间变化,现实中却受走线长度、电容、器件差异、温度等影响,到达时间并不一致。可以想象一辆“过度安全”的汽车:车门打开且发动机运转时切断点火。门信号和发动机信号若在不同时间到达与门,就可能在你刚点火后立刻熄火。更常见的数字例子是两个数相加:一个从 11 变到 22,另一个从 5 变到 10,期望看到 16 和 32,实际却可能短暂出现 27 或 21 这样的中间结果。
时钟的作用就是给组合逻辑留出稳定时间,然后在时钟沿统一采样。每个设计都有最长路径,它通常决定最高时钟频率:如果某段逻辑需要 1 微秒才能可靠稳定,时钟周期就不能短于这个时间。
示例设计与 IP
本文的示例仍然很简单:在 20K 板上驱动 LED。输入时钟为 27 MHz,因此需要计数大量周期后偶尔翻转 LED。设计还会用 IP 核搭一个 UART:默认把收到的字符回显;按下板上的用户按钮后,进入“字符加一再发”的模式;再按一次恢复回显。无论处于哪种模式,按下按钮时都会先发送一个星号。
按钮信号不能直接进同步逻辑:它没有和时钟对齐,机械触点还会抖动。例如按下时可能产生一串快速通断,几毫秒后才稳定。示例里的 debounce 模块同时做消抖和时钟同步。
GOWIN 的 IP 生成器提供多种选择,并链接到对应文档。
UART 核原本更偏向与微处理器配合,这里为了简化用法做了一点“作弊”式连接。IP 可以从 Tools 菜单的 IP Core Generator 插入工程。
模块、端口与赋值
源文件开头常见这样一行:
default nettype none
它能让拼写错误立刻暴露。否则,像把 count 误写成 countt 这样的笔误,Verilog 可能不报错,而是默默创建一根新线。不过 GOWIN 的部分 IP 期望默认网络类型是 wire,所以在文件末尾还要恢复:
default nettype wire
led 模块通过 #() 暴露参数,复用时可以改时钟频率和闪烁间隔;仿真时就能用慢时钟和短间隔,而不必沿用板上的默认值。模块内部用 localparam 和 $clog2 计算计数器位宽。wire 用来连接,reg 则是 Verilog 变量,并不必然等于触发器。它们除了 0/1,还可能取 X(未知)或 Z(高阻)等值;SystemVerilog 的 logic 又是另一套写法,但读懂传统 Verilog 仍然很重要。
消抖模块的实例化大致如下:
debounce #(
.CLOCK_FREQUENCY (CLOCK_FREQUENCY),
.DEBOUNCE_MS (10)
) button_filter (
.Clock (Clock),
.reset (reset),
.noisy_button (pushbtn),
.clean_button (button)
);
这里把时钟频率和 10 毫秒消抖时间传进去,输出一个干净、已同步的按钮信号。assign 语句则像导线,把某个表达式直接接到 wire 上;UART 实例化虽然端口更多,本质上仍是把输入、输出和常量连起来。例如 uart_rx_ready_n 变低时,可以从 uart_rx_data 读取字符;发送侧也有类似的握手信号。
always 块与赋值方式
文件里还有几个 always 块。先看最简单的 LED 翻转逻辑:
always @(posedge Clock) begin
if (reset)
led_state <= 1'b0;
else if (interval_tick)
led_state <= ~led_state;
end
这段代码推断出一个在 Clock 上升沿触发的触发器:复位时把 led_state 清零,否则在 interval_tick 有效时翻转。led_state 只应在这个 always 块里赋值。更重要的是使用非阻塞赋值 <=,而不是阻塞赋值 =。
如果在时钟块里写 =,Verilog 会按顺序立即求值,可能推断出意料之外的锁存器,既不是想要的功能,也会让器件实现变得低效。非阻塞赋值则表达“在时钟沿采样旧值,块结束后统一更新”:同一个 always 块里读到的都是时钟沿之前的值,最后一次 <= 赋值生效。这样更接近实际触发器行为,也能避免奇怪的锁存结构。
亚稳态与同步
触发器并非理想器件,它有建立时间和保持时间。数据必须在时钟沿前的一段窗口内稳定,并在时钟沿后继续保持一段时间,否则触发器可能进入亚稳态,输出既非 0 也非 1,甚至短暂振荡。
数据必须在建立时间和保持时间构成的窗口内保持稳定。
逻辑路径的延迟会影响最高时钟频率;跨时钟域或异步输入则更危险。常见做法是用几级触发器对异步信号做同步,即使第一级偶尔进入亚稳态,也不至于把不确定状态传遍整个系统。前面提到的 debounce 模块就承担了同步和消抖两项工作。
开始探测
理解了设计里的时钟、计数器、按钮同步和 UART 握手,就可以把关心的信号接到 GAO 上。下面两张图展示了分析仪的输出:一张是翻转 LED 的脉冲,在 27 MHz 采样时钟下它相当窄;另一张是串口数据,使用的是专门放慢的调试时钟。
分段捕获的间隔逻辑,可以看到触发 LED 翻转的窄脉冲。
低速串行数据捕获,便于观察 UART 的位时序。
完整的配置与抓取过程见 演示视频,示例文件则放在 GitHub。下一步就可以按自己的设计改触发条件,把 GAO 变成排查时序问题的常用工具。
讨论
0 条评论