關注微信公眾號摸魚范式
,后台回復COOKBOOK
獲取COOKBOOK原本和譯本
PDF度盤鏈接
這一部分主要介紹一些UVM的基礎知識,其實《UVM實戰》中已經有了足夠的涉獵。所以這一章着重加注一些UVM的使用哲學探討。
Testbench基礎
UVM采用分層的、面向對象的方法進行testbench開發,允許在不同的團隊成員之間進行“關注點分離”。UVM testbench中的每個組件都有特定的用途,以及和testbench其余部分的接口,以提高生產力並促進重用。把這些組件加入到testbench中,就得到一個模塊化的可重用驗證環境,它使編寫測試用例的人在transaction級思考,專注於必須驗證的功能,而testbench架構師則專注於test如何與DUT交互。
DUT被連接到一些事務器(driver、monitor、responder)上。這些事務器通過驅動和采樣DUT信號在pin級與DUT通信,並通過傳遞transaction對象與UVM testbench的其余部分通信。它們在接口信號和transaction之間轉換數據。在這些事務處理器之上的testbench的部分由專門在事務級進行交互的組件組成,如scoreboard, coverage collector, sequencer等。UVM testbench中的所有組件都是從uvm_component基類擴展而來。
UVM testbench的最低級別是特定於接口的。對每個接口而言,UVM提供一個uvm_agent,其中包括 driver, monitor, sequencer和coverage collector。因此,agent體現了與DUT的所有特定於協議的通信。agent和其他特定於設計的組件封裝在uvm_env 組件中,該組件由頂層的uvm_test組件例化並配置。uvm_sequence_item是一個uvm_object,它包含實現協議和與DUT通信所需的數據字段。uvm_driver負責將sequence_item轉換為信號級接口上的“pin wiggle”,以向DUT發送或從DUT接收數據。sequence_item由一個或多個uvm_sequence對象提供,這些對象在transaction級定義激勵,並在agent的uvm_sequencer組件上執行。sequencer負責執行、仲裁sequences,並在sequences和driver之間傳遞sequence item。
uvm_sequence_item習慣上被稱為transaction,實際上是由從uvm_object派生出的uvm_transaction類派生而來。
uvm_monitor負責觀察DUT接口上的信號級行為,並將其轉換為sequence items,並將這些sequence items提供給agent或testbench上其他地方(如coverage collectors或scoreboard)中的analysis組件。UVM agent還有一個config object,該對象允許test編寫人員在組裝和執行testbench時配置agent。
通過為testbench提供統一的接口,UVM agent將testbench和UVM sequence與接口實現的細節隔離開來。例如,提供數據包的sequence可以在不同的UVM agent中重用,這些agent可能實現AHB、PCI或其他協議。UVM testbench通常為DUT的每個接口編寫一個對應的agent。
對於給定的設計,UVM Agents和其他組件一起被封裝在uvm_env組件中,該組件通常是特定於具體設計的。與agent一樣,env通常也有一個與之相關聯的config object,該對象允許test對env進行配置,包括在env中例化的agent。由於env本身就是UVM組件,所以也可以將其組裝到更高級別的env中。模塊級設計被組裝成子系統和系統時,模塊相關的模塊級env可以作為子系統級env中的組件重用,而子系統級env本身也可以在系統級testbench上重用。
一旦定義了env,uvm_test將例化、配置並構建env,包括自定義整個testbench的關鍵方面,包括選擇在env中使用的組件的變化。選擇UVM sequences在后台運行或是作為test的主要部分。定義testbench上的env、sub_env(如果有)和agent的config object。
sequence包括驗證環境執行的主體sequence以及一些用於響應DUT的掛在后台待調用的sequence。
UVM test通過在頂層initial塊中調用run_test()啟動。
UVM 組件
UVM testbench由擴展自uvm_component基類的組件對象構成。當創建uvm_component派生類的對象時,其成為testbench層次結構的一部分,會在整個仿真期間持續存在。這就與UVM類層次結構的sequence分支形成了對比,而后者涉及到瞬態對象(動態的)
,一旦解除引用,就會創建、使用和銷毀的對象。
動態的: 筆者認為原文含義為,創建、使用的瞬態對象會在解除使用后(沒有句柄指向該對象時,即原對象的句柄指向另一個新對象時)自動銷毀。 例如,不同的sequencer上可能會掛載過多個sequence,而同一時間,只有一個sequence對象,執行過的sequence就會被自動銷毀。同樣地,sequence產生過的transaction(seq item),傳遞出去之后,就自動銷毀。
UVM消息機制使用(准)靜態uvm_component層次結構來打印發出報告消息的組件的作用域,配置過程使用它來確定哪些組件可以訪問配置對象,UVM工廠使用它來應用工廠覆蓋。這個組件層次結構由一個在創建每個組件時遞增構建的鏈表表示。每個組件的層次位置由在構造時傳遞給其create方法的name和parent參數決定。
例如,在下面的代碼片段中,在spi_env中創建了一個apb_agent組件。假設spi_env在頂層test組件中以名稱“m_env”實例化,則agent的分層路徑名是與spi_env組件名稱“uvm_test_top.m_env”,(".")操作符,以及作為第一個參數傳遞給create()
方法的name的拼接,最終agent的層次名稱為"uvm_test_top.m_env.m_apb_agent"。對agent的任何引用都需要使用這個字符串名稱。
即這個分層路徑名由頂層test實例化名至當前組件的實例化名用(".")操作符逐級拼接。
// Hierarchical name example
class spi_env extends uvm_env;
apb_agent m_apb_agent;
// Declaration of the apb agent handle
// ...
function void build_phase(uvm_phase phase);
// Create the apb_agent:
//
// Name string argument is the same as the handle name
// The parent argument is 'this' - i.e. the spi_env
//
// The spi_env has a hierarchical path string "uvm_test_top.m_env"
// is concatenated with the name string to arrive at
// "uvm_test_top.m_env.m_apb_agent" as the
// hierarchical reference string for the apb_agent
m_apb_agent = apb_agent::type_id::create("m_apb_agent", this);
// ...
endfunction: build_phase
// ...
endclass: spi_env
uvm_component類繼承了uvm_report_object類,而該類位於UVM消息傳遞基礎結構的核心。消息機制使用組件靜態層次結構將組件的分層路徑名添加到報告消息字符串中(即這條消息是由哪個組件(對象)報告的)。
uvm_component基類模板的每個UVM phase都有一個虛方法,用戶可以根據需要實現這些方法。未實現的phase級虛方法將導致組件無法有效地參與該phase。
uvm_component基類中還嵌入了對配置表的支持,以存儲與testbench層次結構中組件的子節點相關的配置對象。當使用uvm_config_db API時,這個靜態層次結構(分層路徑名)被用作路徑機制的一部分,以控制哪些組件可以訪問給定的配置對象。
為了在配置中提供靈活性並允許以智能的方式構建UVM testbench的層次結構,uvm_components需要被注冊到UVM工廠。當在構建階段創建UVM組件時,將會使用工廠來構造組件對象。UVM工廠允許使用工廠覆蓋來把一個組件替換為它的一個兼容的派生類型。這種方式可以在不直接更改testbench源代碼的情況下更改testbench的功能(直接更改testbench源代碼需要重新編譯並會阻礙重用)。工廠工作需要許多編碼約定,在關於UVM工廠的內容中對這些約定做了概述。
UVM package包含許多uvm_component基類的擴展(即派生類),用於公共testbench組件。這些擴展中的大多數類都非常“瘦”,也就是說,它們實際上只是uvm_component類的一個小擴展,主要是用來添加一個新的名稱空間。雖然這些不是很關鍵,原則上我們也仍然可以使用 uvm_component 類,但重要的是這些擴展類有助於實現組件的“自我分類”,因為其清楚地表明了實際表示的組件類型,例如driver或monitor。
通俗說就是是UVM強制為環境組件分類做引導,筆者認為UVM倡導的驗證環境構建方式趨向於讓單個組件處理單一類型的事務,這樣環境結構清晰且易於分類維護
此外,還有一些可用的分析工具,它們也使用這些無關的基類來擴展,以幫助建立testbench層次結構的圖像。另一方面,一些預構建的uvm_component擴展實際上是結構塊,其通過實例化具體的子組件,來實現一組復雜的功能。下表總結了直接從uvm_component基類派生的可用的UVM組件類:
UVM 工廠
UVM工廠的目的是允許用派生類型的對象替換其基類的對象,且不需要更改testbench結構甚至testbench代碼。這種機制稱為覆蓋(override)(按type或實例)。此功能對於更改sequence行為或用組件的一個版本替換另一個版本來說十分方便。但是,要交換的任何兩個組件必須是多態兼容的。這就要求所有相同的TLM接口句柄和TLM對象必須由用來替換的派生組件聲明及例化。此外,為了利用工廠的優勢,我們必須遵循某些編碼規范。
override的兩種類型:替換這種class的實例,還是只替換該class的某個實例
工廠編碼規范1:注冊
組件或對象必須包含由以下元素組成的工廠注冊代碼:
- 一個uvm_component_registry wrapper,typedef為type_id
- 獲取type_id的靜態函數
- 獲取類名的函數
例如:
class my_component extends uvm_component;
// Wrapper class around the component class that is used within the factory
typedef uvm_component_registry #(my_component, "my_component") type_id;
// Used to get the type_id wrapper
static function type_id get_type();
return type_id::get();
endfunction
// Used to get the type_name as a string
function string get_type_name();
return "my_component";
endfunction
...
endclass: my_component
注冊代碼有一個規則的模式,可以安全地用四個工廠注冊宏中的一個生成:
// For a component
class my_component extends uvm_component;
// Component factory registration macro
`uvm_component_utils(my_component)
// For a parameterized component
class my_param_component #(int ADD_WIDTH=20, int DATA_WIDTH=23) extends uvm_component;
typedef my_param_component #(ADD_WIDTH, DATA_WIDTH) this_t;
// Parameterized component factory registration macro
`uvm_component_param_utils(this_t)
// For a class derived from an object (i.e. uvm_object, uvm_transaction,
// uvm_sequence_item, uvm_sequence etc.)
class my_item extends uvm_sequence_item;
`uvm_object_utils(my_item)
// For a parameterized object class
class my_item #(int ADD_WIDTH=20, int DATA_WIDHT=20) extends uvm_sequence_item;
typedef my_item #(ADD_WIDTH, DATA_WIDTH) this_t
`uvm_object_param_utils(this_t)
工廠編碼規范2:構造函數默認值
uvm_component和uvm_object構造函數是帶有原型模板的虛方法,用戶必須遵守。為了在構建階段支持延遲構造,工廠構造函數應該包含構造函數參數的默認值。這允許使用默認值在工廠內構建一個工廠注冊類,然后可以將類屬性重新賦值給通過uvm_component_registry wrapper類的create方法傳遞的參數。組件和對象的默認值是不同的:
// For a component:
class my_component extends uvm_component;
function new(string name = "my_component", uvm_component parent = null);
super.new(name, parent);
endfunction
// For an object:
class my_item extends uvm_sequence_item;
function new(string name = "my_item");
super.new(name);
endfunction
工廠編碼規范3:組件和對象創建
Testbench組件是在構建階段使用uvm_component_registry的創建方法創建的。它先構造類,然后在正確地賦值name和parent參數之后,將類的句柄賦值給testbench中的聲明句柄。對於組件而言,構建過程是自頂向下的,這就能允許更高級別的組件和配置來控制實際構建的內容。
根據需要使用create方法創建對象,示例見下面的代碼:
class env extends uvm_env;
my_component m_my_component;
my_param_component #(.ADDR_WIDTH(32), .DATA_WIDTH(32)) m_my_p_component;
// Constructor & registration macro left out
// Component and parameterized component create examples
function void build_phase( uvm_phase phase );
m_my_component = my_component::type_id::create("m_my_component", this);
m_my_p_component = my_param_component #(32, 32)::type_id::create("m_my_p_component", this);
endfunction: build
task run_phase( uvm_phase phase );
my_seq test_seq;
my_param_seq #(.ADDR_WIDTH(32), .DATA_WIDTH(32)) p_test_seq;
// Object and parameterised object create examples
test_seq = my_seq::type_id::create("test_seq");
p_test_seq = my_param_seq #(32,32)::type_id::create("p_test_seq");
// ...
endtask: run
Phasing
為了有一個一致的testbench執行順序,UVM使用phases來對仿真期間的主要階段進行排序。有三組phases,按以下順序執行:
-
Build phases 配置和構造 testbench
-
Run-time phases 在testbench上運行測試用例時消耗時間
-
Clean up phases 收集和報告測試用例的結果
不同的組的phase如上圖所示。uvm_component基類包含被每個不同phase方法調用的虛方法,這些方法由testbench組件創建者根據組件參與的phase來進行填充。使用已定義的phase可以獨立地開發驗證組件,由於對於每個phase應該進行的工作內容是有共同理解的,故組件間仍然可以互動,
類似於UVM對組件的分類,phase的划分,是UVM從環境的執行順序角度出發,對仿真程序進行分類,也就是說,UVM不僅倡導在規定的組件中干特定的事,還倡導在相應的時間干相應的事。這樣就從空間和時間的角度對testbench進行了良好的規划,最后得到的自然是結構清晰、易於維護的環境。
UVM Phase的執行
要啟動UVM testbench,必須從其靜態部分調用run_test()
方法。我們通常是在testbench的top中的initial
塊中調用。
調用run_test()
構造UVM環境的根組件,然后開始UVM phasing。run_test()
方法可以傳入一個字符串參數來定義uvm_component派生類的默認類型名,該類被用作testbench層次結構的根節點。而且,run_test()
方法會檢查一個名為UVM_TESTNAME的命令行附加參數,並使用該附加參數字符串來查找工廠注冊的uvm_component,覆蓋任何默認類型名稱。按照慣例,根節點應該派生自uvm_test組件,但其實它也可以派生自別的任何uvm_component。根節點通過指定testbench組件的配置和由它們執行的激勵來定義要執行的測試用例。
比如,指定my_test(這就是執行的測試用例名)為仿真環境根節點。
`+UVM_TESTNAME=my_test `
Phase 描述
以下部分描述了每個UVM phase的用途。
Build Phases
Build Phases在仿真開始時執行,其主要目的是構造、配置以及連接testbench組件層次結構。所有的Build Phases方法都是函數,因此在零仿真時間內執行。
build
實例化了UVM testbench根節點組件(test)后,就開始執行build phase,它從上到下構造testbench組件層次結構。推遲每個組件的構造,以便於組件層次結構中的每級組件都可以由上面一級配置。build phase使用UVM工廠機制來間接實例化uvm_components。
connect
connect phase用於在組件之間建立TLM連接或為testbench資源賦值句柄。connect phase會在build方法將testbench組件按照層次結構例化之后自底向上執行。
end_of_elaboration
end_of_elaboration phase用於在仿真開始之前對testbench的結構、配置或連接性做出最終調整。它的實現可以假設testbench組件層次結構實現和內部連接已經就緒。也是自底向上執行。
Run Time Phases
testbench激勵在build phases之后的Run Time Phases生成並執行。在start_of_simulation phase之后,UVM並行執行run phase和pre_reset到post_shutdown phase。run phase出現在OVM中,並被保留以允許OVM組件可以輕松遷移到UVM。這也是事務處理器運行的phase。UVM中添加了其他的run time phases,以便於為tests、scoreboards和其他類似組件提供更細的run time phase粒度。對絕大多數testbench來說只使用reset, configure, main和shutdown phase就足夠了,沒有必要使用相應的pre和post phase。
start_of_simulation
start_of_simulation phase是一個函數,它在testbench耗時的部分開始之前執行。它是用來打印(開始)標志,testbench的拓撲結構或配置信息的。同樣是自底向上調用。
筆者不明白UVM cookbook為什么將屬於function的start_of_simulation phase歸類於Run Time Phases,或許是作為run time phases 開始的標志?
run
run phase在start_of_simulation phase之后執行,它用於生成激勵和檢查testbench的活動。run phase為 task phase,並且所有uvm_component的run_phase()都是並行執行的。driver和monitor等事務處理器幾乎一直使用這個phase。
VIP的編寫基本上都只使用run phase,而不使用其他run time phase,這主要是因為考慮到VIP和其所集成到的testbench的兼容性。
Parallel Run-Time Phases
注意:以下run-time phases 是與run_phase並行執行的。這些phase應該只在test和env中調用來啟動sequence,而在driver、monitor和其他組件中則不應該實現這些phase。
pre_reset
這一phase的用途不大。
reset
reset phase 用於實現DUT或接口的特定復位行為。例如,此phase用於生成一個reset並將interface置於其default狀態。
post_reset
這一phase的用途不大。
pre_configure
這一phase的用途不大。
configure
configure phase用於對testbench上的DUT和memory進行編程,便於准備執行測試用例。它還可用於將信號設置為准備執行測試用例的狀態。
post_configure
這一phase的用途不大。
pre_main
這一phase的用途不大。
main
這是由測試用例指定的激勵被生成並應用到DUT的地方。它在所有激勵都耗盡或者發生超時后結束。最多的數據吞吐量將由這個phase開始的sequence來處理。(主要產生激勵的phase)
post_main
這一phase的用途不大。
pre_shutdown
這一phase的用途不大。
shutdown
shutdown phase用於確保在main phase產生的激勵已經施加給DUT且驅動完成,任何的結果數據都已經耗盡。它也可以用於執行讀取狀態寄存器的耗時sequence。
post_shutdown
這一phase的用途不大。
Clean Up Phases
clean up phases 用於從scoreboard和functional coverage monitor中提取信息,以確定測試用例是否已經通過並且達到了預期覆蓋目標。clean up phases 是作為函數實現的,所以執行時間為零。它們自底向上執行。
查看UVM源碼可以發現,final phase是派生自uvm_topdown_phase的(同build phase),應該是自上向下執行的。我覺得是因為UVM希望上層模塊能夠控制下層模塊,若上層模塊中調用$finish結束仿真,那么下層模塊的final phase便不會執行。所以不太理解為什么原文說clean up phases 都是自底向上執行的。
extract
extract phase用於從scoreboard和functional coverage monitor中檢索和處理信息。包括計算report phase所使用的統計信息。通常會在analysis組件中使用這個phase。
check
check phase用來檢查DUT的行為是否正確,並檢查在testbench運行期間可能發生的任何錯誤。這個phase通常在analysis組件中使用。
report
report phase用於打印仿真結果或是將結果寫入文件中。這個phase通常在analysis組件中使用。
final
final phase可以用於完成testbench尚未完成的任何未完成的操作。
個人覺得可能除非是上層提前調用$finish結束仿真,不然感覺沒什么用。report phase打印仿真結果后基本就認為仿真結束了。
UVM Driver
概述
sequence產生激勵(sequence_item),sequencer與driver通過TLM 通信機制交互(發送sequence產生的激勵給driver,接收driver回給的response),然后driver將激勵轉換為接口級的信號並通過虛接口與DUT交互。也就是說,環境中的信息傳遞是transaction級的,driver負責進行transaction級和接口信號格式之間的轉換並按照接口協議驅動DUT。當然,一個driver也可以作為一個“responder”(類似“slave模式”),對接口中的pin級信號驅動作出響應,並與sequence通信,然后sequence產生一個response transaction回給driver,來完成協議。當driver的agent被配置為passive模式(只monitor,不drive)時,根據定義,driver不會在agent中實例化。
也就是說,driver可以只驅動DUT,也可以通過接受分析DUT驅動到接口上的信號來反饋給sequence,sequence來根據反饋決定發送何種transaction給driver來驅動DUT。driver是作為借助虛接口與DUT進行雙向交互的媒介。
剖析 UVM Driver
用戶定義的driver是一個派生自uvm_driver基類的代理類,它包含一個是SystemVerilog接口的BFM。uvm_driver基類提供了一個seq_item_port,該seq_item_port由agent連接到agent的uvm_sequencer的seq_item_export。通常,responses也通過seq_item_port傳遞回sequence,但某些應用程序可能需要通過driver的rsp_port將responses發送回sequencer。有關將driver連接到sequencer的詳細討論,可以參閱Sequencer-Driver的連接。
driver的seq_item_port和sequencer的seq_item_export是UVM做好的,而二者的連接工作則需要使用者手動進行。
UVM Driver API
uvm_driver最終被設計為與連接的uvm_sequencer上運行的uvm_sequence交互。本文中描述了這個API的細節。
UVM Driver Use Models
UVM中的激勵產生依賴於sequence和driver之間的耦合。sequence只能在已知driver特征的情況下編寫,否則sequence或driver就有可能陷入死鎖,一直等待另一方提供item。我們可以通過提供一組可與driver一起使用的基本的實用的sequence,並記錄driver行為的方式來緩和死鎖的問題。
Sequencer-Driver組合存在大量潛在的激勵的生成使用模型,本文將對其中的大部分進行討論。
UVM monitor
概述
testbench分析部分的第一個任務是監測DUT上的活動。和driver一樣,monitor也是agent的組成部分。類似於driver組件,執行的也是實際信號活動和該活動的抽象表示之間的轉換(接口上的信號變化翻譯成環境中的transaction)。Monitor和Driver之間的關鍵區別是Monitor總是被動的,不驅動接口上的任何信號。當agent處於passive模式時,Monitor仍將執行。
Monitor通過虛接口與DUT信號通信,並且包含識別信號活動中的協議模式的代碼。一旦識別出協議模式,Monitor將構建表示該信號活動的transaction模型,並將transaction廣播給任何感興趣的組件。
構造
monitor由從uvm_monitor擴展而來的代理類和BFM (SystemVerilog接口)組成。這個代理應該有一個analysis port 和一個指向BFM接口的虛接口句柄。
class wb_bus_monitor extends uvm_monitor;
`uvm_component_utils(wb_bus_monitor)
uvm_analysis_port #(wb_txn) wb_mon_ap;
virtual wb_bus_monitor_bfm m_bfm; //BFM handle
wb_config m_config;
// Standard component constructor
function new(string name, uvm_component parent);
super.new(name,parent);
endfunction
function void build_phase( uvm_phase phase );
wb_mon_ap = new("wb_mon_ap", this);
m_config = wb_config::get_config(this); // get config object
m_bfm = m_config.WB_mon_bfm; // set local virtual if property
m_bfm.proxy = this; //Set BFM proxy handle
endfunction
task run_phase(uvm_phase phase);
m_bfm.run(); //Don't start the BFM until we get to the run_phase
endtask
function void notify_transaction(wb_txn item); //Used by BFM to
return transactions
wb_mon_ap.write(item);
endfunction : notify_transaction
endclass
interface wb_bus_monitor_bfm (wishbone_bus_syscon_if wb_bus_if);
import wishbone_pkg::*;
//------------------------------------------
// Data Members
//------------------------------------------
wb_bus_monitor proxy;
//------------------------------------------
// Methods
//------------------------------------------
task run();
wb_txn txn;
forever @ (posedge wb_bus_if.clk) begin
//Capture protocol pin activity into txn
proxy.notify_transaction(txn);
end
endtask
endinterface
Passive 監測
正如在科學實驗中,觀察的行為不應該影響所觀察的活動,監測組件應該是passive的。它不應該向DUT注入任何激勵。這也就意味着monitor代碼在與DUT信號交互時應該是完全只讀的(只采樣DUT的信號)。此外,BFM除非被調用,否則將不會觀察數據,以確保BFM與UVM phasing一致。
簡單來說,這段話映射的還是UVM的組件分類哲學,monitor就只monitor,不進行除此之外的工作(比如驅動DUT,對比結果等)。另外就是其行為完全要由phase機制調度,來保證在特定的時間段進行特定的工作。
識別協議
monitor必須了解協議才能檢測信號活動中的可識別模式。可以通過在monitor BFM 的 run() task中編寫特定於協議的狀態機代碼來完成檢測。 這部分代碼通過監測虛接口來等待目標信號活動。
例如,最簡單的,檢測SoC驗證中外設的中斷信號,或是識別系統總線上的一筆讀寫操作。
創建事務級對象
一旦識別出接口上的特定操作,monitor將構建一個或多個抽象地表示信號活動的transaction。這可以通過調用一個函數並將特定於transaction的屬性(例如數據和地址)作為參數傳遞給函數來實現,或者通過在檢測到現有transaction時在其上設置屬性來實現。
這也就是說,事務級對象的構建通過建立與特定信號活動之間的映射來實現。
即寫即拷貝方針
由於SystemVerilog中的對象是基於句柄的,所以當Monitor從其分析端口寫入transaction句柄時,只有該句柄被復制並廣播給接收的組件。每次 Monitor 在 run()
task中運行其正在進行的協議識別循環時,都會發生此寫入操作。為了防止在循環的下一次迭代中覆蓋相同的transaction對象內存,廣播的句柄應該指向Monitor創建的transaction對象的單獨副本。
這可以通過兩種方式實現:
- 在循環的每次迭代(即循環內部)中創建一個新的transaction對象
- 在循環的每次迭代中重用相同的transaction對象,但在調用write()之前克隆該對象,並廣播克隆的句柄。
筆者關於randc用法的tips:之前我在sequence中使用`uvm_do來例化、隨機、發送transaction,發現randc不生效,並不是所有值都出現之后才開始重復下一輪。后來意識到,`uvm_do每次都會重新例化transaction,這樣對於單個的transaction來說,相當於只隨機了一次,而randc是只針對於同一transaction對象的(隨機調用的是對象的randomize函數)。我當時采用的方式是只實例化transaction一次,但是並沒有出現上文提到的transaction被覆蓋的問題,顯然這是因為driver未索取transaction時,sequence不會發新的transaction給driver,所以不存在會被覆蓋的問題。但若是那種會緩存多筆transaction的通信場景(例如使用analysis fifo),那就會出現原文中的問題。如果此時我們還想randc生效,那顯然應該采取上文中提到的第二種方式。每次隨機都使用的是同個對象,但是采用clone對象的方式來發送transaction。
廣播transaction
一旦構建了一個新的或克隆的transaction,就應該通過寫入analysis port的方式廣播給所有需要接收該transaction的組件。
Monitor 示例
//Full run task from monitor BFM
task run();
wb_txn txn;
forever @ (posedge wb_bus_if.clk) begin
if(wb_bus_if.s_cyc) begin
// Is there a valid wb cycle?
txn = wb_txn::type_id::create("txn");
// create a new wb_txn
txn.adr = wb_bus_if.s_addr;
// get address
txn.count = 1;
// set count to one read or write
if(wb_bus_if.s_we) begin
// is it a write?
txn.data[0] = wb_bus_if.s_wdata;
// get data
txn.txn_type = WRITE;
// set op type
while (!(wb_bus_if.s_ack[0] | wb_bus_if.s_ack[1]|wb_bus_if.s_ack[2]))
@ (posedge wb_bus_if.clk);
// wait for cycle to end
end
else begin
txn.txn_type = READ;
// set op type
case (1)
//Nope its a read, get data from correct slave
wb_bus_if.s_stb[0]: begin
while (!(wb_bus_if.s_ack[0])) @ (posedge wb_bus_if.clk);
//wait for ack
txn.data[0] = wb_bus_if.s_rdata[0];
// get data
end
wb_bus_if.s_stb[1]: begin
while (!(wb_bus_if.s_ack[1])) @ (posedge wb_bus_if.clk);
// wait for ack
txn.data[0] = wb_bus_if.s_rdata[1];
// get data
end
endcase
end
// else: !if(wb_bus_if.s_we)
proxy.notify_transaction(txn);
end
endtask
UVM Agent
UVM agent是用於給定邏輯接口(如APB或USB)的驗證組件“套件”。agent包括一個封裝了相應的一組接口信號的interface,monitor和driver的BFM,以及一個package,其中包含組成整個agent組件的各種類。agent本身是一個包含sequencer、driver和monitor的容器,也包含其他相關的驗證組件,比如functional coverage collector 以及scoreboard(一般不會放置在agent內)。代理只是提供與“普通”類的對象相同API的類的對象。driver代理和monitor代理以尋常的方式與UVM testbench的其余部分通信,同時還通過虛接口句柄分別訪問driver和monitor的BFM接口。因此,一個完整的monitor是由monitor代理服務和monitor BFM成對工作組成的,對driver來說也一樣。agent還有一個連接到monitor的analysis port 的analysis port ,用戶不需要知道agent的內部結構,便可以將外部分析組件連接到agent。agent是testbench上最低級的層次結構塊,其確切結構取決於其配置,每個agent的配置(config object)可能因test而異。這些類和接口共同構成一個可移植可重用的agent。
這也就是說agent是與DUT的一組接口強相關的,一般對應的是DUT的一類功能或是協議接口。顯然,對於有相同接口的DUT,agent是可重用的。關於代理器和相應BFM的更多內容可參考https://verificationacademy.com/patterns-library/implementation-patterns/environment-patterns/bfm-proxy-pair-pattern
接下來我們研究一下APB agent是如何組成、配置、構建和連接的。APB agent的pin接口apb_if編寫在apb_if.sv文件中。命名為apb_monitor_bfm的monitor BFM接口有一個apb_if端口。同樣地,命名為apb_driver_bfm的driver BFM接口也有一個apb_if端口。BFM定義了與apb_if引腳接口中信號交互的任務和函數。driver和monitor代理不直接訪問BFM局域的pin。文件apb_agent_pkg.sv包含package和APB agent的各種類文件。任何使用這個package中文件的組件,比如env,都要import這個package。
package apb_agent_pkg;
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "config_macro.svh"
`include "apb_seq_item.svh"
`include "apb_agent_config.svh"
`include "apb_driver.svh"
`include "apb_coverage_monitor.svh"
`include "apb_monitor.svh"
typedef uvm_sequencer#(apb_seq_item) apb_sequencer;
`include "apb_agent.svh"
//Reg Adapter for UVM Register Model
`include "reg2apb_adapter.svh"
// Utility Sequences
`include "apb_seq.svh"
`include "apb_read_seq.svh"
`include "apb_write_seq.svh"
endpackage: apb_agent_pkg
注意, 這里的apb_sequencer 類型實際上是一個“typedef”,它使用“sequence_item”類型簡單地參數化了“uvm_sequencer”基礎組件。
Agent的config object
agent的config object定義了:
- agent的子組件的拓撲結構(確定要構造哪些子組件)
- driver和monitor代理使用的BFM虛接口的句柄
- agent的行為
按照約定,UVM agent config類有一個類型為uvm_active_passive_enum的枚舉類型變量,該變量定義了agent是構造了sequencer和driver的UVM_ACTIVE還是二者均未構造的UVM_PASSIVE。這個參數稱為active,默認值為UVM_ACTIVE。
而是否構建其他子組件則應該由附加的配置屬性來控制,這些屬性應該具有描述性名稱。例如,如果有一個 functional coverage collector,那么應該有一些控制位來控制是否構建它,比如適當地將其命名為has_functional_coverage。
config object包含driver和monitor使用的BFM虛接口句柄。config object是在test中構造和配置的,在這個頂層,把從testbench module傳入的虛接口賦值給虛接口句柄。
agent config object還可以包含其他數據成員,來控制agent的配置方式或行為。例如,APB agent的config object具有數據成員,用於設置內存映射並確定通過關聯地址映射拉高的APB PSEL信號。
config類應該將所有配置數據成員默認為通用值。
下面的代碼示例展示了APB agent的config object。
//
// Class Description:
//
//
class apb_agent_config extends uvm_object;
// UVM Factory Registration Macro
//
`uvm_object_utils(apb_agent_config)
// BFM Virtual Interfaces
virtual apb_monitor_bfm mon_bfm;
virtual apb_driver_bfm drv_bfm;
//------------------------------------------
// Data Members
//------------------------------------------
// Is the agent active or passive
uvm_active_passive_enum active = UVM_ACTIVE;
// Include the APB functional coverage collector
bit has_functional_coverage = 0;
// Include the APB RAM based scoreboard
bit has_scoreboard = 0;
//
// Address decode for the select lines:
// Address decode for the select lines:
int no_select_lines = 1;
int apb_index = 0;
// Which PSEL is the monitor connected to
logic[31:0] start_address[15:0];
logic[31:0] range[15:0];
//------------------------------------------
// Methods
//------------------------------------------
// Standard UVM Methods:
extern function new(string name = "apb_agent_config");
endclass: apb_agent_config
function apb_agent_config::new(string name = "apb_agent_config");
super.new(name);
endfunction
Agent的Build Phase
在agent的build phase發生的操作由其config object的內容決定。第一個操作是獲取config object的句柄,然后,當要構造子組件時,相關配置字段的值來決定是否構造。
該規則的例外是monitor代理和monitor BFM,它們總是存在的,因為不管agent是active還是passive,都會使用它們。driver僅在agent處於active模式時構造。driver BFM則需要額外的思考,根據重用目標可以采取如下不同的操作。
Driver BFM 實例化
作為interface,driver BFM是在構造硬件部分代碼時實例化和創建的。因此為了防止信號在RTL及testbench的其余部分准備就緒之前被驅動或采樣,BFM狀態機通常不應該自啟動。相反,應該使用來自相應代理的啟動指示作為BFM開始其活動的trigger。這就允許BFM被同步到標准的UVM Phasing機制中,並且如果agent被配置為passive,driver還會保持靜默。除了保持靜默,driver在passive模式下也不能驅動信號。這意味着要么driver必須使所有輸出默認處於三態('hz)模式,要么driver在passive模式下不能被實例化。
要決定是否實例化driver,需要考慮所需的agent重用的級別。對於簡單的block-to-top重用(RTL代碼代替了driver的功能),driver在任何情況下都不需要,也不應該被實例化。如果driver BFM還可能需要驅動信號,那么必須實例化,以及使用三態信號值。要控制實例化,參數可以與生成語句一起使用。
Agent的Connect Phase
構建了agent的子組件后,就需要將它們連接起來。通常需要的agent連接有:
-
monitor的analysis port到agent的analysis port
-
sequencer的seq_item_pull_export到driver的seq_item_pull_port (僅對active agent而言)
-
分析子組件的analysis export到monitor的analysis port(存在分析子組件的地方)
-
monitor/driver的代理虛接口到configobject的monitor/driver的BFM虛接口
注意:
-
agent的analysis port句柄可以直接由monitor的analysis port賦值。這樣就不必在agent中再例化單獨的analysis port對象。(句柄傳遞,指向同一個analysis port對象)
-
在agent中給driver代理器和monitor代理器虛接口賦值移除了這些子組件具有配置表查找開銷的需要。
第二點這段話的意思是說在agent中賦值driver和monitor的虛接口,就不需要在這兩個子組件中通過config_db的方式去get了。
APB agent的以下代碼說明了config object如何確定在build phase和connect phase進行的工作:
//
// Class Description:
//
//
class apb_agent extends uvm_component;
// UVM Factory Registration Macro
//
`uvm_component_utils(apb_agent)
//------------------------------------------
// Data Members
//------------------------------------------
apb_agent_config m_cfg;
//------------------------------------------
// Component Members
//------------------------------------------
uvm_analysis_port #(apb_seq_item) ap;
apb_monitor m_monitor;
apb_sequencer m_sequencer;
apb_driver m_driver;
apb_coverage_monitor m_fcov_monitor;
//------------------------------------------
// Methods
//------------------------------------------
// Standard UVM Methods:
extern function new(string name = "apb_agent", uvm_component parent = null);
extern function void build_phase( uvm_phase phase );
extern function void connect_phase( uvm_phase phase );
endclass: apb_agent
function apb_agent::new(string name = "apb_agent", uvm_component parent = null);
super.new(name, parent);
endfunction
function void apb_agent::build_phase( uvm_phase phase );
if (m_cfg == null)
if( !uvm_config_db #( apb_agent_config )::get(this, "",
"apb_agent_config",m_cfg) )
`uvm_fatal(...)
// Monitor is always present
m_monitor = apb_monitor::type_id::create("m_monitor", this);
// Only build the driver and sequencer if active
if(m_cfg.active == UVM_ACTIVE) begin
m_driver = apb_driver::type_id::create("m_driver", this);
m_sequencer = apb_sequencer::type_id::create("m_sequencer", this);
end
if(m_cfg.has_functional_coverage) begin
m_fcov_monitor = apb_coverage_monitor::type_id::create("m_fcov_monitor", this);
end
endfunction: build_phase
function void apb_agent::connect_phase(uvm_phase phase);
ap = m_monitor.ap;
// Only connect the driver and the sequencer if active
if(m_cfg.active == UVM_ACTIVE) begin
m_driver.seq_item_port.connect(m_sequencer.seq_item_export);
end
if(m_cfg.has_functional_coverage) begin
m_monitor.ap.connect(m_fcov_monitor.analysis_export);
end
endfunction: connect_phase
UVM Sequences
sequence概述
傳統的Verilog或VHDL編寫的testbench很難支持有約束的隨機激勵,test case通常都是'hard-wired' ,很難更改並運行一個新的test case,一般來說是要重新編譯的。而UVM Sequences則帶來了一種面向對象的激勵生成的方式,十分靈活,為生成激勵提供了新的選擇。
sequence是一個被用作方法的對象。UVM sequence包含一個叫做body的task。當使用sequence時,實例化它,之后執行body方法,然后就可以丟棄該sequence。與uvm_component不同,sequence具有有限的仿真生命周期,因此可以稱為瞬態對象。sequence的body方法可以用來實例化和執行其他sequence,或者用來生成sequence_item對象(這些對象通過sequencer發送到driver,用於驅動DUT。),當然也可以同時包含這兩種使用方式。sequence_item對象也是瞬態對象,它們包含了driver與DUT進行交互所需要的信息。當DUT生成響應時,driver使用sequence_item將響應信息通過sequencer回傳給原sequence。實例化和執行其他sequence實際上與能夠調用常規子線程相同,因此可以通過將簡單sequence鏈接在一起來構建復雜的功能。
在類的繼承關系方面,uvm_sequence繼承於uvm_sequence_item, uvm_sequence_item繼承於uvm_object。這兩個基類都被稱為對象而並非組件。UVM testbench組件層次結構是由具有不同屬性的uvm_component構建的,這些組件主要是在實例化時將它們綁定到靜態組件層次結構中,並且組件層次結構在仿真的整個生命周期中都保持不變。
上文注釋中也提到過,uvm_sequence_item實際上是派生於uvm_transaction的,而uvm_transaction才是直接由uvm_object派生。
sequence是UVM中產生激勵的主要手段。事實上,sequence和sequence_items都是對象,這意味着它們可以很容易地隨機化來產生有趣的激勵。其面向對象的特性也意味着可以像其他任何對象一樣被操作。UVM架構也使這些類與testbench組件層次結構分開,優點是通過調用和執行來自庫package的不同sequence組合,可以輕松定義新的test case,而不會被鎖定到組件范圍內可用的方法。但缺點是,sequence不能直接訪問在組件層次結構中可用的testbench資源,比如配置信息,或是寄存器模型的句柄。故sequence使用sequencer作為進入組件層次結構的媒介來對testbench資源進行訪問。
在UVM sequence體系結構中,sequence負責激勵產生流程,並通過sequencer將sequence_items發送給driver。driver負責將sequence_items中包含的信息轉換為pin級活動。sequencer是實現通信通道和仲裁機制的中間組件,來促進sequence和driver之間的交互。數據流是雙向的,請求項通常會從序列路由到驅動程序,而響應項將從驅動程序返回到序列。通信接口的sequencer端在connect phase與driver端連接。
UVM Sequence Items
sequence中的UVM激勵生成程序通過生成 sequence_items 並經由sequencer將它們發送給driver來控制driver的行為。 激勵生成的框架是圍繞sequence結構構建的,但是數據的生成則是使用sequence_items作為數據對象。 由於 sequence_items 是構建sequence的基礎,因此在設計時需要注意一些問題。 sequence_item內容由driver驅動DUT所需的信息決定; 要易於生成新的數據對象內容,通常是通過支持隨機約束生成;以及其他因素如analysis hooks。
數據屬性成員
sequence_item的內容與driver的需求密切相關。driver依賴它接收到的sequence_items的內容來確定要執行哪種類型的驅動。sequence items屬性成員將由表示以下信息類型的數據域組成:
- control eg:傳輸類型,傳輸size
- payload eg:傳輸的主要數據內容
- configuration eg:建立新的操作模式,error行為等
- analysis eg:有助於分析的域——時間戳、rolling checksums等
隨機考慮因素
sequence_items在sequence中隨機化,以生成數據流。因此,激勵的數據屬性通常應該聲明為rand,並且sequence_item應該包含任何需要的約束,以確保默認生成的值是合法的,或者在合理的范圍內。在sequence中,sequence_items通常使用內嵌約束進行隨機化,內嵌約束便於對這些基本約束做擴展。
由於sequence_items用於驅動和響應DUT,所以我們最好約定驅動DUT的屬性應該是rand,響應DUT的屬性不是rand。這將隨機化過程進行優化,來確保所收集到的響應信息不會被可能發生的隨機化破壞。例如,考慮以下總線協議sequence_item:
class bus_seq_item extends uvm_sequence_item;
// Request data properties are rand
rand logic[31:0] addr;
rand logic[31:0] write_data;
rand bit read_not_write;
rand int delay;
// Response data properties are NOT rand
bit error;
logic[31:0] read_data;
`uvm_object_utils(bus_seq_item)
function new(string name = "bus_seq_item");
super.new(name);
endfunction
// Delay between bus cycles is in a sensible range
constraint at_least_1 { delay inside {[1:20]};}
// 32 bit aligned transfers
constraint align_32 {addr[1:0] == 0;}
// etc
endclass: bus_seq_item
Sequence Item方法
uvm_sequence_item通過uvm_transaction類繼承了uvm_object。uvm_object有許多虛方法,用於實現常見的數據對象函數(copy、clone、compare、print、transaction recording),應該實現這些方法以使sequence_item更具通用性。sequence_item經常用於analysis traffic ,它可能有助於添加實用函數來幫助實現功能覆蓋率或analysis。
UVM 配置數據庫 (uvm_config_db)
uvm_config_db類是訪問資源數據庫的推薦方式。資源是兩個或多個組件/對象之間共享的任何信息片段。使用uvm_config_db::set將信息放入數據庫,使用uvm_config_db::get從數據庫檢索信息。uvm_config_db類是一個參數化類,因此這就好像它被划分為許多特定類型的“迷你數據庫”。參數類型沒有限制,可以是類、uvm_object、bit、byte或虛接口等內置類型。
uvm_config_db有兩種典型用法。第一個是將虛接口從HDL/DUT域傳遞給環境,第二個是通過testbench層次結構向下傳遞配置對象。
這兩種用法基本就是最常用的兩種,config_db機制放置資源的位置是全局的,只要通過set,get方法匹配數據類型和資源名就可以完成信息傳遞。
set方法
set方法的完整簽名為void uvm_config_db #( type T = int )::set( uvm_component cntxt , string
inst_name , string field_name , T value );
- T是被添加的資源或元素的類型——通常是虛接口或配置對象。
- cntxt和inst_name一起形成一個作用域,用於在數據庫中定位資源;它是通過將實例名附加到特定組件節點后的完整層次名稱而形成的,即{cntxt.get_full_name(),".",inst_name}。
- field_name是給資源的名稱。
- value是放入數據庫的實際值或引用。
如下示例,是將虛接口放入UVM配置數據庫:
interface ahb_if data_port_if( clk , reset );
interface ahb_if control_port_if( clk , reset );
...
uvm_config_db #( virtual ahb_if )::set( null , "uvm_test_top" , "data_port" , data_port_if );
uvm_config_db #( virtual ahb_if )::set( null , "uvm_test_top" ,"control_port" , control_port_if );
這段代碼將兩個AHB接口放入配置數據庫的層次位置“uvm_test_top”,這是由run_test()創建的頂層test組件的默認位置。這兩個不同的接口在數據庫中使用兩個不同的域名“data_port”和“control_port”。
注意:
- 使用“uvm_test_top”,因為它比“*”更精確,因此更有效; 通常沒什么問題,除非你的頂層test組件在其構造函數中執行除 super.new( name , parent ) 之外的其他操作,在這種情況下,就必須相應地調整實例名稱。
- 第一個參數中使用"null",因為這段代碼是在頂層module中而不是在uvm_component中。
- 使用參數化的uvm_config_db::set()方法,而不使用在UVM1.2中棄用的set_config_[int,string,object]()方法。更多細節請參見UVM1.2摘要。
下面是在env中配置agent的示例:
class env extends uvm_env;
ahb_agent_config m_ahb_agent_config;
function void build_phase( uvm_phase phase );
...
m_ahb_agent = ahb_agent::type_id::create("m_ahb_agent" , this );
...
uvm_config_db #( ahb_agent_config )::set( this , "m_ahb_agent*" , "ahb_agent_config" , m_ahb_agent_config );
...
endfunction
endclass
這段代碼設置AHB agent及其所有子組件的配置。有兩點需要注意:
- 使用"this"作為第一個參數,以確保只設置了這個agent的配置,而沒有設置組件層次結構中任何其他ahb_agent的配置。
- 使用“m_ahb_agent*”確保agent及其子組件都在查找范圍內。如果沒有'*',只有agent本身,它的driver、sequencer和monitor子組件將無法訪問配置。
這里的原理是這樣的,get配置生效的前提是第一個組件節點的字符串與第二個參數中的字符串用‘ . ’拼接后與set中設置的相同。這里使用了通配符使得driver、monitor、sequencer中使用(以driver為例)get( this , " " , "ahb_agent_config" , m_ahb_agent_config_drv );就可以直接匹配成功。其實就算這里只set到了m_ahb_agent,在driver中照樣可以使用get( null , "uvm_test_top.env.m_ahb_agent" , "ahb_agent_config" , m_ahb_agent_config_drv );來獲取成功。原因就是,config_db機制是全局的,實際上它依賴的只是第一和第二個參數組成的字符串能否匹配成功而已。而UVM中做這樣的設定,UVM cookbook中這樣引導,都是為了讓使用者對自己set的目標有一個清晰的把握,避免同類型組件誤用了並非自己的config_db的配置。
get方法
get方法的完整簽名是bit uvm_config_db #( type T = int )::get( uvm_component cntxt , string
inst_name , string field_name , ref T value );
- T是被添加的資源或元素的類型——通常是虛接口或配置對象。
- cntxt和inst_name一起形成一個作用域,用於在數據庫中定位資源;它是通過將實例名附加到特定組件節點后的完整層次名稱而形成的,即{cntxt.get_full_name(),".",inst_name}。
- field_name是給資源的名稱。
- value保存從數據庫檢索到的實際值或引用;如果檢索成功,get()調用返回1,否則返回0來表示數據庫中不存在這種類型且具有這種作用域名稱的資源。
從配置數據庫中獲取虛接口的示例如下:
class test extends uvm_test;
...
function void build_phase( uvm_phase phase );
...
if( !uvm_config_db #( virtual ahb_if )::get( this , "" , "data_port" ,
m_cfg.m_data_port_config.m_ahb_if ) ) begin
`uvm_error("Config Error", "uvm_config_db #( virtual ahb_if )::get cannot find resource data_port" ) )
end
...
endfunction
...
endclass
上面的代碼試圖獲取AHB數據端口的虛接口,並將其分配到正確的agent的配置對象中。當數據庫查找失敗時,將提供一個有意義的錯誤消息。
下面是一個為事務器檢索配置的示例:
class ahb_monitor extends uvm_monitor;
ahb_agent_config m_cfg;
function void build_phase( uvm_phase phase );
...
if( !uvm_config_db #( ahb_agent_config )::get( this , "" , "ahb_agent_config" , m_cfg ) ) begin
`uvm_error("Config Error" , "uvm_config_db #( ahb_agent_config )::get cannot find resource ahb_agent_config" )
end
...
endfunction
endclass
還有幾點需要注意:
- 使用“this”作為context參數。
- 使用""作為inst_name。
- 使用get()調用的返回值來檢查是否獲取失敗,並給出有用的錯誤消息。
- 使用參數化的uvm_config_db::get()方法,而不是在UVM1.2中棄用的get_config_[int,string,object]()方法。更多細節請參見UVM1.2摘要。
優先級規則
兩組優先規則適用於uvm_config_db。首先,在build_phase,組件層次結構上層的context中的set()調用優先於層次結構下層的set()調用。其次,在相同的context中或在build_phase之后,最后的set()調用優先於前一個。有關這些規則的更多細節,可以參閱UVM參考手冊或直接查看UVM源代碼。
Using Packages
package是一種SystemVerilog語言構造,它支持將相關的聲明和定義組合在其名稱空間中。package可以包含類型定義、常量聲明、函數和類模板。要在作用域內使用package,必須導入,然后才能引用package的內容。
SystemVerilog package是組織代碼以及確保對類型、類等的引用一致的有用方法。UVM基類庫包含在一個名為“uvm_pkg”的package中。應該使用package來開發UVM testbench,以收集和組織為實現agent、env、sequence庫、test庫等而開發的各種類定義。
UVM Package編碼指南
命名package和package文件
package應該以_pkg后綴命名。包含package的文件的名稱應該反映package的名稱,並具有.sv后綴名。例如,文件spi_env_pkg.sv將包含package spi_env_pkg。
正當性: .sv擴展名是一種約定,表示package文件是一個獨立的編譯單元。后綴_pkg表示該文件包含一個package。這兩種約定對人和機器解析腳本都很有用。
在package中包含類
在package的作用域內聲明的類模板應該被分隔成具有.svh后綴名的單獨文件。這些文件應該按照需要編譯的順序在包中`include。package文件是唯一應該使用`include的地方,也就是說,在被`include的文件本身中不應該再有`include語句。
正當性:在單獨的文件中聲明類更容易維護,也能更清楚地傳達package的內容。
導入package和package元素
package的內容可以引用由package導入啟用的其他package的內容。這樣的外部package導入應該在package的頂部作為package“主體”的第一個語句聲明。可能包含的類模板等單獨文件不應該單獨導入。
正當性:將所有package導入本地化到一個地方可以使package依賴關系更加清晰。將package導入放在package的其他部分或被`include的文件中是不太可見的,而且更有可能導致排序問題和潛在的類型沖突。
將package文件組織到一個目錄中
一個package中包含的所有文件都應該放在一個目錄中。這對於agent來說尤其重要,因為agent目錄結構需要是一個完整獨立的package。
正當性:一個package文件的包含目錄有助於編譯流程的設置,也有助於重用,這是因為一個package的所有文件都可以很容易地收集在一起。
下面是UVM env的package文件示例。這個env包含兩個agent(SPI和APB)和一個寄存器模型,它們作為sub-package導入。與env相關的類模板是被`include的:
// Note that this code is contained in a file called spi_env_pkg.sv
//
// In Questa it would be compiled using:
// vlog +incdir+$UVM_HOME/src+<path_to_spi_env> <path_to_spi_env>/spi_env_pkg.sv
//
//
// Package Description:
//
package spi_env_pkg;
// Standard UVM import & include:
import uvm_pkg::*;
`include "uvm_macros.svh"
// Any further package imports:
import apb_agent_pkg::*;
import spi_agent_pkg::*;
import spi_register_pkg::*;
// Includes:
`include "spi_env_config.svh"
`include "spi_virtual_sequencer.svh"
`include "spi_env.svh"
endpackage: spi_env_pkg
package范圍
SystemVerilog package定義了一個作用域,這一事實常常讓用戶感到困惑。這意味着在package中聲明的所有內容,以及導入到package中的其他package的元素,都只在該package的范圍內可見。如果一個package被導入到另一個作用域(例如,另一個package或一個模塊),則只有被導入package的元素是可見的,而被導入package本身所導入的任何package的元素都是不可見的。因此,如果在新范圍中需要這些其他package的元素,則需要分別導入它們。
//
// Package Scope Example
// ----------------------------------------------------
//
package spi_test_pkg;
// The UVM package has to be imported, even though it is imported in the spi_env package.
// This is because the import of the uvm_pkg is only visible within the current scope
import uvm_pkg::*;
// The same is true of the `include of the uvm_macros
`include "uvm_macros.svh"
// Import of uvm_pkg inside the spi_env package is not
// visible within the current spi_test_pkg scope
import spi_env_pkg::*;
// Other imports and `includes
`include spi_test_base.svh
endpackage: spi_test_pkg
這也就是說package本身就是一個作用域,而導入外層package中的package只對外層package內可見。關於package的這部分內容,主要是幫助我們組織文件結構的。