The UVM Primer -- Chpater 21 UVM Transaction

Chpater 21 UVM Transaction

21.1 Storing Data in Objects

从学习本书开始,我们将测试平台拆分形成一个小模块,每个模块,每个class只专注于一件事情。尽力避免一个class需要详细了解另一个class的工作内容的场景,两个不同class之间可以通过port的方式传递数据。以上工作都使得我们的测试平台变得十分高效,但是我们还留下了一些遗憾:没有对传输的数据进行处理!
TinyALU测试平台中的数据结构比较简单,command_s中包含三个变量:A,B,op。即使我们的TinyALU测试平台很小,我们也在许多地方反复调用这些值。driver,coverage,scoreboard,因此如果我们可以对数据进行处理优化,就可以进一步提升测试平台的工作效率。将数据存储在class中,可以使用函数进行数据处理,这是class优于struct的地方。

  • 在UVM中这种数据class称作transaction,transaction封装了数据和数据的所有操作类型,这些操作如下:
    • convert2string(): 将数据以字符串的形式打印;
    • do_copy(): 将另外一个transaction的数据拷贝至当前transaction;
    • do_compare(): 比较两个transaction的数据是否一致;
    • randomize(): 利用SystemVerilog的random机制;
  • 定义Transaction:
    • 我们扩展uvm_transaction以生成需要的command_transaction,我们需要重新改写以下三个函数:
    • do_copy()
    • do_compare()
    • convert2string()

21.1.1 定义数据的随机范围

command_transaction中需要定义随机变量,采用SV的rand定义,并用constraint约束变量的随机范围。这样我们就不再需要get_op()函数和get_data()函数,SV可以接手所有的随机工作,我们只需要在定变量的时候在前面增加rand关键字即可:

class command_transaction extends uvm_transaction;
   `uvm_object_utils(command_transaction)
   rand byte unsigned        A;      //定义操作数A,只需要在定义之前加rand关键字
   rand byte unsigned        B;
   rand operation_t         op;

   constraint data { A dist {8'h00:=1, [8'h01 : 8'hFE]:=1, 8'hFF:=1};
                     B dist {8'h00:=1, [8'h01 : 8'hFE]:=1, 8'hFF:=1};} 
    //指定A B的范围可以指定,甚至可以用dist决定随机值的出现概率

21.1.2 uvm_transaction的new函数

uvm_transaction扩展自uvm_object,不是component类型,在构造时不需要parent handle,只需要name即可。(component类型的class在构建的时候需要知道在环境中的具体节点位置,所以需要parent)

   function new (string name = "");
      super.new(name);
   endfunction : new

21.1.3 do_copy函数

uvm_object类和其子类都包含copy()函数和clone()函数,其中copy函数拷贝一个对象的数据至另一个同类型对象,clone函数返回数据完全一致的同类型的对象;copy是将数据拷贝一份,clone是返回一个新的对象。
copy函数和clone函数能够工作的前提是:我们必须重构do_copy函数,do_copy函数的工作机制和我们上一章节定义lion的convert2string的机制一致,需要调用父类的同名函数。

   function void do_copy(uvm_object rhs);
      command_transaction copied_transaction_h;

      if(rhs == null) 
        `uvm_fatal("COMMAND TRANSACTION", "Tried to copy from a null pointer")
      
      //检查传输的rhs不是command_transaction类型,拷贝失败
      if(!$cast(copied_transaction_h,rhs))
        `uvm_fatal("COMMAND TRANSACTION", "Tried to copy wrong type.")
      
      super.do_copy(rhs); // copy all parent class data

      A = copied_transaction_h.A;
      B = copied_transaction_h.B;
      op = copied_transaction_h.op;

   endfunction : do_copy

21.1.4 clone_me函数

  • 使用uvm_transaction的好处之一:在多个模块多个对象中都可以看到相同的数据,在模块较小的TinyALU中可能考不到优势,如果我们的设计包含250个port,原本需要250份备份数据的空间,现在只需要一个即可。
  • 双刃剑:上述机制也存在一定的短板:无论任何一个对象对数据进行了修改,整个测试环境看到的数据都会变化。因此引入了MOOCOW(Manual Obligatory Object Copy On Write)机制,如果我们希望对transaction中的数据进行操作就需要强制拷贝一个备份,并在自己的世界里进行修改。
  • 当然,有些设计中希望在某个模块(如uvm_callbacks)检测transaction数据,发现异常的时候就进行修改,这样也是充分利用了uvm_transaction的优势。
   function command_transaction clone_me();
      command_transaction clone;
      uvm_object tmp;

      tmp = this.clone();    //通过clone函数,我们得到的是一个新的对象,具有独立的存储空间
      $cast(clone, tmp);
      return clone;          //函数的返回值是新的对象,和this具有不同的存储空间,但是包含完全一致的数据
   endfunction : clone_me

21.1.5 do_compare函数

  • 在scoreboard中我们需要对数据尽心对比,我们希望uvm_transaction自己就可以完成数据比对。所有数据都在transaction内部,使用也更加顺手,也可以将功能封装起来。
  • do_compare需要传入uvm_object类型的同类型transaction,也需要传入一个uvm_comparer类型的变量comparer(此处不再介绍)
  • do_compare函数属于deep operation,所以我们需要将transation传给父类,让父类函数比较父类成员是否一致。
function bit do_compare(uvm_object rhs, uvm_comparer comparer);
      command_transaction compared_transaction_h;
      bit   same;
      
      if (rhs==null) `uvm_fatal("RANDOM TRANSACTION", 
                                "Tried to do comparison to a null pointer");
      
      if (!$cast(compared_transaction_h,rhs))
        same = 0;
      else
        same = super.do_compare(rhs, comparer) &&    //父类成员变量一致
               (compared_transaction_h.A == A) &&    //this的成员变量一致才返回same=1
               (compared_transaction_h.B == B) &&
               (compared_transaction_h.op == op);
               
      return same;
   endfunction : do_compare

21.1.6 convert2string函数

和上一章lion中的机制一致,我们使用格式化打印显示成员变量。

   function string convert2string();
      string s;
      //使用SV的格式化打印机制显示成员变量
      s = $sformatf("A: %2h  B: %2h op: %s",A, B, op.name());
      return s;
   endfunction : convert2string

21.2 使用UVM Transaction

我们把数据data从信号级传输封装成struct,然后又升级为uvm transaction,所以我们需要修改测试平台中的几乎每一个模块。不过这样有利于代码重用,有利于简化代码结构,一劳永逸。

21.2.1 创建rusult_transaction用于存储计算结果

class result_transaction extends uvm_transaction;

   shortint result;

   function new(string name = "");
      super.new(name);
   endfunction : new

   ......

   function bit do_compare(uvm_object rhs, uvm_comparer comparer);  //scoreboard可以直接嗲用这个函数进行DUT计算结果和预测结果比对
      result_transaction RHS;
      bit    same;
      assert(rhs != null) else
        $fatal(1,"Tried to copare null transaction");
      same = super.do_compare(rhs, comparer);
      $cast(RHS, rhs);
      same = (result == RHS.result) && same;
      return same;
   endfunction : do_compare

endclass : result_transaction

21.2.2 创建add_transaction仅产生add操作

利用继承,复用command_transaction定义的功能,但是将opcode限制为add。

class add_transaction extends command_transaction;  //有没有感受到继承的魅力?
   `uvm_object_utils(add_transaction)

   constraint add_only {op == add_op;}

   function new(string name="");super.new(name);endfunction
endclass : add_transaction

21.2.3 将base_tester改名为tester

此前我们定义了random_tester和add_tester产生随机操作和add操作,现在有了

class tester extends uvm_component;
   `uvm_component_utils (tester)

   uvm_put_port #(command_transaction) command_port;
 
   task run_phase(uvm_phase phase);
      command_transaction  command;     //使用定义的transaction

      phase.raise_objection(this);

      command = new("command");
      command.op = rst_op;
      command_port.put(command);

      repeat (10) begin
         command = command_transaction::type_id::create("command");
         assert(command.randomize());   //使用randomize函数实现数据随机
         command_port.put(command);     //使用put函数将数据通过put_port送至测试平台
      end
   ....

21.2.4 升级common_monitor

command_monitor传递的类型改变了,write函数需要更新。

class command_monitor extends uvm_component;
   `uvm_component_utils(command_monitor);

   virtual tinyalu_bfm bfm;

   uvm_analysis_port #(command_transaction) ap;     //analysis端口传输的数据类型改变

   function new (string name, uvm_component parent);
      super.new(name,parent);
   endfunction

   function void build_phase(uvm_phase phase);
      if(!uvm_config_db #(virtual tinyalu_bfm)::get(null, "*","bfm", bfm))
	`uvm_fatal("COMMAND MONITOR", "Failed to get BFM")
      bfm.command_monitor_h = this;
      ap  = new("ap",this);
   endfunction : build_phase

   function void write_to_monitor(byte A, byte B, operation_t op);
     command_transaction cmd;              //噢,数据类型变成了command_transaction     
     `uvm_info("COMMAND MONITOR",$sformatf("MONITOR: A: %2h  B: %2h  op: %s",
                A, B, op.name()), UVM_HIGH);
     cmd = new("cmd");                     //transaction使用需要先分配内存空间哟
     cmd.A = A;
     cmd.B = B;
     cmd.op = op;
     ap.write(cmd);                        //送出的数据类型也变成了command_transaction类型
   endfunction : write_to_monitor
endclass : command_monitor

21.2.5 升级result_monitor

class result_monitor extends uvm_component;
   `uvm_component_utils(result_monitor);

   virtual tinyalu_bfm bfm;
   uvm_analysis_port #(result_transaction) ap;  //monitor的analysis端口的数据类型更新了哟
   
   ......

   function void build_phase(uvm_phase phase);
    if(!uvm_config_db #(virtual tinyalu_bfm)::get(null, "*","bfm", bfm))
        `uvm_fatal("RESULT MONITOR", "Failed to get BFM")

      bfm.result_monitor_h = this;
      ap  = new("ap",this);
   endfunction : build_phase

   function void write_to_monitor(shortint r);
      result_transaction result_t;         //噢,这里需要声明result_transaction类型对象
      result_t = new("result_t");          //别忘了分配内存空间
      result_t.result = r;
      ap.write(result_t);                  //端口送出的数据类型也变了哦
   endfunction : write_to_monitor
endclass : result_monitor

21.2.6 在scoreboard中使用transaction的do_compare函数

class scoreboard extends uvm_subscriber #(result_transaction);
   `uvm_component_utils(scoreboard);

   uvm_tlm_analysis_fifo #(command_transaction) cmd_f;
   
   function void build_phase(uvm_phase phase);
      cmd_f = new ("cmd_f", this);
   endfunction : build_phase

   ......

   function void write(result_transaction t);
      string data_str;
      command_transaction cmd;                 //get端口的数据类型也变了哟
      result_transaction predicted;            //预测结果的数据类型也变了哦

      do
        if (!cmd_f.try_get(cmd))               //还记的try_get是啥类型么?
          $fatal(1, "Missing command in self checker");
      while ((cmd.op == no_op) || (cmd.op == rst_op));

      predicted = predict_result(cmd);         //计算预测结果,(函数定义这里没给出)
      
      data_str = {                    cmd.convert2string(), 
                  " ==>  Actual "  ,    t.convert2string(), 
                  "/Predicted ",predicted.convert2string()};
                  
      if (!predicted.compare(t))               //调用result_transaction的compare函数
        `uvm_error("SELF CHECKER", {"FAIL: ",data_str})
      else
        `uvm_info ("SELF CHECKER", {"PASS: ", data_str}, UVM_HIGH)
   endfunction : write

endclass : scoreboard

21.2.7 升级add_test

使用set_type_override告诉UVM,所有遇到commad_transction的地方都使用add_transaction代替,不用对random_test做任何修改。

class add_test extends random_test;
   `uvm_component_utils(add_test);

   function void build_phase(uvm_phase phase);
    command_transaction::type_id::set_type_override(add_transaction::get_type());  //还记得override的作用么?
    super.build_phase(phase);
   endfunction : build_phase
   
   function new (string name, uvm_component parent);
      super.new(name,parent);
   endfunction : new
endclass

21.3 Transaction Summary

  • 本章中我们使用UVM Transaction,将数据打包。许多数据的处理都在UVM Transaction内部完成,测试平台的组件只需要直接调用transaction内部的函数即可。
  • 下一章,我们重新回到UVM的hierarchy。我们将认识到,与给定块或接口关联的所有类可以组合在一起,并在使用该接口的其他测试平台中重用。
  • 在env下再增加一级封装,env结构变化时这些给定块可以重用。

版权声明:本文为qq_39556143原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。