In my personal opinion, one of the most appealing word in UVM is “Factory”. It stimulates our thinking & our imagination to fall down on industrial settings and mechanical setup of machinaries & processing units/pipes with products getting made and packaged before running on the transport rail & to be delivered to the warehouses. Hence the word “Factory” makes us curious to know about this jargon in Verification Methodology Context i.e. UVM.
Since UVM primarily deals with the the high level of data abstraction e.g. Transactions represented by OOPs based Classes, hence it looks like Factory has to do something with OOPs based components & objects, their generation and/or replacements in the UVM based Verification Environment. Lets dig deep into “Factory” & know more about it?
First of all, its important to understand that as per the recommended UVM methodology, we should never construct new components and/or transactions using new() class constructor. Instead it is recommended that – we should make calls to a look-up table to create the requested components and transactions. This special look-up table is called “Factory” in UVM. Entries to this look-up table is made by registering the components and/or transactions while defining them. To create a component/transaction using Factory, create() command is used. We’ll see all the process steps in detail using the applicable UVM code below.
From the application point of view, UVM Factory facilitates an object of one type to be substituted with an object of derived type without having to change the structure of the Testbench or modify the Testbench code. This behavior is called “overriding” and there are 2 types of overriding is possible with UVM Factory (described below). Overriding helps to replace one transaction/sequence with another to generate new scenarios or conditions without making any change in the Testbench structure/code. Similarly, new version of the components can be brought into the Testbench without any change in the structure of the Testbench & beauty of all this is that – it happens all the fly at the run time.
To achieve & enable this capability of the UVM Testbench, it is required to follow certain steps while defining & creating our components/sequences/transactions. Lets go through these required steps:
1. Factory Registration:
Every UVM component or transactions must contain Factory registration code & this registration code can be generated by using Factory registration macros, as shown below in the code:
//// Registration for a component class my_comp extends uvm_component; `uvm_component_utils(my_comp) ... ... ... endclass: my_comp
It is recommended to extend your component class from UVM base classes available e.g uvm_env, uvm_agent, uvm_driver, uvm_monitor, uvm_sequencer etc. for the the corresponding physical component.
//// Registration for a sequence class my_seq extends uvm_sequence #(my_txn); `uvm_object_utils(my_seq) ... ... ... endclass: my_seq //// Registration for a transaction class `uvm_object_utils(my_txn) ... ... ... endclass: my_txn
After registration macro usage, another important coding convention for UVM Factory is the constructor syntax in components and objects, let see it next:
2. Default Constructor:
Since uvm_component and uvm_object constructors are virtual methods hence user have to follow their prototype template. As we know that in UVM components/objects are constructed during build phase but Factory constructor should contain default arguments in the definition of the components/objects. This allows Factory registered component/object to be created inside Factory initially & later to be re-assigned to the class properties passed via the create() command as arguments. The default arguments are different for components and objects.
class my_comp extends uvm_component; `uvm_component_utils(my_comp) // Component Default Constructor function new (string name = "my_comp", uvm_component parent = null); super.new(name, parent); endfunction: new endclass: my_comp class my_seq extends uvm_seqeuence #(my_txn); `uvm_object_utils(my_seq) // Sequence Default Constructor function new (string name = "my_seq"); super.new(name); endfunction: new endclass: my_seq class my_txn extends uvm_sequence_item; `uvm_object_utils(my_txn) // Transaction Default Constructor function new (string name = "my_txn"); super.new(name); endfunction: new endclass: my_txn
3. Component & Object Creation:
Using Factory, hierarchically below components/objects (childs) are created by the immediate higher components/objects (parent). Hence, next goal would be the Factory supported component and object creation code entry for the child components inside the parent components and objects. Lets see how to do that –
class my_agent extends uvm_agent; `uvm_component_utils(my_agent) my_driver drvr; my_monitor moni; // Component Constructor function new (string name = "my_agent", uvm_component parent = null); super.new(name, parent); endfunction: new // Build Phase function void build_phase (uvm_phase phase); super.build_phase(phase); drvr = my_driver::type_id::create("drvr", this); moni = my_monitor::type_id::create("moni", this); endfunction: build_phase endclass: my_agent class my_driver extends uvm_driver #(my_txn); `uvm_component_utils(my_driver) // Driver Constructor function new (string name = "my_driver", uvm_component parent = null); super.new(name, parent); endfunction: new // Run Task task run_phase (uvm_phase phase); my_txn txn; txn = my_txn::type_id::create("txn", this); .......; endtask: run_phase endclass: my_driver
As described above regarding “overriding” behavior of Factory, here lets elaborate it in detail now –
As we discussed it before, The UVM Factory can be thought of as a look-up table, so when normal component construction takes place using <type>::type_id::create(“<name>”, <parent>); approach, what happens is that the type_id is used to pick up the Factory component wrapper for the class, construct its contents & pass the resultant handle back again to the LHS.
The Factory override changes the way in which lookup happens so that looking up the original type_id results in a different type_id being used. Consequently, a handle to a different type of constructed object is returned. This technique is primarily based on Polymorphism which is the ability to be able to refer to the derived types using a base type handle. In practice, an override will only work if the parent class is being overridden by one of its derived classes.
Now lets discuss the supported 2 types of overriding:
Type Overriding:
A type overriding means that every time a component class type is created in the Testbench hierarchy, a substitute type i.e. derived class of the original component class, is created in its place. It applies to all the instances of that component type.
Syntax:
<original_type>::type_id::set_type_override(<substitute_type>::get_type(), replace);
where “replace” is a bit which is when set equals to 1, enables the overriding of an existing override else existing override is honoured.
Example UVM Code:
class my_driver extends uvm_driver #(my_txn); `uvm_component_utils(my_driver) .. .. endclass: my_driver class my_updated_driver extends my_driver #(my_txn); `uvm_component_utils(my_updated_driver) .. .. endclass: my_updated_driver class my_agent extends uvm_agent; `uvm_component_utils(my_agent) my_driver drvr; // Agent Constructor function new (string name, uvm_component parent); super.new(name, parent); endfunction: new // Agent Build Phase function void build_phase (uvm_phase phase); super.build_phase(phase); drvr = my_driver::type_id::create("drvr", this); endfunction: build_phase endclass: my_agent class my_test extends uvm_test; `uvm_component_utils(my_test) env e; function new (string name, uvm_component parent); super.new(name, parent); endfunction: new function void build_phase(uvm_phase phase); super.build_phase(phase); my_driver::type_id::set_type_override(my_updated_driver::get_type(),1); e = env::type_id::create("e", this); endfunction: build_phase task run_phase (uvm_phase phase); ... ... ... endtask: run_phase endclass: my_test
An important point to note here is the order of 2 commands i.e. set_type_override() to be placed before the create() command inside the build_phase() of the my_test class. Only with this order of commands the substitution will get into effect. In case the order is reversed, original driver in the code i.e. my_driver will be constructed instead of the intended driver i.e. my_updated_driver.
Instance Overriding:
In Instance Overriding, as name indicates it substitutes ONLY a particular instance of the component OR a set of instances with the intended component. The instance to be substituted is specified using the UVM component hierarchy.
Syntax:
<original_type>::type_id::set_inst_override(<substitute_type>::get_type(), <path_string>);
Where “path_string” is the hierarchical path of the component instance to be replaced.
Example UVM Code:
We can use the same code as mentioned above for the “Type Override” except the following highlighted line:
function void build_phase(uvm_phase phase);
super.build_phase(phase);
my_driver::type_id::set_inst_override(my_updated_driver::get_type(), "top.e.agent.drvr");
e = env::type_id::create("e", this);
endfunction: build_phase
Objects or sequence related objects are generally only used with type overrides since the instance override approach relates to a position in the UVM Testbench component hierarchy which objects do not take part in.
Debugging the UVM Testbench Structure & Factory Content:
After assembling a rather complex UVM Testbench Environment, it is often useful to print out the structure of the testbench in tabular form and to query the types that were registered with the Factory. A great technique to view the structural composition of the Testbench classes and the Factory setup is to call the this.print() and factory.print() methods in the end_of_elaboration_phase() (as shown in Code below) from the top-level testbench. By the time the end_of_elaboration_phase() executes, the entire environment has already been built and connected, so these print() methods show what had been built in the Testbench and the types that were registered with the factory.
function void end_of_elaboration_phase(uvm_phase phase); super.end_of_elaboration_phase(phase); this.print(); factory.print(); endfunction: end_of_elaboration_phase
At this point of time, I’m going to pen down & conclude this topic about UVM Factory. I hope this shall provide you friends a fair amount of information and wish you would be able to get benefited by this post. Thank you for your time to go through it. Please feel free to write down your feedback and/or suggestions.
I wish all the success in your work & life, See you again with another topic soon..till then take care, Bye!
Kudos!! for a very well explained and demontrated piece, cleared many of my doubts.
Hi,
Very much like this article. I invite you to join and post similar articles on Verification Management Group on LinkedIn where I am the moderator: https://www.linkedin.com/grp/home?gid=3403447
Thanks.
Dear sir,
Thank you the information sharing.
I did some exercise using the reference flow 1.1 from accellera.org
I add “uvm_top.print_topology();” in the beginning of run_phase.
But the table didn’t print the cfg’s “value”.
Is the sequencer some thing special?
class uart_ctrl_virtual_sequencer extends uvm_sequencer;
apb_pkg::apb_master_sequencer apb_seqr;
uart_pkg::uart_sequencer uart_seqr;
uart_ctrl_reg_sequencer reg_seqr;
// UVM_REG: Pointer to the register model
uart_ctrl_reg_model_c reg_model;
// Uart Controller configuration object
uart_ctrl_config cfg;
function new (input string name=”uart_ctrl_virtual_sequencer”, input uvm_component parent=null);
super.new(name, parent);
endfunction : new
`uvm_component_utils_begin(uart_ctrl_virtual_sequencer)
`uvm_field_object(cfg, UVM_DEFAULT | UVM_NOPRINT)
`uvm_field_object(reg_model, UVM_DEFAULT | UVM_REFERENCE)
`uvm_component_utils_end
endclass : uart_ctrl_virtual_sequencer
100 kudos to you!!! Its a amazing blog and very clear explanation about UVM Factory. Thank you very much.
Few doubts I have regarding the types of overrides as mentioned below
1. set_inst_override_by_type
2. set_inst_override_by_name
3.set_type_override_by_type
4.set_type_override_by_name
example
factory.set_type_override_by_type(driver::get_type(),driver_2::get_type(),”*”);
factory.set_type_override_by_name(“monitor”,”monitor_2″,”*”);
these are differs from what you have explained above. Please provide some comments on my doubts.
Amazing blog! Do you have any suggestions for aspiring writers?
I’m planning to start my own website soon but I’m a little lost on everything.
Would you advise starting with a free platform like WordPress or go for a paid option? There are so many choices out there that I’m totally confused
.. Any suggestions? Many thanks!
Hey,
Thanks for sharing your knowledge with us!
You wrote “Since uvm_component and uvm_object are virtual method”.
I’m a little confused. uvm_component and uvm_object are not classes?
He means to say:
“Since uvm_component and uvm_object (have)* virtual method”.
Great article!!
Thank you, Sir.
This is the best explanation of the UVM factory for the beginners.
Nice article.Good information
unfortunately , came to know about this blog very lately ….Very good work..Thanks
Thanks Tanajirao!
Thanks for sharing useful Information in such simple wordings!
Very well explained. Short and crispy to the point. Love it
Thankfulness to my father who informed me about this weblog, this blog is really amazing.
Great Work! Thanks for the effort.