5 Programming with DML 7 DML Tips and Tricks
Model Builder User's Guide  /  II Device Modeling  / 

6 Building Well-Behaved Models

This chapter provides some insights into various guidelines and recommendations for how to build high quality models that integrate and perform well in Simics. You may also refer to chapter 8 for further guidance.

6.1 Structuring DML Code

The intention of this chapter is to standardize how to write DML devices. This includes how to structure the source code and naming DML objects, DML files, and devices. The purpose is to learn how to produce readable and maintainable code.

6.1.1 Device Names

The DML device name must contain the modeled device identification name. It is not recommended to name the device after the type of device (such as "watchdog" or similar), as there can be more than one device of that type in Simics.

The complete human-readable device name should be set using the device's desc parameter.

Example: a TBD4711 watchdog device should be named TBD4711 and its desc parameter should be set to "TBD4711 watchdog".

6.1.2 File Names

Running the project-setup script will give you a DML skeleton file and module directory with the same name as the device and in general this pattern should be followed. Occasionally when modeling a chip with distinct logical units which are not used individually it is appropriate to model both devices in one module directory, see section 9.5 for guidelines. The other case where deviation from standard naming is appropriate is when the device name is overly long. In these cases the following rules should be followed:

6.1.3 Bank Names

Descriptive bank names are vital to make the DML code easy to read. Bank names are also used when mapping regions in memory spaces.

This is the priority list when naming a bank:

  1. Registers are often documented in groups in the Programmer's Reference Manual for a device. It is best to use the group name from the manual when implementing a device from a user manual.
  2. Name the bank after the logical unit type, if the registers are not grouped, or the device is a logical unit in a SoC.
  3. The last alternative is to name the bank regs to highlight that the bank contains common registers.

6.1.4 Register Fields

The register field definitions can be written in several ways. Here are some examples of recommended ways to define fields.

register a size 4 @ 0x0 {
    field Enable   @ [0:0];
    field Disable  @ [4:1];
    field Trigger  @ [31:11];
}
register b size 4 @ 0x4 {
    field Enable  @ [0];
    field Disable @ [4:1];
    field Trigger @ [31:11];
}
register c size 4 @ 0x8 {
    field Trigger @ [31:11];
    field Disable @ [4:1];
    field Enable  @ [0];
}

The field order should always comply with the device documentation. It is otherwise hard to compare code and documentation.

It is often better to use @ [0:0] when there are several multi-bit fields in the device. But it is better to use @ [0] in a register with only single-bit fields.

6.1.5 Structuring DML Files

This section proposes a DML file structure that makes DML code easy to read. Keep in mind that you have to adapt these recommendations for your own devices.

The recommended order is:

To learn more about the sample DMA device and how it is implemented, refer to section 18.

6.1.6 Object Members and Scope

DML allows you to group methods and data together with DML objects. Here is an example:

attribute fifo {
    param type = "[i*]";
    session uint8 vals[MAX_VALS];

    // [...]

    method pop() -> (uint8) {
        // [...]
    }

    method push(uint8 val) {
        // [...]
    }
}

// [...]
fifo.push(17);

The pop() and push() methods and the vals session variable are members of the fifo attribute. This makes the usage of FIFO simpler and there is no confusion which method pops and which methods push data on the FIFO, as it would if the methods where global.

Here is another very useful template for attributes to use when saving dbuffer data:

template dbuffer_attribute {
    param type = "d|n";
    session dbuffer_t *buf;
    method set(attr_value_t val) throws {
        if (buf)
            dbuffer_free(buf);
        if (SIM_attr_is_data(val)) {
            buf = new_dbuffer();
            memcpy(dbuffer_append(buf, SIM_attr_data_size(val)),
                   SIM_attr_data(val), SIM_attr_data_size(val));
        } else {
            buf = NULL;
        }
    }
    method get() -> (attr_value_t) {
        if (!buf)
            return SIM_make_attr_nil();
        else
            return SIM_make_attr_data(dbuffer_len(buf),
                                      dbuffer_read_all(buf));
    }
}

// [...]
attribute frame {
    is dbuffer_attribute;
    param documentation = "An Ethernet frame.";
}

// [...]
send_frame(frame.buf);

6.2 General Usability

This chapter describes how to write device models that are easy to use and the generic rules on how to write device modules that comply with the standard way of writing Simics modules.

The user interface of a Simics module consists of three parts: its attributes, its interfaces, and the commands it adds to the Simics command line interface. You should try to make the user interface of your model similar in style to that of existing Simics models.

Every model should have an info command, giving static information about the device, and a status command, that gives dynamic information. See chapter 13.7 for more information. Model Builder also includes a library for writing tests to check that all devices in your modules have info and status commands. See the API Reference Manual for more information.

Look at the interfaces of similar devices to see what other commands may be useful, and try to use the same names for similar commands and parameters. Use existing API functionality where appropriate, rather than inventing your own.

6.2.1 Checkpointing

The ability to checkpoint and restore the state of a system is crucial to Simics functionality. Your device model should support checkpointing. In particular, you should ensure that:

As attribute setter functions for more complex attributes can be hard to get right, be sure to read 4.2.7.3 very carefully.

Attributes containing configuration parameters that never change during the lifetime of the device still need to accept setting their values. But since they will only be set with the value they already have, they only have to check that the value is unchanged and signal an error if not.

Ensure that the internal state of the device model is consistent at all times. If, for example, the model caches some information that depends on attribute values, these caches need to be flushed when the attribute is set. This is usually not a problem when restoring checkpoints from disk, but when using micro checkpoints and reverse execution it can easily cause trouble.

The checkpointing and reverse execution test libraries included with Model Builder can be used to test for at least basic support for these features.

6.2.2 Deterministic Models

Simics is deterministic and to keep the system deterministic all device models must be deterministic.

The basic rule to make a model deterministic is to save all device state data when writing checkpoints. The state is read from the device via the device attributes. Several DML object types implicitly corresponds to device attributes, examples are; attribute, register and connect.

Take extra care when using the data declaration as it does not implicitly correspond to an attribute.

6.2.3 Saving Data

There are several ways to save device data. The best way to save the data depends on how much data to save. A state with little data is best saved by creating an attribute with an integer or floating-point type or a small array:

attribute counter is uint64_attr {
    param documentation = "Counting number of packets.";
}

Saving larger blocks of unstructured data is best done by creating an attribute with type set to data:

attribute buffer_attribute is pseudo_attr {
    param documentation = "Packet data.";
    param type = "d|n";
}

Structured state can be saved in the form of lists, or list of lists etc:

attribute structured_attribute is pseudo_attr {
    param documentation = "A string and two integers.";
    param type = "[sii]";
    // [...]
}

The best way to save a large amount of data is to use Simics images. Images are optimized to only save differences between two consecutive checkpoints and not all data in each checkpoint:

import "simics/model-iface/image.dml";

connect data_image {
    param documentation = "Image holding data";
    param configuration = "required";
    interface image;
}

method save_data(uint64 address, const uint8 *buffer) {
    data_image.image.write(
        cast(buffer, const void *),
        address,
        256);
}

6.2.4 Support inquiry accesses

As listed in the Simics Model Development Checklist - Device Checklist , DE-11; device objects should handle inquiry accesses correctly. In Simics an 'inquiry access' is defined as an access without any side effects beyond changing the value of the register being accessed. Other domains call this 'debug access'. When using DML this is automatically handled for registers where the read_access and write_access methods have not been overridden. If overridden, or access is handled at bank level or elsewhere, the model must add the corresponding logic to handle inquiry accesses.

6.2.5 Error Reporting

The model should handle errors in a forgiving way. Avoid crashing or triggering assertions; instead, log an error message and try to continue anyway.

There are three different kinds of errors that should be reported by a Simics device:

  1. Outside architecture error. A part of the device whose behavior is not specified in the hardware documentation has been accessed. For example, a reserved register has been written to. Use log spec_viol for this kind of error.

  2. Unimplemented error. A part of the device which has been left unimplemented in the model (abstracted away) was accessed. This suggests a bug in the model, or that the model is used with software it was not developed for. Use log unimpl for this kind of error.

    In some cases it is sufficient to give a warning for this kind of situation, for example if the model returns approximate or invented values.

  3. Internal error. The internal state of the model implementation has been corrupted beyond repair. Look for "this will never happen" comments and reconsider*…* Use log error for this kind of error.

Simics has extensive support for logging, allowing you to assign the output to different message categories, and different levels of verbosity. See the DML 1.4 Reference Manual and the API Reference Manual for details. Logging is mostly used during development and debugging of the model, but is also useful to aid inspection of the device state during actual simulation.

Always use detailed error messages. Often, the error messages are the only source of information when a bug is reported by another user. It is not certain that the execution can be repeated, or that the developer will have access to the same setup.

5 Programming with DML 7 DML Tips and Tricks