Verdvana

空持千百偈 不如吃茶去

SystemVerilog语法介绍

13 Jan 2020 » SystemVerilog

1 前言

        SystemVerilog完全兼容Verilog HDL,还加入了类似C++的语法用于验证。总之一句话,用TMD!


2 特定逻辑过程

2.1 always_ff

        描述时序逻辑电路模块,如果过程内部写成组合逻辑,则会报错。

2.2 always_comb

        描述组合逻辑电路模块,如果过程内部写成锁存器,则会报错。

2.3 always_latch

        描述锁存器电路模块,如果写成其他电路,则会报错。


3 新添加的数据类型

3.1 Logic

        4态,0、1、X、Z,位宽可变,可以代替所有其他类型,包括reg

        Logic类型类似于VHDL的std_ulogic类型:

  • 对应的具体元件待定;
  • 只允许使用一个驱动源,或者来自于一个或者多个过程块的过程赋值(对同一变量既进行连续赋值(assign)又进行过程赋值是非法的);
  • 在SV中,logic和reg是一致的(类似于Verilog中wire和reg类型是一致的)。

        wire数据类型仍旧有用,因为:

  • 用于多驱动源总线:如多路总线交换器;
  • 用于双向总线(两个驱动源)。

3.2 bit

        2态,1位0或1,位宽可变。

        相当于2态数据类型的变量或线网。

3.3 byte

        2态,8位有符号整型数。

3.4 shortint

        2态,16位有符号整型数。

3.5 int

        2态,32位有符号整型数。

3.6 longint

        2态,64位有符号整型数。

3.7 用户定义的类型——typedef

        允许生成用户定义的或者容易改变的类型定义,通常命名用“_t”做后缀:

`ifdef STATE2
    typedef bit bit_t;  //2态
`else
    typedef logic bit_t;//4态
endif

        只要用typedef就能很容易的在4态和2态逻辑仿真之间切换以加快仿真速度。

3.8 枚举——enum

        缺省状态下为2态整型(int)变量:

enum {red,yellow,green} lignt1,lignt2;  //red = 0, yellow = 1, green = 2
enum {bronze=3,silver,gold} medal;      //silver = 4, gold = 5 
enum {bronze=4'h3,silver,gold} medal;   //silver = 4'h4, gold = 4'h5 

        常用于状态机设计中状态的声明。

        整型:

module fsm_1;
......  
    enum {              //类型缺省,为整型
        IDLE = 3'b000,  //对枚举名赋值
        READ,           //3'b001
        DLY,            //3'b010
        DONE,           //3'b011
        XX = 3'b111
    } state,next;

....

endmodule

        四状态:

module fsm_2;
......  
    enum reg [1:0]{     //指定四状态数据类型
        IDLE = 2'b00,   //对枚举名赋值
        READ,           //2'b01
        DLY,            //2'b10
        DONE,           //2'b11
        XX = 'x         //x赋值在仿真无关项优化综合和调试时非常有用
    } state,next;

....
endmodule

4 打包和未打包的数组

        未打包的四维数组:

logic xdata [3:0][2:0][1:0][7:0];   //对于这个语句最大的可访问单元为1位

        打包的一维数组和未打包的三维数组:

logic [7:0] xdata [3:0][2:0][1:0];  //对于这个语句最大的可访问单元为8位

        打包的二维数组和未打包的二维数组:

logic [1:0][7:0] xdata [3:0][2:0];  //对于这个语句最大的可访问单元为16位

        打包的四维数组:

logic [3:0][2:0][1:0][7:0] xdata;  //对于这个语句最大的可访问单元为192位

5 接口

5.1 隐含的端口连接

        Verilog和VHDL都能用按端口名连接或按顺序连接的方式引用实例模块。SystemVerilog用了两个新的隐含端口连接解决了顶层代码编写时表示端口连接代码的冗长:

  • .name:端口连接;
  • .*:隐含的端口连接。

        隐含.name和.*端口连接的规则:

  • 在同一个实例引用中禁止混用.*和.name端口;
  • 允许在同一个实例引用中使用.name和.name(signal)连接;
  • 允许在同一个实例中引用中使用.*和.name(signal)连接;
  • 必须用.name(signal)连接的情况:
    • 位宽不匹配;
    • 名称不匹配;
    • 没有连接的端口。

5.2 SystemVerilog中的接口

        隐藏的端口连接的缺点之一是不容易发现错误,所以出现了接口。

        接口提供了新的层次结构,把内部连接和通信封装起来,把模块的通信功能从其他功能中分离出来,并且消除了接线引起的错误,让RTL级别的抽象成为可能。

        有关接口的说明:

  • 接口能传递穿越端口的记录数据类型;
  • 有两种类型的接口元素:
    • 声明的;
    • 作为参数可以直接传递进入模块。
  • 接口可以是:
    • 参数,常数和变量;
    • 函数和任务;
    • 断言。

        接口的引用:

  • 接口变量可以用接口实例名加“.”变量名引用;
  • 接口函数可以用接口实例名加“.”变量名引用;
  • 通过接口的模块连接:
    • 能调用接口任务和函数的成员来驱动通信;
    • 抽象级和通信协议能容易地加以修改,用一个包含相同成员的新接口来替换原来的接口。

        接口的使用:

interface intf;         //接口类型声明
    logic a,b;
    logic c,d;
    logic e,f;
endinterface

module top;
    intf w();           //接口实例引用
    mod_a m1(.i1(w));   //具体化的接口实例w在mod_a中被称为i1
    mod_b m2(.i2(w));   //具体化的接口实例w在mod_b中被称为i2
endmodule

module mod_a(intf i1);  //括号内:引用定义的接口类型 / 被引用接口的本地访问名
endmodule

module mod_b(intf i2);  //括号内:引用定义的接口类型 / 被引用接口的本地访问名
endmodule

        如图:

1

        接口不要都用线网类型。一般情况下,接口将把模块的输出连接到另一模块的输入,输出可能是过程性的或者连续性驱动的,输入是连续性驱动的。既然接口是把输出与输入连接起来,而输出常由过程性语句赋值,即使输出是由连续赋值语句指定的,当它们被连接到不同模块时,它也往往被转变成过程性赋值。而双向和多驱动线网在接口定义中必须声明为线网类型


6 不定长赋值

  • ‘0:所有位赋0;
  • ‘1:所有位赋1;
  • ‘z:等于Verilog中的’bz;
  • ‘x:等于Verilog中的’bx。

7 循环语句性能增强

        举例说明吧,在Verilog中:

module for4a(
    input               s,
    input      [31:0]   a,
    output reg [31:0]   y
);

    integer i;                  //独立的迭代变量声明

    always@(a or s)
        for (i=0;i<32;i=i+1)    //显式递增
            if(!s)
                y[i] = a[i];
            else
                y[i] = a[31-i];

endmodule

        而在SystemVerilog中:

module for4a(
    input               s,
    input      [31:0]   a,
    output reg [31:0]   y
);


    always_comb
        for (int i=0;i<32;i++)      //本地迭代变量声明,退出循环后不存在
            if(!s)                  //隐式自动递增
                y[i] = a[i];
            else
                y[i] = a[31-i];

endmodule

8 void函数

        void用于定义没有返回值的函数。

        与Verilog中的任务的异同:

  • 相同点:
    • 不必从Verilog表达式中被调用,可以像Verilog的任务一样,被独立调用。
  • 不同点:
    • 不能等待;
    • 不能包括延迟;
    • 不能包括事件触发;
    • 被always_comb搜寻到的信号自动加入敏感列表。

        舉例說明:

module comb1(
    input  bit_t        a,b,c,
    output bit_t [2:1]  y
);

    always_comb         //等价于always@(a,b,c)
        orf1(a);
    
    function void orf1; //void函数的行为类似于0延迟的任务
        input a;        //b和c是隐含的输入
        y[1] = a|b|c;
    endfunction

    always_comb         等价于always@(a)
        ort1(a);
    
    task ort1;          //Verilog任务
        input a;        //b和c是隐含的输入
        y[2] = a|b|c;
    endtask

endmodule

9 结构体

        在非面向对象编程中,最经常使用的就是函数。要实现一个功能,那么就要实现相应的函数。当要实现的功能比较简单时,函数可以轻易的完成目标。但是,当要实现的功能比较复杂时,仅仅使用函数实现会显得比较笨拙。

        结构保留了逻辑分组,虽然引用成员需要用比较长的表达式但是代码的意义很容易理解:

struct {
    addr_t src_adr;
    addr_t dst_adr;
    data_t data;
} pkt;

initial begin
    pkt.src_adr = src_adr;          //把src_adr的值赋给pkt结构中的src_adr
    if(pkt.scr_adr == node.adr);    //把node结构中的adr区与pkt结构中的dst_adr区做比较
    ...
end

        结构、联合的打包:

typedef logic [7:0] byte_t;

typedef struct packed{
    logic [15:0] opcode;
    logic [7:0]  arg1;
    logic [7:0]  arg2;
} cmd_t;

typedef union packed{
    byte_t [3:0] bytes;
    cmd_t        fields;
} instruction_u;

instruction_u cmd;

        可以得出,cmd为32位,cmd_t的区域为:

cmd.fields.opcode[15:0],cmd.fields.arg1[7:0],cmd.fields.arg2[7:0]

        也等于:

cmd.byte[3],cmd.byte[2],cmd.byte[1],cmd.byte[0]

        打包的联合使得我们能方便地用不同的名称引用同一个数据。


        告辞