From The MPEG-4 Structured Audio Book by John Lazzaro and John Wawrzynek.

Part II/2: SAOL Expressions and Statements

Sections

In This Chapter

Statements:

assignment if if-else while

Other Elements:

+ - * / == != <= >= && || ! ? : = array

Introduction

In this chapter, we show how to construct arithmetic and logical expressions in SAOL. We also explain the SAOL statements for assignment, conditional execution, and looping.

We show how to determine the width and rate of SAOL expressions, and how width and rate rules govern the use of expressions in SAOL statements.

The rate rules we present in this book are sometimes more conservative than those in the SAOL language specification. We reformulated these rules to make them easier to understand and remember.

We find that SAOL programs written using these rules are easier to maintain, and often run faster as well.

 

Arithmetic Expressions

The simplest SAOL expressions use the basic arithmetic operations (negation, addition, subtraction, multiplication, and division) on scalar signal variables and numbers.

Negation is evaluated first in SAOL expressions, followed by addition and subtraction, and lastly multiplication and division. This ordering forms three precedence classes: unary -, binary (+, -), and binary (*, /).

For the binary classes, operators are performed in the order they appear in an expression, scanning from left to right. For the unary class, operators are performed in order scanning from right to left. Parenthesis act to change this ordering.

These precedence and scanning order rules are identical to the C language. The right panel shows examples of correct evaluation.

Unlike C, all numbers and variables in SAOL expressions are 32-bit floating point values. As a result, the type conversion issues in evaluating C expressions aren't needed. An example on the right panel show a consequence of this distinction.

All unary and binary operations in SAOL follow a simple rate rule: the rate of an expression is the rate of its fastest subexpression (i-rate is the slowest rate, k-rate is a faster rate, and a-rate is the fastest rate).

If a subexpression is an atomic element (a variable, instr parameter, standard name, or number) the rate of the subexpressions is the rate of the atomic element.

See the right panel for example expressions and their rates.

Expression Evaluation

SAOL Expression      Evaluation

-10.0*12 + 5/10        -119.5  
   
-(10.0*12 + 5/10)      -120.5

-10.0*(12 + 5)/10      -17

-10.0*(12 + 5/10)      -125



SAOL Expression     Evaluation

-10.0*12 + 5/10        -119.5

C Expression        Evaluation

-10.0*12 + 5/10        -120.0

Rate Semantics

ivar i1;
ksig k1;
asig a1;

10*i1 + 1/i1    // i-rate

i1*k1 - k1      // k-rate

-a1 + i1*k2     // a-rate   

Arrays and Expressions

In Part II/1, we described how to declare array signal variables. In this section, we describe how to use arrays in arithmetic expressions.

Like C arrays, arrays in SAOL may be indexed to access a single value in an array, using square bracket syntax. See the right panel for examples.

SAOL array indexes are numbered starting with 0 and can take on values up to N - 1, where N is the declared width of an array. The index value is a scalar SAOL expression.

The index expression is evaluated to a 32-bit floating point value, then rounded to the nearest integer to produce the index position. Rounding is performed by adding 0.5 to the index value and truncating the result.

Indexed arrays in SAOL are scalars. The rate of an indexed array is either the declared rate of the array itself, or the rate of the indexing expression, whichever is faster. The panel on the right shows examples of indexed arrays in SAOL expressions.

Arrays in SAOL are different from C arrays in one significant way: an unindexed array may be used in an expression.

One simple way to use arrays in SAOL arithmetic expressions is for all atomic elements be array variables of the same width N. In this case, the expression is evaluated as in the scalar case, for each position in the array. The final result has width N. See the right panel for an example.

Expressions between a scalar and a width N array are also supported in SAOL. For a binary operator, the scalar is promoted to an array of width N that takes on the scalar value for all elements, and the operation proceeds as an array operation.

Operations between two arrays of different widths are prohibited (note we consider arrays of width 1 as scalars in this context). See the right panel for legal and illegal array expressions.

The rate semantics for array expressions are identical to scalar expressions.

Declarations

// used in examples below

ivar i[2]; 
ksig k;    
asig a[3]; 

Legal Indexes

i[0]       // 
i[0.25*5]  // 1.25 rounds to 1

Illegal Indexes

i[2]       // out of range
i[1.5]     // 1.5 rounds to 2
k[0]       // k not an array

Arrays and Rates

i[0]       // i-rate 
i[k]       // k-rate
a[k]       // a-rate

Arrays and Width

i[0] = 1;   // setup
i[1] = 2;
k = 3;


i*i      // expression width: 2
         // 
	 // expression value: (1,4)


i*2      // expression width: 2
         // 
         // expression value: (2,4)


i*k      // expression width: 2
         // 
         // expression value: (3,6)
	 //
	 // expression rate: k-rate


i*a      // illegal (width mismatch) 
         //
         // a: width = 3
	 //
         // i: width = 2

Logical Expressions

SAOL provides relational operators for comparing signal variables and logical operators for doing Boolean algebra on a binary interpretation of signal variables.

Like arithmetic expressions, the rate of relational and logical operators is the rate of the fastest subexpression. Apart from the && and || operators (see below) the width semantics are also identical to arithmetic expressions.

SAOL has six relational operators (less than, greater than, less than or equal to, greater than or equal to, equal to, not equal to) that use the same symbols as C (< > <= >= == !=). If an operator is true, it takes the value 1.0. If it is false, it takes the value 0.0.

SAOL has three logical operators that implement the Boolean AND, OR, and NOT functions. SAOL logical operators interpret the floating-point value 0.0 as false, and all other values as true.

The unary NOT operator ! performs logical negation on a signal variable, mapping 0.0 to 1.0 and all other values to 0.0.

The binary logical operators && and || perform the logical AND and OR operations. The semantics of these operators depend on the width of their operand subexpressions. If at least one subexpression has a width greater than one, the left and right subexpressions are both evaluated, and the operator is applied as usual.

However, if both operands are scalar width, the && and || operators take on the short-circuit semantics of their C language equivalents. For AND, if the left subexpression evaluates to false, the right subexpression is not evaluated, and the result of the AND operation is set to 0.0. Only if the left subexpression evaluates to true is the right subexpression evaluated.

Likewise, for the logical OR operator with scalar width operands, if the left subexpression evaluates to true, the right subexpression is not evaluated, and the result of the OR operation is set to 1.0. Only if the left subexpression evaluates to false is the right subexpression evaluated.

Apart from the short-circuit behavior, width semantics of && and || are identical to arithmetic operators.

Precedence

! - * / + - < > <= >= == != && || ?: (see next section)

Notes. Table indicates the order operators are performed during expression evaluation. Operators on the top line are performed first, operators on the bottom line are performed last.

Association

right left left left left left left right
Notes. All operators on a left-associative line are performed in the order they appear in an expression, scanning from left to right. For right-associative lines, operators are performed in the order they appear in an expression, scanning from right to left.

Switch

The SAOL switch operator (see right panel for syntax) has both logical and arithmetic properties. If the logical value of the first operand is non-zero (logical true) the switch operator takes the value of the second operand, else it takes the value of the third operand.

The rate of the switch operator is the rate of the fastest of its three operands. As shown in the table in the last section, the precedence of the switch operator is the lowest of all SAOL operators.

The exact semantics of the switch operator depend on the width of its subexpression operands. If its operands all have scalar width, the operator has short circuit semantics. The first subexpressions is always evaluated, and depending on its logical value, either the second or the third subexpression is evaluated (but never both).

However, if at least one subexpression has width greater than 1, all three subexpressions are evaluated, and then the operator logic happens on an element-by-element basis.

Apart from the short-circuit behavior, width semantics are identical to arithmetic operators.

The switch operand concludes our tour of the SAOL operators. The C operators that are missing from SAOL are those that target integer data types, such as bit shifts, bit-wise logic, and modulo. In addition, SAOL expressions may not have embedded assignments to variables within them, unlike C expressions.

The Switch Operator

op1 ? op2 : op3   

|a| using Switch

(a >= 0) ?  a  : -a  

Illegal in SAOL Expressions

+   // unary plus 

%   // not a floating point op
^   // 
&   // 
<<  // 
>>  // 
~   // 

=   // assignment not embeddable

++  // illegal in assignment too!
--  // 
+=  // 
-=  // 
*=  // 
/=  // 
%=  // 
<<= // 
>>= // 
&=  // 
^=  // 
|=  // 

Assignment Statement

In this chapter, we describe the three SAOL statements that are the core tools for expressing algorithms. We begin with the assignment statement that sets a signal variable to a new value.

The panel on the right shows the syntax of the assignment statement. The lval is the instr parameter or signal variable that receives the new value. The expr is the expression that is evaluated to generate the value to assign.

An lval may be a scalar or array variable, and so assignment statements also have rules regarding width semantics.

If the lval has scalar width (i.e. it is a scalar, an array of width 1, or an indexed array), the expression must also have scalar width. Indexed array lvals follow the rate, width, and indexing rules for indexed arrays in expressions.

If the lval is an unindexed array of width N, the expression must either have width N or scalar width (in which case each element of the lval takes on the scalar value).

See the right panel for examples showing the width semantics of assignment statements.

If the lval and expr both have width greater than one, SAOL leaves the sequence order of expression evaluation and assignment undefined. One implementation may evaluate the expression for all array elements before doing the assignment; a second implementation may evaluate and assign array elements member by member, in an arbitrary order.

This implementation detail matters to the SAOL programmer, because it is possible to write assignment statements whose answer depends on the sequence of operations, by using indexed versions of the lval variable in the expr. Reliable SAOL programs break up this sort of assignment statement into several simpler statements.

Assignment statements have two rate rules:

  1. The rate of the lval sets the rate for the assignment statement. For example, if the lval is a-rate, the expression is evaluated and the lval is set to a new value during every a-pass. This is true even if the expression is k-rate or i-rate.
  2. The rate of the expression may not be faster than the rate of the lval. For example, if the lval is k-rate, the expression may not be a-rate.

These rules underpin the SAOL multi-pass compute model described in the tutorial in Part I. The first rule establishes the convention for setting the rate of variable assignments. The second rule forces information to flow from slower-rate variables to faster-rate variables.

SAOL also has a null variant of the assignment statement, in which an expression is computed but not assigned to an lval. The null assignment statement runs at the rate of the expression.

Syntax

lval = expr ; 

Width Semantics

ksig stereo[2], quad[4];

		   // stereo set to:
stereo = 2;        // (2,2)
stereo = stereo*2; // (4,4)

// illegal statement

quad = stereo;  // width mismatch

Rate Semantics

ivar i[2]; 
ksig k;
asig a;
	      // runs at:

i = 10;       // i-rate
i = i*i;      // i-rate

k = i[0];     // k-rate
k = k + i[0]; // k-rate

a = i[0]*k;   // a-rate
a = k*a;      // a-rate

// illegal statements
// violate rule 2

i = k;        
i = a;        

// legal statement
// satisfies rule 2

i[0*a] = a;   // array index is
	      // a-rate, and so
	      // the statement 
	      // is too!

Null Syntax

expr ; 

Null Example

ivar i; 
ksig k;

i*i;   // runs at i-rate
i*k;   // runs at k-rate

If and If-Else Statements

The if and if-else statements support conditional execution. The right panel shows the syntax for these statements.

These conditional statements work as follows. If the guard expression expr of an if statement is non-zero (logical true) the statement block is executed.

The if-else statement adds a second statement block that is executed if expr is zero (logical false).

Unlike C, the curly braces surrounding the statement blocks in the SAOL if and if-else statements are required, regardless of the number of statements in the block.

The guard expression must have scalar width. The short-circuit semantics of the scalar-width &&, ||, and switch operators are useful for constructing efficient guard expressions.

The if and if-else statements have four rate rules. The first two rules are simple to state:

  1. The rate of the if or if-else statement is the rate of the fastest statement in the statement block(s) it controls.
  2. No statement in the statement block(s) may be slower than the guard expression.

Rule 1 sets a simple convention for setting the rate of the if or if-else statement. Rule 2 lets slower-rate variables conditionally control the execution of faster-rate statements.

The right panel shows an example of an if-else statement, whose behavior may be understood using these two rate rules.

Multi-rate semantics

Note that rate Rules 1 and 2 permit multi-rate if and if-else statements. For example, a k-rate if statement with an i-rate guard may include i-rate statements in its statement block.

We recommend avoiding multi-rate statement blocks, since the resulting code is difficult to maintain. However, because this construction is legal, you may encounter it in SAOL code. Rate rules 3 and 4 cover this construction:

  1. If i-rate statements appear in a statement block of a k-rate or a-rate if or if-else statement, the i-rate statements execute at most once, during the first time the statement block executes. The i-rate statements execute at this start of statement block execution, before any k-rate or a-rate statements run.
  2. If k-rate statements appear in a statement block of an a-rate if or if-else statement, the k-rate statements execute at most once per execution cycle, during the first a-pass the statement block executes. The k-rate statements execute at this start of statement block execution, before any a-rate statements run.

Syntax


if ( expr )
 {
   statement
   statement
       .
       .
 }


if ( expr )
 {
   statement
   statement
       .
       .
 }
else
 {
   statement
   statement
       .
       .	
 }

Example


instr piano(note) {

  ksig decay;


// note:
//
// (1) if-else runs at k-rate
// (2) { and } are required
                

  if (note > 5)   // expr i-rate 
   {
     decay = 0.8; // k-rate 
   }
  else
   {
     decay = 0.9; // k-rate
   }
}

While Statement

The while statement is the only looping construct in SAOL. See the right panel for the statement syntax.

The curly braces surrounding the statement block are required, regardless of the number of statements in the block.

At the start of while statement execution, the guard expression expr is evaluated. If it has a value of zero (logical false) the statement ends.

However if expr is non-zero (logical true) the program flow alternates between executing the statement block and re-evaluating expr. The while statement ends the first time expr evaluates to false.

The guard expr must have scalar width.

The while statement obeys a single rate rule:

  1. The guard expression and all statements in the block must run at the same rate. This rate becomes the rate of the while statement.

The right panel shows an example of a while statement. The statement block executes 50 times per k-pass.

Syntax


while ( expr )
 {
   statement
   statement
       .
       .
 }

Example


ksig a; 

// while runs at k-rate
// { and } are required

while (a < 50)
 {
   a = a + 1;
 }

// reset for next k-pass

a = 0; 

Summary

In this chapter, we have presented the core language tools for expressing algorithms. Compared to a language like C, the number of constructs may seem small.

The SAOL toolkit is limited because there is so much information to remember about each construct: program semantics, width semantics, and rate semantics. Three core statement types is a manageable number to remember how to use, while 6 or 7 might not be.

The right panel collects the rate semantic rules we have presented in this chapter.

Next section: Part II/3: Simple Core Opcodes

Rate Rules

Expressions

  1. The rate of an expression (as described in this chapter) is the rate of its fastest atomic element.

Assignment Statement

  1. The rate of the lval sets the rate for the assignment statement.
  2. The rate of the expression may not be faster than the rate of the lval.

Null Assignment Statement

  1. The rate of the statement is the rate of the expression.

If and If-Else Statements

  1. The rate of the if or if-else statement is the rate of the fastest statement in the statement block(s) it controls.
  2. No statement in the statement block(s) may be slower than the guard expression.
  3. If i-rate statements appear in a statement block of a k-rate or a-rate if or if-else statement, the i-rate statements execute at most once, during the first time the statement block executes. The i-rate statements execute at this start of statement block execution, before any k-rate or a-rate statements run.
  4. If k-rate statements appear in a statement block of an a-rate if or if-else statement, the k-rate statements execute at most once per execution cycle, during the first a-pass the statement block executes. The k-rate statements execute at this start of statement block execution, before any a-rate statements run.

While Statement

  1. The guard expression and all statements in the block must run at the same rate. This rate becomes the rate of the while statement.

Copyright 1999 John Lazzaro and John Wawrzynek.