UVM Interview Handbook
UVM Interview Handbook
UVM Interview Handbook
HANDBOOK
Basic Level Questions
1. Why do we need UVM methodology?
The Universal Verification Methodology (UVM) is a standardized methodology for
verifying digital designs that helps develop a reusable, modular, and well-structured
verification test bench. UVM provides pre-defined base classes and allows users to
extend and reuse pre-defined methods.
UVM uses a set of rules and approaches for verification that allows different teams to
collaborate more effectively and understand each other’s code. This helps to improve
communication across teams.
UVM also provides standardized ways like verbosity control, phases, analysis ports for
communication, pre-built components like uvm_driver, uvm_monitor, uvm_env, etc
The create() method of the wrapper class is used to create objects for the uvm_object
and uvm_component class which is commonly known as factory registration.
In UVM based testbench, it is valid to use a new() function to create class objects, but
factory registration has its benefits. The UVM factory allows an object of one type to be
overridden with an object of its derived type without changing the testbench structure.
This is known as the UVM factory override mechanism. This is applicable for uvm objects
and components.
The clone method is called the create() method followed by copy(). Thus, it creates and
returns a copy of an object.
1. While using copy mtheod we will make sure object for destination handle already
created
after the copy the object which is created it will return that
object reference through the parent handle.
Levels:
The test execution is a time-consuming activity that runs sequence or sequences for
DUT functionality. On executing the test, it builds a complete UVM testbench structure
in the build_phase and time consuming activities are performed in the run_phase with
the help of sequences. It is mandatory to register tests in the UVM factory otherwise,
the simulation will terminate with UVM_FATAL (test not found).
run_test() task
The run_test task is a global task declared in the uvm_root class and responsible for
running a test case.
Declaration:
virtual task run_test (string test_name = "")
1. The run_test task starts the phasing mechanism that executes phases in a pre-
defined order.
2. It is called in the initial block of the testbench top and accepts test_name as a string.
Example:
// initial block of tb_top
initial begin
run_test("my_test");
end
4. After execution of all phases, run_test finally calls $finish task for simulator exit.
10. Write a simple uvm sequence template and explain each line.
UVM Sequence
UVM sequence is a container that holds data items (uvm_sequence_items) which are
sent to the driver via the sequencer.
task body();
...
endtask
endclass
Why `uvm_object_utils is used in sequence, why `uvm_sequence_utils are not
used?
Along with a body() method, pre_body, and post_body methods are called by default.
class my_sequence extends uvm_sequence #(my_seq_item);
`uvm_object_utils(my_sequence)
task pre_body();
...
endtask
task body();
...
endtask
task post_body();
...
endtask
endclass
Note:
The analysis port is used to connect the uvm monitor to the scoreboard to send the
sequence items or transactions for comparison.
Intermediate level questions
1. What are the UVM factory and its use?
The UVM factory is used to create UVM objects and components. This is commonly
known as factory registration. The factory registration for UVM objects and components
are lightweight proxies of the actual objects and components.
In UVM based testbench, it is valid to use a new() function to create class objects, but
factory registration has its benefits. The UVM factory allows an object of one type to be
overridden with an object of its derived type without changing the testbench structure.
This is known as the UVM factory override mechanism. This is applicable for uvm objects
and components.
2. Why phases are introduced in UVM? What are all phases present
in UVM?
All components are static in the Verilog-based testbench whereas System Verilog
introduced the OOP (Object Oriented Programming) feature in the testbench. In Verilog,
as modules are static, users don’t have to care about their creation as they would have
already created at the beginning of the simulation. In the case of UVM based System
Verilog testbench, class objects can be created at any time during the simulation based
on the requirement. Hence, it is required to have proper synchronization to avoid
objects/components being called before they are created, The UVM phasing mechanism
serves the purpose of synchronization.
1. build_phase
2. connect_phase
3. end_of_elaboration_phase
4. start_of_simulation_phase
5. run_phase
6. pre_reset
7. reset
8. post_reset
9. pre_configure
10. configure
11. post_configure
12. pre_main
13. main
14. post_main
15. pre_shutdown
16. shutdown
17. post_shutdown
18. extract
19. check
20. report
21. final
`include "uvm_macros.svh"
import uvm_pkg::*;
module tb_top;
bit clk;
bit reset;
always #5 clk = ~clk;
initial begin
clk = 0;
reset = 1;
#5;
reset = 0;
end
add_if vif(clk, reset);
initial begin
// set interface in config_db
uvm_config_db#(virtual add_if)::set(uvm_root::get(), "*", "vif", vif);
// Dump waves
$dumpfile("dump.vcd");
$dumpvars;
end
initial begin
run_test("base_test");
end
endmodule
UVM Description
testbench
UVM Test The test is at the top of the hierarchy that initiates the environment
component construction. It is also responsible for the testbench
configuration and stimulus generation process.
UVM Agent An agent is a container that holds the driver, monitor, and sequencer.
This is helpful to have a structured hierarchy based on the protocol or
interface requirement
UVM Driver The driver interacts with DUT. It receives randomized transactions or
sequence items and drives them to the DUT as a pin-level activity.
UVM The scoreboard receives the transaction packet from the monitor and
Scoreboard compares it with the reference model. The reference module is
written based on design specification understanding and design
behavior.
5. What all UVM phases take time?
All run phases (including its sub-phases) take time.
1. run_phase
2. pre_reset
3. reset
4. post_reset
5. pre_configure
6. configure
7. post_configure
8. pre_main
9. main
10. post_main
11. pre_shutdown
12. shutdown
13. post_shutdown
a. build_phase
b. connect_phase
c. end_of_elaboration_phase
Run-time phases
Once testbench components are created and connected in the testbench, it follows run
phases where actual simulation time consumes. After completing, start_of_simulation
phase, there are two paths for run-time phases. The run_phase and pre_reset phase
both start at the same time.
a. run_phase
task run_phase Used for the stimulus generation, checking activities of the
testbench, and consumes simulation time cycles. The run_phase
for all components are executed in parallel.
Clean up phases
The clean-up phases are used to collect information from functional coverage monitors
and scoreboards to see whether the coverage goal has been reached or the test case
has passed. The cleanup phases will start once the run phases are completed. They are
implemented as functions and work from the bottom to the top of the component
hierarchy. The extract, check, and report phase may be used by analysis components.
function extract Used to retrieve and process the information from Bottom to
functional coverage monitors and scoreboards. This top
phase may also calculate any statistical information
that will be used by report_phase.
function check Checks DUT behavior and identity for any error that Bottom to
occurred during the execution of the testbench. top
function report Used to display simulation results. It can also write Bottom to
results to the file. top
function final Used to complete any outstanding actions that are yet Top to
to be completed in the testbench. down
7. Why do we use the super keyword in phases likes
super.build_phase, super.main_phase etc?
The super keyword is used to refer to class members (like build_phase, main_phase,
run_phase, etc) of its immediate base class or uvm_component (if derived class is
extended directly from uvm_component).
`uvm_do (seq/item) – On calling this macro, create, randomize and send to the driver
will be executed
Example:
class my_sequence extends uvm_sequence #(seq_item);
`uvm_object_utils(my_sequence)
task body();
`uvm_do(req);
endtask
endclass
task body();
`uvm_do(seq1); // calling seq1
`uvm_do(seq2); // calling seq2
endtask
endclass
task body();
`uvm_create(req);
`uvm_rand_send(req);
endtask
endclass
The below code snippet for the uvm_config_db class is taken from the uvm source code.
...
...
endclass
Example:
seq_item.sv
`include "seq_item.sv"
`uvm_component_utils(producer)
req = seq_item::type_id::create("req");
assert(req.randomize());
`uvm_info(get_type_name(), $sformatf("Send value = %0h", req.value),
UVM_NONE);
a_put.write(req);
endtask
endclass
`uvm_component_utils(consumer_A)
`uvm_component_utils(consumer_B)
function new(string name = "consumer_B", uvm_component parent = null);
super.new(name, parent);
analysis_imp_B = new("analysis_imp_B", this);
endfunction
module tb_top;
initial begin
run_test("test");
end
endmodule
Output:
pre_body: It is a user-definable callback that is called before the execution of body only
when the sequence is started with a ‘start’ task.
post_body: It is a user-definable callback task that is called after the execution of the
body only when the sequence is started with the start method.
13. What is an active and passive agent?
Active Agent: An Active agent drives stimulus to the DUT. It instantiates all three
components driver, monitor, and sequencer.
Passive Agent: A passive agent does not drive stimulus to the DUT. It instantiates only
a monitor component. It is used as a sample interface for coverage and checker
purposes.
Class Declaration
class uvm_objection extends uvm_report_object
The objection deals with the concept of raise and drop objection which means the
internal counter is increment and decrement respectively. Each participating component
and object may raise or drop objections asynchronously. When all objections are
dropped, the counter value will become zero. The objection has to be raised before
starting any process and drop it once it is completed.
1) UVM phasing mechanism uses objections to coordinate with each other and the
phase should be ended when all objections are dropped. They can be used in all
UVM phases.
2) It allows proceeding for the “End of test”. The simulation time-consuming activity
happens in the run phase. If all objections dropped for run phases, it means
simulation activity is completed. The test can be ended after executing clean up
phases.
task reset_phase( uvm_phase phase);
phase.raise_objection(this);
...
phase.drop_objection(this);
endtask
Note:
Methods in uvm_objection
Methods Description
set_drain_time (uvm_object obj=null, time Sets drain time for corresponding objects.
drain)
16. What are the different ways of exiting the code execution?
1) Using UVM Objections (Recommended approach): It allows proceeding for the
“End of test”. The simulation time-consuming activity happens in the run phase. If all
objections are dropped for run phases, it means the simulation activity is completed.
The test can be ended after executing clean-up phases.
2) $finish: It tells the simulator to exit the simulation. However, it is not a graceful
simulation for a UVM-based environment.
3) `uvm_fatal: Based on certain conditions as per requirement, it can be used to
terminate the simulation.
Example: If class config class is being used during run_phase and if this config class
is not available in config_db, then uvm_fatal is used to terminate simulation right
away.
Lock method
On calling the lock method from a sequence, the sequencer will grant the sequence
exclusive access to the driver when the sequence gets the next available slot via a
sequencer arbitration mechanism. Until the sequence calls the unlock method, no other
sequence will have access to the driver. On calling unlock method, the lock will be
released. The lock is a blocking method and returns once the lock has been granted.
task lock (uvm_sequencer_base sequencer = null)
grab method
The grab method is similar to the lock method except it takes immediate control to grab
the next sequencer arbitration slot itself by overriding any other sequence priorities.
The pre-existing lock or grab condition on the sequence will stop from grabbing the
sequence for the current sequence.
task grab (uvm_sequencer_base sequencer = null)
unlock method
The unlock method of the sequencer is called from the sequence to release its lock or
grab. It is mandatory to call unlock method in order to avoid sequencer locked
conditions.
function void unlock (uvm_sequencer_base sequencer = null)
ungrab method
The ungrab method is an alias of the unlock method
function void ungrab( uvm_sequencer_base sequencer = null )
task body();
req = seq_item::type_id::create("req");
wait_for_grant();
assert(req.randomize());
send_request(req);
`uvm_info(get_type_name(), $sformatf("%0t: seq_item is sent", $time),
UVM_LOW);
wait_for_item_done();
endtask
endclass
task body();
lock(m_sequencer);
req = seq_item::type_id::create("req");
wait_for_grant();
assert(req.randomize());
send_request(req);
`uvm_info(get_type_name(), $sformatf("%0t: seq_item is sent", $time),
UVM_LOW);
wait_for_item_done();
unlock(m_sequencer);
endtask
endclass
task body();
req = seq_item::type_id::create("req");
wait_for_grant();
assert(req.randomize());
send_request(req);
`uvm_info(get_type_name(), $sformatf("%0t: seq_item is sent", $time),
UVM_LOW);
wait_for_item_done();
endtask
endclass
Output:
task body();
req = seq_item::type_id::create("req");
wait_for_grant();
assert(req.randomize());
send_request(req);
`uvm_info(get_type_name(), $sformatf("%0t: seq_item is sent", $time),
UVM_LOW);
wait_for_item_done();
endtask
endclass
task body();
grab(m_sequencer);
req = seq_item::type_id::create("req");
wait_for_grant();
assert(req.randomize());
send_request(req);
`uvm_info(get_type_name(), $sformatf("%0t: seq_item is sent", $time),
UVM_LOW);
wait_for_item_done();
ungrab(m_sequencer);
endtask
endclass
task body();
req = seq_item::type_id::create("req");
wait_for_grant();
assert(req.randomize());
send_request(req);
`uvm_info(get_type_name(), $sformatf("%0t: seq_item is sent", $time),
UVM_LOW);
wait_for_item_done();
endtask
endclass
Output:
The register abstraction layer (RAL) provides standard base class libraries. It is used to
create a memory-mapped model for registers in DUT using an object-oriented model.
The UVM RAL is used to model DUT registers and memories as a set of classes. It
generates stimulus to the DUT and covers some aspects of functional coverage.
Back door Access: A register is said to be accessed as a back door if it uses a simulator
database to directly access the DUT register using design signals.
23. What is TLM?
TLM establishes a connection between producer and consumer components through
which transactions are sent.
TLM provides
uvm_tlm_fifo#(write_xtn) fifoh;
//APB GENERATOR:(Initiator)
----------------------------
//APB DRIVER:(Initiator)
-------------------------
//APB_AGENT:(CONNECTION)
--------------------------
class apb_agent extends uvm_component;
apb_gen apb_genh;
apb_driver apb_drvh;
uvm_tlm_fifo#(write_xtn) fifoh;
fifoh = new("fifoh",this);
endfunction
apb_genh.put_port.connect(fifoh.put_export);
apb_drvh.get_port.connect(fifoh.get_export);
endfunction
endclass
INCASE WE DON'T WANT TO USE FIFO THEN:
//TARGET CLASS
---------------
uvm_blocking_get_imp#(write_xtn,target) get_imp;
uvm_blocking_put_imp#(write_xtn,target) put_imp;
case(t.kind)
READ: //Do read
WRITE: //Do write
endcase
endtask
t = tmp;
endtask
endclass
**********WARNING*******
//These get and put method in target should implemented in such a way data
should be first in first out
//We declare handle of target class in a class in which driver and initiator
enclosed and do connection
//we do not need to write those methods It is already defined inside
uvm_tlm_fifo class
uvm_tlm_fifo Methods
Methods Description
uvm_component parent=null,
int size=1
);
The test execution is a time-consuming activity that runs sequence or sequences for DUT
functionality. On executing the test, it builds a complete UVM testbench structure in the
build_phase and time consuming activities are performed in the run_phase with the help
of sequences. It is mandatory to register tests in the UVM factory otherwise, the
simulation will terminate with UVM_FATAL (test not found).
run_test() task
The run_test task is a global task declared in the uvm_root class and responsible for
running a test case.
Declaration:
virtual task run_test (string test_name = "")
1. The run_test task starts the phasing mechanism that executes phases in a pre-defined
order.
2. It is called in the initial block of the testbench top and accepts test_name as a string.
Example:
// initial block of tb_top
initial begin
run_test("my_test");
end
3. Passing an argument to the run_test task causes recompilation while executing
different tests. To avoid it, UVM provides an alternative way using the +UVM_TESTNAME
command-line argument. In this case, the run_test task does not require passing any
argument in the initial block of the testbench top.
// initial block of tb_top
initial begin
run_test();
end
4. After execution of all phases, run_test finally calls $finish task for simulator exit.
// constructor
function new(string name = "monitor", uvm_component parent = null);
super.new(name, parent);
item_collect_port = new("item_collect_port", this);
mon_item = new();
endfunction
Scoreboard Usage
1) Receive transactions from monitor using analysis export for checking purposes.
2) The scoreboard has a reference model to compare with design behavior. The
reference model is also known as a predictor that implements design behavior so
that the scoreboard can compare DUT outcome with reference model outcome for
the same driven stimulus.
How to write scoreboard code in UVM?
Scoreboard Example
class scoreboard extends uvm_scoreboard;
uvm_analysis_imp #(seq_item, scoreboard) item_collect_export;
seq_item item_q[$];
`uvm_component_utils(scoreboard)
1. In-order scoreboard
2. Out-of-order scoreboard
In-order scoreboard
The in-order scoreboard is useful for the design whose output order is the same as
driven stimuli. The comparator will compare the expected and actual output streams in
the same order. They will arrive independently. Hence, the evaluation must block until
both expected and actual transactions are present.
To implement such scoreboards, an easier way would be to implement TLM analysis
FIFOs. In the below example, there are two monitors whose analysis port is connected
to the scoreboard to provide input and output transactions.
class inorder_sb extends uvm_scoreboard;
`uvm_component_utils(inorder_sb)
uvm_analysis_export #(txn) in_export, out_export;
uvm_tlm_analysis_fifo #(txn) in_fifo, out_fifo;
// Reference model
task process_data(input txn in_txn, output txn exp_txn);
// Generate expected txn for driven stimulus
...
...
endtask
endclass
Out-of-order scoreboard
The out-of-order scoreboard is useful for the design whose output order is different from
driven input stimuli. Based on the input stimuli reference model will generate the
expected outcome of DUT and the actual output is expected to come in any order. So, it
is required to store such unmatched transactions generated from the input stimulus
until the corresponding output has been received from the DUT to be compared. To
store such transactions, an associative array is widely used. Based on index value,
transactions are stored in the expected and actual associative arrays. The entries from
associative arrays are deleted when comparison happens for the matched array index.
join
compare_data();
end
endtask
task compare_data();
int idx;
txn exp_txn, act_txn;
if(expected_out_q.size() > && actaul_out_q.size() > 0) begin
idx = expected_out_q.pop_front();
// Look for idx in actual_out_array to see whether the output has been
received for a driven stimulus or not.
if(actual_out_array.exists(idx)) begin
exp_txn = expected_out_array[idx];
act_txn = actual_out_array[idx];
if(!exp_txn.compare(act_txn)) begin
`uvm_error(get_full_name(), $sformat("%s does not match %s",
exp_txn.sprint(), act_txn.sprint()), UVM_LOW);
end
else begin
expected_out_array.delete(idx);
actual_out_array.delete(idx);
end
end
else expected_out_q.push_back(idx); // exp_idx is not found in
actual_out_array.
end
endtask
endclass
Difficult level questions
1. Difference between p_sequencer and m_sequencer
m_sequencer p_sequencer
Used along with monitors to Controls other sequencers and it is not attached
dynamically respond to the design to any driver.
behavior
Virtual sequencer controls other sequencers and it is not attached to any driver.
Another example can be thought of as multiple cores in SOC. There can be multiple cores
present in SOC that can handle different operations on input provided and respond to
the device differently. In this case, as well, different sequence execution becomes
important on different sequencers.
A virtual sequence is usually executed on the virtual sequencer. A virtual sequence gives
control to start different sequences.
It is recommended to use a virtual sequencer if you have multiple agents and stimulus
coordination is required.
Virtual sequencer controls other sequencers. It is not attached to any driver and can not
process any sequence_items too. Hence, it is named virtual.
// No Virtual Sequencer
class core_A_sequencer extends uvm_sequencer #(seq_item);
`uvm_component_utils(core_A_sequencer)
endclass
class core_B_sequencer extends uvm_sequencer #(seq_item);
`uvm_component_utils(core_B_sequencer)
// base_test
class base_test extends uvm_test;
env env_o;
core_A_seq Aseq;
core_B_seq Bseq;
`uvm_component_utils(base_test)
Aseq.start(env_o.agt_A.seqr_A);
Bseq.start(env_o.agt_B.seqr_B);
phase.drop_objection(this);
endtask
endclass
Output:
// virtual sequence
class virtual_seq extends uvm_sequence #(seq_item);
core_A_seq Aseq;
core_B_seq Bseq;
core_A_sequencer seqr_A;
core_B_sequencer seqr_B;
`uvm_object_utils(virtual_seq)
task body();
`uvm_info(get_type_name(), "virtual_seq: Inside Body", UVM_LOW);
Aseq = core_A_seq::type_id::create("Aseq");
Bseq = core_B_seq::type_id::create("Bseq");
Aseq.start(seqr_A);
Bseq.start(seqr_B);
endtask
endclass
// No Virtual sequencer
class core_A_sequencer extends uvm_sequencer #(seq_item);
`uvm_component_utils(core_A_sequencer)
endclass
Output:
// Virtual sequence
class virtual_seq extends uvm_sequence #(seq_item);
core_A_seq Aseq;
core_B_seq Bseq;
core_A_sequencer seqr_A;
core_B_sequencer seqr_B;
`uvm_object_utils(virtual_seq)
`uvm_declare_p_sequencer(virtual_sequencer)
task body();
`uvm_info(get_type_name(), "virtual_seq: Inside Body", UVM_LOW);
Aseq = core_A_seq::type_id::create("Aseq");
Bseq = core_B_seq::type_id::create("Bseq");
Aseq.start(p_sequencer.seqr_A);
Bseq.start(p_sequencer.seqr_B);
endtask
endclass
// Virtual p_sequencer
class virtual_sequencer extends uvm_sequencer;
`uvm_component_utils(virtual_sequencer)
core_A_sequencer seqr_A;
core_B_sequencer seqr_B;
Output:
// virtual sequence
class virtual_seq extends uvm_sequence #(seq_item);
core_A_seq Aseq;
core_B_seq Bseq;
core_A_sequencer seqr_A;
core_B_sequencer seqr_B;
`uvm_object_utils(virtual_seq)
task body();
env env_s;
`uvm_info(get_type_name(), "virtual_seq: Inside Body", UVM_LOW);
Aseq = core_A_seq::type_id::create("Aseq");
Bseq = core_B_seq::type_id::create("Bseq");
Aseq.start(env_s.v_seqr.seqr_A);
Bseq.start(env_s.v_seqr.seqr_B);
endtask
endclass
// virtual_sequencer
class virtual_sequencer extends uvm_sequencer;
`uvm_component_utils(virtual_sequencer)
core_A_sequencer seqr_A;
core_B_sequencer seqr_B;
Output:
The UVM barrier provides multi-process synchronization that blocks a set of processes
until the desired number of processes reaches a particular synchronizing point at which
all the processes are released.
UVM Barrier is used to synchronize the number of processes based on the threshold
value set by the set_threshold method. Once the threshold is reached, the processes are
unblocked by the wait_for method.
The name argument denotes process name and p_delay indicates delay time taken by
the process to complete. Total 10 processes are forked with different execution delay
time.
The processes will wait till the threshold being reached which is set by the set_threshold
method to proceed further as demonstrated below.
module barrier_example();
uvm_barrier br;
task automatic process(string name, int p_delay);
$display("@%0t: Process %s started", $time, name);
#p_delay;
$display("@%0t: Process %s completed", $time, name);
br.wait_for();
$display("@%0t: Process %s wait_for is unblocked", $time, name);
endtask
initial begin
br = new("br");
br.set_threshold(4);
fork
process("A", 5);
process("B", 10);
process("C", 20);
process("D", 25);
process("E", 30);
process("F", 40);
process("G", 50);
process("H", 55);
process("I", 60);
process("J", 70);
join
end
endmodule
Output:
Note: The UVM 1.1d has an objection object of type uvm_callbacks_objection which is
derived from uvm_objection class and UVM 1.2 has an objection object of uvm_objection
class.
In UVM, the uvm_root class has only one instance. Hence, it is said to be a singleton class.
Note: System Verilog and UVM do not have a separate construct to create a singleton
object.
Singleton Object Example
In the below example, there are two handles for the same singleton component sc1 and
sc2 and objects have been tried to create twice. But on creating an object using handle
sc2, separate memory will not be allotted and sc2 handle points to a memory allocated
using handle sc1.
`include "uvm_macros.svh"
import uvm_pkg::*;
`uvm_component_utils_begin(singleton_comp)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(data, UVM_ALL_ON)
`uvm_component_utils_end
module singleton_example;
initial begin
run_test("base_test");
end
endmodule
Output:
Verification Architecture
In the verification architectural phase, engineers decide what all verification components
are required.
Testbench Development
As a part of testbench development, verification engineers develop testbench
components, interface connections with the DUT, VIP integration with a testbench, inter-
component connections within testbench (like monitor to scoreboard connection), etc.
Testcase Coding
A constraint-based random or dedicated test case is written for single or multiple
features in the design. A test case also kicks off UVM-based sequences to generate
required scenarios.
Simulation/ Debug
In this phase, engineers validate whether a specific feature is targetted or not, If not,
again test case is modified to target the feature. With the help of a checker/ scoreboard,
the error is reported if the desired design does not behave as expected. Using waveform
analysis or log prints, the design or verification environment is judged and a bug is
reported to the design team if it comes out to be a design issue otherwise, simulation is
re-run after fixing the verification component.
Analyze Metrics
Assertions, code, and functional coverage are common metrics that are used as analysis
metrics before we close the verification of the design.