Is it good idea to declare config object in uvm_sequence_item

Learn is it good idea to declare config object in uvm_sequence_item with practical examples, diagrams, and best practices. Covers verilog, verification, system-verilog development techniques with v...

UVM Sequence Items and Configuration Objects: A Best Practice Guide

Hero image for Is it good idea to declare config object in uvm_sequence_item

Explore the implications of declaring configuration objects directly within uvm_sequence_item and discover best practices for managing configuration in UVM environments.

In Universal Verification Methodology (UVM), uvm_sequence_item objects are fundamental for encapsulating transaction-level data that flows between the sequencer and driver. A common question arises regarding the best place to manage configuration settings that influence these transactions. Specifically, is it a good idea to declare a configuration object directly within a uvm_sequence_item? This article delves into the pros and cons, offering guidance on optimal UVM configuration practices.

Understanding UVM Configuration Principles

UVM promotes a clear separation of concerns. Configuration objects (uvm_object or uvm_component derived) are typically used to store settings that control the behavior of verification components (like drivers, monitors, and sequencers) or even the testbench itself. These objects are usually created once, populated with values, and then passed down the hierarchy using the UVM configuration database (uvm_config_db).

uvm_sequence_items, on the other hand, are transient objects. They are created, randomized, sent to the driver, and then often discarded. Their primary role is to carry specific transaction data for a single operation or a small set of operations.

flowchart TD
    A[Test] --> B{Build Phase}
    B --> C[Create Config Object]
    C --> D["uvm_config_db::set()"]
    D --> E[Verification Components (Driver, Monitor, Sequencer)]
    E --> F[uvm_config_db::get()]
    F --> G[Configure Component Behavior]
    G --> H[Sequencer Generates Sequence Items]
    H --> I[Driver Processes Sequence Items]
    I --> J[Monitor Collects Transactions]
    J --> K[Scoreboard Checks Data]
    subgraph UVM Configuration Flow
        B --> K
    end

Typical UVM Configuration Flow

The Case Against Config Objects in Sequence Items

While it might seem convenient to embed a configuration object directly into a uvm_sequence_item, this approach generally goes against UVM best practices and can lead to several issues:

  1. Redundancy and Overhead: Each uvm_sequence_item would carry its own copy of the configuration, even if the configuration is static across many transactions. This increases memory footprint and can make randomization more complex.
  2. Inconsistent Configuration: If the configuration is randomized within the sequence item, different transactions might end up with different configuration settings, which can be difficult to debug and might not reflect the intended test scenario.
  3. Violation of Separation of Concerns: Configuration should ideally be managed at a higher level (e.g., test, environment, or component) and passed down. Sequence items should focus solely on the data required for a specific transaction.
  4. Randomization Challenges: If a config object within a sequence item is randomized, ensuring that its values are consistent with the overall testbench configuration becomes a complex task, often requiring constraints that span multiple objects.
  5. Maintainability: Changes to configuration would require modifying the uvm_sequence_item itself, rather than a dedicated configuration object, potentially impacting many sequences and tests.

Instead of embedding configuration objects, UVM provides robust mechanisms for managing configuration:

1. Using uvm_config_db for Component Configuration

This is the standard and most flexible way to configure UVM components. Configuration objects are created in a higher-level component (like the test or environment) and then set into the uvm_config_db. Components that need this configuration can then get it from the database.

2. Using Sequence Variables for Transaction-Specific Control

If a sequence item needs to be influenced by a specific setting that changes per sequence or per test, it's better to pass this information down from the sequence that generates the item. This can be done by:

  • Adding fields to the sequence item: If the configuration is truly transaction-specific and varies frequently, add the relevant fields directly to the uvm_sequence_item and randomize them or set them from the sequence.
  • Passing parameters to the sequence: The sequence itself can be configured via uvm_config_db or by parameters passed during its creation. The sequence then uses these parameters to populate or constrain the sequence items it generates.
`// Example: Configuration passed to the sequence, which then influences sequence items
class my_sequence_config extends uvm_object;
  rand int start_address;
  rand int num_transfers;
  `uvm_object_utils_begin(my_sequence_config)
    `uvm_field_int(start_address, UVM_ALL_ON)
    `uvm_field_int(num_transfers, UVM_ALL_ON)
  `uvm_object_utils_end

  function new(string name = "my_sequence_config");
    super.new(name);
  endfunction
endclass

class my_sequence extends uvm_sequence#(my_sequence_item);
  my_sequence_config cfg;

  `uvm_object_utils(my_sequence)

  function new(string name = "my_sequence");
    super.new(name);
  endfunction

  virtual task pre_body();
    if (!uvm_config_db#(my_sequence_config)::get(this, "", "seq_cfg", cfg)) begin
      `uvm_fatal(get_full_name(), "Could not get sequence config from uvm_config_db")
    end
  endtask

  virtual task body();
    my_sequence_item item;
    repeat (cfg.num_transfers) begin
      item = my_sequence_item::type_id::create("item");
      start_item(item);
      item.addr = cfg.start_address + $urandom_range(0, 100);
      item.data = $urandom();
      finish_item(item);
    end
  endtask
endclass

// In your test or environment:
class my_test extends uvm_test;
  my_sequence_config seq_cfg;

  `uvm_component_utils(my_test)

  function new(string name = "my_test", uvm_component parent = null);
    super.new(name, parent);
  endfunction

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    seq_cfg = my_sequence_config::type_id::create("seq_cfg");
    seq_cfg.start_address = 'h1000;
    seq_cfg.num_transfers = 5;
    uvm_config_db#(my_sequence_config)::set(this, "env.agent.sequencer.my_sequence", "seq_cfg", seq_cfg);
  endfunction

  virtual task run_phase(uvm_phase phase);
    my_sequence seq = my_sequence::type_id::create("seq");
    phase.raise_objection(this);
    seq.start(env.agent.sequencer);
    phase.drop_objection(this);
  endtask
endclass`

Example of passing configuration to a sequence, which then influences sequence items.