导读:作者有幸在中国电子信息领域的排头兵院校“电子科技大学”攻读研究生期间,接触到前沿的数字IC验证知识,旁听到诸如华为海思、清华紫光、联发科技等业界顶尖集成电路相关企业面授课程,对数字IC验证有了一些知识积累和学习心得。为帮助想入门前端IC验证的朋友,思忱一二后,特开此专栏,以期花最短的时间,走最少的弯路,学最多的IC验证技术知识。
IC的整个开发中,验证通常包含两个:
本节学习目标
掌握ASIC设计流程
掌握ASIC验证的基本概念
掌握ASIC验证策略:RCDV(Random Coverage Driven Verification)
如何验证一个加法器【在实例中固化之前学习的知识】
面试上常常会问,注意理解!
理论上设计流程
注:流程很多,掌握其中一种就可以了。
RTL设计 Design
功能验证Verification
RTL freeze:RTL冻结
逻辑综合 logic synthesis
Postsynthesis design validation
静态时序分析:STA(static timing analysis)
可测试性设计(DFT,Design For Test):Test generation and fault simulation
物理实现(PD,Phsical Design)【迭代过程】
Production-ready masks
UPF: Universal Power Format,描述芯片电源结构的一种语言,主要用来做功耗分析。【倾向于架构级工程师掌握】
UVM:Universal Verification Methodology
BIST:Build In Self Test,内建性测试。membist,内部自己产生激励并对比结果。当然还有LogicBist,逻辑内建性测试。
SCAN:通过芯片外围的IO管脚灌入激励,看scan chain的输出结果是否正确
ATPG:Automatic Test Pattern Generation,从芯片外部的机台产生的激励。
CTS:Clock Tree Synthesis
DRC:Design Rule Check
LVS:Logic Versus Schematic,类比FM
芯片公司,FPGA用来做原型验证,对于数字验证来讲是一个互补的关系,FPGA更多应用在系统级大的模块的数据流验证中,跑的很快;并且SoC软件的调试等工作也会放到FPGA中进行!
EDA更多的是跑一些BT,IT,系统级的更多会放在FPGA上跑!
注:输入的信号称为激励,输入激励的不同组合我们称之为不同的Pattern,也叫测试点(Test Pattern)。输出的信号称为响应。
+plusargs_save +seed=$(SEED)
:把SEED
传入到上面的设计源文件中去!-cm line+cond+fsm+tgl+branch
:代码覆盖率统计,包括行、条件、状态机、tgl、分支.vimrc
设置选项首先在.vimrc
中再添加一些设置选项(具体含义,可自行百度搜索),帮助我们更高效地编程,之前的.vimrc
设置参考:【数字IC验证快速入门】5、快速上手Linux下的文本编辑神器gvim。
如果
.vimrc
打开显示只读,尝试加上前缀sudo
set noai
set nosi
set cursorline
set cursorcolumn
iab md module module_name();<cr><cr><cr>endmodule
iab alc always @(*)begin<cr><cr><cr>end
iab als always @(posedge clk or negedge rst_n) begin<cr>if(rst_n == 1'b0) begin<cr><cr>end<cr>else begin<cr><cr>end
iab if0 if( ) begin<cr><cr>end
iab if1 if( ) begin<cr><cr>end<cr>else begin<cr><cr>end
iab if2 if( ) begin<cr><cr>end<cr>else if( ) begin<cr><cr>end<cr>else begin<cr><cr>end
iab if3 if( ) begin<cr><cr>end<cr>else if( ) begin<cr><cr>end<cr>else if( ) begin<cr><cr>end<cr>else begin<cr><cr>end
iab ini initial begin<cr><cr><cr><cr>end
iab dispb $display("@%0t: xxx a=%0b, b=%0b",$time, a, b);
iab dispd $display("@%0t: xxx a=%0d, b=%0d",$time, a, b);
iab disph $display("@%0t: xxx a=%0h, b=%0h",$time, a, b);
adder32.v
module adder_32(
input wire [31:0] a_in,
input wire [31:0] b_in,
input wire c_in,
output wire [31:0] sum_out,
output wire c_out
);
assign {c_out, sum_out} = a_in + b_in + c_in;
endmodule
md
,然后回车会自动补全我们在.vimrc
设置的对应内容:%s/module_name/adder32_tb_rand/g
,替换module的默认名字ws
来分割窗口,使用ctrl+[h/j/k/l]
在各个窗口之间切换;使用vt
来打开左侧导航窗格,使用r
来刷新显示刚刚新建的adder32_tb_rand.v
:sp .
来分割窗口表示在gvim另开一个窗口,并打开上级目录,然后通过方向键选择文件,回车键确定文件,ctrl + w
可以在两个窗口之间进行切换!adder32.v
中的module头信息复制到adder32_tb_rand.v
中进行例化,在adder32.v
中光标定位到模块开头,然后8yy
即可赋值8行内容!在adder32_tb_rand.v
合适位置按下p
即可粘贴!粘贴完毕后,选中adder32.v
所在窗口,然后输入wc
即可关闭该窗口!adder32_tb_rand.v
粘贴的内容中,在c_out
后面加上一个,
,便于后面使用正则表达式进行内容的替换!module adder32
改成adder32 u_adder32
,使用正则表达式:4,4s/\(\w\+\)\(\s\+\)\(\w\+\)/\3\2u_\3/g
。正则表达式的框架是%s/①/②/g
,首先作用域是第四行,所以%
改成了4,4
;正则表达式的①
- \(\w\+\)\(\s\+\)\(\w\+\)
是将module adder32
分为3组,注意其中括号()
是需要转义的,所以要用\(\)
;\w\+
是匹配任意多个单词字母;\s\+
是匹配任意多个空格;正则表达式的②
- \3\2u_\3
为要替换的内容,\3
表示第三组的内容,其他以此类推即可!adder32.v
中复制的module头信息中的输入输出接口给删除了,删除方法使用visual模式:先按ctrl+v
进入visual block
模式,然后鼠标或者h/j/k/l
来a_in,
为.ain (ain),
。显然可以使用正则将其分为两组,一组是word,一组是逗号。使用的正则表达式::5,10s/\(\w\+\)\(,\)/.\1 (\1)\2/g
,框架还是%s/①/②/g
,首先是5-10行,所以%
改成了5,10
;正则表达式的①
- \(\w\+\)\(,\)
是将a_in,
这一类的代码分为2组,第一组是a_in
之类的字符串,第二组是逗号;正则表达式的②
- \3\2u_\3
为要替换的内容,替换内容是在第一组前面加点,然后第一组括号,最后是第二组。c_out
最后的,
进行去除。同上操作,我们再复制adder32.v
中的信号名到adder32_tb_rand.v
中并用正则表达式进行一些替换操作!至此便把DUT进行了例化并把所有的信号进行了连接,此时代码内容如下:
module adder32_tb_rand();
reg [31:0] a_in;
reg [31:0] b_in;
reg c_in;
wire [31:0] sum_out;
wire c_out;
adder_32 u_adder_32(
.a_in (a_in),
.b_in (b_in),
.c_in (c_in),
.sum_out (sum_out),
.c_out (c_out)
);
endmodule
接下来产生激励,采用的是随机化的方式。
定义一个reg
型的数组data_in :reg [32:0] data_in = {32'h0, 32'h0, 32'h0};
,还有一个需要注意的是:这里定义的是33bit,因为我们后面要使用系统函数sum
来对data_in的三个元素进行求和!(单比特数组的求和返回单比特的数值)
reg [7:0] data_in
,那么data_in[1]
代表数据第1bit;如果data_in是数组,定义reg [7:0] data_in[20]
,那么data_in[1]
代表数据第1元素,元素起始位置也是0,data_in[0].data_in
的值赋给a_in
、b_in
,但是a_in
、b_in
是32bit的。一种解决方法是,在产生data_in
的数据的时候,我们只要32bit,如:data_in[0][31:0] = $random(seed);
定义随机种子seed
和激励数目stimulus_num
,这两个都是外面的脚本(通常是Makefile)传递进来的
接下来再initial块中读取seed
和stimulus_num
,输入ini
即可自动填充完整initial框架!再initial块中输入if1
即可自动填充完整if语句块!
$value$plusargs("seed=%d", seed)
来获取seed,如果没有获取成功将seed赋值为100
,获取成功使用系统函数display
将其打印出来!注意我们是十进制打印,直接在输入模式下dispd
,然后按空格即可自动补全!stimulus_num
,直接使用6yy + p
的方式快速复制粘贴!然后使用:32,37s/seed/stimulus_num/g
进行快速替换接下来使用随机种子来产生激励,在initial块中来赋值,输入ini
然后按空格即可自动补全!然后使用repeat
来产生stimulus_num
个激励(给data_in
赋值),注意变量名字很长时,可以使用tab
键来快速补全!(Tab键自动补全还有个好处是可以避免拼写错误,这是个不错的好习惯!)
data_in[0] = $random(seed);
可以yy + p + p
来快速赋值两行,然后在命令模式下使用r + 1
和 r + 2
来快速替换data_in
的索引。c_in
,所以我们将data_in[2]
设置为1'b0
。快速操作的方法:光标定位到$random(seed);
开头,然后在命令模式下按下D
,即可删除光标到行尾的所有内容!为了和其他模块通信,获取激励是否成功产生的标志,我们这里再定义一个信号量sti_gen
(标志信号),在每次激励产生前sti_gen
等于0,产生后等于1,这样其他模块捕获一个上沿即可获取是否成功产生激励。
sti_gen = 1
,这样可能会造成dut_sum
和gld_sum
错位的情况发生,应该等一会,这里设置为#10;
产生激励后,将激励值赋给DUT,这里直接使用always的组合逻辑即可,输入模式下输入alc
,即可自动补全。
除了产生激励,还要给出Golden模型(Reference Module),Golden结果通常会放到一个队列中!队列语法后面会详细介绍!
给了Golden model还需要将激励发送到Golden model,使用initial
块来完成,输入ini
即可自动补全!注意,gld_sum.push_back(data_in.sum);
操作是队列的一些操作,后面会再详细介绍,这里知道作用即可。其中.push_back
和.sum
均是系统函数!golden model
中的.sum
是一个行为级描述,而DUT中的是一个RTL描述,通过比对两者结果即可验证!
结果收集好了,接下来就是要做对比了。这里定义两个临时变量gld_sum_rslt
和dut_sum_rslt
。在比较前,需要等到stimulus产生完之后,所以这里需要定义一个事件event sti_end;
,关于事件的详细介绍,后面也会详细介绍,这里仅仅知道作用。在stimulus发送完毕之后,把这个事件触发,然后才进行结果的比对!如何把这个事件触发,在Step 2
末尾加上-> sti_end
即可。然后在Step5
中加入@sti_end
等待触发即可!
然后获取gld_sum和dut_sum进行比对!
在testbench中check最终结果,我们可以增加一个错误标志信号!err_cnt
来记录错误个数,如果err_cnt
为0那么就可以进行pass!
adder32_tb_rand.v
module adder32_tb_rand();
reg [31:0] a_in;
reg [31:0] b_in;
reg c_in;
wire [31:0] sum_out;
wire c_out;
int seed;
int stimulus_num;
reg sti_gen;
reg [32:0] data_in[3] = {33'h0, 33'h0, 33'h0};
reg [32:0] gld_sum [$];
reg [32:0] dut_sum [$];
reg [32:0] gld_sum_rslt;
reg [32:0] dut_sum_rslt;
event sti_end;
int err_cnt;
adder_32 u_adder_32(
.a_in (a_in),
.b_in (b_in),
.c_in (c_in),
.sum_out (sum_out),
.c_out (c_out)
);
// Step1, Get random seed and stimulus number arguments from external scripts
initial begin
if( ! $value$plusargs("seed=%d", seed) ) begin
seed = 100;
end
else begin
$display("@%0t: seed a=%0d",$time, seed);
end
if( ! $value$plusargs("stimulus_num=%d", stimulus_num) ) begin
stimulus_num = 50;
end
else begin
$display("@%0t: stimulus_num a=%0d",$time, stimulus_num);
end
end
// Step2, Generate stimulus with random seed
initial begin
sti_gen = 1'b0;
repeat(stimulus_num) begin
sti_gen = 1'b0;
#10;
data_in[0][31:0] = $random(seed);
data_in[1][31:0] = $random(seed);
data_in[2] = 1'b0;
#10;
sti_gen = 1'b1;
#10;
end
-> sti_end;
end
// Step2.1, Send stimulus to DUT
always @(*)begin
a_in = data_in[0];
b_in = data_in[1];
c_in = data_in[2];
end
// Step3, Send stimulus to Golden model
initial begin
repeat(stimulus_num) begin
@(posedge sti_gen)
gld_sum.push_back(data_in.sum);
end
end
// Step4, Capture DUT output
initial begin
repeat(stimulus_num) begin
@(posedge sti_gen)
dut_sum.push_back({c_out, sum_out});
end
end
// Step5, Check result
initial begin
gld_sum_rslt = 'h0;
dut_sum_rslt = 'h0;
err_cnt = 'h0;
@sti_end;
repeat(stimulus_num) begin
gld_sum_rslt = gld_sum.pop_front();
dut_sum_rslt = dut_sum.pop_front();
if(gld_sum_rslt != dut_sum_rslt) begin
$display("@%0t: Error: gld_sum != dut_sum; gld_sum=%0h, dut_sum=%0h",$time, gld_sum_rslt, dut_sum_rslt);
err_cnt ++;
end
end
if( err_cnt == 'h0 ) begin
$display("***********************************");
$display("***********************************");
$display("***********************************");
$display("*************TEST PASS*************");
$display("***********************************");
$display("***********************************");
$display("***********************************");
end
$finish();
end
endmodule
至此,TestBench编写完毕,接下来我们来编写Makefile。
Makefile
all: compilation simulation
seed = $(shell date +%s)
stimulus_num = 100
compilation:
vcs -full64 -sverilog adder32.v adder32_tb_rand.v -debug_all -l comp.log
simulation:
./simv -l sim_$(seed).log +plusargs_save +seed=$(seed) +stimulus_num=$(stimulus_num)
seed = $(shell date +%s);
:执行一个shell命令,shell命令叫date
,%s
以serial的形式打印,打印出来是一串数字,固定格式!stimulus_num = 100;
:定义激励个数为100compilation
-sverilog
:有用到system verilog的语法,所以这里需要加上这个选项!-debug_all
:习惯-l comp.log
:打印记录放里面simulation
./simv
:仿真命令-l sim_$(seed).log
:记录仿真结果,通常与种子联系起来!+plusargs_save +seed = $(seed) +stimulus_num = $(stimulus_num)
:将外部变量读取到TB中!这里就是读取seed
和stimulus_num
了。要特别注意:seed = $(shell date +%s)
和stimulus_num = 100
这两句后面不能加标点,不然会出现传递不进去参数的情况,如果出现传递参数错误,通常会出现如下提示信息:@0: seed a=1630487312
和@0: stimulus_num a=100
则说明参数传递错误,需要及时debug!...
/bin/sh: .log: command not found
/bin/sh: +stimulus_num: command not found
make: *** [simulation] Error 127
[verifier@localhost adder32]$
写完之后便可以进行初步的仿真了。
在命令行直接输入make
即可运行仿真!
成功输出后的打印日志如下:
[verifier@localhost adder32]$ make
vcs -full64 -sverilog adder32.v adder32_tb_rand.v -debug_all -l comp.log
Chronologic VCS (TM)
Version O-2018.09-SP2_Full64 -- Wed Sep 1 17:08:31 2021
Copyright (c) 1991-2018 by Synopsys Inc.
ALL RIGHTS RESERVED
This program is proprietary and confidential information of Synopsys Inc.
and may be used and disclosed only as authorized in a license agreement
controlling such use and disclosure.
Warning-[DEBUG_DEP] Option will be deprecated
The option '-debug_all' will be deprecated in a future release. Please use
'-debug_acc+all -debug_region+cell+encrypt' instead.
Parsing design file 'adder32.v'
Parsing design file 'adder32_tb_rand.v'
Top Level Modules:
adder32_tb_rand
No TimeScale specified
Starting vcs inline pass...
1 module and 0 UDP read.
recompiling module adder32_tb_rand
make[1]: Entering directory `/home/verifier/workspace/adder32/csrc'
rm -f _csrc*.so pre_vcsobj_*.so share_vcsobj_*.so
if [ -x ../simv ]; then chmod -x ../simv; fi
g++ -o ../simv -Wl,-rpath-link=./ -Wl,-rpath='$ORIGIN'/simv.daidir/ -Wl,-rpath=./simv.daidir/ -Wl,-rpath='$ORIGIN'/simv.daidir//scsim.db.dir -rdynamic -Wl,-rpath=/opt/synopsys/vcs/vcs-mx/O-2018.09-SP2/linux64/lib -L/opt/synopsys/vcs/vcs-mx/O-2018.09-SP2/linux64/lib objs/amcQw_d.o _46704_archive_1.so SIM_l.o rmapats_mop.o rmapats.o rmar.o rmar_nd.o rmar_llvm_0_1.o rmar_llvm_0_0.o -lzerosoft_rt_stubs -lvirsim -lerrorinf -lsnpsmalloc -lvfs -lvcsnew -lsimprofile -luclinative /opt/synopsys/vcs/vcs-mx/O-2018.09-SP2/linux64/lib/vcs_tls.o -Wl,-whole-archive -lvcsucli -Wl,-no-whole-archive /opt/synopsys/vcs/vcs-mx/O-2018.09-SP2/linux64/lib/vcs_save_restore_new.o -ldl -lc -lm -lpthread -ldl
../simv up to date
make[1]: Leaving directory `/home/verifier/workspace/adder32/csrc'
CPU time: .148 seconds to compile + .118 seconds to elab + .142 seconds to link
./simv -l sim_1630487312.log +plusargs_save +seed=1630487312 +stimulus_num=100
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Sep 1 17:08 2021
@0: seed a=1630487312
@0: stimulus_num a=100
***********************************
***********************************
***********************************
*************TEST PASS*************
***********************************
***********************************
***********************************
$finish called from file "adder32_tb_rand.v", line 116.
$finish at simulation time 3000
V C S S i m u l a t i o n R e p o r t
Time: 3000
CPU Time: 0.150 seconds; Data structure size: 0.0Mb
Wed Sep 1 17:08:32 2021
[verifier@localhost adder32]$
VCS语法如下:
$ vcs [options] source_files
常用编译选项及其含义
-help:vcs帮助,有各编译选项意义;
-full64:以64位模式编译设计并创建64位可执行文件用于64位模式下的模拟;
-vpi:允许使用vpi PLI访问例程;
-sverilog:允许在Accellera systemVerilog规范中使用Verilog语言扩展;
-v2k:使用Verilog 1364-2001标准;
-cpp:使用c++编译器;
-debug_pp:允许转储到VPD并使用UCLI命令和DVE;
-debug:启用UCLI命令和DVE;
-debug_all:启用UCLI命令和DVE,也使线路步进;
-notice:启用详细的诊断消息;
+lint=[no]ID|none|all,... 使能或者禁用verilog的lint消息;
+rad:对设计进行辐射技术优化;
+vcs+lic+wait:如果没有可用的通知,则告诉VCS等待网络许可证;
-f <filename>: 指定一个文件,其中包含源文件和编译时选项的路径名列表;
-o <name>:指定输出可执行文件的文件名,默认为 simv;
-R:该选项告诉VCS在编译完后直接运行可执行程序,若没有该选项,那么vcs在编译后直接退出;
-l <filename>:(小写字母L)如果包含-R,-RI或-RIG选项,则指定VCS记录编译消息和运行时消息的日志文件;
-Mupdate[=0]:默认情况下,VCS会在编译之间覆盖Makefile。 如果希望在编译之间保存Makefile,请输入此内容选项与0参数。输入不带0参数的参数,指定默认情况下,增量编译和更新Makefile文件;
-CFLAGS <options>:将选项传递给C编译器,允许多个-CFLAGS,允许传递C编译器优化级别。
-timescale=<time_unit>/<time_precision>:指明时间精度;
-ucli:在运行时指定UCLI模式;
+incdir+<directory>:指定包含使用`include 编译器指令指定的文件的目录,可以指定多个目录,用+字符分隔每个路径名称;
+libext+<extension>:指定VCS仅在具有指定扩展名的Verilog库目录中搜索源文件,可以指定多个扩展名,用+字符分隔每个扩展名。例如+libext++.v指定搜索没有扩展名和库扩展名为.v的库文件。 输入-y选项时输入此选项。
+systemverilogext+<ext>:指定包含SystemVerilog源代码的源文件的文件扩展名;
-gui[=<dve|verdi>]:启动用户指定的图形用户界面,如果未提供参数,则在检测到有效的VCS_HOME环境变量时,VCS将启动Verdi。 否则DVE将默认启动;
-vcd <filename>:将输出VCD文件名设置为指定文件。默认文件名为verilog.dump。Verilog源代码中的$dumpfile系统任务将覆盖此选项;
-verdi:使用verdi图形界面;
+vcdfile+<filename>:指定想要用于后期处理的VCD文件;
-vpd_file <filename>:在运行时,定义VCS写入的VPD文件的替代名称,而不是缺省名称vcdplus.vpd;
+define+VCS:定义全局的VCS,编译器在编译时如果源文件有类似`ifdef VCS等字样,那么会执行定义之后的代码。
+vcs+vcdpluson:编译选项,加入后会使能产生vpd文件,默认文件名vcdplus.vpd
问题:'/opt/synopsys/vcs/vcs-mx/O-2018.09-SP2/linux/bin/vcs1' for a machine of this type 'linux'. Please check whether 'VCS_HOME' is incorrect
[verifier@localhost adder32]$ make compilation
vcs -sverilog adder32.v adder32_tb_rand.v -debug_all -l comp.log
Error-[VCS_COM_UNE] Cannot find VCS compiler
VCS compiler not found. Environment variable VCS_HOME
(/opt/synopsys/vcs/vcs-mx/O-2018.09-SP2/linux) is selecting a directory in
which there isn't a compiler
‘/opt/synopsys/vcs/vcs-mx/O-2018.09-SP2/linux/bin/vcs1’ for a machine of
this type ‘linux’.
Please check whether ‘VCS_HOME’ is incorrect; if not, see below.
Perhaps vcs hasn’t been installed for machine of type “linux”.
Or the installation has been damaged.
To verify whether vcsO-2018.09 supports machine of type “Linux 3.10.0-1160.el7.x86_64”,
please look at ReleaseNotes for more details .
We determine the machine type from uname; maybe uname is incorrect.
You can fix installation problems by reinstalling from CDROM
or downloading it from the Synopsys ftp server.
For assistance, please contact vcs technical support
at vcs_support@synopsys.com or call 1-800-VERILOG
make: *** [compilation] Error 1
解决方法:出现这样的问题是因为装的VCS版本是64 bit的,所以要加个-full64
。