基于FPGA的自制逻辑分析仪:采样、触发与VGA显示方案

发布时间:2026/10/20 14:20:11
基于FPGA的自制逻辑分析仪:采样、触发与VGA显示方案
简介这是一套以FPGA为核心的16通道逻辑分析仪完整设计工程面向数字电路、EDA及FPGA开发学习者适合课程设计、毕业设计或工程实践参考。工程实现通过拨码开关选择采样触发模式并调节采样频率采集的逻辑电平序列经VGA接口实时显示可清晰观测时序变化覆盖从源码编写到上板验证的完整流程。压缩包共106个文件包含大量.v、.tdf等Verilog/AHDL源文件qpf/qsf工程配置文件sdc时序约束mif初始化数据以及sof/pof烧录文件同时保留编译报告与readme说明便于读懂工程结构。资源大小仅1.71MB轻量易获取已有951人学习下载。借助该工程可掌握多通道采样控制、VGA显示时序生成、ROM字符库应用等关键设计方法并可直接作为二次开发基础。1. 逻辑分析仪这个选题为什么绕不开 FPGA调试一块新板子上的 I2C 和 SPI 总线时最怕的不是时序不对而是连波形都看不到。示波器通道不够逻辑分析仪得上千元串口打印又跟不上高速信号。这时候用 FPGA 自制一台逻辑分析仪就变成一个很务实的选择用可编程逻辑去采样、触发、存储再用 VGA 把波形直接显示在显示器上。这套方案要解决的是一台通道数足够、采样率可调、触发方式灵活的调试工具问题。适合做这个项目的人很明确正在准备 FPGA 课程设计或者毕业设计的学生需要把采样、触发、存储、显示这一整套流程打通以及做嵌入式开发但预算有限的从业者想用一块开发板给自己做一个顺手的调试设备。它不追求和商用逻辑分析仪比参数而是把 Finest 的采样率、存储深度、触发条件和 VGA 显示之间的约束关系理清楚做一台自己能看懂、能改、能复现的仪器。2. 需求分析与关键指标采样率、存储深度和触发条件怎么互相制约2.1 先搞清楚要抓什么信号通道数与协议类型决定设计方向设计逻辑分析仪的第一步不是选芯片而是明确被测对象。如果只是调试 UART、I2C、SPI 这类低速总线8 个通道完全够用采样率做到 50MHz 就有充足余量。但要调试并行总线或者高速串行协议通道数和采样率就得往上提设计的复杂度也会随之上升。拿某高校的课程设计场景来说A 同学做的方案定在 8 通道、采样率 10MHz 到 100MHz 可调存储深度用 FPGA 内部的 Block RAM 实现目标是把 UART 和 SPI 的协议解码功能跑通。针对这个定位主控 FPGA 不需要选太高端的型号某主流中低端系列器件就能满足需求片上 Block RAM 数量够用是关键约束。采样率的档位设计也需要提前规划。用 100MHz 的系统时钟作为基准分频出 20MHz、50MHz 和 100MHz 三档采样率。档位切换直接在采样模块里做不采用动态重配置时钟管理器的方式避免增加设计的复杂程度。低速信号用低采样率可以拉长可观测的时间窗口高速信号切到高采样率保证能捕捉到足够多的细节。这个设计思路在实际调试中用起来很顺手——抓 I2C 的地址字节时用 50MHz 采样率抓 SPI 的高速模式时切到 100MHz不用重新综合。2.2 为什么显示模块选 VGA比 HDMI 简单比串口直观VGA 显示模块是整套方案里最直观的交付亮点。选择 VGA 的原因有三个时序标准公开、实现复杂度低、几乎所有实验室的显示器都支持。640x48060Hz 的分辨率足够显示 8 通道的波形行场同步信号的时序参数是固定的不像 HDMI 那样涉及 TMDS 编码和像素打包调试难度低一个数量级。接口方面VGA 需要 5 个信号红、绿、蓝各 1 位加上行同步和场同步。FPGA 的引脚可以直接输出 3.3V 电平的 RGB 信号通过一个简单的电阻网络转成模拟电平后接显示器。这里有一个容易被忽略的点VGA 的 RGB 信号在 FPGA 内部要用寄存器同步到像素时钟域否则画面上会出现随机的颜色噪点。某开发者在调试时遇到过波形线条时断时续的问题最后定位到是没有把像素数据打拍同步修完之后画面稳定多了。2.3 三个核心指标的约束关系采样率、深度、触发缺一不可逻辑分析仪的设计难点不在单个模块而在三个指标之间的联动关系。采样率决定能看到多快的信号存储深度决定能看多长时间的波形触发条件决定波形从哪里开始显示。它们之间是互相制约的采样率越高同样的存储深度能记录的时间越短触发位置设置不当可能错过关键的协议时序。以 100MHz 采样率、8K 深度为例一次采样只能记录 80 微秒的波形。如果要抓一段完整的 I2C 读操作通常需要几十微秒到几百微秒这就需要在采样率和深度之间做权衡。一般会提供预触发和延迟触发两个参数预触发用存储深度的四分之一保证触发前的数据也被记录下来。这样在观察异常波形时能看到异常发生之前总线上发生了什么对定位总线冲突非常有用。这些参数在顶层模块里可以通过拨码开关或者按键配置不需要重新综合就能调整。3. 总体架构设计采样、触发、存储、显示四个模块怎么协作3.1 架构总览从引脚到屏幕的数据通路整个系统按数据流向拆成四个模块各模块职责清晰边界明确非常适合课程设计答辩时讲解。采集模块负责对被测信号进行同步和采样触发模块对采样结果进行条件判定存储模块将采样数据写入双口 RAM显示模块从 RAM 读出数据并转换成 VGA 时序输出。数据通路是单向的不存在回流路径这对于 FPGA 实现来说非常友好——不会出现组合逻辑环路时序收敛的压力也小很多。模块之间的接口定义是设计重点。采集模块输出经过同步和采样后的 16 位数据总线每个通道占 2 位还是 1 位需要考虑。这里采用每通道 1 位8 通道共 8 位数据总线配合 2 位通道选择信号在显示模块中分时读取。触发模块输出一个单周期的触发标志信号存储模块根据这个标志决定是否开始写入以及写入到什么位置。VGA 显示模块从存储模块的读端口读取数据经过查表转换成波形像素后送入屏幕。各模块的时钟由顶层统一分配采集和触发用采样时钟存储用系统时钟VGA 用像素时钟。3.2 跨时钟域处理三个时钟域之间的信号同步策略FPGA 设计的核心难点就是多时钟域的同步。本方案中存在三个时钟域100MHz 系统时钟域、可变采样时钟域和 25MHz VGA 像素时钟域。采样时钟根据档位设置切换频率和系统时钟不同频如果直接把采样数据送进存储模块会产生亚稳态风险。最稳妥的做法是用两级寄存器同步即打两拍。采样模块输出的数据先同步到系统时钟域再写入存储 RAM。触发标志信号同理从采样时钟域同步到系统时钟域保证存储模块在安全时序下响应。这里有个容易踩坑的细节两级同步器解决的是单比特信号的亚稳态问题但对于多比特数据总线不能简单用打两拍的方式处理。正确做法是把数据先锁存到与目标时钟域同步的寄存器等数据稳定后再采样。在实现上利用 FIFO 的异步读写时钟特性是更可靠的选择。本设计中存储模块选用双口 RAM 自带的异步读时钟能力写入端口在系统时钟域下工作读出端口在像素时钟域下工作两个时钟域的交互由 RAM 的读写地址独立控制避免了直接跨域传递数据的风险。3.3 存储方案对比Block RAM 比外部 SDRAM 更适合课程设计面对存储深度的选择很多同学第一反应是使用外部 SDRAM认为容量大才够用。但 SDRAM 的控制器设计复杂刷新逻辑和有体切换会让开发周期明显拉长。对于 8K 深度的存储需求FPGA 内部 Block RAM 完全够用。以某中端 FPGA 为例单块 Block RAM 大小为 18Kb配置成 8K x 8 位只需要一块资源占用率极低还能通过级联多块实现 16K 或更大深度。选 Block RAM 还有一个明显的好处读写时序是确定性的不需要等待周期这在调试阶段能大幅降低排查问题的难度。用 SDRAM 的时候一旦读时序不对波形就会随机丢点很难分辨是存储问题还是显示问题。用 Block RAM 之后存储部分基本一次跑通把调试精力集中在触发和显示模块上。扩展性方面如果未来需要更大深度可以在顶层把存储模块替换成 DDR 控制器数据接口保持不变不影响其他模块的设计。4. 关键模块实现从采样到波形上屏的完整代码与设计说明4.1 采样模块等精度采样的原理与代码实现采样模块的职责是将外部信号同步到 FPGA 内部时钟域并以设定的采样率采集数据。这里采用等精度采样法即在采样时钟的上升沿对输入信号进行捕捉。由于被测信号与采样时钟是异步的必须先在每个采样通道上加两级 D 触发器做同步防止亚稳态导致采样值不稳定。同步后的数据通过一个与门网络实现边沿检测每一位输出对应通道的当前电平状态。module sample_module #( parameter CH_WIDTH 8, parameter SAMPLE_CLK_DIV 2 )( input wire clk_100m, input wire rst_n, input wire [CH_WIDTH-1:0] ch_in_raw, output reg [CH_WIDTH-1:0] sample_data ); reg [CH_WIDTH-1:0] sync_stage1; reg [CH_WIDTH-1:0] sync_stage2; reg [7:0] clk_div_cnt; always (posedge clk_100m or negedge rst_n) begin if (!rst_n) begin sync_stage1 8h00; sync_stage2 8h00; end else begin sync_stage1 ch_in_raw; sync_stage2 sync_stage1; end end always (posedge clk_100m or negedge rst_n) begin if (!rst_n) begin clk_div_cnt 8h00; sample_data 8h00; end else if (clk_div_cnt SAMPLE_CLK_DIV-1) begin clk_div_cnt 8h00; sample_data sync_stage2; end else begin clk_div_cnt clk_div_cnt 1b1; end end endmodule这段代码中sync_stage1和sync_stage2构成了打两拍的同步链消除亚稳态的影响。clk_div_cnt是采样时钟分频计数器步长由顶层传入的SAMPLE_CLK_DIV参数控制。当计数器计满时把同步后的数据锁存到sample_data中。参数化的设计让三档采样率切换变得很简单100MHz 采样档位时SAMPLE_CLK_DIV150MHz 档位时等于 220MHz 档位时等于 5。这样采样模块不需要改代码只需改顶层例化时传入的参数。实际使用时需要留意分频计数值必须大于等于 1否则计数器永远达不到计满条件这个边界情况在代码注释里已经标明。4.2 触发模块边沿触发和匹配触发两种模式实现触发模块是整个逻辑分析仪的智能所在。它的功能是根据设定的条件决定何时开始存储数据。本设计实现了两种触发模式边沿触发和数据匹配触发。边沿触发方式下用户可以指定某个通道的上升沿或下降沿作为触发条件数据匹配触发方式下当 8 个通道的数据组合与设定的匹配值相等时产生触发信号。触发模式的切换通过顶层拨码开关实现两个模式共用同一组输入数据总线。module trigger_module #( parameter CH_WIDTH 8, parameter EDGE_CH 0 )( input wire clk_sys, input wire rst_n, input wire [CH_WIDTH-1:0] sampled_data, input wire [1:0] trigger_mode, // 2b00: 上升沿, 2b01: 下降沿, 2b10: 数据匹配 input wire [CH_WIDTH-1:0] match_value, output reg trigger_flag ); reg [CH_WIDTH-1:0] data_d1; wire rising_edge sampled_data[EDGE_CH] ~data_d1[EDGE_CH]; wire falling_edge ~sampled_data[EDGE_CH] data_d1[EDGE_CH]; always (posedge clk_sys or negedge rst_n) begin if (!rst_n) begin data_d1 8h00; end else begin data_d1 sampled_data; end end always (posedge clk_sys or negedge rst_n) begin if (!rst_n) begin trigger_flag 1b0; end else begin case (trigger_mode) 2b00: trigger_flag rising_edge; 2b01: trigger_flag falling_edge; 2b10: trigger_flag (sampled_data match_value); default: trigger_flag 1b0; endcase end end endmodule边沿检测的实现方式是典型的当前值与前一个周期值的组合逻辑比较只消耗极少的资源。trigger_mode的低两位分别对应两种边沿检测第三种模式为数据匹配。匹配触发还有一个增强设计支持屏蔽某些通道即只比较指定的几个通道数据是否等于期望值。做法是增加一个 8 位的mask_in输入比较时先做按位与操作再比较这样就算某个通道的数据是浮空状态也不会干扰匹配判断。触发信号输出为单周期高电平脉冲这个脉冲直接送进存储模块作为写地址的启动信号。4.3 存储模块双口 RAM 的读写时序与预触发设计存储模块采用 Xilinx 原语或 Altera 的 RAM 宏模块实现例化时选择双口模式写时钟由系统时钟驱动读时钟由 VGA 像素时钟驱动。存储深度 8K、位宽 8 位可以同时覆盖显示模块连续读取的需求。关键设计是预触发功能当触发信号到达时写地址已经被写到了深度四分之一的固定位置之后的采样数据继续写入直到地址循环回起点为止。这样存储下来的是触发前一段、触发后一段的完整波形对定位异常时序非常有帮助。module storage_module #( parameter DEPTH 8192, parameter ADDR_WIDTH 13 )( input wire wr_clk, input wire rd_clk, input wire rst_n, input wire [7:0] wr_data, input wire trigger_pulse, input wire wr_enable, output wire [7:0] rd_data, output wire [ADDR_WIDTH-1:0] rd_addr ); reg [ADDR_WIDTH-1:0] wr_addr; reg [ADDR_WIDTH-1:0] rd_addr_reg; reg trigger_latched; always (posedge wr_clk or negedge rst_n) begin if (!rst_n) begin wr_addr 13h0000; trigger_latched 1b0; end else if (trigger_pulse) begin trigger_latched 1b1; wr_addr {1b0, {(ADDR_WIDTH-1){1b1}}}; // 预触发位置 1/4 end else if (wr_enable) begin wr_addr wr_addr 1b1; end end // RAM 例化代码省略使用厂商宏模块 // 读地址由显示模块产生rd_addr 直接透传 assign rd_data ram_out; assign rd_addr rd_addr_reg; endmodule存储模块的细节在于trigger_latched的用法——首轮触发后就不再响应新的触发脉冲保证一次采样过程只记录一段波形。使用者将wr_enable与采样模块的输出有效信号相连确保只在有效数据到来时才递增地址。当用户按下“单次捕获”按键时触发模块被复位地址清零等待下一次触发这样就能稳定地捕获单次事件。读地址的生成逻辑很直接rd_addr_reg在 VGA 的每个行有效显示期间递增一行对应读取 640 个像素点正好对应存储深度的一段。这里隐含的约束是一帧画面读取的数据量等于 640×480 个点而存储深度是 8192 个点所以一次捕获的波形会被重复扫描多行来填满整个屏幕。这种设计在波形显示时是合理的因为同一段波形在每行重复绘制看起来就像一条连续的水平线。4.4 VGA 显示模块行场时序和波形绘制原理VGA 显示模块按经典 640x48060Hz 时序实现。行同步参数为每行 800 个像素时钟其中显示区 640 个前肩 16 个同步脉冲 96 个后肩 48 个。场同步参数为每场 525 行其中显示区 480 行前肩 10 行同步脉冲 2 行后肩 33 行。这些是固定参数任何一份 VGA 时序文档都有。module vga_display #( parameter H_DISPLAY 640, parameter V_DISPLAY 480, parameter H_FRONT 16, parameter H_SYNC 96, parameter H_BACK 48, parameter V_FRONT 10, parameter V_SYNC 2, parameter V_BACK 33, parameter DATA_WIDTH 8 )( input wire pix_clk, // 25.175MHz 像素时钟 input wire rst_n, input wire [DATA_WIDTH-1:0] sample_data, output wire hsync, output wire vsync, output wire [2:0] rgb_out // 3 位 RGB红绿蓝各 1 位 ); reg [9:0] h_cnt; reg [9:0] v_cnt; reg [2:0] rgb_reg; // 行场计数器递增逻辑 always (posedge pix_clk or negedge rst_n) begin if (!rst_n) begin h_cnt 10d0; v_cnt 10d0; end else if (h_cnt H_TOTAL-1) begin h_cnt 10d0; if (v_cnt V_TOTAL-1) v_cnt 10d0; else v_cnt v_cnt 1b1; end else begin h_cnt h_cnt 1b1; end end // 行场同步信号生成 assign hsync (h_cnt H_DISPLAY H_FRONT h_cnt H_DISPLAY H_FRONT H_SYNC) ? 1b0 : 1b1; assign vsync (v_cnt V_DISPLAY V_FRONT v_cnt V_DISPLAY V_FRONT V_SYNC) ? 1b0 : 1b1; // 波形绘制根据采样数据电平决定像素颜色 always (*) begin if (h_cnt H_DISPLAY v_cnt V_DISPLAY) begin if (sample_data[v_cnt[3:0]] 1b1) rgb_reg 3b111; // 高电平显示白色 else rgb_reg 3b000; // 低电平显示黑色 end else begin rgb_reg 3b000; // 消隐区显示黑色 end end assign rgb_out rgb_reg; endmodule波形绘制的思路是把 8 个通道显示在屏幕的不同垂直位置。假设每个通道占 60 像素行第 0 通道在屏幕下方第 7 通道在屏幕上方。当前扫描行的v_cnt对应的通道号为(V_DISPLAY - v_cnt) / 60该通道的采样值决定当前像素是白还是黑。网格线可以在额外的像素判定逻辑中实现——每行或每列取模 32 的像素点上强制显示为灰色这样波形有一个坐标系的参照。显示模块的调试经验是用chipscope或signal tap观察h_cnt和v_cnt的波形确认它们按照预期递增。常见问题是像素时钟频率不准确导致画面偏左或偏右可以通过微调分频器的计数值解决。VGA 显示对时钟抖动不敏感用 PLL 生成的 25.175MHz 或者直接分频得到 25MHz 都可以正常工作显示器会自动校正微小的偏差。5. 常见问题避坑这几个翻车点最容易在调试时卡住5.1 亚稳态采样数据和触发标志跨时钟域时不打拍现象波形显示时偶尔出现一个通道的电平毛刺或者触发后存储的数据里出现几位明显异常的值。 原因采集模块和存储模块工作在不同时钟域数据直接跨域传递时触发亚稳态导致采样值处于中间电平被误判为高或低。 解决严格按照两级触发器同步的方式处理单比特信号多比特数据总线通过异步 FIFO 或双口 RAM 的独立读写时钟隔离。在 FPGA 设计里时钟域边界的安全性是首要考虑。A 同学第一次做这个项目时偷懒省了同步器结果波形随机跳变排查了两天才意识到是同步问题。5.2 VGA 画面偏移或者闪动现象显示器上的波形整体偏向一侧或者整个画面周期性闪动。 原因行场同步参数设置错误或者像素时钟频率偏差过大。另一个常见原因是 RGB 消隐期间没有输出黑色导致显示器在回扫期间画出色带。 解决先对照标准 VGA 时序表逐项核对参数确认H_FRONT、H_SYNC、H_BACK的值。然后用示波器测量像素时钟频率误差控制在百分之五以内即可。消隐区必须强制输出黑色即rgb_reg 0这个逻辑在代码中的else分支里已经处理。如果画面整体偏右调大H_FRONT的值偏左则调小。5.3 触发条件能捕捉到但波形不对现象按下触发按键后存储模块开始写入但波形只显示触发后的数据看不到触发前的状态。 原因预触发深度设置不对或者触发信号响应后trigger_latched逻辑没有正确生效。 解决检查存储模块中预触发位置的赋值逻辑确认触发信号到来时写地址被置为四分之一深度处且之后继续递增。这个机制要配合顶层按键的“单次捕获”模式使用在软件或拨码开关处在连续模式时反复触发会覆盖之前的存储内容看不出预触发效果。5.4 采样率设置过高导致显示波形太密现象用 100MHz 采样率抓 UART 信号屏幕上波形密密麻麻单个字节的位时序完全看不清。 原因采样率和显示分辨率的关系没理清。100MHz 采样率抓 9600 波特率的 UART一个比特位有 10417 个采样点8K 深度下只能显示不到一个字节。 解决根据预计的信号带宽选择合适的采样率档位。低速串行信号用 20MHz 即显示充足细节。如果确实需要高采样率又希望看长时间波形需要扩展存储深度或增加缩放功能。这个限制在设计时就要清楚并在答辩时讲出这个权衡过程是加分项。5.5 按键控制失灵单次捕获和连续捕获模式切换不灵现象按下“单次捕获”按键后触发模块没有响应再按一次才生效。 原因机械按键存在抖动没有做消抖处理。触发模块对单周期脉冲敏感抖动的电平变化会产生多次触发。 解决给按键模块增加 20ms 延时消抖逻辑确认稳定后在按键释放时产生一个单周期脉冲。另一个点是将“单次捕获”和“连续捕获”模式分别绑定到不同按键通过顶层状态机管理。这些逻辑单独验证后再集成到总系统排查起来更省力。6. 验收方法与答辩要点把调试过程变成得分点6.1 分模块验收功能验证的先后顺序验收工作不应该等到所有模块都完成才开始。通常的做法是先验证 VGA 输出让显示模块输出彩条或棋盘格确认显示器画面正常这一步排除了时序问题的最大干扰源。接着验证存储模块从 FPGA 内部 ROM 读取一段已知数据写入存储通过 VGA 显示后观察是否与预期一致。最后验证采集与触发用信号发生器注入方波或者由一块单片机产生的 UART 数据观察波形形状和触发位置是否正确。按这个顺序排查每个阶段的问题都局限在当前模块定位速度快得多。6.2 指标验收的具体方法在交付验收环节重点验证三个指标。采样率准确性用已知频率的信号实测如果注入 1MHz 方波屏幕上应该显示 50% 占空比的波形且一个周期对应的水平像素宽度与理论值相符。存储深度验证方法是触发测量一段已知长度的信号计算波形起点到终点的距离是否等于 8K 个采样点。触发准确率通过重复注入特定模式的数据来测试比如连续发送 100 次 I2C 起始条件统计触发成功的次数每次都要有响应且预触发区域的波形完整。6.3 答辩技巧讲清楚三个关键决策答辩时不需要把每个模块的代码都讲一遍重点讲三个设计决策及其权衡。第一为什么选 Block RAM 而不是 SDRAM——从开发周期和资源占用角度解释。第二为什么 VGA 采用 3 位 RGB 而不是更高位——强调简洁性和需求匹配度。第三预触发深度为什么选四分之一——用抓取异常信号的实例证明这个值的合理性。把这些逻辑讲清楚比罗列代码更能证明设计能力。有人曾经把整个代码从顶层到底层过一遍导师追问“为什么这里打两拍为什么触发地址是这个值”答不上来得分反而不如只讲设计思路但讲得透彻的同学。从那以后每次做完一个 FPGA 项目都强制自己走一遍“需求 → 指标 → 实现 → 验收”的闭环把每个模块之间的交互接口先定义清楚再开始写代码。这次逻辑分析仪的方案也是这样模块之间的信号定义、跨时钟域策略、验收方法都在动手前就梳理完毕。希望这篇方案能帮到你做出自己的逻辑分析仪。本文还有配套的精品资源点击获取