3 Running a SystemC Model in Simics 5 Overview of SystemC Features
SystemC* Library  / 

4 Connecting SystemC and Simics Models

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.

4.1 DMA example

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:

In the example the Adapter uses the following gaskets and gasket-adapters:

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:

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:

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.

4.2 Integrating precompiled object files

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:

To ensure ABI compatibility, it is required to compile all the C++ files and libraries used in the following steps with the same version of the compiler, C++ language standard, and other compiler options that can impact the ABI compatibility.
  1. 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.

  2. 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.

  3. 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.

  4. 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.

  1. Copy dma-device.cc and dma-device.h from Simics Base package to a folder /path/to/device

  2. Download SystemC kernel from Accellera to /path/to/systemc

  3. 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
    
  4. Compile the C++ file by invoking make:

    [/path/to/device] $ make
    
  5. 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
    

4.3 Test the Device in Simics

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.

4.4 SystemC Device Configuration

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.

4.5 Guidelines for Configuring SystemC Model in Simics

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:

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.

4.6 Creating New Gaskets

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.

4.6.1 Simics to SystemC

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.

4.6.2 SystemC to Simics

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.

4.6.3 Protocol Specific Extensions

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.

4.7 Gasket Adapter Ports

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.

4.8 Gasket Objects

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)

4.9 Gaskets

4.9.1 Transaction Gasket

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);

4.9.1.1 Simics2TLM Transaction gasket

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.

4.9.1.2 TLM2Simics Transaction gasket

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:

4.9.1.3 Custom ATOMs

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));
        }
    }
};

4.9.1.4 Asynchronous Completion

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.

4.9.1.5 Non-blocking transport interface

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.

4.9.2 PCIe Gasket

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.

4.9.2.1 SystemC PCIe Device Interfaces

A SystemC PCI device, connected to the PCI bus via the PCIe gasket, must implement the following interfaces:

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.

3 Running a SystemC Model in Simics 5 Overview of SystemC Features