This is a complete working example consisting of multiple approximately-timed (AT) initiators and targets and an AT interconnect. As far as we know, it is the only publicly available complete AT base protocol implementation of its kind.
This example is intended primarily for advanced TLM-2.0 users. AT components such as this would typically be implemented by model vendors rather than by end users. However, even if you do not consider yourself an expert, you may still pick up some useful tips. As an end user, you would typically obtain components such as this off-the-shelf from a vendor or internal development team; your job would be to integrate the components and possibly to write some simple (or less simple) target components.
The AT initiators and targets in this example are of interest because they demonstrate the various base protocol options with respect to using the forward, backward and return paths and early completion. There are two initiators named AT_typeA_initiator and AT_typeB_initiator, and five targets named AT_typeA_target through AT_typeE_target. The type A and B initiators and type A-E targets each use different cases of the base protocol, that is, using the forward/backward paths versus the return path, or skipping END_REQ, or completing early.
The AT interconnect component is of interest because it is a complete AT base protocol implementation, having the following features:
- It uses multi_passthrough_ sockets
- It obeys the base protocol rules, particularly the exclusion rules at initiator and target sockets
- It supports any number of initiators and targets
- It handles b_transport, DMI and debug, as well as nb_transport
Every initiator communicates with every target through the same AT interconnect. The initiators and targets are themselves just shells with a trivial functional core. The main interest is in the AT method calls through the sockets.
The AT Coding Style
Before getting into the detail, we'll start with a brief description of the intent of the approximately-timed coding style.
The idea of AT is to support transaction-level models that include approximate timing, for the purposes of performance modeling and architectural exploration. Approximately-timed can mean whatever you want it to mean, but it is generally supposed to be more accurate than untimed or loosely-timed and less accurate than cycle-accurate. AT models are expected to be significantly slower than loosely-timed (LT) models, but significantly faster than RTL.
The TLM-2.0 base protocol supports AT modeling using the non-blocking transport (nb_transport) interface, which allows a transaction to execute in multiple phases. The base protocol defines 4 phases (BEGIN_REQ, END_REQ, BEGIN_RESP, END_RESP), but specific protocols can add further phases of their own (but pay the price of a loss of interoperability).
With the base protocol, requests propagate forward from initiator through zero or more interconnects to the target, and responses propagate backward from target through zero or more interconnects to the initiator. The initiator is obliged to set all the attributes of the generic payload before sending BEGIN_REQ, and is not permitted to modify the attributes subsequently. The target is only permitted to modify generic payload attributes between receiving BEGIN_REQ and sending BEGIN_RESP.
Because AT transactions have 4 distinct phases, they are able to model both accept delays (initiation intervals) and latencies.
- The accept delay for requests is the delay between BEGIN_REQ and END_REQ
- The accept delay for responses is the delay between BEGIN_RESP and END_RESP
- The latency of the target is the delay between BEGIN_REQ and BEGIN_RESP
- The latency of an interconnect is the delay between receiving BEGIN_REQ and sending BEGIN_REQ
- and the same for BEGIN_RESP on the backward path
The AT coding style supports multiple transctions in-flight at the same time. However, the request and response exclusion rules prevent there being more than one request or more than one response in progress through any given socket at any given time. In other words, a component is not allowed to send the next BEGIN_REQ until it has seen the previous END_REQ, or the next BEGIN_RESP until it has seen the previous END_RESP. These exclusion rules allow any component to put back-pressure on any other component trying to send a request or response.
The nb_transport method is always called with a phase argument. If there is an answering phase, it can be returned in one of two ways; either as part of the same method call (the return path) or using a second and subsequent method call in the opposite direction. The use of the return path is signalled to the caller by having nb_transport return the value TLM_UPDATED. It is also possible for a component to jump direct to the end response phase and shortcut the intervening phases by having nb_transport return TLM_COMPLETED.
AT versus LT
This short video compares the RTL and TLM abstraction levels, and also compares the AT and LT coding styles.
The file at_example.cpp contains the top-level SystemC module, which instantiates the initiators, interconnect and targets. There is a base protocol checker instantiated for each socket of the interconnect component. The base protocol checkers are included solely for debug and verification.
At the top of the file there are a set of macro definitions:
#define initiator0_t AT_typeA_initiator #define initiator1_t AT_typeA_initiator #define initiator2_t AT_typeB_initiator #define initiator3_t AT_typeB_initiator #define target0_t AT_typeA_target #define target1_t AT_typeB_target #define target2_t AT_typeC_target #define target3_t AT_typeD_target #define target4_t AT_typeE_target
These definitions may be altered in order to instantiate a different combination of initiator and target types.
Notice that this configuration permits an initiator of any type to communicate with a target of any type.
The Structure of the Source Code
The structure of the source code is straightforward. As well as at_example.cpp, there is a header file for each initiator and target and for the interconnect, there is a header file common_header.h that is included by all the other headers, and there are a couple of header files for the base protocol checker and generic payload memory manager.
For your convenience, there is also a project file at_example_proj.vcproj for use with Microsoft Visual C++ 2008.
Behavior of the Components
The initiators generate a series of generic payload transactions with the address chosen at random but word-aligned, and with the command (read or write) chosen at random. In the case of a write command, the initiator sets the data equal to the address. For a read command, the targets set the data to the negation of the address, and in all cases the targets dump out a diagnostic text message to the file example.log. In effect, the initiators and targets check that every request and response is transported correctly end-to-end, but they have no functionality above that.
The initiators use a set of data buffers so that concurrent transactions each point to a different data area.
When the interconnect decodes the generic payload address attribute, it passes the bottom 8 bits of the address on to the target and uses the top bits of the address to select the target. If the integer formed from the top bits is greater than the number of targets, it simply wraps around. In other words, the target number is calculated by:
(address >> 8) % number_of_targets
This scheme permits the example interconnect to be used with any number of targets. The two methods decode_address and reconstruct_address could be replaced to implement an alternative routing algorithm.
The interconnect uses a first-come-first-served strategy to arbitrate between concurrent requests. Transactions are selected by the following two lines of code:
Trans req = req_queue[target].front(); req_queue[target].pop_front();
These two lines could be replaced to implement an alternative arbitration algorithm. All components call the function rand_ps() from common_header.h to generate the delays used for timing annotation and waits. This ensures that the TLM-2.0 interfaces are thoroughly stressed.
The Initiator Types
There are two types of initiator, AT_typeA_initiator and AT_typeB_initiator.
The type A initiator always returns TLM_ACCEPTED for nb_transport_bw calls, never TLM_UPDATED or TLM_COMPLETED, so it never uses the return path and never causes early completion of a base protocol transaction. END_RESP is always sent on the forward path.
The type B initiator returns TLM_COMPLETED when it receives BEGIN_RESP on the backward path. However, if BEGIN_RESP is received on the return path (to nb_transport_fw), the type B initiator will still be obliged to send END_RESP on the forward path.
The Target Types
There are five types of target, AT_typeA_target through AT_typeE_target.
The type A target returns TLM_ACCEPTED for nb_transport_fw calls, except in the case of certain error responses when it returns TLM_COMPLETED. The type A target never returns TLM_UPDATED, so does not use the return path. END_REQ and BEGIN_RESP are always returned on the backward path.
The type B target returns END_REQ on the return path (TLM_UPDATED) whenever possible. However, there are circumstances where the target needs to delay sending END_REQ in order to put back-pressure on the initiator, in which case it is obliged to send END_REQ on the backward path.
The type C target does not return END_REQ at all. Instead, it jumps direct to BEGIN_RESP, which it always sends on the backward path, never on the return path (TLM_UPDATED).
The type D target does not return END_REQ at all, but unlike the type C target, it sends BEGIN_RESP on the return path (TLM_UPDATED) whenever possible. However, if the target is forbidden by the exclusion rule from sending BEGIN_RESP immediately, is it obliged to send BEGIN_RESP on the backward path.
The type E target always returns immediately with TLM_COMPLETED. It never uses the backward path, and never returns an explicit END_REQ or BEGIN_RESP.
Note that these two initiator types and five target types are just examples. An actual initiator or target may switch between these modes of operation dynamically.
The interconnect can route incoming transactions from any initiator to any target, and vice versa. However, the interconnect is obliged by the base protocol to obey the request exclusion rule at the initiator sockets and the response exclusion rule at the target sockets. To do this it maintains queues of transactions associated with each socket.
Responses returning in the direction of the initiators must be routed back through the correct target socket. To do this, the interconnect uses instance-specific extensions to attach routing information to each transaction.
There is a lot more to learn about the implementation of the TLM-2.0 base protocol and the interconnect component than is explained above. If you need to know more, you should consider attending a Doulos face-to-face training class. For further details, click here.
Click here to download the source code. In exchange, we will ask you to enter some personal details. To read about how we use your details, click here. On the registration form, you will be asked whether you want us to send you further information concerning other Doulos products and services in the subject area concerned.