2.0 Code Generation with Ptolemy
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.
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:
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.
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"); }
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.