数字asic流程实验(EX)VCS+Verdi前仿真&后仿真
数字asic流程实验(EX)VCS+Verdi前仿真&后仿真
1. 前言
写数字asic流程实验系列博客已经过去一年多了,现在也算结束了纯小白的状态,稍微有了一些数字前端开发经验。在老的系列教程里面用的前仿后仿工具还是modelsim,实际上业界主流工具还是功能更强大的VCS和Verdi。两个也都是synopsys家的工具,VCS是编译器,Verdi是波形查看工具。为什么主流会是这两个工具我就不去复读了,网上有很多解释,而且确实功能强大,谁用过谁知道。我自己切换到VCS+Verdi上做完两次数字设计了,这里share一下一些开发经验。
2. 前仿真
还是使用cic_filter的那个case。先来一个小插曲,那份代码最后梳状器的部分的逻辑有点问题,存在竞争冒险,后仿真出来的波形有毛刺就是因为这个原因。如果直接用原来的代码的话虽然modelsim里面能把波形仿出来,VCS里面却是不行的,所以我插了一级寄存器来处理,修改后的代码为:
module cic_filter(
input clk,
input rst_n,
input in,
output [18:0] out
);
reg [18:0]out_reg;
wire clk_div;
reg [18:0]sum1,sum2,sum3;
wire [18:0]sum1_nxt,sum2_nxt,sum3_nxt;
assign sum1_nxt = sum1 + in;
assign sum2_nxt = sum2 + sum1;
assign sum3_nxt = sum3 + sum2;
always @(posedge clk or negedge rst_n) begin
if (rst_n == 0) begin
sum1 <= 19'b0;
sum2 <= 19'b0;
sum3 <= 19'b0;
end
else begin
sum1 <= sum1_nxt;
sum2 <= sum2_nxt;
sum3 <= sum3_nxt;
end
end
divider div(
.clk(clk),
.rst_n(rst_n),
.clk_div(clk_div)
);
reg [18:0]sub1,sub2,sub3;
reg [18:0]sub1_nxt,sub2_nxt,sub3_nxt;
always @(posedge clk_div or negedge rst_n) begin
if (rst_n == 0) begin
sub1_nxt <= 19'b0;
sub2_nxt <= 19'b0;
sub3_nxt <= 19'b0;
end
else begin
sub1_nxt <= sum3_nxt - sub1;
sub2_nxt <= sub1_nxt - sub2;
sub3_nxt <= sub2_nxt - sub3;
end
end
always @(posedge clk_div or negedge rst_n) begin
if (rst_n == 0) begin
sub1 <= 19'b0;
sub2 <= 19'b0;
sub3 <= 19'b0;
end
else begin
sub1 <= sum3_nxt;
sub2 <= sub1_nxt;
sub3 <= sub2_nxt;
end
end
always @(posedge clk_div or negedge rst_n) begin
if (rst_n == 0) begin
out_reg <= 0;
end
else begin
out_reg <= sub3_nxt;
end
end
assign out = out_reg;
endmodule
VCS分成编译(compile)和仿真(simulation)两步,编译步骤会把verilog代码编译到可执行文件,仿真步骤会执行编译步骤输出的可执行文件。由于VCS的调用命令相对复杂,这里写了一个makefile的脚本:
.PHONY:vcs_com vcs_sim verdi
OUTPUT = cic_filter
TIMESCALE = 1ns/1ns
#start vcs compile
vcs_com:
cd ../vcs && vcs -full64 +v2k -debug_pp -timescale=${TIMESCALE} -cpp g++ -cc gcc -LDFLAGS -no-pie -LDFLAGS -Wl,--no-as-needed -CFLAGS -fPIE -fsdb -f file_list.f -o ${OUTPUT} -l compile.log
#start vcs sim
vcs_sim:
cd ../vcs && ./${OUTPUT} -l sim.log
#start verdi
verdi:
cd ../verdi && verdi -f ../vcs/file_list.f -ssf ../vcs/tb_${OUTPUT}.fsdb
脚本里OUTPUT和TIMESCALE两个变量可以根据自己的情况来修改。为了简化输入,这里读取文件使用了file_list.f这个文件来标记所有设计文件的路径:
//Macro define
+define+FSDB
// Source
../src/cic_filter.v
../src/divider64.v
// Netlist
//../icc/outputs/cic_filter_post_layout.v
// Library
//../lib/verilog/smic18.v
// Testbench
../tb/tb_cic_filter.v
在前仿真时保留Source和Testbench下的内容即可,Netlist和Library下的内容无关,直接注释掉。
testbench也针对VCS和Verdi的工具需求做了一下修改,主要是Verdi要吃fsdb文件(记录了信号波形),所以要用Dump命令把信号抽取出来。最后还有一段吃sdf文件来跑后仿真的代码,在前仿真时可以把post_sim的宏定义给注释掉,另外因为VCS仿真的结束需要在testbench里面控制,在initial块中记得加入$finish命令,否则到了vcs_sim那一步会一直卡住:
`define period 78.125
module testbench;
// input
reg clk,rst_n,in;
// output
wire [18:0]out;
// 设置时钟周期为156.25ns
always #`period clk <= ~clk;
// 初始化
initial begin
rst_n <= 1'b0;
clk <= 1'b0;
#500;
rst_n <= 1'b1;
#(10*12800*`period);
$finish;
end
integer i;
// 定义存储器mem
reg mem[0:3000000];
// 将1k1000mv.txt文件读入mem
initial $readmemb("../src/1k1000mv.txt",mem);
// 将mem中数据次序输出到in
always @(posedge clk or negedge rst_n) begin
if(rst_n == 0) begin
i = 0;
in <= 0;
end
else begin
in <= mem[i];
i = i + 1;
end
end
// 调用cic滤波器
cic_filter cic(
.clk(clk),
.rst_n(rst_n),
.in(in),
.out(out)
);
`ifdef FSDB
initial begin
$fsdbDumpfile("tb_cic_filter.fsdb");
$fsdbDumpvars;
$fsdbDumpMDA();
end
`endif
//`define post_sim
`ifdef post_sim
// sdf
initial begin
$sdf_annotate("../icc/outputs/cic_filter_post_layout.sdf", cic);
end
`endif
endmodule
我的路径处理上,将makefile放到了新创建的和src以及tb(testbench专门摆到这个里面来)平级的prj目录里,再平级创建一个vcs文件夹和一个verdi文件夹分别作为vcs和verdi的执行目录。运行整个脚本需要到达prj目录下,执行三步shell命令
make vcs_com
make vcs_sim
make verdi
执行每步命令后都可以看一下工具的输出,如果有报错就处理一下然后再重复步骤即可。Verdi内部可以读取代码,并直接选中要观察的变量,右键Add to waveform加到波形监视窗口。也可以在Instance中选中要观察的Instance,右键Add to waveform将其所有端口信号加到波形监视窗口。
至此前仿真结束。
3. 后仿真
后仿真的步骤很简单,首先修改一下file_list.f,改成吃网表和单元库:
//Macro define
+define+FSDB
// Source
//../src/cic_filter.v
//../src/divider64.v
// Netlist
../icc/outputs/cic_filter_post_layout.v
// Library
../lib/verilog/smic18.v
// Testbench
../tb/tb_cic_filter.v
其次取消testbench中对post_sim宏定义的注释,使得仿真时能够吃到sdf文件来反标延时信息。然后还是那三步shell命令即可:
`define period 78.125
module testbench;
// input
reg clk,rst_n,in;
// output
wire [18:0]out;
// 设置时钟周期为156.25ns
always #`period clk <= ~clk;
// 初始化
initial begin
rst_n <= 1'b0;
clk <= 1'b0;
#500;
rst_n <= 1'b1;
#(10*12800*`period);
$finish;
end
integer i;
// 定义存储器mem
reg mem[0:3000000];
// 将1k1000mv.txt文件读入mem
initial $readmemb("../src/1k1000mv.txt",mem);
// 将mem中数据次序输出到in
always @(posedge clk or negedge rst_n) begin
if(rst_n == 0) begin
i = 0;
in <= 0;
end
else begin
in <= mem[i];
i = i + 1;
end
end
// 调用cic滤波器
cic_filter cic(
.clk(clk),
.rst_n(rst_n),
.in(in),
.out(out)
);
`ifdef FSDB
initial begin
$fsdbDumpfile("tb_cic_filter.fsdb");
$fsdbDumpvars;
$fsdbDumpMDA();
end
`endif
`define post_sim
`ifdef post_sim
// sdf
initial begin
$sdf_annotate("../icc/outputs/cic_filter_post_layout.sdf", cic);
end
`endif
endmodule
实验文件我就不再打包上传了,所需的代码/脚本上面都已经有了。