Saturday 16 January 2016

TLM1 in UVM


Before going into the TLM interface concepts, let’s see why we need TLM interface: 

Port based Data Transfer:
Following is a simple verification environment.
Components generator and driver are implemented as modules. These modules are connected using module ports or SV interfaces.
The advantage of this methodology is, the two above mentioned components are independent. Instead of consumer module, any other component which can understand producer interface can be connected, which gives a great re-usability. 
The disadvantage of this methodology is, data transfer is done at lower level of abstraction. 

Task based Data Transfer:
In the above environment, methods are used to transfer the data between components.
So, this gives a better control and data transfer is done at high level.
The disadvantage is, components are using hierarchical paths which do not allow the re-usability.  

TLM interface:
UVM has TLM interfaces which provide the advantages which we saw in the above two data transfer styles.
Data is transferred at high level of abstraction. Transactions which are developed by extending the uvm_sequence_item can be transferred between components using method calls. These methods are not hierarchical fixed, so that components can be reused.
The advantages of TLM interfaces are
 1) Higher level abstraction
 2) Reusable. Plug and play connections.
 3) Maintainability
 4) Less code.
 5) Easy to implement.
 6) Faster simulation.
 7) Connect to SystemC.
 8) Can be used for reference model development.

TLM-1 and TLM-2.0 are two TLM modeling systems which have been developed as industry standards for building transaction-level models. Both were built in SystemC and standardized within the TLM Working Group of the Open SystemC Initiative (OSCI).

TLM-1 is a message passing system. Interfaces are either untimed or rely on the target for timing. None of the interfaces provide for explicit timing annotations. 

Tlm Terminology:
Producer:
A component which generates a transaction.
Consumer:
A component which consumes the transaction.
Initiator:
A component which initiates process.
Target:
A component which responded to initiator.

Transaction-level interfaces define a set of methods that use transaction objects as arguments. 

Interfaces:
The UVM provides ports, exports and implementation and analysis ports for connecting your components via the TLM interfaces. Port, Export, implementation terminology applies to control flow not to data flow.
Port:
A TLM port defines the set of methods (the application programming interface (API)) to be used for a particular connection.
Import:
Interface that provides an implementation is import or implementation port.
Export:
A TLM export supplies the implementation of those methods which is defined in TLM port. Connecting a port to an export allows the implementation to be executed when the port method is called.
Interface used to route transaction interfaces to other layers of the hierarchy.
Analysis:
Interface used to distribute transactions to passive components. 

  • TLM is all about communication through method calls.
  • A TLM port specifies the “API” to be used. 
  • A TLM export supplies the implementation of the methods. 
  • Connections are between ports/exports, not components. 
  • Transactions are objects. 
  • Ports & exports are parameterized by the transaction type being communicated

Difference between export and import:
Basically, both exports and imps provide the implementations of whatever methods your TLM port requires. The difference is an export is an indirect connection to an implementation. It normally used when there is component hierarchy involved. 

Operation Supported By Tlm Interface:
Putting:
Producer transfers a value to Consumer.
Getting:
Consumer requires a data value from producer.
Peeking:
Copies data from a producer without consuming the data.
Broadcasting:
Transaction is broadcasted to none or one or multiple consumers. 

Putting:
The most basic transaction-level operation allows one component to put a transaction to another. Consider below figure.
The square box on the producer indicates a port and the circle on the consumer indicates the export.
The producer generates transactions and sends them out its put_port:
The actual implementation of the put() call is supplied by the consumer.


In this case, the put()call in the producer will block until the consumer’s put implementation is complete.
Consumer could be replaced by another component that also implements put and producer will continue to work in exactly the same way. 

Getting:
In this case, the consumer requests transactions from the producer via its get port:
The get() implementation is supplied by the producer.

get() call will block until the get_producer’s method completes. In TLM terms, put() and get() are blocking methods. 

Communicating between Processes:
In the basic put example above, the consumer will be active only when its put() method is called. In many cases, it may be necessary for components to operate independently, where the producer is creating transactions in one process while the consumer needs to operate on those transactions in another. UVM provides the uvm_tlm_fifo channel to facilitate such communication. The uvm_tlm_fifo implements all of the TLM interface methods, so the producer puts the transaction into the uvm_tlm_fifo, while the consumer independently gets the transaction from the fifo, as shown in below figure.

When the producer puts a transaction into the fifo, it will block if the fifo is full, otherwise it will put the object into the fifo and return immediately.


The get operation will return immediately if a transaction is available (and will then be removed from the fifo), otherwise it will block until a transaction is available.
Thus, two consecutive get() calls will yield different transactions to the consumer. The related peek()method returns a copy of the available transaction without removing it. Two consecutive peek() calls will return copies of the same transaction. 

Tlm Interface Compilation Models:
Blocking:
A blocking interface conveys transactions in blocking fashion; its methods do not return until the transaction has been successfully sent or retrieved. Its methods are defined as tasks.
virtual task put(input T1 t)
virtual task get(output T2 t)
virtual task peek(output T2 t) 

Non-Blocking:
A non-blocking interface attempts to convey a transaction without consuming simulation time. Its methods are declared as functions. Because delivery may fail (e.g. the target component is busy and cannot accept the request), the methods may return with failed status.
virtual function bit try_put(input T1 t)
virtual function bit can_put()
virtual function bit try_get(output T2 t)
virtual function bit can_get()
virtual function bit try_peek(output T2 t)
virtual function bit can_peek()
The try_put()method returns TRUE if the transaction is sent. 

Combined:
A combination interface contains both the blocking and non-blocking variants. 

Peer-to-Peer connections
When connecting components at the same level of hierarchy, ports are always connected to exports. All connect() calls between components are done in the parent’s connect() method.
See “my_test” class in examples in putting section. 

Port/Export Compatibility:
In order for a connection to be valid, the export must provide implementations for at least the set of methods defined by the port and the transaction type parameter for the two must be identical. 

Hierarchical Connections:
Making connections across hierarchical boundaries involves some additional issues, which are discussed in this section. Consider the hierarchical design shown in below figure.
The hierarchy of this design contains two components, producer and consumer.
Producer contains three components, stim, tlm_fi, and conv. consumer contains two components, tlm_fi and drive.
Notice that, from the perspective of top, the producer and consumer appear identical to those in first figure.

Connections A, B, D, and F are standard peer-to-peer connections as discussed above.
gen.put_port.connect(fifo.put_export);

Connections C and E are of a different sort than what have been shown. Connection C is a port-to-port connection, and connection E is an export-to-export connection. These two kinds of connections are necessary to complete hierarchical connections.

All export-to-export connections in a parent component are of the form export.connect(subcomponent.export);

so connection E would be coded as:
class consumer extends uvm_component;
uvm_put_export #(trans) put_export;
uvm_tlm_fifo #(trans) fifo;
...
function void connect();
put_export.connect(fifo.put_export); // E
bfm.get_port.connect(fifo.get_export); // F
endfunction
...
endclass

Conversely, port-to-port connections are of the form:
subcomponent.port.connect(port);

so connection C would be coded as:
class producer extends uvm_component;
uvm_put_port #(trans) put_port;
conv c;
...
function void connect();
c.put_port.connect(put_port);
...
endfunction 


In short:
  • Port-to-Export
    • port.connect(export);
  • Port-to-Port
    • child.port.connect(parent_port);
  • Export-to-Export
    • parent_export.connect(child.export);
  • Last Export is actually an ‘imp’ 

Connection Type:

  • All TLM connections go from ‘origin’ to ‘destination’
  • port.connect(export);
  • child_port.connect(parent_port);
  • parent_export.connect(child_export); // or imp

Analysis Communication:


Analysis Port:
The uvm_analysis_port (represented as a diamond on the monitor in Figure) is a specialized TLM port whose interface consists of a single function, write().
The analysis port contains a list of analysis_exports that are connected to it. When the component calls analysis_port.write(), the analysis_port cycles through the list and calls the write() method of each connected export.
If nothing is connected, the write() call simply returns. Thus, an analysis port may be connected to zero, one, or many analysis exports, but the operation of the component that writes to the analysis port does not depend on the number of exports connected. Because write() is a void function, the call will always complete in the same delta cycle, regardless of how many  components (for example, scoreboards, coverage collectors, and so on) are connected.

In the parent environment, the analysis port gets connected to the analysis export of the desired components, such as coverage collectors and scoreboards.
As with other TLM connections, it is up to each component connected to an analysis port to provide an implementation of write() via an analysis_export. The uvm_subscriber base component can be used to simplify this operation, so a typical analysis component would extend uvm_subscriber as:
class sub1 #(type T = simple_trans) extends uvm_subscriber #(T);
...
function void write(T t);
// Record coverage information of t.
endfunction
endclass

TLM connection between an analysis port and export, allows the export to supply the implementation of write(). If multiple exports are connected to an analysis port, the port will call the write() of each export, in order. Since all implementations of write() must be functions, the analysis port’s write() function completes immediately, regardless of how many exports are connected to it.

Note:
When multiple subscribers are connected to an analysis_port, each is passed a pointer to the same transaction object, the argument to the write() call. Each write() implementation must make a local copy of the transaction and then operate on the copy to avoid corrupting the transaction contents for any other subscriber that may have received the same pointer.

UVM also includes an analysis_fifo, which is a uvm_tlm_fifo that also includes an analysis export, to allow blocking components access to the analysis transaction stream. The analysis_fifo is unbounded, so the monitor’s write() call is guaranteed to succeed immediately. The analysis component may then get the transactions from the analysis_fifo at its leisure. 

Analysis Export:
uvm_subscriber has built-in single analysis_export. So it allows us to send only single transaction stream.
If you need multiple transaction streams then extend your class from uvm_component and include multiple analysis_export.

There are two ways to do this:
  1. Use imp suffixes defined via macro:
    1. Declare macros outside of component
    2. Instantiate suffixed imps
    3. Implement write_SUFFIX methods
  • Write methods are functions 
    • Can’t synchronize between streams (because there is no way through which we can delay in write() method since it is a function)

If you need to be able to have streams somehow synchronized, then go with second approach.
  1. Use embedded fifos 
    1. Declare analysis exports 
    2. Connect exports to fifos
  • Run_phase must actively pull from fifos
Here you do whatever synchronization you need to between two transaction stream
Give flexibility to compare out of order transaction



4 comments:

  1. Hi sagar,
    Thanks a lot for the wonderful blog on TLM ports . You have done a brilliant job, i have almost searched every website and referred cookbook but i never completely understood TLM ports and was finding it very difficult. But after i read your blog the concepts now are very clear and you explained it really well with examples.Thanks a lot ,keep up the good work.

    Regards,
    sandhya

    ReplyDelete
  2. Can you please explain about TLM 2 and scoreboard, it will be very help full

    ReplyDelete
  3. Hi Sagar. Very well explained/presented and the way you selected the topics and written the content is very much useful and helpful and this kind of information comes only with experience. it's not just the topic explanation, gathering from all the sources(giving good idea about each topic) and spending time to write the blogs is very much appreciated and we are learning a lot from all of your blogs. thanks a lot and keep blogging..

    ReplyDelete