-
Notifications
You must be signed in to change notification settings - Fork 4
chapter13
.. ....... ........ ........ ....... .. ........... ...... .... .. ...... ..... .. ..... .... ........ ... ...... . ... .... .. ......... ........... .... .... ........ .. .... . ..... ....... .... ... ........ .... ............ .. ... ... ....... .. ...... .... ... .... ....... .. ..... ... .... ....... ... ....... ......... ..... .......... ....... ..... ....... ... ....... ... ....... ..... ..... .... . ........ .. ... ..... ......... .. ........ ..... ....... .......... .......... ... ........ .. ... ..... .. ........ ..... .......... .... ... ...... .. .....
.. ....... ........ ........ ....... .. ........... ...... .... .. ...... ..... .. ..... .... ........ ... ...... . ... .... .. ......... ........... .... .... ........ .. .... . ..... ....... .... ... ........ .... ............ .. ... ... ....... .. ...... .... ... .... ....... .. ..... ... .... ....... ... ....... ......... ..... .......... ....... ..... ....... ... ....... ... ....... ..... ..... .... . ........ .. ... ..... ......... .. ........ ..... ....... .......... .......... ... ........ .. ... ..... .. ........ ..... .......... .... ... ...... .. .....
.. ....... ........ ........ ....... .. ........... ...... .... .. ...... ..... .. ..... .... ........ ... ...... . ... .... .. ......... ........... .... .... ........ .. .... . ..... ....... .... ... ........ .... ............ .. ... ... ....... .. ...... .... ... .... ....... .. ..... ... .... ....... ... ....... ......... ..... .......... ....... ..... ....... ... ....... ... ....... ..... ..... .... . ........ .. ... ..... ......... .. ........ ..... ....... .......... .......... ... ........ .. ... ..... .. ........ ..... .......... .... ... ...... .. .....
.. ....... ........ ........ ....... .. ........... ...... .... .. ...... ..... .. ..... .... ........ ... ...... . ... .... .. ......... ........... .... .... ........ .. .... . ..... ....... .... ... ........ .... ............ .. ... ... ....... .. ...... .... ... .... ....... .. ..... ... .... ....... ... ....... ......... ..... .......... ....... ..... ....... ... ....... ... ....... ..... ..... .... . ........ .. ... ..... ......... .. ........ ..... ....... .......... .......... ... ........ .. ... ..... .. ........ ..... .......... .... ... ...... .. .....
The Universal Verification Methodology (UVM) is a standardized methodology for verifying integrated circuits, particularly in the field of System-on-Chip (SoC) design. UVM is built on SystemVerilog and provides a robust framework for creating reusable and modular verification environments. Below is a detailed explanation of UVM and its components.
UVM aims to provide a structured and reusable approach to verification, facilitating the development of testbenches that can be easily maintained and adapted to various verification scenarios. Its key features include the ability to create complex verification environments, stimulate designs under test (DUT) with various scenarios, and check the DUT's behavior against expected results.
The typical UVM testbench architecture is composed of several key components, each with a specific role in the verification process. The hierarchy of a UVM testbench is designed to be modular, allowing each component to be developed, tested, and reused independently.
...
The UVM testbench is the top-level module in a UVM environment. It instantiates all other components and coordinates the overall verification process. The testbench is responsible for setting up the DUT, initializing the UVM environment, and running the tests.
...
The UVM test is a specific scenario or set of scenarios used to verify the DUT. Each UVM test inherits from the uvm_test
base class and typically configures the environment and sequences to be used. It may override configuration settings, instantiate specific sequences, and control the execution flow.
...
The UVM environment is a container for the various agents, scoreboards, and other components needed for verification. It typically includes one or more agents, a scoreboard for result checking, and any necessary interfaces. The environment class inherits from uvm_env
.
...
The UVM scoreboard is responsible for checking the DUT's output against expected results. It collects data from various monitors and compares it to expected values, flagging any discrepancies. The scoreboard is crucial for functional verification, ensuring that the DUT behaves as intended.
...
A UVM agent is a modular component that encapsulates the functionality required to stimulate and monitor a specific interface of the DUT. It typically includes a sequencer, driver, and monitor. An agent can be active, generating stimuli and driving them onto the interface, or passive, simply monitoring the interface.
...
The UVM sequencer controls the flow of sequences to the driver. It manages the order and timing of transaction generation, ensuring that the correct sequences are sent to the DUT. The sequencer class inherits from uvm_sequencer
.
...
A UVM sequence is a series of transactions or operations to be performed on the DUT. Sequences are defined by inheriting from the uvm_sequence
base class and can be composed to create complex stimulus scenarios. Sequences are executed by the sequencer and can be randomized to improve test coverage.
...
The UVM driver receives sequences from the sequencer and drives them onto the DUT's interface. It converts high-level transactions into pin-level activity, ensuring that the DUT receives the correct stimuli. The driver class inherits from uvm_driver
.
...
The UVM monitor observes the DUT's interface, capturing data for analysis by the scoreboard and other components. It is typically passive, meaning it does not influence the DUT's behavior. The monitor class inherits from uvm_monitor
.
The UVM class library provides a comprehensive set of base classes and utilities for building verification environments. It includes classes for components (such as the test, environment, agent, sequencer, sequence, driver, and monitor), as well as utilities for configuration, reporting, messaging, and more.
Key classes and utilities in the UVM class library include:
- uvm_component: Base class for all UVM components.
- uvm_test: Base class for tests.
- uvm_env: Base class for environments.
- uvm_agent: Base class for agents.
- uvm_sequencer: Base class for sequencers.
- uvm_sequence: Base class for sequences.
- uvm_driver: Base class for drivers.
- uvm_monitor: Base class for monitors.
- uvm_config_db: Utility for configuration and parameterization.
- uvm_report_server: Utility for reporting and messaging.
- uvm_factory: Utility for component creation and management.
These classes and utilities provide a standardized way to construct verification environments, ensuring consistency and reusability across different projects and teams.
Transaction-Level Modeling (TLM) is a high-level approach to modeling the communication between components in a system, focusing on the transactions rather than the lower-level signal details. TLM abstracts the details of the communication, making it easier to develop, understand, and simulate complex systems.
TLM is widely used in system-level design and verification, providing a higher abstraction level than traditional register-transfer level (RTL) modeling. It allows for faster simulations and easier debugging, as well as better reuse of models. TLM is particularly beneficial in early stages of design for architectural exploration and software development.
TLM has evolved over time, leading to different versions with varying levels of abstraction and capabilities:
- TLM: The initial concept of transaction-level modeling, focusing on abstracting communication into transactions.
- TLM-1: The first standard version, providing basic mechanisms for transaction-level communication, focusing on the communication itself rather than timing accuracy.
- TLM-2.0: An advanced version that introduces standardized interfaces and protocols, supporting more detailed timing and communication mechanisms. TLM-2.0 is more comprehensive and includes features for interoperability and more accurate modeling of timing and synchronization.
...
TLM-1 provides the foundation for transaction-level modeling. It abstracts communication into transactions, which are data structures representing read or write operations. Components communicate by passing these transactions rather than manipulating signals directly.
...
In TLM-1, components are typically encapsulated in a hierarchical manner. This encapsulation helps manage complexity and promotes modularity. Each component can be a master, slave, or intermediary, initiating or responding to transactions.
...
Analysis communication in TLM-1 involves observing and analyzing transactions as they pass through the system. Monitors or analysis components can be attached to observe transactions, enabling the collection of statistics, performance analysis, and functional verification without affecting the communication itself.
TLM-2.0 builds on the concepts introduced in TLM-1, adding more detailed and standardized features for improved modeling accuracy and interoperability.
...
The generic payload in TLM-2.0 is a standardized transaction data structure that includes fields for address, data, command (read/write), response status, and other attributes. This standardization simplifies communication and promotes reuse across different models and platforms.
...
TLM-2.0 defines core interfaces and ports for communication:
- tlm_fw_transport_if: Forward transport interface for initiating transactions.
- tlm_bw_transport_if: Backward transport interface for receiving responses.
- tlm_initiator_socket: Socket used by initiators to send transactions.
- tlm_target_socket: Socket used by targets to receive transactions.
...
Blocking transport in TLM-2.0 involves transactions that complete before control is returned to the caller. This is useful for simple and immediate communication scenarios. The b_transport
method is used for blocking transport, ensuring that a transaction is fully processed before moving on.
...
Nonblocking transport allows transactions to be initiated without waiting for their completion, enabling more complex and asynchronous communication. The nb_transport_fw
and nb_transport_bw
methods are used for forward and backward nonblocking transport, respectively.
...
Sockets in TLM-2.0 facilitate the connection between initiators and targets. They encapsulate the interfaces and provide a standardized way to connect components. Sockets support both blocking and nonblocking transport methods, promoting flexibility and ease of use.
...
TLM-2.0 includes mechanisms for modeling timing and delays, allowing for more accurate simulation of system behavior. Time annotations can be added to transactions to represent communication delays, processing times, and synchronization points.
...
TLM-2.0 supports various use models, including:
- Loosely-timed (LT) models: Focus on functionality with approximate timing. Suitable for early software development and high-level architectural exploration.
- Approximately-timed (AT) models: Provide more detailed timing information, suitable for performance analysis and detailed design exploration.
- Timed models: Capture precise timing details for accurate hardware simulation and verification.
In summary, TLM-2.0 provides a rich set of features and standardized mechanisms for high-level system modeling, supporting a range of use cases from early design exploration to detailed performance analysis and verification.
Creating reusable verification components is crucial for an efficient and effective verification process. This involves building modular, flexible, and highly configurable components that can be reused across different projects and verification environments.
...
Modeling data items involves defining the structure and properties of the transactions that will be used in verification. In UVM, data items are typically modeled as classes that extend from uvm_sequence_item
. Inheritance allows for creating base transaction classes with common fields and methods, which can then be extended to create specific transaction types.
Constraint layering is used to apply constraints to randomize the fields of the data items. Constraints can be layered to apply different sets of constraints based on the context or scenario.
class base_transaction extends uvm_sequence_item;
rand bit [7:0] address;
rand bit [31:0] data;
`uvm_object_utils(base_transaction)
function new(string name = "base_transaction");
super.new(name);
endfunction
// Define constraints here
constraint valid_address { address < 128; }
endclass
class specific_transaction extends base_transaction;
rand bit [3:0] control_flags;
`uvm_object_utils(specific_transaction)
function new(string name = "specific_transaction");
super.new(name);
endfunction
// Additional constraints for specific transaction
constraint control_constraint { control_flags != 4'b0000; }
endclass
...
Control fields, or "knobs", are used to adjust the behavior of the verification components dynamically. These fields can be configured through the UVM configuration database, enabling flexibility in controlling the verification environment without modifying the code.
class transaction_knobs extends uvm_object;
bit enable_error_injection;
bit [15:0] max_transactions;
`uvm_object_utils(transaction_knobs)
function new(string name = "transaction_knobs");
super.new(name);
endfunction
endclass
Transaction-level components are responsible for generating, processing, and monitoring transactions. These components include drivers, sequencers, and monitors.
The driver is responsible for driving the transactions onto the DUT's interface. It extends from uvm_driver
and implements the run_phase
method to fetch transactions from the sequencer and drive them onto the interface.
class my_driver extends uvm_driver<base_transaction>;
virtual dut_if dut_interface;
`uvm_component_utils(my_driver)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual dut_if)::get(this, "", "dut_interface", dut_interface))
`uvm_fatal("NOVIF", "Virtual interface not found")
endfunction
task run_phase(uvm_phase phase);
base_transaction trans;
forever begin
seq_item_port.get_next_item(trans);
// Drive transaction onto DUT
dut_interface.addr = trans.address;
dut_interface.data = trans.data;
seq_item_port.item_done();
end
endtask
endclass
The sequencer manages the flow of transactions. It generates or selects transactions to be sent to the driver.
class my_sequencer extends uvm_sequencer<base_transaction>;
`uvm_component_utils(my_sequencer)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclass
...
The driver and sequencer interact through TLM ports and exports. The sequencer generates transactions, which the driver then processes.
...
The sequencer generates randomized items, which the driver fetches and processes.
...
The driver fetches consecutive transactions from the sequencer, ensuring continuous operation.
...
Processed data can be sent back to the sequencer if needed, typically using TLM analysis ports.
...
TLM-based drivers use TLM interfaces for more abstract communication.
The monitor observes the DUT's interface and collects data for analysis. It extends from uvm_monitor
and typically implements the run_phase
method to capture transactions from the DUT.
class my_monitor extends uvm_monitor;
virtual dut_if dut_interface;
uvm_analysis_port#(base_transaction) ap;
`uvm_component_utils(my_monitor)
function new(string name, uvm_component parent);
super.new(name, parent);
ap = new("ap", this);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual dut_if)::get(this, "", "dut_interface", dut_interface))
`uvm_fatal("NOVIF", "Virtual interface not found")
endfunction
task run_phase(uvm_phase phase);
base_transaction trans;
forever begin
// Capture transaction from DUT
trans = base_transaction::type_id::create("trans");
trans.address = dut_interface.addr;
trans.data = dut_interface.data;
ap.write(trans);
end
endtask
endclass
Components are instantiated in the testbench, and their connections are configured using the UVM configuration database.
...
An agent can operate in active or passive mode. In active mode, it generates and drives transactions. In passive mode, it only monitors transactions.
...
The agent instantiates and connects the driver, sequencer, and monitor.
class my_agent extends uvm_agent;
my_driver drv;
my_sequencer seqr;
my_monitor mon;
`uvm_component_utils(my_agent)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
drv = my_driver::type_id::create("drv", this);
seqr = my_sequencer::type_id::create("seqr", this);
mon = my_monitor::type_id::create("mon", this);
endfunction
virtual function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
drv.seq_item_port.connect(seqr.seq_item_export);
endfunction
endclass
...
The environment class encapsulates agents and other components, creating a complete verification environment.
class my_env extends uvm_env;
my_agent agent;
`uvm_component_utils(my_env)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
agent = my_agent::type_id::create("agent", this);
endfunction
endclass
...
The build_phase
method is used to instantiate and configure components.
...
User-defined sequences extend from uvm_sequence
and define specific transaction scenarios.
...
Subsequences and sequence items can be sent to the sequencer to create complex scenarios.
...
Sequences are started on the sequencer using the start
method.
...
Sequence items and sequences can be overridden to customize the scenarios.
Managing the end of the test involves detecting when all transactions are complete and stopping the simulation.
...
Checks and coverage can be implemented in verification components to ensure the DUT behaves as expected and to measure verification progress.
...
Interfaces can also include checks and coverage points to capture detailed information about the DUT's behavior.
...
Checks and coverage can be controlled through configuration settings, allowing for flexible and targeted verification efforts.
By following these guidelines, you can develop reusable and robust verification components that can be easily integrated and adapted to different verification environments, improving efficiency and effectiveness in the verification process.
A top-level environment encapsulates all the components needed for verification. This environment includes agents, scoreboards, monitors, and other verification components. It provides a cohesive structure to manage the interactions between these components.
class top_env extends uvm_env;
my_agent agent;
my_scoreboard scoreboard;
`uvm_component_utils(top_env)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
agent = my_agent::type_id::create("agent", this);
scoreboard = my_scoreboard::type_id::create("scoreboard", this);
endfunction
virtual function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
agent.mon.ap.connect(scoreboard.analysis_export);
endfunction
endclass
Verification components such as drivers, sequencers, monitors, and agents are instantiated within the top-level environment. These components are configured and connected during the build and connect phases.
Test classes define specific verification scenarios. Each test class inherits from uvm_test
and configures the environment, sequences, and other parameters needed for the test.
class base_test extends uvm_test;
top_env env;
`uvm_component_utils(base_test)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
env = top_env::type_id::create("env", this);
endfunction
virtual function void run_phase(uvm_phase phase);
phase.raise_objection(this);
// Run the test sequences here
phase.drop_objection(this);
endfunction
endclass
...
Configurable parameters allow for dynamic control of verification components. These parameters can be set through the UVM configuration database.
...
Configuration is typically done using uvm_config_db
. Parameters can be set and retrieved using this database, allowing for flexible and reusable configurations.
...
- uvm_resource_db: Suitable for global settings that are rarely changed.
- uvm_config_db: Preferred for flexible, dynamic configurations that may vary between tests.
...
A configuration class encapsulates all the parameters needed for a component. This class is then used to configure the component during the build phase.
class my_config extends uvm_object;
bit enable_error_injection;
int max_transactions;
`uvm_object_utils(my_config)
function new(string name = "my_config");
super.new(name);
endfunction
endclass
class my_agent extends uvm_agent;
my_config cfg;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(my_config)::get(this, "", "my_config", cfg))
`uvm_fatal("NOCFG", "No configuration found")
endfunction
endclass
...
The base test class sets up the environment and provides common functionality for derived test classes.
...
Derived test classes extend the base test and implement specific scenarios.
class my_test extends base_test;
`uvm_component_utils(my_test)
function void run_phase(uvm_phase phase);
super.run_phase(phase);
phase.raise_objection(this);
// Specific test sequence
phase.drop_objection(this);
endfunction
endclass
...
Tests are selected at runtime, typically through command-line arguments or a test management framework.
...
Constraining data items ensures that generated transactions meet the required conditions.
...
Data items are defined using uvm_sequence_item
and constrained using SystemVerilog constraints.
...
Test-specific frames define the context and conditions for the test, ensuring meaningful verification scenarios.
...
A virtual sequencer coordinates multiple sequencers, allowing for complex, multi-interface tests.
class my_virtual_sequencer extends uvm_sequencer;
my_sequencer seq1;
my_sequencer seq2;
`uvm_component_utils(my_virtual_sequencer)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclass
...
A virtual sequence orchestrates other sequences, providing a higher level of control over the verification process.
class my_virtual_sequence extends uvm_sequence;
`uvm_object_utils(my_virtual_sequence)
function new(string name = "my_virtual_sequence");
super.new(name);
endfunction
task body();
my_sequence seq1;
my_sequence seq2;
// Start sequences on different sequencers
fork
seq1.start(p_sequencer.seq1);
seq2.start(p_sequencer.seq2);
join
endtask
endclass
...
The virtual sequencer controls other sequencers by starting and managing their sequences.
...
Virtual sequencers are connected to subsequencers during the build phase.
Correctness checks ensure the DUT behaves as expected. These checks are implemented in scoreboards and monitors.
...
The scoreboard collects and compares results, ensuring the DUT's output matches expected values.
class my_scoreboard extends uvm_scoreboard;
uvm_analysis_export#(base_transaction) analysis_export;
`uvm_component_utils(my_scoreboard)
function new(string name, uvm_component parent);
super.new(name, parent);
analysis_export = new("analysis_export", this);
endfunction
virtual function void write(base_transaction t);
// Compare the transaction with expected values
endfunction
endclass
...
Exports are added to connect the scoreboard to other components.
...
The TLM implementation ensures smooth communication between components, allowing for data exchange and synchronization.
...
The action taken by the scoreboard involves comparing actual results with expected values and reporting discrepancies.
...
The scoreboard is instantiated and connected in the environment.
...
Coverage methods can be functional or code-based. Functional coverage captures specific conditions and scenarios, while code coverage measures exercised code paths.
...
Functional coverage models are implemented using SystemVerilog covergroups and coverpoints.
class my_coverage extends uvm_subscriber#(base_transaction);
covergroup cg;
coverpoint trans.address;
coverpoint trans.data;
endgroup
`uvm_component_utils(my_coverage)
function new(string name, uvm_component parent);
super.new(name, parent);
cg = new();
endfunction
virtual function void write(base_transaction t);
cg.sample();
endfunction
endclass
...
Coverage can be enabled or disabled using configuration settings, allowing for targeted analysis.
By following these steps, you can effectively use verification components to create robust and reusable verification environments. These practices ensure comprehensive verification of the DUT, leveraging the modularity and flexibility provided by UVM.
The Register Layer in UVM provides a robust framework for modeling and verifying the configuration and state registers of a design. It offers a structured way to define, access, and verify the registers and memories in a device under test (DUT).
The UVM register layer simplifies the modeling of registers and memories, offering mechanisms for defining the structure, accessing the values, and verifying the correctness of the data. It supports both front-door (via bus transactions) and back-door (direct access) mechanisms for register operations.
...
Mirroring refers to keeping a software model (the register model) synchronized with the actual hardware state of the DUT. This synchronization can be done using front-door accesses (through the DUT's bus interface) or back-door accesses (directly accessing the DUT's internal state).
...
Unlike registers, memories are typically not mirrored. This is because memory contents can be large and constantly changing, making it impractical to maintain an exact software copy. Instead, specific memory locations can be accessed and verified as needed.
The UVM register layer provides a rich API for accessing and manipulating register and memory contents.
...
These methods are used to perform front-door access to the registers through the bus interface.
reg_model.REG1.write(status, value);
reg_model.REG1.read(status, value);
...
These methods are used for back-door access, directly accessing the register value without going through the bus interface.
reg_model.REG1.peek(value);
reg_model.REG1.poke(value);
...
These methods are used to get or set the register field values in the register model without actually accessing the hardware.
reg_model.REG1.FIELD1.set(value);
value = reg_model.REG1.FIELD1.get();
...
This method randomizes the field values of a register according to their constraints.
reg_model.REG1.randomize();
...
This method writes the current values from the register model to the hardware using front-door access.
reg_model.REG1.update(status);
...
This method reads the current values from the hardware into the register model, ensuring synchronization.
reg_model.REG1.mirror(status);
...
The UVM register layer handles concurrent accesses by serializing them, ensuring data integrity.
...
The register layer provides predefined coverage identifiers for register and field accesses, allowing users to collect coverage data for verification purposes.
...
Coverage model construction and sampling can be controlled through UVM configuration settings, enabling users to focus on specific areas of interest.
...
Fields are the smallest unit in a register model, representing individual bits or groups of bits with specific attributes like access type (read/write).
...
Registers are collections of fields, representing a single register in the hardware. They can have attributes such as address, access type, and reset value.
...
Register files are collections of registers grouped together for organizational purposes.
...
Memories are large storage elements, modeled separately from registers due to their size and access patterns.
...
Blocks are top-level containers for register files, registers, and memories, representing an entire addressable block of the DUT.
...
A register model is packaged by defining all its components (fields, registers, register files, memories, and blocks) and linking them together.
...
The maximum data size for register fields and memories is determined by the specific implementation and limitations of the hardware and software tools used.
...
Back-door read/write methods use hierarchical HDL paths to directly access the hardware, while peek/poke methods access the register model.
...
Back-door access relies on hierarchical HDL paths to directly reference and manipulate hardware states.
...
VPI (Verilog Procedural Interface) provides a standard way to implement back-door access in Verilog-based designs.
...
Users can define custom back-door access methods to suit specific needs or hardware configurations.
...
Protected memories require special handling to ensure security and data integrity during back-door accesses.
...
Active monitoring involves continuously checking the DUT's state to detect and report any discrepancies between the register model and the actual hardware.
...
Certain registers have predefined roles and behaviors, such as status registers or control registers.
...
Unmapped registers and memories are those that do not have a direct correspondence in the hardware but are included in the model for completeness or simulation purposes.
...
Aliased registers share the same address space but serve different purposes based on context or mode of operation.
...
Unimplemented registers are placeholders for future expansion or optional features not currently present in the hardware.
...
Read-only (RO) and write-only (WO) registers can share the same address if they represent different aspects of the same hardware location.
...
The transaction adapter translates high-level register operations into low-level bus transactions compatible with the DUT's bus protocol.
...
Bus sequencers control the sequence of bus transactions, coordinating with the register model to perform reads and writes.
...
A bus monitor observes bus transactions and updates the register model to reflect changes in the DUT's state, ensuring consistency and correctness.
Randomizing field values allows for extensive testing of different register states and transitions, uncovering edge cases and potential issues.
reg_model.REG1.randomize();
Pre-defined sequences provide common register access patterns and scenarios, facilitating efficient and thorough verification.
class my_reg_sequence extends uvm_sequence;
`uvm_object_utils(my_reg_sequence)
function new(string name = "my_reg_sequence");
super.new(name);
endfunction
task body();
// Define sequence of register operations
endtask
endclass
By leveraging the capabilities of the UVM register layer, verification engineers can create comprehensive, flexible, and reusable register models that enhance the effectiveness and efficiency of the verification process.
The uvm_component
base class is a fundamental building block in UVM, providing a framework for building modular, reusable verification components. It serves as the base class for all hierarchical components in a UVM testbench.
Key features of uvm_component
include:
-
Phases: UVM components participate in predefined simulation phases such as
build_phase
,connect_phase
,run_phase
, etc. - Factory Registration: Allows components to be created dynamically using the UVM factory.
- Configuration Database: Enables flexible configuration of component properties.
Example:
class my_component extends uvm_component;
`uvm_component_utils(my_component)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Component build logic
endfunction
virtual function void run_phase(uvm_phase phase);
phase.raise_objection(this);
// Component run logic
phase.drop_objection(this);
endfunction
endclass
...
The UVM factory is a mechanism for creating objects and components dynamically at runtime. It supports object-oriented principles such as polymorphism and abstraction, enabling flexibility and reusability.
...
To use the factory, components and objects must be registered using macros such as uvm_component_utils
and uvm_object_utils
.
class my_component extends uvm_component;
`uvm_component_utils(my_component)
// ...
endclass
...
Overrides allow one component or object type to be replaced with another at runtime, facilitating easier configuration changes and extending testbenches without modifying the original code.
uvm_factory::set_type_override_by_type(original_type::get_type(), override_type::get_type());
...
Callbacks are a mechanism to add custom behavior to UVM components without modifying their source code. They enable a flexible way to extend and customize components.
...
class my_callback extends uvm_callback;
`uvm_object_utils(my_callback)
function new(string name = "my_callback");
super.new(name);
endfunction
virtual function void post_write(uvm_reg reg, uvm_reg_data_t data);
// Custom behavior after a register write
endfunction
endclass
class my_component extends uvm_component;
`uvm_component_utils(my_component)
my_callback cb;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
cb = my_callback::type_id::create("cb");
uvm_reg::get().add_callback(cb);
endfunction
endclass
The UVM sequence library provides a framework for creating, managing, and executing sequences. It allows for defining complex transaction flows and reuse across different tests and environments.
...
Complex scenarios can be implemented by composing sequences from simpler sequences, controlling the order, timing, and conditions of execution.
...
Protocol layering involves creating sequences that operate at different levels of abstraction, enabling a layered approach to verification.
...
Sequences can pre-generate items before sending them to the sequencer, allowing for more complex pre-processing or timing control.
...
Sequences can target different sequencers, enabling coordination between multiple protocol layers or functional units.
task body();
my_seq1.start(p_sequencer);
my_seq2.start(other_sequencer);
endtask
...
The UVM CLI allows users to pass arguments to the simulation, controlling various aspects of the verification environment.
...
To use the CLI, UVM provides built-in macros and functions to parse and handle command-line arguments.
...
UVM-aware command-line processing involves using the UVM configuration database and factory to set up the environment based on CLI arguments.
if ($test$plusargs("enable_feature")) begin
uvm_config_db#(bit)::set(null, "uvm_test_top", "enable_feature", 1);
end
UVM provides a variety of macros to simplify the creation and management of verification components. Some commonly used macros include:
-
uvm_component_utils
: Registers a component with the UVM factory. -
uvm_object_utils
: Registers an object with the UVM factory. -
uvm_info
,uvm_warning
,uvm_error
,uvm_fatal
: Logging and messaging macros.
Example:
`uvm_info("MY_TAG", "This is an informational message", UVM_LOW)
`uvm_warning("MY_TAG", "This is a warning message")
`uvm_error("MY_TAG", "This is an error message")
`uvm_fatal("MY_TAG", "This is a fatal message")
By mastering these advanced topics, verification engineers can create highly flexible, reusable, and robust verification environments, leveraging the full power of UVM.
The UBus example demonstrates a complete UVM testbench for verifying a simple bus protocol called UBus. This example showcases the architecture, components, and interactions necessary for verifying a bus protocol.
The UBus example architecture consists of several components that work together to verify the UBus protocol. These components include:
- UBus Top Module
- Test
- Testbench Environment
- UBus Environment
- UBus Master Agent
- UBus Master Sequencer
- UBus Driver
- UBus Agent Monitor
- UBus Bus Monitor
- UBus Interface
The top module instantiates the DUT and connects it to the UVM testbench components. It provides the necessary signals and interfaces for the UBus protocol.
module ubus_top;
// Clock and reset signals
reg clk;
reg reset;
// UBus signals
wire [31:0] addr;
wire [31:0] data;
wire [3:0] control;
wire ready;
wire valid;
// Instantiate DUT
ubus dut (
.clk(clk),
.reset(reset),
.addr(addr),
.data(data),
.control(control),
.ready(ready),
.valid(valid)
);
// Instantiate UVM testbench
initial begin
run_test();
end
endmodule
A UVM test sets up the environment and specifies the sequences to be executed. It configures the UBus environment and starts the verification process.
class ubus_test extends uvm_test;
`uvm_component_utils(ubus_test)
ubus_env env;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
env = ubus_env::type_id::create("env", this);
endfunction
virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);
// Start the sequence
ubus_master_seq seq;
seq = ubus_master_seq::type_id::create("seq");
seq.start(env.masters[0].sequencer);
phase.drop_objection(this);
endtask
endclass
The testbench environment instantiates and connects all the necessary components for verifying the UBus protocol. It includes agents, monitors, and scoreboards.
class ubus_env extends uvm_env;
`uvm_component_utils(ubus_env)
ubus_master_agent masters[NUM_MASTERS];
ubus_monitor monitor;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
for (int i = 0; i < NUM_MASTERS; i++) begin
masters[i] = ubus_master_agent::type_id::create($sformatf("master%d", i), this);
end
monitor = ubus_monitor::type_id::create("monitor", this);
endfunction
endclass
The UBus environment is a specialized environment for the UBus protocol, integrating specific agents and monitors required for UBus verification.
class ubus_env extends uvm_env;
`uvm_component_utils(ubus_env)
ubus_master_agent master_agent;
ubus_monitor bus_monitor;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
master_agent = ubus_master_agent::type_id::create("master_agent", this);
bus_monitor = ubus_monitor::type_id::create("bus_monitor", this);
endfunction
virtual function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
master_agent.monitor.ap.connect(bus_monitor.analysis_export);
endfunction
endclass
The UBus master agent contains the sequencer, driver, and monitor components necessary for generating and monitoring bus transactions from the master perspective.
class ubus_master_agent extends uvm_agent;
`uvm_component_utils(ubus_master_agent)
ubus_master_sequencer sequencer;
ubus_driver driver;
ubus_monitor monitor;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
sequencer = ubus_master_sequencer::type_id::create("sequencer", this);
driver = ubus_driver::type_id::create("driver", this);
monitor = ubus_monitor::type_id::create("monitor", this);
endfunction
virtual function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
driver.seq_item_port.connect(sequencer.seq_item_export);
endfunction
endclass
The UBus master sequencer is responsible for managing sequences and providing transactions to the driver.
class ubus_master_sequencer extends uvm_sequencer #(ubus_transaction);
`uvm_component_utils(ubus_master_sequencer)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclass
The UBus driver converts sequence items into pin-level activity on the bus interface.
class ubus_driver extends uvm_driver #(ubus_transaction);
`uvm_component_utils(ubus_driver)
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Driver-specific build logic
endfunction
virtual task run_phase(uvm_phase phase);
forever begin
ubus_transaction tr;
seq_item_port.get_next_item(tr);
// Drive the transaction on the bus
seq_item_port.item_done();
end
endtask
endclass
The UBus agent monitor observes bus transactions and converts them into analysis transactions for further processing.
class ubus_monitor extends uvm_monitor;
`uvm_component_utils(ubus_monitor)
uvm_analysis_port #(ubus_transaction) ap;
function new(string name, uvm_component parent);
super.new(name, parent);
ap = new("ap", this);
endfunction
virtual task run_phase(uvm_phase phase);
// Monitor logic
endtask
endclass
...
The bus monitor collects transactions from the bus and converts them into analysis transactions.
...
The monitor tracks the number of transfers and other relevant statistics.
...
The monitor can emit notifications for specific events, such as errors or protocol violations.
...
The monitor performs protocol checks and collects coverage information, ensuring comprehensive verification of the bus protocol.
The UBus interface defines the signals and structure of the UBus protocol.
interface ubus_if(input bit clk);
logic [31:0] addr;
logic [31:0] data;
logic [3:0] control;
logic ready;
logic valid;
// Clocking block for synchronous signals
clocking cb @(posedge clk);
output addr;
output data;
output control;
input ready;
output valid;
endclocking
endinterface
This example provides a comprehensive overview of how to set up and utilize a UVM testbench for the UBus protocol, highlighting the key components and their interactions.
...
The UBus (Universal Bus) protocol is designed to provide a flexible and efficient means of communication between different components in a system. It supports various types of data transfers and can be extended to include optional pipelining for improved performance.
...
UBus is a synchronous bus protocol that includes arbitration, address, and data phases. It supports multiple masters and slaves, ensuring fair access to shared resources through an arbitration mechanism.
...
UBus consists of several signals that manage data transfer, address selection, and control functions:
-
clk
: The clock signal driving the synchronous operation of the bus. -
reset
: Resets the bus and all connected devices. -
addr[31:0]
: The 32-bit address bus. -
data[31:0]
: The 32-bit data bus. -
control[3:0]
: Control signals for various bus operations. -
ready
: Indicates the slave's readiness to accept or provide data. -
valid
: Indicates the master's validity of the address or data on the bus.
...
UBus is a synchronous protocol driven by a single clock signal. All bus transactions occur on the rising edge of the clock.
...
The reset
signal is used to initialize the bus and all connected devices to a known state. It is an active-high signal.
The arbitration phase determines which master gains control of the bus. Each master competes for bus access, and a centralized arbiter grants access based on a predefined policy, such as round-robin or priority-based.
During the address phase, the master places the address of the target slave on the address bus.
...
A No Operation (NOP) cycle occurs when no master is driving the bus, and all signals remain in their idle state.
...
In a normal address phase, the master asserts the address on the bus and sets the control signals to indicate the type of operation (read/write).
The data phase follows the address phase and involves the actual data transfer between the master and the slave.
...
In a write transfer, the master places the data on the data bus, and the slave latches the data when the ready
signal is asserted.
...
If an error occurs during a write transfer, the slave can assert an error signal to indicate the failure.
...
In a read transfer, the master places the address on the address bus, and the slave returns the data on the data bus when the ready
signal is asserted.
...
If an error occurs during a read transfer, the slave can assert an error signal to indicate the failure.
Data is driven on the bus by the master during write operations and by the slave during read operations. The valid
signal indicates that the data on the bus is valid and can be latched by the receiving device.
Pipelining improves bus performance by overlapping the arbitration, address, and data phases of multiple transactions.
...
In the pipelined arbitration phase, the next master to gain control of the bus is determined while the current data transfer is still in progress.
...
The pipelined address phase allows the next address to be placed on the bus before the current data transfer is completed.
...
In the pipelined data phase, data transfers for different transactions overlap, with each phase (arbitration, address, data) occurring simultaneously for different transactions.
Below are example timing diagrams illustrating different phases and operations in the UBus protocol.
This document outlines the UBus protocol, its phases, signal descriptions, and example timing diagrams, providing a comprehensive guide for implementing and verifying UBus in a hardware design.