Global training solutions for engineers creating the world's electronics

Requests, Responses, Layered Protocols and Layered Agents

Returning Transactions in Response to Requests from the Sequencer

UVM offers several ways in which a driver or monitor can return transactions (or other information) back upstream to a sequencer in response to requests from the sequencer. Each approach is applicable in different situations, so you need to understand the pros and cons of each to make the right decision in any given circumstance. The issues involved are complex and controversial: even the experts still debate which approach is best.

Figure: Request and Response

Here are the three approaches, stated in our recommended order of preference:

  1. Return a separate response object from the driver to the sequencer by having the driver call the get or try_next_item method to get the request, followed by the put method to return the response. This approach is simple and well-defined in the sense that it uses the TLM-1 semantics of unidirectional message passing between sequencer and driver, and allows multiple request and response objects to be in-flight simultaneously when modeling pipelined protocols. The sequence may call the get_response method to retrieve the response from the response queue, either in-order (the default) or out-of-order (by passing the request id as an argument to get_response).

    // Driver
    rsp = my_tx::type_id::create("rsp"); =;
    // Sequence
    req = my_tx::type_id::create("req");
    if( !req.randomize() )
  2. Allow the driver to modify one or more members of the request object itself rather than returning a separate response object. This is done by having the driver call the get_next_item method followed by the item_done method and modifying the request object between the two calls. This approach has the disadvantage that it does not allow multiple requests to be pipelined, but it does dispense with the need for a separate response object, simplifying the code a little.

    // Driver
    @(virtual_interface.clocking_block_name); =;
  3. Do not return a response from the driver to the sequencer, but instead use transactions sent from the analysis port of the monitor component in the same agent. Any component that needs to react to that response will need an analysis export connected to the analysis port of the monitor in order to receive the response from the monitor. This approach separates the request path from the response path, which means that the relationship between request and response objects need not be one-to-one. This can be both a blessing and a curse: You have the flexibility of something other than a one-to-one relationship, but one-to-one relationships are easier to debug. This approach avoids having the logic and state machines that keep track of activity on the DUT interface duplicated between the driver and the monitor, but at the same time it includes the monitor in the reactive loop between transaction generation and the DUT. This approach violates the principle that the monitor should be entirely passive and should be coded separately from the driver.

    class my_env extends uvm_env;
      top_agent     m_top_agent;
      my_agent      m_agent;
      my_subscriber m_subscriber;
      function void connect_phase(uvm_phase phase);
        // Connect the monitor to a regular analysis component for checking or coverage
        m_agent.m_monitor.analysis_port.connect( m_subscriber.analysis_export );
        // Connect the monitor to a sequencer that itself sends transactions to m_agent
        // Note that class uvm_sequencer has no analyis_export: it must have been extended
        m_agent.m_monitor.analysis_port.connect( m_top_agent.m_sequencer.analysis_export );
      task run_phase(uvm_phase phase);
        // Start a sequence on m_agent that pulls down requests from m_top_agent
        translation_sequence seq;
        seq = translation_sequence::type_id::create("seq");
        if ( !seq.randomize ) ...
        seq.top_sequencer = m_top_agent.m_sequencer;
        seq.start( m_agent.m_sequencer );

Layered Protocols and Layered Agents

Layered protocols should be modeled using layered sequencers or layered agents, where transaction types appropriate to the individual protocols are used at each layer. Various approaches are possible, and the experts still debate which approach is best.

The common denominator across all of the recommended approaches is the concept of a layering component which separates and isolates the transactions that represent each protocol from the transactions representing the other protocols. The transactions for each protocol are generated by a sequencer dedicated to that particular protocol. Each layering component is unaware of the details of the components in the neighboring layers except insofar as the layering component implements the protocols in question using the transaction objects sent and received from neighboring layers. The goal is to be able to reuse the protocol-specific sequencers or agents as far as possible, rather than writing new components that handle multiple protocols specifically for each layered scenario.

The two main approaches are to layer just the sequencers or to layer the agents. It may be appropriate to layer just the sequencers when it is possible to reuse the constituent parts of the original protocol agents and when the original agents were written in such a way that the sequencers can be used independently of the monitors (i.e. when sending a response from the driver back to the sequencer rather than using the path through the monitor to return a response). It may be appropriate to layer the agents when it is not possible to decompose the agents into their constituent parts and use the parts independently. When layering agents, it is generally necessary to use the factory to replace individual components within the agent wherever those individual components need to be modified or disabled.

Figure - Translation Sequence versus Layering Driver when Layering Agents

When layering sequencers, the sequencer runs a translation sequence (also known as a layering sequence) that exchanges transactions with both the layers immediately above and immediately below, getting requests from the higher layer sequencer and sending requests to the driver or lower layer sequencer. The translation sequence is aware of two protocols, so a separate translation sequence is necessary for every combination of two protocols that is used. Because the protocol translation occurs within a sequence, the components need not be modified. Each component only need be aware of a single protocol. The translation sequence object can contain a reference to the higher layer sequencer (from which it will get requests), which would typically be set just before the translation sequence is started from a virtual sequence.

When layering agents, the protocol translation can be done using either a translation sequence (in effect the same as the layering sequencers approach, except that the sequencer now happens to be embedded within an agent) or a layering driver. The layering driver is a distinctive approach, because it is now the driver (not the sequence) that translates between two protocols, while the sequencer it is paired with runs a dummy pass-through sequence to pass on the request from the layering driver to the low level driver. The layering driver approach is appropriate when it is the monitor and not the driver that returns the response to requests from the sequencer.

The layering driver approach is usually recommended in combination with explicit TLM port-export connections between the layers, whereas the translation sequence approach (whether layering sequencers or agents) is generally recommended in combination with direct references from the translation sequence to the higher layer sequencer, avoiding the necessity for TLM port-export connections.

When layering agents, it is necessary to use the factory to replace the driver and monitor. Depending on whether the translation sequence or layering driver approach is being used, the driver would either be replaced with a dummy component or with a driver containing a transaction-level interface to the lower layer instead of a pin-level interface to the DUT. The monitor would be replaced with a monitor containing an analysis export instead of a pin-level interface.


Easier UVM Coding Guidelines

Easier UVM - Deeper Explanations

Easier UVM Code Generator

Easier UVM Video Tutorial

Easier UVM Paper and Poster

Easier UVM Examples Ready-to-Run on EDA Playground


Back to the full list of UVM Resources