Creating Blocks for C6Flo 1.0
From Texas Instruments Embedded Processors Wiki
This article is part of a series describing the C6Flo graphical development tool. Click the link above to return to the main page.
Contents |
Introduction
This page explains how to create blocks for C6Flo, a graphical development tool for C6000 DSPs. The block creation process consists of five basic steps:
- Create a new folder for your block
- Create JavaScript package and module files that describe your block to C6Flo
- Write C-language source template files that manifest your block in generated application code
- Build your block's package and module using the XDC command line tool
- (Experimental release only) Create an XML and ZIP file so your block will work with legacy GUI components
Steps 2, 3, and 5 can be completed in any order, but certain parts of each must agree with each other. For instance, to define and use a user-controlled parameter, such as a filter tap, that parameter must be defined and used consistently in the JavaScript module, C source templates, and XML file.
The XML and ZIP files in step #5 must be created using the Component Publisher utility that comes with early releases of C6Flo. These files are only required by certain legacy components of the C6Flo GUI and will deprecated as that GUI is updated in subsequent releases (target date: Q4 2010).
Creating a New Folder
The first step to create a new block is to create a folder to contain it. This folder should be placed in the Blocks folder within the C6Flo installation. A typical block uses the following folder location:
<C6Flo Installation>/Blocks/ti/c6flo/<category>/<name>
For instance, the math.gain block uses math and gain for category and name, respectively. The folder's location will ultimately determine how the block is listed in the component publisher.
Note: Blocks not made by TI can use their own vendor name to further modify the suggested folder path.
Note: The block palette category is also explicitly set in the legacy XML file for preliminary releases of C6Flo.
Creating the JavaScript Package and Module Files
Once the (empty) block folder has been created, you must create three simple JavaScript files to define a package and module for your block. Each block has its own package and module definition. The package consists of two files, package.xdc and package.bld, while the module consists of only one: <Name>.xdc. By convention, the module file is named using CamelCase (as in Gain.xdc or FirFilter.xdc).
The package file contents are very simple. In practice, package.bld is the same for all blocks and package.xdc contains only two items: the path to the block's folder (relative to Blocks) and the name of the block's JavaScript module. The following examples show the contents of each file for the math.gain block that comes with C6Flo.
| package.xdc |
|---|
package ti.c6flo.math.gain { module Gain; } |
| package.bld |
|---|
Pkg.attrs.exportAll = true; |
Defining your block's module is more involved. The file must be created as <Module>.xdc, matching the module name given on line 3 of package.xdc. The following shows the contents of Gain.xdc, from the math.gain block. We will explain each element of this file below.
| Gain.xdc |
|---|
/*! * ======== Gain ======== * Scale buffer samples by specified gain */ metaonly module Gain inherits ti.c6flo.IBlock { override config int allowed_inputs[] = [1]; override config int allowed_outputs[] = [1]; override config String critical_params_proc[] = ["c6flo_input_type"]; instance: /*! * ======== gain ======== * Gain factor */ config float gain = 1.0; } |
There are several elements that can appear in this file:
- Module definition with inheritance (line 5)
- Override module parameters (lines 7-10)
- Override instance parameters (none in this example)
- New instance parameters (lines 13-19)
- New or override instance functions (none in this example)
The module definition occupies line 5 of our example file; note that the module name must be the same as in package.xdc. Note also that the block inherits from the interface called IBlock. This is basically a parent module that contains common parameters that all blocks must have. The interface has its own module definition file located at Blocks/ti/c6flo/IBlock.xdc.
The remaining contents of the Module.xdc file will be described in the following sub-sections.
Override Module Parameters
You can override IBlock parameters in your own module file if you want to use different default values for your block. This is done (basically) by copying a relevant line from IBlock.xdc, adding the keyword override to the beginning of the line, and assigning a different value. This is typically used to change how blocks are treated by the C6Flo GUI. For instance, changing the variable allowed_inputs controls how many inputs the user can select for this block within their block diagram.
Module-level parameter overrides must be placed above the instance: label within the module file.
New Module Parameters
Blocks should not define new module-level parameters. Only standard parameters will be recognized by the C6Flo GUI. All user-configurable parameters are applied on a per-instance basis, and thus are defined as instance-level parameters.
Override Instance Parameters
Any of the instance parameters defined in IBlock.xdc can be overridden. The process is similar to module-level parameters, but instance-level overrides must be placed below the instance: label within the module file.
New Instance Parameters
Many blocks allow the user to configure instance parameters in the GUI. For this to work, those parameters must be listed as instance-level parameters in the module file. These definitions must be placed below the instance: label within the module file. In the above example, we define a single parameter, gain, as a floating point number.
Instance Functions (advanced)
It is possible (but absolutely not required) to create or override instance functions in the JavaScript domain. Functions declared in this way must be defined in a separate file, <Module>.xs, which is placed in same folder as <Module>.xdc. These functions can then be called from the block's C source template files as this.<function name>(<args>). This is an advanced feature that is not used by most blocks.
These functions exist only in the JavaScript domain. They have no relation to the standard C functions implemented by each block.
Writing the Source Template Files
Ultimately, your block will be used to generate part of the C-language source code for any C6flo system that uses it. For this to happen, you must provide six (6) source template files. These files are summarized in the following table.
| File name | Description |
|---|---|
| typedef.ht | Defines the format of instance structs for the block; placed in application header file |
| struct.ct | Lays out the instance struct for a block instance; placed in application _blocks.c source file |
| create.ct | Implements the standard create function; placed in application _blocks.c source file |
| init.ct | Implements the standard initialization function; placed in application _blocks.c source file |
| proc.ct | Implements the standard processing function; placed in application _blocks.c source file |
| ctrl.ct | Implements the standard control function; placed in application _blocks.c source file |
During code generation, C6Flo applies a sophisticated JavaScript-based template engine to each of these files to generate pure C-language code fragments. The template engine can be used extensively to adapt blocks to efficiently account for changing parameters, or it can be used sparingly to create very simple source template files. The following rules summarize the capabilities of the template engine.
- By default, text is copied as-is from the template file into the generated code
- Lines beginning with % are interpreted as JavaScript and are not copied to the generated code
- Lines between JavaScript control braces are conditionally copied to the generated code (see example below)
- Lines between %%{ and %%} are interpreted as JavaScript and are not copied to the generated code
- Elements within `back apostrophes` are evaluated as JavaScript and are copied to the generated code
- `Back apostrophes` can only be used in a line that is not already interpreted as JavaScript
The following example shows how these various rules may be used in a template file.
| Template file contents |
|---|
% // this is some JavaScript code % var my_string = "// hello world"; % var display_string = true; % if (display_string) { `my_string` % } else { // (didn't display my_string) % } %%{ // more JavaScript // ... %%} |
| Generated code contents |
|---|
// hello world |
Four Standard Functions
One of our primary goals when creating a block is to implement the four standard functions required by C6Flo: create, initialize, process, and control. This section explains the purpose of each function and describes its arguments and return values. Each function has a standard prototype that is automatically generated by C6Flo. This prototype is inserted in your source template using this.c6flo_cg_<name>_fxn_prototype, where name is an abbreviated name for the function in question: create, init, proc, or ctrl. You should not attempt to type in the function prototype manually.
Note: Always use `this.c6flo_cg_<name>_fxn_prototype` to insert the function prototype in your templates.
All standard functions should return their status using the standard values defined in the following table.
| Status Macro | Description |
|---|---|
| C6Flo_EOK | Status OK; continue normal thread operation |
| C6Flo_ERESET | Reset thread; call initialize functions again before next processing loop |
| C6Flo_EGENERIC | Generic error condition; abort thread |
| C6Flo_EID | ID error condition; abort thread |
| C6Flo_ERANGE | Range error condition; abort thread |
| C6Flo_EALLOC | Allocation error condition; abort thread |
Create
| Prototype |
|---|
int ti_c6flo_<name>_v1_create(ti_c6flo_<name>_v1_hdl blockp); |
- This function is called only once when the thread begins
- All dynamic allocation (buffer allocation, driver handles, etc.) must be done in this function
- All "one time only" configuration or setup must be done in this function
- Buffer allocation should use the C6Flo_MEM API instead of malloc or the DSP/BIOS MEM API
- If this function returns an error condition, the thread will never run its normal processing loop
Initialize
| Prototype |
|---|
int ti_c6flo_<name>_v1_init(ti_c6flo_<name>_v1_hdl blockp); |
- This function is called once when the thread begins and again whenever the thread is reset
- No dynamic allocation can be done in this function
- Any re-doable initialization for the first time through the processing loop should be done in this function
- If this function returns an error condition, the thread will abort before the next (first?) run through its normal processing loop
Process
| Prototype |
|---|
int ti_c6flo_<name>_v1_proc(ti_c6flo_<name>_v1_hdl blockp, C6Flo_buf_hdl *inputs, C6Flo_buf_hdl *outputs); |
- This function is called repeatedly as the thread runs its normal processing loop
- The input and output buffers are passed via the arrays inputs and outputs
- Buffers and buffer sizes may be get/set by the standard macros (see below)
- Processing is always in place (inputs[0] = outputs[0], inputs[1] = outputs[1], etc.)
- If your algorithm requires separate input/output buffers, you will need to allocate a private buffer in the create function and swap buffers in process using the C6Flo_set_buf_ptr macro
- If this function returns an error value, the thread will abort after the current pass through the processing loop
| Macro | Description |
|---|---|
| C6Flo_get_buf_size(a) | Gets size (in bytes) of buffer a (ex. a = inputs[0]) |
| C6Flo_set_buf_size(a, b) | Sets size of buffer a to value b |
| C6Flo_get_buf_length(a) | Gets length (in elements) of buffer a |
| C6Flo_set_buf_length(a, b) | Sets length of buffer a to value b |
| C6Flo_get_buf_ptr(a) | Gets buffer data pointer of buffer a (return as void *) |
| C6Flo_set_buf_ptr(a, b) | Sets buffer data pointer of buffer a to value b |
Control
| Prototype |
|---|
int ti_c6flo_<name>_v1_ctrl(ti_c6flo_<name>_v1_hdl blockp, C6Flo_HOST_msg_hdl incoming_msg, C6Flo_HOST_msg_hdl outgoing_msg); |
- This thread is only called in response to a host control event (and only if the block is explicitly enabled for host control in the C6Flo diagram view)
- Typically, a block will expect a specific number of arguments and use those to set/update certain key parameters at run time
- If a block perceives that its incoming message is malformed (wrong number of arguments, invalid values, etc.), it should return an OK status value and indicate the problem to the host via the outgoing message
- If this function returns an error condition, the host processing thread will abort. Generally, this should be avoided
| Message struct definition |
|---|
typedef struct { Uint16 block_id; Uint16 arg_count; void *arg_buffer; } C6Flo_HOST_msg_obj, *C6Flo_HOST_msg_hdl; |
Accessing Block Parameters
Block instance parameters are accessible in the source template files. This includes standard instance parameters defined in IBlock.xdc as well as "extra" instance parameters defined in <Block>.xdc. It does not include module-level parameters defined above the instance: label in IBlock.xdc.
The block instance is passed to the template file as this during code generation. Thus, to access a parameter, simply use this.<parameter name> in a JavaScript context:
| Two methods to access block instance parameters |
|---|
% var input_count = this.c6flo_input_count; float my_gain = `this.gain`; |
In general, you should use parameters directly in struct.ct. Parameters that are used directly in a function are called critical parameters for that function. If a function has no critical parameters, it is only generated once and can be shared by any number of block instances. Critical parameters can thus increase code size in the generated application, and should only exist when they provide a clear benefit in terms of performance or code structure.
Critical Parameters
An instance parameter is called a critical parameter of a particular function if it appears directly in the source template for that function. In general, instance parameters are only used in the struct template. This allows function code to be re-used between different instances of the same block, reducing code size and resulting in cleaner application code. Critical parameters allow you to change how a function looks depending on parameters that the use sets when designing their system. This is a trade off that allows greater flexibility and (sometimes) more elegant code at the cost of code size. For example, this is the processing function template for the math.sum block:
| math/sum/proc.ct |
|---|
`this.c6flo_cg_proc_fxn_prototype` { % var type_string = this.get_input_type_string(); `type_string` *x[`this.c6flo_input_count`], *y; int count, i; // get buffer count (NOTE: assume all buffers same size) count = C6Flo_get_buf_length(outputs[0]); // get buffer data pointers for (i = 0; i < `this.c6flo_input_count`; i++) x[i] = C6Flo_get_buf_ptr(inputs[i]); y = C6Flo_get_buf_ptr(outputs[0]); // calculate sum of inputs for (i = 0; i < count; i++) % var x_string = "x[0][i] + x[1][i]"; % for (var k = 2; k < this.c6flo_input_count; k++) % x_string += " + x[" + k + "][i]"; y[i] = `x_string`; return C6Flo_EOK; } |
There are two critical parameters in this function, c6flo_input_type and c6flo_input_count. The input type parameter is used to cast the input and output data pointers to the appropriate C datatype. (The helper function, get_input_type_string(), is a JavaScript-domain function implemented in IBlock.xs that uses c6flo_input_type for this purpose.) This allows the generated code to cleanly add the input signals using the appropriate addition opcodes.
The input count parameter is used to produce a cleaner summation expression and maximize efficiency. The alternative here is to use a more complicated structure in the source template, as in the following counter-example:
| math/sum/proc.ct (alternate) |
|---|
if (blockp->std.input_count == 2) for (i = 0; i < count; i++) y[i] = x[0][i] + x[1][i]; else if (blockp->std.input_count == 3) for (i = 0; i < count; i++) y[i] = x[0][i] + x[1][i] + x[2][i]; else if (blockp->std.input_count == 4) // etc |
Clearly the original template is an improvement over this messy implementation. However, using an instance parameter directly in a function is not always the right thing to do. This is the processing function template for the math.gain block.
| math/gain/proc.ct |
|---|
`this.c6flo_cg_proc_fxn_prototype` { % var type_string = this.get_input_type_string(); `type_string` *data; int count, i; // get element count and data pointer count = C6Flo_get_buf_length(inputs[0]); data = C6Flo_get_buf_ptr(inputs[0]); // apply gain (in place) for (i = 0; i < count; i++) data[i] = (`type_string`)(data[i] * blockp->gain); return C6Flo_EOK; } |
Note that, while data type is again used as a critical parameter, the gain parameter is included as blockp->gain. This is because creating multiple nearly-identical copies of the function for different gain values is inefficient and would generate sloppy, repetitive code. Wherever possible, instance parameters should be left in the instance struct and not used directly in functions.
The names of critical parameters must be listed in the module-level parameter critical_params_<name>[], where name is the abbreviated name of the function. For instance, note line 8 of Sum.xdc:
| math/sum/Sum.xdc | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
metaonly module Sum inherits ti.c6flo.IBlock { override config int allowed_inputs[] = [2, 3, 4, 5, 6, 7, 8]; override config int allowed_outputs[] = [1]; override config Bool is_symmetric = false; override config String critical_params_proc[] = ["c6flo_input_count", "c6flo_input_type"]; instance: } Note that this function has critical parameters only for the processing function. By default, each function has no critical parameters. Using Helper Functions in the JavaScript DomainThe IBlock interface provides utility functions in the JavaScript domain to perform common template tasks. The following table lists these functions.
Building the Block PackageOnce all of the package and module files have been created, you are ready to build your block's JavaScript package. This is a simple matter of using the xdc command line tool. Navigate to your block's folder in a terminal window and use the following command: xdc all Note that there are a few simple prerequisites here:
Also, if you want to distribute your block to other people, you may want to use the following syntax: xdc release This will build the package and place all of the relevant files into a ZIP file that can be easily shared. Creating the XML File (temporary)While C6Flo will ultimately parse blocks' module files to understand each block's parameters, that functionality does not exist in the preliminary GUI used in early releases of the C6Flo software. For blocks to work with this GUI, they must also provide an XML file that it can understand. The contents of this XML file are much more complicated than the other files we've created, but they can be generated by the legacy ComponentPublisher tool instead of being written by hand. While this tool is somewhat complicated and initially confusing, you don't need to worry about most of its tabs and controls. Only two tabs are relevant when creating blocks for C6Flo: Main and Design Properties. Main TabThe Main tab contains several fields that determine how the block appears in the palette and block diagram editor within the legacy GUI. The following table lists these fields and how they should be set.
This screenshot shows the settings used for the math.gain block. Design Properties TabThe Design Properties tab is used to list the block's parameters in a way that ther preliminary GUI can understand. Since this GUI was (mostly) written before the other pieces of C6Flo, there are several parameters that must be defined in this tab. The following table lists these required parameters.
In addition to these required values, and extra parameters used by your block should be defined on this tab. These definitions should use the same name, data type, and default value that they use in your JavaScript module and source template files. The following screenshot shows the Design Properties tab for the math.gain block. Note that the gain parameter is defined on this tab as a floating point value that defaults to 1.0. Saving and PublishingWhen you have finished setting all of the relevant fields in the Main and Design Properties tabs, you are ready to save and publish the XML file. First, use the File->Save XML command to save your XML file. The XML file should be saved in the same folder as your block's other files. Next, you must publish your XML file to a special folder used by the preliminary GUI. Click the Publish button in the lower right corner of the ComponentPublisher, and select the <C6Flo Installation>/ComponentLibrary folder. Afterward, you will find a ZIP file containing your XML file (and any icon files it uses) in that location. Miscellaneous Tips
See Also |
Comments
Comments on Creating Blocks for C6Flo


May want to remove References to PurePath Studio in the screen captures in order to make things less confusing.
--Drew 16:13, 9 November 2010 (CST)