1.Makefile可以做什么?
在ic设计和验证中,脚本的使用会大大提高办公效率,编写脚本是一个一劳永逸的事,能够免除你每次仿真重复的输入相同的命令,有了脚本以后你只需要简单的执行脚本命令即可。常用的脚本有perl,tcl,shell,makefile等,下面主要介绍一下makefile的简单使用。
Makefile可以根据指定的依赖规则和文件是否有修改来执行命令。常用来编译软件源代码,只需要重新编译修改过的文件,使得编译速度大大加快。
2.Makefile的基本格式
2.1目标:依赖
命令
目标是要生成的结果,依赖是生成结果需要的源文件和上一步骤的结果,命令是当目标不存在或者依赖更新时执行的命令。注意命令前必须用tab键来缩进,不可以用空格。
示例一:
simv: tb.sv dut.v
vcs -full64 -sverilog +v2k -debug_all +notimingcheck +nospecify tb.sv dut.v
这个例子中,simv是目标,是我们要生成的仿真执行文件。tb.sv和dut.v是依赖,执行命令前会先检查tb.sv和dut.v是否存在,以及是否有修改。当依赖文件有修改时,或者目标不存在时,则执行命令vcs -full64 -sverilog +v2k -debug_all +notimingcheck +nospecify tb.sv dut.v来生成simv。
2.2伪目标
有时候目标并不是真实要生成的文件,比如我们要用Makefile调用simv来仿真,并不存在一个目标文件,这种情况我们称之为伪目标PHONY。伪目标是仿真经常使用的makefile写法。
示例二:
.PHONY sim
sim: simv
./simv -xxx
这样,我们在terminal里就可以用make sim来调用仿真命令。
这个例子中,sim并不是要生成的结果文件,而只是我们给操作起的一个名字。由于伪目标总是不存在,所以命令也一定会重新执行,即使simv没有修改。
我们常常在Makefile的开头来用.PHONY显式指明伪目标。
2.3默认目标
如果我们只是敲make(后面不跟目标),那么将调用Makefile里的第一个目标。那么我们为了防止出错通常把第一个目标定义成all(执行完整的流程)或者help(显示帮助菜单)。我更倾向于后者,可以帮助我们回忆如何使用Makefile脚本。
示例三:
.PHONY help sim
help:
echo "make help"
echo "make simv to compile"
echo "make sim to run simulation"
simv: tb.sv dut.v
vcs -full64 -sverilog tb.sv dut.v
sim:
./simv -xxx
这样,当我们不记得如何使用Makefile的时候,直接敲make就会有使用帮助菜单。另外,我们还可以看到,一个目标后面可以执行多条命令,比如这里的三条echo命令。
2.4隐藏回显
在执行命令前,make会先回显命令(就是打印出命令)。上面的make help会输出:
echo "make help"
make help
echo "make simv to compile"
make simv to compile
echo "make sim to run simulation"
make sim to run simulation
看起来有点重复了。在命令前加@可以关闭回显示,这正是我们需要的。改进过的Makefile如示例五。
示例四:
.PHONY help
help:
@echo "make help"
@echo "make simv to compile"
@echo "make sim to run simulation"
2.5makefile内定义变量
当源文件比较多,且常需要增减,我们可以把依赖定义成一个变量,放成文件开头,如下。
示例五:
tbfile := tb.sv env_pkg.sv test_pkg.sv
rtlfile := dut.v a.v b.v c.v
simv: $(tbfile) $(rtlfile)
vcs -full64 -sverilog $(tbfile) $(rtlfile)
当要增减文件时,只需要修改文件开头即可。
2.6调用shell命令
如果rtl文件太多,还可以在Makefile里调用shell命令来帮助生成。如下面的例子:
示例六:
tbfile := $(shell ls *.sv)
rtlfile := $(shell find rtl -name "*.v")
simv: $(tbfile) ($rtlfile)
vcs -full64 -sverilog $(tbfile) $(rtlfile)
例中的tb和rtl文件写两遍,是不是有点麻烦。我们最好能简化一下。在Makefile中有几个特殊变量,如$@表示目标,$^表示依赖。所以示例中的命令可以简化成:
simv: $(tbfile) $(rtlfile)
vcs -full64 -sverilog $^
学到到这里你已经可以写出大部分的Makefile脚本了。
2.7为makefile增加选项
但我们还需要进一步学习两个重要功能:选项和目录递归。
我们常需要在仿真时提供一些选项,比如testcase名,是否是post仿真,是否要dump波形。那么怎么实现呢?其实Makefile允许从命令行提供额外的变量,格式为OPTION=value。如下面的例子,假设有三个选项,TC、POST、DUMP:
示例七:
ifeq ($(POST),1)
SRC := "netlist.v"
else
SRC := "rtl.v"
endif
ifeq ($(DUMP),1)
DUMP_DEF := "+define+DUMP"
else
DUMP_DEF := ""
endif
sim:
@echo "vcs -full64 -sverilog $(SRC) $(DUMP_DEF) +UVM_TESTNAME=$(TC)"
那么,使用时就可以通过命令行控制选项开关:
make sim TC=basic_test
make sim TC=basic_test POST=1
make sim TC=basic_test DUMP=1
make sim TC=basic_test POST=1 DUMP=1
2.8makefile的大杀器:目录递归
另一个重要功能是目录递归,目录递归有一个典型的应用:make clean。在顶层目录里make clean时,将会自动调用子目录的make clean。这个怎么实现呢?看下面的例子:
示例八:
./Makefile
clean:
rm -f *~
make -C a clean
make -C b clean
./a/Makefile
clean:
rm -f *~
./b/Makefile
clean:
rm -f *~
make -C c clean
./b/c/Makefile
clean:
rm -f *~
我们看到一个make -C subdir clean,就是说可以通过-C来把目标clean传递给子目录,相当于在Makefile里调用了另一个Makefile。这样在顶到make clean时,将自动递归到所有的子目录。
2.9Makefile的引用与复用
最后还有一点,我们也会经常遇到,把共用的Makefile脚本写到common.mk,然后再include common.mk,这样可以让Makefile看起来更简洁。
与IC Flow的联系
到这里,学了这么多,你已经可以写一些复杂的Makefile了。但重在应用,在IC设计里,我们常常用Makefile串起多个工具,实现完整的流程。下面是一个启发型的例子。
示例9:
.PHONY help clean rtl lint sim syn lec pr pt lvs
help:
@echo "make help"
clean:
rm -rf *~ *.log *.fsdb csrc simv* ...
make -C xxx clean
rtl:
python3 ...
lint:
sg_shell/nLint ...
sim:
vcs/irun ...
syn:
dc_shell -64bit -topographical -f run_syn.tcl | tee log/syn.log
lec:
fm_shell/lec ...
pt:
pt_shell ...
pr:
innovus/icc ...
lvs:
calibre ...
3.vcs仿真简单makefile示例
.PHONY: com cov clean debug sim dve
OUTPUT = simv_asyn_fifo
ALL_DEFINE = +define+DUMP_VPD
# Code coverage command
#CM = -cm line+cond+fsn+branch+tql
CM = -cm line+cond+branch
CM_NAME = -cm_name $(OUTPUT)
CM_DIR = -cm_dir ./$(OUTPUT).vpd
#vpd file name
VPD_NAME = +vpdfile+$(OUTPUT).vpd
#Comppile command
#VCS = vcs -sverilog +v2k \
-debug_all \
+notimingcheck \
+nospecify \
+vcs+flush+all \
+vpdfile+$(OUTPUT).vpd \
$(CM) \
$(CM_NAME) \
$(CM_DIR) \
$(ALL_DEFINE) \
-o $(OUTPUT) \
-l compile.log \
-f file_list.f
#VCS = vcs -sverilog +v2k \
-debug_all \
+notimingcheck \
+nospecify \
+vcs+flush+all \
$(ALL_DEFINE) \
$(VPD_NAME) \
-o $(OUTPUT) \
-l compile.log \
-f file_list.f
# simulation command
#SIM = ./$(OUTPUT) \
$(CM) $(CM_NAME) $(CM_DIR) \
-l $(OUTPUT).log \
+vpdfile+$(OUTPUT).vpd
#SIM = ./$(OUTPUT) \
$(VPD_NAME) \
-l $(OUTPUT).log
#CLEAN=./clean
#start compile
com:
$(VCS)
#start simulation
sim:
$(SIM)
#start dve show
dve:
dve -vpd $(OUTPUT).vpd &
#show the coverage
cov:
dve -covdir *vdb &
debug:
dve -vpd $(OUTPUT).vpd &
#start clean
clean:
rm -rf ./csrc *daidir ./csrc *.log *.vpd *.vdb simv* *.key *race.out vc_hdrs.h DVEfiles sim* compile* *.fsdb