数字前端工程师的所有日常工具,本质上都在服务同一件事:把人写的 Verilog/SystemVerilog 代码,变成"可以跑、可以看、可以改"的仿真闭环。
这个闭环是:写 RTL 和 testbench → 用 Makefile 把编译仿真动作固化成一条 make 命令 → VCS 完成「编译 + elaboration + 仿真」并 dump 出 fsdb 波形 → 打开 Verdi 看波形、追 driver、定位 bug → 回去改 RTL,再跑一遍。
而这一切都发生在 Linux 服务器上:用 ssh 登录、用 Vim 改代码、用 SVN/Git 管版本。所以本页的顺序就是「环境(Linux/Vim/版本管理)→ 调度(Makefile)→ 工具(VCS/Verdi)→ 串成工作流」。每一节都按「是什么 → 为什么 → 怎么做 → 命令/代码 → 工程与面试怎么问 → 易错点」展开,读完即可照抄上手。
| 工具 | 在闭环中的角色 | 一句话记忆 | 对应本页章节 |
|---|---|---|---|
| Linux 服务器 | 一切工具的运行平台 | IC 的"车间"全在 Linux 上 | 第 2 节 |
| Vim | 服务器上的代码编辑器 | ssh 上去唯一靠得住的编辑器 | 第 3 节 |
| SVN / Git | RTL 与脚本的版本管理 | 改坏了随时能退回来 | 第 4 节 |
| GNU Make | 编译仿真流程的调度器 | 把十行命令变成一行 make | 第 5 节 |
| VCS | 编译 + elaboration + 仿真 | Synopsys 事件驱动仿真器,工业标配 | 第 6/7 节 |
| Verdi | 源码 + 波形交互式调试 | nTrace 看层次,nWave 看波形,能追 driver | 第 6/8 节 |
| iverilog + GTKWave | 开源仿真 + 波形查看 | 零成本跑通入门闭环 | 6.3 节 |
| Verilator | 开源 RTL→C++ 高速仿真 / lint | 速度极快,调试弱于 Verdi | 6.3 节 |
这一节回答三个问题:IC 公司的服务器环境长什么样、每天真正会敲哪些命令、以及 ssh / 环境变量 / 权限这三件"第一天就会卡住"的事。目标不是成为 Linux 运维,而是达到"在服务器上独立干活不求助"的水平。
是什么:IC 公司的工程师工位上通常只有一台装了 VNC/终端的普通电脑(甚至 Windows/Mac),而所有真正的开发资源 —— EDA 工具、设计数据库、仿真服务器、license 服务 —— 全部在远端的 Linux 服务器集群(多为 RHEL/CentOS 系)上。你本地的电脑只是一个"遥控器"。
为什么 IC 行业几乎 100% 用 Linux 服务器,而不是 Windows 工作站:
先给一张"第一天就能用上"的命令速查卡,再给几条真实组合。IC 工程师 80% 的终端时间都花在这一小节。
几个 IC 日常高频的组合用法,建议逐条敲一遍:
# 1. 全工程搜索:clk 分频信号 div_clk 在哪些 RTL 里出现过?
grep -rn "div_clk" ./rtl --include="*.v"
# 2. 找出最近 1 天内改过的 Verilog 文件(回忆"我昨天改了啥")
find ./rtl -name "*.v" -mtime -1
# 3. 看自己的仿真还在不在跑、占了多少内存
ps -ef | grep simv
top -u $USER # 只看自己的进程,按 P(CPU)/M(内存)排序
# 4. 仿真日志太大,只看错误和最后 50 行
grep -i -E "error|fatal" sim.log
tail -n 50 sim.log
# 5. 统计代码量(面试前看看自己写过多少行 RTL)
find ./rtl -name "*.v" | xargs wc -l | tail -n 1
ssh 远程登录 —— 每天进公司的第一个动作:
ssh zhangsan@10.20.30.40 # 用户名@服务器IP,输密码登录
ssh -Y zhangsan@10.20.30.40 # -Y 开 X11 转发:可在本地弹出 Verdi 图形界面
ssh -L 2022:10.20.30.40:22 jump@gw.company.com # 经跳板机端口转发(大公司常见)
scp rtl.tar.gz zhangsan@10.20.30.40:~/work/ # 本地文件传服务器
scp zhangsan@10.20.30.40:~/sim/sim.log ./ # 服务器文件拉回本地
环境变量 export / PATH —— 决定"敲 vcs 时系统去哪找这个程序":
echo $PATH # 查看命令搜索路径,冒号分隔
which vcs # 看 vcs 命令实际来自哪个路径
# 把 VCS 的可执行目录追加进 PATH(只在当前终端生效)
export PATH=$PATH:/opt/synopsys/vcs/current/bin
export VCS_HOME=/opt/synopsys/vcs/current
export LM_LICENSE_FILE=27000@lic-server:27001@lic-server # license 服务地址
# 永久生效:写进 ~/.bashrc(bash)或 ~/.cshrc(csh,不少 IC 公司用 csh)
echo 'export PATH=$PATH:/opt/synopsys/vcs/current/bin' >> ~/.bashrc
source ~/.bashrc # 让修改立刻生效,不用重新登录
软件安装与权限:
# 开源工具(iverilog/gtkwave)在有 sudo 权限时一键安装
sudo yum install iverilog gtkwave # RHEL/CentOS 系
sudo apt install iverilog gtkwave # Ubuntu/Debian 系
# 权限三连:看 → 改 → 换主
ls -l run.sh # -rwxr-xr-x: 属主rwx,组r-x,其他r-x
chmod +x run.sh # 给脚本加可执行权限,才能 ./run.sh
chmod 644 note.txt # 数字法:属主读写,组和其他只读
chown zhangsan:icgrp sim.log # 改文件属主/属组(通常需要 root)
sudo ! # 忘了 sudo?重跑上一条命令并加上 sudo
工程与面试怎么问:
Vim 是"模式化编辑"的代表:键盘不再只是打字工具,而是一套可组合的"编辑指令语言"。本节只讲 IC 日常真正高频的那 20% 功能 —— 三种模式、移动、编辑、查找替换、分屏,以及一份拿来就能用的 .vimrc。
是什么:Vim(Vi IMproved)是 Unix 世界默认安装的终端文本编辑器,核心设计是模式分离 —— 同一个键盘,在不同模式下承担完全不同的职责:
| 模式 | 进入方式 | 键盘的作用 | 典型按键 |
|---|---|---|---|
| Normal 普通模式(启动默认) | 任何时刻按 Esc | 移动光标、删除、复制、粘贴、查找 | h j k l、dd、yy、p、u、/ |
| Insert 插入模式 | Normal 下按 i / a / o / A | 老老实实打字 | i 光标前插入、a 光标后插入、o 下方新开一行、A 行尾插入 |
| Command-line 命令行模式 | Normal 下按 : 或 / 或 ? | 执行保存/退出/替换/跳转等命令 | :wq、:q!、:%s/a/b/g、:vs |
为什么 IC 工程师离不开 Vim(面试也常问"你为什么用 Vim 而不是 VSCode"):
「—— 移动(全部在 Normal 模式)——」
h j k l 左 下 上 右(禁用方向键是练 Vim 的最快方式)
0 / $ 跳到行首 / 行尾
gg / G 跳到文件开头 / 结尾
42G 或 :42 跳到第 42 行(编译器报错行号,直接跳)
w / b 下一个 / 上一个单词开头
Ctrl+f / Ctrl+b 向下 / 向上翻一页
「—— 编辑 ——」
i a o 进入插入模式(光标前 / 光标后 / 下一行)
x 删除光标处单个字符
dd / ndd 剪切一行 / n 行
yy / nyy 复制一行 / n 行
p / P 粘贴到下方 / 上方
u / Ctrl+r 撤销 / 重做
>> / << 整行右移 / 左移一个缩进(整理 always 块层次很好用)
. 重复上一次修改(神器)
「—— 查找与替换 ——」
/key 向下查找 key,n 下一个,N 上一个
?key 向上查找
:noh 取消高亮
:%s/old/new/g 全文把 old 换成 new(如把 clk_a 全部改名为 clk_b)
:%s/old/new/gc 同上,但每次替换先确认(改公共代码更安全)
:10,50s/old/new/g 只替换 10~50 行
「—— 分屏与多文件 ——」
:vs file.v 垂直分屏打开另一个文件(左 RTL 右 TB 对照着写)
:sp file.v 水平分屏
Ctrl+w w 在分屏间跳转;Ctrl+w h/j/k/l 定向跳
:tabnew file.v 新标签页打开
:q / :wq / :q! 退出 / 保存退出 / 不保存强制退出
ZZ 等价 :wq(顺手)
gf 跳到光标下文件名对应文件(include 的文件,一键跟进去)
.vimrc 基础配置:家目录下建 ~/.vimrc,Vim 每次启动自动读取。下面这份是"写 RTL 够用"的最小配置,可以直接抄:
" ===== 基础显示 =====
syntax on " 语法高亮(RTL 关键字变色,必开)
set number " 显示行号(编译器报错按行号跳)
set cursorline " 高亮当前行
set hlsearch " 搜索结果高亮
set incsearch " 边输入边匹配
set wrap " 长行折行显示
" ===== 缩进与制表符(RTL 可读性的命根子)=====
set tabstop=4 " Tab 显示为 4 空格宽
set shiftwidth=4 " >>/<< 缩进宽度 4
set expandtab " 输入 Tab 自动转空格(防止混合缩进)
set autoindent " 新行继承上一行缩进
set smartindent " begin/end 后自动缩进更聪明
" ===== 其他 =====
set encoding=utf-8 " 编码
set backspace=indent,eol,start " 让 Backspace 在插入模式正常删除
set showmatch " 括号匹配跳一下(begin/end 配对直观)
set laststatus=2 " 常显状态栏
" ===== Verilog 小辅助(进阶可加)=====
autocmd BufRead,BufNewFile *.v,*.sv set filetype=verilog " 识别 .sv
autocmd FileType verilog setlocal commentstring=//\ %s " 注释风格
工程与面试怎么问:
:%s/\<old_sig\>/new_sig/g。版本管理解决的是同一个问题:代码改坏了能退回来、多人改同一文件能合并、任何历史版本都能追溯。但 SVN 与 Git 用了两种完全不同的模型 —— 而这两种模型恰好对应了 IC 行业与互联网软件行业两条生态。本节先懂模型,再学命令,最后给你那张面试常考的对比表。
SVN(Subversion)—— 集中式:整个项目只有一台中央仓库服务器。你 checkout 出来的是"工作副本"(只有文件,没有历史),提交(commit)必须连网、直接进入中央仓库,所有人提交即共享。相当于"图书馆":书都在馆里,你借阅复印,改动要登记回馆。
Git —— 分布式:clone 下来的是"完整仓库"(文件 + 全部历史)。提交、建分支、看 diff 全在本地完成,离线可干所有事;最后再 push/pull 与远端同步。相当于"每人一个完整图书馆,定期互同步"。
# 0. 第一次参与项目:从中央仓库检出工作副本
svn checkout https://svn.company.com/repos/robot_soc/trunk ./robot_soc
cd robot_soc
# 1. 干活前:先把别人的最新代码拉下来(避免冲突)
svn update # 简写 svn up
# 2. 改完代码:先看自己动了哪些文件
svn status # M=已修改 ?=未纳管 A=新增 C=冲突
# 3. 新增的文件必须 add,否则 commit 不会带上它(经典事故!)
svn add rtl/fifo.v
svn diff # 逐行看自己改了什么,提交前必看
svn diff > ~/fix_fifo.diff # 把改动存成补丁文件,发同事评审
# 4. 提交(必须写 log,说明为什么改)
svn commit -m "fifo: fix full flag when depth wraps at boundary"
# 5. 常用辅助
svn log -l 5 # 看最近 5 条提交历史
svn revert rtl/fifo.v # 放弃本地修改,恢复到上次 update 的状态(后悔药)
svn co -r 1024 URL ./old # 检出第 1024 版(复现历史 bug 用)
svn merge -r 1030:1029 . # 回滚某次错误提交(反向合并)
# 0. 第一次拿项目:clone 整个仓库(含全部历史)
git clone https://github.com/example/robot-npu-rtl.git
cd robot-npu-rtl
# 1. 日常修改三连(add → commit;都在本地完成,离线可用)
git status # 看哪些文件变了(红色=未暂存,绿色=已暂存)
git add rtl/fifo.v # 暂存单个文件;git add . 暂存全部改动
git commit -m "fifo: fix full flag when depth wraps at boundary"
# 2. 分支:建分支、切换、合并(核心优势就是分支便宜)
git branch fix-fifo # 建分支
git switch fix-fifo # 切换到分支(老写法 git checkout fix-fifo)
# ……在分支上随便改、随便提交,主分支不受影响……
git switch master # 切回主分支
git merge fix-fifo # 把分支成果合并进来
# 3. 与远端同步
git pull origin master # 先拉远端最新(等价 fetch + merge)
git push origin master # 把本地提交推上远端(此时才真正共享)
git push origin fix-fifo # 推分支,通常用于发起 PR/MR 评审
# 4. 常用辅助
git log --oneline -5 # 最近 5 条提交(一行一条)
git diff # 未暂存的改动;git diff --staged 看已暂存的
git restore rtl/fifo.v # 丢弃本地未提交修改(老写法 git checkout -- file)
git stash # 临时藏起改到一半的代码,切分支救急;git stash pop 取回
| 对比项 | SVN(集中式) | Git(分布式) |
|---|---|---|
| 仓库模型 | 单一中央仓库,工作副本只有当前版本 | 每人完整仓库(含全部历史) |
| 提交是否需联网 | 必须联网,提交直接进中央仓库、全员可见 | commit 在本地,离线可提交;push 时才联网 |
| 版本编号 | 全局递增 revision(r1024),一眼看出先后 | SHA-1 哈希(a3f8c21),无顺序含义 |
| 分支 | 重(整目录拷贝),建分支/合并较笨重 | 轻(毫秒级),分支是日常工作方式 |
| 权限控制 | 目录级精细权限(按 IP 模块控制可见性) | 仓库级为主,细粒度要靠服务端方案(GitLab 保护分支等) |
| 大体积二进制 | 较友好(GDS/波形/库文件常见做法) | 原生不宜放大文件(需 LFS,且历史膨胀快) |
| 典型用途 | IC 主线 RTL/IP/版图库、签核流程 | 开源项目、模型/固件/脚本/文档、CI 生态 |
| 检出/克隆 | svn checkout URL | git clone URL |
| 更新/同步 | svn update | git pull |
| 提交 | svn commit -m "…"(立即共享) | git add + git commit -m "…"(本地),git push(共享) |
| 看改动 | svn diff / svn status | git diff / git status |
工程与面试怎么问:①「SVN 和 Git 有什么区别?」—— 答模型差异(集中/分布式)+ 各自生态定位(IC 资产管控 vs 开源协作),再补一句"我们 RTL 主库用 SVN,脚本和模型用 Git"立刻显得有实战经验;②「svn add 和 git add 的作用一样吗?」—— 不一样:svn add 决定文件"是否纳入版本管理"(不 add 的文件 commit 不会带上),git add 是"暂存进本次提交范围"(git 有暂存区概念,svn 没有);③「怎么回滚一次错误提交?」—— SVN 用反向合并 svn merge -c -REV,Git 用 git revert(生成反向提交,不用 force push)。
每次仿真要敲七八条命令?换个目录又要重敲?Makefile 的价值就是把"编译 → 仿真 → 看波 → 清理"固化成 make sim、make wave 这样的短语,并且只重跑"受改动影响"的步骤。它是 IC 工程师写的第一种"脚本",也是面试常考的基本功。
是什么:Make 是一个"按规则执行命令"的工具,规则(rule)写在 Makefile 里,每条规则由三要素构成:
目标(target): 依赖(prerequisites)
[TAB]命令(recipe)
*** missing separator)。simv: tb_top.v fifo.v # 目标 simv 依赖两个源文件
vcs -full64 -R tb_top.v fifo.v # 命令:注意行首是 TAB
clean: # 伪目标:不产出文件,只执行动作
rm -rf simv* csrc *.log
为什么 IC 流程离不开它:① 仿真命令又长又多(选项七八个、文件几十个),手敲必错;② make 自带依赖检查 —— 源文件没变就不重编,大项目增量编译省大量时间;③ Makefile 进版本库后,任何人 checkout 下来敲 make 就能得到一模一样的结果,这是可复现性的最低成本实现。编译 C/Verilog、跑回归、生成文档,全靠它。
# ===== 变量:一次定义,处处替换 =====
TOOL = vcs # 定义(递归展开 =)
OPTS := -full64 -kdb # 简单展开 :=,只求值一次(推荐,防自引用坑)
FILES = $(wildcard rtl/*.v) # 引用:$(变量名)
# ===== 自动化变量:规则里的"代词" =====
# $@ = 目标名 $< = 第一个依赖 $^ = 全部依赖(去重)
%.o: %.c
gcc -c $< -o $@ # 例:$<=main.c(第一个依赖) $@=main.o(目标)
simv: $(RTL) $(TB)
vcs $^ -o $@ # $^ = 所有 .v 依赖一次列全
# ===== 伪目标 .PHONY:声明"这不是文件名,是动作" =====
.PHONY: all sim clean
all: sim # 第一条规则是默认目标:敲 make 就执行它
sim:
./simv
clean:
rm -rf simv* csrc
# 若不加 .PHONY,目录里恰好有个叫 clean 的文件时,make clean 会
# 说"clean 已是最新,不执行"—— 这就是必须 .PHONY 的原因
# ===== 条件:同一份 Makefile 适配不同工具/环境 =====
ifeq ($(TOOL),vcs)
SIM = vcs -full64 -R -kdb
else
SIM = iverilog -g2012 -o simv # 切开源工具链,一条变量就行
endif
# ===== 常用函数 =====
# $(wildcard rtl/*.v) 通配收集所有 .v 文件,免去手写文件列表
# $(patsubst %.v,%.o,$(SRC)) 模式替换:.v 后缀换成 .o
# $(shell date +%m%d_%H%M) 调用 shell,常用来给日志目录加时间戳
# $(foreach f,$(SRC),$(call …)) 遍历列表(回归脚本里常见)
下面是一份可直接落地的 VCS 仿真 Makefile(目录结构:rtl/ 放设计,tb/ 放激励,sim/ 为工作目录),先抄后读:
##############################################################
# 通用 Verilog 仿真 Makefile(VCS) 用法: make sim / make wave / make clean
##############################################################
# ---------- 可配置区:换项目通常只改这里 ----------
TOP = tb_top # ① 仿真顶层模块名
SIM_DIR = sim # ② 仿真工作目录
RTL = $(wildcard ../rtl/*.v) # ③ 自动收集全部设计文件
TB = ../tb/$(TOP).v # ④ 激励文件
VCS_OPTS = -full64 -kdb -debug_access+all \ # ⑤ 编译选项(见第 7 节)
-R +v2k -sverilog +define+FSDB \
-l compile.log
# ---------- 目标区 ----------
.PHONY: all compile sim wave clean # ⑥ 声明伪目标
all: sim # ⑦ 默认目标:make 等价 make sim
compile: # ⑧ 编译(+elaboration)
mkdir -p $(SIM_DIR)
cd $(SIM_DIR) && vcs $(VCS_OPTS) $(RTL) $(TB)
sim: compile # ⑨ 仿真依赖编译:自动先编译
cd $(SIM_DIR) && ./simv -l sim.log
wave: # ⑩ 用 Verdi 打开刚生成的波形
cd $(SIM_DIR) && verdi -ssf $(TOP).fsdb &
clean: # ⑪ 清理所有生成物
rm -rf $(SIM_DIR)/simv* $(SIM_DIR)/csrc \
$(SIM_DIR)/*.log $(SIM_DIR)/*.fsdb $(SIM_DIR)/verdiLog
逐行讲解:
TOP = tb_top:把"顶层模块名"提成一个变量,换项目只改这一处;⑩ 里生成 tb_top.fsdb 与之呼应,所以 TB 里的 dump 文件名要写成 $(TOP).fsdb 对应的名字。SIM_DIR = sim:所有编译产物(simv、csrc、fsdb、日志)全部圈进独立目录,根目录永远干净,make clean 一把清。RTL = $(wildcard ../rtl/*.v):用 wildcard 函数自动收集设计文件 —— 新增 .v 文件零维护;若文件分布在多级目录,可换成 $(shell find ../rtl -name "*.v")。工程上更常用 -f file.f 文件列表(见 7.3),两者取其一。TB = ../tb/$(TOP).v:激励文件名与顶层同名,又是"约定优于配置"。VCS_OPTS:-full64 64 位;-kdb 生成 Verdi 调试数据库(第 7 节详讲);-debug_access+all 开调试权限(可 force/追 driver);-R 编译完直接仿真;+v2k/-sverilog 开 Verilog-2001 与 SystemVerilog;+define+FSDB 给 TB 里的 ifdef FSDB 开宏,只在需要波形时才 dump;-l compile.log 落日志。.PHONY:声明这些名字是动作不是文件,防止同名文件"劫持"目标(见 5.2)。all: sim:make 不带参数时执行第一条规则,所以默认行为就是"编译+仿真"。compile 规则:先 mkdir 建工作目录;cd $(SIM_DIR) && vcs … —— 注意每行命令是独立 shell,cd 与 vcs 必须用 && 写在同一行,分两行写则 cd 不生效(新手第二大坑)。sim: compile:依赖关系让 make 自动"先编译再仿真",一条 make sim 完成全流程。wave:verdi -ssf 直接加载指定 fsdb 文件,& 让它后台运行,不占用终端。clean:simv* 同时覆盖 simv 与 simv.daidir 等生成物;csrc 是 VCS 编译产生的 C 中间目录;verdiLog 是 Verdi 运行日志目录 —— 这四样是 VCS 流程的标准垃圾,忘记清理会占几十 GB。iverilog -g2012 -o $(SIM_DIR)/simv $(RTL) $(TB) 与 cd $(SIM_DIR) && ./simv,波形用 gtkwave $(SIM_DIR)/tb_top.vcd & 查看 —— 其余目标原封不动,这正是 5.2 节 ifeq 条件变量的用武之地。工程与面试怎么问:
%.o: %.c; gcc -c $< -o $@ 最有说服力。cd sim 单独一行对下一行毫无影响;必须 cd sim && vcs … 同行写。rm -rf $(DIR)/ 在 DIR 未定义时变成 rm -rf /;清理类规则里变量必须先判空(ifeq ($(DIR),) 直接 error)或用 $(error DIR is empty)。VCS/Verdi 装好不等于能用 —— 商业 EDA 的"可用"由三件事决定:license 授权、环境变量指向、库依赖完整。本节给出现场配置套路与启动报错的排查顺序,最后给一张商业 vs 开源工具链的选型对比表。
License 是什么:商业 EDA 按"许可点数"计费,工具启动前必须向公司的 license 服务器(Synopsys SCL / FlexLM 体系,守护进程 lmgrd + snpslmd)申请 feature 授权;拿不到 license,工具直接起不来。个人学习环境常见的"破解版"即是用伪造的 license 文件绕过校验,企业内则全部连真实 license 服务。
# ===== 典型的 VCS/Verdi 环境配置(写进 ~/.bashrc 或 ~/.cshrc)=====
# bash 写法
export VCS_HOME=/opt/synopsys/vcs/current # 工具安装根目录
export PATH=$VCS_HOME/bin:$PATH # 可执行文件(vcs/verdi)进 PATH
export LM_LICENSE_FILE=27000@lic01:27000@lic02 # license 服务:端口@主机名,可多个冗余
# 有些站点用 Synopsys 专用变量
export SNPSLMD_LICENSE_FILE=27000@lic01
# csh 写法(不少老牌 IC 公司默认 csh)
setenv VCS_HOME /opt/synopsys/vcs/current
setenv PATH $VCS_HOME/bin:$PATH
setenv LM_LICENSE_FILE 27000@lic01
# ===== 验证 =====
which vcs # 确认命令来自预期安装路径
vcs -ID # 查看版本信息(报 bug/查兼容性必备)
lmstat -a -c 27000@lic01 # 查看 license 池占用(哪个 feature 被谁占着)
端口@主机 或 端口@IP,冒号分隔可写多个做冗余;module load 是什么:机群上工具版本众多(不同项目锁不同版本),逐人手改 PATH 容易乱。Environment Modules 体系把每个工具/版本的路径与变量打包成 modulefile,用命令按需加载、干净卸载 —— 这是公司服务器上配置 EDA 环境的主流方式:
module avail # 看有哪些可加载的工具模块
module list # 看当前已加载了什么
module load synopsys/vcs-2020.03 # 加载指定版本 VCS(路径随 license 变量一起配好)
module load synopsys/verdi-2020.03
module switch synopsys/vcs-2020.03 synopsys/vcs-2022.06 # 换版本
module purge # 全部卸载,回到干净环境(排查环境问题的第一步)
常见启动问题排查表(按命中率排序):
| 现象 | 最可能原因 | 排查/解决 |
|---|---|---|
command not found: vcs | PATH 没配或没生效 | which vcs;echo $PATH;source ~/.bashrc;或 module load |
Cannot find license file / checkout failed | LM_LICENSE_FILE 未配 / license 被占满 / 网络不通 | echo $LM_LICENSE_FILE;lmstat 看池子;ping license 主机;错峰再跑 |
verdi: cannot connect to X display | 无图形转发 | ssh -Y 重新登录;echo $DISPLAY 应非空;或改用 VNC;纯跑脚本可 verdi -play xx.tcl -nologo |
| Verdi 读不了 KDB / 报版本不匹配 | VCS 与 Verdi 版本不配套 | 两边 module 版本对齐;重编生成 KDB |
编译报 libX11.so: cannot open shared object 等库缺失 | 系统依赖库不全(自己装的虚拟机常见) | yum/apt 安装对应 32/64 位库;公司环境找 IT |
| 磁盘写满 / 仿真莫名挂死 | fsdb/日志把分区写爆 | df -h 确认;make clean;波形只 dump 必要层级(见 7.2) |
没有 license / 没有服务器也能跑通整条仿真闭环 —— 开源世界给了两件定位完全不同的工具,先上手它们再上商业工具,过渡零成本:
| 维度 | 商业:VCS + Verdi | 开源:iverilog + GTKWave | 开源:Verilator |
|---|---|---|---|
| 定位 | 工业级事件驱动仿真 + 交互式调试(标配) | 轻量事件驱动仿真器 + 波形查看器(入门/教学) | RTL → C++/SystemC 编译型高速仿真 + lint |
| 仿真机制 | 事件驱动,编译为原生可执行 simv | 事件驱动,解释执行(vvp) | 把 RTL"Verilate"成 C++ 模型,与 C++ testbench 一起编译,速度极快 |
| 波形格式 | fsdb(KDB 数据库支持源码级调试) | VCD/FST(GTKWave 打开) | VCD/FST(配合 GTKWave);无交互式追 driver |
| 语言支持 | Verilog/SystemVerilog/UVM 全家桶,最全 | Verilog-2005 为主,SV 支持有限,不能跑 UVM | 可综合子集的 SV 支持好,不支持事件级延迟时序类结构 |
| 调试体验 | nTrace/nWave/Active Annotation,追 driver、X 态定位(第 8 节) | 看波形基本够,无源码联动 | 以 lint 与大规模回归见长,调试靠波形与 printf |
| 成本/许可 | 昂贵 license,公司购买 | 免费开源(GPL/Apache) | 免费开源(LGPL/Artistic-2.0) |
| 典型用法 | 公司项目日常仿真与签核 | 学校作业、HDLBits 练习、小模块验证 | CPU 项目(如一生一芯)、大规模回归、代码质量检查 |
# 开源组合跑通与 5.3 节完全相同的闭环(Linux 或 WSL)
sudo apt install iverilog gtkwave # 或 yum install
iverilog -g2012 -o sim/simv ../rtl/*.v ../tb/tb_top.v # 编译+elaboration
vvp sim/simv # 仿真,产出 tb_top.vcd
gtkwave sim/tb_top.vcd & # 打开波形
# Verilator:lint 一条命令揪出位宽/锁存器问题(写 RTL 时的免费"语法警察")
verilator --lint-only -Wall ../rtl/fifo.v
VCS 的一次完整运行,内部其实是「编译(compilation)→ elaboration(设计层次展开与链接)→ 仿真(simulation)」三步;日常使用的所有命令形态,都是这三步的不同打包方式。本节讲清楚三步各自干了什么、-R 如何一步到位、以及怎么把波形(fsdb)和调试数据库(KDB)带出来给 Verdi。
# ========== 三步分开写(调试问题时用,能看清哪一步出错)==========
vcs -full64 tb_top.v fifo.v -o simv # 编译+elaboration,生成可执行 simv
./simv # 单独运行仿真
# ========== 日常一步到位:-R = 编译完成后立即运行 ==========
vcs -full64 -R tb_top.v fifo.v -l compile.log
# ========== 标准生产姿势:文件列表 + 选项进 Makefile(见 5.3)==========
vcs -full64 -kdb -debug_access+all -R \
-f rtl.f \ # 文件列表(几十上百个源文件全在里面)
+v2k -sverilog \
+define+FSDB \
-l compile.log
./simv -l sim.log
Verdi 能把"波形"和"源码"联动起来,靠的是两样东西:KDB 数据库(编译期由 VCS 生成,记录层次、源码与信号的关系)和 fsdb 波形文件(仿真期 dump 生成)。两者的产生方式如下:
# 1) KDB:编译时加 -kdb(VCS2017 以后 -kdb 自动挂接 Verdi 平台,无需再单独加 -P)
vcs -full64 -kdb -debug_access+all -R -f rtl.f +define+FSDB
# 之后可直接 verdi -top tb_top 启动,同时拿到源码窗口与波形窗口
# 2) fsdb:在 testbench 里加系统函数(推荐写法,用宏保护)
initial begin
`ifdef FSDB
$fsdbDumpfile("tb_top.fsdb"); // 波形文件名
$fsdbDumpvars(0, tb_top, "+all"); // 0=整个层次, "+all"=记录全部信号(含被优化掉的)
// 常用变体:
// $fsdbDumpvars(1, tb_top.u_fifo); // 只 dump u_fifo 一层
// $fsdbDumpvars(0, tb_top, "+all", "+fsdbfile+tb_top.fsdb");
`endif
end
# 3) 传统 PLI 写法(老版本 VCS/教材上会遇到,认识即可):
vcs -full64 -R -P $VERDI_HOME/PLI/VCS/LINUX64/novas.tab \
$VERDI_HOME/PLI/VCS/LINUX64/novas.a \
tb_top.v fifo.v +define+FSDB
# novas.tab / novas.a 就是 Verdi 提供的 PLI 表文件与库,作用是让 VCS
# 认识 $fsdbDump* 这些非标准系统函数;新版本 -kdb 已内置,不用再手写
initial begin #2us $fsdbDumpon; end(或先 $fsdbDumpoff 再 Dumpon),波形小一个数量级,Verdi 也流畅得多。| 选项 | 作用 | 备注 |
|---|---|---|
| -full64 | 64 位编译运行 | 现代环境基本必带 |
| -R | 编译后立即运行仿真 | 一步到位;不加则手动 ./simv |
| -kdb | 生成 KDB 调试数据库 | 配合 Verdi 源码级调试(VCS2017+ 内置 fsdb 挂接) |
| -debug_access+all | 开放调试权限 | 允许 Verdi 中对信号 force/release、追 driver 等 |
| -sverilog | 启用 SystemVerilog | 写 SV/TB 必开 |
| +v2k | Verilog-2001 语法 | 老代码常需要 |
| +define+NAME | 定义编译宏 | 如 +define+FSDB 打开 dump 分支;多个用 + 相连 |
| -f file.f | 读文件列表 | 工程标准做法;列表内可用 +incdir+/ -v |
| +incdir+dir | include 搜索路径 | `include 的 .svh 头文件在哪找 |
| -timescale=1ns/1ps | 全局 timescale 兜底 | 解决"某文件没写 timescale"的混编警告 |
| -Mupdate | 增量编译 | 只重编改动部分,大工程省时 |
| -l file.log | 输出日志 | 编译与仿真日志分开落盘,方便 grep |
| -gui / -verdi | 编译后直接进图形界面 | -verdi 直接联动 Verdi(DVE 已退役) |
| +lint=TFIPC-L | 开 lint 检查 | 端口位宽不匹配等,初学强烈建议开 |
工程与面试怎么问:①「VCS 从编译到出波形,流程说一下?」—— vcs -kdb -R 编译仿真 → TB 里 $fsdbDumpvars 出 fsdb → verdi -ssf 看;能提 KDB 与 fsdb 分工、宏保护 dump,就是熟练工;②「编译通过但仿真波形全是 X,先查什么?」—— 先查复位有没有真放开、时钟有没有起振(TB 问题),再查跨模块未驱动的输入(7.1 elaboration 与 lint 能提前暴露);③「仿真太慢/波形太大怎么优化?」—— dump 层级收窄 + 时段裁剪(7.2)、增量编译 -Mupdate、必要时换 Verilator 做回归。
Verdi 不是"波形查看器",而是源码与波形联动的交互式调试器:在波形上点一个信号,源码窗口立刻高亮它的驱动者 —— 这是它相对 GTKWave 的核心价值。本节按真实调试顺序走一遍:nTrace 看层次 → nWave 看波形 → 添加/分组/测量 → X 态定位 → driver 追溯 → 脚本化。
verdi -top tb_top & # 用 KDB 启动,同时打开源码(nTrace)+ 波形(nWave)
verdi -ssf tb_top.fsdb & # 直接挂波形启动(无 KDB 时,只有波形能力)
verdi -ssf tb_top.fsdb -sswr save-1.rc & # 启动时恢复已保存的信号配置
标准调试五步(全流程):
「—— 添加信号 ——」
从 nTrace 源码/层次树:左键选中信号 → 拖拽 或 中键粘贴到 nWave
Ctrl+W(源码中点信号名) 临时查看该信号波形,不必预先添加
nWave: Signal → Get Signals 按模块批量选信号加入
「—— 整理与分组(大波形不分组没法看)——」
Insert → Group 新建分组(如 clock/reset / datapath / handshake)
拖动信号进组、右键 Rename 命名分组:clk_rst / addr / data / valid_ready
右键信号 → Radix 改进制:数据总线用 Hex,状态机用 ASCII(绑定枚举名)
右键信号 → Show Cursor Values 浮窗显示两游标处数值对照
「—— 游标与测量 ——」
左键点一下 放置主游标(Cursor-1)
中键点一下 放置从游标(Cursor-2),两游标间自动显示 Δt(时间差)
左键拖拽框选 放大该时间段(Zoom In);键盘 L 回到全景
Ctrl+/ 或搜索框 按信号值搜索:如 valid==1 && ready==0 的区间
Waveform → Search 搜索信号跳变(上升/下降/特定值),配合毛刺排查
X 态定位:未复位、未初始化、多驱动冲突、跨时钟域未同步,在波形里都表现为红色 X。人工顺着 X 往前游标倒推又慢又容易漏,Verdi 提供了半自动定位:
driver 追溯(Trace Driver):"这个信号为什么变成 0?" —— 波形上选中信号、游标停在跳变处,右键 Trace → Driver(或按快捷键),Verdi 列出所有驱动者(连续赋值 / always 块 / 模块端口),逐级双击往上追,几步就到根因;Trace Load 反向查"这个信号被谁用了",评估改动影响面时非常好用。前置条件只有一个:编译时开了 -kdb -debug_access+all(7.2/7.3 节),否则这两个功能不可用。
手点 20 次的调试动作,写成脚本就变成一条命令;团队共享脚本,谁复现 bug 都是同一视角。Verdi 的脚本有两套:nWave 的 .do(信号配置恢复)与 Tcl(可驱动全部 GUI 动作)。
# ===== 1) 信号配置复用(nWave 原生,最常用)=====
# nWave 中 File → Save Signal File 存出 fifo_sig.rc(记录分组/进制/顺序)
verdi -ssf tb_top.fsdb -sswr fifo_sig.rc & # 下次启动自动恢复全部信号视图
# ===== 2) Tcl 脚本:启动即完成"加载波形+建窗口+加信号" =====
# 文件 run_verdi.tcl:
# simvision 不得混用;Verdi Tcl 示例:
# if {[file exists tb_top.fsdb]} {
# wv_createWindow
# wv_openFile tb_top.fsdb
# wv_addSignal -group FIFO tb_top/u_fifo/wr_ptr tb_top/u_fifo/rd_ptr tb_top/u_fifo/full
# wv_zoomAll
# }
verdi -play run_verdi.tcl -nologo & # -nologo 纯脚本模式,不弹欢迎界面
# ===== 3) 回归侧:仿真结束自动把相关脚本/波形归档 =====
./simv -l sim.log && cp tb_top.fsdb /proj/wave/$(date +%m%d_%H%M)_tb.fsdb
现在把前面所有工具接成一天的真实工作循环。场景:给一个 FIFO 模块修一个"满标志在边界深度处出错"的 bug。
「—— ① 开工:上服务器、进环境、拿最新代码 ——」
ssh -Y zhangsan@10.20.30.40 # 登录(-Y 为看 Verdi 留图形通道)
module load synopsys/vcs-2020.03 synopsys/verdi-2020.03 # 配好工具链
cd ~/proj/robot_soc && svn up # 更新到仓库最新版本
「—— ② 修改:Vim 里改 RTL(改前先拉分支/确认没人同时在改)——」
vim rtl/fifo.v # 定位第 87 行,补上边界比较
# :wq 保存退出
「—— ③ 验证:一条 make 完成编译+仿真 ——」
make sim # Makefile 调 VCS:-kdb -R +define+FSDB
grep -i "error" sim/compile.log # 编译无错
tail sim/sim.log # 看到一条 case fail:full_flag 错误
「—— ④ 调试:Verdi 里找根因 ——」
make wave # 等价:verdi -ssf sim/tb_top.fsdb -sswr fifo_sig.rc
# nWave:游标停在 full 拉高的错误时刻 → 右键 Trace Driver
# 源码高亮驱动 always 块 → 发现写指针比较少算一个深度
# (本例在 ② 中已顺手修正,此处验证波形已正确)
「—— ⑤ 回归与提交 ——」
make regress # 跑全量用例(回归脚本遍历 case 列表)
make clean # 清掉几十 GB 的波形与编译产物
svn diff > ~/fifo_full_fix.diff # 导出改动自查/发评审
svn commit -m "fifo: fix full flag off-by-one at boundary depth"
「—— ⑥ 循环:下一个 bug / 下一个特性,回到 ② ——」
这条循环里每一环都对应本页一节:① 是第 2/6 节(登录与环境)、② 是第 3/4 节(Vim 与版本管理)、③ 是第 5/7 节(Makefile 与 VCS)、④ 是第 8 节(Verdi)、⑤ 是第 4 节(提交)。所谓"数字前端工程师的基本功",就是把这条循环跑到不经思考 —— 思考留给 RTL 逻辑本身。
以下资源均已核实真实可达(2026-09),按「环境基础 → 视频教程 → 图文教程 → 开源工具 → 练习平台」排列;⭐ 为强烈推荐。