本文衔接第二篇 :在上一篇中,我们使用
run.do脚本实现了 ModelSim 自动化仿真。本篇将更进一步:用批处理文件(
.bat)实现双击完成全部仿真流程 ,同时系统梳理 ModelSim 波形调试的常用操作,快速定位设计问题。
1 从 do 脚本到 bat 批处理
在第二篇中,我们每次仿真都需要:
- 打开 ModelSim GUI
- 在 Transcript 窗口输入
cd <路径>和do run.do
虽然比手动点按钮已经方便很多,但能否做到直接双击一个图标就自动完成编译、仿真、打开波形 ?答案是肯定的------利用 Windows 批处理文件(.bat)调用 ModelSim 的命令行模式即可。
1.1 编写最简单的批处理文件
在工程文件夹(与 run.do、设计文件 .v 同目录)下新建一个文本文件,命名为 run.bat,内容如下:
batch
@echo off
echo Starting ModelSim simulation...
vsim -do "do run.do"
pause
关键点说明:
vsim -do "do run.do":启动 ModelSim 并执行run.do脚本(GUI 模式)。pause:仿真结束后暂停窗口,方便查看 Transcript 中的提示信息。
1.2 配置环境变量(如果 vsim 无法识别)
如果双击 .bat 后提示 'vsim' 不是内部或外部命令,说明系统找不到 ModelSim 的可执行文件,按如下方式给出modelsim路径:
在批处理中使用完整路径
batch
@echo off
set MODELSIM_PATH=D:\modeltech64_10.5\win64
"%MODELSIM_PATH%\vsim" -do "do run.do"
pause
将 MODELSIM_PATH 改为实际安装 ModelSim 的路径。
1.3 运行效果
- 双击
run.bat→ 自动启动 ModelSim GUI → 自动完成建库、编译、仿真 → 打开波形窗口并运行 2000 ns → 最后窗口显示"Simulation completed."。 - 关闭 ModelSim 或点击批处理窗口的任意键退出。
至此,我们实现了真正的一键仿真(无需打开 ModelSim、无需输入任何命令)。
2 ModelSim 波形调试常用操作指南
自动仿真完成后,我们通常需要观察波形来验证设计是否正确或定位问题。ModelSim 的波形窗口功能强大,以下 8 项操作是日常调试中最常用、最实用的技能。
2.1 添加信号到波形窗口
- 方法一(GUI) :在
sim面板中选中顶层模块或子模块,右键点击需要观察的信号 → Add to Wave。 - 方法二(脚本) :在
run.do中用add wave命令批量添加(如第二篇所示)。 - 技巧 :按住
Ctrl可多选,按住Shift可连选。

图1 从 sim 面板添加信号到波形
2.2 波形缩放与时间轴移动
- 缩放 :
- 工具栏点击 Zoom In (放大镜+)/ Zoom Out(放大镜-)
- 快捷键:
i放大,o缩小
- 全屏显示所有波形 :点击 Zoom Full (或快捷键
F) - 区域放大:在波形区域按CTRL同时按住鼠标左键拖选一个时间区间,松开后自动放大该区间
- 移动时间轴:拖动波形窗口底部的滚动条

图2 波形缩放工具栏

图3 波形操作动图
2.3 使用光标测量时间间隔
ModelSim 提供 主光标 (Cursor 1)和 辅助光标(Cursor 2),用于测量两个时刻之间的差值。
-
添加光标:
-
点击波形窗口顶部的 "Add Cursor" 按钮(或菜单
Wave→Cursors→Add Cursor)

图4 通过菜单栏添加标尺
-
直接点击波形区域的灰色时间标尺条,即可在点击位置生成光标

图5 通过时间标尺添加标尺
-
-
移动光标:拖拽光标顶部三角形,或选中光标后按键盘左右箭头微调
-
测量:当两个光标都存在时,在时间标尺处会显示光标的差值

图6 使用双光标测量延迟时间
2.4 改变波形颜色与显示格式
为了区分不同信号(如时钟、数据、控制信号),可以自定义颜色和显示进制。
-
修改颜色 :右键点击波形左侧信号名 → Properties 。

图7 波形设置属性
-
Properties界面下:View→Color Settings→Wave Color→选择颜色。

图8 设置波形颜色
-
修改进制 :右键 → Radix → 可选择 Binary / Decimal / Hexadecimal / ASCII 等。

图9 显示进制修改
-
修改波形高度 :右键点击波形左侧信号名 → Properties →Format →Height。

图10 波形设置属性

图11 高度设置
2.5 将相关信号分组(Group)
当信号数量较多时(如状态机的状态位),可以将它们编为一组,折叠或展开显示。
-
创建组 :选中多个信号 → 右键 → Group → 输入组名(例如 "SEQ_CTRL")。

图12 分组
-
展开/折叠组 :点击组名左侧的
+/-图标。

图13 展开/折叠分组
-
取消分组 :右键点击组 → Ungroup 。

图14 取消分组
2.6 保存与加载波形配置
调试一个设计时,往往需要反复查看同一组信号和相同的颜色/分组设置。保存波形配置可以免去重复添加和调整。
-
保存 :
File→Save Format...→ 输入文件名(如wave_config.do)。

图15 保持波形配置
-
加载 :在
do脚本或命令行执行do wave_config.do。
图16 加载配置文件
2.7 实时更新与停止仿真
- 连续运行 :点击 Run。
- 单步运行 :点击 ContinueRun。
- 停止 :点击 Stop。
- 设定运行时长 :在
run.do中用run <时间>,如run 2000 ns;或者在testbench设置$finish。

图17 仿真按钮
2.8 让波形显示状态机名称(Verilog 专用技巧)
如果使用 VHDL 或 SystemVerilog 的 enum 类型,ModelSim 会自动显示状态名。但标准 Verilog 的状态机通常用 parameter 定义编码值,波形窗口只能看到数字(如 4'h1)。以下两种方法可以将数字映射为有意义的名称。
方法一:使用 virtual type 命令(推荐,不修改代码)
在 run.do 脚本中添加以下三行(注意路径中的实例名 u_dut 与您的设计一致):
tcl
# 定义虚拟枚举类型
virtual type {
{4'b0000 S0}
{4'b0001 S1}
{4'b0010 S2}
{4'b0011 S3}
{4'b0100 S4}
{4'b0101 S5}
{4'b0110 S6}
{4'b0111 S7}
{4'b1000 S8}
} FSM_TYPE
# 将 DUT 内的 state 信号转换为新信号 state_name
virtual function {(FSM_TYPE)/seq_detect_8bit_tb/dut/state} state_name
# 将转换后的信号添加到波形
add wave sim:/seq_detect_8bit_tb/dut/state_name
重新运行仿真后,波形中会出现 state_name 信号,显示 S0, S1, ..., S8。
注意 :
virtual type中的二进制值必须与 DUT 中localparam的编码严格一致(本例为4'd0~4'd8,写成4'b0000~4'b1000)。
方法二:在 Testbench 中添加转换逻辑(纯代码方案)
在 seq_detect_8bit_tb.v 中添加以下代码:
verilog
reg [8*8-1:0] state_name; // 每个状态名最长8字符
always @(*) begin
case (u_dut.state)
4'd0: state_name = "S0 ";
4'd1: state_name = "S1 ";
4'd2: state_name = "S2 ";
4'd3: state_name = "S3 ";
4'd4: state_name = "S4 ";
4'd5: state_name = "S5 ";
4'd6: state_name = "S6 ";
4'd7: state_name = "S7 ";
4'd8: state_name = "S8 ";
default: state_name = "ERROR ";
endcase
end
然后在 run.do 中用 add wave sim:/seq_detect_8bit_tb/state_name 添加到波形即可。
3 整合示例:完整文件清单(可直接复制)
为了方便直接运行,下面列出第三篇所需的全部文件内容(基于第二篇的 Moore 型序列检测器)。请将以下四个文件放在同一文件夹中。
文件列表
modelSim_8bit_seq_fsm/
│
├── seq_detect_8bit.v # 设计模块(Moore状态机)
├── seq_detect_8bit_tb.v # 测试激励(固定测试序列)
├── run.do # do 脚本(编译、仿真、添加波形)
└── run.bat # 批处理一键仿真脚本
3.1 设计模块 seq_detect_8bit.v
verilog
// 功能:检测8位序列 10110001 (MSB最先输入),Moore型状态机
// 重叠检测:检测到序列后,根据剩余后缀转移(din=1 → S1, din=0 → S2)
module seq_detect_8bit (
input wire clk ,
input wire rst_n ,
input wire din ,
output wire dout
);
// 状态编码:需要4位二进制(S8=8)
localparam S0 = 4'd0, S1 = 4'd1, S2 = 4'd2, S3 = 4'd3,
S4 = 4'd4, S5 = 4'd5, S6 = 4'd6, S7 = 4'd7,
S8 = 4'd8;
reg [3:0] state, next_state;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= S0;
else
state <= next_state;
end
always @(*) begin
case (state)
S0: next_state = din ? S1 : S0;
S1: next_state = din ? S1 : S2;
S2: next_state = din ? S3 : S0;
S3: next_state = din ? S4 : S2;
S4: next_state = din ? S1 : S5;
S5: next_state = din ? S3 : S6;
S6: next_state = din ? S1 : S7;
S7: next_state = din ? S8 : S0;
S8: next_state = din ? S1 : S2; // 重叠检测:保持后缀匹配
default: next_state = S0;
endcase
end
assign dout = (state == S8);
endmodule
3.2 测试激励 seq_detect_8bit_tb.v
verilog
`timescale 1ns/1ps
module seq_detect_8bit_tb();
// ---------------------------- 信号定义 ----------------------------
reg clk;
reg rst_n;
reg din;
wire dout;
// ---------------------------- 可配置参数(支持命令行覆盖)------------------------
reg [7:0] target_seq = 8'b10110001; // 默认目标序列
integer test_num = 0; // 测试用例编号
// ---------------------------- DUT 实例化 ----------------------------
seq_detect_8bit dut (
.clk (clk),
.rst_n (rst_n),
.din (din),
.dout (dout)
);
// ---------------------------- 时钟生成 (20ns 周期) ----------------------------
initial clk = 0;
always #10 clk = ~clk;
// ---------------------------- 任务:发送一个字节(MSB 先)------------------------
task send_byte(input [7:0] data);
integer i;
begin
for (i = 7; i >= 0; i = i - 1) begin
@(posedge clk) din = data[i];
#1; // 避免与时钟边沿竞争
end
end
endtask
// ---------------------------- 任务:发送一位 ----------------------------
task send_bit(input value);
begin
@(posedge clk) din = value;
#1;
end
endtask
// ---------------------------- 镜像状态机(与 DUT 完全一致)--------------------
reg [3:0] exp_state;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
exp_state <= 4'd0;
else begin
case (exp_state)
4'd0: exp_state <= din ? 4'd1 : 4'd0;
4'd1: exp_state <= din ? 4'd1 : 4'd2;
4'd2: exp_state <= din ? 4'd3 : 4'd0;
4'd3: exp_state <= din ? 4'd4 : 4'd2;
4'd4: exp_state <= din ? 4'd1 : 4'd5;
4'd5: exp_state <= din ? 4'd3 : 4'd6;
4'd6: exp_state <= din ? 4'd1 : 4'd7;
4'd7: exp_state <= din ? 4'd8 : 4'd0;
4'd8: exp_state <= din ? 4'd1 : 4'd2;
default: exp_state <= 4'd0;
endcase
end
end
wire exp_dout = (exp_state == 4'd8);
// ---------------------------- 自动比对与统计 ----------------------------
integer pass_cnt, fail_cnt, report_file;
initial begin
pass_cnt = 0;
fail_cnt = 0;
// 读取命令行参数(覆盖默认值)
if ($value$plusargs("TARGET=%b", target_seq))
$display("[INFO] Parameter overridden: TARGET = %b", target_seq);
if ($value$plusargs("TEST_NUM=%d", test_num))
$display("[INFO] Parameter overridden: TEST_NUM = %0d", test_num);
// 打开报告文件
report_file = $fopen("test_report.txt", "w");
if (report_file) begin
$fdisplay(report_file, "========================================");
$fdisplay(report_file, " Sequence Detector Test Report");
$fdisplay(report_file, " Target Sequence : %b", target_seq);
$fdisplay(report_file, " Test Number : %0d", test_num);
$fdisplay(report_file, " Start Time : %0t", $time);
$fdisplay(report_file, "========================================");
end else begin
$display("[ERROR] Failed to open report file.");
end
end
// 每个时钟沿比对(复位期间不检查)
always @(posedge clk) begin
if (rst_n) begin
if (dout !== exp_dout) begin
$display("[FAIL] time=%0t: dout=%b, exp_dout=%b", $time, dout, exp_dout);
if (report_file)
$fdisplay(report_file, "[FAIL] time=%0t", $time);
fail_cnt = fail_cnt + 1;
end else begin
$display("[PASS] time=%0t: dout=%b", $time, dout);
pass_cnt = pass_cnt + 1;
end
end
end
// ---------------------------- 激励产生 ----------------------------
initial begin
$display("==================================================");
$display("8-bit Sequence Detector Test (Target: %b)", target_seq);
$display("==================================================");
rst_n = 0; din = 0; #30;
rst_n = 1; #20;
// Test1: 正确序列
$display("\n[Test1] Send correct sequence: %b", target_seq);
send_byte(target_seq);
#40;
// Test2: 错误序列(逐位取反)
$display("[Test2] Send wrong sequence: %b", ~target_seq);
send_byte(~target_seq);
#40;
// Test3: 连续两个正确序列
$display("[Test3] Two correct sequences back-to-back");
send_byte(target_seq);
send_byte(target_seq);
#40;
// Test4: 重叠检测 - 跨字节出现目标序列
$display("[Test4] Overlap test: target across byte boundary");
send_bit(1); send_bit(1); send_bit(0);
send_byte(target_seq);
#40;
// Test5: 后缀重叠测试
$display("[Test5] Sequence with potential internal overlap");
send_byte(target_seq);
send_bit(1);
#40;
$display("\n=== Simulation finished ===");
#100; // 让剩余比对完成
// ---------------------------- 写入统计并关闭文件------------------------
if (report_file) begin
$fdisplay(report_file, "----------------------------------------");
$fdisplay(report_file, " Total Comparisons : %0d", pass_cnt + fail_cnt);
$fdisplay(report_file, " Passed : %0d", pass_cnt);
$fdisplay(report_file, " Failed : %0d", fail_cnt);
$fdisplay(report_file, "========================================");
$fclose(report_file);
end
$display("\nReport saved to test_report.txt");
$display("PASS = %0d, FAIL = %0d", pass_cnt, fail_cnt);
$stop;
end
endmodule
3.3 自动化仿真脚本 run.do
tcl
# =========================< 清空软件残留信息 >==============================
# 退出当前仿真(如果存在)
quit -sim
# 清空 Transcript 窗口中的信息
.main clear
# =========================< 建立工程并仿真 >===============================
# 建立新的工作库
vlib work
# 映射逻辑库到物理目录
vmap work work
# 编译 Verilog 设计文件
vlog seq_detect_8bit.v
vlog seq_detect_8bit_tb.v
# 启动仿真(+acc 保留内部信号)
vsim -voptargs="+acc" work.seq_detect_8bit_tb
# =========================< 添加波形信号 >=================================
# 设置信号名显示宽度(只显示最后一级名称)
configure wave -signalnamewidth 1
# 添加波形信号
add wave -position insertpoint sim:/seq_detect_8bit_tb/dut/*
# =========================< 添加虚拟状态机 >===============================
# 定义虚拟枚举类型
virtual type {
{4'b0000 S0}
{4'b0001 S1}
{4'b0010 S2}
{4'b0011 S3}
{4'b0100 S4}
{4'b0101 S5}
{4'b0110 S6}
{4'b0111 S7}
{4'b1000 S8}
} FSM_TYPE
# 将 DUT 内的 state 信号转换为新信号 state_name
virtual function {(FSM_TYPE)/seq_detect_8bit_tb/dut/state} state_name
# 将转换后的信号添加到波形
add wave sim:/seq_detect_8bit_tb/dut/state_name
# =========================< 运行仿真 >=====================================
# 运行 2000 ns
run 2000 ns
# 波形窗口自动缩放至全部时间范围
wave zoomfull
# 输出完成提示
echo "Simulation completed. Check waveform window."
3.4 一键仿真批处理 run.bat
batch
@echo off
echo ========================================
echo 8-bit Sequence Detector Simulation
echo ========================================
echo Starting ModelSim...
REM 请将下面的路径修改为您的 ModelSim 安装路径
set MODELSIM_PATH=D:\modeltech64_10.5\win64
"%MODELSIM_PATH%\vsim" -do "do run.do"
echo Simulation finished.
pause
4 操作步骤
-
准备环境
- 确认 ModelSim 安装路径,修改
run.bat中的MODELSIM_PATH。 - 将上述四个文件(
.v、.do、.bat)放入同一文件夹。

图18 统一文件
- 确认 ModelSim 安装路径,修改
-
运行仿真
双击
run.bat,等待 ModelSim 启动并自动完成编译、仿真、打开波形窗口。 -
调试波形
- 波形窗口出现后,使用第 2 节介绍的操作进行调整(缩放、添加光标、分组、显示状态名等)。
- 观察
dout信号是否在输入正确序列时拉高(应在发送10110001后的第一个时钟上升沿变为 1)。
-
保存波形布局 (可选)
在 ModelSim 波形窗口调整好信号颜色、分组等后,执行
File→Save Format...保存为my_wave.do。下次仿真后可在 Transcript 中输入do my_wave.do恢复布局。
5 总结
至此,我们实现了:
- ✅ 双击
.bat文件一键完成从编译到仿真的全部流程(无需手动打开 ModelSim 输入命令)。 - ✅ 系统掌握 ModelSim 波形窗口的 8 项核心调试操作,包括信号添加、缩放测量、颜色进制修改、分组、配置保存以及 Verilog 状态机名称显示。
下一篇文章预告 :
本篇中的 Testbench 仍然是"固定输入"模式。为了高效测试多组序列、自动判断正确性并生成报告,我们需要引入参数化测试、自动比对和报告生成技术。敬请期待第四篇:《ModelSim入门实战(四): 参数化测试、自动比对与报告生成》。
📢 关于文章 :原创实战分享,转载需注明出处。如果觉得有用,请点赞收藏支持~
🔔 点击关注,第一时间收到后续教程推送。
📚 参考文档 :ModelSim SE User's Manual