Field automation - UVM

1, Fundamentals of field automation

1.1 core base class (uvm_void) and uvm_object relationship

The UVM world is made up of uvm_void derived, but this uvm_void is just a virtual class. Member variables and methods wait for the following subclasses to expand. UVM here_ Object inherits uvm_void, in addition to this, there is UVM_ port_ Base < if >, which is the parent of transaction interface class. The focus here is not on it. Let's focus on UVM first_ object.

1.2 field automation related macros

`define uvm_field_int(arg,FLAG) //integer
`define uvm_field_enum(T,arg,FLAG) //Enumeration type
`define uvm_field_object(arg,FLAG)
`define uvm_field_string(arg,FLAG)//character string
`define uvm_field_array_int(arg,FLAG)//dynamic data 
`define uvm_field_queue_int(arg,FLAG)//queue
  • arg: variable
  • T: Type, which can be integer type, enumeration type, string, queue, etc.
  • FLAG: FLAG bit, let's talk about it
Flag bitmeaning
UVM_COPY / UVM_NOCOPYTurn replication on / off
UVM_PACK /UVM_NOPACKTurn byte stream on / off
UVM_ALL_ONAll functions are on
UVM_PRINT / UVM_NOPRINTTurn printing on / off

2, And uvm_object related

For UVM_ For object, it mainly provides services for data operation, as listed below

functionmeaning
copyIt is used for instance replication, such as B.copy(A), which copies the contents of A to B
cloneClone an object and create a new object if it does not exist
printPrint content, such as B.print(), that is, print all variables and arrays registered in the field in the B object
pack_bytesThe variables and arrays registered in the domain are packaged into a byte stream and output to a dynamic array
unpack_bytesPass in a dynamic array to convert all contents into fields in the class according to the order of registration in the domain
packPackage into bit stream
unpackRestore bit stream attention to class

2.1 copy

The copy of field automation only copies the variables and object s registered with field automation

class cfg1 extends uvm_object; 
  int id;  
  ....
endclass 
//=============================================================== 
class cfg2 extends uvm_object; 
  int id;
  cfg1 sub_cfg; 
  function new(string name = "cfg");
    super.new(name);
    sub_cfg = cfg1::type_id::create("sub_cfg");
  endfunction  
endclass 
//=============================================================== 
class ref extends uvm_component;
   ...
    cfg2 o_cfg;
    cfg1 s_cfg; 
   virtual function void build_phase(uvm_phase phase)
     super.build_phase(phase);
     if(!uvm_config_db#(cfg2)::get(this,$sformatf("o_cfg2"),"cfg",o_cfg))
       `uvm_fatal(...) 
     s_cfg = o_cfg.sub_cfg;//Saved sub_cfg handle     
   endfunction
   /*The first method calls the copy function to update cfg2
   virtual task run_phase(uvm_phase phase)
     o_cfg_ary = cfg2::type_id::create("o_cfg_ary");
     o_cfg_ary.sub_cfg.id = 2;
     o_cfg.copy(o_cfg_ary);
   endtask
     */
   //In order not to keep the handle address unchanged, the copy function is called recursively to update only the ID
   o_cfg_ary = cfg2::type_id::create("o_cfg_ary");
   o_cfg_ary.sub_cfg.id = 2;
   o_cfg.copy(o_cfg_ary);
   o_cfg.sub_cfg.copy(o_cfg_ary.sub_cfg);//Recursive call to copy function  
endclass
  • cfg2 contains objects with id and cfg1. If the first method is used to call the copy function to update cfg2, O_ After CFG is updated, ref still gets the original value, which is due to the object sub in cfg2 at the time of copying_ CFG will reallocate memory space, that is, the handle address will change
  • Usually in build_phase use Config_db passes the handle. Once it is passed, if the handle changes halfway, for example, in the above example, I wanted to use copy function to change the ID (ensure that the address of the handle remains unchanged and only the content is updated), but o_ cfg. sub_ The handle address of CFG changes, and ref saves sub_ The address of CFG handle causes the class that needs to get the handle to still point to the original memory space.
  • If sub is used in ref_ CFG is o_cfg.sub_cfg.id and copy functions are not affected (even if the handle address of o_cfg.sub_cfg changes)
  • If "s_cfg = o_cfg.sub_cfg" in ref saves sub_cfg handle address, but o_ cfg. sub_ The handle address of CFG has changed and cannot point to sub_ CFG the newly allocated space will not get the updated value;

2.2 compare

source code

function bit uvm_object::compare(uvm_object rhs,
                                 uvm_comparer comparer = null);
     bit t,dc;
     static int style;
     bit done;
     done= 0;
    ...
endfunction
  • Generally, you only need to add rhs(right hand side). If the user defines uvm_comparer, you need to add a custom comparer when calling When you call compare, you will first confirm whether the comparer parameter is null; If it is null, call the default uvm built-in comparer. If it is not null, call the user-defined comparer
if(comparer != null)
  m_uvm_status_container.comparer = comparer;
else
  m_uvm_status_container.comparer = uvm_default_comparer;
  comparer = m_uvm_status_container.comparer;
  • M will be called when compare_ uvm_ field_ Automation and do_compare function. When returning the result, judge the comparer The values of result and dc; By default, if there is no custom do in xaaction_ Compare function, then UVM will be called_ Do in object base class_ Compare its return value is 1; If do is customized in xaction_ Compare function, then the user decides when to return 1 and when to return 0
if(!done)begin
  comparer.compare_map.set(rhs,this);
  m_uvm_field_automation(rhs,UVM_COMPARE,"");
  dc = do compare(rhs,comparer);
end
if(rhs != null)
  comparer.print_rollup(this,rhs);
  return(comparer.result == 0 && dc == c);

2.3 print

Three printers are built in the UVM

typedescribe
uvm_default_table_printerBy default, in UVM_ default_ table_ The information printed in the printer is based on name and type_ Printed by name, size, value
uvm_default_tree_printerYou can print object data in a tree structure
uvm_default_line_printerYou can print object data onto a line

2.4 clone

The clone() method creates and returns a copy of the object, that is, clone = create() + copy()

class mem_seq_item extends uvm_sequence_item;
  //data and control fields
  rand bit [3:0] addr;
  rand bit       wr_en;
  rand bit       rd_en;
  rand bit [7:0] wdata;
       bit [7:0] rdata;
   
  //Utility and Field macros,
  `uvm_object_utils_begin(mem_seq_item)
    `uvm_field_int(addr,UVM_ALL_ON)
    `uvm_field_int(wr_en,UVM_ALL_ON)
    `uvm_field_int(rd_en,UVM_ALL_ON)
    `uvm_field_int(wdata,UVM_ALL_ON)
  `uvm_object_utils_end
   
  //Constructor
  function new(string name = "mem_seq_item");
    super.new(name);
  endfunction
   
  //constaint, to generate any one among write and read
  constraint wr_rd_c { wr_en != rd_en; };
   
endclass
 
//-------------------------------------------------------------------------
//Simple TestBench to access sequence item
//-------------------------------------------------------------------------
module seq_item_tb;
   
  //instance
  mem_seq_item seq_item_0;
  mem_seq_item seq_item_1;
   
  initial begin
    //create method
    seq_item_0 = mem_seq_item::type_id::create("seq_item_0");
     
    seq_item_0.randomize(); //randomizing the seq_item  
    seq_item_0.print();     //printing the seq_item_0
     
    //clone method
    $cast(seq_item_1,seq_item_0.clone()); // Assign the result conversion after the clone() method is executed to SEQ_ item_ one
//The biggest difference from copy() above is that you don't need to create another seq_item_1 object, you only need to create a handle
     
    //changing the seq_item_1 values will not reflect on seq_item_0 values.
    seq_item_1.addr  = 8;
    seq_item_1.wdata = 'h56;//Change seq_ item_ Members of 1
    `uvm_info("","Printing seq_item_0", UVM_LOW)
    seq_item_0.print();          //printing the seq_item_0
    `uvm_info("","Printing seq_item_1", UVM_LOW)
    seq_item_1.print();          //printing the seq_item_1
     
    //Note:: name of seq_item_1 will be printed as seq_item_0, because there is no option to pass argument to create method while calling the clone method.
  end 
endmodule

2.5 pack

2.5.1 driver packed byte stream

`ifndef GMII_RX_TRANSACTION_SV
`define GMII_RX_TRANSACTION_SV

typedef enum { RANDOM,INCR,FIXED } payload_kind
class gmii_rx_transaction extends uvm_sequence_item;
	rand bit [2:0]  spa;
	rand bit [47:0] dmac;
	rand bit [47:0] smac;
	rand bit [15:0] ether_type;
	rand bit [7:0]  payload[];
	
	//For combining payload s
	rand int        pkt_len; 
	rand bit [7:0]  first_data; 
	rand payload_kind kind;  
	
	`uvm_object_utils_begin(gmii_rx_transaction)
		`uvm_field_int(sap,UVM_ALL_ON | UVM_NOPACK)
		`uvm_field_int(dmac,UVM_ALL_ON)
		`uvm_field_int(smac,UVM_ALL_ON)
		`uvm_field_int(ether_type,UVM_ALL_ON)
		`uvm_field_array_int(payload,UVM_ALL_ON)
		`uvm_field_int(pkt_len,UVM_ALL_ON | UVM_NOPACK) 
		`uvm_field_int(first_data,UVM_ALL_ON | UVM_NOPACK)
		`uvm_field_enum(payload_kind,kind,UVM_ALL_ON | UVM_NOPACK)  		
	`uvm_object_utils_end
	...
endclass

`endif

Here, by sending Ethernet packet, the data needs to reach the bus in byte s.

//Definition of driver
class gmii_rx_driver extends uvm_driver#(gmii_rx_transaction);
	byte unsigned data_a[];  #Used to save byte stream
	...
endclass

task gmii_rx_driver::run()
	...
	seq_item_port.get_next_item(req);
	req.pack_bytes(data_a);  #Save byte stream data to data_a in a dynamic array
	foreach( data_a[i] )begin
		vif.drv_cb.RXD <= data_a[i];  //Output the data to the bus by byte
	end
	...
endtask

  • The driver needs to define a byte type dynamic array as a member variable to store the transaction transmitted by the sequencer;
  • Through get_next_item tells the sequencer to get the transaction, and then packages the req into data_a medium.
  • Then use the forever loop to convert the data_a put it on the bus.
  • Data sent_ A [] is sorted from high to low, for example, send the maximum value first and then the minimum value;

2.5.2 monitor packed byte stream

class gmii_rx_monitor extends uvm_monitor#(gmii_rx_transaction);
	gmii_rx_transaction trans;
	byte unsigned data_q[$]; //Used to save data read from the bus
	byte unsigned data_a[];  //Used to convert byte streams
	...
endclass

task gmii_rx_driver::run();
	@(posedge vif.rstn);
	trans = new("trans");
	while(vif.mon_cb.RX_DV )begin
		data_q.push_back(vif.mon_cb.RXD); //Read data on the bus
		@(posedge vif.clk);
	end
	data_a = new[data_q.size()];
	for(int i = 0;i <data_q.size( ) ; i++ )begin
		data_a[i] = data_q.pop_front(); //Store data in a dynamic array
	end
	trans.payload = new[data_a.size() - 14];
	trans.pkt_len = trans.unpack_bytes(data_a) / 8; //Data_ The byte stream in a is converted into various fields in the transaction	
endtask

  • The member variable needs a byte queue to save the data read by the bus;
  • Then the byte stream must be converted and saved with a dynamic array

3, And uvm_component related

3.1 and uvm_objecet differences

Although uvm_component derived from uvm_object, in theory, the former should have all the characteristics of the latter, but because the component exists as a node of the UVM tree, it loses some UVM_ The nature of the object; As the clone() method above, it is used to convert uvm_object allocates a piece of memory and copies the instance object to this new piece of memory, but the component cannot use the clone() method, because once used, the parent parameter of the new class cannot be specified!
However, because clone=new+copy, the object has been created and instantiated before using the copy() method, so the component can use the copy method!
In addition, for different component s under the same parent node, you cannot use the same instance name when creating objects, otherwise an error will be reported!

3.2 uvm_component domain automatic registration method

	`uvm_component_utils_begin(my_component)
		`uvm_field_int(data,UVM_ALL_ON )
		...
	`uvm_component_utils_end

3.3 omit config_db#(T)get()

int pre_num;
 `uvm_component_utils_begin(my_driver)
 `uvm_field_int(pre_num, UVM_ALL_ON)
 `uvm_component_utils_end

 function new(string name = "my_driver", uvm_component parent = null);
 super.new(name, parent);
 pre_num = 3;
 endfunction

 virtual function void build_phase(uvm_phase phase);
 `uvm_info("my_driver", $sformatf("before super.build_phase, the pre_num is %0d", pre_num), UVM_LOW)
 super.build_phase(phase);
 `uvm_info("my_driver", $sformatf("after super.build_phase, the pre_num is %0d", pre_num), UVM_LOW)
 if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
 `uvm_fatal("my_driver", "virtual interface must be set for vif!!!")
 endfunction

The key here is build_ Super in phase build_phase statement, when executed to the driver's super build_phase, the get statement is automatically executed. Before this practice
First, my_driver must use uvm_component_utils macro registration; Second, pre_num must use uvm_field_int macro registration; Third, call the set function
The third parameter of the set function must be consistent with the name of the variable in the get function, that is, it must be pre_num.

Keywords: uvm

Added by cgraz on Sun, 09 Jan 2022 15:27:59 +0200