Each object in the simulation has a thread domain associated with it. The same
thread domain can be associated with multiple objects.
The basic rule is that before the state of an object is accessed, the
corresponding thread domain needs to be held by the thread performing the
access. This ensures that an object is never accessed concurrently
by two different threads.
The thread domain containing the cell object, the cell thread domain
(cell TD), has some special properties. Sometimes it is referred to as just
the "cell" for brevity, as in "acquiring the cell", and "holding the cell",
which should be read as "acquiring the cell thread domain", and
"holding the cell thread domain" respectively.
The cell TD should be thought of as a single-threaded domain.
To a model in the cell TD, everything in the entire cell appears to be
simulated from a single thread, even if this is not the case.
Note:
In most cases, the appropriate thread domain is already held, and no
special action needs to be taken. This is for instance the case for normal
device models, which run in Cell Context, or for code running in
Global Context.
Only models which use custom threads or use the Threaded Device Model
need to acquire thread domains explicitly.
The relationship between the API execution context, defined in
section 1.3.2, and thread domains
is as follows:
- Global Context
- All thread domains (and by implication, all cells) in
the simulation are held by the thread.
The thread has exclusive access to all objects. CLI scripts,
Python scripts, CLI commands, script-branches, and
object initialization code, all run in Global Context.
- Cell Context
- The cell TD is held by the thread. The thread can freely
access all other objects in the cell. Normal device models (i.e. models
which are not thread-aware) can assume that they are invoked in this
context.
- Threaded Context
- No thread domains are required to be held, but thread-aware models
often hold their own thread domain.
Note:
The locking model in Simics is asymmetrical. Models running in
Cell Context can call interface functions on any object in the cell
without taking any special measures, and this includes interfaces on
objects belonging to a different TD. This is possible since thread-aware
models are required to guard model entry points by acquiring
their own domains. Conversely, thread-aware models are required to enter
Cell Context explicitly, by using a special API call, before
calling interface functions on objects in the cell TD.
A thread domain has the following basic properties:
- Exclusive - a thread domain can only be held by a single thread at
a time
- Recursive - a thread domain can be acquired multiple times
by the same thread
- Extendable - multiple thread domains can be held simultaneously
by a thread, and the thread may access any object whose thread domain is
held.
The following macros are used to acquire thread domains.
- SIM_ACQUIRE_OBJECT
- Acquires the thread domain associated with the object.
This function is intended to be used by thread-aware objects
to obtain its own thread domain before modifying internal state
protected by the domain.
This primitive does not enter Cell Context, even if the
cell TD is acquired. The reason is that the retention mechanism
is not activated (see below).
- SIM_ACQUIRE_TARGET
- Enters Cell Context if the specified object belongs
to the cell TD. As part of entering Cell Context,
the cell TD is acquired.
This primitive does nothing if the object does not belong to
the cell TD. In other words, it is a no-op if the specified
object is thread-aware.
Thread-aware code, which is not running in Cell Context, uses this
function before invoking an interface method on an external object.
- SIM_ACQUIRE_CELL
- Enters Cell Context unconditionally. The specified object
is associated with a cell whose TD is acquired as part of entering
Cell Context.
The function should be used before calling an API function, or callback,
requiring Cell Context.
Each primitive above
should be used together with the corresponding release function.
Macros are used in order to allow lock statistics to be collected
with information about where the lock was acquired. There are also
corresponding SIM-functions available.
Note:
If multiple thread domains are acquired, then they must be released
in strict reverse order. Failure to do so will result in a hard error
and a complaint about locks being released in an incorrect order.
Note:
The difference between
SIM_ACQUIRE_CELL and
SIM_ACQUIRE_TARGET is really that the former
always acquires the
cell thread domain and enters Cell Context, whereas the latter is a
no-op when a thread-aware object is specified.
The reason for the distinction is that thread-aware objects
are required to protect incoming as needed; this self-protection
usually involves a call to SIM_ACQUIRE_OBJECT, but
models are free to use alternate locking schemes.
Note:
This section describes how Simics handles thread domain contention,
and it is mostly provided to allow for a deeper understanding of the
Simics threading model.
When a thread tries to acquire a thread domain which is already held or
requested by another thread, then the following happens:
- The thread is assigned a priority, using the table below.
- All domains held by the thread are released and marked as contended
- The current holder of the requested domain is notified that a thread
is waiting for the domain, and the domain is marked as contended.
- The thread is blocked until all needed domains are available and can
be assigned to the thread. Among all threads waiting for a domain,
the domain can only be assigned to the thread with the highest priority.
The priority is assigned as follows:
-
Priority | Name | Situation |
1 | Execute |
TD acquired for instruction execution (lowest priority)
|
2 | Yield |
domains reacquired after explicit yield
|
3 | Entry |
TD acquired, no other domains held
|
4 | Entry 2 |
TD acquired, other TDs already held
|
5 | Cell Entry |
cell acquired with
SIM_ACQUIRE_CELL/TARGET
|
6 | Elevated |
TD acquired in Cell Context
|
7 | Message |
TD acquired for delivery of direct memory message
|
In the table above, TD stands for a thread domain which is not the cell TD.
A contended thread domain is always assigned to the waiting thread with
the highest priority. The domain is never released to a thread with lower
priority, even if the domain is unused and the highest priority thread is
waiting upon some other domain.
The priority scheme serves two purposes:
- It ensure that a deadlock situation cannot occur.
- It ensures that a thread in Cell Context is not preempted
by other threads when there is lock contention.
Note:
For performance reasons, a thread waiting for a thread domain will typically
spin for a certain amount of time before falling back to sleeping on some
condition variable.
In Cell Context, a special mechanism is used when
additionally acquired thread domains are released:
- Domain retention mechanism
- The release of additionally acquired domains is deferred until
Cell Context is exited, or in other words, until the cell TD
is released.
As an example, consider a thread doing the following, with CPU1 belonging
to thread domain TD_cpu1, CPU2 to TD_cpu2, and device DEV to TD_cell,
respectively:
- CPU1 is simulated while holding TD_cpu1
- EC is entered before the model calls an interface on DEV.
TD_cell is acquired when EC is entered.
- device DEV queries CPU2 for its cycle count. The TD_cpu2 domain is
acquired and released during this operation, but the actual release
of TD_cpu2 is deferred by the retention mechanism
- device DEV posts an event on CPU2, again taking and releasing TD_cpu2
- TD_cell is released when the DEV interface call returns, and
the thread leaves Cell Context. The retention mechanism causes TD_cpu2
to be released for real at this point
The retention mechanism ensures that TD_cpu2, in the example above, is
held until the device access is complete. Between point 3 and point 5,
CPU2 will not be simulated, and its state will be stable.
Note:
The retention mechanism ensures that a device model sees
a stable state for all objects it interacts with. The mechanism allows
CPUs to run concurrently with device models, but when a
device model interacts with a CPU, it is stopped until the device
operation is complete.