写Verilog 的环境:逻辑综合、逻辑仿真

发布于:2025-07-04 ⋅ 阅读:(14) ⋅ 点赞:(0)

目录

EDA 工具

仿真有三种形式:

1、verilog 源文件(RTL coding)是 .v 文件。

2、仿真要有testbench, 也是.v 文件, 简称_tb.v

3、先compile , 再simulate

多文件仿真

Design Compiler

lab2:

vector:

扩展

VCS 安装

lab3:

counter.v

 truncate.v 截断

integers.v  整型和reg 型

#号作用:

converter.v

param_counter_top.v  

@号作用


EDA 工具

EDA,全称电子设计自动化(Electronics Design Automation),在数字、模拟和混合集成电路的各个开发阶段都发挥着重要作用。常用的EDA工具主要来自三家公司:Synopsys、Cadence和Mentor(已被Siemens收购)。以下是这些工具的详细介绍:

🌟 RTL功能仿真:VCS+Verdi套装或ModelSim是常用的功能仿真工具。VCS用于仿真,Verdi用于波形debug,在Linux下非常好用;ModelSim则兼具仿真和波形debug功能,在Windows下常用。功能仿真阶段不区分FPGA或ASIC,工具通用;这些工具也可以进行后仿,即动态时序仿真。

🌟 逻辑综合:Synopsys的Design Compiler(DC)是常用的逻辑综合工具。通常使用TCL脚本(工具命令语言),网上有丰富的相关资料。综合阶段还可以在工具内进行timing等分析,但结果不如PT准确。(RTL 转成网表)

布局布线:Synopsys的ICC/ICC2是常用的布局布线工具,功能强大,可以根据用户的floorplan自动完成布局优化和布线优化。工具还支持timing、DRC、LVS检查等功能,但不是sign-off工具。

 形式验证:Synopsys的Formality是常用的sign-off工具,主要用于静态时序分析、功耗分析、设计规则检查和版图一致性验证,确保芯片在流片前满足项目规格。

仿真有三种形式:

RTL级,门级,时序仿真。

RTL行为级仿真:这个阶段的仿真可以用来检查代码中的语法错误以及代码行为的正确性,其中不包括延时信息。 需要的文件:写好的RTL代码.v和测试代码_tb.v文件。

综合后门级功能仿真:

1、verilog 源文件(RTL coding)是 .v 文件。

SR.v 触发器。先知道了门级结构,才有coding

module SR(
output Q,Qn,
input S,R
);

    wire q,qn;  //内部连线

    assign #1 Q=q;     //将内部连线,连到Q Qn 上
    assign #1 Qn = qn;

    assign #10 q= ~(S & qn);   // 电路结构
    assign #10 qn = ~(R & q);

endmodule

XorNor 异或门

module XorNor(
output X, Y,
input A, B, C
);

wire x;

assign #1  X=x;  // wire connection
assign #10 x= A ^ B;
assign #10 Y= ~(x|C);

endmodule // XorNor

Top 层

使用top 层指明设计的顶层结构,

module Intro_Top(
    output X,Y,Z;
    input A,B,C,D
);

    wire ab, bc, q, qn;  //内部的连线

    assign #1 Z = ~qn;//反相器

    AndOr InputCom01(  //实例化,instance
    .X(ab),        //   X是本身的, X(ab) 表示X 要连接到ab上
    .Y(bc),
    .A(A),
    .B(B),
    .C(C)
);
    SR    SR01(    //这个代码的连接 是框架图延申的
    .Q(q),
    .Qn(qn),
    .S(bc),
    .R(D)
);
    XorNor   OutputCom01 (
    .X(X),
    .Y(Y),
    .A(ab),
    .B(q),
    .C(qn)
);

endmodule
2、仿真要有testbench, 也是.v 文件, 简称_tb.v

initial  不可综合的,

// `timescale  1ns / 1ps  
 
module tb_counter;      //testbench 没有port,即没有输入输出

    reg Astim,Bstim,Cstim,Dstim;   //内部变量
    wire Xwatch,Ywatch,Zwatch;


initial begin  //testcase   顺序执行的
$dumpportsall;
$dumpfile("VSC_.VCD");  //   .vcd 是标准的waveform format
$dumpvars;

#1 Astim=1'b0;      //initial和always 里的左值必须是reg 类型
#1 Bstim=1'b0;
#1 Cstim=1'b0;
#1 Dstim=1'b0;

#50 $finish; //仿真终止
end

initial begin   // 多个initial 是并发执行的
    $vcdpluson();
end

Intro_Top Topper01 (
    .X(Xwatch),    //把 X Y Z的输出,接到 Xwatch 线上,来观察
    .Y(Ywatch),  
    .Z(Zwatch),
    .A(Astim),   //A B C D 是输入信号,reg 类型的Astim 激励 用来驱动
    .B(Bstim),
    .C(Cstim),
    .D(Dstim)

);

endmodule;
3、先compile , 再simulate
  • 先运行源文件,输出.out 文件(VScode 里的方法)
  • 再ctrl + shift +p, 选择testbench,生成testbench 例子。
  • 用testbench 和 源文件,生成 vvp:         iverilog -o test_1.vvp test_1.v test_tb.v
  • vvp .\test_1.vvp  生成 .vcd 波形文件
  • 点击.vcd 添加信号,查看波形

4、老师案例:

make 调用仿真工具,(vcs 命令)

dve 调出来GUI 界面,打开波形文件

根据testbench , check 波形对不对(verdi 这个工具也可以看波形)

dc_shell ,使用.tcl  调用综合工具,输出综合网表

多文件仿真

 先编译文件:

  • iverilog -o param_counter_top.out -y ./  .\param_counter_top.v   
  • iverilog -o param_counter_top_tb.out -y ./  .\param_counter_top_tb.v

再生成 vvp(源文件+tb 文件)

        iverilog -o test_1.vvp test_1.v test_tb.v
   6 iverilog -o param_counter_top.vvp  -y ./  .\ .\param_counter_top.v .\param_counter_top_tb.v
   7 iverilog -o param_counter_top.vvp  -y ./   .\param_counter_top_tb.v

再生成vcd 文件
   9 vvp .\param_counter_top.vvp

其中tb.v 文件里要添加

    initial

    begin            

        $dumpfile("param_counter_top.vcd");        //生成的波形 vcd文件名称

        $dumpvars(0, tb);    //参与仿真的tb模块名称

    end

endmodule//tb

Design Compiler

一种由Synopsys公司开发的电子设计自动化(EDA)工具,主要用于将硬件描述语言(如Verilog或VHDL)编写的高层次电路设计转换为优化的门级网表。

主要功能: 该工具能够进行逻辑综合、设计约束设置、时序分析等操作,以满足设计的性能和面积要求。

Design Compiler (二)——DC综合与Tcl语法结构概述_Design Compiler-CSDN专栏

dc_shell: DC以命令行的格式启动:$dc_shel,需要知道一些tcl的简单语法。

.tcl 是个脚本

综合后需要查看timing、area 符不符合需要;

综合后的多个文件需要写到一个ddc 文件里,或者一个netlist

lab2:
vector:

理解:信号线有顺序,有方向。指定是reg 或者wire, [0:15]与[15:0] 方向不一样。


`timescale 1ns/100ps// `是键盘左上角的~键,不是单引号'。`'这俩不一样

module Vector;
    reg  [0:15] Mybus; //0~15
    wire [15:0] mybus; //15~0
    wire         mybit;

    reg   [47:0] My48bits;

    assign mybus = 16'heeee;// e=4'b1110;  assign 当做一个连线,把信号线连上
    assign mybit = 1'b0;

    initial
    begin
    $display("at %0t: mybus=16'b %0b", $time,mybus); //%0t 对应时间.%d

    #10 Mybus=16'h3333;
    $display("at %0t: Mybus=16'b %0h", $time,Mybus); //%h
    $display("at %0t: Mybus[0:2]=3'b %0b", $time,Mybus[0:2]);//[取部分位]
    $display("at %0t: mybus[10:8]=3'b %0b", $time,mybus[10:8]);//[取部分位]

    #10 Mybus[0:2]=mybus[10:8];
    $display("after Mybus[0:2]=mybus[10:8]");
    $display("at %0t: Mybus[0:2]=3'b %0b", $time,Mybus[0:2]);//[取部分位]
    $display("at %0t: mybus[10:8]=3'b %0b", $time,mybus[10:8]);//[取部分位]

    #10 Mybus=mybus; //全匹配
    $display("after Mybus=mybus");
    $display("at %0t: Mybus=16'b %0b", $time,Mybus);
    $display("at %0t: mybus=16'b %0b", $time,mybus);

    /*以下是位扩展*/
    #10 Mybus=16'hxxxx;
    #5 My48bits= 'bz; $display("at %0t: My48bits=48'b %b", $time,My48bits);
    #5 My48bits= 'bx; $display("at %0t: My48bits=48'b %b", $time,My48bits);
    #5 My48bits= 'b0; $display("at %0t: My48bits=48'b %b", $time,My48bits);
    #5 My48bits= 'b1; $display("at %0t: My48bits=48'b %b", $time,My48bits);

    #5 My48bits= 1'bz; $display("at %0t: My48bits=48'b %b", $time,My48bits);
    #5 My48bits= 1'bx; $display("at %0t: My48bits=48'b %b", $time,My48bits);
    #5 My48bits= 1'b0; $display("at %0t: My48bits=48'b %b", $time,My48bits);
    #5 My48bits= 1'b1; $display("at %0t: My48bits=48'b %b", $time,My48bits);
    #5 $finish;

    end
    endmodule

输出结果:

[Running] Vector.v
at 0: [15:0]mybus=16'b 1110111011101110
at 10: [0:15]Mybus=16'b 3333
at 10: Mybus[0:2]=3'b 1
at 10: mybus[10:8]=3'b 110
after Mybus[0:2]=mybus[10:8]
at 20: Mybus[0:2]=3'b 110
at 20: mybus[10:8]=3'b 110
after Mybus=mybus
at 30: Mybus=16'b 1110111011101110
at 30: mybus=16'b 1110111011101110
at 45: My48bits=48'b zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
at 50: My48bits=48'b xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
at 55: My48bits=48'b 000000000000000000000000000000000000000000000000
at 60: My48bits=48'b 000000000000000000000000000000000000000000000001
at 65: My48bits=48'b 00000000000000000000000000000000000000000000000z
at 70: My48bits=48'b 00000000000000000000000000000000000000000000000x
at 75: My48bits=48'b 000000000000000000000000000000000000000000000000
at 80: My48bits=48'b 000000000000000000000000000000000000000000000001
Vector.v:43: $finish called at 85 (1ns)
[Done] exit with code=0 in 0.147 seconds

  

扩展

VCS 安装

VCS(Synopsys Verification Compiler Simulator)的安装主要包括下载安装包、配置环境及破解License等步骤,具体方法因操作系统和版本而异。

VCS安装步骤概述

  1. 下载安装包‌:

    • 访问Synopsys官网或通过授权渠道获取VCS安装镜像(如2023.12版本)。‌‌1
    • 若使用虚拟机(如VMware),需提前准备Linux系统环境。‌‌
  2. 安装依赖项‌:

    • 在Linux系统中安装必要工具(如cshlsb-core)以支持VCS运行。‌‌2
    • 通过Synopsys Installer工具完成VCS和SCL(License管理工具)的安装。‌‌2
  3. 配置License‌:

    • 使用scl_keygen生成Synopsys.dat文件,并确保其路径正确。‌‌2
    • 验证License有效性,若失败需检查网卡名称或HostID匹配问题。‌‌2
  4. 环境变量设置‌:

    • 编辑~/.bashrc文件,添加VCS和SCL的路径,并通过source命令生效。‌‌2

注意事项

  • 空间需求‌:VCS安装包较大(约40GB),建议分配充足磁盘空间。‌‌
  • 版本兼容性‌:不同版本的VCS可能对操作系统或依赖项有特定要求,需参考官方文档。‌‌1
  • 虚拟机安装‌:若在虚拟机中运行,需确保共享文件夹配置正确以传输安装文件。‌‌

lab3:
counter.v
// this is a unit:up-count, 异步复位,clock gating,  时序电路里用非阻塞赋值

module counter #(parameter WIDTH=4)(
output [WIDTH-1:0] count,
input clk, count_enable,count_reset
);

reg[WIDTH-1:0] count_reg;
wire           clock_wire;

assign #1 count=count_reg;               //输出要寄存,要连到 reg 上  

assign clock_wire = (count_enable==1'b1)? clk:1'b0;  //连接clock 线

always @(posedge clock_wire, posedge count_reset)   //异步复位:在敏感列表里加 reset 信号
    begin
        if(count_reset == 1'b1)
        count_reg<='b0;                //不要1'b0, 用'b0 可以忽略位宽
        else
            count_reg <= count_reg + 1'b1;   // 加1 

    end

    endmodule //counter
 truncate.v 截断

    integer Int=0; //一个整型 = 32bit 。integer 有符号,分正负。 reg 无符号,负数是2进制补码。

    reg[007:0] Byte='b0;

    reg[031:0] Word='b0; // 一个word = 32bit,half word = 16bit

    reg[063:0] Long='b0;  //long = 64bit

    reg[127:0] Dlong='b0;   //double word =128bit

#20 $finish 要加 #20 时间延迟,如果不加,因为是并发执行,一开始就会finish, 就无法运行其它#20 的代码段

//filename:truncate.v
`timescale 1ns/1ns
module truncate;
    integer Int=0; //一个整型 = 32bit 。integer 有符号,分正负。 
    reg[007:0] Byte='b0; //reg 无符号
    reg[031:0] Word='b0;// 一个word = 32bit,half word = 16bit
    reg[063:0] Long='b0;//long = 64bit
    reg[127:0] Dlong='b0; //double word =128bit

    initial
    begin
        #20  //%h,没有数值的位数,会补0
        $display("the value is 6'd1");
        Int=6'd1;$display("at %t: Int=32'h%h",$time,Int);
        Byte=6'd1;$display("at %t: Byte=8'h%h",$time,Byte);
        Word=6'd1;$display("at %t: Word=32'h%h",$time,Word);
        Long=6'd1;$display("at %t: Long=64'h%h",$time,Long);
        Dlong=6'd1;$display("at %t: Dlong=128'h%h",$time,Dlong);

        #20  //负号
        $display("the value is -6'd1");
        Int=-6'd1;$display("at %t: Int=32'h%h",$time,Int);
        Int=-6'd1;$display("at %t: Int=32'd%d",$time,Int);
        Int=-6'd1;$display("at %t: Int=32'b%b",$time,Int);
        Byte=-6'd1;$display("at %t: Byte=8'h%h",$time,Byte);//无符号应当显示为二进制补码
        Word=-6'd1;$display("at %t: Word=32'h%h",$time,Word);
        Long=-6'd1;$display("at %t: Long=64'h%h",$time,Long);
        Dlong=-6'd1;$display("at %t: Dlong=128'h%h",$time,Dlong); 
    
    #20 $finish;
    end

endmodule

输出结果:

16进制输出,4个bit 为一个位置,8bit=2个位置

在16进制、2进制中,负数都用“补码”表示,十进制有符号,用负数表示。

因为定义的Dlong 为[127:0], 所以就算Dlong=6'd1; 打印的%h,仍然要占用128bit.  即32个位置。

[Running] truncate.v
the value is 6'd1
at                   20: Int=32'h00000001 //32bit= 4*8, 8个位置
at                   20: Byte=8'h01 //8bit=2个位置
at                   20: Word=32'h00000001
at                   20: Long=64'h0000000000000001
at                   20: Dlong=128'h00000000000000000000000000000001
the value is -6'd1
at                   40: Int=32'hffffffff
at                   40: Int=32'd         -1
at                   40: Int=32'b11111111111111111111111111111111
at                   40: Byte=8'hff
at                   40: Word=32'hffffffff
at                   40: Long=64'hffffffffffffffff
at                   40: Dlong=128'hffffffffffffffffffffffffffffffff
truncate.v:30: $finish called at 60 (1ns)

截断赋值:

代码段:
        //截断输出
        #10
        Word =32'h 7eee_777f;
        Byte=Word;$display("at %t: Byte=8'h%h",$time,Byte);//长值赋给短值,则只保留低位
        Int=Word;$display("at %t: Int=32'h%h",$time,Int);
        Long=Word;$display("at %t: Long=64'h%h",$time,Long);//短值赋给长值,不够的补0
        Dlong=Word;$display("at %t: Dlong=128'h%h",$time,Dlong);
结果:
[Running] truncate.v
at                   10: Byte=8'h7f
at                   10: Int=32'h7eee777f
at                   10: Long=64'h000000007eee777f
at                   10: Dlong=128'h0000000000000000000000007eee777f
truncate.v:39: $finish called at 30 (1ns)
integers.v  整型和reg 型

iX是整型,最终计算结果是整型。

rA rB rX 是无符号型,正数就是正数,负数就是补码。

reg 在运算时直接转为整型,输出就是整型。

整型和reg 型也可以混合运算。

#号作用:

在Verilog中,#有2种作用。

       第一种作用是指定时间延时,如#10,表示延时10个时间单位;

       第二种作用在模块定义时指定常量型参数的默认值,在模块实例化时传递常量型参数的指定值。

converter.v

 //用到数值的地方都需要加上 size 和 进制

 //output input 默认是wire 类型

// this is a design,功能是在输出的bus 上加 一组pad 线
 
 module converter #(parameter WIDTH=4,PAD_NUM=1)//
        ( output[WIDTH-1+PAD_NUM:0] out_bus,               //默认wire 类型,插入pad_num 线
            input [WIDTH-1:0]       in_bus,
            input                   enable
        );

    reg[WIDTH-1+PAD_NUM:0] bus_reg;                  //count value  ,内部变量
    reg[WIDTH-1+PAD_NUM:0] out_bus_gate;               //存储3态


    /*main code,一直更新bus_reg,always 是可综合的*/

    always @(in_bus) begin                                 //@ 表示敏感,() 里为电平信号
        if(PAD_NUM!=1'b0) begin   
        bus_reg[WIDTH-1+PAD_NUM:WIDTH]= 'd0;              //高位置为0
        bus_reg[WIDTH-1:0]=in_bus;                        //低位接到 in_bus
        end

        else                                           //有if 就要有else, 否则出现latch
            bus_reg[WIDTH-1:0]=in_bus;
    end

//使能的话,输出bus_reg , 否则为高阻态
    always @(enable,bus_reg) begin
        if(enable == 1'b1)
            #2 out_bus_gate=bus_reg; 
            else #1 out_bus_gate= 'bz;                //z用二进制,设置高阻态
    end

    assign out_bus=out_bus_gate;                      //输出的drive 连接到输出的pin脚
    endmodule //converter                          在end 写一个module name 标识

param_counter_top.v  

参数化设计

//
`define PADWIDTH 3
//top最顶层
module param_counter_top #(parameter WIDTH=`PADWIDTH, PAD_NUM=5)(
    output [WIDTH-1+PAD_NUM:0] count,
    input   clk, count_enable,count_reset,out_enable
);

wire[WIDTH-1:0] Xfer;                   //连接模块instance

//实例化 //u 表示实例,端口通过显式端口连接(.port_name(signal_name))与顶层模块的信号关联。
//counter #(.WIDTH(WIDTH)) u_counter(Xfer, clk, count_enable,count_reset);
counter #(.WIDTH(WIDTH)) 
    u_counter(
    .count          (Xfer),
    .clk            (clk), 
    .count_enable   (count_enable),
    .count_reset    (count_reset)
    );

converter #(.WIDTH(WIDTH), .PAD_NUM(PAD_NUM)) 
    u_converter01(
        .out_bus (count),
        .in_bus(Xfer),
        .enable(out_enable)
        );
    endmodule //param_counter_top


module  tb;// 不综合
    parameter WIDTH=`PADWIDTH,PAD_NUM=3;
    wire [WIDTH-1+PAD_NUM:0] CountTest;
    reg count_enableTest, clockTest,CountResetTest,OutEnableTest;

//instance
param_counter_top #(.WIDTH(WIDTH),.PAD_NUM(PAD_NUM))
    u_param_counter_top(
       .count(CountTest),
        .clk(clockTest), 
        .count_enable(count_enableTest),
        .count_reset(CountRestTest),
        .out_enable(OutEnableTest)
    );

initial begin
    #01 count_enableTest=1'b0;
        clockTest=1'b0;
        CountResetTest=1'b0;
        OutEnableTest=1'b0;

    #02 CountResetTest=1'b1;
        CountResetTest=1'b0;

    #01 count_enableTest=1'b1;  
    #02 OutEnableTest=1'b1;    

    #05 clockTest=1'b1;
    #05 clockTest=1'b0;
    #05 clockTest=1'b1;
    #05 clockTest=1'b0;

    #01 OutEnableTest=1'b0; 
    #02 OutEnableTest=1'b1;

    #05 clockTest=1'b1;
    #05 clockTest=1'b0;
    #05 clockTest=1'b1;
    #05 clockTest=1'b0;
    #05 clockTest=1'b1;
    #05 clockTest=1'b0;
    #05 clockTest=1'b1;
    #05 clockTest=1'b0;
    #05 clockTest=1'b1;
    #05 clockTest=1'b0;
    #05 clockTest=1'b1;
    #05 clockTest=1'b0;
    #10 $finish;
end

endmodule//tb

输出波形:

@号作用

 Verilog中的@符号用于定义always块的执行条件。

如果是 * 号,

  • always @(*) // 自动把用到的所有变量都加进来!
  • “只要块中用到的任何输入信号发生变化,这个 always 块就会自动执行一次。”