Coroutines are a language mechanism that allows suspension and resumption of a subroutine. Coroutines are a useful tool when writing tests for target software, since it allows test logic to be expressed in a linear manner, typically by waiting for the system to enter a particular state, inject a stimuli, awaiting a reaction and validate that reaction.
The recommended way to implement coroutines in the Python language is through
the async/await syntax, and the asyncio library.
This document presents two libraries that together leverage asyncio-style
Python coroutines in Simics: The snoop library, which provides a unified
representation of simulation events, and the sloop library which allows
snoopers to be used within asyncio.
A snooper is a Python object responsible for monitoring a particular kind of simulation event. Snoopers offer a unified API for registering callbacks.
The primary operation of a snooper is its add_callback method, which
registers a callback on the snooped event. The callback will then be called
repeatedly until it is canceled, which is done by calling the cancel method
on the Handle object returned by add_callback. The add_callback method is
typically not called directly; for most use cases there are utilities
that manage things like cancellations automatically in a safe manner.
The sloop library is an integration of Python's asyncio library with the
Simics scheduler. The library provides means to create and run tasks based on
coroutine functions (declared in Python using the async def syntax), but
where the progression of an asyncio coroutine typically depends on
asynchronous operating system events on the host machine, the progression of a
sloop coroutine instead depends on simulation events. The API of the sloop
library generally imitates asyncio, but only supports a subset of all
functions available in asyncio. Most notably, sloop.create_task spawns a
task which can await simulation events, and sloop.run_until_complete runs
simulation until a sloop-based awaitable yields, similar to the run-until
commands in Simics CLI.
The sloop library also contains some primitives specific to Simics, in
particular:
wait(snp) is a coroutine that returns the next value yielded by a snooper
snp.timeout asynchronous context manager is similar to its asyncio
counterpart, but the timeout is specified by a snooper rather than a number
of host seconds. For instance, async with timeout(snoop.Seconds(conf.cpu0, 1)): gives a timeout if the block is not exited within one simulated second.Tracer asynchronous context manager and the trace coroutine are used
to collect a sequence of values from a snooper into an asynchronous iterator
and list, respectively.Script branches serve a similar purpose as sloop coroutines; just like
coroutines, script branches are Python subroutines that run piecewise in Global
Context to provide an asynchronous behaviour. Thanks to this similarity, the
script_branch module provides a two-way integration between script-branches
and sloop coroutines; this permits gradual migration of script-branches to
sloop coroutines:
A snooper script_branch.ScriptBranch(f, *args, **kwargs) starts a script
branch that repeatedly calls f(*args, **kwargs), where f is a script
branch function, and yields each return value. This is typically useful to
call a sb_wait_* call from a sloop coroutine; e.g., the expression await wait(ScriptBranch(script_branch.sb_wait_log(obj))) is roughly equivalent to
await wait(snoop.Log(obj)).
The function script_branch.sb_wait_for_sloop_awaitable(awaitable, loop=sloop.loop) can be used within a script branch to await a task or
coroutine. Similarly, sb_wait_for_snooper can be used to wait directly
for a snooper inside a script branch.
The breakpoint manager, represented by the bp configuration object, provides
a number of breakpoint types, which are similar in spirit to snoopers: Like a
snooper, a breakpoint type represents an observable simulation event, and
provides various options for awaiting or tracing this event. The bp-manager
module provides a generic wrapper that turns a breakpoint type into a snooper:
The Python class simmod.bp_manager.snooper.BP(obj, *args, **kwargs) is a
snooper based on the breakpoint type represented by the configuration object
obj, where args and kwargs are interpreted as CLI arguments to the
breakpoint type. For instance, the command bp.log.run-until substr = "a.*b" -regexp, which has a direct Python wrapping
conf.bp.log.cli_cmds.run_until(substr='a.*b', regexp=True), can be wrapped in
a snooper as simmod.bp_manager.snooper.BP(conf.bp.log, substr='a.*b', regexp=True).