Data Type Support

This example shows a full implementation of a UVM-compliant transaction type whose fields represent all the data types supported by the UVMC library.  Although the example defines all the do_* operations, UVMC requires only do_pack and do_unpack.

Contents
Data Type SupportThis example shows a full implementation of a UVM-compliant transaction type whose fields represent all the data types supported by the UVMC library.
packetDefines a packet class containing a field for each of the data types supported by UVMC.
producerA simple producer that generates packet transactions and sends them out its out blocking-put and ap analysis ports.
scoreboardA simple scoreboard that implements in-order comparison of expect and actual packets.
sv_mainCreates an instance of a producer and scoreboard, makes both native and cross-language connections using UVM Connect, then calls run_test.

packet

Defines a packet class containing a field for each of the data types supported by UVMC.

integralsbit, byte, shortint, int, longint long, and their unsigned counterparts.
enumuser-defined enumeration types are packed by their numeric value.  A compatible enumeration type must be defined on the SC side.
realsshortreal and real translate to SC-side float and double, respectively.
stringsuse only for ASCII strings.  Use vector<char> for an array of bytes whose elements can include the ‘0’ value
arraysfixed arrays, queues, dynamic arrays, and associate arrays are supported as long as the element and key types are among the supported types.  These types are analogous to the STL vector<T>, list<T>, and map<KEY,T> types in C++.
timepacked as 64-bit values.  Equates to the SC ‘sc_time’ type.
sc data typesThe SV bit-vector and logic-vector types are mapped to any of the following SC built-in types: sc_bit, sc_logic, sc_bv<N>, sc_lv<N>, sc_int<N>, sc_uint<N>, sc_bigint<N>, and sc_biguint<N>, for any valid width, N. For any given N, the SV-side declaration should be bit [N-1:0] var_name.  See the SC-side definition of packet.

Although do_pack and do_unpack are the only methods required by UVMC, this example also implements do_copy, do_compare, do_print, and do_record.  There are many reasons for opting to implement the do_* methods as below instead of using the `uvm_field macros.  For details, see the white paper, OVM/UVM Macros: A Cost-Benefit Analysis, at http://www.verificationacademy.com.

class packet extends uvm_object;

   // default converter assumes trans derives from uvm_object
   `uvm_object_utils(packet)

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

    typedef enum { ADD, SUBTRACT, MULTIPLY, DIVIDE } cmds_t;

    rand cmds_t            enum32;

    rand longint           int64;
    rand int               int32;
    rand shortint          int16;
    rand byte              int8;
    rand bit               int1;

    rand longint unsigned  uint64;
    rand int unsigned      uint32;
    rand shortint unsigned uint16;
    rand byte unsigned     uint8;
    rand bit unsigned      uint1;

         real              real64;

    rand time              time64;

         string            str;

    rand int               arr[3];
    rand byte              q[$];
    rand shortint          da[];
         shortint          aa[shortint];

    rand sub_object        obj;

    rand bit               scbit;
    rand logic             sclogic;
    rand bit [16:0]        scbv;
    rand logic [34:0]      sclv;
    rand bit [5:0]         scint;
    rand bit [24:0]        scuint;
    rand bit [36:0]        scbigint;
    rand bit [61:0]        scbiguint;


    constraint C_q_size  { q.size  inside {[1:11]}; }
    constraint C_da_size { da.size inside {[1:11]}; }
Summary
packet
Defines a packet class containing a field for each of the data types supported by UVMC.
Class Hierarchy
ovm_object
packet
Class Declaration
class packet extends uvm_object
Methods
do_packConverts this transaction’s contents into a form transferrable outside SystemVerilog.
do_unpackConverts a bit-vector representation of a transaction into this transaction object.
do_copyCopies the values of fields from another object of the same type into this object.
do_compareCompares the values of fields with those of another object of the same type, returning 1 if a match, 0 otherwise.
do_printImplements printing of all fields in this transaction using the provided printer policy class.
do_recordRecords all members of this transaction class for later viewing in the GUI’s wave window.
pre_randomizeRandomizes the string variable, str, and associative array, aa.
pre_randomizeProvides rough randomization of real and shortreal fields by casting randomized integrals to reals then taking their quotients.

do_pack

Converts this transaction’s contents into a form transferrable outside SystemVerilog.

Each field is packed using the smaller, more efficient packing macros included in UVM.  Associative arrays are packed by first packing its size in a 32-bit value.  Then, you pack each key-value pair use the macro appropriate for their types.

Subobjects are packed by calling packer.pack_object(subob);.

If your transaction extends a base class with its own fields, call super.do_pack(packer) before packing anything in this class.

virtual function void do_pack(uvm_packer packer);

  `uvm_pack_int(enum32)
  `uvm_pack_int(int64)
  `uvm_pack_int(int32)
  `uvm_pack_int(int16)
  `uvm_pack_int(int8)
  `uvm_pack_int(int1)
  `uvm_pack_int(uint64)
  `uvm_pack_int(uint32)
  `uvm_pack_int(uint16)
  `uvm_pack_int(uint8)
  `uvm_pack_int(uint1)
  `uvm_pack_int(scbit)
  `uvm_pack_int(sclogic)
  `uvm_pack_int(scbv)
  `uvm_pack_int(sclv)
  `uvm_pack_int(scint)
  `uvm_pack_int(scuint)
  `uvm_pack_int(scbigint)
  `uvm_pack_int(scbiguint)
  `uvm_pack_int(time64)
  `uvm_pack_real(real64)
  `uvm_pack_string(str)
  `uvm_pack_sarray(arr)
  `uvm_pack_queue(q)
  `uvm_pack_array(da)

  `uvm_pack_intN(aa.size(),32)
  foreach (aa[i]) begin
    `uvm_pack_intN(i,16)
    `uvm_pack_intN(aa[i],16)
  end

  packer.pack_object(obj);

endfunction

do_unpack

Converts a bit-vector representation of a transaction into this transaction object.

The order and manner of unpacking must be identical to how packing was performed.  Packing an object then unpacking into a new instance of the object should be equivalent to copying the original object, minus any fields that were not packed.

Each field is unpacked using the smaller, more efficient unpacking macros included in UVM.

Associative arrays are packed by first packing its size in a 32-bit value.  Then, you pack each key-value pair use the macro appropriate for their types.

To unpack sub-objects, first call packer.is_null() to determine if the packed sub-object is null or now.  If not, you set this transaction’s sub-object to null.  If is_null returns 0, then alloate obj if its null and call packer.unpack_object().

If your transaction extends a base class with its own fields, call super.do_unpack(packer) before unpacking anything in this class.

Instead of the macros, you could call methods of the packer for every field, just as you do for sub-objects.  However, packing of built-in integral types is less efficient, and there is no methods for unpacking arrays.

virtual function void do_unpack(uvm_packer packer);

  int unsigned n;

  `uvm_unpack_enum(enum32,cmds_t)
  `uvm_unpack_int(int64)
  `uvm_unpack_int(int32)
  `uvm_unpack_int(int16)
  `uvm_unpack_int(int8)
  `uvm_unpack_int(int1)
  `uvm_unpack_int(uint64)
  `uvm_unpack_int(uint32)
  `uvm_unpack_int(uint16)
  `uvm_unpack_int(uint8)
  `uvm_unpack_int(uint1)
  `uvm_unpack_int(scbit)
  `uvm_unpack_int(sclogic)
  `uvm_unpack_int(scbv)
  `uvm_unpack_int(sclv)
  `uvm_unpack_int(scint)
  `uvm_unpack_int(scuint)
  `uvm_unpack_int(scbigint)
  `uvm_unpack_int(scbiguint)
  `uvm_unpack_int(time64)
  `uvm_unpack_real(real64)
  `uvm_unpack_string(str)
  `uvm_unpack_sarray(arr)
  `uvm_unpack_queue(q)
  `uvm_unpack_array(da)

  aa.delete();
  `uvm_unpack_int(n)
  for (int i=0; i<n; i++) begin
    shortint k, val;
    `uvm_unpack_int(k)
    `uvm_unpack_int(val)
    aa[k]=val;
  end

  if (packer.is_null())
    obj = null;
  else begin
    if (obj == null)
      obj = new();
    packer.unpack_object(obj);
  end

endfunction

do_copy

Copies the values of fields from another object of the same type into this object.

Do_copy uses the assignment operator for all built-in types.  Subobjects are copied by calling subobj.copy(_rhs.subobj).

If your transaction extends a base class with its own fields, call super.do_copy(rhs) before copying anything in this class.

function void do_copy(uvm_object rhs);

  packet _rhs;
  assert($cast(_rhs,rhs));

  enum32    = _rhs.enum32;
  int64     = _rhs.int64;
  int32     = _rhs.int32;
  int16     = _rhs.int16;
  int8      = _rhs.int8;
  int1      = _rhs.int1;
  uint64    = _rhs.uint64;
  uint32    = _rhs.uint32;
  uint16    = _rhs.uint16;
  uint8     = _rhs.uint8;
  uint1     = _rhs.uint1;
  time64    = _rhs.time64;
  str       = _rhs.str;
  arr       = _rhs.arr;
  q         = _rhs.q;
  da        = _rhs.da;
  aa        = _rhs.aa;
  real64    = _rhs.real64;
  scbit     = _rhs.scbit;
  sclogic   = _rhs.sclogic;
  scbv      = _rhs.scbv;
  sclv      = _rhs.sclv;
  scint     = _rhs.scint;
  scuint    = _rhs.scuint;
  scbigint  = _rhs.scbigint;
  scbiguint = _rhs.scbiguint;
  obj.copy(_rhs.obj);
endfunction

do_compare

Compares the values of fields with those of another object of the same type, returning 1 if a match, 0 otherwise.

Use the boolean equality operator (==) for most built-in types.  It is more efficient than the provided methods in the comparer policy class.

If you opt to employ direct comparison with the == operator as in this example, you must still set the comparer.result to 0 if there were no miscompares, or to if there was a miscompare.

Leverage short-circuit expression evaluation for higher efficiency.  Expression evaluation stops as soon as a result is certain.  For example, given an expression (a && b && c && d), if a or b are 0, the whole expression evaluates to 0, so there is no point in examining c or d.  The expression evaluates to 0 no matter what their values.  In this packet class, if the int64 property doesn’t match with the right hand side, the remaining 25 equality comparisons that follow are not evaluated, thus speeding up the comparison operation considerably.

Comparison of scalars is more efficient than comparison of arrays and other composite types (e.g. sub-objects).  So, put composite types at the end of the expression.  That way, these types will not be compared unless all the previous expression terms evaluate to true.

Subobjects are compared by calling subobj.compare(_rhs.subobj,comparer).

If your transaction extends a base class with its own fields, call super.do_compare(rhs,comparer) before comparing any fields of this class.  If super.do_compare returns 0, return immediately with 0.  Otherwise, continue with comparing this transaction’s fields.

function bit do_compare(uvm_object rhs, uvm_comparer comparer);

  packet _rhs;
  assert($cast(_rhs,rhs));
  comparer.result = 1;

  do_compare =
          enum32    == _rhs.enum32 &&
          int64     == _rhs.int64 &&
          int32     == _rhs.int32 &&
          int16     == _rhs.int16 &&
          int8      == _rhs.int8 &&
          int1      == _rhs.int1 &&
          uint64    == _rhs.uint64 &&
          uint32    == _rhs.uint32 &&
          uint16    == _rhs.uint16 &&
          uint8     == _rhs.uint8 &&
          uint1     == _rhs.uint1 &&
          time64    == _rhs.time64 &&
          str       == _rhs.str &&
          arr       == _rhs.arr &&
          q         == _rhs.q &&
          da        == _rhs.da &&
          scbit     == _rhs.scbit &&
          sclogic   == _rhs.sclogic &&
          scbv      == _rhs.scbv &&
          sclv      == _rhs.sclv &&
          scint     == _rhs.scint &&
          scuint    == _rhs.scuint &&
          scbigint  == _rhs.scbigint &&
          scbiguint == _rhs.scbiguint &&
          $realtobits(real64) == $realtobits(_rhs.real64)
          ;

  if (!do_compare)
    return 0;

  // temporary limitation: assoc arrays must be compared "item by item"
  if (aa.size() != _rhs.aa.size())
    return 0;
  foreach (aa[i])
    if (!(_rhs.aa.exists(i) && aa[i] == _rhs.aa[i]))
      return 0;

  // compare sub-object deeply
  if (obj == null) begin
    if (_rhs.obj != null)
      return 0;
  end
  else
    do_compare = obj.compare(_rhs.obj,comparer);

  comparer.result = 1-do_compare;

endfunction

do_print

Implements printing of all fields in this transaction using the provided printer policy class.

To cut down on repetitive typing, small macros are used to “inline” verbose calls to printer.print_generic.

If your transaction extends a base class with its own fields, call super.do_print(printer) before printing any fields of this class.

virtual function void do_print(uvm_printer printer);

  `define do_print_int(VAR,TYP,SZ) \
    printer.print_generic(`"VAR`", `"TYP`",SZ,$sformatf("'h%h",VAR));

  `define do_print_ele(VAR,TYP,SZ) \
    printer.print_generic($sformatf("[%0d]",i),\
          `"TYP`",SZ,$sformatf("'h%h",VAR));

  printer.print_generic("cmd","cmds_t",32,enum32.name());
  `do_print_int(int64,     longint,           64)
  `do_print_int(int32,     int,               32)
  `do_print_int(int16,     shortint,          16)
  `do_print_int(int8,      byte,               8)
  `do_print_int(int1,      bit,                1)
  `do_print_int(uint64,    longint unsigned,  64)
  `do_print_int(uint32,    int unsigned,      32)
  `do_print_int(uint16,    shortint unsigned, 16)
  `do_print_int(uint8,     byte unsigned,      8)
  `do_print_int(uint1,     bit unsigned,       1)
  `do_print_int(scbit,     bit,                1)
  `do_print_int(sclogic,   logic,              1)
  `do_print_int(scbv,      bit[16:0],         17)
  `do_print_int(sclv,      bit[34:0],         35)
  `do_print_int(scint,     bit[5:0],           6)
  `do_print_int(scuint,    bit[24:0],         25)
  `do_print_int(scbigint,  bit[36:0],         37)
  `do_print_int(scbiguint, bit[61:0],         62)
  printer.print_time   ("time64",time64);
  printer.print_real   ("real64",real64);
  printer.print_string ("str",  str);

  // print arrays one element at a time, between a header
  // and footer
  printer.print_array_header("arr",3,"int[3]");
  foreach (arr[i])
    `do_print_ele(arr[i],int,32)
  printer.print_array_footer(3);

  printer.print_array_header("q",q.size(),"byte[$]");
  foreach (q[i])
    `do_print_ele(q[i],byte,8)
  printer.print_array_footer(q.size());

  printer.print_array_header("da",da.size(),"shortint[]");
  foreach (da[i])
    `do_print_ele(da[i],shortint,16)
  printer.print_array_footer(da.size());

  printer.print_array_header("aa",aa.num(),"shortint[shortint]");
  foreach (aa[i])
    `do_print_ele(aa[i],shortint,16)
  printer.print_array_footer(aa.num());

  printer.print_object("obj",obj);

endfunction

do_record

Records all members of this transaction class for later viewing in the GUI’s wave window.

This implementation uses the small uvm_record_field macro to record most field types.  Arrays are recorded iteratively using the same macro.

To record a subobject, call the recorder’s record_object method.

The component’s end_tr method indirectly calls this method, but only if its recording_detail configuration parameter is set to something above UVM_NONE.

If your transaction extends a base class with its own fields, call super.do_record(recorder) before recording any fields of this class.

virtual function void do_record(uvm_recorder recorder);
  int unsigned real_bits32;
  int unsigned n;
  `uvm_record_field("enum32",enum32)
  `uvm_record_field("int64",int64)
  `uvm_record_field("int32",int32)
  `uvm_record_field("int16",int16)
  `uvm_record_field("int8",int8)
  `uvm_record_field("int1",int1)
  `uvm_record_field("uint64",uint64)
  `uvm_record_field("uint32",uint32)
  `uvm_record_field("uint16",uint16)
  `uvm_record_field("uint8",uint8)
  `uvm_record_field("uint1",uint1)
  `uvm_record_time("time64",time64)
  `uvm_record_field("real64",real64)
  `uvm_record_string("str",str)
  foreach(arr[i])
    `uvm_record_field($sformatf("arr[%0d]",i),arr[i])
  foreach(q[i])
    `uvm_record_field($sformatf("q[%0d]",i),q[i])
  foreach(da[i])
    `uvm_record_field($sformatf("da[%0d]",i),da[i])
  foreach (aa[i]) begin
    string val = $sformatf("'h%h",aa[i]);
    `uvm_record_field($sformatf("aa[%0d]",i),val)
  end
  recorder.record_object("obj",obj);

  `uvm_record_field("scbit",scbit);
  `uvm_record_field("sclogic",sclogic);
  `uvm_record_field("scbv",scbv);
  `uvm_record_field("sclv",sclv);
  `uvm_record_field("scint",scint);
  `uvm_record_field("scuint",scuint);
  `uvm_record_field("scbigint",scbigint);
  `uvm_record_field("scbiguint",scbiguint);
endfunction

pre_randomize

Randomizes the string variable, str, and associative array, aa.

For strings, we randomize its length to be within a narrow range, then randomize each character to be within the range of printable characters.

For the associative array, we randomize its size (number of entries) to be within a narrow range, then randomize each key/value pair.

function void pre_randomize();
  int aa_size;
  int str_size;

  // randomize assoc array
  void'(std::randomize(aa_size) with { aa_size inside {[4:11]}; });
  aa.delete();
  for (int i=0; i < aa_size; i++) begin
    shortint key;
    shortint val;
    key = $urandom;
    val = $urandom;
    aa[key] = val;
  end

  // randomize string
  void'(std::randomize(str_size) with { str_size inside {[4:11]}; });
  str = "";
  for (int i=0; i < str_size; i++) begin
    byte ele;
    void'(std::randomize(ele) with { ele inside {[32:126]}; });
$sformat(str, "%s%x", str, ele); // str = {str, string'(ele)};
  end

  // allocate sub-object
  if (obj == null)
     obj = new("obj");

endfunction

pre_randomize

Provides rough randomization of real and shortreal fields by casting randomized integrals to reals then taking their quotients.

    function void post_randomize();
      // reals derive from quotient of two randomized ints
      real64 = real'(uint64) / real'(uint32);
    endfunction

endclass : packet

producer

A simple producer that generates packet transactions and sends them out its out blocking-put and ap analysis ports.

class producer extends uvm_component;

   uvm_blocking_put_port #(packet) out;
   uvm_analysis_port #(packet) ap;

   `uvm_component_utils(producer)

   function new(string name, uvm_component parent=null);
      super.new(name,parent);
      out = new("out", this);
      ap = new("ap", this);
   endfunction : new

   task run_phase (uvm_phase phase);

     packet pkt;

     phase.raise_objection(this);

     for (int i = 1; i <= NUM_PKTS; i++) begin
       pkt = new;
       assert(pkt.randomize());

       `uvm_info("PRODUCER/SEND",
          $sformatf("Sending packet #%0d",i),UVM_MEDIUM)
          pkt.print();

       ap.write(pkt);
       out.put(pkt);

     end

       `uvm_info("PRODUCER/STOP", "Stopping the test", UVM_LOW);

     phase.drop_objection(this);

   endtask

endclass
Summary
producer
A simple producer that generates packet transactions and sends them out its out blocking-put and ap analysis ports.
Class Hierarchy
ovm_component
producer
Class Declaration
class producer extends uvm_component

scoreboard

A simple scoreboard that implements in-order comparison of expect and actual packets.  The expect packets arrive via the expect_in analysis export.  They are stored in an internal FIFO for later comparison with incoming actual packets, which arrive on its actual_in analysis imp.  Strict comparison is performed as each actual arrives.

`uvm_analysis_imp_decl(_expect)
`uvm_analysis_imp_decl(_actual)

class scoreboard extends uvm_component;

   packet expect_q[$];
   uvm_analysis_imp_expect  #(packet, scoreboard) expect_in;
   uvm_analysis_imp_actual  #(packet, scoreboard) actual_in;

   `uvm_component_utils(scoreboard)

   function new(string name, uvm_component parent=null);
      super.new(name,parent);
      actual_in   = new("actual_in", this);
      expect_in   = new("expect_in", this);
   endfunction : new

   virtual function void write_expect(packet t);
     expect_q.push_back(t);
     if (run_ph.phase_done.get_objection_count(this) == 0)
       run_ph.raise_objection(this, "Scoreboard has expect packet");
   endfunction

   virtual function void write_actual(packet t);
     packet exp;

     `uvm_info("SCOREBD/RECV_ACTUAL",
            $sformatf("SV scoreboard recevied actual:\n  %p",t),UVM_HIGH);

     if (expect_q.size() == 0)
       uvm_report_fatal("SCOREBD/NO_EXPECT",
         $psprintf("%m: No expect packet to compare with incoming actual."));

     exp = expect_q.pop_front();

     if (!exp.compare(t))
       uvm_report_error("SCOREBD/MISCOMPARE",
         $psprintf("Actual does not match expect:\nexpect=%p\nactual=%p",
           exp,t));

     if (expect_q.size() == 0 && run_ph.phase_done.get_objection_count(this) != 0)
       run_ph.drop_objection(this, "Scoreboard has no more expect");

   endfunction

endclass
Summary
scoreboard
A simple scoreboard that implements in-order comparison of expect and actual packets.
Class Hierarchy
ovm_component
scoreboard
Class Declaration
`uvm_analysis_imp_decl(
    _expect
) `uvm_analysis_imp_decl(_actual) class scoreboard extends uvm_component

sv_main

module sv_main

Creates an instance of a producer and scoreboard, makes both native and cross-language connections using UVM Connect, then calls run_test.

module sv_main;

  import uvmc_pkg::*;

  producer prod = new("prod");
  scoreboard sb = new("sb");

  initial begin

    // expect path = normal TLM connection between producer and scoreboard
    prod.ap.connect(sb.expect_in);

    // actual path - SC-side consumer to SV-side scoreboard
    uvmc_tlm1 #(packet)::connect(prod.out,"foo");
    uvmc_tlm1 #(packet)::connect(sb.actual_in,"bar");

    run_test();

  end

endmodule
Summary
sv_mainCreates an instance of a producer and scoreboard, makes both native and cross-language connections using UVM Connect, then calls run_test.
class packet extends uvm_object
Defines a packet class containing a field for each of the data types supported by UVMC.
class producer extends uvm_component
A simple producer that generates packet transactions and sends them out its out blocking-put and ap analysis ports.
`uvm_analysis_imp_decl(
    _expect
) `uvm_analysis_imp_decl(_actual) class scoreboard extends uvm_component
A simple scoreboard that implements in-order comparison of expect and actual packets.
module sv_main
Creates an instance of a producer and scoreboard, makes both native and cross-language connections using UVM Connect, then calls run_test.
Converts this transaction’s contents into a form transferrable outside SystemVerilog.
Converts a bit-vector representation of a transaction into this transaction object.
Copies the values of fields from another object of the same type into this object.
Compares the values of fields with those of another object of the same type, returning 1 if a match, 0 otherwise.
Implements printing of all fields in this transaction using the provided printer policy class.
Records all members of this transaction class for later viewing in the GUI’s wave window.