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:
- 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 seq_item_port.get(req); @(virtual_interface.clocking_block_name); rsp = my_tx::type_id::create("rsp"); rsp.data = virtual_interface.data; rsp.set_id_info(req); seq_item_port.put(rsp); ... // Sequence req = my_tx::type_id::create("req"); start_item(req); if( !req.randomize() ) ... finish_item(req); ... get_response(rsp); ...
- 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 seq_item_port.get_next_item(req); @(virtual_interface.clocking_block_name); req.data = virtual_interface.data; seq_item_port.item_done(); ...
- 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 ); endfunction 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.set_starting_phase(phase); seq.top_sequencer = m_top_agent.m_sequencer; seq.start( m_agent.m_sequencer ); endtask endclass
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 happen 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
Introduction to the Easier UVM Coding Guidelines
Summary of the Easier UVM Coding Guidelines
Detailed Explanation of the Easier UVM Coding Guidelines
Easier UVM Glossary
Easier UVM Coding Guidelines - Download
Easier UVM - Deeper Explanations
Coverage-Driven Verification Methodology
Requests, Responses, Layered Protocols and Layered Agents
How to Access a Parameterized SystemVerilog Interface from UVM
Easier UVM Code Generator
Easier UVM Code Generator - Download
Easier UVM Code Generator - Tutorial Part 1: Getting Started
Easier UVM Code Generator - Tutorial Part 2: Adding User-Defined Code
Easier UVM Code Generator - Tutorial Part 3: Adding the Register Layer
Easier UVM Code Generator - Tutorial Part 4: Hierarchical Verification Environments
Easier UVM Code Generator - Tutorial Part 5: Split Transactors
Easier UVM Code Generator - Frequently Asked Questions (FAQ)
Easier UVM Code Generator - Reference Guide
Easier UVM Video Tutorial
Introducing Easier UVM
Easier UVM - The Big Picture
Key Concepts of the Easier UVM Code Generator
Running Easier UVM in EDA Playground
Easier UVM - Components and Phases
Easier UVM - Configuration
TLM Connections in UVM
Easier UVM - Transaction Classes
Easier UVM - Sequences
Easier UVM - Tests
Easier UVM - Reporting
Easier UVM - Register Layer
Easier UVM - Parameterized Interfaces
Easier UVM - Scoreboards
The Finer Points of UVM Sequences (Recorded Webinar)
UVM Run-Time Phasing (Recorded Webinar)
A YouTube playlist with all the above videos and more
Easier UVM Paper and Poster
Easier UVM - Coding Guidelines and Code Generation - as presented at DVCon 2014
Easier UVM Q&A Forum
Easier UVM Google Group
Easier UVM Examples Ready-to-Run on EDA Playground
Minimal example with driver
Minimal example with coverage in a subscriber as well as driver and monitor.
Minimal example with register sequence and register block
Example with four interfaces/agents, two of which use a register model.
Minimal example with dual-top modules and split transactors
Minimal example showing a UVM sequence getting information from the config database
Minimal example showing features of objections and the command line processor
Minimal example showing the reporting features of UVM.
Example that drops an objection when coverage exceeds some threshold
Example that sends a response transaction from the driver back to the uvm_reg_adapter
Example that uses a frontdoor sequence to pass a response object back to the register sequence that called read/write
Example of a parameterized interface generated from an Easier UVM interface template file
Example that pulls in a user-defined parameterized interface
Example of a reference model with the Syosil scoreboard
Back to the full list of UVM Resources