[UVM]一文搞懂UVM callback

                               一文搞懂UVM callback

        前言:在UVM验证平台中,callback的最大用处就是提高验证平台的复用性。很多情况下,我们期望在一个项目中开发的验证平台能够用于另外一个项目。但是,通常来说,完全的复用是比较难实现的,两个不同的项目之间或多或少会有一些差异。如果把两个项目不同的地方使用callback来做,而把相同的地方写成一个完整的env,这样复用时,env可以完全的复用,只要改变相关的callback即可。

  • Table of Contents

一、callback简介

1.1、最簡單的callback函數

二、callback在VIP開發中的應用

2.1、在driver中使用callback

三、UVM callback的實現

3.1、要实现真正的pre_tran,需要首先定义好上节所说的类A:

3.2、接下来定义好一个A_pool类:

3.3、在mii_driver中要做如下声明:

3.4、调用pre_tran

四、UVM callback的使用步驟

4.1、首先从A派生一个类:

4.2、在base_test中把my_callback实例化


一、callback简介

1.1、最簡單的callback函數

       先看一個最簡單的callback函數,我們還是以前面的mac_transaction為例:

class mac_transaction extends uvm_sequence_item;
  rand bit[47:0] dmac;
  rand bit[47:0] smac;
  rand bit[15:0] eth_type;
  rand byte pload[];
  rand bit[31:0] crc;

  `uvm_object_utils_begin(mac_transaction)
    `uvm_field_int(dmac, UVM_ALL_ON)
    `uvm_field_int(smac, UVM_ALL_ON)
    `uvm_field_int(eth_type, UVM_ALL_ON)
    `uvm_field_array_int(pload, UVM_ALL_ON)
    `uvm_field_int(crc, UVM_ALL_ON)
  `uvm_object_utils_end
endclass

       transaction中的crc字段需要在整個transaction都固定好之後,才能計算。即需要執行如下function才能實現:

mac_transaction     tr;
assert(tr.randomize());
tr.calc_crc();

       执行前两句之后,tr中的crc字段的值是一个随机的值,我们要把其设置成真正的反正这个transaction数据的crc信息,需要在randoimize()之后调用一个calc_crc(),calc_crc()是一个自定义的函数。
       这个调用calc_crc的过程有点繁琐,因为每次randomize之后都要调用一次,如果有一次,忘记调用了,这很可能会成为验证平台的一个隐患,非常隐蔽,不容易发现。

       因此,我们期望有一种方法,能够在randomize之后自动调用calc_crc()函数randomize是systemverilog提供的一个函数,同时systemverilog还提供了一个post_randomize()函数,当randomize()之后,系统会自动调用post_randomize函数,像如上的三句话,执行时实际上如下:

mac_transaction     tr;
assert(tr.randomize());
tr.post_randomize();
tr.calc_crc();

       其中tr.post_randomize是自动调用的,所以如果我们能够定义post_randomize函数,在其中执行calc_crc函数,那么就可以达到我们的目的了:

function void mac_transaction::post_randomize();
  super.post_randomize();
  this.calc_crc();
endfunction

       像上面的post_randomize就是systemverilog提供的一个callback函数。这也是最简单的callback函数。

二、callback在VIP開發中的應用

        对于一个VIP来说,一个很容易预测到的需求是在driver中,在发送transaction之前,用户可能会针对transaction做某些动作,因此应该提供一个pre_tran的接口,例如:

  • A用户可能在pre_tran中把要发送的内容的最后4个byte设置为发送的包的序号,这样在包出现比对错误的时候,可以快速的定位,
  • B用户可能在整个包发送之前先在线路上发送几个特殊的字节,
  • C用户可能把整个包的长度给截去一部分,
  • D用户……

       总之不同的用户会有不同的需求。正是callback的存在,满足了这种需求,扩大的VIP的应用范围

2.1、在driver中使用callback

       假设下面code是一个成熟的VIP中的driver,考虑如何实现这个pre_tran这个callback呢?

task mii_driver::main_phase();
  …
  while(1) begin
    seq_item_port.get_next_item(req);
    pre_tran(req);
    …
  end
endtask

       它应该是mii_driver的一个函数(任务)。如果按照上面的post_randomize的经验,那么我们应该从mii_driver派生出一个类来,然后重写pre_tran这个函数(任务)。这种想法是行不通的,因为这是一个完整的VIP,我们虽然从mii_driver派生了一个类,但是这个这个VIP中正常运行时使用的依然是mii_driver,而不是它的派生类。我们的派生类根本就没有实例化过,所以pre_tran从来不会运行。

       为了解决这个问题,UVM中新引入了一个类:

task mii_driver::main_phase();
  …
  while(1) begin
    seq_item_port.get_next_item(req);
    A.pre_tran(req);
    …
  end
endtask

        这样的话,我们可以避免把mii_driver重新定义一次,我们只需要重新定义A的pre_tran就可以了。重新派生A的代价是要远小于mii_driver的。
        在使用的时候,我们只要从A派生一个类,然后把这个类实例化,重新定义其pre_tran函数,于是callback的目的就达到了。看起来似乎一切顺利,但是忽略了一点。因为我们从A派生了一个类,把它实例化,但是作为mii_driver来说,怎么知道A派生了一个类呢?又怎么知道A实例化了呢?

        为了应付这个问题,UVM中又引入了一个类,假设这个类称为A_pool,意思就是专门存放A或者A的派生类的一个池子。我们约定会执行这个池子中所有实例的pre_tran函数(任务),即:

task mii_driver::main_phase();
  …
  while(1) begin
    seq_item_port.get_next_item(req);
    foreach(A_pool[i]) begin
      A_pool[i].pre_tran(req);
    end
    …
  end
endtask

        这样,在使用的时候,只要从A派生一个类,把其实例化,并加入到A_pool中,那么系统运行到上面的foreach(A_pool[i])语句时,会知道加入了一个实例,于是就会调用其pre_tran函数(任务)。

三、UVM callback的實現

3.1、要实现真正的pre_tran,需要首先定义好上节所说的类A:

class A extends uvm_callback;
  virtual task pre_tran(mii_driver mii_drv, ref mii_transaction tr);
  endtask
endclass

        这里要注意的是A类一定要从uvm_callback派生另外还需要定义一个pre_tran的任务,此任务的类型一定要是virtual的,因为从A派生的类需要重载这个任务。

3.2、接下来定义好一个A_pool类:

typedef uvm_callbacks#(mii_driver, A)     A_pool;

        A_pool的定义相当简单,只需要一个typedef语句即可。另外,在这个声明中除了要指明这是一个A类型的池子外,还要指明这个池子将会用于哪个类。在本例中,mii_driver将会使用这个池子,所以要把此池子声明为mii_driver专用的。

3.3、在mii_driver中要做如下声明:

class mii_driver extends uvm_driver#(mii_transaction);
  …
  `uvm_register_cb(mii_driver, A)
endclass

        这个声明与A_pool的类似,要指明mii_driver和A。在mii_driver的main_phase中调用pre_tran时并不如上节所示的那么简单,而是调用了一个宏来实现。

3.4、调用pre_tran

task mii_driver::main_phase();
  …
  while(1) begin
    seq_item_port.get_next_item(req);
    `uvm_do_callbacks(mii_driver, A, pre_tran(this, req))
    …
  end
endtask

        uvm_do_callback宏的第一个参数是调用pre_tran的类的名字,这里自然是mii_driver,第二个参数是哪个类具有pre_tran,这里是A,第三个参数是调用的是哪个函数(任务),这里是pre_tran,在指明是pre_tran时,要顺便给出pre_tran的参数。

四、UVM callback的使用步驟

4.1、首先从A派生一个类:

class my_callback extends A;
  virtual task pre_tran(mii_driver mii_drv, ref mii_transaction tr);
    $display(“pre_tran, the transaction is”);
    tr.print();
  endtask

  `uvm_object_utils(my_callback)
endclass

 

4.2、在base_test中把my_callback实例化

class base_test extends uvm_test;
  …
  my_callback my_cb;
  …

  function void connect_phase(uvm_phase phase);
    my_cb = my_callback::type_id::create(“my_cb”);
    A_pool::add(mii_env.agent.driver, my_cb);
    …
  endfunction

endclass

        my_callback的实例化是在connect_phase中完成的,实例化完成后需要把my_cb加入到A_pool中。同时,在加入的时候,要指定是给哪个mii_driver使用的。因为很可能整个base_test中例化了多个mii_env,所以要把mii_driver的路径作为add函数的第一个参数。
        至此,一个简单的callback就完成了。这个callback几乎是涵盖中UVM中所有可能用到的callback的知识,大部分的callback的使用都与这个例子相似。

发布了208 篇原创文章 · 获赞 150 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/gsjthxy/article/details/105402618