11 Defining New Interface Types 13 Using Python in a Simics module
Model Builder User's Guide  /  II Device Modeling  / 

12 Memory Transactions in DML

12.1 Introduction

This chapter focuses on devices receiving or issuing transactions. Devices where transactions pass through, i.e. interconnects, are covered in chapter Modeling Interconnects.

The API fundamentals for issuing memory transactions in Simics are the functions SIM_new_map_target and SIM_issue_transaction. The details for these functions and the complete API is covered in the Transactions API chapter.

Standard interfaces in Simics i.e.: signal, i2c and serial_peripheral are directly called by the initiator. For memory transaction the recommended approach is to convert the receiver object into a map_target_t through method SIM_new_map_target and then call function SIM_issue_transaction on the map_target_t and not interact directly with the memory interface. One advantage with this approach is that receivers can implement any of the below Simics memory interfaces and the initiator does not have to know or probe which interface to use. It also makes the initiator device more generic and can be integrated into multiple platforms where the receivers implement different memory interfaces.

  1. transaction
  2. translator
  3. transaction_translator
  4. memory_space
  5. io_memory
  6. ram
  7. rom
  8. port_space

For a device to be the end receiver of transactions the device should implement the transaction interface. For a device to receive and then forward the transaction somewhere else should implement either the translator or the transaction_translator interfaces. See chapter Transactions API for details.

12.2 Issuing Transactions

The example DMA device below showcases how to issue read and write transactions from DML.


dml 1.4;

device simple_dma_doc;
param classname = "simple-dma-doc";
param desc = "sample DMA device";

import "utility.dml";

method validate_map_target(conf_object_t *obj) -> (bool) {
    local map_target_t *tmp = SIM_new_map_target(obj, NULL, NULL);
    if (!tmp) {
        local exception_type_t _exc = SIM_clear_exception();
        SIM_attribute_error(SIM_last_error());
        return false;
    }
    SIM_free_map_target(tmp);
    return true;
}

connect memory {
    session map_target_t *map_target;
    method validate(conf_object_t *obj) -> (bool) {
        return validate_map_target(obj);
    }
    method set(conf_object_t *obj) {
        SIM_free_map_target(this.map_target);
        default(obj);
        this.map_target = obj ? SIM_new_map_target(obj, NULL, NULL) : NULL;
    }
}

method write_memory32(uint64 addr, uint32 value) throws {
    if (!memory.map_target)
        throw;
    local atom_t atoms[5] = {
        ATOM_data(cast(&value, uint8*)),
        ATOM_size(sizeof(value)),
        ATOM_flags(Sim_Transaction_Write),
        ATOM_initiator(dev.obj),
        ATOM_LIST_END
    };
    local transaction_t t;
    t.atoms = atoms;
    if (SIM_issue_transaction(memory.map_target, &t, addr) != Sim_PE_No_Exception)
        throw;
}

method read_memory32(uint64 addr) -> (uint32) throws {
    if (!memory.map_target)
        throw;
    local uint32 val;
    local atom_t atoms[4] = {
        ATOM_data(cast(&val, uint8*)),
        ATOM_size(sizeof(val)),
        ATOM_initiator(dev.obj),
        ATOM_LIST_END
    };
    local transaction_t t;
    t.atoms = atoms;
    if (SIM_issue_transaction(memory.map_target, &t, addr) != Sim_PE_No_Exception)
        throw;
    return val;
}

bank regs {
    register addr size 8 @ 0x0;
    register data size 4 @ 0x8 is (write, read) {
        method write(uint64 value) {
            try {
                write_memory32(addr.val, value);
            } catch {
                log error: "Failed to write to memory @ 0x%08x", addr.val;
            }
        }
        method read() -> (uint64) {
            try {
                return read_memory32(addr.val);
            } catch {
                log error: "Failed to read from memory @ 0x%08x", addr.val;
                return 0;
            }
        }
    }
}

Figure 4. Example DMA device
The DML utility template map_target would normally be applied to connect memory in the code above. It automatically allocates the map_target_t variable and defines helper methods for reading and writing. It was left out in this example to explicitly show the usage of the SIM_new_map_target API.

12.3 Receiving Transactions

The remote FIFO device below showcases how to receive and process transactions through the transaction interface. It returns proper error codes depending on error type. For accesses considered unmapped the device shall return Sim_PE_IO_Not_Taken and for accesses it has to abort because of internal state it shall return Sim_PE_IO_Error.


dml 1.4;

device remote_fifo_doc;
param classname = "remote-fifo-doc";

param desc = "sample FIFO device";

import "utility.dml";

param fifo_len = 100;
saved int fifo[fifo_len + 1];
saved int head_pos = 0;
saved int tail_pos = 0;

method push(uint32 v) throws {
    if (is_full())
        throw;

    fifo[head_pos] = v;
    head_pos = next_pos(head_pos);

}
method pop() -> (uint32) throws {
    if (is_empty())
        throw;

    local uint32 value = fifo[tail_pos];
    this.tail_pos = next_pos(tail_pos);
    return value;
}
method is_empty() -> (bool) {
    return tail_pos == head_pos;
}

method is_full() -> (bool) {
    return next_pos(head_pos) == tail_pos;
}

method next_pos(uint16 pos) -> (uint16) {
    return (pos + 1) % (fifo_len + 1);
}

implement transaction {
    method issue(transaction_t *t, uint64 addr) -> (exception_type_t) {
        local uint64 size = SIM_transaction_size(t);
        local conf_object_t *ini = SIM_transaction_initiator(t);

        if (addr != 0) {
            log spec_viol:
                "Remote FIFO only only accepts accesses @ 0x0, got: 0x%x", addr;
            /* Return IO not take for unmapped access */
            return Sim_PE_IO_Not_Taken;
        }
        if (SIM_transaction_size(t) != 4) {
            log spec_viol: "Remote FIFO only support 4-byte accesses";
            /* Return IO not take for unmapped access */
            return Sim_PE_IO_Not_Taken;
        }

        local uint32 v;
        if (SIM_transaction_is_write(t)) {
            local buffer_t bytes;
            bytes.data = cast(&v, uint8*);
            bytes.len = size;
            SIM_get_transaction_bytes(t, bytes);
            try {
                push(v);
            } catch {
                log spec_viol: "Fifo overflow";
                /* Return IO error for device internal error */
                return Sim_PE_IO_Error;
            }
        }
        if (SIM_transaction_is_read(t)) {
            try {
                v = pop();
            } catch {
                log spec_viol: "Fifo underflow";
                /* Return IO error for device internal error */
                return Sim_PE_IO_Error;
            }
            local bytes_t bytes;
            bytes.data = cast(&v, uint8*);
            bytes.len = size;
            SIM_set_transaction_bytes(t, bytes);
        }

        return Sim_PE_No_Exception;
    }
}

Figure 5. Example remote FIFO device
The above example could have utilized functions SIM_get_transaction_value_le and SIM_set_transaction_value_le to make the code more compact.

12.4 Defining Custom User Atoms

Users have the capability to define their own custom atoms in Simics. The usage could for instance be to transport metadata between the initiator and the receiver in either direction. See Custom Atom Types for details how to declare and register custom atoms. For devices written in DML the user also has to add a DML file to expose the atom types and access functions for DML. This file currently has to be handwritten by the user.

Expanding on the previous example by adding two new atoms: fifo_status and clear_fifo. The fifo_status atom is filled in by the receiver and represents the current length of the FIFO. The clear_fifo atom is set by the initiator when it wants the receiver to clear the FIFO.

The file fifo-atom.dml exposes the atoms to the DML devices. The .c and .h file are left out because how it would be done is already covered here.

dml 1.4;

header %{
#include "fifo-atom.h"
%}

extern typedef struct {
    int len;
} fifo_status_t;

// Create atom
extern atom_t ATOM_fifo_status(const fifo_status_t *msg);
// Get atom
extern const fifo_status_t* ATOM_get_transaction_fifo_status(const transaction_t *t);

// Create atom
extern atom_t ATOM_clear_fifo(bool clear);
// Get atom
extern const bool ATOM_get_transaction_clear_fifo(const transaction_t *t);

Figure 6. fifo-atom.dml

12.5 Issuing Transactions with Custom Atoms


import "utility.dml";
import "fifo-atom.dml";

connect fifo is map_target;

method write_to_fifo(uint32 value) -> (fifo_status_t) throws {
    if (!fifo.map_target)
        throw;
    local uint8 buf[4];
    local fifo_status_t status;
    local atom_t atoms[6] = {
        ATOM_fifo_status(&status),
        ATOM_data(buf),
        ATOM_size(sizeof(buf)),
        ATOM_flags(Sim_Transaction_Write),
        ATOM_initiator(dev.obj),
        ATOM_LIST_END
    };
    local transaction_t t;
    t.atoms = atoms;
    SIM_set_transaction_value_le(&t, value);
    if (SIM_issue_transaction(fifo.map_target, &t, 0) != Sim_PE_No_Exception)
        throw;

    return status;
}

method read_from_fifo() -> (uint32, fifo_status_t) throws {
    if (!fifo.map_target)
        throw;
    local uint8 val[4];
    local fifo_status_t status;
    local atom_t atoms[5] = {
        ATOM_fifo_status(&status),
        ATOM_data(val),
        ATOM_size(sizeof(val)),
        ATOM_initiator(dev.obj),
        ATOM_LIST_END,
    };
    local transaction_t t;
    t.atoms = atoms;
    if (SIM_issue_transaction(fifo.map_target, &t, 0) != Sim_PE_No_Exception)
        throw;
    return (SIM_get_transaction_value_le(&t), status);
}

method clear_fifo() throws {
    if (!fifo.map_target)
        throw;
    local atom_t atoms[3] = {
        ATOM_clear_fifo(true),
        ATOM_initiator(dev.obj),
        ATOM_LIST_END,
    };
    local transaction_t t;
    t.atoms = atoms;
    if (SIM_issue_transaction(fifo.map_target, &t, 0) != Sim_PE_No_Exception)
        throw;
}

bank regs {
    register data size 4 @ 0x0 is (write, read) {
        method write(uint64 value) {
            try {
                local fifo_status_t status = write_to_fifo(value);
                fifo_status.val = status.len;
            } catch {
                log error: "Failed to write to fifo";
            }
        }
        method read() -> (uint64) {
            try {
                local (int v, fifo_status_t status) = read_from_fifo();
                fifo_status.val = status.len;
                return v;
            } catch {
                log error: "Failed to read from fifo";
                return 0;
            }
        }
    }
    register fifo_status size 4 @ 0x4;
    register clear size 1 @ 0x8 is (write) {
        method write(uint64 value) {
            try {
                clear_fifo();
            } catch {
                log error: "Failed to clear fifo";
            }
        }
    }
}

Figure 7. Example device accessing remote FIFO with status and clear atom

12.6 Receiving Transactions with Custom Atoms

import "fifo-atom.dml";

implement transaction {
    method issue(transaction_t *t, uint64 addr) -> (exception_type_t) {
        local uint64 size = SIM_transaction_size(t);
        local conf_object_t *ini = SIM_transaction_initiator(t);

        if (addr != 0) {
            log spec_viol:
                "Remote FIFO only only accepts accesses @ 0x0, got: 0x%x", addr;
            /* Return IO not take for unmapped access */
            return Sim_PE_IO_Not_Taken;
        }
        if (size != 4) {
            log spec_viol: "Remote FIFO only support 4-byte accesses";
            /* Return IO not take for unmapped access */
            return Sim_PE_IO_Not_Taken;
        }

        /* If clear atom set, just clear fifo and return */
        if (ATOM_get_transaction_clear_fifo(t)) {
            clear();
            return Sim_PE_No_Exception;
        }

        local uint8 buf[size];
        local int v;
        if (SIM_transaction_is_write(t)) {
            local buffer_t bytes;
            bytes.data = cast(&v, uint8*);
            bytes.len = size;
            SIM_get_transaction_bytes(t, bytes);
            try {
                push(v);
            } catch {
                log spec_viol: "Fifo overflow";
                /* Return IO error for device internal error */
                return Sim_PE_IO_Error;
            }
        } else {  // Read
            try {
                v = pop();
            } catch {
                log spec_viol: "Fifo underflow";
                /* Return IO error for device internal error */
                return Sim_PE_IO_Error;
            }
            local bytes_t bytes;
            bytes.data = cast(&v, uint8*);
            bytes.len = size;
            SIM_set_transaction_bytes(t, bytes);
        }

        /* Check if fifo status atom is present and set it */
        local fifo_status_t *status = cast(ATOM_get_transaction_fifo_status(t), fifo_status_t *);
        if (status != NULL)
            status->len = current_len();

        return Sim_PE_No_Exception;
    }
}

Figure 8. Example remote FIFO device with status and clear atom functionality
11 Defining New Interface Types 13 Using Python in a Simics module