1. VCS 介绍
VCS是编译型 Verilog 模拟器,它完全支持 OVI 标准的 Verilog HDL 语言、PLI 和 SDF。VCS 具有行业中较高的模拟性能,其出色的内存管理能力足以支持千万门级的 ASIC 设计,而其模拟精度也完全满足深亚微米 ASIC Sign-Off 的要求。
VCS 对文件的处理主要分为以下几个部分:
原始的 .v 文件输入。
转换成 .c 文件。
编译成可执行的二进制文件。
最后生成 simv,即可查看仿真的结果。
VCS 有三步法和两步法,三步法用于仿真混合语言,比如 Verilog 和 VHDL 的混合,而两步法用于仿真单种语言,一般都只仿真 Verilog 语言,因此这里只介绍两步法。
两步法分为:
Compilation 编译。编译是仿真设计的第一步。在这个阶段,VCS 构建实例层次结构并生成一个二进制可执行文件 simv。这个二进制可执行文件稍后将用于仿真。
Simulation 仿真。在编译过程中,VCS生成一个二进制可执行文件simv。可以使用 simv 来运行仿真。
2. 常用的编译指令
编译命令的格式:vcs sourcefile [compile_time_option] (编译选项用来控制编译过程),可以另外加一些命令,比如:debug_all(加入单步调试功能)、-sverilog(提供对systemverilog的支持)等
常见的编译命令如下:
-v lib_file:lib_file 是 Verilog 文件,包含了引用的 module 的定义,可以是绝对路径,也可以是相对路径。
-y lib_dir:lib_dir 是参考库的目录,vcs 从该目录下寻找包含引用的 module 的 Verilog 文件,这些文件的文件名必须和引用的 module 的名一样。
-full64:vcs 以 64 位模式编译,生成 64 位的 simv,如果不加这条命令则无法生成 simv。
-R:编译后立即进行仿真。
+v2k:支持 Verilog 2001 标准。
-sverilog:提供对 Systemverilog 的支持。
-l log_file:用于将编译产生的信息放在 log 文件内。
-debug_all:用于产生debug所需的文件。
-Mupdate:源文件有修改时,只重新编译有改动的.v文件,节约编译时间。
-timescale=1ns/1ns 设置仿真精度,可以根据实际需要进行更改。
-kdb:-kdb 选项支持输出 kdb 格式的数据,用于与 Verdi 在交互模式交换数据,而 kdb 格式属于 "Limited Customer Availability" 特性,必须通过 -lca 选项开启。
3. Verdi 介绍
Verdi 和 VCS 一样也是 Synopsys 公司的软件,它是一款强大的仿真波形查看器,通过查看波形进行 debug 会更高效,配合着 VCS 共同进行仿真验证是众多公司的选择,因此学习 VCS 联合 Verdi 进行仿真很有必要。
在 testbench 中添加以下系统函数,用于生成 .fsdb 文件,fsdb文件是 Verdi 可识别的波形文件。
initial begin
$fsdbDumpfile("*.fsdb");
$fsdbDumpvars(0);
end
以下为常见的fsdb波形文件的系统函数及其含义。
如果要Dump FSDB波形,将以下语句选择性加在TB中。
initial begin
$fsdbDumpfile(“dump.fsdb”) ; //fsdbDumpfile - 指定FSDB文件名
$fsdbDumpvars(level,start_module) ; //要记录的信号,level=0表示记录所有
$dumpvars(2, top. u1); // Dump实例top. u1及其下一层的信号
$fsdbDumpMDA(); //fsdb dump波形时会记录二维数组2D array signal的值,
便于在verdi中debug查memory内部信号。
$fsdbDumpSingle; //Dump指定的信号
$fsdbDumpvariable; //Dump指定的VHDL变量
$fsdbSwitchDumpFile(“<newFSDBname>”); //将dumping切换到另一个FSDB文件
$fsdbAutoSwitchDumpfile(<filesize>, “<FSDBname>”,< numberoffile>);
//限制文件大小并在数据量过大时自动创建新的FSDB文件
$fsdbDumpMem(<regname>, [<startaddr>, [<size>]]);
$fsdbDumpoff; //停止记录
$fsdbDumpon; //重新开始记录
$fsdbDumplimit(); //限制VCD文件的大小(以字节为单位)
$fsdbDumpall; //记录所有指定的信号值
end
4. makefile 书写
如果使用终端手敲命令的方法进行 VCS 和 Verdi 的仿真,会显得过于麻烦,不仅费时间而且容易出错,因此可以通过 makefile 脚本的形式对整个过程的命令自动化,在对不同的文件进行仿真时,只需要修改部分内容即可重复使用,非常便捷。
以下为 Makefile 的脚本,可以使用 “make all” 命令实现从 VCS 编译到 Verdi 查看波形等一系列操作的自动化。
PRJ = /home/ICer/Project/prj
FILENAME = $(PRJ)/*.v
OUTPUT = sync_fifo
#仿真并打开vcs verdi界面
all:clean elab run verdi
elab:
vcs -full64 -LDFLAGS -Wl,-no-as-needed -debug_acc+all -timescale=1ns/1ns -fsdb -sverilog -l comp.log ${FILENAME}
run:
./simv -l ${OUTPUT}.log ${FILENAME}
rung:
./simv -gui -l run.log ${FILENAME}
verdi:
verdi ${FILENAME} -ssf ./*.fsdb &
clean:
rm -rf ./csrc *.daidir *.log simv* *.key *.vpd ./DVEfiles verdiLog novas*
5. 文件准备
现在用一个实际例子,用来演示 VCS 联合 Verdi 进行仿真,这里以一个简单的同步 FIFO 为例,以下为同步 FIFO 的 Verilog 实现。
`timescale1ns/1ps
//
// Company:
// Engineer: Linest-5
// Create Date: 2022/05/07
// Design Name:
// Module Name: Sync_FIFO
// Project Name:
// Target Devices:
// Tool Versions:
// Description: 同步FIFO的verilog实现
// Dependencies:
// Revision:
// Additional Comments:
//
//
moduleSync_FIFO#(
parameter DATA_WIDTH =8, //数据位宽
parameter FIFO_DEPTH =8, //FIFO的深度
parameter ALMOST_FULL_DEPTH =7, //将满的深度
parameter ALMOST_EMPTY_DEPTH =1, //将空的深度
parameter ADDR_WIDTH =3, //地址位宽
parameter READ_DATA_MODE =0 //读取的模式,0为组合逻辑。1为时序逻辑
)(
input clk,
input rst_n,
input wr_en,
input rd_en,
input [DATA_WIDTH-1:0] wr_data,
output reg[DATA_WIDTH-1:0] rd_data,
output almost_full,
output almost_empty,
output full,
output empty,
output reg overflow, //上溢出信号
output reg underflow //下溢出信号
);
reg [ADDR_WIDTH-1:0] wr_ptr; //写指针
reg [ADDR_WIDTH-1:0] rd_ptr; //读指针
reg [ADDR_WIDTH:0] fifo_cnt; //使用的FIFO空间计数
reg [DATA_WIDTH-1:0] buf_mem[FIFO_DEPTH-1:0]; //创建数组作存储
//对FIFO空间使用作计数
always @(posedgeclkornegedgerst_n)begin
if(!rst_n)begin
fifo_cnt <='d0;
end
elseif(wr_en&&~full&&rd_en&&~empty)begin
fifo_cnt <=fifo_cnt;
end
elseif(wr_en&&~full)begin
fifo_cnt <=fifo_cnt+'d1;
end
elseif(rd_en&&~empty)begin
fifo_cnt <=fifo_cnt-'d1;
end
elsebegin
fifo_cnt <=fifo_cnt;
end
end
//写指针
always @(posedgeclkornegedgerst_n)begin
if(!rst_n)begin
wr_ptr <='d0;
end
elseif(wr_en&&~full)begin
wr_ptr <=wr_ptr+'d1;
end
elseif(wr_ptr==FIFO_DEPTH-1)begin
wr_ptr <='d0;
end
elsebegin
wr_ptr <=wr_ptr;
end
end
//读指针
always @(posedgeclkornegedgerst_n)begin
if(!rst_n)begin
rd_ptr <='d0;
end
elseif(rd_ptr==FIFO_DEPTH-1)begin
rd_ptr <='d0;
end
elseif(rd_en&&~empty)begin
rd_ptr <=rd_ptr+'d1;
end
elsebegin
rd_ptr <=rd_ptr;
end
end
//数据写入
always @(posedgeclkornegedgerst_n)begin
if(!rst_n)begin
buf_mem[wr_ptr] <='d0;
end
elseif(wr_en&&~full)begin
buf_mem[wr_ptr] <=wr_data;
end
elsebegin
buf_mem[wr_ptr] <=buf_mem[wr_ptr];
end
end
//根据模式的选择将数据读出,模式0为组合逻辑输出,模式1为时序逻辑输出
generate
if(READ_DATA_MODE=='d0)begin
always @(*)
rd_data=buf_mem[rd_ptr]; //组合逻辑输出
end
elsebegin
always @(posedgeclkornegedgerst_n)begin
if(!rst_n)begin
rd_data <='d0;
end
elseif(rd_en&&~empty)begin
rd_data <=buf_mem[rd_ptr]; //时序逻辑输出
end
elsebegin
rd_data <=rd_data;
end
end
end
endgenerate
//上溢出信号
always @(posedgeclkornegedgerst_n)begin
if(!rst_n)begin
overflow <='d0;
end
elseif(wr_en&&full)begin
overflow <='d1;
end
end
//下溢出信号
always @(posedgeclkornegedgerst_n)begin
if(!rst_n)begin
underflow <='d0;
end
elseif(rd_en&&empty)begin
underflow <='d1;
end
end
//定义将满、将空、满、空信号
assignalmost_full=(fifo_cnt >=ALMOST_FULL_DEPTH);
assignalmost_empty=(fifo_cnt <=ALMOST_EMPTY_DEPTH);
assignfull=(fifo_cnt==FIFO_DEPTH);
assignempty=(fifo_cnt=='d0);
endmodule
此外,还需要准备其对应的 testbench,如下所示。
//
// Company:
// Engineer: Linest-5
// Create Date: 2022/05/07
// Design Name:
// Module Name: tb_Sync_FIFO
// Project Name:
// Target Devices:
// Tool Versions:
// Description: 同步FIFO的testbench文件
// Dependencies:
// Revision:
// Additional Comments:
//
//
`timescale1ns/1ns
moduletb_Sync_FIFO();
parameter DATA_WIDTH=8;
parameter FIFO_DEPTH=8;
parameter ALMOST_FULL_DEPTH=7;
parameterALMOST_EMPTY_DEPTH=1;
parameter ADDR_WIDTH=3;
parameter READ_DATA_MODE=1;
reg clk;
reg rst_n;
reg wr_en;
reg rd_en;
reg [DATA_WIDTH-1:0] wr_data;
wire[DATA_WIDTH-1:0] rd_data;
wire almost_full;
wire almost_empty;
wire full;
wire empty;
wire overflow;
wire underflow;
initialbegin
clk=0;
rst_n=0;
#50
rst_n=1;
end
always#10clk=~clk;
initialbegin
wr_en=0;
rd_en=0;
wr_data=0;
end
initialbegin
#100
send_wr;
end
initialbegin
#400
send_rd;
#1000;
$finish;
end
//创建写任务
integeri;
tasksend_wr;
begin
for(i=0;i<8;i=i+1)begin
@ (posedgeclk)begin
wr_en <='d1;
wr_data <=i+1;
end
end
@ (posedgeclk)begin
wr_en <='d0;
wr_data <='d0;
end
repeat(10)@ (posedgeclk);
end
endtask
//创建读任务
tasksend_rd;
begin
for(i=0;i<8;i=i+1)begin
@ (posedgeclk)begin
rd_en <='d1;
end
end
@ (posedgeclk)begin
rd_en <='d0;
end
end
endtask
Sync_FIFO#(
.DATA_WIDTH (DATA_WIDTH),
.FIFO_DEPTH (FIFO_DEPTH),
.ALMOST_FULL_DEPTH (ALMOST_FULL_DEPTH),
.ALMOST_EMPTY_DEPTH(ALMOST_EMPTY_DEPTH),
.ADDR_WIDTH (ADDR_WIDTH),
.READ_DATA_MODE (READ_DATA_MODE)
)inst_Sync_FIFO(
.clk (clk),
.rst_n (rst_n),
.wr_en (wr_en),
.rd_en (rd_en),
.wr_data (wr_data),
.rd_data (rd_data),
.almost_full (almost_full),
.almost_empty (almost_empty),
.full (full),
.empty (empty),
.overflow (overflow),
.underflow (underflow)
);
initialbegin
$fsdbDumpfile("tb_Sync_FIFO.fsdb");
$fsdbDumpvars(0);
$fsdbDumpMDA();
end
endmodule
有几点需要注意:
添加仿真精度和仿真单位。
`timescale 1ns/1ns
添加停止仿真系统函数。如果不加的话仿真会一直仿真不停止,导致无法观察仿真波形。
$finish
添加仿真需要的系统函数,$fsdbDumpfile 表示生成的 fsdb 文件名,用于 Verdi 查看波形;$fsdbDumpvars 表示需要查看的信号,0 表示所有信号都将纳入观察;$fsdbDumpMDA 表示 fsdb dump 波形时会记录二维数组 2D array signal 的值,便于在 Verdi 中 debug 查 memory 内部信号。
initial begin
$fsdbDumpfile("tb_Sync_FIFO.fsdb");
$fsdbDumpvars(0);
$fsdbDumpMDA();
end
6. 实操练习
接下来直接进行最后的实操仿真,打开终端进入到工程目录下。
ls 查看当前目录所含的文件,可以看到包含有同步 FIFO 的 .v 文件以及对应的 TB 文件,另外还有实现自动化的 makefile 脚本文件。
根据前面 makefile 的说明,直接使用 make all 命令即可实现自动仿真。
随后会显示一些信息,如顶层文件,仿真精度和单位等。
同时也会打印 VCS 仿真报告。
随后会自动弹出 Verdi 的 GUI 界面。
第一部分为菜单栏部分,这里可以进行查看电路图、加载源文件等操作。
第二部分为当前加载的文件层次结构,可以看到顶层文件为 sync_fifo,因为当前只有一个文件,所以毫无疑问顶层文件只能是它,如果包含多个设计文件,含有顶层及子模块,这里就会清晰的显示出来。
第三部分为设计文件的内容显示,同时在后续会介绍可以直接通过这里查看相应信号的波形。
第四部分为波形显示区,这里就是 Verdi 最重要的区域,我们就是通过这里查看信号波形进行 debug 的。
但是这是可以看到在波形显示区并没有波形出现,这时需要我们手动选择添加观察信号,鼠标选择要观察的模块信号,并按下快捷键“CTRL + 4”,即可将选中模块所包含的信号加入到波形观察区中。
此外在前面埋下伏笔说了可以在代码观察区添加观察信号,在 debug 时,有时需要观察非端口信号,比如在内部定义的 reg/wire 信号,这时就可以在代码观察区添加,比如我要观察在内部定义的写使能信号 wr_en,鼠标选中此信号,按下快捷键“CTRL + w”即可将信号添加至观察区进行 debug。
可以看到当前的波形太大,不利于我们观察,可以按 F 键,便会自动将波形整形成最适合当前的比例。
另外可以通过上方的按钮将波形进行放大和缩小,也可以通过按住 CTRL 键在滑动鼠标滚轮将波形进行放大和缩小。
当添加的模块信号太多时,会显得很杂乱,可以将其分组分类进行观察,只需要将同组的信号选中向下拖拽即可。下图将信号分为五组。
过多的信号都是同一种颜色容易看花,可以将不同类的信号设置成不同的颜色,便于区分,只需选中信号,按 T 键即可切换颜色。
另外选中信号右击鼠标会出现一列菜单栏,在这里可以更改信号的进制、形式(数字或模拟)、重命名等操作,这里就不一一操作了,如果之前使用过 Vivado、Modelsim等工具,相信这些操作还是很容易上手的。
7. 总结
这篇文章只介绍,VCS 和 Verdi 最基本的操作,更多高阶的操作可以查看其官方手册,也可以在平时的练习中不断加强对软件操作的熟练度,工具学起来还是比较容易的,难的是如何对设计进行快速的仿真并找出其中的 bug,这需要在未来长期学习训练与总结。