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 bit | meaning |
---|---|
UVM_COPY / UVM_NOCOPY | Turn replication on / off |
UVM_PACK /UVM_NOPACK | Turn byte stream on / off |
UVM_ALL_ON | All functions are on |
UVM_PRINT / UVM_NOPRINT | Turn printing on / off |
2, And uvm_object related
For UVM_ For object, it mainly provides services for data operation, as listed below
function | meaning |
---|---|
copy | It is used for instance replication, such as B.copy(A), which copies the contents of A to B |
clone | Clone an object and create a new object if it does not exist |
Print content, such as B.print(), that is, print all variables and arrays registered in the field in the B object | |
pack_bytes | The variables and arrays registered in the domain are packaged into a byte stream and output to a dynamic array |
unpack_bytes | Pass in a dynamic array to convert all contents into fields in the class according to the order of registration in the domain |
pack | Package into bit stream |
unpack | Restore 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
type | describe |
---|---|
uvm_default_table_printer | By 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_printer | You can print object data in a tree structure |
uvm_default_line_printer | You 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.