天天看點

UVM概述

類庫地圖

SV的核心特性包括面向對象、随機限制、線程通信、功能覆寫率收集等,這些特性也為建立一個驗證環境提供了足夠多的便利。(uvm提供了架構,進而可以更多聚焦于測試用例)

root--test--env

在可以看到對驗證環境的共同需求是:元件的建立和通路、環境的結建構立、元件之間的連接配接和運作、不同階段的順序安排、激勵的生成、傳遞和控制、測試的報告機制

UVM類庫地圖按照UVM的核心機制将地圖進行了分塊:·

  • 核心基類
  • 工廠(factory)類
  • 事務(transaction)和序列(sequence)類
  • 結建構立(structure creation)類
  • 環境元件(environment component)類
  • 通信管道(channel)類
  • 資訊報告(message report)類
  • 寄存器模型(register model)
  • 線程同步(thread synchronization)類
  • 事務接口(transaction interface)類   在環境元件中例化,元件通過端口連接配接通信
UVM概述
  • 由于軟體環境中對象的生成是動态的,驗證環境中的元件也需要UVM提供底層功能來完成對象的建立和通路。
  • 在元件建立之外,UVM也需要提供環境上下層次中建立、連接配接和運作元件的順序控制方法,隻有在底層機制上有效地保證這一點,才會避免可能發生的句柄懸空問題。
  • 在元件通信中,UVM也提供了功能更豐富的TLM (TransactionLevel Model)接口,這可以保證相鄰元件的通信不再通過顯式句柄引用,而是獨立于元件的通信方式。
  • 對于測試序列(sequence)的生成和傳輸也是利用了TLM傳輸在sequence和driver之間完成。而對于不同sequence的發送順序控制,也類似于SV測試MCDF子系統的要求,需要實sequence之間的靈活排程。
  • 為了便于驗證環境的調試,UVM的報告機制可以将來自于不同元件、不同級别的資訊并且加以過濾,最終生成測試報告。

工廠機制

  • UVM工廠的存在就是為了更友善地替換驗證環境中的執行個體或者注冊了的類型,同時工廠的注冊機制也帶來了配置的靈活性。
  • 這裡的執行個體或者類型替代,在UVM中稱作覆寫(override) ,而被用來替換的對象或者類型,應該滿足注冊(registration)和多态(polymorphism)的要求。(實作多态,有二種方式,覆寫,重載。覆寫,是指子類重新定義父類的虛函數的做法。重載,是指允許存在多個同名函數,而這些函數的參數表不同(或許參數個數不同,或許參數類型不同,或許兩者都不同)。)
  • (73條消息) 繼承與多态的差別_l_漫漫的部落格-CSDN部落格_繼承和多态的差別
  • UVM的驗證環境構成可以分為兩部分,一部分構成了環境的層次這部分代碼是通過uvm_component類完成,另外一部分構成了環境的屬性(例如配置)和資料傳輸,這一部分通過uvm_object類完成。
  • 這兩種類的內建關系從UVM類庫地圖可以看到,uvm_component類繼承于uvm_object類,而這兩種類也是進出工廠的主要模具和生産對象。之是以稱為模具,是因為通過注冊,可以利用工廠完成對象建立
  • 而之是以對象由工廠生産,也是利用了工廠生産模具可靈活替代的好處,這使得在不修改原有驗證環境層次和驗證包的同時,實作了對環境内部元件類型或者對象的覆寫。

uvm_{component,object}的例化

  • 每一個uvm_{component, object}在例化的時候都應該給予一個名字(string)。
  • “full name”指的是component所處的完整層次結構。在每個層次中例化的元件名稱,應該獨一無二 (unique) 。建立component或者object的方法如下:

建立uvm_component對象時,

comp_type: :type_id::create(string name,uvm_component parent);  

//type_id:注冊到工廠裡的類型,create表示方法,create背景會調用new。

建立uvm_object對象時,

object type: : type_id: : create (string name) ;

class comp1 extends uvm_component;
    'uvm_component_utils(comp1)
    function new(string name="comp1",uvm_component parent=null);   //範式,參數固定  建構
        super.new(name,parent);
    endfunction:new
endclass
class obj1 extends uvm_object;
    'uvm_object_utils(obj1)
    function new(string name="obj1");
        super.new(name);
    endfunction:new
endclass

comp1 c1,c2;
obj1 o1,o2;
initial begin
    c1=new("c1");
    o1=new("o1");                            //sv中的建立執行個體
    c2=create::type_id::create("c2",null);  
    o2=create::type_id::create("o2");        //uvm中的建立執行個體
end
           
  • c2和o2的例化方式也是最後通過調用new()函數實作的。畢竟對于任何對象的例化,最終都要通過new()建構函數來實作的。
  • 一般來說運用factory的步驟可分為:
  1. 将類注冊到工廠
  2. 在例化前設定覆寫對象和類型(可選的)
  3. 對象建立

在兩種類comp1和obj1的注冊中,分别使用了UVM宏uvm_component_utils和~uvm_object_utilso·什麼是宏(macro呢?

·為什麼需要宏呢?

  • 這兩個宏做的事情就是将類注冊到factory中。在解釋注冊函數之前,我們需要懂得在整個仿真中,factory是獨有的,即有且隻有一個,這保證了所有類的注冊都在一個“機構”中。

uvm_coreservice_t類

  • 該類内置了UVM世界核心的元件和方法,它們主要包括
  1. 唯一的uvm_factory,該元件用來注冊、覆寫和例化
  2. 全局的report_server,該元件用來做消息統籌和報告
  3. 全局的tr_database,該元件用來記錄transaction記錄.
  4. get_root()方法用來傳回目前UVM環境的結構頂層對象(root(uvm)而不是test(sv))
  • 而在UVM-1.2中,明顯的變化是通過uvm_coreservice_t将最重要的機制(也是必須做統一例化處理的元件)都放置在了uvm_coreserice_t類中。
  • 該類并不是uvm_component或者uvm_object,它也并沒有例化在UVM環境中,而是獨立于UVM環境之外的。
  • uvm_coreservice_ t隻會被UVM系統在仿真開始時例化一次。使用者無需,也不應該自行再額外例化該核心服務元件。該類裡的4個類型都不是component或者object,比如factory,就是用來例化uvm_componcent/object的,故不屬于uvm_component/object。
UVM概述
  • 這個核心元件如同一個随時待命的仆人,做好服務的準備。
  • 理論上,使用者可以擷取核心服務類中的任何一-個對象,例如uvm_ _default_ _factory對象, 繼而直接利用factory來實作建立和覆寫。當然,建立和覆寫也可以由其它方式完成。

注冊宏`uvm_{component,object}_utils

  • `uvm_ component_utils用來注冊元件類uvm_component
  • `uvm_ object_utils用來注冊核心基類uvm_object
  • 在宏調用的過程中,實作類型定義typedef uvm_ component_ registry #(T,` "S`") type_ id
  • uvm_factory::register()來注冊type_ id并且得到執行個體。
  • 一旦發生注冊,type_ id:create()函數就可以最終通過uvm_ factory::create_ component_ by_ _type()來實作。
  • 無論對于uvm_ component或者uvm_ object, 在UVM世界中,請養成習慣使用注冊宏uvm_ {component, object}_utils
  • 對于注冊,并不是真正地将一個抽象的類型(空殼)放置在什麼地方,而是通過例化該類的對象來完成。
  • 由于一種類型在通過宏調用時隻注冊一次,那麼在不考慮覆寫的情況下uvm_default_factory就将每一個類對應的對象都放置到了factory的字典當中。
  • uvm_default_factory::create_component_by_type()經過代碼簡化,讀者可以看到關鍵語句,它們首先檢查處在該層次路徑中需要被例化的對象,是否受到了“類型覆寫”或者“執行個體覆寫”的影響,進而将最終類型對應的對象句柄(正确的産品模闆)交給工廠。
  • 有了正确的産品模闆,接下來就可以通過uvm_component_registry::create_component()來完成例化。

注冊後的對象建立(component或者object)

  • 建立對象時,需要結合工廠的注冊和覆寫機制來決定,應該使用哪一個類型來建立
  • uvm_ component和uvm_ object在建立時雖然都需要調用create()函數,但最終建立出來的uvm_ component是會表示在UVM層次結構中的,而uvm_object則不會顯示在層次中。
  • 這一點也可以從uvm_ component::new(name, parent)和uvm_ object::new(name)中看 得出來。
  • uvm_ component::new(name, parent)保留兩個參數,就是為了通過類似‘‘鈎子”的做法,一層層由底層勾住上一層,這樣就能夠将整個UVM結構串接起來了。
  • uvm_object::new(name)則沒有parent參數,是以也不會顯示在UVM層次中,隻能作為configuration或者transaction等用來做傳遞的配置結構體或者抽象資料傳輸的資料結構體,成為uvm_ component的成員變量。

component/object與工廠有關的方法

  • 配合工廠的注冊、建立和覆寫的相關方法(屬于uvm_coponent/object):●create()   ●create_ component()  ●get()  ●get_ type_ name()  ●set_ inst_ _override()  ●set_ type_ override()
  • 每一個uvm_component的類在注冊時,會定義一個新的uvm_component_ registry類,其如同一個外殼,一個包裝模闆的紙箱,在factory中注冊時, 該紙箱中容納的是被注冊類的“圖紙”并沒有一個“執行個體”
  • 除了使用component/object來建立執行個體,也可以利用factory(屬于uvm_factory)來建立:

    ●create_component_by_name ()

    ●create_component_by_type ()

    ●create_object_by_name ()

    ●create_object_by_type ()

    為了避免不必要的麻煩,我們在使用宏`uvm_component_utils和`uvm_object_utils注冊類型時,宏内部就将類型T作為類型名Tname='T'注冊到factory中去。這就使得通過,上面的任何一種方法在建立對象時,不會受困于類型與類型名不同的苦惱。

覆寫方法

工廠提供的便利--覆寫(override)

  • 覆寫機制可以将其原來所屬的類型替換為另外一個新的類型。在覆寫之後,原本用來建立原屬類型的請求,将由工廠來建立新的替換類型。(比如替換vip裡的driver)
  1. 無需再修改原始代碼,繼而保證了原有代碼的封裝性。
  2. 新的替換類型必須與被替換類型相相容,否則稍後的句柄指派将失敗,是以使用繼承。
  • 做頂層修改時,非常友善 !
  1. 允許靈活的配置,例如可使用子類來覆寫原本的父類
  2. 可使用不同的對象來修改其代碼行為
  • 要想實作覆寫特性,原有類型和新類型均需要注冊。
  • 當使用create()來建立對象時:
  1. 工廠會檢查,是否原有類型被覆寫。
  2. 如果是,那麼它會建立一個新類型的對象。
  3. 如果不是,那麼它會建立一個原有類型的對象。
  • 覆寫發生時,可以使用“類型覆寫”或者“執行個體覆寫”
  1. 類型覆寫指,UVM層次結構下的所有原有類型都被覆寫類型所替換。
  2. 執行個體覆寫指,在某些位置中的原有類型會被覆寫類型所替換。

set_inst_override()

static function void set_inst_override (uvm_object_wrapper override_type,string inst_ path,uvm_component parent=null);.

  • string inst_path指向的是元件結構的路徑字元串
  • uvm_component parent=null  如果預設,表示使用inst_path内容為絕對路徑   如果有值傳遞,則使用{parent.get_full_name(), '.', inst_path}來作為目标路徑。(''root.test.env.checker"這種帶雙引号的字元串索引路徑   不帶雙引号的階層化也可以實作)這裡用字元串是告訴執行個體化的名稱是什麼

orig_type::type_id::set_inst_override(new_type::get_type(),"orig_inst _path")

 typedef                        靜态函數              靜态函數

set_type_override()

static function void set_type_override (uvm_object_wrapper override_type, bit replace=1) ;

  • uvm_object_wrapper override_type       這是什麼?并不是某一個具體執行個體的句柄,實際上是注冊過後的某一個類在工廠中注冊時的句柄。怎麼找到它呢?就使用new_typeget_type()。
  •  bit replace=1     1:如果已經有覆寫存在,那麼新的覆寫會替代舊的覆寫。0:如果已經有覆寫存在,那麼該覆寫将不會生效。
  • set_type_override是一個靜态函數(靜态函數意味在任何一個地方都可以調用這個函數)     參照此覆寫方式

orig_type::type_id::set_type_override (new_type::get_type())

{     typedef          } {    靜态函數        } {         靜态函數          }

這裡用的type_id也是為了調用另一個靜态函數set_type_override,之前是為了調用create建立對象

如何使用覆寫相關函數

  • 首先需要知道,有不止一個類提供與覆寫有關的函數,然而名稱與參數清單可能各不相同:
  1. uvmcomponent: :set_{ type, inst}_override{_by_type}
  2. uvm component _registry : :set_{ type, inst}_override
  3. uvm_object _registry : :set_{type, inst}_override
  4. uvm_factory: :set_{ type, inst}__override
  • 是以,想要實作類型替換,也有不止一種方式。包括上述給的例子中通過orig type : :type_ id來調用覆寫函數,還可以在uvm_component的域中直接調用,或者使用uvm_factory來做覆寫。
module factory_override
    import uvm_pkg::*;   //vcs裡需要先編譯,Questa則已經編譯到庫裡
    'include "uvm_macros.svh"
    class comp1 extends uvm_component;
        'uvm_component_utils(comp1)
        function new(string name="comp1",uvm_component parent=null);
          super.new(name,parent);
          $display($formatf("comp1::%s is created",name));
        endfunction:new
        virutal function void hello(string name);  //方法繼承或覆寫記得加virtua
          $display($formatf("comp1::%s said hello!",name));
        endfunction
    endclass
    class comp2 extends comp1;   //需要繼承
        'uvm_component_utils(comp2)
        function new(string name="comp2",uvm_component parent=null);
          super.new(name,parent);
          $display($formatf("comp2::%s is created",name));
        function void hello(string name);
          $display($formatf("comp2::%s said hello!",name));
        endfunction
    endclass
comp1 c1,c2;
initial begin
    comp1::type_id::set_type_override(comp2::get_type());
    c1=new("c1");
    c2=comp1::type_id::create("c2",null);  //傳回子類句柄,之後指派給父類句柄,這也是為什麼要
    c1.hello("c1");                        //有繼承關系
    c2.hello("c2");
    end
endmodule
輸出結果:
comp1::c1 is created
comp1::c2 is created
comp2::c2 is created  //這裡的new調用是預設的嗎?先comp1進行例化,由于覆寫會自動執行個體化comp2?
comp1::c1 said hello!    //使用new建立對象,沒有進行覆寫
comp2::c2 saie hello!   //隻有使用工廠建立對象的才進行了覆寫  父類有virtual,會調用子類方法
           

覆寫執行個體

  • comp2覆寫了comp1類型comp1: :type_id: :set_type_override (comp2: :get_type());
  • 緊接着對c1和c2對象進行了建立,可以從輸出結果看到,cl的所屬類型仍然是compl,c2的所屬類型則變為了comp2。這說明了factory的覆寫機制隻會影響通過factory注冊并且建立的對象。
  • 是以通過type_id::create()和factory的類型覆寫可以實作對象所屬類型在例化時的靈活替換。
  • 在例化c2之前,首先應該用comp2來替換compl的類型。隻有先完成了類型替換,才可以在後面的例化時由factory選擇正确的類型。
  • 在後面發生了類型替換以後,如果原有的代碼不做更新,那麼c2句柄的類型仍然為comp1,但卻指向了comp2類型的對象。這就要求,comp2應該是comp1的子類,隻有這樣,句柄指向才是安全合法的。
  • c2在調用hello()方法時,由于首先是comp1類型,那麼會檢視compl::hello(),又由于該方法在定義時被指定為虛函數,這就通過了多态性的方法調用,轉而調用了comp2::hello()函數。是以,顯示的結果也是“comp2::c2 said hello!”。

確定正确覆寫的代碼要求

  • 将UVM環境中所有的類都注冊到工廠中,并通過工廠來建立對象。
  • 在使用某些類的時候,確定該類已經被導入(import)到目前域(scope)中。
  • 通過工廠建立對象時,句柄名稱應該同傳遞到create()方法中的字元串名稱相同。無論是通過層次路徑名稱來覆寫還是配置,将例化元件的句柄名稱同建立時create()方法中的字元串名稱保持一緻。
  • 由于覆寫是采用parent wins模式(層次越高,配置的優先級越高,比如test中的override優先級高于env中的override),是以要注意在同一個頂層build_phase()中覆寫方法應發生在對象建立之前。
  • 為了盡量保證運作時覆寫類可以替換原始類,覆寫類最好是原始類的子類,而調用成員方法也應當聲明為虛方法。
  • 另外—種確定運作時覆寫類型句柄正确使用的方式,需要通過$cast()講行動态類型轉換。

總結

将浏覽完UVM工廠後,你有沒有覺得它就像一個大大的樂高世界。

一旦把那些元件裝載盒子注冊之後,接下來的UVM環境搭建就變得更加容易、更友善日後維護了。

整個UVM世界的建構,離不開factory的三個核心要素:注冊、建立和覆寫。

  • `uvm_{component, object}_utils
  • uvm_{component, object}type_id:create().
  • set_{type, inst}_override{,_by_type}()

UVM學習中的一大阻礙就是,實作某—種效果的方法有很多種,但是對于初學者,你隻需要掌握最常用的一種實作方式,就足夠了!因為我們最終需要掌握UVM世界的全貌,而不是研究全部的用法,畢竟我們時間非常有限!

uvm