【芯⽚前端】以vcs编译环境为例做⼀个适⽤于芯⽚前端的极
简版Makefile实操教程
前⾔
Makefile有⼀个经典教程,相信⼤部分⼈认识和学习Makefile都是通过这篇⽂章⼊⼿的:
不过之前和朋友聊,觉得这篇⽂章很完美但是篇幅有点长,同时教程以c语⾔编译环境为基础讲解,实际上⽐我们常⽤的编译环境还是要复杂挺多的;
感觉我们⽤不了这么多但是不学有怪可惜的,那么不如做⼀个极简版硅农专⽤的Makefile教程,本⽂内除vcs相关的内容外,基本所有信息均可以在上⽂中查阅(因此有引⽤部分就不在单独标注),属于n⼿资料吧~~~
指令和变量
当前⽬录如果存在Makefile或makefile⽂件时,那么代表我们能在这⼀⽂件夹内通过make xxx来执⾏命令,⽐如我们常⽤的仿真指令:make run tc=sanity seed=0 wave=on ccov=on
或者lint检查指令:
make full_lint
Makefile中命令的组织极为简单,标准的形式为:
target ... :prerequisites ...
command ...
target为⽬标⽂件,prerequisites为依赖⽂件,command为执⾏命令,执⾏过程即在终端中的当前⽬录内键⼊ make target,则系统检查prerequisites内的⽂件是否有⽐target更新的⽂件,有的话则执⾏对应的command;当然了这个过程类似于⼀个链式反应,如何知道prerequisites内的⽂件是不是⽐target新呢,makefile会去prerequisites的依赖⽂件或依赖项看⼀下prerequisites本⾝时候需要先更新,这样⼀级⼀级的直到到最底下的⽂件有修改,那么就处理整天线路;
⽽我们做编译仿真环境时最长使⽤的是伪⽬标的组织形式。在伪⽬标的组织形式下,prerequisites中的项相当于当前⽬标的前提条件,即要执⾏target那么要先执⾏prerequisites中的伪⽬标,之后再⽆脑执⾏command中设定好的命令,这样⼀来呢对于各种显式推导规则隐式推导规则啥的就没必要学的那么清了;
命令如同⾎液,变量如同⾻骼,Makefile中变量定义与使⽤与sh语法基本⼀致,同时sh环境中的变量
也可以在Makefile中直接使⽤:
创建与赋值
export seed ?= random
使⽤
SEED := $(seed)
Makefile中的变量近似于于systemverilog中的宏,在解释时候会在调⽤处进⾏原地展开为字符串,区别在于systemverilog中的宏是编译到了就直接展开,Makefile变量展开的时间点是在这个变量在宏观解释完成后进⾏的,⽽不是执⾏到这⼀句就展开了,⽐如下⾯这句:
x = aaa
y = $(x) bbb
x = ccc
test:
@echo $(y)
x = ddd
执⾏结果为:
[xiaotu@xiaotu-eda ~/my_work/uvm_demo/sim]$make test
ddd bbb
变量⼤⼩写敏感,可以包含数字字符下划线,赋值时不⽤加$等号前后可以有空格(这个我喜欢),调⽤时需要加上$并且最好以()或{}作为分隔;
创建变量时加export是为了传递变量的值到下级Makefile中,不过⽬前我们常⽤的Makefile形式中⼀般通过include的⽅式来(⾄少⽬前在前端设计和验证⼈员使⽤和调整的维度来看)来展开多层次的Makefile,⽽不是层层传递的⽅式,因此export在我看来意义不是太⼤,当然加上肯定是没有问题的,关于加export的具体⾏为很多地⽅都有说明,因为感觉⽤不到所以不赘述了~
关于赋值⽅式,Makefile中有四种等号:= / ?= / := / +=,他们之间的差别也是很明显的:
=
b = $(a)的⽅式赋值,那么$(a)这个变量在全局任何位置被修改含义了,$(b)的值也会使⽤跟着修改,⽐如刚刚上⾯的例⼦,⼜或者下⾯这样写,其实对$(y)效果还是⼀样的:
x = aaa
y = $(x) bbb
x := ccc
test:
@echo $(y)
x := ddd
这样导致的问题就是,你在前⾯写了⼀个赋值后,这个变量随时都有被后⾯“篡改”的可能性,除⾮你想清楚了就是要这样做哈,否则这种不确定性就搞得⼈很⼼慌,因此呢基本上我们避免这种赋值⽅式;
:=
这个赋值⽅式就⾮常主流⾮常友好了,表⽰把编译到这句代码为⽌时该变量的值展开在此处,⽐如下
⾯这种写法:
x = aaa
y := $(x) bbb
x = ccc
test:
@echo $(y)
x = ddd
执⾏结果:
[xiaotu@xiaotu-eda ~/my_work/uvm_demo/sim]$make test
aaa bbb
+=
在当前值的基础上继续加上后⾯的值,⼀般⽤来叠buff,⽐如vcs的指令那么多,总不能⼀⼝⽓写完吧,所以就分多次分多情况来写:
CMP_OPTIONS += -top $(TOP_MOD)
CMP_OPTIONS += -timescale=1ns/1ps -unit_timescale=1ns/1ps
CMP_OPTIONS += +vcs+initreg+random
#CMP_OPTIONS += -xprop=tmerge
ifeq ($(ccov), on)
CMP_OPTIONS += -cm line+fsm+cond+tgl+assert+branch
CMP_OPTIONS += -cm_cond allops+for+tf -cm_libs yv -cm_cond obs -cm_tgl portsonly -cm_glitch 0
CMP_OPTIONS += -cm_dir $(SIM_PATH)/cov/simv.vdb
endif
当然了,+=本⾝具有=的性质,这以为着如果在其中使⽤了变量,那么要提防变量的值在后⾯被修改:
x = aaa
y = bbb
y += $(x)
x = ccc
test:
@echo $(y)
x = ddd
执⾏结果:
[xiaotu@xiaotu-eda ~/my_work/uvm_demo/sim]$make test
bbb ddd
使⽤的时候⼼⾥时刻想着这个事就⾏了;
=
=的含义是如果这个值没有在其他地⽅被赋值(注意不只是在之前),那么就使⽤=的赋值结果,通常把要外部传参的那些信号习惯⽤=来赋值:
export seed ?= random
export tc  ?= sanity_case
export wave ?= off
export ccov ?= off
export mode ?= sim_base
当然了,?=还是具有=的特性,如果在其他地⽅有赋值⾏为,那么?=即使在前⾯也⽆法⽣效:
x ?= aaa
test:
@echo $(x)
x = ddd
执⾏结果:
[xiaotu@xiaotu-eda ~/my_work/uvm_demo/sim]$make test
ddd
因此呢,使⽤+=和?=的时候,⼀定要留⼼其中使⽤变量的⾏为;
实操
了解了伪指令和变量后,我们就可以开始通过实操来进⼀步学习Makefile了,那么不如就做⼀个make run的仿真执⾏指令吧;先来确定下Makefile是放在哪个位置呢,显然必须放在sim仿真⽬录下,但是有时我们需要多个sim⽬录,⽽Makefile⽂件都是⼀样的,那么我们在Makefile中使⽤include将⽂件直接建⽴在cfg⽬录就好了:
include ../cfg/cfg.mk
好,那么进⼊正题,分析下make run指令的⾏为实际就是编译 + ⽤例仿真,因此实际上这个指令我们可以拆解为两个更⼩粒度的指令make cmp + make ncrun:
.PHONY: cmp ncrun run verdi clean clean_all
run: cmp ncrun
OK,这种写法使⽤了prerequisites区域来设置了两个依赖项,由于cmp和ncrun是另外两个伪指令,因此这样写的含义就是:make run 等价于先执⾏make cmp,再执⾏make ncrun,再后⾯就结束了,因为command项中;
.PHONY中罗列了所有的伪指令,⼀般来说伪指令都会加⼊到.PHONY中,不过不放在⾥⾯并不会影响伪指令的执⾏,但是否有其他场景或者其他情况有影响我不确认,因此保险起见所有的伪指令请都加⼊进去吧;
好的,那么run指令拆解为cmp+nurun了,下⼀步当然是分别组织这两个伪指令;
cmp
先来想⼀下,执⾏编译⼯作⼀共分⼏步?
1. clean⼀下当前的⽬录,把之前那些没⽤的中间⽂件啊删⼀删;
2. 建⽴⼀下编译⽬录,主要是建⽴log和exec⽬录,当然了如果wave=on的时候呢把wave⽬录也要建⽴⼀下,如果ccov=on的时候呢
要把cov⽬录建⽴⼀下;
3. 启动vcs通过命令⾏来进⾏⽂件编译;
那么,我们的cmp指令的⼤概形式就出来了,注意command如果不和target⼀⾏的话,前⾯⼀定要⽤tab键缩进:
cmp: clean
@$(PRE_PROC)
@vcs $(CMP_OPTIONS)
在这种组织⽅式下呢,clean作为⼀个指令是cmp的前提指令,他的形式其实是很简单的:
export SIM_PATH  := ./$(mode) #这⾥不⽤要,就是给仿真⼦⽬录根据mode起个名字
clean:
@-rm -rf $(SIM_PATH)/exec ucli.key csrc vc_hdrs.f  novas_dump.log  verdiLog
可以看到clean的作⽤就是删除各种中间⽂件以及exec⽂件夹,这⾥的rm加了⼀个-rm,作⽤是如果遇到了没有执⾏成功的时候,不要停继续执⾏下去,在这⾥还看不清楚,下⾯可能会看的更明⽩;rm前⾯还加了⼀个@符号,原因是makefile会将其执⾏的命令⾏在执⾏前输出到屏幕上,@做前缀可以使这个命令不在屏幕上打印出来;
完成删除操作后呢,下⾯就是建⽴各种⽂件夹的$(PRE_PROC)操作了,这个操作的初始组织形式如下:
export PRE_PROC:= mkdir $(SIM_PATH)/log $(SIM_PATH)/exec
ifeq ($(wave), on)
PRE_PROC += $(SIM_PATH)/wave
endif
ifeq ($(ccov), on)
PRE_PROC += $(SIM_PATH)/cov
endif
makefile phony然后执⾏的时候就会发现这个问题:
[xiaotu@xiaotu-eda ~/my_work/uvm_demo/sim]$make cmp
mkdir: cannot create directory `./sim_base/log': File exists
make: *** [cmp] Error 1
什么意思呢?我们在clean的时候不是没有把log⽬录删除嘛(本来就不应该删除,不能跑⼀次仿真就把之前case的log都⼲掉吧),然后这⾥执⾏的时候mkdir⽂件夹就报了⼀个⽆伤⼤雅的error:log⽬录已存在。那么要如何解决这个问题呢?
⼀种⽅法是使⽤mkdir -p xxx,这样mkdir本⾝就会忽略已经存在的⽬录;
另⼀种⽅法就是使⽤-mkdir xxx,在makefile中主动忽略这个linux执⾏过程中的报错;
⽤哪个都⾏所以最后我就⽤的-p的⽅式来做的;
然后下⼀个知识点,ifeq($(ccov), on)这句看着就是条件判断,makefile⾥还是提供了⼏种条件判断语句如ifeq-else-endif,ifneq,ifdef 这⼏个了,当然了还有其他⼀些函数啥的,可以在开头的教程中看看,这⾥不做。于是乎呢$(PRE_PROC)呢就被组织完成,只要在cmp中直接调⽤既可以;
最后⼀步,组织vcs $(CMP_OPTIONS),也就是vcs真正的编译命令,这步的难点不在于makefile中,在于你对vcs命令⾏的熟悉程度,因此看⼀下就可以了,哦对了顺便说⼀句makefile中使⽤#来做注释,使⽤\来换⾏,同时可以直接使⽤环境变量:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。