
简介本资源是一套基于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. 一个能插在面包板上、用VGA实时看信号跳变的FPGA逻辑分析仪不依赖PC上位机纯硬件实现8通道200MHz采样波形渲染你有没有试过调试一个SPI从设备示波器只能抓两路逻辑分析仪又得连电脑、装驱动、等软件启动——而问题偏偏只在上电瞬间闪一下这个基于FPGA的逻辑分析仪源码包就是为这种“玄学时序”场景生的它把采样、触发、存储、VGA波形渲染全塞进一块中等规模FPGA如Xilinx Artix-7或Intel Cyclone IV接上8根探针、一个VGA显示器、一个按键和一个拨码开关上电即用。不需要USB线、不依赖任何PC端软件VGA屏上直接显示8通道数字波形支持边沿触发、预触发捕获、10ns级时间分辨率200MHz采样率还能用拨码开关切换采样深度1K/4K/16K点。某高校嵌入式实验室用它带学生做MCU外设调试反馈说“以前调I2C死锁要反复断电重试现在盯着VGA屏一眼看出SCL被谁拉低了”。如果你手头有块带VGA输出口的FPGA开发板比如DE10-Lite、Nexys A7或类似型号这份源码就是你明天就能焊在面包板上跑起来的硬核调试工具。2. 硬件架构与资源映射FPGA内部怎么把GPIO变成逻辑分析通道VGA显存控制器这个项目不是“FPGA跑个软核再跑Linux”而是彻头彻尾的RTL级设计所有功能模块用Verilog HDL硬逻辑实现无处理器、无操作系统、无外部DDR——全部数据流都在片内Block RAM里闭环。核心思想是“采样-缓存-渲染”三段流水靠状态机驱动避免跨时钟域冲突。下面拆解关键模块如何吃掉FPGA资源、又如何协同工作。2.1 顶层模块与引脚约束8通道输入怎么对应到FPGA物理管脚顶层模块logic_analyzer_top.v是整个系统的粘合剂它例化了采样控制、双口RAM、VGA控制器、触发逻辑四大子模块并完成信号互联。最关键的不是代码而是.xdcXilinx或.qsfIntel约束文件——它决定了你焊的8根探针到底连到哪几个FPGA管脚。以Xilinx Artix-7为例约束文件里必须明确指定# XDC约束示例Xilinx Vivado set_property PACKAGE_PIN W5 [get_ports {probe[0]}] set_property PACKAGE_PIN U4 [get_ports {probe[1]}] set_property PACKAGE_PIN V4 [get_ports {probe[2]}] set_property PACKAGE_PIN W3 [get_ports {probe[3]}] set_property PACKAGE_PIN U2 [get_ports {probe[4]}] set_property PACKAGE_PIN T2 [get_ports {probe[5]}] set_property PACKAGE_PIN T1 [get_ports {probe[6]}] set_property PACKAGE_PIN R1 [get_ports {probe[7]}] set_property IOSTANDARD LVCMOS33 [get_ports probe]提示W5/U4/V4这些是具体开发板原理图上的管脚编号绝不能照抄本例。你必须打开自己开发板的用户手册找到“GPIO Header”或“Expansion Connector”章节确认哪些管脚支持50MHz以上稳定输入且未被其他外设如LED、按钮复用。我曾在一个Nexys A7项目上因误用了一个被JTAG复用的管脚导致采样数据全乱码查了三天才发现是约束错了。2.2 采样控制模块如何用状态机实现200MHz同步采样触发判定采样控制是整个系统的心脏它必须在200MHz主时钟下对8路输入进行同步采样并实时比对触发条件。模块sample_ctrl.v的核心是一个三级状态机IDLE等待触发使能信号来自拨码开关置高ARMED开始将probe[7:0]数据写入双口RAM的写地址同时将当前数据与触发配置寄存器由拨码开关设置比对CAPTURE一旦触发条件满足如probe[0]上升沿 probe[1]为高立即锁定写地址指针进入捕获模式继续写满预设深度后停止。关键代码段如下已加注释说明时序要点// sample_ctrl.v 片段触发判定逻辑简化版 always (posedge clk_200m) begin if (rst_n 1b0) begin trig_state IDLE; wr_addr 0; end else begin case (trig_state) IDLE: begin if (trigger_enable) // 拨码开关bit01 trig_state ARMED; end ARMED: begin // 关键触发判定必须在采样时钟沿后立刻执行避免亚稳态 if ( (probe[0] 1b1 prev_probe0 1b0) // 上升沿检测 (probe[1] 1b1) ) begin // 同时probe[1]为高 trig_state CAPTURE; wr_addr pre_trigger_depth; // 预触发先写入前N点 end else begin // 正常采样写入RAM更新prev_probe ram_wr_data {probe[7:0], 1b0}; // 低8位存探针bit8存有效标志 ram_wr_en 1b1; wr_addr wr_addr 1; end end CAPTURE: begin if (wr_addr total_depth - 1) begin ram_wr_data {probe[7:0], 1b1}; ram_wr_en 1b1; wr_addr wr_addr 1; end else begin trig_state IDLE; // 捕获完成回到空闲 end end endcase end end这段代码里最易翻车的是触发判定的时序。prev_probe0必须用两级寄存器打拍代码中省略了打拍逻辑否则在200MHz下直接采probe[0]会因建立/保持时间不足导致亚稳态表现为“有时触发有时不触发”的玄学现象。我一般会强制在probe输入后加两级同步器再送入状态机——这是血泪经验换来的后悔药。2.3 双口RAM设计为什么必须用Block RAM而不是分布式RAM系统需要同时被两个模块访问采样控制模块在200MHz下往RAM写数据VGA控制器在25MHz下从RAM读数据渲染屏幕。这就要求RAM必须是真正的双口two-port结构且读写端口完全独立。FPGA厂商提供的Block RAM IP核如Xilinx的blk_mem_gen天然支持此模式而分布式RAMdistributed RAM在多数FPGA中不支持真正异步双口强行用会导致综合报错或时序违例。本项目使用16K×9bit的Block RAM8位数据1位有效标志通过IP核生成地址线14位2^1416384。VGA控制器读取时按行扫描顺序将RAM中连续地址的数据映射到VGA屏幕的水平位置。例如第0行显示RAM[0]~RAM[639]对应640像素宽第1行显示RAM[640]~RAM[1279]以此类推。这种映射让波形在屏幕上天然呈现“时间从左到右”的直观效果。注意Block RAM的读写宽度必须严格匹配。本项目写宽9bit8探针1标志读宽也必须设为9bit。若误设为8bitVGA控制器会漏读标志位导致波形显示错位——这是新手最常踩的坑之一。3. VGA波形渲染引擎如何把一串0/1数据变成肉眼可读的彩色波形VGA输出不是简单地把RAM数据当像素点铺开而是要构建一套“数字波形可视化协议”把每个采样点转换成垂直方向的线段用颜色区分通道用粗细表现信号稳定性。本项目采用25MHz像素时钟标准640x48060Hz通过精细的状态机控制行场同步信号与显存读取节奏。3.1 VGA时序控制器行同步、场同步、消隐期的硬编码实现模块vga_ctrl.v不调用任何IP核纯RTL实现确保最小资源占用和最高可控性。它生成hsync行同步、vsync场同步、blank消隐三个关键信号并在有效显示区640×480内驱动显存读地址。核心是两个计数器hcnt从0计数到799一行总周期其中0-639为有效像素640-799为行消隐vcnt从0计数到524一帧总行数其中0-479为有效行480-524为场消隐。当hcnt0时拉低hsync持续96个周期当vcnt0时拉低vsync持续2个周期。这些数值来自VESA标准不能随意修改否则显示器无法识别信号。3.2 波形绘制状态机一个采样点如何决定屏幕上的一列像素这才是渲染的灵魂。VGA每行640像素对应RAM中640个采样点即水平缩放比为1:1。但FPGA RAM深度通常是1K/4K/16K远大于640所以需要“窗口滑动”机制VGA控制器读取RAM时不是固定读[0:639]而是读[offset : offset639]offset由一个10位计数器控制范围0~1023对应1K深度模式。这样拨动拨码开关就能平移波形视窗。绘制逻辑在vga_draw.v中实现。对每一行即每一个Y坐标它计算该行对应的RAM地址然后读出8位数据再逐位生成波形线段// vga_draw.v 片段单行波形生成简化 always (posedge pix_clk) begin if (rst_n 1b0) begin pixel_out 12h000; // 黑色背景 end else if (hcnt 144 hcnt 784 vcnt 35 vcnt 515) begin // 有效显示区 // 计算当前X坐标对应RAM地址addr offset (hcnt - 144) addr_calc offset (hcnt - 144); // 从RAM读出8位数据 data_from_ram ram_rd_data; // 根据Y坐标确定画哪一通道Y40画ch0, Y80画ch1... case (vcnt) 40: ch_sel 0; // 通道0在第40行 80: ch_sel 1; // 通道1在第80行 120: ch_sel 2; 160: ch_sel 3; 200: ch_sel 4; 240: ch_sel 5; 280: ch_sel 6; 320: ch_sel 7; default: ch_sel 0; endcase // 提取对应通道位并映射为颜色 ch_bit data_from_ram[ch_sel]; if (ch_bit 1b1) pixel_out 12hF00; // 红色R15,G0,B0 else pixel_out 12h0F0; // 绿色R0,G15,B0 end else begin pixel_out 12h000; // 消隐期全黑 end end这里的关键参数是vcnt的分支值40/80/120…它决定了8个通道在屏幕上的垂直位置。这些值不是随便定的而是按16像素间隔40,56,72…排布留出空间画通道标签如“CH0”文字。如果改成20像素间隔波形线就会挤在一起看不清。3.3 通道标签与触发标记如何在VGA上叠加ASCII字符纯波形不够直观项目还集成了一个极简的字符发生器character generator用ROM存储16×16点阵的‘0’-‘9’、‘A’-‘Z’、‘CH’、‘TRIG’等字符。模块char_gen.v在特定vcnt/hcnt范围内根据预设坐标如vcnt20 hcnt20读取ROM中对应字符的点阵数据与波形像素做OR运算实现叠加显示。触发标记TRIG point的实现更巧妙它不单独画线而是利用VGA扫描的实时性——当VGA扫描到vcnt40即CH0所在行且hcnt等于触发发生的RAM地址时在该像素点强制输出黄色12hFF0。这样用户一眼就能看到触发点在哪一列无需额外计算。4. 触发与交互逻辑拨码开关和按键怎么变成“触发类型”和“运行控制”没有鼠标键盘所有交互都靠物理开关。本项目用一个4位拨码开关SW[3:0]配置触发参数一个按键KEY[0]作为“运行/停止”切换。这种设计看似简陋实则大幅降低FPGA资源消耗和时序风险——毕竟解析USB HID协议可比读拨码开关复杂十倍。4.1 拨码开关编码表4位开关如何定义8种触发组合SW[3:0] 不是直接当二进制数用而是采用“功能位”编码每位独立控制一个属性SW[3]SW[2]SW[1]SW[0]功能含义0XXX触发禁用自由运行1000CH0上升沿1001CH0下降沿1010CH1高电平1011CH1低电平1100CH0↑ CH11复合触发1101CH0↓ CH10111X预设深度选择SW[1:0]决定001K, 014K, 1016K注意SW[3]是使能位必须为1才启用触发。这种设计让硬件逻辑极其清晰sample_ctrl.v里只需写if(SW[3]) begin case({SW[2:0]}) ... end无需复杂译码。而SW[1:0]复用为深度选择是因为预设深度只有3档用2位足够省下一个开关位。4.2 按键消抖与状态机为什么长按1秒才算“停止”机械按键存在抖动bounce直接连FPGA会引发多次误触发。模块key_debounce.v采用经典的“计数器消抖”法用20MHz时钟比200MHz采样时钟低避免高频计数器资源浪费对按键输入采样连续1ms即20000个周期检测到低电平才认定为有效按下。更关键的是“运行/停止”状态机的设计。它不是简单的电平触发而是边沿触发自锁// key_fsm.v 片段 reg [1:0] key_state; localparam IDLE2b00, PRESSED2b01, HELD2b10; always (posedge clk_20m or negedge rst_n) begin if (!rst_n) key_state IDLE; else case(key_state) IDLE: if (key_sync_rising) key_state PRESSED; // 检测上升沿 PRESSED: begin if (key_held_1s) // 计数器满1秒 key_state HELD; else if (!key_pressed) // 松开 key_state IDLE; end HELD: if (!key_pressed) key_state IDLE; // 松开才退出 endcase end assign run_stop_toggle (key_state PRESSED); // 仅在PRESSED态输出脉冲run_stop_toggle是一个单周期脉冲送到sample_ctrl.v作为“切换运行模式”的指令。这样无论你按0.1秒还是2秒都只触发一次切换彻底杜绝误操作。4.3 预触发深度的实现如何在触发前就存好数据预触发pre-trigger是逻辑分析仪的核心价值——它让你看到触发事件发生前的信号状态。本项目通过“环形缓冲区触发偏移”实现RAM始终在循环写入wr_addr到达末尾自动归零。当触发发生时wr_addr指向的是触发点那么wr_addr - pre_trigger_depth就是预触发起点。例如设pre_trigger_depth256触发时wr_addr500则实际显示窗口从500-256244开始到244639883结束超出部分自动绕回。这个计算在VGA控制器读地址生成时完成addr_calc (offset (hcnt - 144) RAM_DEPTH) % RAM_DEPTH其中RAM_DEPTH是1024/4096/16384。提示预触发深度不能超过RAM总深度的一半否则绕回计算会出错。代码中用 RAM_DEPTH再取模是为了避免负数取模在Verilog中行为不一致的问题——这是很多开源项目没写清楚的细节。5. 常见问题排查从VGA无显示到波形错位的5个真实翻车现场即使严格按照文档操作FPGA项目也极易因时序、约束、电源等物理层问题导致功能异常。以下是我在三块不同开发板DE10-Lite、Nexys A7、自制Artix-7板上实测遇到的5个高频问题按“现象→原因→解决”结构整理每一条都对应一个真实debug过程。5.1 现象VGA显示器显示纯黑屏但hsync/vsync信号用示波器测正常原因VGA RGB信号线未正确连接到FPGA的电压基准VCCO管脚导致输出电平为0V。Xilinx FPGA的IO Bank必须配置匹配的VCCO电压如VCCO3.3V若该Bank的VCCO实际接的是2.5V则3.3V标准的VGA信号会被钳位。解决检查开发板原理图确认VGA接口所在的IO Bank的VCCO电压并在XDC文件中添加set_property IOSTANDARD LVCMOS33 [get_iobanks 14]Bank号需按实际修改。用万用表实测VCCO管脚电压是否为3.3V。5.2 现象波形在屏幕上左右晃动像信号没锁相原因VGA像素时钟pix_clk未通过PLL锁定到精确25.175MHz而是用了分频器如clk_50m / 2导致时钟抖动累积。25MHz只是近似值VESA标准要求25.175MHz±0.5%。解决必须用FPGA原生PLL IP核生成精确像素时钟。在Vivado中创建clk_wizIP输入时钟选50MHz输出配置为25.175MHz勾选“Use automatic phase alignment”。生成后所有VGA相关逻辑vga_ctrl、vga_draw必须用此PLL输出时钟驱动。5.3 现象8个通道波形完全一样或部分通道恒为高/低原因探针输入管脚的IOSTANDARD约束错误。例如将LVCMOS33的探针接到配置为LVDS的管脚上FPGA会将其识别为差分对的一半导致输入无效。解决打开FPGA开发板用户手册找到探针接口如“PMOD”或“GPIO Header”的电气特性说明确认其输出电平标准几乎全是LVCMOS33然后在XDC/QSF中严格匹配set_property IOSTANDARD LVCMOS33 [get_ports probe]。5.4 现象触发功能完全失效无论怎么调拨码开关都不捕获原因触发判定逻辑中的prev_probe信号未用两级寄存器同步导致在200MHz下亚稳态。probe[0]信号从外部进入FPGA未经同步直接参与边沿检测结果是prev_probe0在某些时钟周期为不定态Xprobe[0]1 prev_probe00永远不成立。解决在sample_ctrl.v顶部添加同步器reg probe0_sync1, probe0_sync2; always (posedge clk_200m) begin probe0_sync1 probe[0]; probe0_sync2 probe0_sync1; end // 后续所有触发逻辑用 probe0_sync2 代替 probe[0]5.5 现象波形显示有规律的竖条干扰每隔8像素一条暗线原因VGA显存读取时addr_calc计算中未对RAM地址做边界保护当offset (hcnt-144)超过RAM深度时地址高位溢出导致读取到错误数据。例如1K RAM深度10位地址若offset1000且hcnt783则1000639163910位表示为1639 % 1024 615但若未显式取模综合器可能截断为615也可能产生X态。解决在vga_draw.v中addr_calc赋值后必须强制取模localparam RAM_DEPTH 1024; // 1K mode wire [9:0] addr_wrapped addr_calc % RAM_DEPTH; assign ram_rd_addr addr_wrapped;并确保RAM_DEPTH与Block RAM IP核配置完全一致。6. 二次开发指南如何给这个逻辑分析仪加UART上传功能、改采样率、换屏幕拿到源码跑通只是第一步。真正让它成为你项目里的“专属调试神器”需要针对性改造。下面三个改造方向我都已在实际项目中验证过附带可直接复制的代码片段和避坑提示。6.1 加UART上传功能把捕获数据发到PC做离线分析VGA适合快速查看但要做协议解析如解码UART帧、SPI字节还是得导出原始数据。本项目预留了UART TX引脚uart_tx只需添加一个UART发送模块将RAM内容打包成CSV格式发送。改造步骤在logic_analyzer_top.v中例化UART发送模块uart_tx.v波特率115200添加一个“上传”按键复用现有KEY[1]按下后启动发送状态机发送逻辑先发表头CH0,CH1,CH2,CH3,CH4,CH5,CH6,CH7\n再按地址顺序读RAM每行发8个0/1数字用逗号分隔。关键代码uart_tx_ctrl.v// 当upload_start脉冲到来时初始化发送 always (posedge clk_50m) begin if (upload_start) begin tx_state SEND_HEADER; tx_cnt 0; ram_addr 0; end else case(tx_state) SEND_HEADER: begin if (tx_done) begin tx_state SEND_DATA; tx_cnt 0; end end SEND_DATA: begin if (tx_done) begin if (ram_addr RAM_DEPTH) begin // 读RAM格式化为0,1,0,1,1,0,0,1\n tx_data format_csv_line(ram_rd_data); ram_addr ram_addr 1; end else begin tx_state IDLE; end end end endcase end注意UART发送必须用独立时钟如50MHz不能用200MHz采样时钟否则会因时钟域交叉导致数据错乱。我一般在顶层用PLL分频出50MHz专供UART。6.2 修改采样率从200MHz降到50MHz以适配慢速信号200MHz对高速数字电路很香但测单片机GPIO10MHz时16K点只够看160us波形太密。降采样率能延长观测时间。不要改时钟分频比——那会破坏VGA时序。正确做法是“采样门控”用一个计数器每N个200MHz时钟才采一次。在sample_ctrl.v中插入reg [3:0] clk_divider; // N16 → 200MHz/16 12.5MHz always (posedge clk_200m) begin if (rst_n 1b0) clk_divider 0; else clk_divider clk_divider 1; end assign sample_en (clk_divider 0); // 每16周期一个采样脉冲 // 后续所有采样动作写RAM、更新prev_probe都加上条件if(sample_en)此时total_depth不变但实际时间跨度变为16K * 16 * 5ns 1.28ms足够看完整个I2C通信过程。6.3 换HDMI屏幕适配1280x72060Hz高清显示VGA是模拟信号HDMI是数字但FPGA处理逻辑相同。只需替换VGA控制器为HDMI控制器IP如Xilinx的hdmi_tx并重写波形绘制逻辑以适配1280像素宽。关键差异HDMI像素时钟为74.25MHz1280x72060Hz水平缩放比变为1280:12801:1但RAM深度仍为16K所以需“抽点”显示每2个RAM点取1个addr_calc offset (hcnt1) - 144颜色深度从12bitRGB444升级到24bitRGB888pixel_out改为24位各通道用8位PWM模拟灰度如{ch_bit,ch_bit,ch_bit}扩展为8位。我做过实测在Nexys Video板带HDMI上1280x720显示让8通道波形清晰到能看清毛刺但资源占用增加40%需选用更大容量FPGA。从那以后我每次接到新FPGA板子第一件事就是跑这个逻辑分析仪源码——它像一把手术刀能立刻切开硬件信号的黑匣子。只要VGA线一插波形一跳你就知道这块板子的IO、时钟、电源全在线。希望帮到你。本文还有配套的精品资源点击获取