SystemVerilog

发展历史

SystemVerilog的历史可以追溯到2002年，当时一个被称为“Superlog”的语言被捐赠给Accellera公司（Verilog的主要支持者）。而Synopsys公司捐赠的OpenVera后来发展成为SystemVerilog中硬件验证语言子集。2005年，SystemVerilog获批成为电气电子工程师学会1800-2005标准。当时Verilog作为电气电子工程师学会1364-2005标准尚独立存在。2009年，SystemVerilog与的Verilog进行了合并，成为了新的电气电子工程师学会1800-2009标准。现在的最新标准是电气电子工程师学会1800-2012标准。

设计特性

对于电路设计工程师来说，SystemVerilog中有大部分内容继承自Verilog，但是也提供了一些增强或者改进的特性。下面的章节主要讲述SystemVerilog的这些特性。

逻辑型变量

SystemVerilog定义了一种新的逻辑型（logic）变量。和Verilog中变量的声明类似，以下代码描述了一个32位的逻辑型变量：
logic [31:0] my_var;
在传统的Verilog中，变量主要分为线网型（wire）和寄存器型（reg）两大类型。只有寄存器型变量才能够在过程代码块中被赋值，而线网型变量只能在过程代码块之外被连续赋值。寄存器型变量的过程赋值和线网型变量的连续赋值使用了完全不同的语句结构。在Verilog中，二者的区分比较微妙，以至于有些专业的工程师也在设计中混淆二者。由于always过程代码块不仅可以描述时序逻辑电路，还可以通过将所有寄存器型的输入变量添加到敏感列表来实现纯组合逻辑电路，因此“寄存器型”这个带有时序逻辑意义的术语本身也令人误会。
SystemVerilog增强了寄存器型变量的功能，它可以像Verilog中线网型变量一样由线网（如逻辑门等模块的输出）驱动（这样的线网驱动寄存器的方式在Verilog中是不允许的）。这种增强的变量类型被命名为“逻辑型”，从而避免“寄存器型”在字面上给人带来的误会。在大多数情况中，SystemVerilog中的logic可以替代Verilog中的reg和wire，但是如果某个某个变量具有多个驱动源，那么就不能使用logic，而要使用严格的wire来定义它。

多维压缩数组

这种结构将Verilog中与寄存器、存储器相关的概念进行了合并和扩展。
在Verilog中，变量名称左边的索引被用来表示二进制变量的位宽，Verilog规定它只能是一维的。而这个数组名称右边的索引用来表示以这种位宽变量组成数组的元素个数，因为数组可以是一维数组、二维数组或者多维数组，因此这个索引可以是任意整数。在SystemVerilog中，如果在变量名称左边指定了由高至低的位宽（如8位信号由[7:0表示），则称之为“压缩数组”（packed array，有时也被译为“合并数组”）。压缩数组本身可以是多维的，即变量名称左边可以具有多维索引。如果在变量名称右边指定了数组尺寸，则称之为“非压缩数组”。下面用示例代码表示了一个由二维压缩数组构成的一维非压缩数组：
logic [1:0][2:0] my_pack[32];
在上面的例子里，非压缩数组数组my_pack具有32个元素（这里用到了类似C语言的数组元素个数表示方法，这里也可以写成Verilog中常见的[31:0]形式）。这个非压缩数组每一个元素本身又是压缩数组，即2个位宽为3的逻辑型变量，因此非压缩数组的每一个元素包含六位二进制数的信息。A variable of packed array type maps 1:1 onto an integer arithmetic quantity.

枚举

SystemVerilog引入了枚举型变量，它使用一系列有实际字面意义的名称来代表若干变量。如果不进行专门的数据类型转换，一个枚举型变量不能直接赋值给另一个枚举型的变量。过去在Verilog中描述有限状态机常常使用参数（关键字为parameter），而在SystemVerilog中，使用枚举则更为方便。
typedef enum logic [2:0] {
   RED, GREEN, BLUE, CYAN, MAGENTA, YELLOW} color_t;color_t   my_color = GREEN;initial $display("The color is %s", my_color.name());
上面的例子使用了typedef来创建了一个新的数据类型名称，从而可以用它来创建一系列枚举数据。枚举类型的数据类型，为位宽为3的逻辑型变量。3位二进制数能够逐一指代六种颜色。使用代码color_t my_color = GREEN;创建了一个新的color_t型变量，其值初始化为六种颜色中的绿色。系统函数$display的作用与Verilog相同，其参数是my_color所属函数name()的返回值，即当前枚举值的ASCII值。

其他新增的数据类型

除了基本的逻辑型变量（logic），SystemVerilog正还提供了字节型变量（byte，8位）、短整型变量（shortint，16位）、整型变量（int，32位）和长整型变量（longint64位）、比特型变量（bit，1位，仅具有两个逻辑状态，和逻辑型数据（logic）缺少了未知逻辑（x）和高阻态（z）。使用两态逻辑可以提高逻辑仿真速度）。

结构体和联合体

这两种数据类型和C语言中的结构体和联合体类似。在SystemVerilog中，与这两种数据类型相关的增强特性为压缩属性（packed）和标签属性（tagged）。压缩属性使得mapped 1:1 onto a packed array of bits，而标签属性允许跟踪当前联合体中实际被使用的成员。这些结构占据了连续的存储空间。
typedef struct packed {
    bit [10:0]  expo;
    bit         sign;
    bit [51:0]  mant;} FP;FP     zero = 64'b0;

条件、选择语句的唯一性和优先性

当条件、选择语句的路径分支较为复杂时，设计人员稍不留意就可能造成代码所描述的行为违背设计人员预计的优先级别，或者被判断的表达式同时满足多个分支条件，从而在仿真过程中产生无规律结果。
为此，SystemVerilog增强了条件、选择语句的功能，允许设计人员为这分支流程的执行设置特别的约束。在多级条件、选择语句中使用关键字unique，可以限定有且只有一个分支可能被执行到，否则将产生一个警告。而关键字priority则指出某些分支路径具有更高的优先级。过去，在传统的Verilog中，要实现类似的功能，设计人员需要在可综合代码中附带一些特殊的注释（例如： // synopsys full_case parallel_case）来通知逻辑综合工具产生正确的分支逻辑电路。不过，注释并非SystemVerilog代码的正式组成部分，它们只是在逻辑综合过程被特定的工具读取，而不严谨的注释很可能造成综合后电路与综合前仿真结果不同的情况。

接口

对于小型的设计，Verilog设计人员可以使用端口（port）来简洁地描述模块与外部环境的连接情况。不过，在规模较大、抽象层次较多的设计中，处于枢纽地位的模块往往具有大量连线与外界的连通。为此，SystemVerilog引入了接口（interface）的概念，这个概念一方面减少了大量需要声明的端口名称，另一方面，它还方便设计人员将某些相关信号的沟道作为一捆相对独立的线网组，这样就使复杂的设计更加简单、明晰。另一个概念是modport，它显示了逻辑连接的方向。例如：
interface intf;
  logic a;
  logic b;
  modport in (input a, output b);
  modport out (input b, output a); 
endinterfacemodule top;
  intf i ();
  u_a m1 (.i1(i));
  u_b m2 (.i2(i));endmodulemodule u_a (intf.in i1);endmodulemodule u_b (intf.out i2);endmodule

验证特性

随着集成电路集成规模的不断提高，电路的复杂程度也越来越大。在这种情况下，设计验证在整个设计流程中所占用的时间越来越高。SystemVerilog在Verilog基础上增加了许多专门针对验证的特性，使其成为一种杰出的硬件验证语言。下面所提到的验证代码通常是不可综合的，它们的作用主要体现在测试平台的搭建过程中。

新的数据类型

SystemVerilog引入了专门的字符串型变量，用关键字string表示，例如：
string s1 = "Hello";string s2 = "world";string p = ".?!";string s3 = {s1, ", ", s2, p[2]}; // 字符串的拼接$display("[%d] %s", s3.len(), s3); // 仿真结果显示为：“[13] Hello, world!”
除了在设计中使用静态数组，SystemVerilog还提供了动态数组、关联数组（associative array）和队列（queues）：
int cmdline_elements; // # elements for dynamic arrayint da[];       // 动态数组int ai[int];    // 以整数位索引的关联数组int as[string]; // 以字符串为索引的关联数组int qa[$];      // 队列initial begin
    cmdline_elements = 16;
    da = new[ cmdline_elements ]; // 为da数组分配16个元素end
动态数组的行为和非压缩数组相似，不过它允许仿真在运行时动态分配元素个数（正如上面例子所示）。压缩数组部分的元素个数（以往称之为“位宽”）必须在编译的时候已知（通过由常数或常数表达式直接确定），而非压缩数组的动态大小则可以在程序运行时，由另一个运行中的程序变量进行初始化，这样就可以在运行中根据所需改变数组的大小。

类

SystemVerilog为验证代码的编写提供了面向对象程序设计的模型。
SystemVerilog支持类的单一继承，即不允许一个类继承多个基本类。需要注意的是, 虽然SystemVerilog里提供了与Java中名称相同的“接口”概念，但是二者的实际含义际ha却大有不同，后者的接口能够提供类似多继承的功能。SystemVerilog的类可以自带配置参数，这种类被称为参数化的类，其功能与C++的模板类似，不过SystemVerilog不支持模板特化和函数模板。SystemVerilog的多态性和C++类似，在子类中被覆盖的同名方法在基本类中必须使用关键字virtual予以标识。SystemVerilog里也有诸如local、protected等关键字来管理数据、方法成员的可见性。默认情况下，所有方法、数据都是对外公有的。类的示例由构造函数new来创建，而析构函数则无需验证人员手动创建。如果某个对象没有被任何句柄指向，系统会自动销毁它。SystemVerilog的类是构建诸如通用验证方法学类库结构的基础。
virtual class Memory;
    virtual function bit [31:0] read(bit [31:0] addr); endfunction
    virtual function void write(bit [31:0] addr, bit [31:0] data); endfunctionendclassclass SRAM #(parameter AWIDTH=10) extends Memory;
    bit [31:0] mem [1<<AWIDTH];

    virtual function bit [31:0] read(bit [31:0] addr);
        return mem[addr];
    endfunction

    virtual function void write(bit [31:0] addr, bit [31:0] data);
        mem[addr] = data;
    endfunctionendclass

含约束的随机化

可以对定义在类或者其他语句块中的整数型变量进行随机化，从而实现输入激励的随机化，大大降低了验证人员专门定义输入激励的复杂程度。这时使用SystemVerilog构建随机验证平台的重要技术基础。在类数据变量声明前使用rand和randc这两个关键字，可以让系统得知这些变量在随后被随机化。randc产生的随机数序列可以保证变量被再次赋值为同一数值之前，所有可能数值已经被遍历，而声明为rand的变量则在随机化过程中没有这一特性。
class eth_frame;
    rand bit [47:0] dest;
    rand bit [47:0] src;
    rand bit [15:0] type;
    rand byte       payload[];
    bit [31:0]      fcs;
    rand bit [31:0] fcs_corrupt;

    constraint basic {
        payload.size inside {[46:1500]};
    }

    constraint good_fr {
        fcs_corrupt == 0;
    }endclass
在上面这一例子中，数据成员fcs未被声明为随机变量，而其他几个数据成员则被声明为随机变量。在这个类中，紧随变量声明的是两个对随机化进行约束的语句块，用关键字constraint予以标识。例如第一个名为basic的约束要求数据成员payload[]（动态数组）的大小在46和1500之间（包含这两个边界值）。如果没有专门指定约束条件，则变量在可能的取值范围内的随机化不受任何约束。约束块内代码的写法有若干种，验证人员可以设定更加复杂的约束关系，例如变量获取区间甲内某一数值的权重为某一定值，或者采用多个数学关系式来限定变量的随机化取值。当然，这样做的前提是数学上能够找到数值来同时满足这些约束。SystemVerilog的解析器会尽量产生匹配条件的随机数值，但是如果没能够成功，则会返回信息告知验证人员此随机化过程失败。

随机化方法

每一个SystemVerilog类都有隐含的三个与随机化有关的方法，它们是pre_randomize、randomize和post_randomize。其中，randomize方法为整个随机化过程中最重要的方法，验证人员可以在过程代码中使用该方法来启动变量的随机化过程。注意该方法的主体是事务对象本身（在通用验证方法学中，事务一般是一个具有uvm_sequence_item类型或从它继承而来的类型），因此需要用object.randomize()来调用该方法，每执行一次该方法，该对象所有的随机变量将会得到满足约束条件（如果有）的随机数值。如果随机化失败，例如没能找到满足工程师定义约束的数值，则该方法将返回0表示随机化存在问题。另外两个方法pre_randomize和post_randomize则可以被用来处理随机化主过程之前、之后其他需要的事情。

控制约束条件

此外，SystemVerilog还提供了constraint_mode()和random_mode()这两个方法来在过程代码中控制随机化，前者用于临时开启或关闭一个约束，而后者则用于临时开启或关闭某个变量的随机化。此外还有randomize () with {constraint}方法可以在该方法被调用时将花括号内的约束附加到原有的约束条件之上。
class eth_frame;
    rand bit [47:0] dest;
    rand bit [47:0] src;
    rand bit [15:0] type;
    rand byte       payload[];
    bit [31:0]      fcs;
    rand bit        corrupted_frame;

    constraint basic {
        payload.size inside {[46:1500]};
    }
   
    constraint one_src_cst {
        src == 48'h1f00
    }
    
endclass...eth_frame my_frame; 

my_frame.one_src_cst.constraint_mode(0); //关闭one_src_cst约束，之后该变量的随机化将不再受该约束限制my_frame.type.random_mode(0);        // 关闭type变量的约束，之后的随机化将不会给该变量赋予随机数值my_frame.randomize();

断言

与其他属性规约语言类似，SystemVerilog具有断言这一概念。在整个芯片验证流程中，断言检查器会自动检查定义的属性是否得到满足，如果违背，则会给出警告。SystemVerilog断言由序列（sequences）和属性（properties）两部分组成。属性可以是多个序列在时间上的组合。序列主要用布尔表达式和时序操作符来描述。
sequence S1;
    @(posedge clk) req ##1 gnt;endsequence
这一简单序列的意思是，信号gnt会在信号req为高电平一个时钟周期后也为高电平，如果这一情况没有发生，则断言失败。SystemVerilog还提供其他一些时序操作符，例如重复、连续操作符。这些操作符可以让验证人员定义更加复杂的变量关系。在上面这个例子中，加入信号req为低电平，则这个断言永远都是失败的。我们可以使用蕴含操作符|=>来表示我们所需的情况。
property req_gnt;
    @(posedge clk) req |=> gnt;endpropertyassert_req_gnt: assert property (req_gnt) else $error("req not followed by gnt.");
上面这个例子显示了蕴含（implication）操作符|=>的使用。它左边的子句称为先行算子（antecedent）而右边的子句称为后续算子（consequent）。这时，如果req未满足高电平的要求，则后面的gnt无论是高电平还是低电平，整个断言都默认为成功；但是如果req为高电平，则系统会根据gnt的逻辑值来决定断言是否成功。除此之外，SystemVerilog还支持断言覆盖。

功能覆盖

在硬件验证中，功能覆盖是指验证过程中对人工定义信号事件的数据采样和收集过程。功能覆盖率在某种程度上反映了被测设计在所给输入激励下，其内部功能正确与否被检测到的百分比。功能覆盖率越高，一般代表测试越完备。注意功能覆盖率和代码覆盖率不同，后者只是测量被测设计的所有代码有多少在仿真过程中被执行过。功能覆盖的目的是确保设计中的所有边界情况都能够被经历。
SystemVerilog覆盖组（coverage group）内可以定义若干个仓（bins）来储存相关变量取值在其范围内的次数。交叉覆盖（Cross-coverage）则可以进一步获取多个变量分别在某一范围内统计数据。通过使用覆盖组的sample方法，可以启动该覆盖率的统计。
For example:
class eth_frame;
   // Definitions as above
   covergroup cov;
      coverpoint dest {
          bins bcast[1] = {48'hFFFFFFFFFFFF};
          bins ucast[1] = default;
      }
      coverpoint type {
          bins length[16] = { [0:1535] };
          bins typed[16] = { [1536:32767] };
          bins other[1] = default;
      }
      psize: coverpoint payload.size {
          bins size[] = { 46, [47:63], 64, [65:511], [512:1023], [1024:1499], 1500 };
      }

      sz_x_t: cross type, psize;
   endgroupendclass
在这一例子中，验证工程师重点关注广播和单播帧的分布以及负载大小。

同步化

一个复杂的测试平台往往是包含多个可重用验证模块的环境，这些模块之间需要互相通信。Verilog的事件（event）允许不同语句块之间相互触发，不过这在复杂测试平台的线程间通信比较繁琐。为此，SystemVerilog提供了两种新的语言要素，邮箱（mailbox）和旗语（semaphore）。邮箱类似一个参数化的FIFO（意味着其内容的类型是可定义的），可以将匹配其类型的对象“装入”和“取出”邮箱。而旗语则限定了对同一资源的访问个数。