☰
基于FPGA的简易逻辑分析仪设计:采样、触发与VGA显示
2026/10/20 3:41:12 网站建设 项目流程

简介:一套面向FPGA学习者的逻辑分析仪完整工程源码,主要解决数字逻辑时序观测与VGA显示的入门实践问题。设计通过开关切换采样触发模式、调节采样频率,利用16个采样通道对多路信号进行采集,并将采集到的数据经VGA接口输出到显示器,可清晰呈现逻辑电平时序变化,适合课程设计、毕业设计或FPGA进阶练习。压缩包共106个文件,以v/tdf源码、qpf/qsf工程配置、mif存储初始化及bsf原理图符号为主,辅以rpt/qmsg编译报告、sof/pof下载文件和readme说明,整体约1.71MB,目录结构便于对照完整实现流程;从触发状态机到字符ROM、VGA时序,关键模块均有工程文件支撑。目前已有951人学习,可作为逻辑分析仪设计直接参考或二次开发的起点,也能帮助加深对采样控制、时序同步与数字显示链路的理解。

1. 调试数字电路时,一台自己做的 FPGA 逻辑分析仪比买的更实用

做过数字电路设计的人都有过这种经历:仿真波形看着一切正常,一上板子信号就翻车。示波器只有两三个通道,想同时观察一组总线的并行数据根本不够用;USB 逻辑分析仪入门级要几百块,而且通道数和采样率往往被砍得很厉害。与其纠结买哪台设备,不如用手里那块 FPGA 开发板自己搭一个。这套基于 FPGA 的逻辑分析仪设计,核心是把采样、触发、存储和 VGA 显示四件事拆成独立模块,通过硬件描述语言在单块芯片上实现多通道波形采集与显示,适合正在做课设或毕设的学生,也适合想深入了解采样系统与显示时序的从业者。它解决的痛点是:在没有昂贵仪器的情况下,用最小成本拥有一个通道足够多、采样率可调、结果直接上显示器的调试工具。

2. 总体架构设计:三层结构与四个核心模块的职责划分

2.1 系统分层:采样层、控制层与显示层为什么必须分开

逻辑分析仪本质上是一个高速采集加慢速显示的系统。被测信号可能跑在几十兆赫兹,而 VGA 显示一帧 60 赫兹的刷新率节奏远低于采样速率。如果让采样逻辑直接驱动显示逻辑,显示端的时序压力会反向污染采样端的稳定性。把这个系统拆成三层来设计,是最稳妥的做法。

第一层是采样层,负责把外部引脚上的数字信号同步到 FPGA 内部时钟域,并完成上升沿、下降沿或多电平条件的判断。第二层是控制层,职责是管理存储器的写使能与触发逻辑,它决定数据什么时候开始写、什么时候结束写、触发位置落在存储区的哪个比例点。第三层是显示层,从存储区读出数据,按照 VGA 时序把每个通道的波形绘制到屏幕上。

这个分层的核心思想是:各层只通过存储区交换数据,不直接互相调用信号。采样层写完数据后,显示层按自己的节奏读走。这样采样时钟和像素时钟互不干扰,调试时也能逐层定位问题。

2.2 关键参数设计:通道数、采样率与存储深度怎么匹配

选参数之前先算一笔账。采样率和存储深度的乘积决定了每次触发能捕获的时间窗口。假设系统主时钟是 50MHz,做三档采样率——1MHz、10MHz、50MHz,配 32K 字节的存储区。在 1MHz 档位下,32KB 能存 32 毫秒的波形;在 50MHz 档位下,只能存 640 微秒。这个比例关系直接决定了这台逻辑分析仪能观测的信号类型。

通道数方面,做成 8 通道是性价比较高的方案。8 通道恰好覆盖一组并行总线的典型宽度,同时 VGA 屏幕竖向也放得下。如果通道太多,每个通道分配的显示区域高度会压缩到难以辨认;通道太少,又失去逻辑分析仪相对示波器的核心优势。拨码开关用来切换采样率档位,这个设计比用按键轮询更直接,上电后硬件电路就能确定采样时钟分频系数。显示波形深度用 800 个像素点映射 32K 存储内容,即每像素约显示 41 个采样点,垂直方向每个通道分配约 50 像素的高度,配合网格线清晰可读。

2.3 数据通路与时序关系:数据从引脚到屏幕的完整路径

完整数据流可以这样描述:外部信号进入 FPGA 后,先经过采样子模块做两级寄存器同步与电平判决,然后写入双端口 RAM。触发模块实时监视同步后的信号,当预设条件满足时,向控制模块发出触发标志。控制模块根据触发标志决定存储区的写停止时刻,从而保证触发点前后都有数据可供分析。显示模块则按照 25MHz 像素时钟持续读 RAM,将存储内容转换为屏幕上的波形。

触发位置的选择也很关键。当触发条件命中时,如果立即停止写入,那屏幕上只显示触发点之后的数据,看不到触发前发生了什么;如果延迟停止写入,则能保留触发点之前的上下文。通常的做法是设置三个位置档位——触发点位于存储区的 25%、50% 或 75% 处。实现方式是维护一个写地址计数器,触发发生后继续写入一定数量的采样点,再冻结写使能。这个数量的计算依赖采样率档位,需要在每个档位下单独标定。

3. 采样、触发与存储实现:核心逻辑的硬件描述语言代码拆解

3.1 采样子模块:两级打拍同步与亚稳态消除

外部信号进入 FPGA 内部时,极可能与采样时钟没有任何相位关系,直接采样的结果会进入亚稳态,表现为仿真正确但实际运行时数据随机跳动。解决这个问题的标准方案是级联两级寄存器。

module sample_capture ( input wire clk_samp, // 采样时钟,来自分频模块 input wire rst_n, input wire [7:0] din, // 8 通道被测信号 output reg [7:0] data_out // 同步后的采样数据 ); reg [7:0] din_d1; reg [7:0] din_d2; always @(posedge clk_samp or negedge rst_n) begin if (!rst_n) begin din_d1 <= 8'h0; din_d2 <= 8'h0; data_out <= 8'h0; end else begin din_d1 <= din; // 第一级:原始输入 din_d2 <= din_d1; // 第二级:输出到逻辑使用 data_out <= din_d2; end end endmodule

两级打拍的本质是给亚稳态一个完整的时钟周期去稳定。第一级寄存器可能输出一个中间电平,但第二级寄存器再采样时,第一级的输出已经稳定到有效电平。这里有一个经验参数:如果采样时钟超过 100MHz,建议把两级打拍升级为三级;在 50MHz 采样率以下,两级足够应付绝大多数板级信号。还有一点容易被忽略:data_out 必须是 reg 类型且在使用它的模块中只读,不能在这个模块之外再对它赋值。

3.2 触发模块:上升沿检测与触发位置的窗口控制

触发是逻辑分析仪区别于普通波形显示的关键。实现一个通道可选的上升沿触发,核心是一个边沿检测电路:把同步后的信号打一拍,再与当前值做组合逻辑判断。

module trigger_unit ( input wire clk_samp, input wire rst_n, input wire [7:0] data_in, // 来自 sample_capture input wire [2:0] trigger_ch, // 选择监控哪个通道 input wire arm, // 触发使能信号 output reg trigger_flag // 触发命中标志 ); reg [7:0] data_prev; always @(posedge clk_samp or negedge rst_n) begin if (!rst_n) begin data_prev <= 8'h0; trigger_flag <= 1'b0; end else begin data_prev <= data_in; // 检测所选通道的上升沿:当前为高且上一拍为低 if (arm && data_in[trigger_ch] && !data_prev[trigger_ch]) trigger_flag <= 1'b1; else if (!arm) trigger_flag <= 1'b0; end end endmodule

触发模块的写法有几个工程化细节。trigger_ch 用 3 位选择 8 个通道之一,端口的位宽必须是 3 位。触发条件检测用的是组合条件,综合工具会把它映射为寄存器的时钟使能逻辑。这里很容易踩坑的是:很多人会把触发判断放在采样的同一拍上,导致触发标志与采样数据不同步。上面的写法中 data_in 已经经过两级打拍,data_prev 又是它的下一拍,所以 trigger_flag 有效时,data_in 上保存的恰好是触发沿附近的数据,时序关系是自洽的。

触发后控制模块需要决定何时停止写入存储区。设存储深度为 32K,触发点在 50% 时,触发标志有效后继续写入 16384 个采样点再冻结写地址。这个继续写入的计数器需要按采样率分频后的时钟计数,不能用系统主时钟直接计数值,否则在不同档位下触发的左右窗口比例会偏离设计值。

3.3 存储模块:双端口 RAM 的写控制与地址回卷

存储区使用双端口 RAM 是最合理的方案。一个端口专用于写入,受触发控制模块管理;另一个端口专用于 VGA 读取,地址由显示模块产生。两个端口独立时钟,写时钟是采样时钟,读时钟是像素时钟,互不牵制。

module wave_ram #( parameter ADDR_WIDTH = 15, // 32K 深度 parameter DATA_WIDTH = 8 )( input wire wr_clk, input wire rd_clk, input wire [ADDR_WIDTH-1:0] wr_addr, input wire [ADDR_WIDTH-1:0] rd_addr, input wire wr_en, input wire [DATA_WIDTH-1:0] wr_data, output reg [DATA_WIDTH-1:0] rd_data ); reg [DATA_WIDTH-1:0] mem [0:(1<<ADDR_WIDTH)-1]; always @(posedge wr_clk) begin if (wr_en) mem[wr_addr] <= wr_data; end always @(posedge rd_clk) begin rd_data <= mem[rd_addr]; end endmodule

写地址的生成方式直接决定触发后看到的数据范围。通常的做法是:未触发时写地址从 0 向上递增,写满后回卷覆盖最旧的数据,始终保持最新数据在 RAM 中。触发命中后,继续写入预先设定的深度,然后冻结写使能。这个冻结动作依赖一个计数器,它的目标值由触发位置参数决定。需要注意的是,RAM 读端口有一拍输出延迟,显示模块取数时要根据这个延迟做地址对齐,否则波形会整体偏移若干像素,给人造成数据错误的错觉。

还有一点要在综合时确认:这段双端口 RAM 代码在大多数综合工具中会被正确推断为块 RAM,但如果写了单独的读时钟和写时钟而没有任何约束,部分工具会把它映射成分布式寄存器阵列,导致资源消耗急剧上升。上板前要检查综合报告里的资源占用类型。

4. VGA 显示模块实现:波形绘制与界面合成的完整方案

4.1 VGA 行场时序:640×480@60 的完整参数表

VGA 显示的时序没有玄学,完全由计数器驱动。选用 640×480 分辨率、60Hz 刷新率,像素时钟正好是 25MHz,与 FPGA 开发板上的常用 50MHz 主频做二分频即可得到。这个分辨率在逻辑分析仪场景里足够展示 8 通道波形,多出来的带宽还能画网格和触发位置标记。

时序项像素周期数说明
行同步脉冲96水平同步信号低电平宽度
行后沿48同步脉冲结束到有效数据开始
行有效数据640显示期间计数对应水平像素位置
行前沿16有效数据结束到下一个同步脉冲
行总周期80096 + 48 + 640 + 16
场同步脉冲2垂直同步信号低电平宽度,单位行
场后沿33单位行
场有效数据480单位行,对应纵向像素
场前沿10单位行
场总周期5252 + 33 + 480 + 10

4.2 像素时钟与同步信号生成代码

module vga_timing ( input wire clk_25m, input wire rst_n, output reg hsync, output reg vsync, output reg video_active, output reg [9:0] pixel_x, output reg [9:0] pixel_y ); reg [9:0] h_cnt; reg [9:0] v_cnt; always @(posedge clk_25m or negedge rst_n) begin if (!rst_n) begin h_cnt <= 0; end else if (h_cnt == 799) begin h_cnt <= 0; end else begin h_cnt <= h_cnt + 1; end end always @(posedge clk_25m or negedge rst_n) begin if (!rst_n) begin v_cnt <= 0; end else if (h_cnt == 799) begin if (v_cnt == 524) v_cnt <= 0; else v_cnt <= v_cnt + 1; end // 其余时间 v_cnt 保持不变 end always @(posedge clk_25m or negedge rst_n) begin if (!rst_n) begin hsync <= 1'b0; end else begin // 行同步低电平为脉冲 hsync <= (h_cnt < 96) ? 1'b0 : 1'b1; end end always @(posedge clk_25m or negedge rst_n) begin if (!rst_n) begin vsync <= 1'b0; end else begin vsync <= (v_cnt < 2) ? 1'b0 : 1'b1; end end always @(posedge clk_25m or negedge rst_n) begin if (!rst_n) begin pixel_x <= 0; pixel_y <= 0; video_active <= 1'b0; end else begin pixel_x <= (h_cnt >= 96+48) && (h_cnt < 96+48+640) ? h_cnt - 144 : 10'd0; pixel_y <= (v_cnt >= 2+33) && (v_cnt < 2+33+480) ? v_cnt - 35 : 10'd0; video_active <= (h_cnt >= 144) && (h_cnt < 784) && (v_cnt >= 35) && (v_cnt < 515); end end endmodule

这段代码的关键在计数器的复用。行计数从头到尾数是 0 到 799,像素坐标 pixel_x 只取有效区域内对应的计数值,超出有效区不产生输出。hsync 和 vsync 的极性必须按照上一节的参数表严格设置,如果极性写反,显示器会直接不亮或画面不同步。这里也暴露了一个常见的坑:行同步和像素坐标共用同一个计数器,导致综合工具检查时序时会把像素坐标的组合逻辑链路计算进关键路径,解决方法是让 pixel_x 也走寄存器输出,不在同一周期内参与大量组合运算。

4.3 波形绘制与界面合成:从存储地址到屏幕像素的映射

存储区读出的数据是 8 位并行值,每一位对应一个通道。要把这 8 位展开成波形,需要两步。第一步是水平映射:把屏幕有效区 640 像素映射到存储区 32K 深度,也就是每个屏幕像素位置对应一个存储区地址。这样绘制出来的波形是数据顺序的直观展示。第二步是垂直映射:每个通道在屏幕上占据固定的纵向区域,通道 0 在最上方,通道 7 在最下方,通道与通道之间用网格线分隔。

绘制逻辑的核心是一个比较判断:当前像素对应地址的数据位与上一个地址的数据位不同,说明发生了电平跳变,则该像素垂直画一条线;若相同,则整个纵向区域填充对应电平的高亮颜色。这个做法避免了逐点插值的计算量,用最省资源的方式还原出方波轮廓。颜色编码也简单:波形用高亮色,背景用暗色,网格与基准线用次亮色。触发位置标记则读取控制模块保存的触发地址,把该地址换算成水平像素坐标,画一条贯穿显示区的虚线。

5. 常见问题与避坑排查:仿真全对、上板翻车的高频故障点

5.1 采样数据乱跳——异步信号没做同步处理

现象:上板后显示波形在最前面一段是乱的,到了后面才正常,甚至从开始到结束都像毛刺一样跳动。

原因:外部被测信号与采样时钟不同步,直接进入触发判断逻辑。综合工具不会报错,仿真时因为人为给定了确定相位关系也测不出问题,只在真实板上暴露。

解决方法:确保所有外部输入先过两级打拍再参与逻辑。打拍模块不能省略,也不能只打一拍。如果采样时钟在 50MHz 以上,建议在两级之后再加一个边沿同步器,确保 data 和 data_prev 的相位严格差一个时钟周期。

5.2 显示波形整体横向偏移——RAM 读延迟没对齐

现象:波形形状完全正确,跳变沿的位置也对,但整条波形相对触发标记点偏移了固定像素数。

原因:双端口 RAM 的读端口输出是有延迟的,通常是读时钟的一个周期。显示模块拿到 rd_data 的当前值,实际对应的是上一拍的 rd_addr。如果不修正,波形就会偏右若干像素。

解决方法:把 rd_addr 也打一拍,和 rd_data 保持同一拍对齐,再做地址到像素的映射。或者把映射函数改为根据打拍后的地址计算,总之要让数据与坐标严格同步。

5.3 VGA 屏幕滚动或画面抖动——像素时钟分频与参数表不匹配

现象:显示器能点亮,但画面周期性上下跳或横向滚动,换台显示器症状加重。

原因:行同步或场同步的时序参数与 25MHz 像素时钟不匹配。最常见的是把行后沿和行前沿数值写反,或者行总周期不是 800。显示器对行频率有容忍范围,但偏差超过一定值就无法锁定。

解决方法:按第 4 章表格逐项核对。用示波器测行同步信号的周期,应该是 31.7 微秒左右;场同步周期约 16.7 毫秒。没有示波器时,可以算:800 个像素周期乘 40 纳秒,等于 32 微秒,再乘 525 行,正好是 16.8 毫秒,约 59.5Hz,匹配 60Hz 刷新率。如果实测值对不上,回头检查计数器边界。

5.4 综合后时序违例——组合逻辑链路过长

现象:综合报告里时序分析显示关键路径建立时间不满足,约束文件里设置的时钟频率越高越容易爆红。

原因:采样时钟与触发判断、写地址生成连成了一长串组合逻辑。特别是在触发模块里,trigger_ch 的译码逻辑和 data_in 的逐位比较合在一起,经过多级查找表后到达寄存器,延迟超标。

解决方法:把触发通道的比较逻辑拆成独立的通道选择寄存器,先选通道再做边沿检测,不要在一个 always 块里同时完成选择与判断。或者直接对 8 个通道各做 8 份边沿检测逻辑,然后用一个多路选择器选出结果,让综合工具更容易优化。

5.5 触发位置标记与实际波形对不上——触发地址换算遗漏

现象:触发点标记线画在屏幕中间偏左,但实际波形跳变沿在标记线右侧明显位置。

原因:触发发生时写地址还在变化,存储冻结后 RAM 里的数据起点并非地址 0,而是回卷后的某个地址。显示模块如果假设数据永远从地址 0 开始读,就会把触发点的相对位置算错。

解决方法:在触发冻结时,把当前的写地址锁存到一个寄存器里,这就是触发地址。显示模块计算坐标时,必须用这个地址作为水平映射的基准偏移量。这一步容易忽略,但它是整个显示逻辑里最后一段需要厘清的关系。

6. 验收与答辩:从测试清单到演示流程,一套完整通过的方法

设计做完不等于项目结束,验收和答辩才真正检验方案成立。先列一份测试清单,逐项打钩。第一项是采样率档位验证:把拨码开关依次切到 1MHz、10MHz、50MHz,分别输入一个已知频率的方波信号,屏幕上显示的时间窗口宽度应该按对应比例变化。第二项是触发功能验证:输入信号设为重复脉冲,触发通道选固定通道,拨动触发位置档位,观察波形在屏幕上的位置是否跟随变化。第三项是通道隔离验证:同时给 8 个通道输入不同的方波,屏幕上应显示 8 条独立的波形,互不串扰。第四项是长时间稳定性:连续运行 30 分钟,画面无闪烁、无漂移,采样数据不回卷异常。

答辩时最容易出问题的是演示流程设计。很多同学一上来就展示波形,结果导师问为什么波形是这个形状时,答不上来。更稳的顺序是:先讲需求——示波器通道不够用、买逻辑分析仪成本高,所以自己做一个;再讲架构——一张框图说明采样、存储、显示三层的分工;然后现场演示触发功能,因为这是逻辑分析仪相对普通波形显示的核心卖点;最后展示资源占用报告和时序分析结果,证明方案在硬件上站得住。演示时控制在 10 分钟内,重点放在触发前后波形变化这个瞬间,最好提前用开发板把触发位置调到 50% 档位,让波形左右对称,视觉效果最有说服力。

答辩中有个高频问题:为什么不直接用现成的逻辑分析仪软件加一个采集卡?答案不在技术本身,而在方案价值——这套设计完整覆盖了从信号采集到显示输出的全链路,展示了时序设计、存储器管理、外设接口三方面的工程能力。回答时抓住这一点展开即可。还有导师会问采样率和发生存储深度的关系,把第 2 章那笔账重新算一遍,比背诵概念更有说服力。

最后说一个自己的血泪经验。某次交项目前,自认为仿真已经跑通,只演示了正弦波输入下的显示效果,结果被问到触发功能时手忙脚乱。从那以后我每次验收强制走一遍完整流程:先拨采样率档位,再设定触发通道,然后切换触发位置,最后才展示多通道输入。这四步一次不落,已经成了习惯。项目能不能过,往往就在这些细节里。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询