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.
transactiontranslatortransaction_translatormemory_spaceio_memoryramromport_spaceFor 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.
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;
}
}
}
}
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;
}
}
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);
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";
}
}
}
}
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;
}
}