ModelSim入门实战(三): 批处理一键仿真与波形调试

本文衔接第二篇 :在上一篇中,我们使用 run.do 脚本实现了 ModelSim 自动化仿真。

本篇将更进一步:用批处理文件(.bat)实现双击完成全部仿真流程 ,同时系统梳理 ModelSim 波形调试的常用操作,快速定位设计问题。

1 从 do 脚本到 bat 批处理

在第二篇中,我们每次仿真都需要:

  1. 打开 ModelSim GUI
  2. 在 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" 按钮(或菜单 WaveCursorsAdd Cursor

      图4 通过菜单栏添加标尺

    • 直接点击波形区域的灰色时间标尺条,即可在点击位置生成光标

      图5 通过时间标尺添加标尺

  • 移动光标:拖拽光标顶部三角形,或选中光标后按键盘左右箭头微调

  • 测量:当两个光标都存在时,在时间标尺处会显示光标的差值


图6 使用双光标测量延迟时间

2.4 改变波形颜色与显示格式

为了区分不同信号(如时钟、数据、控制信号),可以自定义颜色和显示进制。

  • 修改颜色 :右键点击波形左侧信号名 → Properties

    图7 波形设置属性

  • Properties界面下:View→Color Settings→Wave Color→选择颜色。

    图8 设置波形颜色

  • 修改进制 :右键 → Radix → 可选择 Binary / Decimal / Hexadecimal / ASCII 等。

    图9 显示进制修改

  • 修改波形高度 :右键点击波形左侧信号名 → PropertiesFormat →Height。

    图10 波形设置属性


图11 高度设置

2.5 将相关信号分组(Group)

当信号数量较多时(如状态机的状态位),可以将它们编为一组,折叠或展开显示。

  • 创建组 :选中多个信号 → 右键 → Group → 输入组名(例如 "SEQ_CTRL")。

    图12 分组

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

    图13 展开/折叠分组

  • 取消分组 :右键点击组 → Ungroup

    图14 取消分组

2.6 保存与加载波形配置

调试一个设计时,往往需要反复查看同一组信号和相同的颜色/分组设置。保存波形配置可以免去重复添加和调整。

  • 保存FileSave 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 操作步骤

  1. 准备环境

    • 确认 ModelSim 安装路径,修改 run.bat 中的 MODELSIM_PATH
    • 将上述四个文件(.v.do.bat)放入同一文件夹。

      图18 统一文件
  2. 运行仿真

    双击 run.bat,等待 ModelSim 启动并自动完成编译、仿真、打开波形窗口。

  3. 调试波形

    • 波形窗口出现后,使用第 2 节介绍的操作进行调整(缩放、添加光标、分组、显示状态名等)。
    • 观察 dout 信号是否在输入正确序列时拉高(应在发送 10110001 后的第一个时钟上升沿变为 1)。
  4. 保存波形布局 (可选)

    在 ModelSim 波形窗口调整好信号颜色、分组等后,执行 FileSave Format... 保存为 my_wave.do。下次仿真后可在 Transcript 中输入 do my_wave.do 恢复布局。


5 总结

至此,我们实现了:

  • ✅ 双击 .bat 文件一键完成从编译到仿真的全部流程(无需手动打开 ModelSim 输入命令)。
  • ✅ 系统掌握 ModelSim 波形窗口的 8 项核心调试操作,包括信号添加、缩放测量、颜色进制修改、分组、配置保存以及 Verilog 状态机名称显示。

下一篇文章预告

本篇中的 Testbench 仍然是"固定输入"模式。为了高效测试多组序列、自动判断正确性并生成报告,我们需要引入参数化测试、自动比对和报告生成技术。敬请期待第四篇:《ModelSim入门实战(四): 参数化测试、自动比对与报告生成》。


📢 关于文章 :原创实战分享,转载需注明出处。如果觉得有用,请点赞收藏支持~

🔔 点击关注,第一时间收到后续教程推送。
📚 参考文档ModelSim SE User's Manual


相关推荐
Ryan-Lily2 小时前
曲轴基于灵敏度的拓扑优化-CAE操作过程
abaqus·仿真
23124_803 小时前
【无标题】
单片机·嵌入式硬件
ytttr8733 小时前
STM32 读写 SD 卡源码(SPI 模式 + FATFS 文件系统)
stm32·单片机·嵌入式硬件
Quinn273 小时前
正点原子 STM32MP257 修复异核 FreeRTOS+OpenAMP 例程里 SysTick 延时异常的问题
stm32·嵌入式硬件·正点原子·arm linux
Deitymoon3 小时前
STM32——OLED显示图片
stm32·单片机·嵌入式硬件
深圳英康仕4 小时前
龙芯2K3000嵌入式工控机的技术拆解:算力、接口与国产系统适配
嵌入式硬件·工控机·工业计算机·国产工控机·龙芯2k3000
Deitymoon4 小时前
STM32——OLED显示汉字
stm32·单片机·嵌入式硬件
狮驼岭的小钻风4 小时前
单片机启动流程与 .s 文件详解
单片机·嵌入式硬件
iCxhust4 小时前
8086/8088单板机VSCode集中环境开发编译(第二版整理)
ide·vscode·嵌入式硬件·编辑器·嵌入式·微机原理·8086最小系统