Top Up Prev Next Bottom Contents Index Search

13.2 Writing Code Generation Stars


Code generation stars are very similar to the C++ simulation stars. The main difference is that the initialization (setup()), run time (go()), and termination (wrapup()) methods generate code to be compiled and executed later. Additionally, code generation stars have two more methods called initCode()and execTime().

The setup() method is called before the schedule is generated and before any memory is allocated. In this method, we usually initialize local variables or states. Note that the setup method of a star may be called multiple times. This means that the user should be careful so that the behavior of the star does not change even though setup method is called multiple times. The initCode() method of a star is called after the static schedule has been generated and before the schedule is fired. This method is used to generate the code outside of the main loop such as initialization code and procedure declaration code. To generate start-up code, use the initCode method, NOT the setup method, since setup is called before scheduling and memory allocation. The main use of the setup method, as in SDF, is to tell the scheduler if more than one sample is to be accessed from a porthole with the setSDFParams call.

The go() function is used to generate the main loop code for the star. Finally, the wrapup() function is used to generate the code after the main loop.

The execTime() method returns an integer specifying the time needed to execute the main loop code of a code generation star in processor cycles or instruction steps. These numbers are used by the parallel schedulers. In the assembly code generation domains, the integer returned is the main loop code execution time in DSP instruction cycles. The better the execTime() estimates are for each star, the more efficient the parallel schedule becomes.

If a star is invoked more than once during an iteration period, the precedence relation between stars should be known to the parallel scheduler. If there is no precedence relation between invocations, the parallel scheduler will try to parallelize them. By default, there is a precedence relation between invocations for any star (this is equivalent to having a self-loop). To assert that there is no such self-loop for a star, we have to call the noInternalState() method in the constructor:

constructor {
noInternalState();
}
It is strongly recommended that the star designer determine whether the star is parallelizable or not, and call noInternalState() if it is.

The CGStar class is the base class for all code generation stars, such as high level language code generation stars and assembly language code generation stars. In this section, we will explain the common features that the CGStar class provides for all derivative code generation stars.

As a simple example to see how code generation stars are written, let's write an adder star for the C code generation domain. The defstar is almost the same as for a simulation star:

defstar {
name {Add}
domain {CGC}
desc { Output the sum of the inputs, as a floating
value.}
author { J. Pino }
input {
name {input1}
type {float}
}
input {
name {input2}
type {float}
}
output {
name {output}
type {float}
}
...

13.2.1 Codeblocks

Next we have to define the C code which will be used to generate the run-time code. For this we use a codeblock. A codeblock is a pseudo-language specification of a code segment. By pseudo-language we mean that the block of code is written in the target language with interspersed macros. Macros will be explained in the following section.

Codeblocks are implemented as protected static class members (e.g. there is one instance of a codeblock for the entire class). Since they are protected, codeblocks from one star class can be used from a derived star. The codeblock directive defines a block of code with an associated identifying name ("addCB" in this case).

codeblock (addCB) {
/* output = input1 + input2 */
$ref(output) = $ref(input1) + $ref(input2);
}
Special care should be given to codeblock specification. Within each line, spaces, tabs, and new line characters are important because they are preserved. For this reason, the brackets
"{ }" should not be on the same lines with the code. Had addCB been defined as follows:

codeblock (addCB) { /* output = input1 + input2 */
$ref(output) = $ref(input1) + $ref(input2); }
the line


ref(output) = $ref(input1) + $ref(input2);
would be lost! This is because anything preceding the closing "}" on the same line is discarded by the preprocessor (ptlang). Secondly, the spaces and tabs between the opening "{" and the first non-space character will be ignored.

The first definition of the addCB codeblock is translated by ptlang into a definition of a static public member in the .h file:

class CGCAdd : public CGCStar
{
...
static CodeBlock addCB;
...
}
An associated constructor call will be generated in the .cc file:

CodeBlock CGCAdd :: addCB (
" /* output = input1 + input2 */\n"
" $ref(output) = $ref(input1) + $ref(input2);\n"
);
The argument is a single string, divided into lines for convenience. The following will complete our definition of the add star:

go {
addCode(addCB);
}
Notice that the code is added in the go method, thus implying that the code is generated in the main loop.

The

addCode(code,stream name,<unique name>) method of a CG star provides an interface to all the code streams (stream name and unique-name arguments are optional). This method defaults to adding code into the myCode stream (codestreams are explained later on). If a stream name is specified, addCode looks up the stream using the getStream(stream-name) method and then adds the code into that stream. Furthermore, if a unique name is provided for the code, the code will only be added if no other code has previously been added with the given unique name. The method addCode will return TRUE if the code-string has been added to the stream and otherwise will return FALSE.

The star just defined is a very simple star. Typical code generation stars will define many codeblocks. Conditional code generation is easily accomplished, as is done in the following example:

go {
if (parameter == YES)
addCode(yesblock);
else
addCode(noblock);
}
So far, we have used the addCode() method to generate the code inside the main loop body. In the assembly language domains, addCode can be called in the initCode and
wrapup methods, to place code before or after the main loop respectively. In all of the code generation domains, we can use the addProcedure() method to generate declarations outside of the main body. Refer to
"Code streams" on page 13-16 for documentation on the addCode and addProcedure methods.

The next section describes the extended codeblock support. The previous discussion of simple codeblocks is still correct and supported by ptlang; the extensions below are upward compatible. These extensions are experimental. They may change in future version of Ptolemy, and may still contain bugs.

13.2.2 Codeblocks with arguments

Simple codeblocks (as described above) have a name and are implemented as static member strings. Extended codeblocks have a name, optional arguments, and are implemented as non-static functions. They have an escape mechanism so that C++ expressions may be evaluated at run time and inserted into the generated code. However, in order to take advantage of this escape mechanism, a codeblock must be defined and called with arguments, even if those arguments are empty. An example:


codeblock(cbLoop,"int N, double x") {
for (i=0; i < @N; i++) {
$ref(output,i) = sin(i*@x);
}
}
This defines a codeblock named cbLoop with two arguments: N and x. The variable i will appear in the generated code, while the C++ expressions N and x are escaped by @ and will be evaluated at code-generation time. When this is called as


cbLoop(5, 0.1);
the following string will be returned:


for (i=0; i < 5; i++) {
$ref(output,i) = sin(i*0.1);
}
This might be used within a go() method as:


go {
addCode(cbLoop(5, 0.1));
}
The addCode() method will process the $ref() macro as described elsewhere. More complicated expressions are allowable. In general, the @ clause may be delimited by parentheses "("and ")", and must be operator << printable. The above codeblock could have been equivalently declared as:

codeblock(cbLoop,"int N, double x") {
for (i=0; i < @(N); i++) {
$ref(output,i) = sin(i*@(x));
}
}
A more complicated example follows:


codeblock(cbLoop2,"char *portname, int N, double x") {
for (i=0; i < @(int(length)); i++) {
$ref(@portname,i) = sin(i*@(x/N));
}
}
In this example, length is a data member of the star (typically a state). When called as:


cbLoop2("ina", 3, 0.2);
it would generate (assuming the value of length is 20):


for (i=0; i < 20; i++) {
$ref(ina,i) = sin(i*0.6666666);
}
In order to trigger the C++ expression processing via @-escapes in codeblocks which would otherwise have no arguments, add in a null argument list as in:


codeblock(cbLoop3,"") {
for (i=0; i < @(int(length)); i++) {
$ref(output,i) = sin(i*0.1);
}
}
In the example above, the @(int(length)) will be replaced with the value of the class member length. The above example would be called with an empty argument list as:


go {
addCode(cbLoop3());
}
The complete parsing rules are:

@@ ==> @ (double "@" goes to single)
@ATSIGN ==> @
@{ ==> {
@LBRACE ==> { (LBRACE is literal string)
@} ==> }
@RBRACE ==> } (RBRACE is literal string)
@\ ==> \
@BACKSLASH ==> \ (BACKSLASH is literal string)
@id ==> C++ token {id} (id is one or more alphanumerics)
@(expr) ==> C++ expr {expr}(expr is arbitrary with balanced
parens)
@(white_space) ==> nothing
@anything_else is passed through unchanged (including the @)
In an extended codeblock, trailing backslashes "\" will omit the following newline in the generated code. This special meaning of trailing "\" may be prevented by using "@\" or
"@BACKSLASH".

13.2.3 In-line codeblocks

Code blocks may be specified in the body of a method. Inside the definition of a method (such as go()), all contiguous blocks of lines with a leading @ will be translated into an in-line codeblock (i.e., an addCode() statement). The @ escape mechanism for C++ expressions works as described above for codeblocks with arguments. Within @-escaped expressions, in-line codeblocks may reference local method variables as well as member variables.

Leading white-space before a leading @ will be ignored. Note that no override mechanism is provided to prevent the in-line codeblock interpretation. Note also that @ has dual meanings: the first @ on the line introduces in-line codeblock mode, while subsequent @ characters on the same line escape into C++ expressions. For example:


go() {
@ CMAM_wait( &$ref(ackFlag), 1);
}
is equivalent to:


go() {
addCode(" CMAM_wait( &$ref(ackFlag), 1);\n");
}
A more complicated example:


go {
@ $ref(output) = \
int ni = input.numberPorts();
for (int i = 1; i <= ni; i++) {
@$ref(input#@i) @(i < ni ? " + " : ";\n") \
}
}
If "input.numberPorts()" returns 3 when the above program is run, the generated code will be:

" $ref(output) = $ref(input#1) + $ref(input#2) + $ref(input#3);\n"

Currently, only the pre-defined methods (start, go, exectime etc.) are processed this way; not user-defined methods.

13.2.4 Macros

In code generation stars, the inputs and outputs no longer hold values, but instead correspond to target resources where values will be stored (for example, memory locations/registers in assembler generation, or global variables in C-code generation). A star writer can also define states which can specify the need for global resources.

A code generation star, however, does not have knowledge of the available global resources or the global variables/tables which have already been defined in the generated code. For star writers, a set of macros to access the global resources is provided. The macros are expanded in a language or target specific manner after the target has allocated the resources properly. In this section, we discuss the macros defined in the CGStar class.

$ref(name)
Returns a reference to a state or a port. If the argument, name, refers to a port, it is functionally equivalent to the name%0 operator in the SDF simulation stars. If a star has a multi-porthole, say input, the first real porthole is input#1. To access the first porthole, we use $ref(input#1) or $ref(input#internal_state) where internal_state is the name of a state that has the current value, 1.
$ref(name,offset)
Returns a reference to an array state or a port with an offset that is not negative. For a port, it is functionally equivalent to name%offset in SDF simulation stars.
$val(state-name)
Returns the current value of the state. If the state is an array state, the macro will return a string of all the elements of the array spaced by the new line character. The advantage of not using $ref macro in place of $val is that no additional target resources need to be allocated.
$size(name)
Returns the size of the state/port argument. The size of a non-array state is one; the size of a array state is the total number of elements in the array. The size of a port is the buffer size allocated to the port. The buffer size is usually larger than the number of tokens consumed or produced through that port.
$starName()
Returns the instantiated name of the star (without galaxy or universe names)
$fullName()
Returns the complete name of the star including the galaxies to which it belongs.
$starSymbol(name)
Returns a unique label in the star instance scope. The instance scope is owned by a particular instance of that star in a graph. Furthermore, the scope is alive across all firings of that particular star. For example, two CG stars will have two distinct star instance scopes. As an example, we show some parts of ptlang file of the CGCPrinter star.

initCode {
...
StringList s;
s << " FILE* $starSymbol(fp);";
addDeclaration(s);
addInclude("<stdio.h>");
addCode(openfile);
...
}
codeblock (openfile) {
if(!($starSymbol(fp)=fopen("$val(fileName)","w"))) {
fprintf(stderr,"ERROR: cannot open output file
for Printer star.\n");
exit(1);
}
}
The file pointer fp for a star instance should be unique globally, and the $starSymbol macro guarantees the uniqueness. Within the same star instance, the macro returns the same label.
$sharedSymbol(list,name)
Returns the symbol for name in the list scope. This macro is provided so that various stars in the graph can share the same data structures such as sin/cos lookup tables and conversion table from linear to mu-law PCM encoder. These global data structures should be created and initialized once in the generated code. The macro sharedSymbol does not provide the method to generate the code, but does provide the method to create a label for the code. To generate the code only once, refer to
"Code streams" on page 13-16. A example where a shared symbol is used is in CGCPCM star.

codeblock (sharedDeclarations)
{
int $sharedSymbol(PCM,offset)[8];
/* Convert from linear to mu-law */
int $sharedSymbol(PCM,mulaw)(x)
double x;
{
double m;
m = (pow(256.0,fabs(x)) - 1.0) / 255.0;
return 4080.0 * m;
}
}
codeblock (sharedInit)
{
/* Initialize PCM offset table. */
{
int i;
double x = 0.0;
double dx = 0.125;
for(i = 0; i < 8; i++, x += dx)
{
$sharedSymbol(PCM,offset)[i] =
$sharedSymbol(PCM,mulaw)(x);
}
}
initCode {
...
if (addGlobal(sharedDeclarations, "$sharedSymbol(PCM,PCM)"))
addCode(sharedInit);
}
The above code creates a conversion table and a conversion function from linear to mu-law PCM encoder. The conversion table is named offset and belongs to the PCM class. The conversion function is named mulaw, and belongs to the same PCM class. Other stars can access that table or function by saying $sharedSymbol(PCM,offset) or $sharedSymbol(PCM,mulaw). The initCode method tries to put the sharedDeclarations codeblock into the global scope (by addGlobal() method in the CGC domain). That code block is given a unique label by $sharedSymbol(PCM,PCM). If the codeblock has not been previously defined, addGlobal returns true, thus allowing addCode(sharedInit). If there is more than one instance of the PCM star, only one instance will succeed in adding the code.

$label(name), $codeblockSymbol(name)
Returns a unique symbol in the codeblock scope. Both label and codeblockSymbol refer to the same macro expansion. The codeblock scope only lives as long as a codeblock is having code generated from it. Thus if a star uses addCode() more than once on a particular codeblock, all codeblock instances will have unique symbols. A example of where this is used in the CG56HostOut star.
codeblock(cbSingleBlocking) {
$label(wait)
jclr #m_htde,x:m_hsr,$label(wait)
jclr #0,x:m_pbddr,$label(wait)
movep $ref(input),x:m_htx
}
codeblock(cbMultiBlocking) {
move #$addr(input),r0
.LOOP #$val(samplesOutput)
$label(wait)
jclr #m_htde,x:m_hsr,$label(wait)
jclr #0,x:m_pbddr,$label(wait)
movep x:(r0)+,x:m_htx
.ENDL
nop
}
The above two codeblocks use a label named wait. The $label macro will assign unique strings for each codeblock.

The base CGStar class provides the above 8 macros. In the derived classes, we can add more macros, or redefine the meaning of these macros. Refer to each domain document to see how these macros are actually expanded. There are three commonly used macros in the assembly code generation domains; these are:

$addr(name)
This returns the address of the allocated memory location for the given state or porthole name. The address does not include references to the memory bank the location is coming from; for instance, "x:2034" for location 2034 in the "x" memory bank for Motorola 56000 is output as 2034.
$addr(name,<offset>)
This macro returns the numeric address in memory of the named object, without (for the 56000) an "x:" or "y:" prefix. If the given quantity is allocated in a register (not yet supported) this function returns an error. It is also an error if the argument is undefined or is a state that is not assigned to memory (e.g. a parameter).
Note that this does NOT necessarily return the address of the beginning of a porthole buffer; it returns the "access point" to be used by this star invocation, and in cases where the star is fired multiple times, this will typically be different from execution to execution.
If the optional argument offset is specified, the macro returns an expression that references the location at the specified offset -- wrapping around to the beginning of the buffer if that is necessary. Note that this wrapping works independent of whether the buffer is circularly aligned or not.
$ref(name,<offset>)
This macro is much like $addr(name), only the full expression used to refer to this object is returned, e.g. "x:23" for a 56000 if "name" is in x memory. If "name" is assigned to a register, this expression will return the corresponding register. The error conditions are the same as for $addr
$mem(name)
Returns the name of the memory bank in which the given state or porthole has its memory allocated.
To have "$" appear in the output code, put "$$" in the codeblock. For a domain where "$" is a frequently used character in the target language, it is possible to use a different character instead by redefining the virtual function substChar (defined in CGStar) to return a different character.

It is also possible to introduce processor-specific macros, by overriding the virtual function processMacro (rooted in CGStar) to process any macros it recognizes and defer substitution on the rest by calling its parent's processMacro method.

13.2.5 Assembly PortHoles

Here are some methods of class AsmPortHole that might be useful in assembly code generation stars:


bufSize()
Returns an integer, the size of the buffer associated with the porthole.

baseAddr()
Returns the base address of the porthole buffer

bufPos()
Returns the offset position in the buffer, which ranges from 0 to bufSize()-1.


13.2.6 Attributes

Attributes are assertions about the object they are applied to. Both states and portholes can have attributes. Attributes that apply to states have the prefix "A_". Attributes that apply to portholes have the prefix "P_". The following attributes are common to all code generation domains:

A_GLOBAL
If set, this state is declared global so that it is accessible everywhere. Currently, it is only supported in the CGC domain.
A_LOCAL
This is the opposite of A_GLOBAL.
A_SHARED
A state that is shared among all stars that know its name, type, size.
A_PRIVATE
Opposite of A_SHARED.
The default for stars is A_LOCAL|A_PRIVATE. Right now, only A_SHARED|A_LOCAL is supported in the assembly language domains. This combination means that all stars will share the particular state across a processor. For all stars to share it in a universe the bits A_SHARED|A_GLOBAL need to be set; this combination is not implemented yet - the default method will probably restrict all the stars that share this state to the same processor.

A_CONSTANT
The state value is not changed by the star's execution.
A_NONCONSTANT
The state value is changed by the star's execution.
A_SETTABLE
The user may set the value of this state from a user interface.
A_NONSETTABLE
The user may not set the value of this state from a user interface (e.g. edit-parameters doesn't show it).
Applying an attribute to an object implies that some bits are to be "turned on", and others are to be "turned off". The underlying attribute bits have names beginning with AB_ for states, and PB_ for portholes. The only two bits that exist in all states are AB_CONST and AB_SETTABLE. By default, they are on for states, which means that the default state works like a parameter (you can set it from the user interface, and the star's execution does not change it).

For assembly language domains, the following attributes are defined:

A_CIRC
If set, the memory for this state is allocated as a circular buffer, whose address is aligned to the next power of two greater than or equal to its length.
A_CONSEC
If set, allocate the memory for the next state in this star consecutively, starting immediately after the memory for this star.
A_MEMORY
If set, memory is allocated for this state.
A_NOINIT
If set, the state is not be automatically initialized. The default is that all states that occupy memory are initialized to their default values.
A_REVERSE
If set, write out the values for this state in reverse order.
A_SYMMETRIC
If set, and if the target has dual data memory banks (e.g. M56000, Analog Devices 2100, etc.), allocate a buffer for this object in both memories.
Given these attributes (technically, the above also have "bit" representations of the form AB_xxx; A_xxx just turns the bit AB_xxx on), the following attributes correspond to requests to turn some attributes off and to turn other attributes on. For example:

A_ROM
Allocate memory for this state in memory, and the value will not change -- A_MEMORY and A_CONSTANT set.
A_RAM
A_MEMORY set, A_CONST not set
For portholes in code generation stars, we have:

P_CIRC
If set, then allocate the buffer for this porthole as a circular buffer, even if this is not required because of any other consideration.
P_SHARED
Equivalent to A_SHARED, only for portholes.
P_SYMMETRIC
Similar to A_SYMMETRIC, but for portholes.
P_NOINIT
Do not initialize this porthole.
Attributes can be combined with the "|" operator. For example, to allocate memory for a state but make it non-settable by the user, I can say

AB_MEMORY|A_NONSETTABLE

13.2.7 Possibilities for effective buffering

In principle, blocks communicate with each other through porthole connections. In code generation domains, we allocate a buffer for each input-output connection by default. There are some stars, however, that do not modify data at all. A good, and also ubiquitous, example is a Fork star. When a Fork star has N outputs, the default behavior is to create N buffers for output connections and copy data from input buffer to N output buffers, which is a very expensive and silly approach. Therefore, we pay special attention to stars displaying this type of behavior. In the setup method of these stars, the forkInit() method is invoked to indicate that the star is a Fork-type star. For example, the CGCFork star is defined as


defstar {
name { Fork }
domain { CGC }
desc { Copy input to all outputs }
version { @(#)CGCFork.pl 1.6 11/11/92 }
author { E. A. Lee }
copyright { 1991-1994 The Regents of the University of California }
location { CGC demo library }
explanation {
Each input is copied to every output. This is done by the way the buffers are laid out; no code is required.
}
input {
name {input}
type {ANYTYPE}
}
outmulti {
name {output}
type {=input}
}
constructor {
noInternalState();
}
start {
forkInit(input,output);
}
exectime { return 0;}
}
Where possible, code generation domains take advantage of Fork-type stars by not allocating output buffers, but instead the stars reuse the input buffers. Unfortunately, in the current implementation, assembly language fork stars can not do their magic if the buffer size gets too large (specifically, if the size of the buffer that must be allocated is greater than the total number of tokens generated or read by some port during the entire execution of the schedule). Here, forks or delay stars that copy inputs to outputs must be used.

Another example of a Fork-Type star is the Spread star. The star receives N tokens and spreads them to more than one destination. Thus, each output buffer may share a subset of its input buffer. We call this relationship embedding: the outputs are embedded in the input. For example, in the CGCSpread star:


setup {
MPHIter iter(output);
CGCPortHole* p;
int loc = 0;
while ((p = (CGCPortHole*) iter++) != 0) {
input.embed(*p, loc);
loc += p->numXfer();
}
}
Notice that the output is a multi-porthole. During setup, we express how each output is embedded in the input starting at location loc. At the buffer allocation stage, we do not allocate buffers for the outputs, but instead reuse the input buffer for all outputs. This feature, however, has not yet been implemented in the assembly language generation domains.

A Collect star embeds its inputs in its output buffer:


setup {
MPHIter iter(input);
CGCPortHole* p;
int loc = 0;
while ((p = (CGCPortHole*) iter++) != 0) {
output.embed(*p, loc);
loc += p->numXfer();
}
}
Other examples of embedded relationships are UpSample and DownSample stars. One restriction of embedding, however, is that the embedded buffer must be static. Automatic insertion of Spread and Collect stars in multi-processor targets (refer to the target section) guarantees static buffering. If there is no delay (i.e., no initial token) in the embedded buffer, static buffering is enforced by default. A buffer is called static when a star instance consumes or produces data in the same buffer location in any schedule period. Static buffering requires a size that divides the least common multiple of the number of tokens consumed and produced; if such a size exists that equals or exceeds the maximum number of data values that will ever be in the buffer, static allocation is performed.



Top Up Prev Next Bottom Contents Index Search

Copyright © 1990-1997, University of California. All rights reserved.