数字asic流程实验(EX)VCS+Verdi前仿真&后仿真

发布时间 2023-05-02 17:43:07作者: sasasatori

数字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命令即可:

实验文件我就不再打包上传了,所需的代码/脚本上面都已经有了。