This section contains reference documentation for the
snoop Python module.
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.
@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.
@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.
class Handle(abc.ABC)
Handle returned by Future.Callback.add_callback
@abc.abstractmethod
def cancel(self) -> None
Stop receiving callbacks. Idempotent.
@dataclass
class ExecContext(abc.ABC)
Base class for execution context specifiers. Should not be subclassed by users, mainly exposed for typing annotations
class GlobalContext(ExecContext)
Callback can only be called in Global Context.
@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.
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.
class ThreadedContext(ExecContext)
Callback can be called in any context, Threaded, Global or Cell.
def object_handle(obj, cancel)
Return a Handle object that calls cancel() when cancelled,
and which self-cancels when obj is deleted.
class Error(Exception)
Base class for errors emitted by the snoop library
class InvalidObjError(Error)
Raised by snooper constructors if an object passed as argument to the constructor does not fulfil the requirements of this snooper
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:
The yield_exc argument is optional; leaving it out makes
sense in the common case where the snooper is known to never yield
exceptions. If an exception happens without explicit
yield_exc, then a critical error is signalled.
If the once flag is True, then the callback will be
automatically cancelled after the first yield_value call.
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(...): ...
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.
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.
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.
def __init__(self, obj)
obj is the object from which we listen to all logged messages.
class Notifier(Snooper[None])
Yield the value None when a notifier is notified on an object.
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.
class DeviceAttribute(Poll[int])
Yield the value of an attribute in a DML device when it changes
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.
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.
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.
@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.
def __init__(self, clk: simics.conf_object_t, seconds: float=1)
None is yielded once every seconds cycles on the clock clk.
@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.
def __init__(self, clk: simics.conf_object_t, cycles: int)
None is yielded once every cycles cycles on the clock clk.
@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.
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.
@dataclass
class ConsoleString(Snooper[None])
Yield None when a matching string appears on a console.
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.
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.
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.
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.
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.
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.
class Filter(Snooper[T])
Wraps a snooper and selectively yields the values yielded by the wrapped snooper.
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.
@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:
With a too long interval, it will take some time from the state
change until a value is yielded. The state might also change
twice between poll calls, causing a complete omission of
a yield; this may cause intermittent hard-to-debug bugs
if simulation is not deterministic.
With a too short interval, the polling may harm simulation speed.
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.
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.
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.
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.
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.
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.