数字验证——VCS使用
一,基础介绍#
VCS用来编译仿真verilog/systemverilog,先将HDL源文件转化为C文件,在linux下编译和链接生成可执行文件,运行可执行文件即可得到仿真结果。
- 编译命令格式 :vcs sourcefile [compile_time_option] (编译选项用来控制编译过程)
- 执行仿真命令格式:./simv [run_time_option] (simv为可执行文件)
常用编译选项(更详细参考 VCS 编译选项 - 知乎 (zhihu.com)):
- +neg_tchk:使能时序检查中的负延时
- +notimingcheck:不进行时序检查
- +sdf_nocheck_celltype:SDF反标时不检查SDF文件中的celltype的一致性
- +v2k:支持Verilog-2001标准
- -f <filename>:指定文件列表的文件名,文件中可包括源代码文件的路径和名称,也可以包括编译选项参数
- -R:在编译之后立即执行产生的可执行文件
- -v <filename>:指定verilog库文件
- -l <filename>:指定记录VCS编译和运行信息的log文件名
- -o <name>:指定编译生成的可执行文件的名称,默认是simv
- -gui:仿真开始后启动DVE图形界面
- -verdi:使用verdi图形界面
- -debug_all:用于产生debug所需的文件
在复杂设计的VCS使用中,经常将需要编译的源文件写进一个verilog.f文件,然后用-f选项指定文件进行编译。
同时,也可以把编译过程写成makefile脚本,提高程序的复用性,提高效率:
#一种参考运行vcs的makefile脚本
.PHONY:com sim clean OUTPUT = adder_top VCS = vcs -sverilog +v2k -timescale=1ns/1ns \ -debug_all \ -o ${OUTPUT} \ -l compile.log \ SIM = ./${OUTPUT} -l run.log com: ${VCS} -f verilog_file.f sim: ${SIM} clean: rm -rf ./csrc *.daidir *.log simv* *.key
执行make com、make sim、make clean即可执行编译、仿真、清除指令。
二,VCS testbench搭建#
简单的testbench常常是initial给波形,always给时钟,然后使能信号看输出,但这种方法效率很低,目前通用的是uvm(Universal Verification Method,通用验证方法学)。对于个人开发者或者学生而言,需要快速搭建testbench用于vcs对自己的设计进行验证。
1,verilog testbench基本语法#
完整的testbench测试文件结构为:
`timescale 仿真单位/仿真精度 module Test_bench();//通常无输入无输出 信号或变量声明定义 逻辑设计中输入对应 reg 型 逻辑设计中输出对应 wire 型 使用 initial 或 always 语句产生激励 例化待测试模块 监控和比较输出响应 endmodule
对应的testbench典型架构如下:
(1)声明一个没有输入输出的模块作为顶层
一般命名方法是在被测模块名后加上_tb
module <module_name> (); //在这里写testbench endmodule
(2)声明仿真单位和精度
<unit_time> 指定时间的单位,<resolution>则指定时间精度。
<resolution> 表示精度,可以使用小数来指定 verilog 代码中的延时。例如,如果设计人员想要 10.5ns 的延迟,就可以简单地写为 #10.5。因此,编译指令中的 <resolution> 决定了可以实现最小时间的步长(即精度)。
`timescale <unit_time> / <resolution>
(3)信号或者变量定义
parameter X=20; //把常量定义为参数的形式方便全局修改 //reg define reg A; reg B; //wire define wire [3:0] C; //通常initial或者always语句块中的变量定义为reg类型,在assign或者用于例化模块名的信号定义成wire类型
(4)例化被测模块
example_design dut ( .clock (clk), .reset (reset), .a (in_a), .b (in_b), .q (out_q) ); //其中clk、reset、in_a等信号在(3)中需要定义
(5)生成时钟和复位信号
可以在initial块中为时钟和复位信号编写代码,然后使用延时运算符来实现信号状态的变化。以使用 forever 关键字在仿真期间持续运行时钟信号。使用此语法将每 1 ns 进行一次反转,从而实现500MHz 的时钟频率----选择此频率纯粹是为了实现快速仿真。
//生成时钟信号 initial begin clk = 1'b0; forever #1 clk = ~clk; end //生成复位信号 initial begin reset = 1'b1; #10 reset = 1'b0; end
下面为其他常用的时钟和复位设计:
/*---------------------------------------------------------------- 时钟激励产生方法一: 50%占空比时钟 ----------------------------------------------------------------*/ parameter ClockPeriod=10; initial begin clk_i=0; forever #(ClockPeriod/2) clk_i=~clk_i; end /*---------------------------------------------------------------- 时钟激励产生方法二: 50%占空比时钟 ----------------------------------------------------------------*/ initial begin clk_i=0; always #(ClockPeriod/2) clk_i=~clk_i; end /*---------------------------------------------------------------- 时钟激励产生方法四:产生固定数量的时钟脉冲 ----------------------------------------------------------------*/ initial begin clk_i=0; repeat(6) #(ClockPeriod/2) clk_i=~clk_i; end /*---------------------------------------------------------------- 时钟激励产生方法五:产生非占空比为 50%的时钟 ----------------------------------------------------------------*/ initial begin clk_i=0; forever begin #((ClockPeriod/2)-2) clk_i=0; #((ClockPeriod/2)+2) clk_i=1; end end
/*---------------------------------------------------------------- 复位信号产生方法一:异步复位 ----------------------------------------------------------------*/ initial begin rst_n_i=1; #100; rst_n_i=0; #100; rst_n_i=1; end /*---------------------------------------------------------------- 复位信号产生方法二:同步复位 ----------------------------------------------------------------*/ initial begin rst_n_i=1; @(negedge clk_i) rst_n_i=0; #100; //固定时间复位 repeat(10) @(negedge clk_i); //固定周期数复位 @(negedge clk_i) rst_n_i=1; end /*---------------------------------------------------------------- 复位信号产生方法三:复位任务封装 ----------------------------------------------------------------*/ task reset; input [31:0] reset_time; //复位时间可调,输入复位时间 RST_ING=0; //复位方式可调,低电平或高电平 begin rst_n=RST_ING; //复位中 #reset_time; //复位时间 rst_n_i=~RST_ING; //撤销复位,复位结束 end endtask
(6)文本输入输出
reg [a:0] data_mem [0:b]; //定义位宽为(a+1)深度为(b+1)的存储器 $readmemb/$readmemh("<读入文件名>",<存储器名>); $readmemb/$readmemh("<读入文件名>",<存储器名>,<起始地址>); $readmemb/$readmemh("<读入文件名>",<存储器名>,<起始地址>,<结束地址>); $readmemb /*------------------------------------------------------------------------*\ 读取二进制数据,读取文件内容只能包含:空白位置,注释行,二进制数 数据中不能包含位宽说明和格式说明,每个数字必须是二进制数字。 \*------------------------------------------------------------------------*/ $readmemh /*------------------------------------------------------------------------*\ 读取十六进制数据,读取文件内容只能包含:空白位置,注释行,十六进制数 数据中不能包含位宽说明和格式说明,每个数字必须是十六进制数字. \*------------------------------------------------------------------------*/ //========================================================================== //== 输出txt文件 //========================================================================== integer fp_write; //定义 initial begin begin fp_write = $fopen("output.txt"); //打开输出文件 begin $fwrite(fp_write, "\n%h", output_data); //写入数据16进制 #(`clk_period); end end $fclose(fp_write); //关闭文件,不可少 end
(7)打印信息
$monitor //仿真打印输出,打印出仿真过程中的变量,使其终端显示 /*------------------------------------------------------------------------*\ $monitor($time,,,"clk=%d reset=%d out=%d",clk,reset,out); \*------------------------------------------------------------------------*/ $display //终端打印字符串,显示仿真结果等 /*------------------------------------------------------------------------*\ $display(” Simulation start ! "); $display(” At time %t,input is %b%b%b,output is %b",$time,a,b,en,z); \*------------------------------------------------------------------------*/ $time //返回 64 位整型时间 $stime //返回 32 位整型时间 $realtime //实行实时模拟时间
2,快速搭建soc验证平台#
待学习
三,用VCS对设计进行debug#
1,在testbench中使用系统函数#
这在testbench基本结构中已经有所介绍,在编写verilog模块的testbench时,可以在里面使用一些verilog的系统函数,在运行simv文件跑仿真时,进行一些控制。例如:
- $time 代表当前的仿真时间。
- $display 类似C语言的printf函数,仿真时在终端上打印一些信息,比如一些变量的值。
- $monitor 和$display类似,不同的是$display在被调用的时候打印一些信息,$monitor可以自动监测变量,当变量值发生变化时,便打印出信息。
- $stop 调用时使仿真产生一次中断。
- $finish 调用时使仿真结束。
- $readmemh $readmemb 用于存储器建模时的初始化,$readmemb以二进制数的形式写入,$readmemh以十六进制数写入。
一般而言使用$readmemb较多,下面看一个例子:
`timescale 1ns/10ps module myrom (read_data, addr, read_en_); input read_en_; input [3:0] addr; output [3:0] read_data; reg [3:0] read_data; reg [3:0] mem [0:15]; initial $readmemb ("my_rom_data", mem); //将my_rom_data文件里的数据写入mem rom中 always @( addr or read_en_) if (! read_en_) read_data = mem[addr]; endmodule
2,使用dve进行debug#
新增了一个 initial 块。表示如果在编译时,定义了 DUMP_VPD 这个宏,那么在仿真时,打开 $vcdpluson() 这个开关选项。
initial begin `ifdef DUMP_VPD $vcdpluson(); `endif end
在linux命令行运行vcs时加上 +define+DUMP_VPD,则在仿真完成后,生成了 vcdplus.vpd 这个文件。这个文件记录了仿真过程中所有信号的波形,可以使用 dve 打开。通过 dve & 命令打开 dve, "&"的用途是后台打开dve,以免终端被占用。我们可以看到 dve 打开后界面为空白。File -> Open Database,选择 vpd 文件并打开,在Hierarchy 部分,可以查看顶层模块里面的子模块,右键 -> Add to Waves 查看对应模块的波形图。或者可直接使用命令: dve -vpd vcdplus.vpd & 后台打开 dve 并加载 vpd 文件。
四,验证覆盖率#
在进行功能验证时,给设计添加激励信号,查看仿真结果,需要考虑覆盖率的问题。覆盖率分为代码覆盖率(code coverage)和功能覆盖率(function coverage)。
1.1功能覆盖率(user defined/用户自定义)#
(1)作用:用于度量DUT中哪些功能或者特性被测试用例测试到;
(2)实现:使用sv等验证语言实现覆盖率模型(coverage model)或断言(assertion),并从大量的回归测试(regression)中采集覆盖数据(sample),然后进行统计数据的合并(merge),最后做覆盖率分析(analysis);
(3)侧重点:模块验证会更关心是否所有的功能都被触发,而系统验证会更关心是否耦合了各类工作场景;
(4)涉及点:sv功能覆盖率模型的实现,需要用到covergroup和coverpoint;
1.2代码覆盖率(仿真工具生成)#
(1)分类:翻转覆盖率(toggle cov),行覆盖率(line cov),语句覆盖率(statement cov),分支覆盖率(branch cov),状态覆盖率(FSM cov);
(2)作用:通过对代码覆盖率的分析,可以较容易发现RTL中冗余的代码块,这种冗余的代码块可能来自于RTL实现逻辑的错误,或者验证计划不够完备;
(3)收集:由EDA工具在启用相应功能的选项之后自动化完成,不需要工程师开发额外的代码;
对于代码覆盖率而言,通常需要考虑以下情况:
- Line coverage :行覆盖率,检查语句是否被执行。
- Toggle coverage:检查电路的每个节点是否都有 0 -> 1 和 1 -> 0 的跳变。这种检查通常会使仿真变慢很多。
- conditional coverage:检查条件语句是否覆盖了所有的情况。 比如有时写了if 语句,没有写else语句。
- FSM coverage: 状态机覆盖率,检查状态机所有的状态是否都到达过。
- path coverage:在always语句块和initial语句块中,有时会使用 if ... else 和 case 语句,在电路结构上便会产生一系列的数据路径。检查这些路径的覆盖情况。
VCS在统计代码覆盖率的过程中,我们通常在编译和仿真命令上添加对应的开关选项,生成一个 .vdb文件记录覆盖率的情况。再使用dve打开该文件查看。
VCS有关覆盖率的常用选项如下:
- -cm <coveragetype> :打开对应类型的覆盖率,例如 -cm cond+tgl+lin+fsm+path为统计上述所有覆盖率。可根据需要增减。
- -cm_name:设置记录有覆盖率信息文件的名字。
- -cm_dir:指定生成文件的目录。
- -cm_log + filename.log:.log文件记录仿真过程中统计覆盖率的信息。用的比较少。
- -cm_nocasedef: 在统计case语句的条件覆盖率时,不考虑default条件未达到的情况。
- -cm_hier vcs_cov.cfg:通过.cfg文件(名字随便取)选择要查看覆盖率的模块/文件
“+”代表查看,“-”代表不查看。tree代表查看某个模块调用的子模块。
五,VCS后仿#
当我们把所写的RTL进行的功能仿真通过之后,便输入到 Design Compiler工具中进行逻辑综合,逻辑综合的结果便是RTL代码转化为由与、或、非等门电路和触发器组成的电路,称为门级网表(netlist)。门级网表中便包含了电路的实际信息,例如逻辑门单元的扇入扇出系数,延迟等等。因此在逻辑综合完成之后,需要对网表再进行仿真验证,防止出现意想不到的错误。
在testbench中使用sdf_annotate()系统函数将sdf文件“反标”到设计中,第一个参数指定sdf文件,第二个参数指定反标到哪一层的module上,这里我们选择顶层文件。其他参数保持默认即可。
与前仿不一样的是后仿除了网表verilog文件,还需要工艺库verilog文件和sdf延时文件。
参考资料:
如何快速搭建模块验证平台 – 两位语言学家的相遇 (kellen.wang)
(12条消息) Verilog如何编写一个基础的Testbench_verilog中testbench怎么写_孤独的单刀的博客-CSDN博客
(12条消息) TestBench基本写法与语法详解_testbench怎么写_ZHE980121的博客-CSDN博客
(12条消息) Testbench编写常用语法和必备知识_青青豌豆的博客-CSDN博客
验证覆盖率及用仿真工具收集覆盖率(code cov/func cov) - _见贤_思齐 - 博客园 (cnblogs.com)
作者:zhaohaowen
出处:https://www.cnblogs.com/hwzhao/p/17294716.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具