Previous - Up - Next

5.2   Connecting Objects

As mentioned previously in this chapter, the usual way to make one object accessible from another object, so that the latter (the caller) may communicate with the former (the callee) via the interfaces it implements, is to let the caller have an attribute that can hold an object reference, and set that attribute to point to the callee object (typically done in an initialization script). Although it is possible to write an attribute definition by hand, suitable for connecting an object with a particular interface, it is much better to use a connect definition, which creates such an attribute with minimal effort.

An interface, in Simics, is a struct containing function pointers, and the definition of the struct must be visible both to the caller and the callee. (The standard Simics API interface definitions are included automatically by the DML compiler.) The convention in the Simics API is to use a C typedef to name the struct type, using the suffix _interface_t, and the DML compiler by default follows this convention when it generates interface-related code. E.g., the io_memory interface is described by a datatype io_memory_interface_t, which is a struct containing two function pointers map and operation. If the user wants to create new interfaces, he must write his own struct definitions; this is demonstrated below.

5.2.1   Connect Example

In the following example, we will create a second device and connect it to the first device via a user-defined interface. We start with the example device in section 2.1, and add the following declaration:

    connect plugin {
        interface talk {
            parameter required = true;
Then, we replace the line "log "info": ...;" with the following C function call:

The members of an interface are C functions, not DML methods, so do not use the call or inline keywords here. Also note that $plugin itself is given as the first argument to the function. This is standard in most interfaces used in Simics.

Our device will now have an attribute named plugin, which can hold object references; the attached objects are required to implement the talk interface. However, we cannot yet compile the module, since it is missing the definition of the interface.

5.2.2   Sharing Definitions

There are two main kinds of source files that may be shared by modules:

We will put the definition of our simple talk interface in a shared DML file. First of all, we need a place to put the file. Create a new directory [home]/workspace/include (e.g., using mkdir on a Unix system), and open the new file [home]/workspace/include/talk.dml in your editor. Put the following text in the file and save it:

    dml 1.0;

    struct talk_interface_t {
        void (*hello)(conf_object_t *obj);
(such a struct declaration in DML will both inform the DML compiler about the existence of the struct type, as well as cause a corresponding typedef-declaration in the generated C code).

Then, add this line to the example device code, to import the new file:

    import "talk.dml";

Finally, edit the Makefile for the example device: [home]/workspace/modules/simple_device/Makefile, and add the following option to the definition of DMLC_FLAGS:

(telling dmlc where to look for additional include files).

You should now be able to compile the example device with the connect added as described above.

Sharing C header files (when necessary) is similar to the above: just add a C compiler "-I..." flag to the makefile, and instead of the DML import directive, use a C #include within a header section, as in:

    header %{
      #include "stuff.h"

5.2.3   A Plug-in Module

To complete the example, we need an object that speaks the talk interface, which we can use to connect to our device. For this purpose, we add a new module to our workspace, as follows (cf. Section 2.1):

  $ [simics]/bin/workspace-setup --force --dml=plugin_module \
(note that the --force flag is needed to add a module to an existing workspace).

Edit the generated skeleton file [home]/workspace/modules/plugin_module/plugin_module.dml to look like this:

  dml 1.0;
  device plugin_module;

  import "talk.dml";

  implement talk {
      method hello {
          log "info": "Hi there!";
The only way to use the objects of this class is through the talk interface - there are no memory-mapped registers or similar connections. (Do not take the term "device" too literally; a DML source file does not have to model a piece of hardware - it just defines a component class that can be loaded in Simics.)

Also edit the device makefile: [home]/workspace/modules/plugin_module/Makefile, and add the option -I$(SIMICS_WORKSPACE)/include to the definition of DMLC_FLAGS, just like we did previously for the first example device.

5.2.4   Testing the Connect

Simply running make (or gmake) from the [home]/workspace directory should now compile both modules. Do this and restart Simics as before. (We assume that you have by now added the commands from Section 2.2 to the startup script, as described in Section 2.3, so that you do not have to enter them manually each time you run Simics.)

Enter the following command in the Simics console:

  simics> @SIM_create_object("plugin_module", "dev2", [])
to create an instance of the plugin device. (Simics loads the module automatically.)

Then, set the plugin attribute of the first example device to point to the new object:

  simics> dev1->plugin = dev2

We are now ready to run. Make a memory access to the first device:

  simics> phys_mem.get 0x1000
and you should see the following output:
  [dev2 info] Hi there!

Previous - Up - Next