
刚学 FPGA 那会儿我最容易被卡住的地方不是写不出计数器而是明明代码在脑子里跑得通一打开 Quartus II 的波形仿真就满屏是红的。更尴尬的是很多教程第一步就写着点 Processing → Start Simulation而我在自己装的版本里翻遍菜单都找不到这一项一度以为软件装坏了。这篇就把初步学习使用 Quartus II 波形仿真这件事从头到尾捋一遍VWF 波形文件怎么建、激励怎么画、功能仿真和时序仿真差在哪、以及最关键的——当你看到 ModelSim 里波形变成红线时它到底在告诉你什么。内容偏入门但我会把新手最容易忽略的细节和排查链路都写清楚照着做基本能一次跑通。1. 先把仿真这件事在 Quartus II 的流程里摆正位置1.1 仿真到底在防什么FPGA 开发和写软件最大的区别在于软件编译通过、跑起来不对你还能加打印、断点、单步而 FPGA 一旦烧进芯片内部信号是看不见的你只有几个引脚上的现象。所以行业里那句仿真省下来的时间最后都会以调板子的形式加倍还回去是真的。波形仿真在流程里的位置大概是在代码写完和综合布局布线之间用来验证两件事一是功能对不对——状态机跳转、时序逻辑的握手、计数边界是否符合预期二是时序能不能收敛——在考虑门延时和布线延时之后建立/保持时间还够不够。刚入门的人常犯的错是把这两件事混在一起。功能仿真时你在验证逻辑时序仿真时你在验证物理实现前者几分钟能跑完后者可能半小时起步。先做功能再做时序不要一上来就跑去跑带 SDF 反标的门级仿真那是给自己找麻烦。1.2 一个反直觉的事实Quartus II 13.1 之后就没有自带仿真器了这是我最想先讲清楚的一点。老教程里的标准动作是Processing → Generate Functional Simulation Netlist → Processing → Start Simulation这套流程依赖的是 Altera 自带的Quartus II Simulator。而从 Quartus II 13.1 开始官方把这个内置仿真器移除了最后一个还带它的版本是 13.0sp1 及其之前的版本。替代方案有两个一是 Quartus Prime 里的University Program VWF图形化波形配合 ModelSim 跑二是直接手写 testbench 调用ModelSim-Altera。所以如果你装的是 13.1、14.x、15.x 甚至 Quartus Prime Lite找不到 Start Simulation 完全不是你操作错了。判断方法很简单打开 Processing 菜单看一眼有 Start Simulation 就是老版本流程没有就是新流程。这个判断先做出来后面所有步骤才不会南辕北辙。1.3 VWF、testbench、ModelSim 三者到底是什么关系很多新手在这里绕不出来我用一句话说清VWFVector Waveform File只是激励的图形描述它本身不仿真它是给仿真器看的一张信号时间表。Testbench是 Verilog/VHDL 写的激励代码能力比 VWF 强得多可以随机化、可以自动比对、可以读文件。ModelSim是真正干活的仿真引擎。-Altera Starter Edition 是随 Quartus 一起装的免费版本对代码行数有一定限制大概一万行可执行代码量级学生作业和小项目完全够用。一句话总结VWF 是画出来的激励testbench 是写出来的激励ModelSim 是执行者。图形化路线适合刚上手、信号少的场景一旦你的模块有几十个端口、需要跑几百组激励就必须转向 testbench do 脚本否则手画波形会画到你怀疑人生。2. 动手之前工程设置与仿真工具绑定2.1 工程路径和文件命名的两条铁律在踩过几次莫名其妙的编译报错之后我给自己定了两条死规矩至今没破过第一工程路径全英文、无空格、无中文。ModelSim 的脚本和部分中间文件对中文路径的处理不算健壮D:\我的项目\计组实验\这种路径在某些版本上会直接报找不到文件而且报错信息完全不指向路径问题非常折磨人。第二文件名不要用关键字。曾经有个同学把文件命名成counter_test.v测试模块里又定义了module counter_test而设计顶层恰好叫counter在 NativeLink 里选顶层的时候选错结果时钟一直没驱动波形全红。他排查了两小时最后发现是名字太像导致的误选。另外提醒一点Quartus II 工程目录下会自动生成db、incremental_db、simulation等目录其中simulation/modelsim/就是仿真脚本和网表的落地点后面找 .do 文件和 .sdo 文件都在这里。2.2 EDA Tool Settings 里必须改的四个选项打开Assignments → Settings → EDA Tool Settings → Simulation这里的配置决定了 NativeLink 能不能一键拉起 ModelSim。需要确认的项配置项建议值为什么Tool nameModelSim-Altera 或 ModelSim老版本叫 ModelSim-Altera新版本统一叫 ModelSimFormat for output netlistVerilog HDL 或 VHDL必须和你的设计语言一致混用会编译失败Output directorysimulation/modelsim默认值即可方便脚本引用Compile test bench勾选并指定 testbench 文件与顶层模块名不勾选的话 NativeLink 只会拉仿真器不给激励用 NativeLink 跑 RTL 仿真还有一个前提Analysis Synthesis 必须已经跑过一遍。因为 NativeLink 需要一份 RTL 网表Quartus 会在综合阶段顺手生成到simulation/modelsim/下。如果综合还没跑或者综合报了错点 RTL Simulation 会直接失败但错误提示往往只说找不到某个 .vo 文件。2.3 功能仿真网表和时序仿真网表不是一回事这是另一个高频混淆点。RTL 功能仿真NativeLink 里的 RTL Simulation用综合后的 RTL 网表跑逻辑上是你的代码原意没有门延时。快适合验证功能。门级功能仿真用综合 布局布线后的网表跑但反标延时关掉了验证的是综合优化之后逻辑有没有被改坏。时序仿真Gate Level Timing Simulation同样用后仿网表但把 SDF 文件反标进去每个单元和连线都有真实的延时常数能看出建立/保持违例。要注意的是时序仿真要求工程做过完整的全编译Fitter Timing Analyzer 都跑过否则simulation/modelsim/下不会有带延时信息的 .sdo 文件仿真器会提示反标失败——而反标失败之后波形看起来能跑但你其实是在跑一个没有延时的仿真结论完全不可信。这个坑我见得太多了有人拿时序仿真的结果去证明时序没问题其实 SDF 根本没反标进去。3. 用 VWF 跑通第一次功能仿真3.1 新建 VWF 并设置 End Time 与 Grid老流程是File → New → Other Files → Vector Waveform File13.1 之后如果是 Quartus Prime则走File → New → University Program VWF。两条路的界面长得不一样但核心操作逻辑一致。建好之后第一件事不是插信号而是设置结束时间和网格大小。双击波形区空白处或走Edit → End Time把 End Time 设成 1us 或 10us。然后是 Grid Size它决定了你能画出的最小时间粒度。很多人遇到我想把时钟周期设成 7ns但画出来的波形总是吸附到 10ns 格点上就是 Grid 太大导致的。我的习惯是原理图级/门级仿真Grid 设成 1ns纯 RTL 功能仿真Grid 设 10ns 甚至 100ns 都行。End Time 宁可先设小一点跑通再逐步放大——设成 1ms 结果跑了两分钟没结束是新手常有的体验。3.2 Node Finder 里 Filter 的选择决定你能看到什么插信号靠Edit → Insert → Node or Bus → Node Finder。这里的Filter下拉框是重点选Pins: all只能看到顶层输入输出引脚。选Design Entry (all names)能看到设计里所有信号名包括模块内部的。选Post-synthesis或Post-fitting能看到综合/布局布线之后的节点名——注意这时候原来漂亮的信号名可能已经被优化改名或者打平了。直接在 Node Finder 里点List再全选是新手最常见的操作后果是波形区塞进几百个信号找个时钟得翻半天。我的做法是先只插入时钟、复位、以及要观察的那个总线跑通之后再逐步加信号。波形图不是越全越好你只需要看能验证结论的那几条。还有个小技巧在 Node Finder 里直接敲信号名的一部分再 List比全选快得多。3.3 画时钟为什么不要手绘新手画时钟的典型做法选一段区域点高电平再选一段点低电平来回折腾。只要时钟周期稍微不规则或者你要改周期就得从头再画一遍。正确做法是用Overwrite Clock选中时钟信号走Edit → Value → Overwrite Clock不同版本文案略有差异有的在右键菜单里填周期和相位一次搞定。比如 50MHz 系统时钟就是 20ns 周期、占空比 50%。这样画出来的时钟绝对规整改周期也只要改一个数字。顺带说一个真实场景如果你的工程里有时钟分频或者 PLLVWF 里不要试图去画 PLL 的输出时钟因为那个时钟在仿真中是由 PLL 模型产生的你手动画会和模型输出打架。正确的做法是给 PLL 的输入参考时钟画波形输出让仿真器自己算。这类 PLL、RAM、乘法器这类宏功能模块如果用 VWF 手动驱动输出结果一定不对。3.4 总线分组、进制切换和 X 态的手动设置多位信号在波形区默认显示成一根线双击它旁边的名字可以设置 RadixBIN/HEX/DEC/UNSIGNED。但更实用的做法是把相关的几根位信号选中右键Group成一条总线然后在组属性里设 Radix。比如cnt[7..0]直接按 UNSIGNED 显示一眼就能看出计数值对不对比看八根线拼二进制舒服太多。另外VWF 里每个节点可以手动设成0、1、X不确定、Z高阻以及总线值。X的用途是我不关心这个输入但要注意很多初学者把输入误设成 X然后发现输出全是红线回头以为是自己代码写错了。输入信号该给 0 就给 0别用 X 偷懒除非你确实在验证 X 传播行为。总线的默认值是未定义的如果你插进来一条总线却没给它赋值仿真里它会以 X 的形态出现。这本身不是故障而是你没给激励的正常表现。3.5 功能仿真和时序仿真按钮点哪个在 University Program VWF 里工具栏上通常有直接跑仿真的按钮在老版本 Quartus II 里则是Processing → Start Simulation。跑之前记得确认跑功能仿真需要先综合老版本还要单独生成功能仿真网表。跑时序仿真必须先全编译并在 Settings 里把时序仿真的 SDF 反标选项打开。如果点了之后 ModelSim 一闪而过就退出或者提示 Error loading design八成是编译顺序或库映射的问题第 5 节会专门讲。4. 波形里的红线到底是什么X 与 Z 的物理含义4.1 红色不等于报错先分清三种情况网上那个热词modelsim 仿真波形是红线其实指向的就是这个新手几乎百分百会遇到的现象。我先给结论ModelSim 的波形窗口里信号显示成红色绝大多数情况是这个信号当前的值是 X不定态或 Z高阻态而不是仿真器报错了。仿真器只是在告诉你这个信号的电平现在是个未知量。具体一点说Xunknown / 不定态数字电路里既不是 0 也不是 1的状态。它在真实芯片上也存在但在仿真里出现得更频繁因为它往往代表没被驱动或多个驱动源打架。Z高阻表示这根线没有被任何东西驱动浮空。三态总线在不工作的时刻显示 Z 是完全正常的不是 bug。振荡/不收敛仿真器在某一时刻反复迭代无法稳定报出迭代次数超限的警告波形上可能表现为快速抖动。关键区别在于Z 常常是设计意图的一部分X 几乎总是意味着激励没给全或者代码有结构性错误。所以看到红线第一件事不是慌而是把信号点在波形里看它的值到底是 X 还是 Z。4.2 八类最常造成红线的成因下面这张表是我这些年攒下来的按出现频率从高到低排。新手照着从上往下查八成能命中。序号成因波形特征定位方式1寄存器没有复位或复位信号是 X所有时序信号从头到尾都是红线看复位信号本身是不是 X2testbench 里声明了信号但忘了赋初值输入引脚直接是红线检查 initial 块有没有给每个 input 赋值3顶层选错DUT 没被驱动整个设计端口全红检查仿真顶层模块名4端口连接错位、位宽不匹配、名字拼错某几个信号红其余正常用 by-name 连接而不是 by-order5同一信号被两个 always 块赋值多驱动该信号红线且伴随警告综合日志里的 multi-driver 警告6组合逻辑成环latch 环或反馈环红线且仿真变慢甚至卡住检查是否有 assign 互相依赖7读存储器文件失败$readmemh 路径错存储器输出全红控制信号正常仿真日志里的 file open 警告8索引越界、除以零、位选超出范围局部信号红检查位宽与运算表达式补充两类在时序仿真里才会出现的一是SDF 反标失败导致本该有延时的路径变成零延时波形看起来正常但结论错二是建立/保持违例时序仿真中确实可能出现 X这种情况就要回去看时序约束和时钟频率了。4.3 一个具体例子X 是怎么一步步传染的光说概念没用我们看代码。下面这个计数器很典型module counter ( input wire clk, input wire rst_n, input wire en, output reg [7:0] cnt ); always (posedge clk or negedge rst_n) begin if (!rst_n) cnt 8d0; else if (en) cnt cnt 1b1; end endmodule再看一份看起来很像样的 testbenchtimescale 1ns/1ps module tb_counter; reg clk; reg rst_n; reg en; wire [7:0] cnt; counter u_counter ( .clk (clk), .rst_n (rst_n), .en (en), .cnt (cnt) ); initial begin clk 1b0; forever #10 clk ~clk; // 20ns 周期等效 50MHz end initial begin rst_n 1b0; en 1b0; #100; rst_n 1b1; #20; en 1b1; #1000; en 1b0; #200; $stop; end endmodule这份 testbench 是对的。但如果你漏写了rst_n 1b0;这一行会发生什么rst_n是 reg 类型未赋值时初值是 X。于是!rst_n求值结果是 X在 Verilog 里if (X)会被当作条件不成立处理代码走 else 分支而此时en也是 X于是cnt cnt 1里参与运算的全是 X结果自然还是 X。一个未初始化的复位信号能污染整个数据通路。这就是红线的典型传播路径。所以排查红线的第一条经验从最上游的输入信号开始看不要从输出倒着猜。输入如果是 X后面所有东西红都是结果不是原因。4.4 红线排查的标准动作从驱动源逆推我自己的排查顺序基本固定第一步把波形窗口里所有信号按层次展开看最顶层的那几个 input是不是全都有确定值。只要有输入是 X问题就在 testbench 或 VWF 激励里不在 RTL 里。第二步输入都正常但某个内部信号是 X就站在这个信号上往前一层的驱动源找。ModelSim 提供了专门的命令做这件事见下一节。第三步如果所有信号都在跳但某一条总线一直是红线重点查这条总线的每个位——经常是高位某一位的驱动漏了整条总线就显示成红色。第四步如果时序逻辑在复位释放的瞬间出现一小段红线然后恢复正常这通常是正常的亚稳态模拟或者复位异步释放造成的不用管。4.5 在 ModelSim 里定位驱动源的三条命令图形界面能看到现象但定位根因还是命令行快。ModelSim 的命令行Transcript 窗口里这三条我几乎每次都会用# 查看某个信号当前的完整值包括 X 的位 examine -radix binary /tb_counter/u_counter/cnt # 查看某个信号被谁驱动多驱动和悬空一眼看出来 drivers /tb_counter/u_counter/cnt # 追踪某个信号的驱动源直接列出驱动它的表达式和位置 trace -driver /tb_counter/u_counter/endrivers这条命令特别值得记住。当你在 RTL 里不小心把同一个 reg 在两个 always 块里赋值或者把两个输出接到了同一根线上drivers会直接把这两处都列出来比翻代码快得多。如果你只是想看某个信号被驱动成了什么examine比在波形里数格子靠谱——特别是当一个信号在高频抖动时波形窗口上根本看不出具体值。还有一个实用配置在波形窗口里选中某个信号右键菜单里有类似 Trace Driver / Trace Load 的入口等价于命令行那两条命令不爱敲命令的可以用这个。5. 红线排不掉时完整排查链路与几类假故障5.1 编译顺序、库映射、旧网表三个最容易踩的陷阱有的红线跟代码一点关系都没有纯粹是环境问题。我列三个最典型的。第一个是库映射缺失。当你的设计里用了厂商提供的 PLL、RAM、乘法器等宏功能模块仿真时必须把对应的库映射进去否则实例化会失败输出直接是 X 或者报 Instantiation of ... failed。Quartus 自动生成的 .do 文件里通常已经带了-L参数比如vsim -L altera_mf_ver -L altera_ver -L lpm_ver ...库名和器件系列、工具版本有关。如果你是手写 .do 文件很容易漏掉这一串。表现就是编译能过一加载设计就报错或者端口全红。第二个是仿真了旧的网表。Quartus 生成的 .vo/.vho 文件放在simulation/modelsim/下每次重新综合才会刷新。如果你改了 RTL 但只跑了 Analysis Synthesis 就点仿真有时会用到上一次的网表。表现是波形现象和你的代码对不上你以为代码写错了其实是看到了历史版本。判断方法改动一行不影响功能的注释或者改一个初值重新仿真看现象有没有变化没变化就是网表没刷新。第三个是编译顺序错了。testbench 引用了 DUT 的模块名如果先编译 testbench 后编译 DUTModelSim 会报找不到模块。正确顺序永远是被依赖的先编译。用 Quartus 自动生成的 .do 文件不会有这个问题手写脚本时才需要注意。5.2 顶层选错导致的满屏红线这个坑单独拿出来说因为它太常见了。NativeLink 的 EDA Tool Settings 里有一栏是 testbench 文件名和顶层模块名。如果你把顶层模块名填成了你的设计顶层比如counter而不是 testbench 的顶层tb_counter那么仿真器会直接把counter当顶层例化——此时clk、rst_n、en全是悬空的输入引脚值是 Xcnt自然也是红的。它的迷惑性在于编译完全通过没有任何报错就是波形全红。我见过有人因为这个把 RTL 从头到尾改了三四遍。识别特征很明确如果你的波形里所有顶层输入信号从头到尾都是红线且没有任何跳变先去检查顶层模块名和 testbench 是否真的被加载了。5.3 组合环、迭代上限与仿真卡死还有一种情况仿真不是秒退而是卡在那里不动Transcript 窗口一直在刷警告或者进度条走得极慢。常见原因是组合逻辑成环。举个例子你可能不小心写出这样的东西// 反面示例组合环 assign a b c; assign b a | d;a依赖bb又依赖a仿真器需要反复迭代求解永远收敛不了。ModelSim 会报出类似 Iteration limit reached 或者某个信号在零时刻反复翻转的警告。排查这类问题的经验是在 RTL 里所有组合逻辑都应该能用一张有向无环图表示。如果你发现assign之间互相引用或者在 always 块里对一个变量既读又写在同一个组合敏感列表里就要警惕了。特别注意always (*)里对同一个变量进行的自增、自减运算如果没写全条件分支很容易综合出锁存器甚至组合环。顺便说一个相关现象组合环在功能仿真里可能表现为红线在时序仿真里可能表现为短暂的振荡然后稳定下来——因为真实电路有延时环会锁定在某个状态。但**时序仿真里看起来正常不代表这个设计可用**它只是被物理延时掩盖了换一批器件或者换个温度可能就崩了。5.4 有些红线是可以接受的最后这一点一定要说否则新手会陷入必须消除所有红线的执念。第一三态总线的 Z 态是正常的。一个双向数据总线在没有读写的时刻就是高阻波形上是 Z这不是错误。第二未使用的模块输出可能是 X。比如你例化了一个 RAM 但从来没有对它写入过数据读出来就是 X符合预期。第三复位释放瞬间的短暂不确定值是正常的。复位刚撤掉的那一两个时钟周期内某些信号可能短暂不定只要稳定下来就没问题。第四仿真刚开始的第一个时刻所有寄存器都是 X直到第一个时钟沿到来才被赋值。这在波形上表现为从 0ns 到第一个时钟沿之间的红线属于正常现象。判断标准很简单如果这个 X 后续被正确的值覆盖并且一直保持那它没问题如果一个 X 一直红到仿真结束那它一定有问题。6. 从 VWF 走向脚本化仿真testbench 与 do 文件6.1 为什么迟早要放弃手画波形VWF 跑通第一次仿真很有成就感但它的天花板很低改一次激励要重新画、信号多了看不过来、不能自动比对结果、更不能批量跑。我自己的分界线是只要你的模块端口数超过 10 个或者你需要跑大于 100 个时钟周期的场景就上 testbench。好在 13.1 之后的 University Program VWF 里可以在文件菜单里把当前波形导出成 testbench 代码不同版本入口略有差异算是个不错的过渡手段先用图形化画一版能跑通的激励导出成 HDL 之后再手动改造成更灵活的版本。这个方法我推荐给所有刚入门的人比直接白手写 testbench 友好得多。6.2 一份可以直接抄的 run_sim.do与其每次手动点菜单不如写一个 .do 脚本。这是我平时用的一份模板改改路径就能用# run_sim.do —— RTL 功能仿真 # 用法在 ModelSim 命令行执行 do run_sim.do # 1. 准备工作库 if {[file exists work]} { vdel -all -lib work } vlib work vmap work work # 2. 编译设计文件先 DUT 后 testbench顺序不能反 vlog -sv ../rtl/counter.v vlog -sv ../tb/tb_counter.v # 3. 加载仿真顶层acc 保证内部信号可见 vsim -voptargsacc -t 1ps work.tb_counter # 4. 添加波形 add wave -divider TESTBENCH add wave -radix binary sim:/tb_counter/clk add wave -radix binary sim:/tb_counter/rst_n add wave -radix binary sim:/tb_counter/en add wave -divider DUT add wave -radix unsigned sim:/tb_counter/u_counter/cnt # 5. 跑到底 run -all wave zoom full几个细节值得说vdel -all -lib work是为了清掉上一次的库避免改了代码不生效的幽灵问题-t 1ps设置仿真时间精度精度太粗会让小延时被吃掉精度太细又会拖慢仿真1ps 对大多数工程是个合适的起点run -all会一直跑到$stop或$finish如果没有这两个系统任务仿真会一直跑下去需要你自己手动停。6.3 vopt 优化吃信号的问题这是我当年排查最久的一个坑必须单列。ModelSim 从 10.x 开始默认开启vopt 优化好处是仿真跑得快坏处是未被观察的内部信号会被优化掉。表现就是你在add wave里写了sim:/tb_counter/u_counter/cnt波形窗口里这个信号要么不出现要么全程是一条直线或者红线。解决办法就是加-voptargsacc参数acc是 access 的缩写强制保留信号的可访问性。这个参数我在每一份 .do 文件里都加从来没删过。老版本的 ModelSim比如随 Quartus II 13.x 附带的 6.6d 那一代对应的写法是vsim -novopt work.tb_counter语义上是一样的都是关掉优化。如何判断自己的仿真器是哪种写法看vsim -help或者直接看 Quartus 自动生成的 .do 文件里用的是什么照着抄最省事。6.4 时序仿真里 SDF 反标的注意事项跑门级时序仿真时.do 文件会多出反标参数大致长这样vsim -t 1ps \ -sdfmax /tb_counter/u_counter../simulation/modelsim/counter_8ns_4v.sdo \ -L altera_mf_ver -L altera_ver -L lpm_ver -L cycloneive_ver \ work.tb_counter这里有几个必须确认的点其一.sdo文件必须存在它由 Timing Analyzer 在完整编译之后生成路径和命名跟器件系列、速度等级有关。如果路径写错了ModelSim 只会给一条警告然后继续跑仿真照样能出波形但延时全是 0这就是前面说的看起来正常但结论错。其二-sdfmax用最大延时-sdfmin用最小延时-sdftyp用典型延时。做时序验证通常用-sdfmax看最坏情况能不能满足做功能验证有时会用-sdfmin看有没有意外问题。别随手写一个就完事。其三反标成功之后波形上会明显看到输出相对时钟沿有一个小延时这就是门延时和线延时的体现。如果你在时序仿真里看到输出和时钟沿完全对齐、一点延时都没有那基本可以确定 SDF 没反标上。6.5 用日志做批量回归手工跑仿真的效率瓶颈不在跑在看。当你改了一版代码要跑十组场景时靠肉眼盯波形是不现实的。我的做法是给 testbench 加上自动比对和结果打印期望值算出来之后用$display打一行同时用$error在不等的时候报错仿真结束时用$finish。然后跑批处理模式# 无图形界面跑仿真把日志重定向到文件 vsim -c -do do run_sim.do; quit -f -l sim_log.txt-c参数表示命令行模式不弹图形界面速度快很多。跑完之后直接搜Error或ERROR关键字一眼就能看出哪组场景挂了。这套流程一旦搭起来后面每次改代码就只要跑一次脚本看日志效率提升非常明显。对于纯粹做课程作业或者小项目的同学如果觉得 ModelSim 的命令行太劝退还有一个替代思路在 testbench 里把所有关键状态用$display打印到 Transcript 窗口然后只对关键信号加波形。这样即使波形窗口里信号不多也能通过打印输出确认逻辑走向。这个习惯我用了很多年在调试状态机时特别有效。7. 实操里反复踩到的几个坑以及一点个人体会最后一个部分我把这些年反复遇到的坑整理成一个清单都是文档里不太会写、但实际会耽误你半天时间的东西。关于路径工程目录里的每一级都别带中文和空格尤其是simulation/modelsim/下面自动生成的 .do 和 .sdo 文件路径一旦被转义出错报错信息完全看不出根因。关于时间精度timescale不写或者写错是延时对不上的重要原因。testbench 和 RTL 里的timescale最好保持一致1ns/1ps 是个通用起点。关于仿真时间跑仿真之前先想清楚我要观察的现象发生在第几个时钟周期把 End Time 设成刚好覆盖这个范围。设得过长VWF 里画起来累脚本里跑起来慢。关于信号可见性-voptargsacc和-novopt记一个就行遇到波形里看不到内部信号先加它。关于红线先分 X 还是 Z再从最上游输入往下查别从输出往上猜。关于时序仿真反标有没有成功看波形上有没有小延时这是最直观的判据。关于工具版本教程里的菜单项和你手上的版本不一致是常态先确认自己的 Quartus II 是哪一版再决定走内置 Simulator老路还是VWF ModelSim新路。我个人在实际操作中的体会是波形仿真这件事真正的门槛不在会不会点按钮而在于你能不能从波形上一条红线的位置推理出是哪一层出了问题。功能仿真跑通只是入门能把一次满屏红线在十分钟内定位到具体某一行代码才算真的把工具用起来了。刚开始可以刻意练习一件事每次看到红线先在纸上写出三个可能的成因再去验证几次之后你会发现自己对 Verilog 里 X 的传播规律会有完全不一样的理解——这比看十篇教程都管用。