1 Introduction Sloop module reference
Python Coroutines in Simics  / 

Snoop module reference

This section contains reference documentation for the snoop Python module.

Snooper

class Snooper(abc.ABC, typing.Generic[T])

Abstract class for snoops. A snoop observes a class of simulation events and yields values when they happen.

Snooper.add_callback

@abc.abstractmethod
def add_callback(self, yield_value: Callable[[T], None],
                 yield_exception: Callable[[Exception], None]) -> None

Make sure that functions yield_value and yield_exc are called when the snooper yields a value or an exception, respectively. Return Handle object for disposal. Neither yield_value nor yield_exc may be called after this handle has been cancelled.

The add_callback method typically uses some simulation primitive to register a low-level callback on relevant simulation events. The registered callback is typically not the yield_value argument, but a wrapper function with whatever signature the simulation primitive requires. This wrapper studies its arguments and calls fun zero or one time, and then returns. If an exception can happen when studying arguments, the function body may that and call yield_exc with the exception object as argument. This way it will be passed to typically useful when the snooper's constructor arguments cannot be validated until the callback happens.

If one callback from the simulation primitive may trigger multiple calls to yield_value or yield_exc, then the callback must make sure to check between calls that the handle has not been cancelled.

Neither yield_value nor yield_exc are required to be thread safe, so add_callback must ensure that neither is invoked concurrently from different threads. However, both functions may be reentrantly called if they trigger simulation side-effects.

Snooper.exec_context

@abc.abstractmethod
def exec_context(self) -> ExecContext

Specifies in what execution contexts the callback registered with add_callback() can be called. The execution context also covers entering/exiting the callback's associated exception bridge. The return value is one of CellContext(obj), GlobalContext() and ThreadedContext(). The execution context applies only to callbacks themselves; callback registration and cancellation may only be called in Global Context.

Handle

class Handle(abc.ABC)

Handle returned by Future.Callback.add_callback

Handle.cancel

@abc.abstractmethod
def cancel(self) -> None

Stop receiving callbacks. Idempotent.

ExecContext

@dataclass
class ExecContext(abc.ABC)

Base class for execution context specifiers. Should not be subclassed by users, mainly exposed for typing annotations

GlobalContext

class GlobalContext(ExecContext)

Callback can only be called in Global Context.

CellContext

@dataclass
class CellContext(ExecContext)

Callback can be called in Cell Context within the cell given by the cell member, or in Global Context. If cell is None, can be called from Cell Context in any cell or from Global Context.

CellContext.__init__

def __init__(self, obj: Optional[simics.conf_object_t])

The obj argument can be any object or None; if it is an object then the cell attribute is set to the object's associated cell, or None if the object is not associated to a cell.

ThreadedContext

class ThreadedContext(ExecContext)

Callback can be called in any context, Threaded, Global or Cell.

object_handle

def object_handle(obj, cancel)

Return a Handle object that calls cancel() when cancelled, and which self-cancels when obj is deleted.

Error

class Error(Exception)

Base class for errors emitted by the snoop library

InvalidObjError

class InvalidObjError(Error)

Raised by snooper constructors if an object passed as argument to the constructor does not fulfil the requirements of this snooper

add_callback_simple

def add_callback_simple(
        snooper: Snooper[T], yield_value: Callable[[T], None],
        yield_exc: Optional[Callable[[Exception], None]]=None, *,
        once: bool=False)

Wrapper around Snooper.add_callback:

catch_exceptions

def catch_exceptions(exc_handler: Callable[[Exception], None]) -> Callable[
        [Callable[..., T]], Callable[..., T]]

Decorator to capture exceptions raised by a function by calling the function exc_handler. Typically useful for low-level callbacks created by implementations of Snooper.add_callback, if the callback can raise an exception. In this case, the following makes sure the exception is propagated:

@catch_exceptions(yield_exception) def callback(...): ...

Standard snooper classes

Hap

class Hap(Snooper)

Yield a value when a hap occurs on a given object. The value is a tuple of the hap arguments, excluding the object.

Hap.__init__

def __init__(self, obj: simics.conf_object_t, name: str,
             exec_context: ExecContext=None)

The obj argument denotes the object on which we listen to a hap. name denotes the name of the hap. The exec_context argument declares the execution context of the notifier. The default CellContext(obj) is correct for most notifier types, but some haps only happen in Global Context; passing GlobalContext() for such notifiers may have advantages.

Log

class Log(Hap)

Yield a value when a message is logged. The value has two members: the log type kind, and the message message, both strings.

Log.__init__

def __init__(self, obj)

obj is the object from which we listen to all logged messages.

Notifier

class Notifier(Snooper[None])

Yield the value None when a notifier is notified on an object.

Notifier.__init__

def __init__(self, obj: simics.conf_object_t,
             notifier_type: typing.Union[int, str],
             exec_context: ExecContext=None)

notifier_type specifies the notifier type, as either a string or an integer. The exec_context argument declares the execution context of the notifier. The default CellContext(obj) is correct for most notifier types, but some notifiers only happen in Global Context; passing GlobalContext() for such notifiers may have advantages.

DeviceAttribute

class DeviceAttribute(Poll[int])

Yield the value of an attribute in a DML device when it changes

DeviceAttribute.__init__

def __init__(self, obj: simics.conf_object_t, attr: str)

Listen to changes to the attr attribute in obj. The obj object must belong to a DML device; it may either be the device object itself, or one of its ports, banks or subdevices.

RegisterValue

class RegisterValue(Poll[int])

Yield the value of a register or field of a C++ or DML bank when it changes. Depends on the bank-register-value-change or state-change notifier, together with register metadata from the register_view interface.

RegisterValue.__init__

def __init__(self, reg: dev_util.BankRegister, equal: Optional[int]=None)

Listen to changes to the register defined by reg, as returned by the dev_util.bank_regs function.

If equal is set, then instead of yielding register values, yield True when the register changes to the given value, and False when it changes from the given value to something else.

Seconds

@dataclass
class Seconds(Time)

Yield None when the given number of simulated seconds have passed. Seconds form a vector space over numbers: Second objects can be added or subtracted, and multiplied or divided by numbers.

Seconds.__init__

def __init__(self, clk: simics.conf_object_t, seconds: float=1)

None is yielded once every seconds cycles on the clock clk.

Cycles

@dataclass
class Cycles(Time)

Yield None when the given number of simulated cycles have passed. Cycles form an abelian group: Cycles objects on the same clock can be added or subtracted from each other, and multiplied by integers.

Cycles.__init__

def __init__(self, clk: simics.conf_object_t, cycles: int)

None is yielded once every cycles cycles on the clock clk.

Steps

@dataclass
class Steps(Time)

Yield None when the given number of simulated steps have passed. Steps form an abelian group: Steps objects on the same clock can be added or subtracted from each other, and multiplied by integers.

Steps.__init__

def __init__(self, cpu: simics.conf_object_t, steps: int)

None is yielded once every steps steps on the CPU cpu, which must implement the step interface.

ConsoleString

@dataclass
class ConsoleString(Snooper[None])

Yield None when a matching string appears on a console.

MemoryRead

class MemoryRead(_MemoryAccess)

Yield a value when memory is read from a given location. The value describes the matching transaction and has three members: initiator: Optional(conf_object_t), address: int and size: int.

MemoryRead.__init__

def __init__(self, obj: simics.conf_object_t, address: int, length: int)

Capture all read transactions on memory-space obj, that overlap with the interval defined by address and length.

MemoryWrite

class MemoryWrite(_MemoryAccess)

Yield a value when memory is written from a given location. The value describes the matching transaction and has three members: initiator: Optional(conf_object_t), address: int and value: bytes.

MemoryWrite.__init__

def __init__(self, obj: simics.conf_object_t, address: int, length: int)

Capture all write transactions on memory-space obj, that overlap with the interval defined by address and length.

MemoryExecute

class MemoryExecute(_MemoryAccess)

Yield a value when an instruction is fetched from a given location. The value describes the matching transaction and has three members: initiator: Optional(conf_object_t), address: int and size: int.

MemoryExecute.__init__

def __init__(self, obj: simics.conf_object_t, address: int, length: int)

Capture all instruction fetch transactions on memory-space obj, that overlap with the interval defined by address and length.

Filter

class Filter(Snooper[T])

Wraps a snooper and selectively yields the values yielded by the wrapped snooper.

Filter.__init__

def __init__(self, predicate: Callable[[T], bool], snooper: Snooper[T])

Whenever the wrapped snooper yields, the function predicate is called on the result, and the value is yielded only if the predicate returns True.

Poll

@dataclass
class Poll(Snooper[T])

Abstract snooper that yields the value returned by the method poll, called whenever a specified subordinate snooper yields a value, but only when the value returned from poll differs from the last returned value. The initial value is read on add_callback. Exceptions from poll and from the subordinate snooper are propagated.

The Poll class has an abstract method poll, with no arguments, returning the current value of the polled state. The __init__ method of the class accepts a single argument, the subordinate snooper that controls when to poll.

This snooper has two use cases, one good and one bad: The good use case is when an object fires a custom notifier, say 'my-notifier', whenever a particular attribute, say attr, changes, and one wants to subscribe to that state. This can be expressed as:

class MySnooper(Poll):
    def __init__(self, obj: simics.conf_object_t)
        self._obj = obj
        super().__init__(Notifier(obj, 'my-notifier'))
    def poll(self):
        return self._obj.attr

The bad use case is to use a Seconds snooper as the subordinate snooper, to periodically poll for state changes. This can work as a fallback for snooping an attribute that does not provide an explicit callback mechanism, but this technique has two problems:

For these reasons, Seconds-based polling should only be used as a temporary measure until the object has been extended with a custom notifier type.

CompoundSnooper

class CompoundSnooper(Snooper)

Abstract class for a snooper composed of multiple snoopers. Implements exec_context() and validates that constituent snoopers cannot issue callbacks concurrently.

The add_callback method is kept abstract.

The snoopers member is set to the constructor argument, a list of snoopers.

Latest

class Latest(CompoundSnooper)

Given a set of named snoopers, keeps track of the latest produced value from each one. Whenever one of the snoopers yields a value, this snooper yields an object composed of the latest values from all snoopers, accessed as object members. For instance, Latest(x=X(), Y=Y()) would yield values v such that v.x and v.y contain the latest value of X() and Y(), respectively.

Latest.__init__

def __init__(self, **snoopers)

The keyword arguments specify the snoopers to combine.

The produced tuple will contain None for snoopers that have not yet produced a value.

Any

class Any(CompoundSnooper)

Deliver a callback when any of the given callbacks are delivered. The delivered value is a pair, (triggered Snooper object, value from subordinate snooper). Exceptions are propagated unchanged. The constructor is inherited from CompoundSnooper.

AnyObject

class AnyObject(Any, Snooper[tuple[simics.conf_object_t, T]])

Applies a snooper recursively to a set of objects. The set of objects is either specified as a list in the objs argument, or as a full subtree using the root argument. make_snooper is a function that produces a Snooper from an object, e.g. lambda obj: Notifier(obj, 'my-notifier'). If root is provided, then the make_snooper function may raise InvalidObjError to signal that an object should be excluded; this is done automatically for standard snooper constructors. If objs is provided, all objects must be valid and any InvalidObjError exception is propagated

The value passed to the callback is a pair: the first element is the object passed to make_snooper, the second element is the value produced by the snooper returned by make_snooper.

1 Introduction Sloop module reference