Subsections


3 OSAL files

Operations are divided in ``operation modules''. For example, `base' module, included in the TCE distribution, contains all the operations available to the front end compiler's code generation.

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.


1 Operation Properties

The following properties define a TTA operation in TCE:

1 operation name

The operation name is a string of characters starting with a character in set [A-Z_] and followed by one or more character in set [0-9A-Z_] (i.e. lowercase letters are not allowed). All names of operations of the database must be unique. Different data bases can contain operations with equal names.

2 operation description

Optional description of the operation.

3 inputs

Number of inputs of the operation. The number of inputs is a nonnegative integer. It must be positive if the number of outputs is zero.

4 outputs

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.

5 reads/writes-memory

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.

6 clocked

Clocked attribute indicates that the operation can change its state synchronously with clock signal and independently from its input.

7 side-effect

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.

8 affected-by

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.

9 affects

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.

10 An example of defining multiple operations that share the same state.

A common use case for multiple operations that share state data is a case where one or more operations initialize an internal register file in an FU and one or more operations use the data in the register file to compute their results.

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.


2 Operation Input Properties

Each input of an operation requires an independent declaration of its properties. An operation input is completely defined by the following properties:

1 identification number

Integer number in the range [1,N] where N is the number of inputs as defined in section 4.3.1 of operation declaration. If N is zero, then no input declarations can be specified. TCE does not currently allow operations with zero inputs to be defined.

2 can-swap

A list of identification numbers. All the inputs listed can be swapped with this input. The identification number of this input definition is not allowed in the list. The can-swap property is commutative, thus any of the listed inputs is implicitly `can-swap' with this input and all the other inputs listed. The can-swap declaration need not be symmetrical, but it is not an error if the implied declaration is also specified.

3 mem-address

Optional. Indicates that the input is used to compute (affects the value of) the memory address accessed by this operation.

4 mem-data

Optional. Indicates that the input is used to compute (affects the value of) the data word written to memory by this operation. This property implies that the operation writes to memory.


3 Operation Output 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.

1 mem-data

Optional. Indicates that the output contains a value that depends on a data word read from memory by this operation. This property implies that the operation reads data from memory.


4 Operation DAG

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 name{, name2, ... }
Creates a temporary variables with given name. Usage examples:

SimValue temp;

SimValue temp1, temp2;

EXEC_OPERATION(operationname, input1, ..., inputN, output1, ..., outputN)
Calls another operation with the given operand values.

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>


5 Operation Behavior

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.

1 Definition of Operation Behavior

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(operationName)
Starts an operation behavior definition block for an operation with name operationName. Operations defined with this statement do not contain state. Operation names must be written in upper case letters!
END_OPERATION(operationName)
End an operation behavior definition block for an operation with no state. operationName has to be exactly the same as it was entered in the block start statement OPERATION().
OPERATION_WITH_STATE(operationName, stateName)
Starts an operation behavior definition block for an operation with state. operationName contains the name of the operation, stateName name of the state. DEFINE_STATE() definition for the stateName must occur before this statement in the definition file. Operation and state names must be written in upper case letters!
END_OPERATION_WITH_STATE(operationName)
Ends an operation behavior definition block for an operation with state. operationName has to be exactly the same as it was entered in the block start statement OPERATION_WITH_STATE().

The main emulation function definition block is given as:

TRIGGER ... END_TRIGGER;
Main emulation function.

The bodies of the function definitions are written in the operation behavior language, described in Section 4.3.6.

2 Operations with state.

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.

3 Main emulation function.

The behavior model of an operation must include a function that, given a set of input operand values and, optionally, an operation state instance, produces one or more output values that the operation would produce.

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.


6 Behavior Description language

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.

1 Base data types.

The behavior description language defines a number of base data types. These types should be used to implement the operation behavior instead of the C base data types, because they guarantee the bit width and the format.
IntWord
Unsigned integer 32-bit word.
FloatWord
Single-precision (32-bit) floating-point word in IEEE-754 format.
DoubleWord
Double-precision (64-bit) floating-point word in IEEE-754 format.
HalfWord
Half-precision (16-bit) floating-point word in IEEE-754 format.

2 Access to operation inputs and outputs.

Inputs and outputs of operations (henceforth referred to as terminals, when a distinction is not needed) are referred to by a unique number. The inputs are assigned a number starting from 1 for the first input. The first output is assigned the number $n+1$, where $n$ is the number of inputs of the operations, the second $n+2$, and so on.

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:

UINT(number)
Treats the input terminal denoted by number as a number of type IntWord, which is an unsigned integer of 32 bits maximum length.
INT(number)
Treats the input terminal denoted by number as a number of type SIntWord, which is a signed integer of 32 bits maximum length.
FLT(number)
Treats the input terminal denoted by number as a number of type FloatWord.
DBL(number)
Treats the input terminal denoted by number as a number of type DoubleWord.

Output terminals can be written using the following expression:

IO(number)
Treats the terminal denoted by number as an output terminal. The actual bit pattern (signed, unsigned or floating point) written to the output terminal is determined by the right hand expression assigned to the IO() 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

BWIDTH(number)
returns the bit width of the terminal denoted by number in the implementation of the calling client.

Bit width of the operands can be extended using two different expressions.

SIGN_EXTEND(integer, sourceWidth)
Sign extends the given integer from sourceWidth to 32 bits.

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_EXTEND(integer, sourceWidth)
Zero extends the given integer from sourceWidth to 32 bits.

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);

3 Definition of operation state.

Operation state consists of a data structure. Its value is shared by one or more operations, and it is introduced by the statement
DEFINE_STATE(name)
where name is a string that identifies this type of operation state. This statement is followed by a list of data type fields. The state name string must be generated with the following regular expression:
[A-Z][0-9A-Z_]*
Note that only upper case letters are allowed.

A state definition block is terminated by the statement

END_DEFINE_STATE

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.

INIT_STATE(name)
Introduces the code that initializes the operation state.
END_INIT_STATE
Terminates the block that contains the initialization code.

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.

FINALIZE_STATE(name)
Introduces the code that finalizes the operation state, that is, deallocates the dynamic data contained in an operation state object.
END_FINALIZE_STATE
Terminates the block that contains finalisation code.

The state model provides two special definition blocks to support emulation of operation behaviour.

ADVANCE_CLOCK ...END_ADVANCE_CLOCK
In case the model of operations state is synchronous, this definition can be used to specify activity that occurs ``in the raising edge of the clock signal'', that is, at the end of a simulation cycle. The C `return' statement can used to return from this function.

4 Access to operation state.

Operation state is denoted by a unique name string and is accessed by means of the statement
STATE

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.

5 Access to control registers.

Operations that can modify the program control flow can access the program counter register and the return address of the target processor by means of the following expressions:
PROGRAM_COUNTER
RETURN_ADDRESS
Not all operations can access the control registers. Only operations implemented on a Global Control Unit (see [CSJ04]) provide the necessary data. It is an error to use PROGRAM_COUNTER or RETURN_ADDRESS in operations that are not implemented on a Global Control Unit.

6 Context Identification

Each operation context can be identified with a single integer which can be accessed with CONTEXT_ID.

7 Returning from operation behavior emulation functions.

Normally, an emulation function returns control to the caller when control flows out of the definition body. To return immediately, from an arbitrary point in the definition body, the following normal C++ return statement can be used. The return value is boolean indicating the success of the operation execution. In pratice, 'true' is always returned: return true;.

8 Memory Interface.

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:

MEMORY.read(address, count, target)
Reads count units of data from the address to the variable target in the order of target endianess.
MEMORY.write(address, count, data)
Writes count units of data in variable data to the address in the order of target endianess.
MEMORY.readBE(address, count, target)
Explicitly reads count units of data from the address to the variable target in big endian order.
MEMORY.readLE(address, count, target)
Explicitly reads count units of data from the address to the variable target in little endian order.
MEMORY.writeBE(address, count, data)
Explicitly writes count units of data in variable data to the address in big endian order.
MEMORY.writeLE(address, count, data)
Explicitly writes count units of data in variable data to the address in little endian order.

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