恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
VS Code + iverilog + GTKWave:轻量级FPGA仿真验证指南
首页
资讯中心
/
VS Code + iverilog + GTKWave:轻量级FPGA仿真验证指南
VS Code + iverilog + GTKWave:轻量级FPGA仿真验证指南
发布时间:2026/10/7 1:23:59
做FPGA开发很多人第一时间想到的就是打开Vivado或者Quartus那套大块头IDE。代码改完想看一眼功能仿真波形光是等综合跑完就要喝两杯水。我自己试过一段时间用VS Code写Verilog再用iverilog做功能仿真配合GTKWave拉波形整个验证流程又轻又快尤其适合学习阶段和中小规模的模块验证。这篇文章就聊聊这套组合怎么搭、怎么用以及我踩过的一些坑希望能帮你少走点弯路。先说明一点这套方案解决的是“RTL写代码”和“功能仿真验证”这两个环节。真正要去综合、布局布线、上板调试还是绕不开Vivado、Quartus这类的厂商EDA工具。把两者的边界搞清楚后面用起来就不会产生错误预期。1. 为什么用VS Code iverilog做FPGA开发验证1.1 厂商IDE的痛点和轻量工具的真香之处我最早学FPGA的时候是从Vivado入门的。新建一个工程、添加一个文件、启动综合每个步骤都要等。最难受的是改完一个模块想跑一下仿真光等工程加载和综合启动就能耗掉好几分钟。如果是验证一个很简单的状态机用这种方式效率真的低。VS Code这边就是另一套体验启动快界面干净插件生态丰富。把它当成Verilog代码编辑器配合iverilog做的仿真验证整个链路全在命令行和编辑器里完成非常轻。你写完代码直接在终端敲两条命令波形就出来了这种即时反馈对学习Verilog的帮助非常大。个人体验是从“写完代码”到“看到波形”平均不到三秒这在厂商IDE里是不敢想的。iverilog这个工具很多人叫它Icarus Verilog是一个开源的Verilog仿真器和编译器。它支持Verilog-2001和很大一部分SystemVerilog语法配合GTKWave看波形完全能满足大多数教学场景和中小规模数字电路设计的验证需求。对于学生做课程设计、入门学习Verilog语法、验证组合逻辑和时序逻辑这套组合真的够用。1.2 职责边界iverilog只做仿真不做综合这里想重点强调一个概念因为经常有人混淆。FPGA开发的完整流程大约是设计输入RTL代码→ 功能仿真 → 综合 → 布局布线 → 时序分析 → 生成比特流 → 下载调试其中“功能仿真”就是用仿真器运行你写的Verilog代码检查逻辑行为是否符合预期。而“综合”是把Verilog代码映射成FPGA内部的查找表LUT、触发器FF和布线资源这个过程必须使用厂商提供的综合工具。iverilog在这个流程里的定位非常明确只管编译和功能仿真。它不做综合也不做布局布线更不会给你生成比特流。你可以用iverilog验证一个模块的逻辑是否正确但没法用它把设计烧到FPGA里。所以一套完整的学习流程应该是在VS Code里写RTL和Testbench用iverilog做功能仿真验证逻辑然后再打开厂商工具做综合和上板。提示如果你只是想把Verilog语法和基本逻辑设计练熟不必一开始就装那几GB的厂商IDE先用iverilog把仿真跑顺后面再切到Vivado或者Quartus会轻松很多。2. 环境搭建VS Code、iverilog与GTKWave2.1 安装iverilog并验证命令行可用先说Linux下的安装方式因为很多人在服务器上或者用WSL做FPGA相关学习。Ubuntu/Debian系直接用包管理器安装就行sudo apt install iverilog gtkwave如果用的是其他发行版比如Arch可以用pacman装iverilog和gtkwave。macOS用户可以用Homebrewbrew install icarus-verilog gtkwaveWindows用户稍微留意一下。iverilog官网有提供Windows安装包安装的时候记得选对版本装完以后最好手动检查一下环境变量是否配好。安装完成之后打开命令行工具验证iverilog -V如果输出类似“Icarus Verilog version 12.0”说明安装成功。如果提示“iverilog不是内部或外部命令”那就是环境变量没配好需要在系统环境变量里把iverilog的bin目录加进去。Windows的默认安装路径一般是C:\iverilog\bin你可以去确认一下。GTKWave是波形查看工具会单独安装。Linux下用命令直接装Windows下同样有安装包。装完后在命令行输入gtkwave --version能正常输出版本号就行。2.2 装好VS Code插件体验直接拉满VS Code本体装好以后需要装几个关键的FPGA开发插件不然纯文本编辑Verilog体验跟用记事本差不多。第一个是Verilog-HDL/SystemVerilog插件扩展ID是mshr-h.veriloghdl。这个插件提供语法高亮、代码补全、格式化、模块例化模板生成等功能。装完之后打开.v文件你会立刻感觉到代码有了颜色缩进也顺眼了很多。它还能帮你快速跳转到模块定义、信号定义这个在阅读别人代码时非常有用。第二个是TerosHDL。这个插件功能更强能管理工程文件列表提供基于字典的自动代码生成还能集成仿真脚本。它的界面比单纯开个文件目录要专业一些适合用来管理多文件工程。不过如果你只是写单个模块做验证装第一个插件其实就够用了。还有两个插件可以根据需求选装一个是代码格式化插件Verilog Format用来统一代码风格一个是Code Runner方便在VS Code的终端里一键运行编译和仿真命令。我个人习惯是把编译仿真脚本直接写进VS Code的Task里后面会详细讲那个方式比Code Runner更可控。2.3 最小验证流程从Verilog到波形环境配好之后拿一个最简单的例子把整个链路跑通。先写一个功能模块这里我用最简单的与门和计数器组合来演示module counter #( parameter W 4 )( input wire clk, input wire rst_n, output reg [W-1:0] cnt ); always (posedge clk or negedge rst_n) begin if (!rst_n) cnt d0; else cnt cnt 1b1; end endmodule然后写一个对应的Testbenchtimescale 1ns / 1ps module tb_counter; reg clk; reg rst_n; wire [3:0] cnt; initial begin clk 0; forever #10 clk ~clk; end initial begin rst_n 0; #100; rst_n 1; end initial begin $dumpfile(counter.vcd); $dumpvars(0, tb_counter); #500; $finish; end counter #( .W(4) ) u_dut ( .clk (clk), .rst_n (rst_n), .cnt (cnt) ); initial begin $monitor(time%t, rst_n%b, cnt%d, $time, rst_n, cnt); end endmodule在命令行进入文件所在目录依次执行iverilog -g2012 -s tb_counter -o sim.vvp counter.v tb_counter.v vvp sim.vvp gtkwave counter.vcd第一步是编译把counter.v和tb_counter.v一起编译成sim.vvp。第二步是运行仿真生成counter.vcd波形文件。第三步是用GTKWave打开波形。在GTKWave窗口左侧选择tb_counter模块然后把cnt、clk、rst_n这些信号拖到波形区就能看到计数器的波形了。跑通这个流程你就已经掌握了iverilog的基本用法。后面不管写多复杂的模块本质都是这三步编译、仿真、看波形。3. 实操案例数码管动态显示模块从设计到仿真3.1 动态显示原理与参数计算跑通最小例子以后要看看一个稍微有实际意义的例子。数码管动态显示是一个很典型的FPGA入门设计很多开发板的学习例程里都有。它要解决的核心问题是用有限的IO口驱动多位数码管。数码管由段选和位选两部分组成。段选决定显示的数字比如显示0还是1位选决定当前点亮第几位。如果每一位数码管都需要独立的段选线那4位数码管就需要4个位选加上8个段选如果再算上小数点就是9个段选。动态扫描的核心思路就是分时切换位选让4位数码管轮流点亮只要切换速度足够快人眼会因为视觉暂留效应感觉4位是同时亮的。这里就涉及一个关键参数扫描刷新频率。如果整体刷新率太低人眼会看到明显闪烁如果太高单个数码管的占空比太低整体亮度会下降。工程上通常把整体刷新率做到100Hz以上。我这里设计的模块默认用16位计数器输入时钟50MHz计数器溢出频率是50MHz除以65536约等于762Hz。也就是说每隔约1.31毫秒切换一次位选4位完整循环一次约5.24毫秒整体刷新率约190Hz这个参数下视觉效果很稳定不会闪。仿真的时候要注意如果照搬这个参数仿真器要跑大约5毫秒才能看到完整的一轮4位扫描循环。虽然软件仿真能跑但每次仿真要等的时间会变长VCD文件的体积也会变大。所以我通常会把计数器位宽做成参数化默认是16位仿真例化时改成8位甚至更小。8位计数器溢出频率约195kHz完整4位循环只需要约20.5微秒仿真器瞬间就能跑完。这个技巧非常实用属于仿真提速的经典做法。3.2 RTL代码实现下面这段代码实现了一个4位数码管动态显示模块显示范围只是简单地把0到3计数你也可以修改成其他数字。module seg_display #( parameter CNT_W 16 )( input wire clk, // 50MHz input wire rst_n, // 低电平复位 output reg [7:0] seg, // 段选共阴极段码 output reg [3:0] an // 位选低电平有效 ); reg [CNT_W-1:0] cnt; reg [2:0] scan_cnt; // 分频计数器 always (posedge clk or negedge rst_n) begin if (!rst_n) cnt d0; else cnt cnt 1b1; end // 扫描位计数 always (posedge clk or negedge rst_n) begin if (!rst_n) scan_cnt 3d0; else if (cnt {CNT_W{1b1}}) scan_cnt scan_cnt 1b1; else scan_cnt scan_cnt; end // 位选依次选中第0、1、2、3位数码管 always (*) begin case (scan_cnt) 3d0: an 4b1110; 3d1: an 4b1101; 3d2: an 4b1011; 3d3: an 4b0111; default: an 4b1111; endcase end // 当前位要显示的数字这里固定显示 1、2、3、4 // 你也可以扩展成计数器或者其他逻辑 reg [3:0] num; always (*) begin case (scan_cnt) 3d0: num 4d1; 3d1: num 4d2; 3d2: num 4d3; 3d3: num 4d4; default: num 4d0; endcase end // 共阴极七段码查表0-9以及无显示 always (*) begin case (num) 4d0: seg 8h3F; // 0 4d1: seg 8h06; // 1 4d2: seg 8h5B; // 2 4d3: seg 8h4F; // 3 4d4: seg 8h66; // 4 default: seg 8h3F; // 0 endcase end endmodule这段代码用三个always块分别做了分频计数、位选切换、段码输出。需要注意位选这里用的是“低电平有效”这是很多开发板三极管驱动电路的真实接法。段码用的是“共阴极”的表也就是说段选输出高电平时对应的段点亮。如果你用的板子是共阳极数码管段码值需要取反或者换一套表这个上板前一定要查清楚。3.3 Testbench设计与波形验证功能仿真的Testbench如下timescale 1ns / 1ps module tb_seg_display; reg clk; reg rst_n; wire [7:0] seg; wire [3:0] an; // 50MHz时钟 initial begin clk 0; forever #10 clk ~clk; end // 复位 initial begin rst_n 0; #100; rst_n 1; end // 波形输出 initial begin $dumpfile(seg_display.vcd); $dumpvars(0, tb_seg_display); #80000; // 仿真80us $finish; end // 例化被测模块仿真时把计数位宽改成8位加速扫描 seg_display #( .CNT_W(8) ) u_dut ( .clk (clk), .rst_n (rst_n), .seg (seg), .an (an) ); // 文本监控 initial begin $monitor(time%t, an%b, seg%h, $time, an, seg); end endmodule这里例化时把CNT_W改成8能让扫描切换速度大幅提高在80微秒内可以看到多轮完整的位选切换。编译、仿真、打开波形的命令和前面最小例子一样iverilog -g2012 -s tb_seg_display -o sim.vvp seg_display.v tb_seg_display.v vvp sim.vvp gtkwave seg_display.vcd打开波形后重点观察两个信号an和seg。正常情况下an会以一定周期循环变成1110、1101、1011、0111同时seg的值也对应在0x06、0x5B、0x4F、0x66之间切换。把这几个时刻对应起来你会发现an选中的位和seg显示的数字是匹配的这就说明动态扫描逻辑正确了。提示动态扫描的时序在真实硬件里有一个需要注意的“消隐”问题。当位选从第0位切换到第1位时如果段选数据和位选切换在同一拍发生由于组合逻辑路径延时不同可能出现短暂的“串位”现象也就是上一位的数字残影到了下一位。工程上常用做法是先关闭所有位选更新段选数据再打开新的位选或者对位选用寄存器打一拍延迟。仿真时不加消隐一般也能通过但上板时最好处理一下。4. 在VS Code里把编译仿真做成一键操作4.1 配置tasks.json每次都在终端里手敲三条命令写多了确实累。VS Code的Task功能可以把这些命令固化下来按一下快捷键就能跑这个过程非常值得配置一次。在工程根目录下创建.vscode文件夹里面新建tasks.json参考配置如下{ version: 2.0.0, tasks: [ { label: iverilog: compile sim, type: shell, command: iverilog -g2012 -s tb_seg_display -o sim.vvp seg_display.v tb_seg_display.v vvp sim.vvp, group: { kind: build, isDefault: true }, problemMatcher: [] }, { label: gtkwave: open waveform, type: shell, command: gtkwave seg_display.vcd, group: build } ] }配置好之后在VS Code里按CtrlShiftB就会直接执行编译加仿真接着在命令行工具面板手动执行第二个任务打开波形。如果你想一次到底可以把gtkwave也拼进第一个任务的command后面中间用连接。不过我个人习惯分开因为有些场景需要快速改代码重新编译但不需要每次都重新打开波形。Windows用户要注意如果gtkwave命令没有加入系统PATH需要在command里写完整路径比如C:\Program Files\gtkwave\bin\gtkwave.exe。如果iverilog没加入PATH同样处理。4.2 工程目录与多文件组织模块多了以后建议用规范的目录结构管理文件。我这里提供一个好用的参考结构project/ ├── .vscode/ │ └── tasks.json ├── rtl/ │ ├── seg_display.v │ └── counter.v ├── sim/ │ ├── tb_seg_display.v │ └── tb_counter.v └── out/rtl目录放RTL设计文件sim目录放Testbenchout目录放编译产生的vvp文件和vcd波形文件。编译命令改成iverilog -g2012 -s tb_seg_display -o out/sim.vvp rtl/seg_display.v sim/tb_seg_display.v vvp out/sim.vvp gtkwave out/seg_display.vcd用这样的结构源码目录不会堆满中间文件找东西方便很多。多文件工程的另一个关键是顶层模块指定。iverilog编译时如果存在多个模块定义你需要用-s参数指定仿真顶层否则仿真器默认以文件列表的第一个模块作为顶层。Testbench理论上应该在最前面或者干脆每次都用-s显式指定这个习惯能避免很多意想不到的问题。4.3 实用的VS Code调试技巧在VS Code里写Verilog有几个小技巧能明显提升效率。一个是格式化。Verilog代码格式不一致的时候右键选择Format Document插件会自动整理缩进和对齐。多人协作或者回头再看自己代码的时候这个功能很救命。另一个是模块例化模板。在Verilog-HDL插件里你把光标放到模块名上右键选择生成实例化代码它会自动生成带信号连接的例化模板然后你只需要手动把端口信号填进去就行。对经常要写Testbench的人来说这个省了很多时间。还有一个是用CtrlShiftF全局搜索信号名。当工程文件多了一个信号可能在顶层、子模块、Testbench里都出现全局搜索比肉眼找可靠得多。尤其遇到位宽不匹配、信号名拼错这类问题全局一搜就能看出来。5. 常见问题与排查技巧5.1 编译报错类明明照着写的怎么还编译不过我用iverilog的过程中遇到过最多的编译错误基本是这几类。下面整理成一张速查表方便你遇到问题的时候直接对照。现象可能原因处理方式编译时提示缺少timescale文件没有声明时间单位在Testbench第一行加timescale 1ns / 1ps提示logic、always_ff不认识用了SystemVerilog语法但没开开关编译时加-g2012参数提示模块找不到编译命令里漏了某个.v文件把所有设计文件路径都加到编译命令里提示信号未定义信号名拼写错误或写成大写用VS Code的全局搜索复查信号名提示重复定义两个文件里定义了同名模块或同名宏检查文件是否有重复include一个比较隐蔽的坑是时序相关的时间单位问题。如果Testbench里没有写timescaleiverilog会用默认的时间刻度1s。这个时候你用#10表示延时10秒波形轴看着就非常离谱。我自己早年就踩过这个坑仿真结果完全对不上最后才发现是timescale没写。5.2 仿真结果不对类波形出来了但输出跟预期不一样如果编译完全正常但波形不符合预期先从下面几个方向排查。首先检查Testbench里的复位时序。复位信号的有效宽度是否足够复位释放后有没有给时钟留足时间建议在Testbench里先设计100ns左右的复位时间等复位释放后再观察输出。其次检查时钟。forever #10 clk ~clk这条语句在仿真开始后就会一直翻转如果你忘了写$finish仿真器会永远跑下去终端窗口会一直有输出。常见做法是在仿真时间到达预期观察窗口之后用$finish主动结束仿真比如#80000; $finish;。再有一个常见问题是波形文件为空。这个基本都是没有在Testbench里写$dumpfile和$dumpvars或者dumpvars写错了层级。建议直接写$dumpvars(0, 模块名)其中模块名用Testbench的顶层实例名。GTKWave打开波形时可能一开始窗口里什么都没有。这时候需要在左侧SST区域选中目标模块然后在中间的信号列表里把信号拖拽到右侧波形区。很多新手在这里卡住以为仿真正出了问题其实是GTKWave的操作问题。5.3 数码管动态显示硬件上的典型问题数码管动态显示在仿真里能跑通不代表在上板一定正常硬件上有几个问题几乎每个人都遇到过。一个是段码表选错。共阳极和共阴极的段码完全不一样如果你的板子是共阳极但代码里用了共阴极段码显示出来的数字会错乱甚至有些段会反向。上板前先查开发板原理图确认共阳还是共阴这是最基本的。第二个是位选极性反了。见很多开发板的位选通过PNP三极管控制低电平有效有些板子则可能是高电平有效。如果一个位都不亮或者所有位同时亮乱跳先检查位选的逻辑极性是不是和板子一致。第三个是刷新率不合理。如果你的扫描切换得太慢单个数码管点亮时间变长人眼能看到明显的分时闪烁如果切换太快单个LED的占空比太低整体亮度不够。我前面把刷新率做到190Hz左右就是一个比较稳的中间值实际板子上可以根据亮度适当调整。第四个是消隐问题。位选和段选切换不同步会造成“鬼影”也就是下一位显示上一位的数字。前文提到的打一拍或者先关位选再更新段选再开位选的方案在实际硬件上几乎是必须的。5.4 关于IP核、原语与系统任务iverilog虽然好用但也有限制。Xilinx的BUFG、MMCM/PLL、DSP48这些原语以及Vivado的很多IP核在iverilog里是没法直接仿真的。因为这些是厂商针对自家FPGA硬件实现的专用模块iverilog作为开源工具并没有对应的仿真模型。遇到这种情况你有两个选择要么在Testbench里写一个行为级的仿真模型替代这些原语要么把这一层功能放到厂商EDA工具里仿真。另外iverilog对系统任务的支持也不是百分百完整。$readmemh和$writememh这类文件读写任务可以用但某些厂商扩展的系统任务可能会被忽略或者报错。遇到这类情况我的建议是把“仿真相关的代码”和“可综合的RTL代码”严格分文件管理。Testbench里用到的仿真任务不要混进RTL代码否则综合工具也会报错。6. 工具选型与进阶方向6.1 免费EDA仿真工具对比现在市面上可用的免费Verilog仿真工具不少我用过的有iverilog、ModelSim-Intel FPGA Starter Edition、赛灵思的XSim等。它们各自适合不同场景下面这张表可以做个参考。工具安装体积支持语言适用场景iverilog GTKWave小几十MBVerilog-2001、部分SystemVerilog快速验证、学习、模块级仿真ModelSim-Intel Starter较大需注册Verilog、VHDL部分SystemVerilogAltera/Intel平台配套验证Vivado XSim极大跟Vivado捆绑Verilog、VHDL、SystemVerilogXilinx平台完整流程验证VCS/Questa等商业工具很大贵全面工业级复杂验证如果你现在刚入门或者在做硬件课程设计我建议先上手iverilog这套轻量组合。如果你用的是Altera/Intel的开发板后面可以顺便装个ModelSim-Intel Starter Edition因为Quartus里可以直接调用它跑仿真流程上更顺。但我个人还是喜欢先在iverilog里快速验证逻辑再放到厂商工具里跑完整流程两头兼顾。6.2 哪些工作必须回到厂商IDE虽然iverilog很好用但有几件事是它做不了的必须换回厂商工具。第一是综合和布局布线。iverilog只做编译和行为仿真不做逻辑综合也没有布局布线器。想生成比特流、下载到FPGA必须用Vivado、Quartus或者其他厂商EDA。第二是时序约束和时序分析。FPGA设计里时钟约束、输入输出延迟约束这些供应商工具提供了完整的约束输入和静态时序分析报告。iverilog完全没有这些能力。第三是IP核和厂商原语的仿真与集成。前面提到了MPLL、DSP、Block RAM等IP核以及DDR、PCIe等高速接口都需要厂商工具生成并做较完整的仿真验证。用iverilog替代不了这一层。第四是板级调试。在线逻辑分析仪比如Xilinx ILA、Intel SignalTap、硬件管理器、引脚分配这些操作全部发生在厂商IDE里。上板调试阶段你必须打开Vivado或Quartus。所以我日常的真实工作流是在VS Code里用iverilog快速验证RTL逻辑的正确性逻辑稳了再进厂商IDE做综合、实现、上板。这个习惯既保证了开发效率又不会因为跳过厂商工具导致阶段性问题。6.3 后续可以延伸的方向iverilog这套轻量仿真流程不止能跑数码管这种小模块。我身边有同事用它来做图像处理算法的仿真思路是在Testbench里用$readmemh把一张图片的RGB数据读入寄存器数组经过Verilog实现的卷积、滤波等操作后再用$writememh把结果写出来最后用Python脚本把数据还原成图片看效果。这个流程完全可以在iverilog里跑通比一上来就接厂商IP端高效很多。还有做接口协议验证的比如MIPI、UART、SPI、I2C这些协议先写一个行为级Testbench把时序波形验证清楚再去FPGA上对着真实硬件调会省下大量抓波形的时间。做RISC-V软核学习的人也会用iverilog跑编译好的二进制固件观察CPU内部寄存器跳转情况。这种处理器级仿真用开源工具做其实非常普遍。如果你对FPGA有进一步兴趣后面可以沿着图像处理、RISC-V、MIPI高速接口这些方向去学。这些方向有一个共同点都需要先把RTL算法逻辑用仿真验证明白。而仿真验证这一环用VS Code加iverilog这套组合就是很好的起点。最后分享一点个人习惯我到现在做模块开发依然会在VS Code里完成大部分RTL编写和本地仿真等逻辑稳定了才交给Vivado做综合实现。这样来回折腾厂商IDE的次数少了很多心情也好不少。希望这篇文章能让你少踩几个坑愉快地走上FPGA这条路。