5 Interface UVCs
18 uvm_test_done.drop_objection(this);
5.12 Implementing Protocol-Specific Coverage and Checks
Checks and coverage are crucial in a coverage-driven verification flow. As the traffic is randomized and not explicitly called for, it is critical to use a metric to ensure that the verification goals were achieved and no area was overlooked. Functional coverage is user-defined and should represent precisely (no more and no less) the coverage goals. If a user thinks of a scenario that was not sufficiently exercised, they can add a coverage attribute to their testbench.
Interface UVCs are focused on protocol-specific coverage. The coverage groups are implemented by the UVC developer, who knows the protocol and what kind of interesting scenarios need to be observed. An interface UVC coverage model includes signal and assertion-level coverage and transaction-level coverages, such as the type and nature of data items that are injected, interesting transition of these and more.
Some of the questions related to coverage collection are:
Where do I place the coverage items?
When and how to sample coverage?
How do I enable and disable coverage items?
How do I collect instance versus type coverage?
How do I know how many coverage items I need?
The following section discusses such coverage-related concerns.
5.12.1 Placing Coverage Groups
Coverage and checking are done in the monitor and collector. Coverage is collected on injected or observed transactions (passive mode). After sampling the observed transaction values, the monitor adds these to the coverage database. Sampling the generated data directly in the driver is not recommended. Sampling coverage that was collected from the bus ensures that the data reached the bus and did not get dropped between the randomization and the final injection. It also allows for better reuse of coverage code. Placing coverage in a driver allows you to collect coverage only when creating transactions, while placing coverage in a collector and monitor allows you to capture coverage in every simulation.
Note SystemVerilog allows sampling the coverage via event or by calling the sample() attribute. For many reasons, using the event can be tricky; it is quite possible that between the time the event was emitted and the actual sampling, the values of the transaction properties have changed. We highly recommend using the sample() call.
5.12.1.1 Transaction Coverage
Transaction coverage involves a transaction’s physical properties, information abstracted from these properties (such as ranges of values) and crosses between the properties. Some of the transaction attributes are added for coverage purposes. (For example, a transaction may collect start_time and end_time to capture the processing start and end time). You may want to collect instance-based or type-based transaction coverage. Instance coverage creates a different coverage result for each instance of a monitor or collector. For example, for a router with multiple ports, you may want to collect coverage of packet types that were sent to port1, as opposed to the type of packets that were sent to all ports. Transition coverage is often more meaningful on instance coverage. For example, type-based coverage may indicate a good distribution of reads on writes on multiple ports, but—when used in conjunction with instance-based coverage—transition coverage will indicate an important requirement that you execute a read on a specific port directly following a write to that port.
Transaction coverage groups can be placed either in the data item class or in the monitor. If you follow the object-oriented principle of loose coupling between classes you may believe the best place for transaction coverage groups is in the transaction itself. If the same transaction is used by multiple components, each component can sample the transaction attributes without duplicating the properties or spreading its attributes across multiple components. Coverage groups added to items are more difficult to manage and often cause additional performance penalties.
We recommend that you declare and instantiate coverage groups within the monitor and collector.
The example below illustrates the APB monitor coverage group:
1 //From: class apb_bus_monitor extends uvm_monitor;
2 covergroup apb_transfer_cg;
3 TRANS_ADDR : coverpoint trans_collected.addr { 4 bins ZERO = {0};
5 bins NON_ZERO = {[1:8'h7f]};
6 }
7 TRANS_DIRECTION : coverpoint trans_collected.direction ; 8 TRANS_DATA : coverpoint trans_collected.data {
9 bins ZERO = {0};
10 bins NON_ZERO = {[1:8'hfe]};
11 bins ALL_ONES = {8'hff};
12 }
13 TRANS_ADDR_X_TRANS_DIRECTION: cross TRANS_ADDR, TRANS_DIRECTION;
14 endgroup
Placing a single coverage group in the monitor allows a single instance of the coverage group to be used for all data items that the monitor collects. The SystemVerilog language requires that coverage groups must be created in the class constructor, so you cannot use the build() method. For performance reasons, you may want to disable creation of the coverage group when coverage_enable is set to 0. Thus, we recommend:
Instantiating the desired covergroup in the monitor constructor by calling get_config_int and only creating the covergroup if coverage_enable is set to 1.
Setting the instance name of the coverage group to be the monitor hierarchical path
The example below illustrates construction of the APB monitor coverage group:
// apb_master_monitor constructor
function new (string name, uvm_component parent);
super.new(name, parent);
// Create covergroup only if coverage is enabled
void'(get_config_int("coverage_enable", coverage_enable));
Note While some attributes in the transaction are needed only for coverage, it is typically not recommended to create a base transaction and derive from it a coverage_transaction and a sequencer_transaction. While this could be the right modeling and might have positive memory and run-time impacts, it prevents the user from further extending the definition of both the monitor and the sequencer sub-types.
The embedded covergroup uses the sample() method as its sampling trigger. The sampling of the covergroup is done in a perform_coverage function which is called after a transfer has been collected.
function void apb_bus_monitor::perform_coverage();
cov_apb_transfer.sample();
endfunction : perform_coverage
This function covers several properties of the transfer. The perform_coverage() function is called procedurally after the item has been collected by the monitor.
if (coverage_enable) perform_coverage();
5.12.1.2 Timing-Related Coverage
Timing-related coverage such as inter-packet gap, should be done by the collector. In order to allow transaction-level and even untimed execution mode, the monitor should not contain the concept of time.
5.12.2 Implementing Checks
Class checks should be implemented in the classes derived from uvm_monitor and uvm_collector. The derived classes of uvm_monitor and uvm_collector are always present in the agent, and thus always contain the necessary checks. The bus monitor is created by default in an env, and if the checks are enabled, the bus monitor will perform these functions. The remainder of this section uses the master monitor as an example of how to implement class checks, but they apply to the bus monitor as well.
You can write class checks as procedural code or SystemVerilog immediate assertions.
Tip Use immediate assertions for simple checks that can be written in a few lines of code, and use functions for complex checks that require many lines of code. The reason is that as the check becomes more complicated, so does the debug of that check.
Note Concurrent assertions are not allowed in SystemVerilog classes per the IEEE1800 LRM. These are allowed only within an interface.
Following is a simple example of an assertion check. This assertion verifies that the least-significant bits of the address are 2’b00 for word addressing. Otherwise, the assertion fails.
function void bus_monitor::check_address();
check_address : assert(trans_collected.addr[1:0] == 2’b00) else
‘uvm_error("ADDRERR", "Invalid transfer address!") end
endfunction : check_address
Following is a simple example of a function check for a bus transfer of variable size (not part of the APB protocol). This function verifies that the size field value matches the size of the data dynamic array. While this example is not complex, it illustrates a procedural code example of a check.
function void bus_monitor::check_transfer_data_size();
if (trans_collected.size != trans_collected.data.size())
// Call DUT error: Transfer size field / data size mismatch.
endfunction : check_transfer_data_size
The proper time to execute these checks depends on the implementation. You should determine when to make the call to the check functions shown above. For the above example, both checks should be executed after the transfer is collected by the monitor. Since these checks happen at the same instance in time, a wrapper function can be created so that only one call has to be made. This wrapper function follows.
at the same instance in time, a wrapper function can be created so that only one call has to be made. This wrapper function follows.
function void bus_monitor::perform_checks();
check_address();
check_transfer_data_size();
endfunction : perform_checks
The perform_checks() function is called procedurally after the item has been collected by the monitor.
5.12.3 Enabling and Disabling Coverage and Checks
The monitor and the collector should have coverage_enable and checks_enable configuration fields. Yet, sometimes these flags are not enough, as you may wish to have a finer granularity of control over which coverage and checks are enabled. Some of the requirements on enabling and disabling coverage include:
Enabling and disabling coverage and checking for agents
For performance reasons you may want to avoid collecting the coverage (as opposed to post simulation filtering).
Supporting fine granularity of control for specific checks and coverage groups (see “Fine Granularity for Enabling and Disabling”)
Disabling coverage and checks from the command line without recompilation or re-elaboration
5.12.3.1 Using the checks_enable and coverage_enable Flags
To address coverage and checking filtering at the component level, you should provide a means to control whether the checks are enforced and the coverage is collected. Use the coverage_enable and checks_enable fields for this purpose or provide this information as part of an agent configuration class. The field can be controlled using the uvm_component set_config* interface. The following is an example of using the checks_enable bit to control the checks.
if (checks_enable) perform_checks();
If checks_enable is set to 0, the function that performs the checks is not called, thus disabling the checks. The following example shows how to turn off the checks for all master.monitor components:
set_config_int("*.master.monitor", "checks_enable", 0);
The same capabilities exist for the coverage_enable field in the APB agent monitors and bus monitor.
5.12.3.2 Fine Granularity for Enabling and Disabling
The developer can provide an additional flag for each coverage group to prevent or enable its instantiation. Once this is provided, the user can enable and disable these separately.
A protocol may have multiple operation modes. As well as separate flags, coverage groups and checks can be relevant or irrelevant for these.
For example, if we configure a UVC to have a single master, some coverages and checks of the arbitration may not be necessary. The UVC developer should capture these configuration dependency modes in their UVC. For example, the UVC environment may accept parameters, and based on that data, determine which checkers and coverage attributes should be enabled and configure the agents or monitors accordingly.
In a UVC, checks and coverage are defined in multiple locations depending on the category of functionality being analyzed. In Figure 5-7, checks and coverage are depicted in the uvm_monitor, uvm_collector, and SystemVerilog interface. The following sections describe how the
cover, covergroup, and assert constructs are used in the UVM APB UVC example.
5.12.4 Implementing Checks and Coverage in Interfaces
Interface checks are implemented as assertions. Assertions are added to check the signal activity for a protocol. The assertions related to the physical interface are placed in the env’s interface. For example, an assertion might check that an address is never X or Y during a valid transfer. Use assert as well as assume properties to express these interface checks.
An assert directive is used when the property expresses the behavior of the device under test. An assume directive is used when the property expresses the behavior of the environment that generates the stimulus to the DUT.
The mechanism to enable or disable the physical checks works by mirroring the coverage and checks settings in the physical interfaces and propagating the data into the interfaces. This initial configuration values are copied in the run() phase and their changes are propagated to the interface within a forever loop.