3、Makefile入门:Makefile基本语法、目标与依赖、变量定义、常用仿真Makefile模板
各位同学,今天我们来聊聊Makefile。说实话,我刚入行那会儿,觉得Makefile这东西可有可无。跑仿真嘛,敲几条命令不就完了?直到有一次,我负责一个大型SoC项目,每天要跑几十次回归测试,手动敲命令敲到手指抽筋……嗯,从那以后,我老老实实把Makefile学了个透。
说白了,Makefile就是一套自动化脚本。它帮你管理编译、仿真、清理这些重复劳动。你想想看,每次改完代码都要敲一长串命令,多烦人?Makefile就是你的“一键自动化”工具。
3.1 Makefile基本语法
Makefile的规则其实很简单,就三要素:目标、依赖、命令。我习惯把它理解成“我想做什么,需要什么材料,怎么做”。
目标: 依赖文件1 依赖文件2
命令1
命令2
注意了,命令前面必须是一个Tab键,不是空格!我见过太多新手在这里栽跟头。曾经有个同事,调试了半天Makefile不执行,结果发现用的是空格……
举个最简单的例子:
sim:
vlog -sv counter.sv
vsim -c -do "run -all" counter
这个Makefile定义了一个叫sim的目标。你只要在终端敲make sim,它就会自动执行下面两条命令。是不是很方便?
3.2 目标与依赖
目标可以不止一个。我通常会把仿真流程拆成几个小目标:编译、仿真、波形查看、清理。每个目标只做一件事,这样更清晰。
all: compile sim wave
compile:
vlog -sv counter.sv
sim: compile
vsim -c -do "run -all" counter
wave: sim
vsim -do "add wave *; run -all" counter
clean:
rm -rf work transcript vsim.wlf
这里有个关键点:依赖链。你看sim依赖compile,wave依赖sim。当你敲make wave时,Make会自动先执行compile,再执行sim,最后执行wave。这就是Makefile的智能之处——它知道什么先做什么后做。
all作为第一个目标。这样你直接敲make(不带参数),它就会执行all,也就是完整的流程。省事!
3.3 变量定义
写Makefile最怕什么?重复。比如你有个文件名叫counter.sv,在好几个地方都用到了。万一哪天改文件名了,你得挨个改。这时候变量就派上用场了。
TOP = counter
SRC = counter.sv
TB = testbench.sv
SIM_TIME = -c -do "run -all"
compile:
vlog -sv $(SRC) $(TB)
sim: compile
vsim $(SIM_TIME) $(TOP)
变量用$()引用。我习惯把常用的路径、文件名、仿真选项都定义成变量。这样改起来方便,一眼就能看到配置。
还有一种叫自动变量,比如$@表示目标名,$^表示所有依赖。举个例子:
counter.vvp: counter.sv testbench.sv
vlog -sv $^ -o $@
这里$^展开就是counter.sv testbench.sv,$@展开就是counter.vvp。省得你写两遍文件名。
=(递归展开)、:=(简单展开)、?=(条件赋值)。我建议用:=,它只展开一次,不容易出bug。我曾经被=的递归展开坑过一次,变量引用了半天才发现是死循环……
3.4 常用仿真Makefile模板
下面是我个人最常用的模板,适用于大多数Verilog/SystemVerilog仿真项目。你直接拿去改改就能用。
# 变量定义
TOP = top
SRC_DIR = ./src
TB_DIR = ./tb
WORK_DIR = ./work
WAVE_FILE = wave.vcd
# 文件列表
SRC_FILES = $(wildcard $(SRC_DIR)/*.sv)
TB_FILES = $(wildcard $(TB_DIR)/*.sv)
# 仿真选项
VLOG_OPTS = -sv -work $(WORK_DIR)
VSIM_OPTS = -c -do "run -all; quit"
# 默认目标
all: compile sim wave
# 创建work库
$(WORK_DIR):
vlib $(WORK_DIR)
# 编译
compile: $(WORK_DIR)
vlog $(VLOG_OPTS) $(SRC_FILES) $(TB_FILES)
# 仿真
sim: compile
vsim $(VSIM_OPTS) $(TOP)
# 查看波形
wave: sim
vsim -do "add wave *; run -all" $(TOP)
# 清理
clean:
rm -rf $(WORK_DIR) transcript vsim.wlf $(WAVE_FILE)
# 伪目标
.PHONY: all compile sim wave clean
这个模板有几个亮点:
- 自动查找文件:用
wildcard自动收集所有.sv文件,不用手动一个个写 - 依赖自动管理:编译依赖work库,仿真依赖编译,环环相扣
- 伪目标:
.PHONY告诉Make这些目标不是真实文件,避免同名文件冲突
.PHONY。结果目录里有个叫clean的文件夹,每次make clean都提示“已是最新”,就是不执行清理命令。折腾了半天才发现是伪目标的问题。
3.5 知识体系总览
下面这张图帮你理清Makefile的核心逻辑。我画的是最简版本,你一看就懂。
这张图把Makefile拆成了三个核心模块:基本语法、目标与依赖、变量定义。这三个模块组合起来,就构成了仿真Makefile模板。你只要掌握了这三个点,写任何项目的Makefile都不在话下。
3.6 实战建议
最后给你几条实在的建议:
- 从小做起:先写一个最简单的Makefile,只编译一个文件。跑通了再慢慢加功能
- 多用变量:路径、文件名、选项都定义成变量。改起来方便,不容易出错
- 别忘了.PHONY:尤其是
clean、all这些目标,一定要声明为伪目标 - 测试依赖链:改完Makefile后,故意改个源文件,看看
make会不会重新编译。如果没反应,说明依赖写错了
嗯,Makefile这东西,说白了就是“懒人工具”。你越懒,就越应该把它写好。我当年就是被逼着学会了Makefile,现在想想,真香。