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

2.0 Code Generation with Ptolemy

2.2 Targets


In Ptolemy, a Target class defines those features of an architecture pertinent to code generation. Each domain, which synthesizes a specific language such as C or Motorola 56000 assembly, has a simple target that will generate code and optionally compile or assemble the code. More elaborate Target definitions are derived from these. The more elaborate targets generate and run code on specific hardware platforms or on simulated hardware. Some examples that have been implemented are an S-56X*1 target and the CM5 from Thinking Machines. The latter is an example of a multiprocessor C language target. To define multiprocessor targets, the concept of Parent-Child target relationships is used. For example, the CM5 target contains an arbitrary number of C child targets. For our specific configuration of the CM5 at Berkeley, there are 128 child targets. In this paper we will focus on single-processor targets.

For any given code generation galaxy, a Target must be specified. The Target defines how the generated code will be collected, specifies and allocates resources such as memory, and defines code necessary for proper initialization of the platform. The Target will also specify how to compile and run the generated code. Optionally, it may also define wormholes (covered in section 2.5).

The derivation tree for all currently defined single-processor targets is shown in figure 8.

At the top of the tree is the generic code generation target (CG). All code common to all code generation targets resides in the CG target. Methods defined here include virtual methods*2 to generate, display, compile and run the code, and a method to call these methods based on target or user specified parameters. The Assembly language target adds methods for the allocation of physical memory and interrupt handling. The higher level language target (HLL) contains methods to define and initialize variables, arrays, and include files.

The object-oriented design of Ptolemy code generation makes target specification easy. For a typical target, the target writer must overload the compileCode() and runCode() methods. If the target is an assembly language target, the writer must also specify the memory. Multiple inheritance*3 can also be used to define similar targets. For example, as is shown in figure 1, both of the Motorola simulator targets are derived from a common Motorola simulator target for either the Sim56 or Sim96 target.

The base target for all code generation domains is the CGTarget, which represents a single processor by default. As the generic code generation target, the CGTarget class defines many common functions for code generation targets. Methods defined here include virtual methods to generate, display, compile, and run the code. Derived targets are free to redefine these virtual methods if necessary.

2.2.1 Code Streams

A code generation target manages code streams which are used to store star and target generated code. The CGTarget class has the two predefined code streams: myCode and procedures. Derived targets are free to add more code streams using the CGTarget method addStream(stream-name). For example, the default CGC target defines six additional code streams.

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

Other methods, such as addProcedure(code,<unique name>) can be defined, to provide a more efficient or convenient interfaces to a specific code stream (in this case, procedures). With addProcedure() it becomes clear why unique names are necessary. Recall that addProcedure() is used to declare outside of the main body of the code. For example, say we wanted to write a function in C to multiply two numbers. The codeblock to do this could read:

codeblock(sillyMultiply){
	/* A silly function */
	double $sharedSymbol(silly,mult)(double a, double b){
		double m;
				m = a*b;
				return m;	
		}
}
Note that in this codeblock we used the $sharedSymbol macro described in the
section 2.3.1 on page 20. To add this code to the procedures stream, in the initCode() method of the star, we can call one of the following:

	addProcedure(sillyMultiply,"mult");
	addCode(sillyMultiply,"procedures","mult"); 
	getStream("procedures")->put(sillyMultiply,"mult"); 
As with addCode(), addProcedure() returns a TRUE or FALSE indicating whether the code was inserted into the code stream. Taking this into account, we could have added the code line by line:

if(addProcedure("/* A silly function */\n","mult")){
	addProcedure("double $sharedSymbol(silly,mult)(double a, double b)\n");
	addProcedure("{\n");
	addProcedure("\tdouble m;\n");
	addProcedure("\tm = a*b;\n");
	addProcedure("\treturn m;\n");
	addProcedure("}\n");
}

2.2.2 Target Code Generation Methods

Once the program graph is scheduled, the target generates the code in the virtual method generateCode(). (Note: code streams should be initialized before this method is called.) All the methods called by generateCode() are virtual, thus allowing for target customization. The generateCode() method then calls allocateMemory() which allocates the target resources. After resources are allocated, the initCode() method of the stars are called by codeGenInit(). The next step is to form the main loop by calling the method mainLoopCode(). The number of iteration cycles are determined by the argument of the "run" directive which a user specifies in pigi or in ptcl. To complete the body of the main loop, go() methods of stars are called in the scheduled order. After forming the main loop, the wrapup() methods of stars are called.

Now, all of the code has been generated; however, the code can be in multiple target streams. The frameCode() method is then called to piece the code streams and place its resultant into the myCode stream. Finally, the code is written to a file by the method writeCode(). The default file name is "code.output", and that file will be located in the directory specified by a target parameter, destDirectory.

Finally, since all of the code has been generated for a target, we are ready to compile, load, and execute the code. Derived targets should redefine the virtual methods compileCode(), loadCode(), and runCode() to do these operations. At times it does not make sense to have separate loadCode() and runCode() methods, and in these cases, these operations should be collapsed into the runCode() method.

2.2.3 Target Wormhole Methods

CGTarget defines virtual methods necessary to support wormholes have to support wormholes, a target should redefine the virtual methods, sendWormData(), receiveWormData(), wormInputCode(), and wormOutputCode(). The sendWormData() method sends data from the Ptolemy host to the target architecture. The wormInputCode() method is in charge of defining the code in the target language to read in the data from the Ptolemy host. The methods receiveWormData() and wormOutputCode() are similar except that they correspond to data moving in the opposite direction. Further wormhole discussion is deferred until section 2.5 on page 26.


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

[Next]