Python Scripting

TEMU Python scripts run inside the emulator process. They can use the Python-friendly temu package for common scripting tasks and the lower-level temu.c package when direct access to the public C API is needed. Breakpoint scripts include temu.debugging automatically.

The python libraries are installed in share/temu/wrappers/Python/. The location is automatically detected by TEMU, so scripts normally only need to import the package they use.

In TEMU 5.1, the old ctypes based C API wrappers were replaced with new ones built using SWIG. The previous wrappers are still delivered and can be enabled in case of troubles.

Python scripts can both interact with TEMU objects and run TEMU commands, both global commands and object commands.

The following example illustrates how TEMU commands can be invoked.

import temu

cpu = temu.get_object("board0-cpu")
cpu.freq = 40_000_000
temu.run(cycles=100)

Running Scripts

Python scripts can be run from the TEMU command line:

script-run file=setup.py
script-run script='import temu; temu.run(cycles=100)'

They can also be run when starting TEMU:

temu --run-script setup.py board.temu

Several --run-* options may be passed. They are executed in command-line order, and TEMU stops at the first script error.

Importing TEMU

The installed wrapper directory is added to Python’s module search path automatically. The default wrapper tree is:

share/temu/wrappers/Python/

The temu package contains the Python-friendly layer. The temu.c package contains SWIG wrappers for the C API, grouped by header area. Function names in temu.c normally drop the leading temu_ prefix because the package already provides the namespace.

import temu
from temu.c.support.cpu import cpuGetReg, cpuSetReg

cpu = temu.get_object("board0-cpu")
cpuSetReg(cpu, 1, 0xdeadbeef)
assert cpuGetReg(cpu, 1) == 0xdeadbeef

Objects returned by temu.get_object() can be passed directly to C API functions that take TEMU object handles. The underlying SWIG handle is available as obj.handle. If a raw handle was returned by temu.c, wrap it with temu.Object(handle, name) to use the Python-friendly object layer.

Objects and Properties

temu.get_object(name) returns a temu.Object. It raises KeyError if no object with that name exists.

Object properties are exposed as Python attributes where the property name is a valid Python attribute name:

cpu = temu.get_object("board0-cpu")

print(cpu.freq)
cpu.freq = 25_000_000

Numeric properties are converted to normal Python numbers on read.

Properties with names that are not convenient as Python attributes, or properties hidden by a temu.Object method, can be reached through the mapping view:

cpu.prop["freq"] = 40_000_000
print(cpu.prop["freq"])

Indexed properties use a tuple key:

value = obj.prop["registers", 3]
obj.prop["registers", 3] = value + 1

Dotted property names are exposed as nested namespaces:

uart = temu.get_object("board0-apbuart")
uart.config.fifoSize = 8
assert uart.config.fifoSize == uart.prop["config.fifoSize"]

Completion works through dir(), both on objects and property namespaces. If a TEMU property or command has the same name as a temu.Object attribute such as name, handle, prop, cmd, or subscribe, the Python attribute wins. temu.get_object() emits temu.ShadowedNameWarning once per class for such collisions.

Use obj.prop["name"] or obj.cmd("name") to reach the hidden TEMU member.

Commands

Global TEMU commands are available as functions on the temu module. Object commands are available as methods on temu.Object.

temu.init_scheduler(variant="multi-level")

cpu = temu.get_object("board0-cpu")
sched = temu.get_object("sched")
sched.add_cpu(cpu=cpu)

temu.run(obj=cpu, cycles=100)
cpu.pregs()

Command names containing hyphens are written with underscores in Python:

# Calls the TEMU command named init-scheduler.
temu.init_scheduler(variant="multi-level")

Keyword argument names are translated the same way. If a command option is a Python keyword, add a trailing underscore:

temu.some_command(from_="input")

Typed command calls return the integer TEMU command status. A non-zero status raises temu.CommandError by default:

try:
    temu.run(cycles=-1)
except temu.CommandError as exc:
    print(exc.status)

Pass check=False to receive the status without raising. For commands whose name collides with a Python attribute, or when the command name is already stored in a variable, use temu.cmd() or obj.cmd():

temu.cmd("run", cycles=40)
cpu.cmd("pregs")

For a full TEMU command line string, use temu.command(). This is the escape hatch for command lines built elsewhere or command forms not represented by typed arguments. It returns the command status and does not raise CommandError.

status = temu.command("echo hello from Python")

Notifications

Python callables can subscribe to TEMU notifications. Use temu.subscribe() for all sources of a notification, or obj.subscribe() for notifications from one object.

cpu = temu.get_object("board0-cpu")

def on_trap(source, info):
    print(source.name, hex(info.PC))

sub = cpu.subscribe("temu.cpuTrapEntry", on_trap)

# Later:
sub.unsubscribe()

The returned temu.Subscription object is the subscription identity. It keeps the handler alive until unsubscribe() is called. Calling unsubscribe() twice raises KeyError.

The handler is called as handler(source, info). For global notifications without a source, source is None. Otherwise source is a temu.Object, not a raw object handle.

Known notification payloads are decoded automatically:

Notification Python info value

temu.cpuTrapEntry

temu_TrapEventInfo wrapper

temu.cpuTrapExit

temu_TrapEventInfo wrapper

temu.cpuErrorMode

temu_TrapEventInfo wrapper

temu.modeSwitch

temu_ModeSwitchInfo wrapper

temu.reset

temu_ResetInfo wrapper

temu.power

int

If a notification carries no payload, info is None. For unknown payload types, info is the raw address as an integer. Register a decoder when the payload type is known:

from temu.c.support.notifications import notInfoAs

temu.register_notification_info(
    "my.notification",
    lambda addr: notInfoAs("my_InfoStruct *", addr))

Handlers run on the thread that raises the notification. They may call back into TEMU through temu or temu.c, but they also delay the raising thread while they run.

If a handler raises an exception, TEMU prints it and continues; the exception is not propagated to the code that raised the notification.

Fixed-Width Integers

Python are arbitrary precision. For target arithmetic where C-style wraparound matters, use temu.fixed:

from temu import fixed

pc = fixed.u32(cpu.pc)
cpu.pc = pc + 4

assert int(fixed.i32(fixed.u32(0xffffffff))) == -1
assert int(fixed.i32(-7) // fixed.i32(2)) == -3

The available types are u8, u16, u32, u64, i8, i16, i32, and i64. They wrap on overflow, use C-style division and remainder, and can be written directly to numeric TEMU properties.

Reading a property returns a plain Python int. Wrap it explicitly when subsequent arithmetic should use target-width semantics.

Legacy Wrappers

Older Python scripts used wrappers implemented with ctypes. The new temu.c wrappers are source level compatible with the ctypes-wrappers. In case of issues, the old wrappers are still available.

Location of Legacy Wrappers
share/temu/wrappers/Python-ctypes/

Set the configuration key python/legacy-wrappers to true to select them:

python:
  legacy-wrappers: true

When this option is enabled, TEMU prints a warning because the ctypes wrappers are kept only as a compatibility fallback and will be removed in a future release.