Simics can be used as a framework for testing and developing SystemC models in isolation, or it can be used to integrate SystemC models with other Simics models. The integration spans from just a single model to a full virtual platform. Simics models can be written in any language as long as they use the Simics API and Simics interfaces to communicate. A Simics model consists of a Simics configuration class, instantiated as a Simics object. In Simics SystemC Library this object is called adapter.
SystemC Library makes it possible to run multiple SystemC models in the same Simics configuration at the same time, possibly instantiated at different points in time (i.e. dynamically) and optionally connected directly or indirectly to one another. It is important to understand that each adapter instance will get its own context and that communication via the SystemC scheduler between instances will not be possible; all communication between adapters must go via Simics interfaces.
Also note that time and ordering when communicating between multiple SystemC kernels is not defined by the IEEE SystemC specification. That is, no ordering assumptions can be made when two SystemC models communicate with each other via Simics interfaces.
The project-setup tool can be used to generate a new Simics module with a skeleton of a SystemC device connected to Simics via a SystemC adapter: ./bin/project-setup --sc-device name
, where name is the name of the new Simics module.
Simics contains well defined and stable interfaces for various hardware specific protocols such as Ethernet, USB, PCIe, etc. There is no SystemC standard that covers such protocols yet. Instead, each set of modules must agree on a custom protocol on top of the TLM2 base protocol and any deviations must be handled by an interconnect module. Simics contains a set of interface converters, called gaskets, for a number of common Simics interfaces like memory mapped I/O (MMIO), signal, PCI Express, Ethernet, I2C, etc. These gaskets convert the Simics interface into (or from) a TLM2 payload extension transmitted using the standard TLM2 interface and protocol. Simics provides utility functions to make it easy to pack or unpack the protocol-specific payload extensions.
SystemC Library only supports Simics -> SystemC and SystemC -> Simics interface calls via gaskets. Direct invocation of Simics interfaces are not officially supported.
There is also a non-TLM2 based gasket, converting the Simics signal
interface to/from the SystemC sc_signal<bool>
.
Signals work a bit differently when routed through Simics. There is no support for sc_signal<T>
of arbitrary type T, as there is no corresponding interface in Simics that supports this. In addition, when connecting SystemC configurations via Simics interfaces it is important to understand that each adapter will have its own separate kernel context, and thus cannot rely on delta cycle communication via the scheduler; which is very common when using SystemC signals. Simics does not maintain a global order of SystemC events. There are two ways to address this problem:
The complete list of supported gaskets can be found in the SystemC Library API Reference Manual. Users can also create their own gaskets to support additional or custom interfaces. See 4.6 for more details.
This section demonstrates how to expose a simple DMA device written in SystemC to Simics and how to test it using Simics Python tests. The DMA device example source code is available in the Simics Base package and can be added to the project by issuing the command: ./bin/project-setup --copy-module sample-tlm2-dma-device
in the Simics project directory. This DMA device has no dependency on Simics and can be built as a standalone application. By default, it is built with Intel SystemC kernel by issuing the following command: make -f modules/sample-tlm2-dma-device/Makefile.standalone
No binary is included for Intel SystemC kernel. It needs built in the Simics project first before building the DMA standalone application. See 8 for more details about building Intel SystemC kernel.
User can also build the standalone application using their own SystemC kernel by configuring the SYSTEMC_CORE_CFLAGS
and SYSTEMC_CORE_LDFLAGS
flags.
The device's code will not be described in detail in this guide, but basically, the device implements a simple DMA device with source and destination address registers as well as a control register to start a DMA transaction and control some features such as interrupt on completion. The DMA device can work in "polling" mode, where the software checks the status bit in the control register for DMA completion, or it can work in interrupt mode where an interrupt is issued when the transfer is completed. In addition the DMA device supports reset. The interrupt and reset are modeled as SystemC signals and inbound and outbound memory accesses are modeled as SystemC TLM2 transactions. The focus of this section is on showing how these signals and transactions are translated to Simics interface calls.
In order to connect the SystemC DMA device to Simics an Adapter
class is defined as shown below:
namespace scl = simics::systemc;
class Adapter : public scl::Adapter,
public scl::simics2tlm::IoMemoryGasketAdapter,
public scl::simics2tlm::DirectMemoryUpdateGasketAdapter {
public:
explicit Adapter(simics::ConfObjectRef o)
: scl::Adapter(o),
IoMemoryGasketAdapter(&systemc_io_memory_, this),
DirectMemoryUpdateGasketAdapter(&systemc_direct_memory_update_, this),
dma_(this, "DMADevice") {
systemc_io_memory_.set_gasket(scl::simics2tlm::createGasket(
&dma_->mmio_socket_, o));
simics_interrupt_->set_pin(&dma_->interrupt_);
simics_memory_space_->set_gasket(scl::tlm2simics::createGasket(
&dma_->phys_mem_socket_, o));
systemc_direct_memory_update_.set_gasket(
simics_memory_space_->gasket());
systemc_reset_.set_pin(&dma_->reset_, false, o);
}
static void init_class(simics::ConfClass *cls);
// Outgoing from DMA device to Simics
scl::Connector<scl::tlm2simics::MemorySpace> simics_memory_space_;
scl::Connector<scl::systemc2simics::Signal> simics_interrupt_;
class Port : public simics::Port<Adapter>,
public scl::simics2systemc::SignalGasketAdapter {
public:
explicit Port(simics::ConfObjectRef o)
: simics::Port<Adapter>(o),
SignalGasketAdapter(&parent()->systemc_reset_, parent()) {
}
};
private:
// Incoming from Simics to DMA device
scl::simics2systemc::Signal systemc_reset_;
scl::simics2tlm::IoMemory systemc_io_memory_;
scl::simics2tlm::DirectMemoryUpdate systemc_direct_memory_update_;
// DMA device implementation by SystemC/TLM
scl::Device<DMADevice> dma_;
};
In the code example, the Adapter
class:
A Simics interface is defined by a set of function pointers. To expose such an interface from a SystemC model to Simics the set of function pointers should be mapped to the functions of an object which implements the corresponding C++ version of the interface. A simics-interface-adapter is used for this purpose. It provides the necessary static functions and forwards Simics interface function calls to the object. The simics-interface-adapter must be registered with Simics, along with the Adapter
class, as shown in the init_local
function later in the chapter.
The implementation of the C++ version of the Simics interface is provided by a gasket-adapter. In the code example, the Adapter
class derives from several gasket-adapter classes and thus exposes the corresponding interfaces. To expose the same type of interface multiple times, gasket-adapters on port are needed. See 4.7 for more details. Additionally, gasket-adapters are responsible for managing simulation context.
The gasket-adapter forwards the C++ interface call to the corresponding gasket which then translates the call to the corresponding SystemC semantics. In the DMA example there is a gasket, called simics::systemc::simics2systemc::Signal
, which converts a call to the Simics signal
interface into a SystemC signal; and there is another gasket, called simics::systemc::simics2tlm::IoMemory
, which converts a call to the Simics io_memory
interface into a call to TLM2 b_transport
. Each gasket must be bound to the SystemC target and corresponding gasket-adapter in the constructor of the Adapter
class.
The gasket which translates Simics interface call to a TLM2 transaction is special and needs additional explanation. In fact, this gasket is a gasket-owner or a container for several gasket-owners with specific IDs, where each gasket-owner owns one generic tlm-gasket object. The multiple gasket-owners and corresponding tlm-gaskets are needed for protocols where a single Simics interface is demultiplexed into multiple TLM target sockets. The gasket-owner creates a TLM generic payload with protocol specific extension which is then transmitted to the SystemC target using the tlm-gasket. Each tlm-gasket must be created and bound to the SystemC target with the help of createGasket
factory function. Then, the tlm-gasket must be assigned to a gasket-owner which in turn must be connected to the corresponding gasket-adapter, as shown in the constructor of the Adapter
class.
To connect a SystemC device to a Simics model via a Simics interface it is necessary to:
Register a simics-interface-adapter on the configuration class
Derive the Adapter
class from the corresponding gasket-adapter class, and initialize it with the corresponding gasket
Bind the SystemC target to the gasket using the gasket's member function
simics2tlm::createGasket
factory function. Then, assign the tlm-gasket to the gasket with the help of set_gasket
method of the gasket class Add the gasket as a member to the Adapter
class
In the example the Adapter
uses the following gaskets and gasket-adapters:
IoMemory
and IoMemoryGasketAdapter
- for inbound MMIO, corresponding to Simics io_memory
interface Signal
and SignalGasketAdapter
- for reset, corresponding to the Simics signal
interface DirectMemoryUpdate
and DirectMemoryUpdateGasketAdapter
- to support SystemC direct memory interface (DMI) optimization.A gasket can be used to access the SystemC model from Simics. But it is not the only supported entry point to do this. These are the supported entry points:
Gasket. Can be used for bridging from a Simics interface to a TLM/SystemC interface. For example, raising/lowering a Simics signal
interface which connects to SystemC model via gasket will raise/lower the SystemC input signal.
Device utility class. Whenever the SystemC model is accessed from the adapter directly, this utility class must be used. See code example below.
uint32 getRegister1() const {
return simple_device_->register1();
}
void setRegister1(const uint32 &val) {
register1_ = val;
if (SIM_object_is_configured(obj())) {
simple_device_->set_register1(val);
}
}
register1
and set_register1
are functions defined in SystemC model, but called from the device utility class. In this way, it ensures that the correct simulation context is set in the SC kernel.
Scheduler. The adapter has a helper object, adapter.engine
, that allows for the adapter to be scheduled by Simics. When scheduled, it runs SystemC time forward via the SystemC scheduler. Simics events can be posted on the adapter and are interleaved with the SystemC events and processes. Please note that Simics events would still need to communicate with SystemC via the adapter using Simics interfaces. The helper object always runs in picoseconds so running the adapter 1 ps forward will also run the SystemC scheduler 1 ps forward.
To access a Simics interface, implemented by a Simics object, from a SystemC model the Simics object must be assigned to a Simics attribute first. The attribute is created and registered with Simics using ConnectorAttribute
helper class in the init_local
function, and is called connector-attribute hereafter. The connector-attribute is needed to link the Simics attribute to the connector, which is a member of the Adapter
class. The connector is a Connector
class that wraps the gasket which is responsible for translation of the SystemC semantics to a call of the corresponding Simics interface. Also, the connector makes sure that the Simics interface that the gasket is intended to work with is implemented by the Simics object given to the connector-attribute. In the DMA example the adapter contains two connectors and hence two corresponding gaskets: one for converting a DMA SystemC TLM2 transaction to a Simics memory_space
interface call; and the other for converting an interrupt signal issued on a DMA completion to a Simics signal
interface call.
Similarly to the Simics-to-TLM case, the gasket which translates a TLM2 transaction to a Simics interface call is actually the gasket-owner which owns another tlm-gasket. Each tlm-gasket must be created and bound to the SystemC source, as well as assigned to a connector, as shown in the constructor of the Adapter
class.
In summary, to access a Simics interface from SystemC model it is necessary to:
Adapter
class tlm2simics::createGasket
factory function. Then, assign the tlm-gasket to the connector with the help of arrow operator and the set_gasket
function Calling a Simics interface-specific extension's method will result in an invocation of b_transport
and thus must be done from an SC_THREAD process according to the SystemC IEEE specification.
To register the adapter with Simics an instance of the template class ClassDef
is created the same way as if the C++ Device API is used. The init_local
function from the DMA example is shown below:
extern "C" void init_local(void) {
auto cls = simics::make_class<Adapter>(
DEVICE_CLASS, "sample SystemC TLM2 DMA device",
"The <class>" DEVICE_CLASS "</class> is a Simics module"
" encapsulating a SystemC TLM2-based dma device to demonstrate"
" the use of the Simics SystemC Library.");
}
The code also demonstrates how the connector-attributes and the simics-interface-adapters are registered with Simics along with the Adapter
class.
For a new SystemC model written from scratch or a simple SystemC model, it is recommended to compile it within the Simics project along with the adapter file. This approach ensures ABI compatibility since the same compiler, C++ language standard, and compiler options are used for both the SystemC model, the adapter file, and the SystemC Library libs.
By compiling the SystemC model within the Simics project, you can take advantage of the build system and configurations already set up for the Simics project. This simplifies the build process and ensures that all parts are compiled with the same settings.
However, there may be situations where the SystemC model already exists and it is challenging to integrate it into an Simics project. Or, the SystemC model source can not be provided to Simics for various reasons. In such cases, it may be necessary to compile the SystemC model outside of the Simics project. This can be done by following these steps:
Compile the SystemC model: In this step, the SystemC model is compiled separately outside of the Simics project.
Since Simics SystemC kernel is ABI compatible with the Accellera Standard SystemC kernel, it is not required to include the Simics SystemC kernel headers. An Accellera-compliant (ABI compatible) SystemC kernel of the same version as the one provided in Simics is OK to use when compiling the SystemC model.
It is required to use the -fPIC
(Position Independent Code) compiler option when compiling the SystemC model. This ensures that the code within the shared Simics module can be loaded and executed correctly.
Do not link the object files with the SystemC kernel library. The missing symbols from the SystemC kernel are resolved in step 4.
Add the .o files from step 1 to the Simics module build: The previous step generates object files (.o) for each source file in the SystemC model. These object files contain the compiled code and symbols specific to the model. They can be linked into the Simics module. This involves adding the necessary linker flags and paths in the Simics project's build system (e.g., Makefile, CMakeLists.txt) to include the SystemC model's object files during the linking phase. For example, specify the object files in the Simics Makefile variable EXTRA_OBJ_FILES
.
Add the dependencies needed by the .o files from step 1 to the Simics module build: This involves adding the necessary linker flags and paths in the Simics project's build system (e.g., Makefile, CMakeLists.txt) to include the SystemC model's dependencies during the linking phase. The Simics Makefile variables like MODULE_CFLAGS and MODULE_LDFLAGS could be used here.
Build the Simics Module: Finally, the Simics module is built using the Simics project's build system. The build process includes compiling the Simics module's source files and linking them with the SystemC model's object files, along with any other necessary dependencies.
By following this approach, the pre-compiled SystemC model can be integrated into the Simics project without the need to modify the original code.
Here is an example about how to compile a simple SystemC DMA model outside of Simics project and be used to link into a Simics module. The SystemC model in sample-tlm2-dma-device is used as an example, this model has no dependencies on the Simics files and any arbitrary SystemC model should work in the same way.
Copy dma-device.cc
and dma-device.h
from Simics Base package to a folder /path/to/device
Download SystemC kernel from Accellera to /path/to/systemc
Write a Makefile to compile the dma-device
CXX := /path/to/g++
CXXFLAGS := -std=c++14 -fPIC -I/path/to/systemc/src
all: dma-device.o
%.o : %.cc
$(CXX) $(CXXFLAGS) $^ -c
Compile the C++ file by invoking make
:
[/path/to/device] $ make
The generated object file dma-device.o
can be linked into the Simics module by removing dma-device.cc
from SRC_FILES
and adding the following line in the Simics module's makefile:
EXTRA_OBJ_FILES += /path/to/device/dma-device.o
Once the adapter has been implemented the model can be instantiated and connected in Simics like any other Simics model.
The vacuum target is good for simple testing, and using it the DMA device can be instantiated as follows:
simics> @SIM_create_object('sample_tlm2_dma_device', 'dma', phys_mem=conf.phys_mem)
<the sample_tlm2_dma_device 'dma'>
You can start the vacuum target from the GUI or command line, or by issuing the following command at the Simics prompt: run-command-file "%simics%/targets/vacuum/vacuum.simics"
Since the DMA device requires a memory-space where DMA operations are supposed to occur it is provided in the initial set of attributes. To access the registers in the DMA device it is necessary to map it in the IO space:
simics> phys_mem.add-map device = dma base = 0x1000 length = 0x100
Mapped 'dma' in 'phys_mem' at address 0x1000.
simics>
To configure and test the DMA device a pattern will be written to RAM and copied using the device:
simics> phys_mem.write 0x10000000 0xdeadbeef 8 -l # pattern
simics> phys_mem.write 0x1004 0x10000000 4 -l # src
simics> phys_mem.write 0x1008 0x10001000 4 -l # dest
simics> phys_mem.write 0x1000 0xc0000008 4 -l # control (start transfer)
simics> phys_mem.x 0x10001000
p:0x10001000 efbe adde 0000 0000 0000 0000 0000 0000 ................
simics>
Of course, more commonly the model will be instantiated from a module test or a component. Look in the test directory of the sample-tlm2-dma-device module's source code for more examples.
Sometimes it is necessary to pass configuration parameters to the constructor of the SystemC device. Such configuration parameters are typically passed to the adapter via Simics attributes. Since the attributes are not available in the constructor of the corresponding Adapter
class the creation of the SystemC device has to be deferred to the finalize phase. The Adapter
class has two convenient methods that are called in the beginning of the finalize phase, right after the SystemC context has been set up. These methods are elaborate
and bindGaskets
. The elaborate
method is intended for SystemC elaboration where Simics configuration attributes are required. Otherwise the elaboration could be done entirely in the Adapter
constructor. The bindGaskets
method is used to bind gaskets and other ports created in the elaborate method. An example of the methods is shown in the sample-tlm2-simple-device
device model:
class Adapter : public scl::Adapter
[...]
void elaborate() {
// Because we create the Device in elaborate, any attribute setters that
// use it must be guarded against early access (e.g. during checkpoint
// restore)
SimpleDevice *top = new SimpleDevice("simple_device", delay_ns_);
scl::Device<SimpleDevice> d(this, top);
simple_device_ = d;
// Handle attribute side-effects here
simple_device_->set_register1(register1_);
simple_device_->set_register2(register2_);
}
void bindGaskets() {
systemc_io_memory_.set_gasket(
scl::simics2tlm::createGasket(&simple_device_->target_socket,
obj()));
}
int delay_ns_; // Configured by attribute
private:
// The SystemC TLM device wrapped by the Simics object.
// NOTE: Must use the Device utility class to make sure any access to the
// SystemC device is handled correctly.
scl::Device<SimpleDevice> simple_device_;
[...]
Special care has to be taken if the adapter or SystemC model has dependencies on other Simics objects. It may be tempting to rely on the dependencies during the attribute setting phase, but this is not allowed. Simics objects, referenced by attributes, and interfaces they implement are not allowed to be accessed before the finalize phase. In the finalize phase SIM_require_object
must be called on a Simics object first to ensure that the object is configured. See
Model Builder User's Guide for more details on attribute initialization order.
Simics and the provided SystemC Library are both very flexible and can be configured in many different ways. For example, a virtual platform project may choose to use one or multiple adapters to map SystemC devices into Simics. It is important to understand these different configuration options since the selected solution will impact the overall performance of the virtual platform.
Each SystemC Library based Simics module is statically linked with Intel SystemC Kernel (ISK). Each adapter instance from each such module, when created in Simics, will be assigned its own kernel context (i.e. the sc_simcontext).
The kernel contexts are isolated from one another, making it impossible to communicate during delta cycles between adapters using the SystemC scheduler. Hence, if really tight integration is required between two different SystemC modules, it is suggested to wrap them using a single adapter.
Even though Simics SystemC Library supports multiple instances of each adapter, the kernel itself is not thread-safe. This means that extra care must be taken when creating multi-cell configurations. There are two rules:
check-cell-partitioning
to verify that the cells have been setup correctly. For the same reason, multiple instances of adapters from the same Simics module does not work in the multicore accelerator mode either.
It is also encouraged to read up on the set-time-quantum
command. This command can be used to change the fidelity of the interaction/scheduling between different Simics clocks. That is, one can specify the number of cycles that should be executed on each Simics clock before the next Simics clock is being scheduled. A large time quantum makes it possible to leverage decoupling and ISS optimizations and a small time quantum is good for tight integration between models.
It should also be noted that it is possible to change the number of instructions that are executed per cycle in Simics. This can also be used to tweak how much time is spent and work is done in the different models. Hence, it can be used to change tightness of interaction versus performance in the virtual platform. Please refer to the Simics User's Guide and the API Reference Manual for more information.
It is suggested that these parameters are considered for each virtual platform project and use case.
As already explained, there is a chain of objects that interact in order to translate a Simics interface invocation into a SystemC signal or TLM2 transaction and vice versa. SystemC Library already provides simics-interface-adapters for the most common of Simics interfaces, gasket-adapters and gaskets needed. But for other interfaces, or custom interfaces, these classes must be provided by the model developer.
The best way to implement a new set of classes for an unsupported Simics interface is to base it on an existing set. The SerialDevice
gasket and accompanied set of classes can be used as an example as it maps the Simics serial_device
interface for both Simics → TLM and TLM → Simics directions. We will follow this example in this chapter to describe the typical set of classes to be added by the model developer. The details for each type of class can be found in the
SystemC Library API Reference Manual. The new set should be added to a separate module referenced by EXTRA_MODULE_VPATH until it has eventually been merged into SystemC Library.
Any gasket written for a standard Simics interface should be sent to the Simics team for inclusion into the library.
The Simics to TLM invocation chain, which translates Simics interface call to TLM2 transaction, looks like this: Simics interface → simics-interface-adapter → gasket-adapter → gasket-owner → tlm-gasket → SystemC TLM2 target socket.
The io_memory
and serial_device
are two good examples of Simics interfaces translated into TLM2. The io_memory
interface translates to a generic payload, but the serial_device
interface translates to a protocol specific extension using built-in Simics interface to TLM payload extension marshal/unmarshal capabilities. See the SerialDeviceExtension
class for details.
It is highly recommended to use the built-in marshal/unmarshal capabilities to cut down on the number of Simics specific details exposed to the SystemC model.
Though it is possible to use the Simics C++ API to implement a Simics interface on the Adapter
class and forward the interface call to the SystemC model, this is highly discurraged. It is important that the SystemC simulation context is properly set on enter and reset on exit. Failure to set SystemC simulation context will lead to a crash.
This is provided by a utility classes in the SystemC Library. More specifically, Context
class used by the gasket-adapters. Also the tlm-gasket itself, defined by the Gasket
class, provides the necessary wrappings for running the transaction in an SC_THREAD since b_transport
method might call wait
function.
For describing how custom gaskets are created we will use the simics2tlm::SerialDevice
class as an example. The Simics header file simics/devs/serial-device.h
specifies the Simics interface as:
SIM_INTERFACE(serial_device) {
int (*write)(conf_object_t *obj, int value);
void (*receive_ready)(conf_object_t *obj);
};
Using the simics-interface-adapter, defined by SerialDeviceSimicsAdapter
class, the Simics interface is translated to the corresponding C++ interface. See the file simics/systemc/iface/serial_device_simics_adapter.h
for reference. The Simics C++ interface is defined in the file simics/systemc/iface/serial_device_interface.h
:
class SerialDeviceInterface {
public:
virtual int write(int value) = 0;
virtual void receive_ready() = 0;
virtual ~SerialDeviceInterface() {}
};
As one may notice, the same functions are provided but without the conf_object_t
pointer as parameter. The obj parameter is replaced with the this
pointer in C++.
Though it is possible to use different function names in the two interfaces, it is recommended to keep the same names for clarity.
The Simics C++ interface function call is further translated to the call of corresponding function in the gasket. The translation is required to adjust simulation context before the SystemC code is entered and is performed in the SerialDeviceGasketAdapter
class:
class SerialDeviceGasketAdapter
: public iface::SerialDeviceInterface,
public GasketAdapter<iface::SerialDeviceInterface> {
public:
SerialDeviceGasketAdapter(SerialDeviceInterface *serial_device,
iface::SimulationInterface *simulation)
: serial_device_(serial_device), simulation_(simulation) {
}
int write(int value) override {
Context context(simulation_);
return serial_device_->write(value);
}
void receive_ready() override {
Context context(simulation_);
serial_device_->receive_ready();
}
simics2tlm::GasketOwner *gasket_owner() const override {
return dynamic_cast<simics2tlm::GasketOwner *>(serial_device_);
}
private:
SerialDeviceInterface *serial_device_;
iface::SimulationInterface *simulation_;
};
The serial_device_
member is a helper object which combines the adjustment of the simulation context, provided by simulation_
member, with the call of the Simics C++ interface function implemented by the gasket. The gasket is defined by the SerialDevice
class in the simics/systemc/simics2tlm/serial_device.h
:
class SerialDevice : public simics::systemc::iface::SerialDeviceInterface,
public GasketOwner {
public:
virtual void gasketUpdated();
// SerialDeviceInterface
int write(int value);
void receive_ready();
private:
ExtensionSender sender_;
iface::SerialDeviceExtension extension_;
};
The extension_
member, defined by SerialDeviceExtension
class, provides utility functions to perform marshalling of the interface data to a protocol specific TLM extension. The TLM extension along with payload is then sent to the SystemC target with the help of the sender_
. The protocol specific extensions are described in the 4.6.3.
The TLM to Simics invocation chain looks like this: SystemC TLM2 initiator socket → tlm-gasket → gasket-owner → Simics interface.
The memory_space
and serial_device
are two good examples of Simics interfaces translated from TLM2. The memory_space
interface translates from a generic payload, but the serial_device
interface translates from a protocol specific extension using built-in Simics interface to TLM payload extension marshal/unmarshal capabilities. See the SerialDeviceExtension
class for details.
It is highly recommended to use the built-in marshal/unmarshal capabilities to cut down on the number of Simics specific details exposed to the SystemC model.
It is also highly recommended to use the ConnectorAttribute
and Connector
utility classes when implementing the connector-attribute in the adapter. See the source code for the sample-tlm2-dma-device
module for an example of how to do this.
For describing how custom gaskets are created we will use the tlm2simics::SerialDevice
class as an example. The definition of this gasket can be found in the file: simics/systemc/tlm2simics/serial_device.h
. Being derived from the TransactionHandler
class, the SerialDevice
gasket class owns the tlm-gasket and performs the translation of the TLM payload to the call of the Simics interface. The translation is initiated by the tlm-gasket via the invocation of the simics_transaction
method defined in the SerialDevice
class:
class SerialDevice : public InterfaceProvider,
public TransactionHandler,
public iface::SerialDeviceInterface {
public:
SerialDevice() : InterfaceProvider("serial_device"),
TransactionHandler(this,
iface::SerialDeviceExtension::createIgnoreReceiver()),
receiver_(
iface::SerialDeviceExtension::createReceiver(this)) {}
// SerialDeviceInterface
int write(int value) override;
void receive_ready() override;
// TransactionHandler
iface::ReceiverInterface *receiver() override;
virtual ~SerialDevice();
private:
tlm::tlm_response_status simics_transaction(
ConfObjectRef &simics_obj,
tlm::tlm_generic_payload *trans) override;
iface::ReceiverInterface *receiver_;
};
The payload is then forwarded to the receiver_
member which utilizes the utility functions, defined in the SerialDeviceExtension
class, to perform unmarshalling of the interface data from the protocol specific TLM payload extension and invoke the corresponding Simics C++ interface function. The protocol specific extensions are described in the 4.6.3.
Being derived from the InterfaceProvider
class, the gasket gets the Simics interface, provided by the associated Simics target object, using get_interface
template function and performs the interface call.
Simics SystemC Library provides the gaskets which convert Simics interfaces into/from TLM2 generic payloads using SystemC extension mechanism. The extension mechanism allows extending the generic payload, aimed at modeling memory-mapped buses only, with a protocol specific set of attributes, or simply extension, and thus allows transporting any Simics interface specific data.
In SystemC Library each extension is defined by a separate class in the corresponding simics/systemc/iface/*_extension.h
header file. As an example in this chapter we refer to the SerialDeviceExtension
extension class for the serial_device
Simics interface, which is defined in the simics/systemc/iface/serial_device_extension.h
file:
class SerialDeviceExtension : public Extension<SerialDeviceExtension,
SerialDeviceInterface> {
public:
virtual void call(SerialDeviceInterface *device) {
switch (method_.value<Method>()) {
case WRITE:
method_return_ = device->write(method_input_[0].value<int>());
break;
case RECEIVE_READY:
device->receive_ready();
break;
}
}
virtual int write(int value) {
method_input_.push_back(value);
method_ = WRITE;
send();
return method_return_.value<int>();
}
virtual void receive_ready() {
method_ = RECEIVE_READY;
send();
}
private:
enum Method {
WRITE,
RECEIVE_READY
};
};
To reduce the number of Simics specific details exposed to the SystemC model the extension provides utility functions which marshal and unmarshal Simics C++ interface specific data. In general, an extension with such utility functions can be implemented for any C++ interface. To send the C++ interface data in a TLM transaction the SerialDeviceExtension
class overrides write
and receive_ready
functions of the SerialDeviceInterface
interface class. Each function packs the interface function type along with function arguments into general storage, described later, and calls the send
function in order to attach the extension to the payload and initiate the TLM transaction. On the receiving end, there is a registered receiver, which unpacks the interface function type and its arguments from the storage and calls the corresponding C++ interface function. The unpacking is performed in the extension's call
method.
The general storage is provided by the method_
, method_input_
and method_return_
members of the Extension
base class defined in the simics/systemc/iface/extension.h
file. The method_
member keeps the interface function type, typically declared as Method
enumeration type in the extension class. The method_input_
keeps the function's arguments in a STL vector, while the method_return_
provides the function return value written in the call
function of the extension upon successful completion of the TLM transaction.
There are extension-sender and extension-receiver in the SystemC Library which simplify sending and receiving the extensions.
The extension-sender, intended for sending the extension from Simics-to-TLM gasket to SystemC target socket, is defined by the simics2tlm::ExtensionSender
class in the simics/systemc/simics2tlm/extension_sender.h
file:
class ExtensionSender : public iface::ExtensionSenderInterface {
public:
void init(simics2tlm::GasketInterface::Ptr gasket) {
gasket_ = gasket; // coverity[copy_instead_of_move]
}
virtual iface::Transaction transaction() {
return pool_.acquire();
}
virtual void send_extension(iface::Transaction *transaction) {
gasket_->trigger(transaction);
}
virtual void send_failed(iface::Transaction *transaction) {
SIM_LOG_ERROR(gasket_->simics_obj(), Log_TLM,
"Extension not processed correctly.");
}
private:
simics2tlm::GasketInterface::Ptr gasket_;
iface::TransactionPool pool_;
};
This extension-sender is typically added as a member to the corresponding Simics-to-TLM gasket class and is initialized with the tlm-gasket in the init
function. The sender has a pool of Transaction
objects to support multiple re-entry over the same gasket. The Transaction
class is a thin wrapper around tlm_generic_payload
class which supports a custom extension used by SystemC Library to track the transactions. A new Transaction
is acquired in the above-mentioned send
function of the Extension
base class which also sets the corresponding extension. The function then calls send_extension
method of the extension-sender which forwards the Transaction
to the tlm-gasket to perform the TLM transaction. If the transaction failed, the send_failed
method will produce Simics error message.
There is also a generic extension-sender, intended for sending the extension from a SystemC module to a SystemC module or to the TLM-to-Simics gasket. It is defined in the simics/systemc/iface/extension_sender.h
file and initialized with the initiator socket. The extension-sender sends the payload along with the extension using the b_transport
method of the socket.
The extension-receiver is defined by the ExtensionReceiver
class in the simics/systemc/iface/extension_receiver.h
file:
template<class TExtension, class TInterface>
class ExtensionReceiver : public ReceiverInterface {
public:
explicit ExtensionReceiver(TInterface *device)
: device_(device) {}
bool handle(tlm::tlm_generic_payload *payload) override {
TExtension *extension = payload->get_extension<TExtension>();
if (extension && extension->valid()) {
payload->set_response_status(tlm::TLM_OK_RESPONSE);
extension->method_call(device_);
return true;
}
return false;
}
bool probe(tlm::tlm_generic_payload *payload) override {
TExtension *extension = payload->get_extension<TExtension>();
return extension && extension->valid();
}
private:
TInterface *device_;
};
The extension-receiver is generic: it is intended for receiving the extension in any SystemC module, including the TLM-to-Simics gasket. It is specialized with the extension type and the C++ interface type, implemented by a target object. The TLM-to-Simics gasket is just one example of the target object. If the extension of the provided type is received, the extension-receiver invokes the Extension
's method_call
method which in turn invokes the call
function of the extension to perform unmarshalling of the C++ interface specific data. The extension-receiver should be created with the help of createReceiver
factory method of the Extension
class, which takes the device
pointer to the target object. There is also a createIgnoreReceiver
factory method useful for testing the protocol specific extensions. It creates a receiver which does not require the target object, and hence does not perform C++ interface call, but terminates the transaction correctly.
The target object may need to receive several extensions of different types, for example, if it implements several C++ interfaces. To support this, the extension-dispatcher, defined in the the simics/systemc/iface/extension_dispatcher.h
file, should be used. The extension-receivers of the expected extension types subscribe to the extension-dispatcher using subscribe
method. When a transaction with a matching extension is received, the extension-dispatcher forwards the extension to the corresponding receiver for unmarshalling of the C++ interface specific data.
As explained in the chapter 4, Simics interfaces can be exposed directly on the conf-object by using the simics-adapter and gasket-adapter classes. To expose the same type of interface multiple times, for example an interrupt controller with many interrupt sources, each interface must be registered on individual ports. This is done by creating a port class and registering it with a dedicated port object. See the chapter Port registration
of
Simics C++ Device API v2
documentation for details. An example of this is shown in the sample-tlm2-i2c-devices
device model.
This adapter wraps two SystemC I2C slave devices using Simics port object.
namespace scl = simics::systemc;
class SlaveAdapter : public scl::Adapter {
public:
explicit SlaveAdapter(simics::ConfObjectRef o)
: scl::Adapter(o) {
for (unsigned i = 0; i < 2; ++i) {
systemc_io[i].set_gasket(scl::simics2tlm::createGasket(
&i2c_slave_devs[i]->io_target_socket, o));
systemc_i2c[i].set_gasket(scl::simics2tlm::createGasket(
&i2c_slave_devs[i]->i2c_target_socket, o));
simics_i2c[i].set_gasket(scl::tlm2simics::createGasket(
&i2c_slave_devs[i]->i2c_master_initiator_socket, o));
}
}
static void init_class(simics::ConfClass *cls);
template <int id> int get_register() const;
template <int id> void set_register(const int &val);
template <int id> int get_i2c_address() const;
template <int id> void set_i2c_address(const int &val);
template <int id> simics::ConfObjectRef get_i2c_link() const;
template <int id> void set_i2c_link(const simics::ConfObjectRef &obj_ref);
class Port : public simics::Port<SlaveAdapter>,
public scl::simics2tlm::IoMemoryGasketAdapter,
public scl::simics2tlm::I2cSlaveV2GasketAdapter {
public:
explicit Port(simics::ConfObjectRef o)
: simics::Port<SlaveAdapter>(o),
IoMemoryGasketAdapter(&parent()->systemc_io[index()], parent()),
I2cSlaveV2GasketAdapter(&parent()->systemc_i2c[index()], parent()) {
}
};
private:
scl::simics2tlm::IoMemory systemc_io[2];
scl::simics2tlm::I2cSlaveV2 systemc_i2c[2];
scl::tlm2simics::I2cMasterV2 simics_i2c[2];
std::array<scl::Device<I2cSlave>, 2> i2c_slave_devs {{
{this, "i2c_dev0"},
{this, "i2c_dev1"}}};
};
[...]
auto port = simics::make_class<SlaveAdapter::Port>(
"sample_tlm2_i2c_slave.port", "i2C port", "I2C port");
port->add(scl::iface::createAdapter<
scl::iface::IoMemorySimicsAdapter<SlaveAdapter::Port>>());
port->add(scl::iface::createAdapter<
scl::iface::I2cSlaveV2SimicsAdapter<SlaveAdapter::Port>>());
cls->add(port, "port.I2C[2]");
[...]
extern "C" void init_local_slave(void) {
simics::make_class<SlaveAdapter>(
DEVICE_CLASS,
"sample OSCI TLM2 I2C slave",
"The <class>" DEVICE_CLASS "</class> is a Simics module"
" encapsulating a SystemC TLM2-based I2C slave to demonstrate"
" the use of Simics SystemC Library.");
}
A new Port
class is defined and derived from simics::Port<SlaveAdapter>
and two gasket-adapter classes. These two gasket-adapterclasses expose io_memory
and i2c_slave_v2
interfaces on the port object instance. They are initialized like the other gasket-adapter class except the first constructor parameter is bind to the gasket defined in the parent class. The index
function returns the array index of the port object if it is given an array-like name. It is used to bind to the right gasket.
The new Port
class is registered like the normal Simics class. Also the same for the Simics adapter registration. When registering the port class to the device class, a name for the port is provided as a function parameter. If an array-like name is provided, a port array is registered. In the above example, the two port objects are named like port.I2C[0]
and port.I2C[1]
.
Sometimes there are many gaskets with the same type, but for some reason cannot use the port array. An example of this is shown here.
class Port : public simics::Port<test_sc_gasket_port_adapter>,
public simics::systemc::simics2systemc::SignalGasketAdapter {
public:
explicit Port(simics::ConfObjectRef o)
: simics::Port<test_sc_gasket_port_adapter>(o),
SignalGasketAdapter(&(parent()->*port_gasket[name()]), parent()) {
}
static std::map<std::string,
simics::systemc::simics2systemc::Signal
test_sc_gasket_port_adapter::*> port_gasket;
};
private:
simics::systemc::simics2systemc::Signal systemc_first_in_;
simics::systemc::simics2systemc::Signal systemc_second_in_;
simics::systemc::simics2systemc::Signal systemc_third_in_;
simics::systemc::Device<test_sc_gasket_port_module> dut_;
};
std::map<std::string,
simics::systemc::simics2systemc::Signal
test_sc_gasket_port_adapter::*>
test_sc_gasket_port_adapter::Port::port_gasket {
{"port.first", &test_sc_gasket_port_adapter::systemc_first_in_},
{"port.second", &test_sc_gasket_port_adapter::systemc_second_in_},
{"port.third", &test_sc_gasket_port_adapter::systemc_third_in_},
};
The port class here acts like a trampoline by defining a map between the port name and the bind gasket. Thus no need to create a port class to bind to each individual gasket.
Section 4.1 shows how gaskets are added to the configuration as part of the adapter's source code. The current section shows a different approach, where the gaskets are created and configured as pre-conf objects before they are added to the configuration by invoking SIM_add_configuration()
. In such a configuration the adapter only need to setup the SystemC model but not any gaskets. The gaskets are connected as part of finalizing the Simics configuration.
Therefore, when gasket objects are used, one can use the same adapter template class for all kinds of SystemC models:
template<class TModel>
class Adapter : public simics::systemc::Adapter {
public:
explicit Adapter(simics::ConfObjectRef o)
: simics::systemc::Adapter(o)
, top_("top") {}
private:
TModel top_;
};
In the following example, Top
is used as SystemC model and template parameter for the Adapter
class. The model is just a dummy that creates two sockets to show how they are connected to the gasket objects further below.
class Top : public sc_core::sc_module {
public:
SC_CTOR(Top)
: target_socket_("target_socket")
, initiator_socket_("initiator_socket") {
target_socket_.register_b_transport(this, &Top::b_transport);
}
private:
tlm_utils::simple_target_socket<Top> target_socket_;
tlm_utils::simple_initiator_socket<Top> initiator_socket_;
void b_transport(tlm::tlm_generic_payload &trans, // NOLINT: SystemC API
sc_core::sc_time &local_time) {
initiator_socket_->b_transport(trans, local_time);
}
};
The binding between the gaskets and the TLM2 sockets is based on the hierarchical names of the sockets. The following example shows how this can be done from CLI, but typically this is done in a component or from a python test.
simics> @adapter = pre_conf_object('adapter', 'sample_tlm2_gasket_device_doc_example')
simics>
simics> @io_gasket = pre_conf_object('io_gasket', 'sample_tlm2_gasket_device_gasket_simics2tlm_IoMemory')
simics> @io_gasket.target = 'top.target_socket'
simics> @io_gasket.simulation = adapter
simics>
simics> @ms = pre_conf_object('ms', 'memory-space')
simics> @ms_gasket = pre_conf_object('ms_gasket', 'sample_tlm2_gasket_device_gasket_tlm2simics_MemorySpace')
simics> @ms_gasket.initiator = 'top.initiator_socket'
simics> @ms_gasket.simulation = adapter
simics> @ms_gasket.object = ms
simics>
simics> @adapter.gasket_list = [io_gasket, ms_gasket]
simics>
simics> @SIM_add_configuration([adapter, io_gasket, ms_gasket, ms], None)
All gasket objects must be listed in the adapter's gasket_list
attribute. The order in which the gasket objects are finalized is controlled in such a way that the adapter first creates the SystemC world and then each gasket object binds the sockets according to their attributes.
There are different types of gasket objects and they require different attributes to be set at configuration. All gaskets require the attribute simulation
to be set. The simulation
attribute couples the gasket to the adapter. In addition, gaskets translating Simics interface calls to TLM2 transactions, i.e. simics2tlm gaskets, require that the target
attribute is set. The target
attribute binds the initiator socket within the gasket to the target socket in the SystemC model. Gaskets translating TLM2 transactions to Simics interface calls, i.e. tlm2simics gaskets, require that the initiator
attribute is set. The initiator
attribute binds the target socket within the gasket to the initiator socket in the SystemC model. The tlm2simics gaskets has one additional attribute named object
. This should point to a Simics object implementing the corresponding interface of the gasket, and can be left unset.
The third type of gasket objects is the composite PCI gasket. Required attributes are device
, pci_bus
and simulation
. For more details see the sample-tlm2-pci-device source code.
To prevent problems caused by incompatible toolchains it is not possible to set the simulation
attribute of the gasket object to an adapter built from a different module than the gasket.
Each module builds its own set of gasket objects. Therefore the conf-class names used for these gasket objects are prefixed with the module's name. The following gasket objects are currently supported:
Besides the flexibility of binding, gasket objects can be used to reduce the code size and increase the code readability. For example, if a SystemC module exposes hundreds of signals, the traditional way of adding gaskets to the Adapter quickly explodes as each new interface of the same type requires four lines of code. Using gasket objects could replace these four hundred lines of code with just a few lines.
In the following example, TopVector
is used as SystemC model and template parameter for the Adapter
class. The model has 100 signal input and 100 signal output. The example shows how easy the signals can be connected to the gasket object further below.
class TopVector : public sc_core::sc_module {
public:
SC_CTOR(TopVector)
: sc_in_vec_("sc_in_vec", VECTOR_SIZE)
, sc_out_vec_("sc_out_vec", VECTOR_SIZE) {}
static const int VECTOR_SIZE = 100;
private:
sc_core::sc_vector<sc_core::sc_in<bool>> sc_in_vec_;
sc_core::sc_vector<sc_core::sc_out<bool>> sc_out_vec_;
};
The following example shows how this can be done from a python script.
import dev_util as du
import simics
adapter = simics.pre_conf_object('adapter', 'sample_tlm2_gasket_device_doc_vector_example')
VECTOR_SIZE = 100
vector_in = [None] * VECTOR_SIZE
for idx in range(VECTOR_SIZE):
vector_in[idx] = simics.pre_conf_object('vector_in_%d' % idx,
'sample_tlm2_gasket_device_gasket_simics2systemc_Signal')
vector_in[idx].signal = 'top.sc_in_vec_%d' % idx
vector_in[idx].simulation = adapter
signal_object_vect = [du.Dev([du.Signal]) for _ in range(VECTOR_SIZE)]
vector_out = [None] * VECTOR_SIZE
for idx in range(VECTOR_SIZE):
vector_out[idx] = simics.pre_conf_object('vector_out_%d_' % idx,
'sample_tlm2_gasket_device_gasket_systemc2simics_Signal')
vector_out[idx].signal = 'top.sc_out_vec_%d' % idx
vector_out[idx].object = signal_object_vect[idx].obj
vector_out[idx].simulation = adapter
adapter.gasket_list = vector_in + vector_out
simics.SIM_add_configuration([adapter] + vector_in + vector_out, None)
The transaction
interface is preferred over the io_memory
interface to perform memory/IO transaction. The transaction_t
data type used in the interface is more flexible and supports more features than the old generic_transaction_t
data type. For information about transaction atoms, transaction_t
data type and transaction API, please refer to the chapter Transactions of Model Builder User's Guide for more details.
The Transaction
gasket bridges between the Simics transaction
interface and the TLM blocking transport interface.
The Simics transaction
interface looks as follows:
class TransactionInterface {
public:
virtual exception_type_t issue(transaction_t *t, uint64 addr) = 0;
virtual ~TransactionInterface() {}
};
The single method issue
takes two arguments. The first argument is the transaction_t
holding all the information about the transaction and it's payload. The second argument is the address to which the transaction was issued. It is a local offset into the mapped device.
The TLM blocking transport interface looks as follows:
void b_transport(tlm::tlm_generic_payload &trans, sc_core::sc_time &t);
To perform memory transactions from Simics to the SystemC model, SystemC Library provides the Simics-to-TLM simics2tlm::Transaction
gasket which converts the transaction
interface calls to SystemC TLM payload:
/**
* Class that implements the Simics transaction interface and translates it into
* a TLM transaction.
*
* The TLM2 return codes are translated to Simics like this:
* TLM_OK_RESPONSE => Sim_PE_No_Exception,
* TLM_ADDRESS_ERROR_RESPONSE => Sim_PE_IO_Not_Taken or
* Sim_PE_Inquiry_Outside_Memory on inquiry access,
* remaining TLM2 errors => Sim_PE_IO_Error or
* Sim_PE_Inquiry_Unhandled on inquiry access
*/
class Transaction : public simics::systemc::iface::TransactionInterface,
public GasketOwner {
public:
exception_type_t issue(transaction_t *transaction, uint64 addr);
private:
/*
* Update the TLM transaction before sending it over to the SystemC side
* By default this function does nothing since the TLM transaction has
* been filled with the basic required information. It can used to modify
* the filled information or add more information including customized
* extensions.
*
* @param simics_transaction the transaction received from Simics side
* @param tlm_transaction the TLM transaction to be sent over to the SystemC
* side
*/
virtual void update_transaction(const transaction_t *simics_transaction,
tlm::tlm_generic_payload *tlm_transaction) {
}
iface::TransactionPool pool_;
};
The gasket converts the transaction(transaction
) and address(addr
) to TLM trans
. It only supports blocking transport. The TLM2 return codes are translated to Simics like this:
If the b_transport
function calls wait
, directly or indirectly, the transaction is completed asynchronously. Otherwise, it is completed synchronously. The gasket handles both cases automatically and no extra step is needed from the SystemC side. The deferred transaction information is buffered. Once the device is ready with the requested operation, the deferred transaction is completed by calling SIM_complete_transaction
.
To perform memory transactions from the SystemC model to Simics, SystemC Library provides the TLM-to-Simics tlm2simics::Transaction
gasket which converts the SystemC TLM payload to the transaction
interface calls:
/** Protocol specific transaction handler for Simics transaction interface.
*/
class Transaction : public InterfaceProvider,
public DmiTransactionHandler {
public:
Transaction();
virtual ~Transaction();
// DmiTransactionHandler
void set_gasket(GasketInterface::Ptr gasketInterface) override;
private:
tlm::tlm_response_status simics_transaction(
ConfObjectRef &simics_obj,
tlm::tlm_generic_payload *trans) override;
unsigned int debug_transaction(ConfObjectRef &simics_obj,
tlm::tlm_generic_payload *trans) override;
unsigned int transaction(ConfObjectRef &simics_obj, // NOLINT
tlm::tlm_generic_payload *trans, bool inquiry);
/*
* Update the Simics transaction atoms before sending it to the Simics side
* By default, it is empty since the basic required atoms are already filled.
* It can be used to update the existing atoms or add new customized atoms.
*
* @param tlm_transaction the TLM transaction received from SystemC side
* @param atoms the atoms used in the Simics transaction sent to the Simics
* side. No need to add ATOM_LIST_END here.
*/
virtual void add_custom_atoms(
const tlm::tlm_generic_payload *tlm_transaction,
std::vector<atom_t> *atoms) {}
class UpdateTarget : public InterfaceProvider::TargetUpdateListener {
public:
UpdateTarget() : map_target_(NULL) {}
virtual ~UpdateTarget() {
if (map_target_)
SIM_free_map_target(map_target_);
}
// InterfaceProvider::TargetUpdateListener
void update_target(ConfObjectRef old_target,
ConfObjectRef new_target) override {
if (map_target_) {
SIM_free_map_target(map_target_);
map_target_ = nullptr;
}
if (new_target) {
map_target_ = SIM_new_map_target(new_target.object(),
NULL, NULL);
}
}
map_target_t *map_target() {
return map_target_;
}
private:
map_target_t *map_target_;
};
UpdateTarget update_target_;
// The proxy object for the target_socket used in this gasket
conf_object_t *target_socket_proxy_obj_ {nullptr};
};
The gasket converts the TLM trans
to Simics transaction_t
. It currently only support sending transaction synchronously using TLM blocking transport interface (b_transport
) and debug transport interface (transport_dbg
).
The TLM2 return codes are translated from Simics like this:
Besides the Simics defined transaction atoms, it is possible to define custom atoms. Refer to the Transactions of Model Builder User's Guide for more details about how to define custom atoms.
Custom gaskets are needed to handle the custom atoms. And the official transaction gaskets provide help function to make the handling with custom atoms easier. For a custom simics2tlm gasket, the logic of converting the Simics custom atoms to TLM extension should be implemented in the overloaded update_transaction
method. The method is called before sending the transaction to the TLM blocking transaction interface. Example showing this conversion:
// Custom Simics2Tlm Gasket that handles the custom atom
class CustomSimics2TlmTransaction : public scl::simics2tlm::Transaction {
void update_transaction(const transaction_t *simics_transaction,
tlm::tlm_generic_payload *tlm_transaction) override {
custom_atom_ext.custom_atom = ATOM_get_transaction_custom_atom(
simics_transaction);
tlm_transaction->set_extension(&custom_atom_ext);
}
// We should use a pool to support multiple async transactions
CustomExtension custom_atom_ext;
};
In the opposite direction, the logic of creating Simics custom atoms from TLM extensions should be implemented in the overloaded add_custom_atoms
method. The method is called before sending it to the Simics transaction
interface. Example showing this conversion:
// Custom Tlm2Simics Gasket that handles the custom atom
class CustomTlm2SimicsTransaction : public scl::tlm2simics::Transaction {
void add_custom_atoms(const tlm::tlm_generic_payload *tlm_transaction,
std::vector<atom_t> *atoms) override {
CustomExtension *custom_atom_ext = nullptr;
tlm_transaction->get_extension<CustomExtension>(custom_atom_ext);
if (custom_atom_ext) {
atoms->push_back(ATOM_custom_atom(custom_atom_ext->custom_atom));
}
}
};
Transactions can be completed asynchronously, provided that the initiator supports it.
When Simics side initiates a transaction, the simics2tlm::Transaction
sends it over to the SystemC side through the blocking transport interface(See 4.9.1.5 for how to connect it to non-blocking transport interface). The SystemC side could call wait
to simulate the delay in returning data. For synchronous transaction calls, this blocks the Simics simulation until the data is returned. See 6 for more details.
For transactions that can be completed asynchronously, i.e. where the initiator has issued a transaction with a completion callback, the transaction is automatically deferred by the gasket when wait
is called from the SystemC side. This allows the gasket to return immediately back to Simics. Later when SystemC runs and the data is returned from the b_transport
function, the gasket sends the data back to Simics by invoking the completion callback registered on the Simics initiator.
When SystemC side initiates a transaction into Simics it can only be deferred if the non-blocking transport has been used. The nb_transport_fw
function sends the transaction to the Simics side. When the transaction is deferred, tlm::TLM_ACCEPTED
is returned. The response data is later sent via the nb_transport_bw
function defined on the SystemC initiator.
The non-blocking transport interface cannot directly connect with the Simics gaskets. A converter is needed in between to bridge a blocking transport interface to a non-blocking one. Simics does not provide such converter. User can either write own converter or use the convenience sockets from tlm_utils
. For example, In SystemC/TLM, the simple_target_socket
provides built-in support for converting blocking to non-blocking behavior. Please refer to the chapter Convenience sockets of OSCI TLM-2.0 language reference manual for more details.
Simics contains a model of the widely used PCI bus. SystemC Library provides the PCIe gasket enabling users to connect a PCI device written in SystemC to the Simics PCI bus. The gasket is implemented by composite::PciGasket
class which is defined in simics/systemc/composite/pci_gasket.h
file.
To use the gasket the Adapter
class must be derived from the composite::PciGasket
class, and the gasket must be initialized with a pointer to the Adapter
instance.
The main connect
function of the composite::PciGasket
class, which takes the device as a parameter, binds device sockets to a set of helper gaskets used by the PCIe gasket internally. Refer to sample-tlm2-pci-device
module which demonstrates how the PCIe gasket is used.
Inside the gasket the connection is performed with the help of PciMappingInterconnect
class. The class is intended for snooping write accesses to configuration registers and updating Simics specific mapping of the device in Simics memory/IO address space. The class also removes the corresponding mapping upon receiving a bus reset.
A SystemC PCI device, connected to the PCI bus via the PCIe gasket, must implement the following interfaces:
PciDeviceQueryInterface
BaseAddressRegisterQueryInterface
PciDeviceInterface
The PciDeviceQueryInterface
interface exposes a set of sockets and is shown below:
class PciDeviceQueryInterface {
public:
// config space, pci_bus interface and pci_device interface are required
virtual sc_core::sc_object *getConfigTargetSocket() = 0;
virtual sc_core::sc_object *getPciBusInitiatorSocket() = 0;
virtual sc_core::sc_object *getPciDeviceTargetSocket() = 0;
virtual ~PciDeviceQueryInterface() {}
};
The getConfigTargetSocket
function must return a target socket intended to receive downstream transactions to the PCI configuration space. The transactions are forwarded from Simics to SystemC via Simics-to-TLM IoMemory
gasket used by the PCIe gasket. In case of multi-function PCI device the function must return the multi-passthrough target socket. This socket, bound to the initiator socket(s) of the IoMemory
gasket, will get transactions for all functions. Sequential IDs of the initiator sockets correspond to ordered numbers of the valid device functions. For example, functions 0, 3 and 5 of a multi-function device will get the transactions from initiator sockets 0, 1 and 2 correspondingly.
The getPciBusInitiatorSocket
function must return an initiator socket intended to send upstream transactions to the bus. In the PCIe gasket the transactions are translated to the pci_bus
and pci_upstream_operation
interface calls using the TLM-to-Simics PciBus
gasket. The device is supposed to use the PciBusExtension
and PciUpstreamOperationExtension
extensions attached to the TLM payload correspondingly.
A TLM payload sent without the PciBusExtension
or the PciUpstreamOperationExtension
will be dropped by the PCIe gasket.
Upon SW writing to configuration registers that control access to device IO/memory space (BARs and Command register) or upon a bus reset, the PCIe gasket automatically updates or removes device mappings in Simics. This is performed via invocation of the add_map
or remove_map
functions of the pci_bus
interface.
The gasket snoops accesses to standard BARs (BAR0-5) as defined by Type 0/1 PCI Headers. Accesses to device specific BARs, and hence the updating or removing of the corresponding mappings in Simics, must be handled by the device. This also includes handling the PCI bus reset correctly for the device specific BARs.
To perform reads from or writes to the Simics PCI bus the device should use the read
and write
functions of the pci_upstream_operation
interface. Note that access type (i.e. read or write), transaction address, data and size must be encoded in the TLM payload as the PciUpstreamExtension
extension does not transfer them. Refer to sample-tlm2-pci-device
module for an example.
The getPciDeviceTargetSocket
function must return the target socket which receives downstream transactions converted from pci_device
interface calls by the helper Simics-to-TLM PciDevice
gasket of the PCIe gasket. The device is supposed to handle the TLM payload along with the PciDeviceExtension
extension using an extension-receiver. This also means that the device must implement PciDeviceInterface
interface in order to create the extension-receiver. Please refer to chapter 4.6.3 for details. Note that prior to forwarding the bus_reset
function call of the pci_device
interface to the device, the PCIe gasket will remove the device mappings which correspond to regions controlled by the supported standard BARs.
If the PCI device supports PCIe, the same target socket can be used to receive the PCI express messages converted from pci_express
interface calls by the helper Simics-to-TLM PciExpress
gasket of the PCIe gasket. The device will handle another TLM payload along with the PciExpressExtension
extension. Same extension-dispatcher can be used for both extensions. Please refer to chapter 4.6.3 for details.
In Simics, a PCIe device must implement the pci_express
interface to get a full 4K mapping in configuration space when connected to the pcie-bus.
The device exposes the memory/IO target sockets as well as the BARs data with the help of BaseAddressRegisterQueryInterface
interface shown below:
class BaseAddressRegisterQueryInterface {
public:
struct BaseAddressRegister {
int function; ///< PCI function that this BAR belongs to
int offset; ///< BAR offset (0x10-0x24)
bool is_memory; ///< Memory or IO BAR?
bool is_64bit; ///< 64-bit or 32-bit (memory) BAR?
int size_bits; ///< Size of BAR, in number of bits
int mapping_id; ///< Mapping ID, used to identify incoming transactions
};
typedef std::vector<BaseAddressRegister> BarInfo;
virtual BarInfo getBarInfo() = 0;
typedef std::vector<std::pair<BaseAddressRegister,
sc_core::sc_object *> > BarSockets;
virtual BarSockets getBarTargetSockets() = 0;
virtual ~BaseAddressRegisterQueryInterface() {}
};
The getBarInfo
function must return the BaseAddressRegister
configuration which includes the associated target socket for each implemented BAR.