An operation in OSAL is defined by its properties and its behavior. The properties defined by OSAL do not restrict in any way the hardware implementation. For example, latency, bit width or the fact that reading the result of an operation can lock the processor are properties of the implementation, and are not defined in OSAL module.
The following properties define a TTA operation in TCE:
Number of outputs of the operation. The number of outputs is a nonnegative integer. It must be positive if the number of inputs is zero.
Indicates that this operation can access memory. Normally, memory access is also implied from the properties `mem-address' and `mem-data' of operation inputs (or the `mem-data' property of operation outputs). However, it is possible to define operations that perform invisible accesses to memory, whereby no input or output is related to the memory access itself. That is, neither the address nor the data moved into or out of memory is explicitly specified by the operation. In these operations, memory accesses occur as a side effect, and none of the inputs or outputs have memory-related properties. To avoid potential errors, the memory property must be specified explicitly even when it is implied by some of the inputs or outputs of the operation. See sections on input and output declarations, below.
Indicates that two subsequent executions of this operation with the same input values may generate different output values. An operation marked with ``side-effect'' is an operation which writes some state data, affecting further executions of the same operation and other operations sharing that same state data. The other operations that read or write the same state data written by an operation marked with ``side-effect'' must be marked ``affected-by'' that operation (see later for an example).
Note: only operations that write the state should marked to have ``side-effects''. Operations that only read the state data do not have side effects and may be reordered more freely by the compiler.
In case an operation reads or writes state data written by another operation sharing the same state data (that is marked with the ``side-effect'' property), the operation should be marked ``affected-by'' that operation.
This property restricts the compiler's reordering optimizations from moving operations that read or write state data above an operation that also writes the same data, which would result in potentially producing wrong results from the execution.
This is an optional convenience property that allows defining the state data dependency the other way around. If an operation is listed in the ``affects'' list it means that the operation is writing state data that is read or written by the affected operation.
Note: it is not necessary that, if operation A `affects' operation B, then B must contain A in its `affected-by' list. Vice versa, if A is `affected-by' B, it is not needed that B must contain A in its `affects' list. This allows, for example, a user to add new operations that share state with the base operations shipped with TCE, without needing to modify the base operations.
For example, INIT_RF could initialize the internal register file with the given number. This operation should be marked ``side-effects''. Let's say that another two operations COMPUTE_X and COMPUTE_Y only read the internal RF data, thus they can be freely reordered by the computer with each other in the program code in case there are no other dependencies. As they only read the state data, they don't have and visible side effects, thus they should not be marked with the ``side-effects'' property. However, as they read the data written by the INIT_RF operation, both operations should be marked to be ``affected-by'' the INIT_RF.
Each input of an operation requires an independent declaration of its properties. An operation input is completely defined by the following properties:
Note: it is not an error if a program, before instruction scheduling, contains an operation where one of the output moves is missing. If all output moves of an operation are missing, then the only useful work that can be performed by the operation is state change.
The semantics of an operation may be modeled with a simple dag-based language. This can be used to both simulate the operation without writing the simulation code, and to allow the compiler to automatically use custom operations.
The optional field to describe the Operation DAGs is trigger-semantics. It contains description of the operation semantics as a directed acyclic graph (DAG).
The OperationDAG language description is given as the content of this element. The OperationDAG language has the following syntax:
All commands end in semicolon.
SimValue temp;
SimValue temp1, temp2;
The input values can be either temporary variables, inputs to the operation whose semantics is being modeled, or integer constants.
The output value can be either temporary variables or outputs of the operation whose semantics is being modelled.
Operands of the modeled operation are referred with name IO(<number>) where 1 is first input operand, 2 second input operand, and after input operands come output operands.
Temporary values are referred by their name, and integer constants are given in decimal format.
Example OperationDAG of the ADDSUB operation:
<trigger-semantics> EXEC_OPERATION(add, IO(1), IO(2), IO(3)); EXEC_OPERATION(sub, IO(1), IO(2), IO(4)); </trigger-semantics>
To be complete, the model of an operation needs to describe the behavior of the operation. The behavior is specified in a restricted form of C++, the source language of the TCE toolset, augmented with macro definitions. This definition is used for simulating the operation in the instruction set simulator of TCE.
Operation behavior simulation functions are entered inside an operation behavior definition block. There are two kinds of such blocks: one for operations with no state, and one for operations with state.
OPERATION()
.
OPERATION_WITH_STATE()
.
The main emulation function definition block is given as:
The bodies of the function definitions are written in the operation behavior language, described in Section 4.3.6.
To define the behavior of an operation with state it is necessary to
declare the state object of the operation. An operation state declaration
is introduced by the special statement DEFINE_STATE()
. See
Section 4.3.6 for a description of this and related
statements. State must be declared before it is used in operation behavior
definition.
A target processor may contain several implementations of the same operation. These implementations are called Hardware Operations and are described in [CSJ04].Each Hardware Operation instance belongs to a different function unit and is independent from other instances.When an operation has state, each of its Hardware Operations uses a different, independent instance of the state class (one for each function unit that implements that operation).
An operation state object is unambiguously associated with an operation (or a group of operations, in case the state is shared among several) by means of its name, which should be unique across all the operation definitions.
Operation state can be accessed in the code that implements the behavior of
the operation by means of a STATE
expression. The fields of the
state object are accessed with the dot operator, as in C++. See
Section 4.3.6 for a complete description of this
statement.
The definition of an emulation function is introduced by the statement
TRIGGER
and is terminated by the statement END_TRIGGER;
.
An emulation function is expected to read all the inputs of its operation and to update the operation outputs with any new result value that can be computed before returning.
The behavior of operations and the information contents of operation state objects are defined by means of the behavior description language.
The emulation functions that model operation behavior are written in C++ with some restrictions. The OSAL behavior definition language augments the C++ language with a number of statements. For example, control may exit the definition body at any moment by using a special statement, a set of statements is provided to refer to operation inputs and outputs, and a statement is provided to access the memory model.
Two sets of expressions are used when accessing terminals. The value of an input terminal can be read as an unsigned integer, a signed integer, a single precision floating point number, or a double precision floating point number using the following expressions:
Output terminals can be written using the following expression:
Since the behavior of certain operations may depend in non-trivial ways on the bit width of the terminals of a given implementation, it is sometimes necessary to know the bit width of every terminal. The expression
Bit width of the operands can be extended using two different expressions.
Sign extension means that the sign bit of the source word is duplicated to the extra bits provided by the wider target destination word.
For example a sign extension from 1001b (4 bits) to 8 bits provides the result 1111 1001b.
Zero extension means that the extra bits of the wider target destination word are set to zero.
For example a zero extension from 1001b (4 bits) to 8 bits provides the result 0000 1001b.
Example. The following code implements the behavior of an accumulate operation with one input and one output, where the result value is saturated to the ``all 1's'' bit pattern if it exceeds the range that can be expressed by the output:
STATE.accumulator += INT(1); IntWord maxVal = (1 << BWIDTH(2)) - 1; IO(2) = (STATE.accumulator <= maxVal ? STATE.accumulator : maxVal);
[A-Z][0-9A-Z_]*Note that only upper case letters are allowed.
A state definition block is terminated by the statement
Example. The following declaration defines an operation state class identified by the name string ``BLISS'', consisting of one integer word, one floating-point word and a flag:
DEFINE_STATE(BLISS) IntWord data1; FloatWord floatData; bool errorOccurred; END_DEFINE_STATE;
Some operation state definitions may require that the data is initialized to a predefined state, or even that dynamic data structures are allocated when the operation state object is created. In these cases, the user is required to provide an initialization definition inside the state definition block.
Some state definitions may contain resources that need to be released when the state model is destroyed. For example, state may contain dynamically allocated data or files that need to be closed. In these cases, the user must define a function that is called when the state is deallocated. This function is defined by a finalization definition block, which must be defined inside the state definition block.
The state model provides two special definition blocks to support emulation of operation behaviour.
Typically, an operation state data structure consists of several fields,
which are accessed using the dot operator of C++. For example, the
expression STATE.floatData
in a simulation function refers to the
field floatData
of the state object assigned to the operation
being defined. The precise format of an
operation state structure is defined by means of the DEFINE_STATE()
statement, and it is specific for an operation. State must be defined before it
can be used in an operation definition.
PROGRAM_COUNTER
or
RETURN_ADDRESS
in operations that are not implemented on a Global
Control Unit.
Each operation context can be identified with a single integer which
can be accessed with CONTEXT_ID
.
return true;
.
The memory interface of OSAL is very simplified allowing easy modeling of data memory accessing operations. The following keywords are used to define the memory access behavior:
The endianess mode for MEMORY.read(3) and MEMORY.write(3) is determined by the target machine (see section 5.1.3) in the TTA simulator. In the OSAL tester, the endianess mode is fixed to big endian.
Pekka Jääskeläinen 2018-03-12