通用驗證方法學

通用驗證方法學（Universal Verification Methodology, UVM）是一個以SystemVerilog類庫為主體的驗證平台開發框架，驗證工程師可以利用其可重用組件構建具有標準化層次結構和接口的功能驗證環境。它是第一個由電子設計自動化領域三巨頭（Cadence、Synopsys和Mentor Graphics）聯合支持的驗證方法學，其最新版本為1.2版。

## 概述

最原始、最簡陋的Verilog測試平台通常會做以下幾件事情：第一，在測試平台模塊中例化被測設計；第二，創建若干變量（通常是`reg`類型），將它們連接到被測設計的輸入埠，並在過程代碼塊（通常是`initial`塊）中的不同時間點對它們進行賦值，使這些變量充當被測設計的輸入激勵；第三，採集被測設計輸出埠的信號，或者直接採集被測設計的內部信號；第四，在測試平台模塊內建立一些功能模塊，將採集到的信號與根據之前設計規範預測的參考數值進行對比，然後輸出一些資訊以方便故障的排除。

這樣的驗證模式對於簡單的被測設計尚可，但是如果被測設計的複雜程度較高，這樣的測試平台將會變得十分臃腫，而且不利於工程師維護和重用。例如，如果驗證人員想在原有輸入激勵基礎上添加一系列新的測試序列，則很可能會牽一髮而動全身，影響測試平台的其他部分。工程師需要將驗證平台的不同組分，例如輸入激勵、輸出採集、記分板對比等不同部分相互隔離。在超大規模積體電路已經是主流的今天，Verilog結構化的編程方式使代碼的復用成為了一個嚴重的難題。

雖然SystemVerilog的面向對象編程特性提供了解決了上述問題的可能，但是仍然存在一些問題。工程師有了更靈活的語言，但是怎麼用這種語言來搭建驗證平台卻是沒有明確規範的，因而不同人搭建的驗證平台在結構上會有差異。最明顯的是，不同工程師對於驗證平台功能的劃分可能不一樣，即使採用同樣的劃分，其中同類模塊對外的接口（方法和數據成員）也沒有統一的標準。這在一定程度上阻礙了大型驗證平台之間的協作性、擴展性（假設某公司的兩個團隊分別負責驗證平台的輸入激勵部分和輸出信號比較部分，卻各自使用了非標準化的事務接口，其結果是他們將耗費大量的時間對各自的代碼進行修改，這將大大延緩晶片的上市時間）。驗證方法學提供了一套基於SystemVerilog的類庫，驗證工程師以其中預定義的類作為起點，就可以建立起具有標準結構的驗證平台。

由於行業競爭的關係，歷史上不同廠商曾推出了數種略有差異的驗證方法學。使用不同驗證方法學的驗證平台之間的互操作性仍有一定的障礙。為了進行實現驗證方法學的標準化，早在2009年12月，Accellera（電子設計自動化行業的一個致力於標準化的組織）內部就通過投票，決定以之前的開放驗證方法學2.1.1版為基礎，構建一個新的功能驗證方法學。在此之前，不同的電子設計自動化廠商相繼推出了自己的驗證方法學，例如開放驗證方法學就是Cadence和Mentor Graphics合作推出的。這種情況就造成了不同驗證方法學之間的協同工作需要工程師額外的精力。2011年2月21日，Accellera通過了通用驗證方法學的1.0版，並得到了三大廠商的共同支持。目前最新版本為1.2版。

## 類庫結構

以下列出了通用驗證方法學的類庫結構，注意這與驗證平台中實例的層次關係有所不同

- uvm_void
- uvm_object
- uvm_transaction
- uvm_sequence_item
- uvm_sequence
- uvm_report_object
- uvm_component
- uvm_sequencer
- uvm_driver
- uvm_monitor
- uvm_agent
- uvm_scoreboard
- uvm_env
- uvm_test
- uvm_phase
- uvm_configuration

## 例化被測設計的頂層模塊

採用通用驗證方法學的驗證平台最大的特點就是將其中不同功能的代碼以標準化的規範相互分離。整個驗證平台中最頂層的劃分是被測設計實例化和測試本身的劃分。劃分之後，被測設計（通常是硬體描述語言）可以在一個頂層Verilog（或SystemVerilog）模塊中被調用。除此之外，這個非面向對象的頂層模塊還必須完成以下幾個任務，來為之後建立被測設計和測試（面向對象部分）之間的聯繫做準備：

- 實例化一個或多個SystemVerilog接口（`interface`）
- 將接口中的若干信號和被測設計實例的對應信號埠（`port`）相連
- 執行`initial`過程代碼塊中的`run_test()`方法調用（這個方法的具體定義無需驗證人員關注）
- 在配置資料庫（`uvm_config_db`，有時也稱之為工廠）里使用`set`靜態方法註冊用到的接口

## 頂層模塊和測試類之間的關聯

上面簡歷的這個頂層模塊（非面向對象部分）不屬於測試類所在的類、對象層次結構之中，一般使用常規的Verilog或SystemVerilog模塊（`module`）來構建。這種結構不具備面向對象特性，它和測試類（面向對象部分）的聯繫是通過以下兩步建立的。

### run_test()方法

首先，當頂層模塊執行`run_test()`方法（這是個類庫自帶的方法，調用者無需關心其具體定義）時，通用驗證方法學引擎會在後台執行一系列操作來建立測試環境。驗證人員在啟動類似Synopsys VCS之類的仿真工具之前需要制定測試名稱，仿真工具執行到`run_test()`方法時會去查找名稱與之相同的測試類（由`uvm_test`類繼承），找到之後會創建這個測試類的一個對象（在整個面向對象部分，這個對象的實例化由通用驗證方法學引擎而非使用者完成，且該實例名稱為`uvm_test_top`），然後根據人為的實例化語句來逐層創建測試類中的環境類、代理類、驅動器類等的對象層次結構）。這些對象所屬的類都是從通用驗證方法學預定義的基類繼承而來，驗證人員需要覆蓋一些特定名稱的方法，通用驗證方法學的引擎在執行仿真時會自動調用這些方法，來達到驅動被測設計、採樣所需信號的目的

### 接口

接口（`interface`）是連接驗證平台頂層模塊（非面向對象部分）和測試類（面向對象部分）的橋梁（參見總線功能模型）。

頂層模塊還必須將所用的接口在工廠，即配置資料庫（`uvm_config_db`，詳見本條目後面相關章節）里註冊（執行`set`靜態方法），然後測試所在的面向對象部分可以在工廠里將其取出（執行`get`靜態方法）。這是因為，頂層模塊並不在測試部分的類屬層次關係之中，因此頂層模塊與測試之間的關聯必須使用這樣的特殊的方式建立起來。在配置資料庫中註冊的接口相當於一個大的全局變量，聯繫著整個驗證平台的非面向對象硬體描述語言部分（被測設計實例）和面向對象硬體驗證語言部分（真正測試部分各種類的實例，具有面向對象編程的若干層次）。

測試類獲得這個接口之後，會負責將它在測試類這一端連接到測試類內部的相關組件（如驅動器、監視器等）。頂層模塊的被測設計和測試類不直接通過信號名稱匹配相連（類似Verilog中的埠連接），而是分別只對接口內的信號進行讀寫。

#### 虛接口

接口是在頂層模塊被實例化，並與被測設計實例進行連接的。理論上，測試類部分的成分，例如驅動器、監視器，也需要實例化一個接口，並通過配置資料庫與頂層模塊中的接口實例進行匹配。測試類中的這個接口在實例化時，必須使用關鍵字`virtual`關鍵字進行聲明。將配置資料庫中的接口取出，並賦值給此虛接口，就可以將測試類和頂層模塊聯繫起來。

## 測試類

由測試基類`uvm_test`繼承。

整個驗證平台中除去頂層模塊、被測設計之外的面向對象部分，其頂級類被稱為測試類。測試類通常負責實例化一個環境、一個序列，並調用序列實例的`start(sequencer)`方法來啟動一個環境實例的代理實例的序列產生器。

### 環境類

由環境基類`uvm_env`繼承。

記分板實例、訂閱者實例（如功能覆蓋統計模塊）也在環境中被創建，它們通常不直接讀寫那個與頂層模塊直接相連的接口。與那個接口發生直接關係的驅動器、監視器都由環境實例內的代理實例包裹。通過使用通信接口，事務包由代理生成，傳輸給記分板和覆蓋統計模塊。

#### 代理類

由代理基類`uvm_agent`繼承。

##### 序列發生器類

由序列發生器基類`uvm_sequencer`繼承。這一繼承是參數化繼承，需要指定與其相關的序列項目類的名稱。

序列發生器不需要定義數據成員和方法。一旦高層次的測試中創建了一個序列實例，然後在測試類實例中執行了類似`sequence.start(env.agent.sequencer)`這樣的開啟方法，序列發生器實例就在後台將連串的數據包發送出去（開發人員無需關注數據包是怎麼發送出去的，這些操作由`uvm_sequencer`這個基類中定義的方法來完成）。開發人員只需要在驅動器類中調用其基類成員之一的`seq_item_port`通訊埠的`get_next_item(sequence)`方法，驅動器就能獲得數據包。

##### 驅動器類

由驅動器基類`uvm_driver`繼承。這一繼承是參數化繼承，需要指定與其相關的序列項目類的名稱。

驅動器是代理中直接與被測設計發生作用的部分。它本身並不知道如何產生激勵信號，它只是調用`get_next_item(sequence)`方法，從序列發生器接收的一系列數據包，然後將包中對應的信號通過賦值施加到整個測試平台的公共總線接口上，從而間接地給被測設計提供了所需的輸入激勵。

##### 監視器類

由監視器基類`uvm_monitor`繼承。

監視器是一個被動的模塊，通過接口和被測設計中的信號相連。獲得要監測的數據包（或者單獨的信號）之後，監視器只負責將這些資訊通過通訊埠發送出去，至於誰來接收這些資訊則由其他模塊類關心。通常的做法是，在監視器類里聲明分析埠`uvm_analysis_port`類的實例，調用該實例的`write`方法將獲取的資訊放置到分析埠上。這相當於監視器向外界「廣播」內容，然後由對此感興趣的模塊（訂閱者）負責接收。

#### 記分板類

由記分板基類`uvm_scoreboard`繼承。

記分板不直接與被測設計發生互動，而是通過通訊埠，從監視器那裡獲取後者提供的事務（數據包）。記分板的`run_phase()`方法（任務）通常會提取出數據包中的信號，然後將它們與預設的參考模型進行對比，從而驗證被測設計的功能是否與預期相同。如果不同，驗證人員可以選擇在螢幕上顯示提示或警告，或者直接中斷仿真的運行。

#### 訂閱者類

由訂閱者基類`uvm_subscriber`繼承。這一繼承是參數化繼承，需要指定與其相關的序列項目類的名稱。

覆蓋統計模塊通常是一個訂閱者。

### 序列類

由序列基類`uvm_sequence`繼承。這一繼承是參數化繼承，需要指定與其相關的序列項目類的名稱。

序列類不需要定義任何與`phase`有關的方法，開發人員只需要覆寫`task body()`方法（任務）。此方法內部一般會調用`uvm_do(sequence)方法宏，其结果是使用定义好的序列项目来输出一系列事务序列（一连串事务包，或数据包），这些事务序列将有驱动器接收，进而施加给被测设计。如果序列项目类的数据成员被声明为随机变量（例如使用`rand`），随机化方法`randomize()`会在调用`uvm_do生成數據包時被隱式地調用。

## 序列項目類

由序列項目基類`uvm_sequence_item`繼承。

序列項目定義了了數據包每一幀包含哪些數據成員，這些數據成員可以被聲明為SystemVerilog的隨機變量類型（`rand`或`randc`）。通常不需要在這個類中定義任何方法。這個類的實例會作為序列類的`uvm_do(sequence_item)方法的参数，自动进行数据的随机化（前面这个方法宏内嵌了`randomize()`這樣的隨機化方法，這些都在後台由驗證方法學引擎控制運行），進一步產生連串的數據包。

## 使用虛序列產生器和虛序列

虛序列產生器、虛序列本質上是序列產生器類和序列類的實例。

有時存在多個序列來負責產生生針對被測設計不同部分的輸入數據包，並進一步需要使用多個獨立的序列產生器來連接不同的驅動器。這時通常會使用虛序列產生器和虛序列來調度多個這樣的操作。主要需要以下幾步：

- 定義一個虛序列產生器類，這個類內部包含若干實序列產生器類成員
- 定義一個虛序列類，這個類內部包含若干實序列類成員，並在`body()`方法（任務）中調用`uvm_do_on（sequence_item, sequencer)方法宏，來指定哪個實序列項目（數據包的一幀）對應哪個實序列發生器
- 在環境類或代理類中創建一個虛序列發生器類實例，並在合適的`phase`將實序列發生器的句柄賦值給虛序列發生器類中的實序列發生器成員
- 在測試類創建一個虛序列類實例，使用配置資料庫來指定需要運行序列的`phase`的默認序列（`default_sequence`欄位）。這裡不需要再使用實序列的`start()`方法來啟動對應的序列發生器

## Phase的自動運行機制

通用驗證方法學測試類及其成員所屬的類都是從預定義的基類繼承，因此都具有有一系列以`uvm_phase`類的實例為參數的方法，分別對應該對象的建立、連接、運行等階段。開發人員只需要在自己的類定義里覆寫這些方法，驗證方法學引擎就可以在運行測試時依次自動執行這些方法內的代碼（這些方法不允許被開發人員在自己的代碼里直接調用）。

主要的類方法（其中僅`run_phase`為任務，占用仿真時間）的原型：

- `function void build_phase(uvm phase phase);`：實例化測試類中的組件
- `function void connect_phase(uvm phase phase);`：連接事務通訊埠
- `function void end_of_elaboration_phase(uvm phase phase);`
- `function void start_of_simulation_phase(uvm phase phase);`
- `task run_phase(uvm phase phase);`：指定仿真運行時，此類的實例所執行的動作
- `function void extract_phase(uvm phase phase);`
- `function void check_phase(uvm phase phase);`
- `function void report_phase(uvm phase phase);`
- `function void final_phase(uvm phase phase);`

## 配置資料庫

前面提到了在頂層模塊中實例化和使用的接口可以通過配置資料庫（`uvm_config_db`）傳遞給測試類。配置資料庫本身是個參數化的類。配置資料庫的使用方法通常為調用其靜態方法而非實例化，最常用的兩個靜態方法分別為`set()`（存）和`get()`（取）。和實例方法使用`.`（點號）來連接實例名和實例方法名來調用不同，靜態方法使用`::`（雙冒號），類的作用域操作符）來連接類名和靜態方法名來調用。

## 消息報告

Verilog和SystemVerilog都提供了一個`$display`方法用於字串顯示的。這對於簡單的測試平台較為實用，但是試想當驗證平台十分龐大，所運行的測試也極為複雜時，測試運行過程中會產生海量的字串資訊，驗證人員將花費額外的經歷從中篩選與某個故障相關的資訊。通用驗證方法學以已有的SystemVerilog消息顯示方法為基礎，建立了一套更加完善的消息報告機制。工程師不再直接使用SystemVerilog自帶的消息顯示方法，而是調用驗證方法學定義的較高層次的宏來輸出字串。通過在啟動測試時指定`+UVM_VERBOSITY=<VERBOSITY_LEVEL>`，可以對消息進行過濾。

通用驗證方法學的消息報告主要是通過調用以下幾個宏名稱對應的方法（方法已由類庫定義）來完成：

- `uvm_info(ID, MESSAGE, VERBOSITY)：普通消息，方法需要三个参数，分别为消息ID、消息主体（字符串）和消息级别（verbosity），如果不指定消息级别，则默认为`UVM_LOW`
- `uvm_warning(ID, MESSAGE)：警告，方法需要兩個參數，分別為消息ID和消息主體
- `uvm_error(ID, MESSAGE)：錯誤，方法需要兩個參數，分別為消息ID和消息主體。
- `uvm_fatal(ID, MESSAGE)：致命錯誤，方法需要兩個參數，分別為消息ID和消息主體。直接中斷仿真

消息級別從低到高有：`UVM_NONE`、`UVM_LOW`、`UVM_MEDIUM`、`UVM_HIGH`、`UVM_FULL`和`UVM_DEBUG`。如果調用消息方法時指定的消息級別低於啟動測試時指定的消息過濾級別（前面提到的`<VERBOSITY_LEVEL>`，其值也是從低到高的消息級別中的一個），那麼消息會被顯示出來，否則會被過濾掉。