Libraries
The principal library is libTEMUSupport.so
. Normally, you never need
to directly link to any other library. Remaining libraries which
implement CPUs and models, are loaded either in the command line
interface by using the plugin-load
or its alias import
, or by
int temu_loadPlugin(const char *Path)
which is defined in
temu-c/Support/Objsys.h
.
To use the emulator as a library, simply link to libTEMUSupport.so and
initialise the library with temu_initSupportLib()
. The function will
among other things ensure that there is a valid license file for you
machine. In case there is no valid license file available, the
function will terminate your application.
#include "temu-c/Support/Init.h"
int
main(int argc, const char *argv[argc])
{
temu_initSupportLib(); // Initialise the TEMU library
return 0;
}
temu_initSupportLib() will terminate your application if
there is no valid license file on the system.
|
Deprecation Policy
TEMU versions are numbered as Major#.Minor#.Patch#. I.e. 2.0.1 is a bug fix for major version 2, minor version 0.
This policy is in effect starting with TEMU 2.0.0 (and applies to the C-API). The policy will not change unless the major version is incremented.
Patch version increments are for bugfixes and they will be ABI compatible with previous releases of the same major-minor release (you will not need to recompile your models for them to remain functioning).
Minor version increments will remain source level API compatible, but may deprecate functionality and APIs. Deprecated APIs will be marked as such with GCC / Clang deprecation attributes and noted as deprecated in the release notes. Recompilation of user defined models is recommended as ABI may break (e.g. extra functions at the end of interfaces). Minor versions typically add non-invasive features (more models, additional simple API functionality etc).
Major version increments will remove deprecated functions and APIs. Although, models written using the C-API should in general remain compatible, however, no 100-percent-guarantee is made for this. Major versions can add substantial new features.
Experimental Application Programming Interfaces
New API functionality is introduced at regular intervals to help the end user of the system. While simple APIs will be introduced directly, more complex functionality is likely to go through an experimental release cycle (sometimes more than one). For example, source debugging support is being worked on at the time of writing. This is expected to first appear in the command line interface, followed by exposing some functionality via APIs, when these APIs are public, they will be marked as experimental with comments in the headers. Experimental means that the API is subject to change in ways that may be source incompatible, even between patch releases (e.g. between 2.0.0 and 2.0.1).
This way, new APIs can be introduced for public review, and be adapted based on user input.
The Object System
TEMU provides a light weight object system that all built in models are written in. The object system exist to provide a C API in which it is possible to define classes and create objects that support reflection / introspection. Conceptually this is similar to GOBJECT, but the TEMU object system is more tailored for the needs of an emulator and a lot simpler. There is also some correspondence to SMP2, but the interfaces are plain C which is needed in order to interface to the object system from the emulator core.
The key features of the object system are the following:
-
Standardised way for defining classes and models in plain C.
-
Ability to introspect models, even though they are written in C or C++.
-
Automatic save and restore of state
-
Access to object properties by name using scripts
-
Standard way for defining interfaces (such as serial port interfaces etc)
-
Easy to wrap in order to be able to write models in other languages (e.g. Python)
The object system accomplishes this by providing the following:
- Class
-
Blueprint for objects, classes are created, registering properties and interfaces. It is also possible to define external classes, these are special classes which describe object created outside the emulator. Classes have names starting with a letter or underscore followed by any number of letters, digits and underscores.
- Object
-
An instantiated class. Normally the TEMU object system takes care of instantiation, however externally created objects can also be registered with the object system (in order to have scripts build the object graph with external classes). Objects have names with the same naming rules as classes, except objects support object name separators with the minus character '-'. This is used to ensure objects inside components have unique global names.
- Property
-
A named data member of a class (i.e. a field or instance variable). A property is accessible by name (e.g. using strings) and will be automatically serialised by the object system if needed. The system supports all basic fixed with integer types (from
<stdint.h>
), pointer sized integers (i.e.uintptr_t
andintptr_t
), floats, doubles and references to objects and interfaces. Property names start with a letter or underscore, followed by any number of digits, letters or underscores. Property names can be nested using a period '.' as separator. - Pseudo Property
-
A named data member of a class without directly backing storage. Pseudo properties are accessible by name, and will be automatically serialised by the object system if they have a set and get function associated. Pseudo properties are useful for custom check pointing logic (e.g. writing out raw data to a file) or for using external classes, or non standard layout types in the object system.
- Interface
-
A collection of function pointers allowing classes to provide different behaviour for a standardised interface. Similar to an interface in Java or an abstract class in C++. In TEMU this is implemented as structs of function pointers that are registered to a class.
- Port
-
A property and interface with an inverse relationship. Connecting an interface property in the port to an interface in another object will automatically introduce a back link from the destination object to the original source object. If a port has been added combining property a and interface b in the source object, and property c and interface d in the destination object. Connecting a→d will introduce c→b automatically.
When setting up a simulator based on TEMU, the general approach is the following:
-
Create all the needed classes (e.g. load plugins)
-
Create all objects for the system (e.g. CPUs, ROM, RAM, MMIO models etc)
-
Connect objects (build the object graph)
-
Load target software in to RAM or ROM
-
Run the emulator
It is possible to query a class or object for properties and interfaces at runtime by specifying the property or interface name as a string.
For example there is a CPU interface that is common to all CPU models, this contain procedures for accessing registers. In addition, there is a SPARC interface which provides SPARC specific procedures (e.g. accessing windowed registers).
The most important core interfaces are the following:
-
MemAccessIface
-
MemoryIface
-
CpuIface
An interface can be queried using the temu_getInterface function. This function takes an object pointer as first argument and the interface name as second. For example, temu_getInterface(cpu, "MemAccessIface") will return the pointer to the memory access interface structure provided by the CPU object (or NULL if not available). You need to cast the interface pointer to the correct type. The type mappings are provided in the model manuals.
Object Graph and Interface Properties
The objects created in the object system are connected together by linking interface properties to actual interfaces. That is if an object A has an interface property, this interface property can refer to an interface implemented by some other object B. Under the hood this is a pointer pair with an object pointer and an interface pointer, the interface pointer is a pointer to the struct of function pointers implementing the relevant interface.
Object System Functions
This section lists the most important object system functions. The full documentation is in Doxygen based documentation, this is just a quick way to have an overview.
Function | Description |
---|---|
temu_addInterface() |
Add interface to class |
temu_addObject() |
Register an externally created object |
temu_addPort() |
Bind a property / interface pair as a port |
temu_addProperty() |
Add property to class |
temu_addPseudoProperty() |
Add pseudo property to class |
temu_checkSanity() |
Look for unconnected interface properties |
temu_classForName() |
Get a class object by name |
temu_classForObject() |
Get the class object for an object |
temu_connect() |
Connect an interface property to an interface |
temu_createObject() |
Create a new object from an internal class |
temu_deserialiseJSON() |
Restore the state of the emulator |
temu_disposeObject() |
Delete object |
temu_getInterface() |
Get interface pointer by name |
temu_getValue() |
Get property without side-effects |
temu_loadPlugin() |
Load a TEMU plugin |
temu_nameForObject() |
Get the name for the given object |
temu_objectForName() |
Get a named object |
temu_objsysClear() |
Delete all objects and classes |
temu_readValue() |
Get property by calling the read function |
temu_registerClass() |
Create a new class |
temu_registerExternalClass() |
Create a new external class |
temu_serialiseJSON() |
Save the state of the emulator |
temu_setTimeSource() |
Set time source for object |
temu_setValue() |
Set property without side-effects |
temu_writeValue() |
Set property by calling the write function |
Properties
Properties are registered fields in a class. They are associated with the type and not with the object instance themselves. Properties have names, types and read- and write functions. Properties are checkpointed. Properties are associated with an offset in the model type, meaning they have backing storage.
Property names are legal if they start with a letter or underscore followed by any number of letters, digits or underscores. Properties support nesting via dots as well.
Pseudo Properties
Pseudo properties are properties without explicit backing storage, instead they are registered with not only the read and write functions, but also optional set and get functions. Set and get functions serves the same effect as accessing the raw data in a struct, and are used for check-pointing. From the user interface point of view, pseudo properties behave the same as normal properties.
Interfaces
Interfaces are structs populated with function pointers. You can query
an interface by name for a given object using temu_getInterface()
.
Interface names are legal if they start with a letter or underscore followed by any number of letters, digits or underscores.
Object Interface
The object interface provides a way to add support functions for the object system, for example custom serialise and deserialise functions, and custom sanity checkers for a class.
typedef struct {
void (*serialise)(void *Obj, const char *BaseName, void *Ctxt);
void (*deserialise)(void *Obj, const char *BaseName, void *Ctxt);
int (*checkSanity)(void *Obj, int Report);
void (*timeSourceSet)(void *Obj);
} temu_ObjectIface;
Memory Access Interface
The memory access interface defines the interface used by objects connected to the emulated memory system. The memory accesses are invoked by a CPU and can be either fetch, read or write operations.
typedef struct temu_MemTransaction {
uint64_t Va; //!< Virtual address
uint64_t Pa; //!< Physical address
uint64_t Value; //!< Resulting value (or written value)
//! 2-log of the transaction size.
uint8_t Size;
uint64_t Offset; //!< Offset from model base address
void *Initiator; //!< Initiator of the transaction
void *Page; //!< Page pointer (for caching)
uint64_t Cycles; //!< Cycle cost for memory access
} temu_MemTransaction;
// Exposed to the emulator core by a memory object.
typedef struct temu_MemAccessIface {
void (*fetch)(void *Obj, temu_MemTransaction *Mt);
void (*read)(void *Obj, temu_MemTransaction *Mt);
void (*write)(void *Obj, temu_MemTransaction *Mt);
} temu_MemAccessIface;
Memory Interface
The memory interface is a common interface for memory storage devices. It provides procedures for writing and reading larger blocks of memory. The interface takes an offset from the base address of the object is mapped (normally you use a memory space object to cover the physical address space).
The Size parameter is in bytes, and the Swap parameter specify the log-size in bytes of the data units to read or write. Note that the address/offset is assumed to be aligned at the unit size and the size will be truncated if it does not represent a whole number of data units.
I.e. when reading 64 bit words, the size should be 8, 16, 24… and the swap argument should be set to 3.
typedef struct temu_MemoryIface {
void (*readBytes)(void *Obj,
void *Dest, uint64_t Offs, uint32_t Size,
int Swap);
void (*writeBytes)(void *Obj,
uint64_t Offs, uint32_t Size, void *Src,
int Swap);
} temu_MemoryIface;
CPU Interface
The CPU interface provides a way to run processor cores and to access CPU state such as registers and the program counter.
typedef struct temu_CpuIface {
void (*reset)(void *Cpu, int ResetType);
uint64_t (*run)(void *Cpu, uint64_t Cycles);
uint64_t (*step)(void *Cpu, uint64_t Steps);
void __attribute__((noreturn))
(*raiseTrap)(void *Obj, int Trap);
void (*enterIdleMode)(void *Obj);
void __attribute__((noreturn))
(*exitEmuCore)(void *Cpu, temu_CpuExitReason Reason);
uint64_t (*getFreq)(void *Cpu);
temu_CpuState (*getState)(void *Cpu);
void (*setPc)(void *Cpu,
uint64_t Pc);
uint64_t (*getPc)(void *Cpu);
void (*setGpr)(void *Cpu,
int Reg,
uint64_t Value);
uint64_t (*getGpr)(void *Cpu,
unsigned Reg);
void (*setFpr32)(void *Cpu,
unsigned Reg,
uint32_t Value);
uint32_t (*getFpr32)(void *Cpu,
unsigned Reg);
void (*setFpr64)(void *Cpu,
unsigned Reg,
uint64_t Value);
uint64_t (*getFpr64)(void *Cpu,
unsigned Reg);
uint64_t (*getSpr)(void *Cpu,
unsigned Reg);
int (*getRegId)(void *Cpu,
const char *RegName);
uint32_t (*assemble)(void *Cpu,
const char *AsmStr);
const char* (*disassemble)(void *Cpu,
uint32_t Instr);
void (*enableTraps)(void *Cpu);
void (*disableTraps)(void *Cpu);
void (*invalidateAtc)(void *Obj,
uint64_t Addr,
uint64_t Pages,
uint32_t Flags);
} temu_CpuIface;
Ports
A very common case is where a source model is connected to a
destination model, and the destination model must have a back link to
the source model often to a different interface. For example, the IRQ
interfaces have an upstream variant IrqIface
and a downstream
variant IrqClientIface
. The upstream variant is used to raise
interrupts, while the downstream interface have functions to
acknowledge interrupts. To avoid the case where the user forgets to
insert the backlink, it is possible to pair interface properties and
interfaces together using temu_addPort()
.
When a port has been added to a class, the connect function will automatically insert the back links if connecting a port in a source object a port in the destination object.
In the interrupt controller case, assume that the class A has an
interface reference property irqController
and an interface
IrqIface
and class B an interface reference property named irq
and
an interface IrqClientIface
. Then, the user would do the following:
temu_addPort(A, "irqController", "IrqIface", "downstream IRQ port");
temu_addPort(B, "irq", "IrqClientIface", "upstream IRQ port");
// Now normally without ports two connects would be needed
//temu_connect(a, "irqController", b, "IrqClientIface");
//temu_connect(b, "irq", a, "IrqIface");
// With ports, only one connect is needed, it will automatically
// add the reverse link. so, we get both of the links on one
// connect:
// a.irqConroller -> b:IrqClientIface
// b.irq -> a:IrqIface
temu_connect(a, "irqController", b, "IrqClientIface");
To list available ports in a class use the class-info
command.
Event API
The event API is used to provides a common interface for pushing timed events on the event providers. The API is defined in "temu-c/Support/Events.h". The API provides functions for posting events on CPU objects, and provides the ability to post in three different time bases (cycles, nanoseconds and seconds), and the ability to decide if events are synchronised on a single CPU or the parent machine object.
When posting events to a CPU, nano-second events are converted to cycles. This means that you will actually not have NS accuracy for the events. The accuracy is a function of the clock frequency. I.e. for a 100 MHz CPU, the accuracy is 10 ns, while a 50 MHz CPU has an event posting accuracy of 20 ns.
When posting machine synchronised events, the delta time must be at least in the next time quanta. If not, the event will be slipped to the start of the next quanta (and a warning will be printed to the log).
In the case synchronised events are used, the machine scheduler will adjust its quanta length to ensure that CPUs do not execute longer than needed. Note that synchronised events are executed after a CPU has returned to the machine object, potentially executing non-synchronised events before the machine event, even if the strictly speaking have a trigger time after.
In addition to the different types of timed events, it is possible to stack post an event, in which case it will be executed after the current instruction finishes (in case of CPU synchronised events), or after the current time quanta finishes in case of machine synchronised events.
Events are prioritised as follows:
-
CPU synchronised stacked events (in LIFO order).
-
CPU synchronised timed events
-
Machine synchronised stacked events
-
Machine synchronised timed events
That means that machine synchronised events will not be executed until all the stacked events and the normal timer events have been executed.
int64_t temu_eventPublishStruct(const char *EvName,
temu_Event *Ev,
void *Obj,
void (*Func)(temu_Event*));
int64_t temu_eventPublish(const char *EvName, void *Obj,
void (*Func)(temu_Event*));
typedef enum {
teSE_Cpu, // Trigger event when CPU reaches timer
teSE_Machine, // Synchronise event on machine
} temu_SyncEvent;
void temu_eventPostCycles(void *Q, int64_t EvID, int64_t Delta,
temu_SyncEvent Sync);
void temu_eventPostNanos(void *Q, int64_t EvID, int64_t Delta,
temu_SyncEvent Sync);
void temu_eventPostSecs(void *Q, int64_t EvID, double Delta,
temu_SyncEvent Sync);
void temu_eventPostStack(void *Q, int64_t EvID,
temu_SyncEvent Sync);
Events From Other Threads
Note that it is in general not possible to post events from an other thread than the one controlling the execution of the emulator. To solve this issue, the function temu_postCallback() is available. This function is thread safe, and posts an event to be executed alongside the rest of the emulator event queue executions.
The event will be called when the emulator thinks it is safe while it is running, which is when the CPU event timer expires (or when the machine quanta expires by other means). Note that a lone CPU will post a null-event to ensure that the event handlers are triggered at regular intervals and not just when model events are executed. For machines, each CPU runs at most a quanta of time, so the check can be done at quanta expiration.
A possible way to use this capability is when you integrate external hardware / models into your simulator. For example, a separate thread can run and wait on a file-descriptor or socket, when data is available, it reads out the data and posts a thread-safe callback function to be called by the main thread at a safe time. The callback can then take the data that was read from the file-descriptor and inject this over a virtual bus model or write it to emulated memory.
Note that in TEMU 2.2, specific APIs for doing file descriptor and timer monitoring has been added. These are available as the temu_async*() functions in the temu-c/Support/Events.h header.
Notifications
A special type of event is an event that does not have a triggering time. They are instead triggered due to some action or event detected due to other logic inside the emulator. These events are posted using a pub-sub mechanism, with strings as the event key. A model or any other user code can listen for an event which is requested with a specific name. Note that when an event is published, it is assigned an integer ID which is used for fast notifications.
The functions for posting and listening to notifications include:
-
temu_publishNotification()
-
temu_subscribeNotification()
-
temu_unsubscribeNotification()
-
temu_notify()
-
temu_notifyFast()
In order to avoid event namespace collisions, all events issued in TEMU are prefixed with "temu.". It is recommended that user published events use their own namespace.
The notification ID 0 is a special notification that is used for indicating "no event", this way it is easy to disable event emission and have the no-event base case be processed cheaply with a single compare and no function call needed.
The temu_notifyFast is an inline function that allows the compiler to get rid of the function call in case the notification id is the null notification.
Current events include (but this is by no means an exhaustive list):
- temu.cpuErrorMode
-
CPU entered error mode (halted)
- temu.cpuTrapEntry
-
CPU trap taken
- temu.cpuTrapExit
-
CPU trap handler returned (for targets supporting this, e.g. rett instruction on SPARC)
- temu.breakpoint
-
Breakpoint hit
- temu.watchpointRead
-
Watchpoint read access
- temu.watchpointWrite
-
Watchpoint write access
- temu.mil1553Send
-
MIL1553 message sent, reported by bus object
- temu.mil1553Stat
-
MIL1553 status report, reported by bus object