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:
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:
- Use imp
suffixes defined via macro:
- Declare macros outside of component
- Instantiate suffixed imps
- 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.