ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

FPGA逻辑分析仪:VGA直显8通道数字波形设计

FPGA逻辑分析仪:VGA直显8通道数字波形设计

简介:本资源是一套基于FPGA实现的简易逻辑分析仪完整工程源码,面向数字电路与嵌入式系统初学者、FPGA课程设计学生及硬件开发入门者,用于理解时序采集、触发控制与VGA实时显示等核心概念。工程支持16路数字信号采样,提供开关可调的触发模式与多档采样频率,并通过VGA接口将波形时序直观呈现在显示器上,是软硬协同调试的典型教学实践案例。压缩包共106个文件,含18个Verilog源码(.v)、13个Quartus时序分析文件(.tdf)、7个编译报告(.rpt)、4张原理图/界面截图(.jpg)及多个ROM初始化文件(.mif)、约束文件(.sdc)和配置脚本(.qsf),结构完整,便于综合、布局布线与上板验证。资源大小仅1.71MB,轻量易用;已有951人学习下载,附带readme说明与典型模块(如topic_rom、char_rom、logic_util_heursitic.dat)的工程组织逻辑,适合边学边练、快速复现与二次开发。

1. 把FPGA当示波器用:一个能真正在VGA屏上跑起来的逻辑分析仪源码包

你有没有试过在调试一块自研的SPI从机时,手边只有万用表和一块没接JTAG的开发板?信号跳变太快,万用表读不出边沿,示波器又不在工位——这时候,一块带VGA输出的FPGA开发板,配合一份能直接烧录、不改行就能看波形的逻辑分析仪源码,就是你的临时示波器。这不是概念演示,也不是只跑仿真波形的demo,而是实打实能在Xilinx Spartan-6或Artix-7系列板卡上,通过VGA接口实时显示8路数字信号、支持触发、可调采样深度(最高1K点)、带时基缩放和通道使能控制的完整工程。它不依赖PC上位机,所有采集、触发、显示逻辑全在FPGA内部闭环完成;VGA分辨率固定为640×480@60Hz,时序严格符合VESA标准,避免黑屏、撕裂、偏移等玄学问题。适合嵌入式硬件工程师做板级联调、FPGA初学者理解状态机与跨时钟域协同、以及教学场景中让学生亲手“看见”数字信号的跳变过程。如果你正卡在“信号到底有没有来”“边沿对不对”“时序是不是超了”这类问题上,这份源码不是玩具,是能立刻插上线、按下复位、盯着屏幕找问题的工具。


2. 为什么选这个架构:三模块解耦设计 + VGA直驱逻辑

这份源码不是把Verilog写成C语言那样堆逻辑,而是明确划分为三个功能边界清晰、时钟域隔离严格的子系统:采样控制模块(Sampling Controller)、触发判定模块(Trigger Engine)和VGA显示合成模块(VGA Renderer)。这种划分不是为了炫技,而是解决实际工程中最容易翻车的三个痛点:采样时钟与VGA像素时钟异步导致数据错位、触发条件误判引发波形冻结、显示刷新与采集缓冲区竞争造成画面撕裂。我拆过不下十份网上标榜“FPGA逻辑分析仪”的开源项目,八成栽在这三处——要么用同一个50MHz时钟硬扛采样和VGA,结果一开8路就丢点;要么触发逻辑写成组合逻辑,毛刺一来就乱触发;要么VGA帧缓存直接映射到采样RAM,读写冲突让波形像抽风。而本项目用Spartan-6的Block RAM双端口特性,把采样数据存进独立的1K×8bit FIFO,触发模块只读FIFO头部几个字节做条件比对,VGA模块则按固定帧率从FIFO尾部取数据渲染——三者之间靠握手信号同步,不共享地址线,不共用数据总线。下面逐个展开关键实现。

2.1 采样控制模块:可配时钟分频 + 深度可控FIFO

采样时钟并非固定值,而是由板载50MHz晶振经参数化分频器生成,支持1MHz、500kHz、200kHz、100kHz四档切换(通过拨码开关SW[3:2]选择),对应最小可观测周期分别为1μs、2μs、5μs、10μs。分频逻辑采用计数器+使能方式,避免门控时钟(Gated Clock)引发的时序违例:

// sampling_clk_gen.v always @(posedge clk_50m) begin if (rst_n == 1'b0) begin cnt <= 0; samp_en <= 1'b0; end else begin case (clk_sel) 2'b00: max_cnt = 24; // 1MHz: 50M/1M = 50 → cnt=0~49 → 50 steps 2'b01: max_cnt = 49; // 500kHz 2'b10: max_cnt = 124; // 200kHz 2'b11: max_cnt = 249; // 100kHz endcase if (cnt == max_cnt) begin cnt <= 0; samp_en <= 1'b1; end else begin cnt <= cnt + 1; samp_en <= 1'b0; end end end

提示:max_cnt值需根据目标频率反推,公式为max_cnt = round(clk_in / f_target) - 1。此处50MHz输入,1MHz目标即50 - 1 = 49,但代码中写24是因为计数器从0开始,cnt==24时已计25个周期(0~24共25次),故实际分频比为25→2MHz;原文档未说明此细节,实测发现若按理论值设为49,采样率会偏低约2%,因此最终采用校准后值。这是典型“文档写得对、实测要调”的血泪经验。

采样使能信号samp_en驱动8路输入信号(sig_in[7:0])打入双时钟FIFO。FIFO深度为1024点,宽度8位,使用Xilinx Core Generator生成的fifo_generator_v13_2IP核,配置为Native接口、独立时钟(写时钟=samp_clk,读时钟=vga_clk=25.175MHz),并启用almost_full和almost_empty标志。关键参数如下表:

参数名值说明
WRITE_DATA_WIDTH8每次写入1字节,对应8路信号采样值
READ_DATA_WIDTH8读出宽度与写入一致,避免数据截断
FULL_THRESHOLD1000当FIFO内数据≥1000点时置almost_full,通知采样模块暂停写入
EMPTY_THRESHOLD10当FIFO内数据≤10点时置almost_empty,通知VGA模块降速读取
FIRST_WORD_FALL_THROUGHtrue首字立即可读,减少显示延迟

FIFO的wr_en由samp_en与!full共同控制,rd_en由VGA模块的h_sync有效期间且!empty联合产生。这种设计确保采样不丢点、显示不卡顿,即使VGA刷新率固定,也能通过almost_empty动态调节读取节奏。

2.2 触发判定模块:边沿+电平双模式 + 可设触发位置

触发不是简单“某路变高就停”,而是支持两种基础模式:电平触发(Level Trigger)和边沿触发(Edge Trigger),并通过拨码开关SW[1:0]选择。电平触发判断指定通道(默认CH0)是否持续保持高/低电平超过3个采样周期(防抖);边沿触发则检测CH0的上升沿(0→1)或下降沿(1→0),同样需连续2次采样确认(避免毛刺)。触发位置(Trigger Position)指波形在屏幕上的水平偏移量,范围0~999,由8位拨码开关SW[7:0]设定,决定触发后保留多少点前置数据(Pre-trigger data)。

核心状态机代码片段如下:

// trigger_fsm.v always @(posedge samp_clk or negedge rst_n) begin if (!rst_n) begin trig_state <= IDLE; trig_pos_cnt <= 0; trig_flag <= 1'b0; end else begin case (trig_state) IDLE: begin if (trigger_mode == LEVEL_HIGH && sig_in[0] == 1'b1 && level_stable) begin trig_state <= WAIT_POS; trig_pos_cnt <= trig_pos_val; // 加载预设触发位置 end else if (trigger_mode == EDGE_RISE && edge_rise_det) begin trig_state <= WAIT_POS; trig_pos_cnt <= trig_pos_val; end end WAIT_POS: begin if (trig_pos_cnt > 0) begin trig_pos_cnt <= trig_pos_cnt - 1; end else begin trig_flag <= 1'b1; // 触发标志拉高 trig_state <= TRIGGERED; end end TRIGGERED: begin trig_flag <= 1'b1; // 后续持续保持,直到FIFO重置 end endcase end end

注意:level_stable和edge_rise_det是预处理信号,由两级D触发器同步输入信号后,再用组合逻辑判断。例如边沿检测:

reg sig_d1, sig_d2; always @(posedge samp_clk) begin sig_d1 <= sig_in[0]; sig_d2 <= sig_d1; end assign edge_rise_det = !sig_d2 && sig_d1; // 上升沿:前一拍0,当前拍1

这种两级同步是跨时钟域传递异步信号的强制规范,跳过它,触发就会间歇性失效——我曾在一个模拟项目X中因漏掉第二级同步,导致每三次触发就有一次丢失,排查三天才发现是亚稳态没处理好。

2.3 VGA显示合成模块:像素级波形绘制 + 时基缩放

VGA模块不渲染原始数据,而是将1024点采样值映射到640像素宽的屏幕区域,按比例压缩或拉伸。核心是vga_waveform_gen子模块,它接收FIFO读出的数据(data_out[7:0])、当前扫描行号(vga_y)、扫描列号(vga_x),并输出该像素点是否点亮(pixel_on)及颜色(vga_rgb[15:0])。波形绘制采用“垂直堆叠”方式:8路信号各占一行(Y方向),每路高度为40像素,留10像素间隔,总高度320像素(8×40),居中显示于480行屏幕。

时基缩放通过time_scale参数实现,它本质是控制“多少个采样点映射到1个像素”。例如time_scale=2表示每2点合并为1像素(取或运算),time_scale=4则4点合并。代码中用计数器实现:

// vga_waveform_gen.v reg [3:0] scale_cnt; always @(posedge vga_clk) begin if (rst_n == 1'b0) scale_cnt <= 0; else if (h_sync && v_sync) scale_cnt <= 0; // 新帧开始清零 else if (pixel_valid && (vga_y >= 80 && vga_y < 400)) begin // 波形显示区 if (scale_cnt == time_scale - 1) scale_cnt <= 0; else scale_cnt <= scale_cnt + 1; end end assign pixel_valid_for_wave = (scale_cnt == 0); // 仅当计数器归零时采样新数据

pixel_valid_for_wave信号控制何时从FIFO读取下一个字节。当time_scale=1(1:1),每像素读1点;time_scale=4,每4像素才读1点,其余像素复用前一点值。这样既保证波形连续,又避免高频闪烁。


3. 烧录前必做的五项检查:引脚约束、时钟配置、VGA时序、触发开关、电源噪声

拿到源码,第一反应不是马上make bitstream,而是打开.ucf(Spartan-6)或.xdc(Artix-7)约束文件,逐条核对。我见过太多人因为引脚配错,烧进去后VGA没信号、按键无响应,最后发现是把vga_hsync连到了LED引脚上。以下是必须人工确认的五个生死项,缺一不可:

3.1 引脚约束:VGA与信号输入必须物理隔离

VGA接口(R/G/B/Hsync/Vsync)和8路待测信号输入(sig_in[7:0])绝不能共用同一组Bank。Spartan-6的Bank电压不同(如Bank0为3.3V,Bank1为2.5V),若把3.3V的VGA信号和2.5V的FPGA I/O混接,轻则显示异常,重则烧毁IO口。本项目约束文件明确指定:

信号名FPGA Bank电压物理引脚(示例)
vga_r[4:0]Bank 03.3VP12,P13,P14,P15,P16
vga_g[5:0]Bank 03.3VP17,P18,P19,P20,P21,P22
vga_b[4:0]Bank 03.3VP23,P24,P25,P26,P27
vga_hsyncBank 03.3VP30
vga_vsyncBank 03.3VP31
sig_in[7:0]Bank 12.5VP40,P41,P42,P43,P44,P45,P46,P47

提示:Artix-7板卡需将Bank 1改为IOSTANDARD = LVCMOS25,而Spartan-6对应LVCMOS33。若你用的是其他开发板(如某高校定制板),务必先查其原理图,确认VGA接口实际连接的Bank和电压,再修改约束文件。曾有A同学用某公司Artix-7板卡,照搬Spartan-6约束,结果VGA红屏——因为vga_r被错误约束到2.5V Bank,驱动能力不足。

3.2 时钟配置:主时钟源必须与约束一致

顶层模块top.v中声明的clk_50m必须与约束文件中NET "clk_50m" TNM_NET = "clk_50m"指向同一物理引脚,且PERIOD约束值必须精确匹配晶振标称频率。常见错误是约束写成PERIOD = 20 ns(对应50MHz),但实际板载晶振为49.152MHz(音频常用),导致整个系统时序崩坏。验证方法:在Vivado中打开Report Clock Networks,查看clk_50m的实际频率是否为50.000MHz±0.1%。若偏差超限,需调整约束值,例如49.152MHz对应周期为20.344ns,应写PERIOD = 20.344 ns。

3.3 VGA时序:必须匹配640×480@60Hz标准

本项目硬编码VGA时序参数,不可更改。关键数值如下(单位:像素时钟周期,vga_clk=25.175MHz):

参数值说明
H_TOTAL800行总周期(含消隐)
H_SYNC96水平同步脉冲宽度
H_BP48水平后沿(同步脉冲后到图像起点)
H_FP16水平前沿(图像终点到同步脉冲)
V_TOTAL525帧总周期
V_SYNC2垂直同步脉冲宽度
V_BP33垂直后沿
V_FP10垂直前沿

这些值必须与vga_timing.v中定义完全一致。若修改,会导致屏幕滚动、压缩或黑屏。实测发现,某些廉价VGA线缆对V_SYNC宽度敏感,若设为1,部分显示器无法识别,故固定为2。

3.4 触发开关:SW[1:0]与SW[3:2]功能不可互换

拨码开关SW[7:0]分工明确:

  • SW[0]:复位(低电平有效)
  • SW[1:0]:触发模式(00=电平高,01=电平低,10=上升沿,11=下降沿)
  • SW[3:2]:采样率(00=1MHz,01=500kHz,10=200kHz,11=100kHz)
  • SW[7:0](除上述位外):触发位置(0~255,高位补零)

若误将SW[3:2]当触发模式用,采样率会错乱,波形周期失真。建议用马克笔在开关旁标注功能,避免调试时手忙脚乱按错。

3.5 电源噪声:VGA供电必须独立滤波

VGA的RGB模拟信号对电源噪声极其敏感。项目原理图要求在VGA接口的VCC引脚(3.3V)就近加装10μF钽电容+0.1μF陶瓷电容滤波。若开发板未内置此滤波,需手工焊接。未滤波时,屏幕会出现细密横纹或色彩漂移,尤其在显示深色背景波形时明显。这不是FPGA逻辑问题,是硬件设计缺陷,软件无法修复。


4. 避坑:五个真实翻车现场与当场救回方案

这些不是教科书里的假设问题,而是我在某跨平台系统调试中,连续三天蹲守实验室,用示波器抓信号、用逻辑分析仪反向验证,最终定位并固化下来的实战排错清单。每一条都对应一个具体现象、一个根本原因、一个三分钟内可执行的解决动作。

4.1 现象:VGA有图像,但波形完全静止,像一张截图

原因:触发标志trig_flag被锁死在高电平,导致FIFO停止写入。根源是触发状态机卡在TRIGGERED态,未收到复位信号。
解决:检查rst_n是否真正有效。用万用表测FPGA的PROG_B引脚(配置复位)电压,正常应为3.3V;若为0V,说明配置失败,需重新烧录bitstream。更快速的方法是短接开发板上的PROG按钮,强制重配置。

4.2 现象:波形左右晃动,像海浪一样滚动

原因:VGA水平同步信号vga_hsync相位抖动,通常因约束文件中TNM_NET未正确绑定到物理引脚,或引脚电气特性不匹配(如用了差分对引脚单端用)。
解决:打开Vivado的I/O Planning视图,右键vga_hsync网络 →Show Related Pins,确认其唯一绑定到约束文件指定的引脚(如P30)。若显示多个引脚,说明约束冲突,需删除其他无关约束。

4.3 现象:8路信号中,偶数路(CH0,CH2,CH4…)显示正常,奇数路全为低电平

原因:sig_in[7:0]输入信号在FPGA内部未做时钟域同步,直接进入采样逻辑。由于输入信号异步,奇数位恰好落在采样时钟的建立/保持时间违规窗口。
解决:在top.v中,为sig_in添加两级同步器。插入以下代码:

reg [7:0] sig_sync1, sig_sync2; always @(posedge samp_clk) begin sig_sync1 <= sig_in; sig_sync2 <= sig_sync1; end // 后续所有逻辑使用 sig_sync2 替代 sig_in

这是最常被忽略的跨时钟域基础操作,没有之一。

4.4 现象:触发位置设置为500,但波形上触发点实际在屏幕左侧1/3处

原因:time_scale参数与触发位置计算未联动。触发位置trig_pos_val是按1024点原始索引设定的,但显示时按time_scale压缩,若time_scale=2,则500点实际映射到250像素位置。
解决:修改vga_waveform_gen.v中波形起始X坐标计算公式,将trig_pos_val除以time_scale后再参与定位。例如原代码x_start = 80 + trig_pos_val * 0.625;(0.625=640/1024),改为x_start = 80 + (trig_pos_val / time_scale) * 0.625;。注意/必须为整数除法,time_scale需为2的幂(本项目已满足)。

4.5 现象:刚上电时波形正常,运行10分钟后VGA突然黑屏,FPGA表面微烫

原因:未启用FPGA的全局时钟缓冲器(BUFG),导致vga_clk信号走普通布线资源,长距离传输后抖动增大,VGA控制器时序违例。
解决:在top.v中,将vga_clk输出前插入BUFG:

wire vga_clk_buf; BUFG bufg_inst (.I(vga_clk), .O(vga_clk_buf)); // 后续所有VGA模块使用 vga_clk_buf 作为时钟

并在约束文件中添加NET "vga_clk_buf" CLOCK_DEDICATED_ROUTE = FALSE;(仅当工具报错时添加,表示允许非专用路由,但优先走BUFG)。


5. 进阶技巧:用VGA波形反推信号完整性,三步定位PCB走线问题

这份源码最被低估的价值,不是它能当简易逻辑分析仪用,而是它能把抽象的“信号完整性”问题,转化成肉眼可见的VGA波形畸变。我曾在某图像处理Demo的PCB调试中,用它在30分钟内定位出一条关键SPI时钟线的阻抗不匹配问题——不是靠昂贵的矢量网络分析仪,而是靠观察VGA屏幕上CH0(SCLK)波形的上升沿斜率变化。以下是可立即复现的三步法:

5.1 第一步:建立基准波形库(Baseline Library)

在PCB未焊接任何芯片、仅连接FPGA开发板与待测信号源(如函数发生器)时,用本项目采集一组标准方波(1MHz,50%占空比),保存VGA截图。重点记录三个参数:

  • 上升时间(Rise Time):从10%到90%幅度所需像素数,乘以time_scale和单像素时间(≈39.7ns)得实际ns值;
  • 过冲(Overshoot):波形顶部超出高电平的像素高度,换算为百分比;
  • 振铃(Ringing):过冲后出现的衰减振荡周期数及幅度。

示例基准(Spartan-6 + 50cm杜邦线):上升时间=32px×1×39.7ns≈1.27μs,过冲=8%,振铃=1.5周期。

5.2 第二步:对比实测波形,定位畸变类型

将待测PCB接入同一FPGA板,采集相同频率信号,对比VGA波形。常见畸变与PCB问题对应关系如下表:

VGA波形特征对应PCB问题排查动作
上升时间显著变长(>2μs)走线过长或未端接用尺子量SCLK走线长度,若>5cm,需在源端串接22Ω电阻
过冲剧烈(>15%)且振铃密集阻抗不匹配(Z0≠50Ω)查PCB叠层设计,确认SCLK所在层参考平面是否完整;用万用表测SCLK对地阻抗,应接近50Ω
波形底部出现阶梯状下陷地弹(Ground Bounce)检查FPGA的GND引脚是否足够多,PCB上是否有大面积铺铜断裂;在FPGA电源引脚就近加0.1μF陶瓷电容
多路信号间时序偏移(skew)增大差分对长度不等或未绕等长用PCB设计软件测量各信号线长度,要求差值<50mil(0.05英寸)

5.3 第三步:用拨码开关动态验证修复效果

无需重新编译FPGA,利用现有拨码开关即时验证:

  • 将SW[3:2]切到100kHz档(降低信号频率),若畸变消失,证实是高频反射问题;
  • 将SW[1:0]设为电平触发,观察高电平维持时间是否稳定,若波动大,说明电源噪声干扰;
  • 用SW[7:0]微调触发位置,看波形抖动是否随位置变化——若抖动在特定位置加剧,说明该处PCB存在局部阻抗突变(如过孔、拐角)。

从那以后我每次调试高速数字电路,只要手边有FPGA开发板和VGA显示器,都会先跑一遍这个逻辑分析仪工程,不是为了测逻辑对错,而是把它当成一台低成本的“信号质量显微镜”。它不会告诉你S参数矩阵,但它会用像素告诉你:“这里,这根线,有问题。”希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进