UVM Interview Questions

  1. 1. What are TLM ports and exports?

In Transaction Level Modelling, different components or modules communicate using transaction objects. A TLM port defines a set of methods (API) used for a particular connection while the actual implementation of these methods are called TLM exports. A connection between the TLM port and the export establishes a mechanism of communication between two components.

Here is a simple example of how a producer can communicate to a consumer using a simple TLM port. The producer can create a transaction and “put” to the TLM port, while the implementation of “put” method which is also called TLM export would be in the consumer that reads the transaction created by the producer, thus establishing a channel of communication.

2. What are TLM FIFOs?

A TLM FIFO is used for Transactional communication if both the producing component and the consuming component need to operate independently. In this case (as shown below), the producing component generates transactions and “puts” into FIFO, while the consuming component gets one transaction at a time from the FIFO and processes it.

3. What is the difference between a get() and peek() operation on a TLM fifo?

The get() operation will return a transaction (if available) from the TLM FIFO and also removes the item from the FIFO. If no items are available in the FIFO, it will block and wait until the FIFO has at least one entry.

The peek() operation will return a transaction (if available) from the TLM FIFO without actually removing the item from the FIFO. It is also a blocking call that waits if FIFO has no available entry.

4. What is the difference between a get() and try_get() operation on a TLM fifo?

get() is ablocking call to get a transaction from TLM FIFO. Since it is blocking, the task get() will wait if no items are available in the FIFO stalling execution. On the other hand, try_get() is a nonblocking call which will return immediately even if no items are available in the FIFO. The return value of try_get() indicates if a valid item is returned or not.

Following are two equivalent implementations using get() and try_get()

1) Using the blocking method - get()

class consumer extends uvm_component;
  uvm_get_port #(simple_trans) get_port;
  task run;
    for(int i=0; i<10; i++) begin
      t = get(); //blocks until a transaction is
      //Do something with it.

2) Equivalent implementation using nonblocking method - try_get()

class consumer extends uvm_component;
  uvm_get_port #(simple_trans) get_port;
  task run;
    for(int i=0; i<10; i++) begin
      //Try get is nonblocking. So keep attempting
      //on every cycle until you get something
      //when it returns true
      while(!get_port.try_get(t)) begin
        wait_cycle(1); //Task that waits one clock cycle
     //Do something with it

5. What is the difference between analysis ports and TLM ports? And what is the difference between analysis FIFOs and TLM FIFOs? Where are the analysis ports/FIFOs used?

The TLM ports/FIFOs are used for transaction level communication between two components that have a communication channel established using put/get methods.

Analysis ports/FIFOs are another transactional communication channel which are meant for a component to distribute (or broadcast) transaction to more than one component.

TLM ports/FIFOs are used for connection between driver and sequencer while analysis ports/FIFOs are used by monitor to broadcast transactions which can be received by scoreboard or coverage collecting components.

6. What is the difference between a sequence and sequence item?

A sequence item is an object that models the information being transmitted between two components (sometimes it can also be called a transaction).

For Example: consider memory access from a CPU to the main memory where CPU can do a memory read or a memory write, and each of the transaction will have some information like the address, data and read/write type.

A sequence can be thought of a defined pattern of sequence items that can be send to the driver for injecting into the design. The pattern of sequence items is defined by how the body() method is implemented in sequence.

For Example: Extending above example, we can define a sequence of 10 transactions of reads to incremental memory addresses. In this case, the body() method will be implemented to generate sequence items 10 times, and send them to driver while say incrementing or randomizing address before next item.

7. What is the difference between a uvm_transaction and a uvm_sequence_item?

uvm_transaction is the base class for modelling any transaction which is derived from uvm_object .

A sequence item is nothing but a transaction that groups some information together and also adds some other information like: sequence id (id of sequence which generates this item), and transaction id (the id for this item), etc. It is recommended to use uvm_sequence_item for implementing sequence based stimulus.

8. What is the difference between copy(), clone(), and create() method in a component class?

1) The create() method is used to construct an object.

2) The copy() method is used to copy an object to another object.

3) The clone() method is a one-step command to create and copy an existing object to a new object handle. It will first create an object by calling the create() method and then calls the copy() method to copy existing object to the new handle.

9. What is the difference between get_name() and get_full_name() methods in a uvm_object class?

The get_name() function returns the name of an object, as provided by the name argument in the new constructor or set_name() method.

The get_full_name() returns the full hierarchical name of an object. For uvm_components, this is useful when used in print statements as it shows the full hierarchy of a component. For sequence or config objects that don’t have a hierarchy, this prints the same value as a get_name()

10. How is ACTIVE agent different from PASSIVE agent?

An ACTIVE agent is an agent that can generate activity at the pin level interface on which it operates. This means, the components like driver and sequencer would be connected and there would be a sequence running on it to generate activity.

A PASSIVE agent is an agent that doesn’t generate any activity but can only monitor activity happening on the interface. This means, in a passive agent the driver and sequencer will not be created.

An Agent is normally configured ACTIVE in a block level verification environment where stimulus is required to be generated. Same agent can be configured PASSIVE as we move from block level to chip level verification environment in which no stimulus generation is needed, but we still can use same for monitoring activity in terms of debug or coverage.

11. How is an Agent configured as ACTIVE or PASSIVE?

UVM agents have a variable of type UVM_ACTIVE_PASSIVE_e which defines whether the agent is active (UVM_ACTIVE) with the sequencer and the driver constructed, or passive (UVM_PASSIVE) with neither the driver nor the sequencer constructed. This parameter is called active and by default, it is set to UVM_ACTIVE. This can be changed using set_config_int() while the agent is created in the environment class. The build phase of the agent should then have the code as below to selectively construct driver and sequencer.

function void build_phase(uvm_phase phase);
if(m_cfg.active == UVM_ACTIVE) begin
//create driver, sequencer

12. What is the difference between a monitor and a scoreboard in UVM?

A monitor is a component that observes pin-level activity and converts its observations into transactions or sequence_items. It also sends these transactions to analysis components through an analysis port.

A scoreboard is an analysis component that checks if the DUT is behaving correctly. UVM scoreboards use analysis transactions from the monitors implemented inside agents.

13. What steps are needed to run a sequence?

There are three steps needed to run a sequence as follows:

1) Creating a sequence. A sequence is created using the factory create method as shown below:

my_sequence_c seq;
seq = my_sequence_c::type_id::create(“my_seq“)

2) Configuring or randomizing a sequence. A sequence might have several data members that might need configuration or randomization. Accordingly, either configure values or call seq.randomize()

3) Starting a sequence. A sequence is started using sequence.start() method. The start method takes an argument which is the pointer to the sequencer on which sequence has to be run. Once the sequence is started, the body() method in the sequence gets executed and it defines how the sequence operates. The start() method is blocking and returns only after the sequence completes execution.

14. What are pre_body() and post_body() functions in a sequence? Do they always get called?

pre_body() is a method in a sequence class that gets called before the body() method of a sequence is called. post_body() method in sequence gets called after the body() method is called.

The pre_body() and post_body() methods are not always called. The uvm_sequence::start() has an optional argument which if set to 0, will result in these methods not being called. Following are the formal argument of start() method in a sequence.

virtual task start (
uvm_sequencer_base sequencer, // Pointer to sequencer
uvm_sequence_base parent_sequencer = null, // parent sequencer
integer this_priority = 100, // Priority on the sequencer
bit call_pre_post = 1); // pre_body and post_body called

15. Is the start() method on a sequence blocking or nonblocking?

The start() method is a blocking call. It blocks execution until the body() method of the sequence completes execution.

16. How can a sequence get exclusive access to a sequencer?

When multiple sequences are run on a sequencer, the sequencer arbitrates and grants access to each sequence on a sequence item boundary. Sometimes a sequence might want exclusive access to sequencer until all the sequence items part of it are driven to the driver

(for example: if you want to stimulate a deterministic pattern without any interruption).

There are two mechanisms that allow a sequence to get exclusive access to sequencer.

1) Using lock() and unlock( ): A sequence can call the lock method of the sequencer on which it runs. The calling sequence will be granted exclusive access to the driver when it gets the next slot via the sequencer arbitration mechanism. If there are other sequences marked as a higher priority, this sequence needs to wait until it gets its slot. Once the lock is granted, no other sequences will be able to access the driver until the sequence issues an unlock() call on the sequencer which will then release the lock. The lock method is blocking and does not return until the lock has been granted.

2) Using grab() and ungrab(): The grab method is similar to the lock method and can be called by the sequence when it needs exclusive access. The difference between grab and lock is that when grab() is called, it takes immediate effect and the sequence will grab the next sequencer arbitration slot, overriding any sequence priorities in place. The only thing that can stop a sequence from grabbing a sequencer is an already existing lock() or grab() condition on the sequencer.

17. What is the difference between a pipelined and a non-pipelined sequence-driver model?

Based on how a design interface needs to be stimulated, there can be two modes implemented in an UVM driver class.

1) Non-pipelined model: If the driver models only one active transaction at a time, then it is called a non-pipelined model. In this case, the sequence can send one transaction to the driver and the driver might take several cycles (based on the interface protocol) to finish driving that transaction. Only after that, the driver will accept a new transaction from the sequencer

class nonpipe_driver extends uvm_driver #(req_c);
task run_phase(uvm_phase phase);
req_c req;
forever begin
get_next_item(req); // Item from sequence via sequencer
// drive request to DUT which can take more clocks
// Sequence is blocked to send new items til then
item_done(); // ** Unblocks finish_item() in sequence
endtask: run_phase
endclass: nonpipe_driver

2) Pipelined model: If the driver models more than one active transaction at a time, then it is called a pipelined model. In this case sequence can keep sending new transactions to driver without waiting for driver to complete a previous transaction. In this case, on every transaction send from the sequence, driver will fork a separate process to drive the interface signals based on that transaction, but will not wait until it is completed before accepting a new transaction. This modelling is useful if we want to drive back to back requests on an interface without waiting for responses from design.

class pipeline_driver extends uvm_driver #(req_c);
task run_phase(uvm_phase phase);
req_c req;
forever begin
get_next_item(req); // Item from sequence via sequencer
//drive request to DUT which can take more clocks
//separate thread that doesn’t block sequence
//driver can accept more items without waiting
item_done(); // ** Unblocks finish_item() in sequence
endtask: run_phase
endclass: pipeline_driver

18. How do we make sure that if multiple sequences are running on a sequencer-driver, responses are send back from the driver to the correct sequence?

If responses are returned from the driver for one of several sequences, the sequence id field in the sequence is used by the sequencer to route the response back to the right sequence. The response handling code in the driver should use the set_id_info() call to ensure that any response items have the same sequence id as their originating request.

Here is an example code in driver that gets a sequence item and sends a response back

(Note that this is a reference pseudo code for illustration and some functions are assumed to be coded somewhere else)

class my_driver extends uvm_driver;
//function that gets item from sequence port and
//drives response back
function drive_and_send_response();
forever begin
//function that takes req_item and drives pins
//create a new response item
rsp_item = new();
//some function that monitors response signals from dut
rsp_item.data = m_vif.get_data();
//copy id from req back to response
//write response on rsp port

19. What is the difference between early randomization and late randomization while generating a sequence?

In Early randomization, a sequence object is first randomized using randomize() call and then the sequence calls start_item() to request access to a sequencer, which is a blocking call and can take time-based upon how busy the sequencer is. The following example shows an object (req) randomized first and then sequence waits for arbitration

task body()
start_item(req); //Can consume time based on sequencer arbitration

In Late randomization, a sequence first calls start_item() , waits until arbitration is granted from the sequencer, and then just before sending the transaction to sequencer/driver, randomize is called. This has the advantage that items are randomized just in time and can use any feedback from design or other components just before sending an item to driver. Following code shows late randomization of a request (req)

task body()
start_item(req); //Can consume time based on sequencer arbitration

20. What is the difference between get_next_item() and try_next_item() methods in UVM driver class?

The get_next_item() is a blocking call (part of the driver-sequencer API) which blocks until a sequence item is available for the driver to process, and returns a pointer to the sequence item.

The try_next_item() is a nonblocking version which returns a null pointer if no sequence item is available for driver to process.

21. What is the difference between get_next_item() and get() methods in UVM driver class?

The get_next_item() is a blocking call to get the sequence item from the sequencer FIFO for processing by driver. Once the sequence item is processed by a driver, it needs to call item_done() to complete the handshake before a new item is requested using get_next_item().

The get() is also a blocking call which gets the sequence item from sequencer FIFO forprocessing by driver. However, while using get(), there is no need to explicitly call item_done() as the get() method completes the handshake implicitly.

22. What is the difference between get() and peek() methods of UVM driver class?

The get() method part of driver class is a blocking call which gets the sequence item from sequencer FIFO for processing by driver. It unblocks once an item is available and completes handshake with sequencer.

The peek() method is similar to get() and blocks until a sequence item is available. However, it will not remove the request from the sequencer FIFO. So calling peek() multiple times will return same sequencer item in driver.

23. What is the difference in item_done() method of driver-sequencer API when called with and without arguments?

The item_done() method is a nonblockingmethod in driver class that is used to complete handshake with the sequencer after a get_next_item() or try_next_item() is successful.

If there is no need to send a response back, item_done() is called with no argument which will complete the handshake without placing anything in the sequencer response FIFO. If there is a need to send a response back, item_done() is passed with a pointer to a response sequence_item as an argument. This response pointer will be placed in the sequencer response FIFO which can be processed by the sequence as a response to the request it drove.

24. Which of the following driver class methods are blocking calls and which are nonblocking?

1) get()

2) get_next_item()

3) item_done()

4) put()

5) try_next_item()

6) peek()

get(), get_next_item(), peek() are blocking calls.

try_next_item(), item_done(), and put() are nonblocking calls

25. How can you stop all sequences running on a sequencer?

The sequencer has a method stop_sequences() that can be used to stop all sequences. However, this method does not check if the driver is currently processing any sequence_items. Because of this, if the driver calls an item_done() or put(), there can be a Fatal Error as the sequence pointer might not be valid. So a user needs to take care of making sure that once stop_sequence() is called, the sequencer thread is disabled (if started in a fork).

26. Which method in the sequence gets called when user calls sequence.print() method?

convert2string() : It is recommended to implement this function which returns a string representation of the object (values of its data members). This is useful to get debug information printed to simulator transcript or log file.

27. How do we register an uvm_component class and uvm_sequence class with factory?

The uvm_sequence class is registered with the factory using uvm_object_utils() macro and passing the class name as an argument. An example below:

class test_seq_c extends uvm_sequence;

The uvm_component class is registered with the factory using uvm_component_utils() macro and passing the class name as argument. An Example below:

class test_driver_c extends uvm_component;

27. Why should we register a class with factory?

A factory is a special look-up table used in UVM for creating objects of component or transaction types. The benefit of object creation using the factory is that a testbench build process can decide at run-time which type of object has to be created. Based on this, a class type could be substituted with another derived class type without any real code change. To ensure this feature and capability, all classes are recommended to be registered with the factory. If you do not register a class with the factory, then you will not be able to use the factory method::type_id::create() to construct an object.

28. What is meant by factory override?

The UVM factory allows a class to be substituted with another derived class at the point of construction. This can be useful for changing the behavior of a testbench by substituting one class for another without having the need to edit or re-compile the testbench code.

29. What is the difference between instance override and type override?

A type override means that every time a component class type is created in a testbench hierarchy, a substitute type is created in its place. This applies to all instances of that component type. On the other hand, an instance override means, overriding only a specific instance of a component class. A specific instance of a component is referenced by the position of that component in the UVM component hierarchy. Since only UVM component classes can have a hierarchy in UVM test benches, only component classes can be overridden on an instance granularity while sequences (or UVM objects) can be the only type overridden.

30. Can instance override and type override be used for both uvm_component class and transaction types ?

No, only UVM_component classes are part of UVM testbench hierarchy and can be overridden on an instance granularity. The sequence items or sequences are not a part of UVM testbench hierarchy and hence can only be overridden using type override which will override all objects of that type.

31. What is the concept of objections and where are they useful?

The uvm_objection class provides a means for sharing a counter between multiple components and sequences. Each component/sequence may “raise” and “drop” objections asynchronously, which increases or decreases the counter value. When the counter reaches zero (from a non-zero value), an “all dropped” condition is said to occur. The objection mechanism is most commonly used in the UVM phasing mechanism to coordinate the end of each run-time phase. User-processes started in a phase raises an objection first and drops the objection once the process completes. When all processes in a phase drops the objects, the phase’s objection count goes to zero. This “all dropped” condition indicates to the phasing mechanism that every participant agrees the phase should be ended.

Following is an example of how a sequence ( my_test_sequence ) is started on a sequencer ( my_sequencer ) and the objection is dropped after sequence completes execution

task main_phase( uvm_phase phase);
phase.raise_objection( this );
phase.drop_objection( this );

32. How can we implement a simulation timeout mechanism in UVM methodology?

A simulation time out mechanism helps to stop the simulation if the test doesn’t progress because of some bug beyond a maximum time.

In UVM, set_global_timeout(timeout) – is a convenience function that sets uvm_top.phase_timeout variable with the timeout value. If the run() phase doesn’t end before this timeout value, then the simulation is stopped and an error is reported. This function is called in the top-level module which also starts the test as follows

module test;
initial begin
initial begin

33. What is the concept of phasing in UVM methodology?

Unlike a module-based testbench (in which all modules exist statically in a hierarchy), a class-based testbench needs to manage the creation of different objects and execution of various tasks and functions in those objects. Phasing is an important concept in class-based testbenches to have a consistent testbench execution flow. Test execution can be conceptually divided into the following tasks – configuration, creation of testbench components, runtime stimulus, and end of test checks. UVM defines standard phases for each of these as part of the methodology.

34. What are the different phases of a UVM component? What are the subphases for the UVM run_phase()?

UVM uses standard phases to order the major steps that take place during simulation. There are three groups of phases, which are executed in the following order.

1. Build phases – In the build phases; the testbench is configured and constructed. It has following sub-phases which are all implemented as virtual methods in uvm_component

base class.

1) build_phase()

2) connect_phase()

3) end_of_elaboration()

2. Run time phases – These phases can consume time and this is where most of the test execution happens.

1) start_of_simulation()

2) run_phase()

The run_phase() is further divided into 12 sub-phases as below:

1) pre_reset

2) reset

3) post_reset

4) pre_configure

5) configure

6) post_configure

7) pre_main

8) main

9) post_main

10) pre_shutdown

11) shutdown

12) post_shutdown

3. Clean up phase – This phase execute after the test ends and is used to collect, and report results and statistics from the test. This consists of following sub phases:

1) extract()

2) check()

3) report()

4) final()

35. Why is build_phase() executed top down in uvm_component hierarchy?

In UVM, all the testbench components like a test, Env, Agent, Driver, Sequencer are based on the uvm_component class and there is always a hierarchy for the testbench components. The build_phase() method is part of the uvm_component class and is used to construct all the child components from the parent component. So, to build the testbench hierarchy you always need to have a parent object first, which can then construct its children, and that can further construct its children using build_phase. Hence, build_phase() is always executed top-down.

For Example: The top level uvm_test class calls build_phase which should construct all the uvm_env components part of this test, and the build_phase() of each uvm_env class should construct all the uvm_agent components that are part of that uvm_env, and this goes on. For all other phases it really doesn’t matter in which order it is called. The run_phase() for all components runs in parallel.

36. What is the use of phase_ready_to_end() method in a uvm_component class?

phase_ready_to_end(uvm_phase phase) is a callback method available in a component class that gets called when all objections are dropped for that corresponding phase and the phase is going to end.

A component class can use this callback method to define any functionality that it needs to perform when the phase is about to end.

One example is if a component want to delay ending of phase until some condition even after all objections are dropped, it can be done using this callback method.

Another example is if an irritator or reactive sequence is running until a main sequence is complete, phase_ready_to_end() callback method in main_phase() can be used to stop those irritator sequences.

37. What is uvm_config_db and what is it used for?

The UVM configuration mechanism supports sharing of configurations and parameters across different testbench components. This is enabled using a configuration database called uvm_config_db. Any testbench component can populate the configuration database with variables, parameters, object handles, etc. Other testbench components can get access to these variables, parameters, object handles from the configuration database without really knowing where it exists in the hierarchy. For Example, a top-level testbench module can store a virtual interface pointer to the uvm_config_db. Then any uvm_driver or a uvm_monitor component can query the uvm_config_db to get a handle to this virtual interface and use it for actually accessing the signals through the interface.

38. How do we use the get() and set() methods of uvm_config_db?

The get() and set() are the primary methods used to populate or retrieve information from the uvm_config_db. Any verification component can use the set() method to populate the config_db with some configuration information and can also control which other components will have visibility to the same information. It could be set to have global visibility or visible only to one or more specific testbench components. The get() function checks for a shared configuration from the database matching the parameters.

The syntax for the get() and set() methods are as follows:

uvm_config_db#(<type>)::set(uvm_component context, string inst_name, string field_name,<type> value)
uvm_config_db#(<type>)::get(uvm_component context, string inst_name, string field_name, ref value)

The context specifies the current class or component from which get/set is called.

The inst_name is the name of the instance of component from which get/set is called.

The field_name is the name of the object or parameter or variable which is set/get in config_db.

The <type> identifies the type of the configuration information set/get in config_db. For object handles, this will have the class name while for other variables; it will be the type of that variable.

39. Is it possible for a component lower in testbench hierarchy to pass a handle to a component in higher level of hierarchy using get/set config methods?

This is not a recommended way of passing configuration objects in UVM. Normally the higher level component sets up configuration data base with handles and the lower level components do get them using get/set methods.

40. What is the recommended way of assigning virtual interfaces to different components in a UVM verification methodology?

The top-level testbench module which instantiates the DUT and interfaces will set the virtual interface in the uvm_config_db. A test class or any other component in the UVM component hierarchy can then query the uvm_config_db using the get() method to get handles to this virtual interface and use them for accessing signals. The following shows an example of how this is done. The module test actually instantiates a DUT and physical interface for an APB bus master. It then sets the virtual interface handle to the uvm_config_db.

module test;
logic pclk;
logic [31:0] paddr;
//Instantiate an APB bus master DUT
apb_master apb_master(.pclk(pclk),*);
//Instantiate a physical interface for APB interface
apb_if apb_if(.pclk(pclk), *);
initial begin
//Pass this physical interface to test class top
//which will further pass it down to env->agent->drv/sqr/mon
uvm_config_db#(virtual apb_if)::set(null, “uvm_test_top”, “vif”, apb_if);

Following shows a APB Env class that uses the get() method in uvm_config_db to
retrieve the virtual interface that was set in the top level test module.

class apb_env extends uvm_env;
//ENV class will have agent as its sub component
apb_agent agt;
//virtual interface for APB interface
virtual apb_if vif;
//Build phase - Construct agent and get virtual interface handle from test and
pass it down to agent
function void build_phase(uvm_phase phase);
agt = apb_agent::type_id::create(“agt”, this);
if (!uvm_config_db#(virtual apb_if)::get(this, ””, “vif”, vif))
`uvm_fatal(“config_db_err”, “No virtual interface specified for this env instance”)
uvm_config_db#(virtual apb_if)::set( this, “agt”, “vif”, vif);
endfunction: build_phase
endclass : apb_env

41. Explain how simulation ends in UVM methodology?

UVM has a phased execution which consists of a set of build phases, run phases and check phases. The run() phase is where the actual test simulation happens and during this phase every component can raise an objection in beginning and hold it until it is done with its activity. Once all components drops the objection, the run() phase completes and then check() phase of all components execute and then the test ends.

This is how a normal simulation ends, but there are also controls on simulation timeouts to terminate the run() phase if some component hangs due to a bug in design or testbench. When the run() phase starts, a parallel timeout timer is also started. If the timeout timer reaches one of the specified timeout limits before the run() phase completes, the run() phase will timeout, an error message will be issued and then all phases post run() will get executed and test ends after that.

42. What is UVM RAL (UVM Register Abstraction Layer)?

UVM RAL (Register Abstraction Layer) is a feature supported in UVM that helps in verifying the registers in a design as well as in configuration of DUT using an abstract register model.

The UVM register model provides a way of tracking the register content of a DUT and a convenience layer for accessing register and memory locations within the DUT. The register model abstraction reflects the structure of the design specification for registers which is a common reference for hardware and software engineers working on the design.

Some other features of RAL include support for both front door and back door initialization of registers and built in functional coverage support.

43. What is UVM Call back?

The uvm_callback class is a base class for implementing callbacks, which are typically used to modify or augment component behavior without changing the component class. Typically, the component developer defines an application-specific callback class that extends from this class and defines one or more virtual methods, called as callback interface. The methods are used to implement overriding of the component class behavior. One common usage can be to inject an error into a generated packet before the driver sends it to DUT. Following pseudo code shows how this can be implemented.

1) Define the packet class with an error bit

2) Define the driver class that receives this packet from a sequence and sends it to DUT

3) Define a driver callback class derived from the base uvm_callback class and add a virtual method which can be used to inject an error or flip a bit in the packet.

4) Register the callback class using `uvm_register_cb() macro 5) In the run() method of driver that receives and send the packet to the DUT, based on a probability knob, execute the callback to cause a packet corruption

class Packet_c;
byte[4] src_addr, dst_addr;
byte[] data;
byte[4] crc;
//User defined callback class extended from base class
class PktDriver_Cb extends uvm_callback;
function new (string name = “PktDriver_Cb”);
virtual task corrupt_packet (Packet_c pkt);
//Implement how to corrupt packet
//example - flip one bit of byte 0 in CRC
pkt.crc[0][0] = ~pkt.crc[0][0]
endclass : PktDriver_Cb
//Main Driver Class
class PktDriver extends uvm_component;
//Register callback class with driver
function new (string name, uvm_component parent=null);
virtual task run();
forever begin
`uvm_do_callbacks(PktDriver,PktDriver_Cb, corrupt_packet(pkt))
//other code to derive to DUT etc

44. What is uvm_root class?

The uvm_root class serves as the implicit top-level and phase controller for all UVM components. Users do not directly instantiate uvm_root. The UVM automatically creates a single instance of uvm_root that users can access via the global (uvm_pkg-scope) variable, uvm_top .

45. What is the parent class for uvm_test?

uvm_test class is the top-level class that a user can implement and there is no explicit parent that is mentioned. However, UVM has a special component called uvm_top and it is assigned as the parent of the test class.

These are the few UVM Interview Questions that might help you to crack Interviews or refresh concepts before any interviews. I didn’t cover a few topics in this section just because I already posted multiple conceptual posts in UVM sections so please refer to that as well. See you guys 🙂

Books to refer to learn UVM in more detail:

  1. Getting Started with UVM: A Beginner’s Guide by Vanessa Cooper – https://amzn.to/39H4PMF
  2. A Practical Guide to Adopting the Universal Verification Methodology (UVM) – https://amzn.to/3NapXZl
  3. The UVM Primer: A Step-by-Step Introduction to the Universal Verification Methodology by Ray Salemi – https://amzn.to/3QE2FOf
  4. Practical UVM: Step by Step Examples – https://amzn.to/3zYilGj
  5. Advanced UVM – https://amzn.to/3yayPda
  6. Cracking Digital VLSI Verification Interview: Interview Success – https://amzn.to/39Ec1Jy
  7. Writing Testbenches: Functional Verification of HDL Models – https://amzn.to/3yazivW
  8. Formal Verification: An Essential Toolkit for Modern VLSI Design – https://amzn.to/3QDPE7t

About the author

Avatar photo

The Art of Verification

Hi, I’m Hardik, and welcome to The Art of Verification.

I’m a Verification Engineer who loves to crack complex designs and here to help others commit to mastering Verification Skills through self-learning, System Verilog, UVM, and most important to develop that thought process that every verification engineer should have.

I’ve made it my mission to give back and serve others beyond myself.

I will NEVER settle for less than I can be, do, give, or create.

View all posts