2、脚本化仿真基础:Shell脚本入门,Makefile基础,Tcl脚本入门

各位工程师朋友,欢迎来到第二章。上一章我们聊了为什么要做脚本化仿真,说白了就是让机器替我们干那些重复的苦力活。这一章,我们来点实在的——把三个最常用的脚本工具过一遍。Shell、Makefile、Tcl,这三样东西,你玩转了,仿真效率至少翻一倍。

我个人习惯把脚本比作「胶水」。Shell是粘合操作系统和工具的胶水,Makefile是粘合编译流程的胶水,Tcl则是粘合EDA工具的胶水。三管齐下,你的仿真环境就能跑得飞起。

2.1 Shell脚本入门:让命令行听你的话

Shell脚本,说白了就是把一堆Linux命令写进一个文件里,然后一次性执行。我在项目中遇到过最典型的场景:每天上班第一件事,跑一遍回归测试。手动敲十几条命令?太傻了。写个脚本,一键搞定。

先看一个最简单的例子:

#!/bin/bash
# 我的第一个仿真脚本
echo "开始编译..."
vlog -work work top.sv
echo "开始仿真..."
vsim -c -do run.do top
echo "仿真完成!"

嗯,这里要注意:第一行的 #!/bin/bash 叫 shebang,它告诉系统用哪个解释器来跑这个脚本。没有它,脚本就是个普通文本文件。

变量和参数:

写脚本免不了要用变量。比如你要指定不同的测试用例:

#!/bin/bash
TEST_NAME=$1          # 第一个命令行参数
SEED=$2               # 第二个命令行参数
echo "运行测试: $TEST_NAME, 种子: $SEED"
vsim -c -sv_seed $SEED -do "run -all" $TEST_NAME

调用的时候这样写:./run_test.sh my_test 12345。你看,一个脚本就能跑不同的测试,灵活多了。

我的小技巧: 变量名用大写,比如 TEST_NAME、SEED,这样一眼就能看出是变量。小写字母留给命令和函数名,这是行业惯例。

条件判断和循环:

仿真中经常要判断文件是否存在,或者循环跑多个测试。Shell的 if 和 for 就是干这个的:

#!/bin/bash
# 检查日志文件是否存在
if [ -f "sim.log" ]; then
    echo "发现旧的日志文件,删除它..."
    rm sim.log
fi

# 循环跑三个测试
for TEST in test_a test_b test_c; do
    echo "正在运行: $TEST"
    vsim -c -do "run -all" $TEST
done

为什么会这样?因为仿真经常要反复跑,手动清理文件太容易出错了。用脚本自动清理,省心又安全。

避坑指南: 我曾经在 if 判断里漏了空格,写成 if [ -f "file"],结果脚本报错。记住:方括号前后必须有空格!这是Shell语法最坑的地方之一。

2.2 Makefile基础:让编译自动化

Makefile,说白了就是一套「依赖关系管理」工具。你告诉它:A文件依赖B文件,如果B改了,就重新生成A。Make会自动帮你搞定。

我在做大型SoC项目时,代码文件上百个,手动编译?不存在的。Makefile一出,一条 make 命令全搞定。

一个简单的Makefile:

# 变量定义
TOP = top
SRC = top.sv sub_module.sv driver.sv
WORK = work

# 默认目标
all: compile simulate

# 编译规则
compile:
    vlog -work $(WORK) $(SRC)

# 仿真规则
simulate: compile
    vsim -c -do "run -all" $(TOP)

# 清理
clean:
    rm -rf $(WORK) transcript *.log

你看,all 是默认目标,它依赖 compilesimulate。你执行 make,它会先编译,再仿真。如果只编译,就执行 make compile

Makefile的核心语法:

语法 说明 示例
目标: 依赖 定义目标和它的依赖 simulate: compile
$(变量) 引用变量 $(TOP)
% 通配符,匹配任意 %.v: %.sv
@ 隐藏命令回显 @echo "完成"
重点: Makefile的缩进必须用Tab键,不能用空格!这是无数新手踩过的坑。我曾经因为这个原因排查了半小时,最后发现是编辑器把Tab自动转成了空格。

进阶用法:自动推导

Make其实很聪明,它能自动推导一些规则。比如你有一个 top.sv 文件,Make知道它应该用 vlog 来编译。但为了清晰,我建议还是显式写出规则,方便别人阅读。

2.3 Tcl脚本入门:EDA工具的通用语言

Tcl,全称Tool Command Language,是EDA工具的「普通话」。ModelSim、Vivado、Questa、Design Compiler……几乎所有主流工具都支持Tcl。你想想看,学会Tcl,就等于拿到了所有工具的遥控器。

第一个Tcl脚本:

# 创建一个工作库
vlib work

# 编译源文件
vlog top.sv
vlog sub_module.sv

# 启动仿真
vsim top

# 添加波形
add wave -r /*

# 运行仿真
run 1000 ns

把这个保存成 run.do,然后在命令行执行 vsim -c -do run.do。你看,所有操作都自动化了。

Tcl的变量和流程控制:

# 设置变量
set TOP "top_module"
set RUN_TIME "1000 ns"

# 条件判断
if {[file exists "sim.log"]} {
    puts "日志文件已存在,删除..."
    file delete sim.log
}

# 循环
for {set i 0} {$i < 5} {incr i} {
    puts "运行第 [expr {$i + 1}] 次仿真"
    vsim -c -do "run $RUN_TIME" $TOP
}

嗯,这里要注意:Tcl的语法和C语言很像,但又有自己的特点。比如变量引用用 $,表达式计算用 expr,方括号 [] 表示命令替换。

我的经验: Tcl脚本最常用的场景是写do文件。我一般把do文件分成三部分:编译部分、仿真设置部分、运行部分。这样结构清晰,改起来也方便。

常用Tcl命令速查:

命令 作用 示例
vlib 创建库 vlib work
vlog 编译Verilog/SV vlog top.sv
vcom 编译VHDL vcom top.vhd
vsim 启动仿真 vsim top
add wave 添加波形 add wave -r /*
run 运行仿真 run 1000 ns
避坑指南: 我曾经在Tcl脚本里写 run 1000,结果仿真只跑了1000个时间单位(默认是ns),但我以为是1000us。后来养成习惯,每次写run都带上时间单位,比如 run 1000 nsrun 1 us

三个工具如何配合?

在实际项目中,我通常这样用:

  1. Shell脚本:作为总调度,调用Makefile和Tcl脚本
  2. Makefile:管理编译流程,处理文件依赖
  3. Tcl脚本:控制仿真器,设置波形,运行测试

举个例子,一个完整的仿真流程可能是这样的:

#!/bin/bash
# run_all.sh - 一键仿真脚本

echo "=== 开始仿真流程 ==="

# 1. 用Makefile编译
make clean
make compile
if [ $? -ne 0 ]; then
    echo "编译失败!"
    exit 1
fi

# 2. 用Tcl脚本跑仿真
vsim -c -do run.do top_module

# 3. 检查结果
if grep -q "PASS" sim.log; then
    echo "测试通过!"
else
    echo "测试失败,请检查日志。"
fi

你看,Shell负责「胶水」工作,Makefile负责「编译」工作,Tcl负责「仿真」工作。各司其职,清清楚楚。

好了,这一章的内容就到这里。Shell、Makefile、Tcl,这三样东西你掌握了,脚本化仿真的基础就算打牢了。下一章,我们会深入实战,用这些工具搭建一个完整的自动化仿真环境。到时候你会发现,原来仿真可以这么轻松。