SystemVerilog

SystemVerilog是一種在現代積體電路（尤其是超大型積體電路）的設計及驗證流程中，由Verilog發展而來的硬體描述、硬體驗證統一語言，前一部分基本上是2005年版Verilog的擴展，而後一部分功能驗證特性則是一門面向對象程序設計語言。面向對象特性很好地彌補了傳統Verilog在晶片驗證領域的缺陷，改善了代碼可重用性，同時可以讓驗證工程師在比寄存器傳輸級更高的抽象級別，以事務而非單個信號作為監測對象，這些都大大提高了驗證平台搭建的效率。

SystemVerilog已經被採納為電氣電子工程師學會1800-2009標準，並獲得了主流電子設計自動化工具供應商的支持。雖無任一個仿真系統能聲稱自己完全支援SystemVerilog語言參考手冊（Language Reference Manual, LRM）裡介紹的所有語言結構，要改善測試平台的互操作性相當困難，但推進跨平台兼容性的研究開發工作已在進行中。若干種驗證方法學相繼出現，以預定義類的形式對測試平台模塊進行標準化，如今最新基於SystemVerilog的驗證方法學為通用驗證方法學。此方法學主要包括開放原始碼的類庫以及支援可重用測試平台、開發驗證IP核的預置格式。許多第三方提供商則開始推出基於SystemVerilog的驗證IP核。

## 發展歷史

SystemVerilog的歷史可以追溯到2002年，當時一個被稱為「Superlog」的語言被捐贈給Accellera公司（Verilog的主要支持者）。而Synopsys公司捐贈的OpenVera後來發展成為SystemVerilog中硬體驗證語言子集。2005年，SystemVerilog獲批成為電氣電子工程師學會1800-2005標準。當時Verilog作為電氣電子工程師學會1364-2005標準尚獨立存在。2009年，SystemVerilog與的Verilog進行了合併，成為了新的電氣電子工程師學會1800-2009標準。現在的最新標準是電氣電子工程師學會1800-2017標準。

## 設計特性

對於電路設計工程師來說，SystemVerilog中有大部分內容繼承自Verilog，但是也提供了一些增強或者改進的特性。下面的章節主要講述SystemVerilog的這些特性。

### 新的數據類型

#### 邏輯型變量

SystemVerilog定義了一種新的邏輯型（`logic`）變量。和Verilog中變量的聲明類似，以下代碼描述了一個32位的邏輯型變量：

```verilog
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，有時也被譯為「合併陣列」）。壓縮陣列本身可以是多維的，即變量名稱左邊可以具有多維索引。如果在變量名稱右邊指定了陣列尺寸，則稱之為「非壓縮陣列」。下面用示例代碼表示了一個由二維壓縮陣列構成的一維非壓縮陣列：

```verilog
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中，使用枚舉則更為方便。

```verilog
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位）和長整型變量（`longint`64位）、比特型變量（`bit`，1位，僅具有兩個邏輯狀態，和邏輯型數據（`logic`）缺少了未知邏輯（`x`）和高阻態（`z`）。使用兩態邏輯可以提高邏輯仿真速度）。

#### 結構體和聯合體

這兩種數據類型和C語言中的結構體和聯合體類似。在SystemVerilog中，與這兩種數據類型相關的增強特性為壓縮屬性（`packed`）和標籤屬性（`tagged`）。壓縮屬性使得mapped 1:1 onto a packed array of bits，而標籤屬性允許跟蹤當前聯合體中實際被使用的成員。這些結構占據了連續的存儲空間。

```verilog
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中的過程代碼可以描述時序邏輯電路，也可以描述組合邏輯電路，這一點容易造成概念的混淆，如果代碼不嚴謹，邏輯綜合很可能推斷出鎖存器，從而產生不符合預期的硬體模型。SystemVerilog除了繼續支持原來Verilog中的老式過程結構`always`、`initial`，還在原有`always`基礎上針對組合邏輯電路、觸發器、鎖存器設計了專用的`always`結構。雖然它們顯得不夠通用，但是三類硬體模型的差異得到了明確地區分，避免使用老式`always`時容易出現的鎖存器推斷：

- `always_comb`：用於組合邏輯電路（相當於Verilog中對所有輸入變量電平敏感的`always`，但`always_comb`無需手動列出所有輸入變量，系統會自動識別）。
- `always_ff`：用於觸發器及相關的時序邏輯電路（相當於Verilog中對某個或某幾個信號有效跳變沿敏感、並帶有信號儲存特性的`always`）。
- `always_latch`：用於鎖存器級相關的時序邏輯電路（相當於Verilog中對某個或某幾個信號電平敏感、並帶有信號儲存特性的的`always`）。

三種專用的`always`結構在使用時會檢查設計代碼是否符合對應硬體模型的特徵。如果設計人員在過程代碼中描述了與對應硬體不符合的行為（例如在`always_latch`的敏感列表中添加了定時器訊號的上升沿），那麼系統會發出警告。這是SystemVerilog種專用型過程結構的優越之處。

下面的例子使用`always_comb`來描述組合邏輯電路。在SystemVerilog中無需像Verilog中描述組合邏輯電路那樣寫出敏感列表，`always_comb`會告訴邏輯綜合工具這是一個組合邏輯電路，因此系統會默認對所有輸入信號的電平敏感。在表示組合邏輯電路的過程代碼中，一般使用阻塞賦值。

```verilog
always_comb begin
    tmp = b * b - 4 * a * c;
    no_root = (tmp < 0);
end
```

邏輯綜合工具會將`always_ff`推斷為同步時序邏輯，這裡需要寫明對什麼信號的什麼邊緣（上升或者下降）敏感。在表示時序邏輯電路的過程代碼中，一般使用非阻塞賦值。

```verilog
always_ff @(posedge clk)
    count <= count + 1;
```

邏輯綜合工具還會將`always_latch`推斷為電平敏感的鎖存器電路，對輸入信號的電平敏感，但是具有信號儲存特性：

```verilog
always_latch
    if (en) q <= d;
```

## 接口

對於小型的設計，Verilog設計人員可以使用埠（port）來簡潔地描述模塊與外部環境的連接情況。不過，在規模較大、抽象層次較多的設計中，處於樞紐地位的模塊往往具有大量連線與外界的連通。為此，SystemVerilog引入了接口（interface）的概念，這個概念一方面減少了大量需要聲明的埠名稱，另一方面，它還方便設計人員將某些相關信號的通道作為一捆相對獨立的線網組，這樣就使複雜的設計更加簡單、明晰。另一個概念是modport，它顯示了邏輯連接的方向。例如：

```verilog
interface intf;
  logic a;
  logic b;
  modport in (input a, output b);
  modport out (input b, output a); 
endinterface

module top;
  intf i ();
  u_a m1 (.i1(i));
  u_b m2 (.i2(i));
endmodule

module u_a (intf.in i1);
endmodule

module u_b (intf.out i2);
endmodule
```

## 驗證特性

隨著積體電路集成規模的不斷提高，電路的複雜程度也越來越大。在這種情況下，設計驗證在整個設計流程中所占用的時間越來越高。SystemVerilog在Verilog基礎上增加了許多專門針對驗證的特性，使其成為一種傑出的硬體驗證語言。下面所提到的驗證代碼通常是不可綜合的，它們的作用主要體現在測試平台的搭建過程中。

### 新的數據類型

SystemVerilog引入了專門的字串型變量，用關鍵字`string`表示，例如：

```verilog
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）：

```verilog
int cmdline_elements; // # elements for dynamic array
int 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中名稱相同的「接口」概念，但是二者的實際含義卻大有不同，後者的接口能夠提供類似多繼承的功能。SystemVerilog的類可以自帶配置參數，這種類被稱為參數化的類，其功能與C++的模板類似，不過SystemVerilog不支持模板特化和函數模板。SystemVerilog的多態性和C++類似，在子類中被覆蓋的同名方法在基本類中必須使用關鍵字`virtual`予以標識。SystemVerilog里也有諸如`local`、`protected`等關鍵字來管理數據、方法成員的可見性。默認情況下，所有方法、數據都是對外公有的。類的示例由構造函數`new`來創建，而析構函數則無需驗證人員手動創建。如果某個對象沒有被任何句柄指向，系統會自動銷毀它。SystemVerilog的類是構建諸如通用驗證方法學類庫結構的基礎。

```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); endfunction
endclass

class 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;
    endfunction
endclass
```

### 含約束的隨機化

可以對定義在類或者其他語句塊中的整數型變量進行隨機化，從而實現輸入激勵的隨機化，大大降低了驗證人員專門定義輸入激勵的複雜程度。這時使用SystemVerilog構建隨機驗證平台的重要技術基礎。在類數據變量聲明前使用`rand`和`randc`這兩個關鍵字，可以讓系統得知這些變量在隨後被隨機化。`randc`產生的隨機數序列可以保證變量被再次賦值為同一數值之前，所有可能數值已經被遍歷，而聲明為`rand`的變量則在隨機化過程中沒有這一特性。

```systemverilog
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}`方法可以在該方法被調用時將花括號內的約束附加到原有的約束條件之上。

```systemverilog
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`）兩部分組成。屬性可以是多個序列在時間上的組合。序列主要用布爾表達式和時序操作符來描述。

```systemverilog
sequence S1;
    @(posedge clk) req ##1 gnt;
endsequence
```

這一簡單序列的意思是，信號`gnt`會在信號`req`為高電平一個時鐘周期後也為高電平，如果這一情況沒有發生，則斷言失敗。SystemVerilog還提供其他一些時序操作符，例如重複、連續操作符。這些操作符可以讓驗證人員定義更加複雜的變量關係。在上面這個例子中，加入信號`req`為低電平，則這個斷言永遠都是失敗的。我們可以使用蘊含操作符`|=>`來表示我們所需的情況。

```systemverilog
property req_gnt;
    @(posedge clk) req |=> gnt;
endproperty

assert_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:

```systemverilog
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;
   endgroup
endclass
```

在這一例子中，驗證工程師重點關注廣播和單播幀的分布以及負載大小。

### 同步化

一個複雜的測試平台往往是包含多個可重用驗證模塊的環境，這些模塊之間需要互相通信。Verilog的事件（`event`）允許不同語句塊之間相互觸發，不過這在複雜測試平台的線程間通信比較繁瑣。為此，SystemVerilog提供了兩種新的語言要素，信箱（`mailbox`）和旗語（`semaphore`）。信箱類似一個參數化的FIFO（意味著其內容的類型是可定義的），可以將符合其類型的對象「裝入」和「取出」信箱。而旗語則限定了對同一資源的訪問個數。

## 其他與傳統Verilog類似概念的比較

除了前面提到的特性，SystemVerilog還在以下方面對傳統的Verilog進行了增強：

- 過程賦值操作符（包括阻塞和非阻塞）能夠直接對數組進行操作
- 埠（輸入、輸出、雙向）可以支持更多的數據類型，包括結構體、枚舉、實數等
- For循環內部可以自動定義變量，並為它添加了`continue`和`break`功能
- 在原有的`while`循環基礎上添加了`do-while`循環
- 可以使用關鍵字`const`來聲明常量
- 可以對整個陣列進行初始化
- 預處理器的字串宏的功能得到增強
- 除了原有的並發過程語句塊`fork/join`結構，還提供`join_none`和`join_any`兩種代碼塊結尾方式
- 任務的埠能夠被聲明為引用類型（`ref`），這使得任務能夠直接獲取任務參數自身（引用傳遞），而不是一個臨時複製的數值（值傳遞）。因此，對輸入方向的形式參數的操作會直接改變對應的實際參數。
- 函數能夠被聲明為空類型`void`，即不返回任何數值
- 參數（`parameters`）可以被聲明為任何類型，包括使用者使用`typedef`定義的新類型
- 提供SystemVerilog DPI（即SystemVerilog直接編程接口）來連接以C語言、C++編寫的模塊

## 參考文獻

### 來源

- SystemVerilog Assertions Handbook, 2nd Edition - http://SystemVerilog.us
- A Pragmatic Approach to VMM Adoption - http://SystemVerilog.us

## 外部連結

### IEEE 標準文獻

- 1800-2009 IEEE Standard for System Verilog-Unified Hardware Design, Specification, and Verification Language
- SystemVerilog 3.1a Language Reference Manual - 2004 draft version, which is before IEEE 1800-2005 standard.

### 教程

- SystemVerilog Tutorial

### 標準開發

- IEEE P1800 – Working group for SystemVerilog
- Sites used before IEEE 1800-2005
- SystemVerilog official website
- SystemVerilog Technical Committees

### 語言延伸

- Verilog AUTOs - An open-source meta-comment system to simplify maintaining Verilog code.

## 基本資料

- 域：SystemVerilog logo
- 文件擴展名：.sv, .svh
- 編程范型：結構化 (設計)、面向對象 (驗證)
- 發行日期：2002年
- 設計者：Synopsys, 後來是IEEE
- 軟體版本週期：IEEE 1800-2017
- 軟體版本週期：2018年2月22日
- 類型系統：靜態, 弱類型
- 受影響於：設計: Verilog, VHDL, C++, 驗證: OpenVera, Java