[Next] [Previous] [Up] [Top]

2.0 Code Generation with Ptolemy

2.3 Stars


Ptolemy has two basic types of stars: simulation stars and code generation stars. For purposes of this paper, discussion will be limited to code generation stars.

The derivation tree for all currently defined abstract star classes is shown in figure 5. By an abstract star class, we mean that the classes are never used to generate target language code directly. Instead, these classes define macro function expansion and functional interfaces to target specified code streams. The leaf nodes*1 of the tree are used as parents for user definable code generation stars. All methods that are common to all code generation stars reside in base code generation star class (CGStar). Similarly, all code common to assembly code generation stars is found in the assembly language star (AsmStar), and all code common to higher level languages is defined in HLLStar.

Of special interest is the class AnyAsmStar. Stars derived from AnyAsmStar can be utilized in any assembly code generation domain. These stars do not produce code; their purpose is to manipulate the input and/or output buffers connected to these stars. Currently, there are two AnyAsmStars: BlackHole and Fork. A BlackHole star is a data sink that discards its input data. Other code generation stars can check if any of their outputs are connected to a BlackHole, and then conditionally generate code based on this fact. Also, all input buffers to BlackHoles are mapped into one single memory location, so even if stars do not check to see if a BlackHole is connected to one of its outputs, minimal buffer memory is utilized. The other type of AnyAsmStar that exists is the Fork star. A Fork star splits the data path into two or more paths; however, all data paths can share a single buffer. A series of connected Fork stars with interspersed delays can be collapsed and maintained at the output buffer where the first F For each of the leaf nodes in figure 5,

there exist predefined star libraries. However, for most users' needs, these libraries will be insufficient. As a result, special attention has been given to make star writing in Ptolemy, like Gabriel, easy and systematic [22]. Unlike Gabriel and other code generators previously mentioned, Ptolemy is object oriented, thus allowing users to easily re-use code. For example, the C code generation domain has the family of stars fixed lattice filter, adaptive lattice filter, and a vocoder. Here the vocoder star was derived (in the sense of C++ derived classes) from the adaptive lattice filter, in turn derived from the fixed lattice. Karjalainen in [23] states that object oriented programming environments are well suited for DSP programming methodology.

A typical user-defined code generation star will consist of portholes, states, codeblocks, a setup() method, an initCode() method, a go() method, a wrapup() method, and an execTime() method. Portholes, states and codeblocks are all data members of a star. Portholes specify the inputs and outputs of the star and their types. States define user settable parameters or internal memory states required in the generated code. Codeblocks are a pseudo code specification of the target language. By pseudo code, we mean that the codeblock is made up of the target language and star macro functions. These macro functions can be defined at any level of the inheritance tree. Macro functions include parameter value substitution, unique symbol generation with multiple scopes, and state reference substitution.

Setup(), initCode(), go(), wrapup(), and execTime() make up the virtual methods of a star. Users are free to write additional methods that are called from one of five methods listed. The differentiating trait between setup(), initCode(), go(), and wrapup() methods is when the method is called. The setup() method is called before the schedule is generated and before any memory is allocated. It is responsible for setting up information that will affect scheduling and memory allocation, such as the number of values that are read from a particular porthole or the size of an array state. 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 initCode() method is called before the schedule is generated and after the memory is allocated; code generated by initCode() appears before the main loop.

The next method to be called is the go() method. This method is called directly from the scheduler. Hence the code generated in the go() method makes up the main loop code. Finally, the wrapup() method is called after the schedule has been completed, allowing the star to place code after the main loop code. For example, a typical use of this method in assembly code generation would be to define subroutines after the main loop code. The final virtual method that star writers may overload is execTime(). This method returns a number that indicates the approximate time to complete one firing of the star. This information is essential for the parallel schedulers.The better the execTime() estimates are for each star, the more efficient the parallel schedule becomes.

Stars are typically written not in C++ directly, but rather for a preprocessor called ptlang. This preprocessor generates the "standard boilerplate" necessary to properly initialize states and portholes, create codeblocks in a more natural manner, and to register the star with the system so that instances of it may be created by specifying the class name. It also generates documentation for the star.

2.3.1 Generic Code Generation 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.

$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 tables 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 the discussion on code streams in section 2.2.1. An example where a shared symbol is used is in CGCPCM star is shown in figure 6.

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 codeblock 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.

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.

2.3.2 Assembly Code Generation Macros

Here we will present the additional and redefined macros available that have special meaning in assembly language code generation:

$addr(name,<offset>) This macro returns the numeric address in memory of the named object, without anything like (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 independently 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.


Software Synthesis for Single-Processor DSP Systems Using Ptolemy - 04 SEP 94

[Next]