Figure: Request and Response
Here are the three approaches, stated in our recommended order of preference:
// 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); ...
// Driver seq_item_port.get_next_item(req); @(virtual_interface.clocking_block_name); req.data = virtual_interface.data; seq_item_port.item_done(); ...
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 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 Q&A Forum
Easier UVM Examples Ready-to-Run on EDA Playground