SV & UVM中的面向對象


1 SV重載機制
1.1 類的封裝

一般而言,類里的變量/方法有兩種被訪問的方式:(1),在定義類時,在類的內部直接使用變量/方法;(2),當類已經被例化后,通過class_inst.num或者class_inst.method()的方法使用類的變量和方法。因此確定變量/方法的訪問權限很有必要。

類的封裝特性使得類可以根據需要來確定外部訪問的權限級別,一般可以將變量聲明為以下三種形式:

public:       子類和外部均可以訪問(默認);

protected: 只有當前類或者子類可以訪問,外部無法訪問;

local:            只有當前類可以訪問,子類和外部均無法訪問;

舉例:默認情況下,通過class_inst.num就可以訪問class_inst實例的下屬變量num,因此默認的變量申明都是public;但是如果num被聲明成local類型,那么只有class_inst的內部方法可以訪問num,其子類不能以繼承的方式訪問它,也不可以通過class_inst.num這種外部方式訪問它,否則VCS工具會報錯:Could not find member 'num' in class 'xxx'。

1.2 類的繼承
類的繼承特性使得子類可以使用父類的成員變量(variable)或方法(method),當我們子類實例上調用某一個變量或者方法時,分兩種情況說明:

(1): 子類中沒有同名的變量或方法,那么編譯器會去父類中查找該變量或方法並且使用它。也就是說:在子類中可以直接使用父類的變量和方法。

(2): 子類中有同名的變量或方法,那么在子類中通過super.value或者super.method的方法才能使用父類的變量或方法。(如果不使用super., 會默認使用子類中的變量或方法,那么相當於父類中的變量或方法會被子類覆蓋掉)

什么是成員覆蓋:               

覆蓋的前提是父類和子類中所關心的變量或方法名相同。如果子類沒有使用super.來強調同名的變量或方法是父類的,那么父類中的同名變量或方法會被子類覆蓋。當然也可以通過this.來強調同名的變量或方法是子類的。

               當引用某一變量或者方法時,如果沒有用.super或者.this的方式指明作用域,則會根據由近到遠的原則查找該變量和方法:

(a): 查看變量/方法是否是函數內部定義的變量/方法;

(b): 查看變量/方法是否是當前類定義的變量/方法;

(c): 查看變量/方法是否是父類定義的變量/方法;

除此之外,構造函數(new)的繼承需要額外說明:

如果一個類沒有定義new函數,那么默認的new函數會被自動定義;子類在定義new函數時,首先調用父類的new函數即super.new(...);那么在調用子類new函數創建對象時,遵循如下順序:

(1):首先調用父類的new()函數,將父類變量初始化;

(2):然后執行父類new()函數中自定義的代碼;

(3):接着將子類變量初始化:按定義時顯示的默認值初始化,無默認值則不被初始化;

(4):最后執行子類new()函數中自定義的代碼;

1.3 類的多態

1.3.1 虛方法

虛方法是多態的主要特征。關於虛方法,簡而言之就是用父類句柄指向子類對象,然后通過父類的句柄調用實際子類的成員方法。
前提:父類和子類中有同名的method;

場景:"父類句柄指向子類對象"的常見場景如下:

(a): 在子類中調用一些上層的方法時,往往會有參數傳遞,參數傳遞時容易發生隱式轉換,即將子類對象的句柄賦給父類句柄,從而使得父類句柄指向了子類對象;

(b): 在構建上層框架(比如方法學)時,為了通用性,很多句柄都是用父類對象聲明;當用戶使用上層框架中的方法時,很容易將自己創建的子類對象的句柄賦給上層框架中的父類句柄,從而使得父類句柄指向了子類對象;

如果父類和子類中有同名的method,且父類中的method聲明為virtual,那么當通過"指向子類對象的父類句柄"調用method時,SV的虛方法重載機制會檢測到句柄指向的對象是子類,從而只調用子類中的task/function。

缺點:一旦在父類中定義了某虛方法,那么在子類中實現此方法時,要求子類方法和父類虛方法的函數名,參數名都要一樣,而且不能增刪參數,否則多態就不適用了;因此這要求在構建父類虛方法時必須提前做好計划,全面考慮子類實現此方法時的不同情況。

1.3.2 句柄轉換

盡管通過虛方法的聲明使得"在指定子類對象的父類句柄上調用子類方法時,可以正確地找到該方法”,但是,"指向子類對象的父類句柄"仍然有一些變量和方法無法通過虛方法來解決索引問題:

(a): 父類沒有定義,只有在子類中定義了的方法;      (虛方法只能解決父類和子類同時存在method的問題)

(b): 父類沒有聲明,只有在子類中聲明了的變量;      (虛方法只針對method)

(c): 父類和子類同時聲明了的變量;            (虛方法只針對method)

通過$cast(target, source)方法解決上面的三個問題。$cast(target, source)執行成功的前提是:"source指向的對象"和句柄target是同一類型,或者"source指向的對象"是句柄target的擴展類。

因此source一般是"指向子類對象的父類句柄",target一般是"用子類聲明的句柄",完成轉換后,就可以通過target句柄來訪問子類的變量或方法。

示例1如下:

class axi_sbx_item extends uvm_object;
    // define variable
    typedef axi_sbx_item this_type_t;
    this_type_t original_item;
    this_type_t split_item;

    function void split();
        if(!$cast(this.split_item, this.clone())) `uvm_error("split", "cast failed!")
        ......
        this.split_item.original_item = this;
    endfunction

endclass

 this指axi_sbx_item類創建的對象;由於clone()是uvm的方法,uvm事先不知道axi_sbx_item的存在,因此this.clone()必定返回"指向子類對象的父類句柄",而this.split_item是用子類聲明的句柄,執行$cast(this.split_item, this.clone())后,this.split_item就指向了clone出的對象;

1.3.3 一個類的多態的例子

class base_test;
    int data;
int crc;
    virtual function base_test copy();   // 在父類構造copy()方法: base_test t = new();               // (1): 首先創建父類對象,由於在父類中實現,因此該對象類型為base_test; copy(t);   // (2): 然后復制變量,由於在(1)中創建的對象為base_test型,所以copy_data()方法的參數類型也是base_test; return t; endfucntion
    virtual function void copy_data(base_test t);   // copy_data()用來復制父類變量;
        t.data = data;
t.crc = crc;
endfunction endclass ------------------------------------------------------------------------------- class my_test extends base_test; int id;
    function base_test copy();        // 為了實現虛方法重載,子類方法/參數的名字和類型要與父類保持一致,因此被迫聲明為base_test類型;
        my_test t = new(); // (1):首先創建子類對象,在創建子類對象時,會自動調用super.new()方法; copy_data(t); // (2): return t; endfucntion
function void copy_data(base_test t);      // 為了實現虛方法重載,子類方法/參數的名字和類型要與父類保持一致,因此被迫聲明為base_test類型;
        my_test h;
super.copy_data(t); //
$cast(h, t);
h.id = id;
endfunction

endclass
-------------------------------------------------------------------------------
module tb;
my_test m_t1;
my_test m_t2;
initial begin
m_t1 = new();
$cast(m_t2, m_t1.copy());
......
end
endmodule

 相比於UVM中通過Field Automation提供了比較簡單的對象復制,SV中需要我們人工定義copy()方法來實現對象復制,上面的例子就是SV中用於復制對象的代碼;

(1):將成員復制函數copy_data()和新對象生成函數copy()分為兩個方法,便於子類的繼承和方法的復用;

(2):從自頂向下構建copy()/copy_data()方法的角度來看,首先我們在父類中定義copy()/copy_data()方法,實現父類新對象的生成以及父類成員變量的復制。但是為什么父類的copy()/copy_data()方法要聲明為virtual呢?如我們在【1.3.1 虛方法】中所述:如果想在"指向子類對象的父類句柄"上調用copy()/copy_data()方法來復制對象,必須要將父類的copy()/copy_data()方法聲明為virtual,否則子類的變量無法復制;

(3):一旦將父類的copy()/copy_data()方法聲明成virtual,麻煩也就接踵而至,因為虛方法要求子類方法/參數的名字和類型必須與父類一致,因此子類方法的類型也必須聲明為base_test;那么子類在調用copy()方法時,返回一個"指向子類對象的父類句柄",必須通過$cast()方法實現句柄轉換。

 

2 UVM重載機制

2.0 factory的基礎知識

UVM的重載依賴於factory,factory的意義:在uvm中之所以用factory機制創建對象,主要是利用factory提供的重載機制,使得在不修改原有驗證環境層次和驗證事務的同時,實現對環境內的component或object的覆蓋。

UVM中有一個獨立於uvm繼承關系的類:uvm_coreservice_t,該類內置了UVM世界核心的組件和方法,主要包括:

(1): 唯一的uvm_factory實例(singleton mode),用來注冊,覆蓋和例化;   

(2): 全局的report_server;

(3): 全局的tr_database;

(4): get_root()方法用來返回當前UVM環境的結構頂層對象;

ucli模式下,在verdi的Object窗口中可以看到uvm_coreservice_t的實例,如下:

 這里只分析關於uvm_factory的代碼。在src/base/uvm_factory.svh中,定義了兩個class,如下:

virtual class uvm_factory;
    ......
endclass

class uvm_default_factory extends uvm_factory;            // 稍后的代碼中,會用到父類句柄指向子類對象,通過virtual function實現重載
    ......
endclass

uvm_factory只是一個抽象類,聲明了許多pure virtual method,但沒有具體實現;UVM默認使用uvm_default_factory為全局提供服務,通過句柄factory可以訪問它

class uvm_default_factory提供的方法有很多,這里先分析主要的兩個:

(1): register()方法

function void uvm_default_factory::register(uvm_object_wrapper obj);
    if (obj == null) begin
        uvm_report_fatal ("NULLWR", "Attempting to register a null object with the factory", UVM_NONE);
    end
    if (obj.get_type_name() != "" && obj.get_type_name() != "<unknown>") begin
        if (m_type_names.exists(obj.get_type_name()))
            ......
        else 
            m_type_names[obj.get_type_name()] = obj;
        end

    if (m_types.exists(obj)) begin
        if (obj.get_type_name() != "" && obj.get_type_name() != "<unknown>") ......;
end
else begin m_types[obj] = 1; if(m_inst_override_name_queues.exists(obj.get_type_name())) begin m_inst_override_queues[obj] = new; m_inst_override_queues[obj].queue = m_inst_override_name_queues[obj.get_type_name()].queue; m_inst_override_name_queues.delete(obj.get_type_name()); end if(m_wildcard_inst_overrides.size()) begin if(! m_inst_override_queues.exists(obj))
m_inst_override_queues[obj]
= new; foreach (m_wildcard_inst_overrides[i]) begin if(uvm_is_match( m_wildcard_inst_overrides[i].orig_type_name, obj.get_type_name())) m_inst_override_queues[obj].queue.push_back(m_wildcard_inst_overrides[i]); end end end endfunction

通過factory.register(...)方法,在該類沒有被注冊過或者覆蓋過時:

將該類例化后的對象句柄(obj)放置到關聯數組m_type_names[string]中,其中鍵為實例對象的句柄字符串,值為實例對象的句柄;     // m_type_names[obj.get_type_name()] = obj

將該類例化后的對象句柄(obj)放置到關聯數據m_types[uvm_object_wrapper]中,其中鍵為實例對象的句柄,值為1;                         // m_types[obj] = 1

因此所謂注冊並不是將需要注冊的類抽象放置在某個地方,而是通過將需要注冊的類例化(this_type me=new())來完成注冊的,由於一種類型(class)在通過宏調用時只注冊一次,那么在不考慮覆蓋的前提下,factory就將每一個類對應的實例對象(me)都放置到關聯數組m_type_names[string]中。

除此之外我們還關心注冊時override相關的操作:

m_inst_override_queues[$]:保存通過方法set_type_override_by_type()添加的信息;

對一個class注冊時,會對內部的m_wildcard_inst_override和m_inst_override_name_queue進行檢查,如果新注冊的類的name在這兩個queue中,會刪除相應的queue,然后添加到m_inst_override_queues[$]中;

(2): create_component_by_type()方法

function uvm_component uvm_default_factory::create_component_by_type (uvm_object_wrapper requested_type,  
                                                                      string parent_inst_path="",  
                                                                      string name, 
                                                                      uvm_component parent);
    string full_inst_path;

    if (parent_inst_path == "")
        full_inst_path = name;
    else if (name != "")
        full_inst_path = {parent_inst_path,".",name};
    else
        full_inst_path = parent_inst_path;

    m_override_info.delete();
    requested_type = find_override_by_type(requested_type, full_inst_path);
    return requested_type.create_component(name, parent);

endfunction

create_component_by_type()方法首先檢查在該路徑中(full_inst_path)需要被例化的對象,是否受到了"類型覆蓋"或"實例覆蓋"的影響,這一步由find_override_by_type實現;然后通過create_component()方法創建對象;分析如下:

(a):find_override_by_type()

這個方法的第一個參數是靜態成員變量me,第二個參數是full_inst_path;這個方法最重要的地方在於:會去uvm_factory_override  m_type_overrides[$]中查找ovrd_type是不是就是要查找的類型,那么m_type_overrides[$]中的信息是什么時候被添加進去的呢?在【2.2 在例化前設置覆蓋對象和類型】中有具體分析,簡而言之就是如果用戶有使用set_type_override_by_type(...)方法,那么original_type和override_type的信息會被放到m_type_overrides[$]中;

在本例中就相當於是看new_example_comp是不是等於example_comp,如果不等於就說明example_comp被覆蓋過,所以會調用find_override_by_type()函數,這相當於是遞歸調用,只是這次調用傳入的第一個參數是專屬於new_example_comp的 uvm_component_registry#(new_example_comp, “new_example_comp”)的靜態成員變量me,第二個參數是字符串example_comp。

這個 find_override_by_type()函數為什么要做成遞歸的方式呢?因為假設系統中有一個 A類,然后使用override方法把A類用B類給override了,而后面又用override方法句把B類用C類給override了;那么此時創建A類的實例,得到的應該是C類的實例。因此只有在find_override_by_type()中遞歸調用,才能完整的實現這一功能。

(b):create_component()

create_component()是class uvm_object_wrapper的方法;

2.1 將類注冊到factory

2.1.1 實現方式

對於component類型,用下面的宏將class example_comp注冊到factory:`uvm_component_utils(example_comp)

對於object類型,        用下面的宏將class example_object注冊到factory:`uvm_object_utils(example_object)

 為了方便,這里僅僅以component為例說明,即分析`uvm_component_utils(example_comp):

2.1.2 分析`uvm_component_utils(example_comp)

 uvm_component_utils()可以分解為如下兩個宏:

`define uvm_component_utils(T) \
   `m_uvm_component_registry_internal(T,T) \         (macro 1)
   `m_uvm_get_type_name_func(T) \                (macro 2)

注意:宏一般如下定義:`define AWUSER_VFID_RANGE 4:1

(a): 宏在編譯時會被替換,出現AWUSER_VFID_RANGE的地方都會被文本替換成4:1;

(b): 宏只能寫在一行,如果宏的內容過長,應該用反斜杠隔開便於閱讀;

因此,當在class example_comp中通過`uvm_component_utils(example_comp)宏注冊example_comp時,相當於把宏的展開平鋪在class example_comp中。

(1): macro 1

`define m_uvm_component_registry_internal(T,S) \
                                               \
    typedef uvm_component_registry #(T,`"S`") type_id; \
                                         \
    static function type_id get_type(); \                           // 相當於在class example_comp中定義了get_type()方法,
        return type_id::get(); \                      // 用於獲取XXXXX
    endfunction \
                \
    virtual function uvm_object_wrapper get_object_type();  \     // 相當於在class example_comp中定義了get_object_type()方法,
        return type_id::get();  \                      // 用於獲取XXXXX
    endfunction  \

由於注冊時傳遞進來的component類型不同,因此uvm提供了一個參數化的類:class uvm_component_registry #(type T, string Tname);然后根據用戶注冊進來的example_comp類,定義一個專屬於example_comp的類,叫type_id;

結論:相當於在example_comp class下定義了一個uvm_component_registry #(example_comp, "example_comp")類,並命名為type_id;

注意只是定義了type_id這個class,並沒有用這個class去聲明任何變量;

另外雖然別的class在注冊時也會定義type_id這個class,但不用擔心命名沖突,因為都是在自己要注冊的class中去定義的type_id;

為了弄清楚type_id這個類有哪些變量和方法,需要看參數化的類class uvm_component_registry #(type T, string Tname),如下:

class uvm_component_registry #(type T=uvm_component, string Tname="<unknown>") extends uvm_object_wrapper;

    typedef uvm_component_registry #(T,Tname) this_type;

    const static string type_name = Tname;

    virtual function string get_type_name();               // 相當於在class example_comp中定義了get_type_name()方法,
        return type_name;                          // 用於獲取XXXXX
    endfunction

    local static this_type me = get();                  // 最關鍵的一行,由於me是靜態變量,因此在編譯時就通過get()方法初始化me;

    static function this_type get();
        if(me == null) begin
            uvm_coreservice_t cs = uvm_coreservice_t::get();                                                     
            uvm_factory factory=cs.get_factory();
            me = new;
            factory.register(me);
        end
        return me;
    endfunction

    static function T create(string name, uvm_component parent, string contxt="");
        uvm_object obj;                          
        uvm_coreservice_t cs = uvm_coreservice_t::get();                                                     
        uvm_factory factory=cs.get_factory();
        if (contxt == "" && parent != null) contxt = parent.get_full_name();
        obj = factory.create_component_by_type(get(), contxt, name, parent);
        if (!$cast(create, obj)) begin
            ......
        end
    endfunction

如上所述,我們在class example_comp中定義了它專屬的type_id類,也就是class uvm_component_registry #(example_comp, "example_comp");

這里專屬的type_id類的特殊之處在於:申明了一個類型為本身的靜態對象句柄(me),通過靜態函數(get())來創建唯一的一個對象,並將其賦值給對象句柄(me);同時還將構造函數申明為私有成員函數,防止外部調用構造函數來創建對象。因此,此對象句柄(me)就可以作為訪問此單例的的一個窗口,也可以用靜態函數返回值來訪問此單例的對象句柄。

由於me是type_id類型的靜態變量,因此在編譯時,就會將me初始化,因此自然會調用get()方法,調用get()方法主要做兩件事:

(1): 創建type_id類型的對象實例,句柄為me;

(2): 將創建的對象注冊到factory中;正如【2.0 factory的基礎知識】中的分析,所謂注冊就是將專屬的type_id放入到m_type_names[string]這個關聯數組中;

因此在compile階段,准確的說是在elaborate階段,VCS工具就已經為注冊到factory的類創建了專屬的type_id實例,並將該專屬的type_id實例注冊到了factory中。因此在VCS工具中,在0時刻就能夠看到m_type_names[string]中已經存放了注冊類的信息。

到這里,我們已經將class example_comp注冊到了factory中,注冊的作用在哪里體現呢?這個問題在【2.3 對象創建】將會具體分析注冊時如何影響對象創建的。

另外一個值得思考的問題是:我們將class example_comp的專屬類type_id注冊到m_type_names[string],而不是將example_comp直接注冊到m_type_names[string]中,那么type_id和example_comp的關系像什么呢?

type_id相當於是一個生產example_comp實例的模具,由於type_id是一個參數化的類,因此必須實例化后才能攜帶example_comp的信息;通俗的理解就是每個注冊進來的類的"大小"都不一樣,因此要先制造出這個模具(專屬的type_id實例),然后才能用這個模具去生產產品(example_comp實例);所以每一個專屬的type_id都會實例化,然后放到m_type_names[string]中;

有了這個模具(type_id實例),在build_phase階段,我們只需要按這個模具的開關(調用create(...)),既可以生產example_comp實例;

(2): macro 2

`define m_uvm_get_type_name_func(T)

    const static string type_name = `"T`";

    virtual function string get_type_name();
        return type_name;
    endfunction 

由於會在class example_comp extends uvm_component中使用宏`uvm_component_utils(example_comp),因此相當於在class example_comp中定義了上述type_name變量和get_type_name()方法,通過在class example_comp內部或者實例上調用get_type_name()方法,可以獲取到字符串型的類名:"example_comp";

2.2 在例化前設置覆蓋對象和類型(可選)

 系統中存在一個uvm_default_factory類型的全局變量factory,factory提供了很多方法來完成覆蓋(override);

通過類名完成類的覆蓋
factory.set_type_override_by_type(uvm_object_wrapper original_type, //被重載的類型,例如axi_normal_monitor::get_type() uvm_object_wrapper override_type, //要重載的類型,例如axi_abnorm_monitor::get_type() bit replace = 1);
通過類名完成實例的覆蓋 factory.set_inst_override_by_type(uvm_object_wrapper original_type,
uvm_object_wrapper override_type, string full_inst_path);
通過實例名完成類的覆蓋
factory.set_type_override_by_name(string original_type_name, string override_type_name, bit replace = 1); 通過實例名完成實例的覆蓋 factory.set_inst_override_by_name(string original_type_name, string override_type_name, bit full_inst_path);

 不同方法的區別在於,既可以通過類名完成覆蓋,也可以通過實例名完成覆蓋。

作為應用,我們無需關心這些方法的具體實現,但作為源碼分析,這里以factory.set_type_override_by_type為例說明factory是如何實現重載的,如下:

function void uvm_default_factory::set_type_override_by_type (uvm_object_wrapper original_type,
                                                              uvm_object_wrapper override_type,
                                                              bit replace=1);
  bit replaced;

  // 比較original_type和override_type的類型是否相同,如果相同則沒有必要override;
  if (original_type == override_type) begin
      if (original_type.get_type_name() == "" || original_type.get_type_name() == "<unknown>")
      uvm_report_warning("TYPDUP", {"Original and override type ", "arguments are identical"}, UVM_NONE);
  else
      uvm_report_warning("TYPDUP", {"Original and override type ", "arguments are identical: ", original_type.get_type_name()}, UVM_NONE);
  end

  // 檢查original_type和override_type是否已經注冊到factory中,如果沒有則在這里注冊;
  if (!m_types.exists(original_type)) register(original_type); 
  if (!m_types.exists(override_type)) register(override_type); 

  // check for existing type override
  foreach (m_type_overrides[index]) begin
      if (m_type_overrides[index].orig_type == original_type ||                        // 如果original_type之前是否被覆蓋過:
         (m_type_overrides[index].orig_type_name != "<unknown>" &&
          m_type_overrides[index].orig_type_name != "" &&
          m_type_overrides[index].orig_type_name == original_type.get_type_name())) begin
          string msg;
          msg = {"Original object type '",original_type.get_type_name(),
                 "' already registered to produce '",
                 m_type_overrides[index].ovrd_type_name,"'"};
          if (!replace) begin
              msg = {msg, ".  Set 'replace' argument to replace the existing entry."};
              uvm_report_info("TPREGD", msg, UVM_MEDIUM);
              return;
          end
          msg = {msg, ".  Replacing with override to produce type '", override_type.get_type_name(),"'."};
          uvm_report_info("TPREGR", msg, UVM_MEDIUM);
          replaced = 1;                                                                // class uvm_factory_override類有四個成員變量:
          m_type_overrides[index].orig_type = original_type;                 //     uvm_object_wrapper orig_type;
          m_type_overrides[index].orig_type_name = original_type.get_type_name();    //     string             orig_type_name;
          m_type_overrides[index].ovrd_type = override_type;                          //    uvm_object_wrapper ovrd_type;
          m_type_overrides[index].ovrd_type_name = override_type.get_type_name();     //    string             ovrd_type_name;
      end
  end

  // make a new entry
  if (!replaced) begin                                                                // 如果original_type之前沒被覆蓋過:
    uvm_factory_override override;
    override = new(.orig_type(original_type),
                   .orig_type_name(original_type.get_type_name()),
                   .full_inst_path("*"),
                   .ovrd_type(override_type));
    m_type_overrides.push_back(override);
  end

endfunction

首先關注uvm_default_factory下屬的隊列:uvm_factory_override  m_type_overrides[$],該隊列用於存放uvm_factory_override類型對象的句柄,每一個對象/句柄都保存了原始類型和覆蓋類型的信息;

因此當通過set_type_override_by_type(original_type, override_type)方法實現覆蓋時,首先會遍歷m_type_overrides[$],檢查original_type是否被覆蓋過:

如果original_type有被覆蓋過,現在需要用override_type對original_type再次進行覆蓋,那么會將最新的original_type信息和override_type信息更新到m_type_overrides[$]中;

如果original_type沒被覆蓋過,則新創建一個override條目,存放當前的original_type信息和override_type信息,並將新建的override條目push到m_type_overrides[$]中;

因此覆蓋完成后,original_type信息和override_type信息就一定能在m_type_overrides[$]中找到,在之后創建對象時,也一定會用到m_type_overrides[$]里面的覆蓋信息。

2.3 對象創建

(1): 對於component類型,有如下兩種方法創建對象:

c1 = comp_class_name::type_id::create(string_name, uvm_component_parent)
c1 = factory.create_component_by_name("requested_class", this.get_full_name(), name)

如上所述,第一種方法在調用type_id::create(...)創建對象時,本質上也是調用factory的create_component_by_name(...)方法;

到這里,我們分析了注冊,覆蓋和創建的細節。當我們在build_phase階段通過example_comp::type_id::create("example_comp", this)創建對象時:第一個::是類型的域索引,找到與example_comp相對應的type_id實例;第二個::是靜態方法的域索引,找到靜態方法create(...);也就是說VCS首先會去找example_comp自己專屬的代理類實例type_id,然后調用type_id的create(....)方法,因此關注上面的靜態方法create(),在調用create()方法時:

(a): 獲取全局唯一的uvm_factory的句柄factory;

(b): 通過factory的create_component_by_type(get(), contxt, name, parent)方法創建對象,並將對象句柄返回給create()方法;create()方法有四個參數:

(b.1): get(),調用get()將創建類型為type_id的對象,句柄是me;並將對象me注冊到factory中;

(d):

(2): 對於object類型,有如下兩種方法創建對象:

o1 = obj_class_name::type_id::create(string_name)                
o1 = factory.create_object_by_name("requested_class", this.get_full_name(), name)

 說明同上。

3 常用的UVM重載

和SystemVerilog的重載類似,重載的目的都是為了在定義上層通用代碼框架時留給底層用戶足夠多的接口函數,使得用戶不需要修改上層代通用碼框架,就可以將底層自己的代碼參與到上層中,這符合方法學設計的初衷;
對於SystemVerilog,在定義上層通用代碼框架時,會預先在上層通用代碼框架將method A聲明為virtual,然后再使用method A,從而搭建完整的上層通用代碼框架;用戶在使用時,由於上層的virtual聲明,用戶只需實現自己的method A,不用做其他任何事情,用戶的method A就會替代上層的virtual method A,參與到上層通用代碼框架中;
對於uvm,由於factory機制,uvm提供了更加豐富的重載方式,但目的都是一樣:用戶不需要修改上層代通用碼框架,就可以將底層自己的代碼參與到上層中;所謂上冊與下層,只是籠統的說法,上層既可以只uvm方法學,也可以指testbench中固定不變的代碼結構;

另外需要注意的是,為了保證重載成功,重載類需要繼承與待重載的類。

3.1 重載transaction

class normal_sequence extends uvm_sequence #(my_transaction);
    `uvm_object_utils(normal_sequence)

    my_transaction m_trans;
    virtual task body();
        repeat(10) begin
            `uvm_do(m_trans);
        end
        #100;
    endtask
endclass
--------------------------------------------------------------------------------------
function void my_case0::build_phase(uvm_phase phase);
...... super.build_phase(phase); factory.set_type_override_by_type(my_transaction::get_type(), new_transaction::get_type());
......
endfunction

場景:在normal_sequence中默認使用的trans是my_transaction,在某些場景下,我們僅想擴展my_transaction的功能,但不想重新寫一個sequence;
用法:有了factory機制后,我們只需要在my_transaction的基礎上繼承一個新的new_transaction,然后在test層的build_phase中通過factory提供的方法實現重載;

 3.2 重載sequence

class main_sequence extends uvm_sequence #(my_transacton);
    ......
    virtual task body();
        normal_sequence seq;
        repeat(10) begin
            `uvm_do(seq)
        end
    endtask
    ......
endclass
--------------------------------------------------------------------------------------
function void my_case0::build_phase(uvm_phase phase);
    ......
    super.build_phase(phase);
    factory.set_type_override_by_type(normal_sequence::get_type(), abnormal_sequence::get_type());
    ......
endfunction

場景:在main_sequence中默認使用的sequence是normal_sequence,在某些場景下,我們僅想擴展normal_sequence的功能,但不想改變main_sequence中的代碼;
用法:有了factory機制后,我們只需要在normal_sequence的基礎上繼承一個新的abnormal_sequence,然后在test層的build_phase中通過factory提供的方法實現重載;

3.3 重載component

 包括driver,monitor,scoreboard,refmodel等都可以重載,以scoreboard為例:

function void env::build_phase(uvm_phase phase);
    ......
    normal_scoreboard sbx;
    sbx = normal_scoreboard::type_id::create("sbx"); 
    ......
endfunction
------------------------------------------------------------------------------------
function void my_case0::build_phase(uvm_phase);
    ......
    factory.set_type_override_by_type(normal_scoreboard::get_type(), abnormal_scoreboard::get_type());
    ......
endfunction

場景:在env中默認使用的scoreboard是normal_scoreboard,在某些場景下,比如使能的RTL的ECC feature,我們僅想擴展normal_scoreboard的功能,但不想改變env中的代碼;
用法:有了factory機制后,我們只需要在normal_scoreboard的基礎上繼承一個新的abnormal_scoreboard,然后在test層的build_phase中通過factory提供的方法實現重載;

4 UVM對uvm_object的支持
to be continue......


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM