Complex
numbers are commonly encountered in signal processing, so it's no surprise that
C6Flo includes many blocks that accept complex inputs and emit complex outputs.
Typically, blocks that handle complex values will allow you to choose between
one or two ports for a complex value. When using one port, the complex data is
interleaved (as real, imaginary, real, imaginary, ...) inside a single data
buffer:
<![if !vml]><![endif]>
Alternatively,
complex data can be split across two data buffers. In this case, one buffer
contains real data while the second contains imaginary data.
<![if !vml]><![endif]>
The
above assumes that complex values are expressed in Cartesian form (also called
rectangular or x, y coordinates). Some blocks may also use complex values in
polar form. In this case, the complex values are still represented as two
numbers: radius and angle. The radius value replaces the real value, above, and
likewise the angle value replaces the imaginary value. These values can be
interleaved inside a single buffer or split into two buffers just as before.
Custom Blocks in C6Flo
While C6Flo includes over 100 blocks in the
standard set, those blocks can't satisfy the requirements of every developer
and application space. For this reason, it's easy to create your own custom
blocks and add them to C6Flo. Custom blocks appear on the standard palette and
behave like standard blocks in every way. This document describes the necessary
steps to create and use your own custom blocks.
<![if !vml]><![endif]>
|
Custom block on the standard C6Flo palette
|
- Using
the Dummy Block
- Components of a C6Flo Block
- Creating the Block Module
- Understanding Source Code Templates
- Using Custom Blocks
- Advanced: Standard Module Parameters
- Advanced: Four Standard Functions
- Advanced: Critical Parameters
Creating a new block is not the only to insert
custom algorithm code into a C6Flo-generated application. For simple
situations, or as a quick trial, you can also use the dummy block. The dummy
block is a block that does no processing of any kind and can be configured to
have any number of input and output connections. When you generate code, the
dummy block inserts a placeholder function into your application's processing
loop. You can then insert your own C code into that function to perform custom
processing. This is the simplest way to insert your own algorithm code into a
generated DSP application.
<![if !vml]><![endif]>
|
Simple system using the dummy block to perform
custom audio processing
|
After generating code, you will find the
placeholder function in the file <app name>_blocks.c. It will
have a name like ti_c6flo_misc_dummy_v1_proc. This file
does no processing but includes helpful comments to make it easy to insert your
own custom processing.
int
ti_c6flo_misc_dummy_v1_proc(ti_c6flo_misc_dummy_v1_hdl blockp, C6Flo_buf_hdl
*inputs, C6Flo_buf_hdl *outputs)
{
// arg_1 = 1
// arg_2 = 100
// To access data buffers:
// void *ptr = C6Flo_get_buf_ptr(inputs[0]);
// int size_bytes = C6Flo_get_buf_size(inputs[0]);
// int length = C6Flo_get_buf_length(inputs[0]);
//
// Remember: all processing is done in-place
// inputs[0] == outputs[0]
// inputs[1] == outputs[1]
// etc.
return C6Flo_EOK;
}
The custom block contains only two parameters, arg_1 and arg_2. These
parameters normally have no effect on processing. Instead, they allow you to
insert multiple, distinct custom algorithms into your application code. If your
system includes multiple dummy blocks, by default all of these blocks will call
the same placeholder function. However, dummy blocks with different argument
values will generate different versions of the placeholder function. This is an
example of the critical parameter feature,
which is explained in a later section. Basically, this allows you to generate
distinct processing functions so that you can perform more than one custom
algorithm using dummy blocks. For example, the generated code may look
something like this:
int
ti_c6flo_misc_dummy_v1_proc_f0(ti_c6flo_misc_dummy_v1_hdl blockp, C6Flo_buf_hdl
*inputs, C6Flo_buf_hdl *outputs)
{
// arg_1 = 1
// arg_2 = 100
// ...
}
int
ti_c6flo_misc_dummy_v1_proc_f1(ti_c6flo_misc_dummy_v1_hdl blockp, C6Flo_buf_hdl
*inputs, C6Flo_buf_hdl *outputs)
{
// arg_1 = 2
// arg_2 = 100
// ...
}
Note that, in this case, we have generated two
placeholder functions for dummy blocks with arg_1 equal to one
and two, respectively. The included comments make it easy to determine which
function corresponds to which dummy block.
The dummy block is useful for quickly inserting
custom algorithms into C6Flo-generated applications, but it is labor intensive
to repeat the process in every system that uses a particularly useful piece of
custom code. To make your work reusable across multiple C6Flo systems, you may
want to create your own custom block. Before you get started, it's important to
understand how C6Flo parses blocks and adds them to its palette.
The first step is to find the C6Flo blocks
repository. This is the folder that contains all of the block
files that are used by C6Flo. To find it, navigate to the CCS installation
folder on your PC. On a Windows PC, the default installation path is C:/Program
Files/Texas Instruments/ccsv5. Once you have located
your CCS installation, look for a subfolder similar to ccs_base_x.x.x.xxxxx/c6flo_x.x.x.yyyymmddxxxx. If you see
more than one subfolder that matches this form, use the path with the highest
version/timestamp numbers. Inside, you should find another subfolder: Blocks. This folder
is the blocks repository.
The blocks repository contains all of the standard
blocks that appear on the C6Flo palette. Let's take a look at the dummy block
to see what files are required by C6Flo. Inside the blocks repository, navigate
to ti/c6flo/misc/dummy. This folder
contains all of the files required by the dummy block to function normally
inside C6Flo. Don't be overwhelmed by what you see; most of the contents are
generated files. The following table summarizes the required (and optional)
seed files that are used to generate the rest.
Name
|
Type
|
Description
|
<Block Name>.xdc
|
JS
|
JavaScript module definition
|
package.xdc
|
JS
|
JavaScript module declaration
|
package.bld
|
JS
|
JavaScript module build properties (don't modify)
|
config.bld
|
JS
|
JavaScript module build configuration (don't
modify)
|
typedef.ht
|
C
|
Source code template for block typedef
|
struct.ct
|
C
|
Source code template for block struct
|
create.ct
|
C
|
Source code template for block create function
|
init.ct
|
C
|
Source code template for block initialize
function
|
proc.ct
|
C
|
Source code template for block process function
|
ctrl.ct
|
C
|
Source code template for block control function
|
<Icon>.png
|
PNG
|
Small icon graphic used in block palette
(optional)
|
<Icon>_big.png
|
PNG
|
Large icon graphic used in system diagram
(optional)
|
The files listed above are sorted into three
categories: JavaScript module files, source code
template files, and icon files. The JavaScript and template
files will receive detailed explanations in subsequent sections of this page.
To get started creating your own custom block, simply copy all of these files
(including the icon files) from the dummy block folder. Create a new folder to
hold your new block (for example, ti/c6flo/misc/newblock) and paste
the files into that folder.
C6Flo parses a JavaScript module to understand each
block and place it on the palette. A block's module is responsible for defining
all of its properties, including standard items such as the allowed the number
of input and output connections as well as algorithm-specific parameters such
as filter taps.
The JavaScript module for each block is declared
and defined in its package.xdc and <Block Name>.xdc files,
respectively. For example, here are the contents of those files for the dummy
block:
package ti.c6flo.misc.dummy
{
module Dummy;
}
metaonly module Dummy inherits ti.c6flo.IBlock
{
override config int allowed_inputs[] = [0, 1, 2, 3, 4, 5, 6, 7, 8];
override config int allowed_outputs[] = [0, 1, 2, 3, 4, 5, 6, 7, 8];
override config Bool is_symmetric = false;
override config Bool is_homogeneous = false;
override config String critical_params_proc[] = ["arg_1",
"arg_2"];
override config String palette_category = "Custom";
instance:
config float arg_1 = 1.0;
config int arg_2 = 100;
}
Consider the declaration file, package.xdc. This file
basically serves to tell C6Flo that this folder contains a JavaScript module.
The package definition
reflects the block's folder path relative to the Blocks repository: ti.c6flo.misc.<Block Name>. This path
is case sensitive, and by convention the folder name is all lowercase. For our
new block in the ti/c6flo/misc/newblock folder, we
should change the path to be ti.c6flo.misc.newblock. The only
content in the package definition is a one-line module declaration. By
convention, the module name is a CamelCase version of the folder name. For our
new block (in the newblock folder), we can use NewBlock.
Once the module declaration is complete, we can
move on to the module definition. Before we even look at this file, please
note: the file name of the module definition must match the name specified in
package.xdc. For our new block, we must rename Dummy.xdc to NewBlock.xdc before we continue. This
file name is case sensitive. Inside the file, there are several things to
note. First, the module itself must be defined with the same (case sensitive)
name as the file itself (minus ".xdc"). For our new block, we must
set this to NewBlock. Note that
our module inherits from the common module shared by all C6Flo blocks, IBlock. You can
find the definition for this shared module in the file ti/c6flo/IBlock.xdc.
Inside our module definition, there are several
parameter definitions as well as the instance label. Parameters
defined above the instance label are called module
parameters and apply to all instances of our block.
Parameters below the instance label are called instance
parameters and can vary between different instances of our
block. Note that all of the module parameters in our file are defined using the
override keyword. These parameters are inherited from the
IBlock module, but override the default value of that parameter with a new
value. All other parameters from IBlock still exist in our module; they are
just using their standard default values.
Let's make a few changes to our NewBlock module
definition. First, we will make our new block always use one input connection
and one output connection. This is as simple as changing the allowed_inputs and allowed_outputs arrays to
include only one value: 1. The next three override parameters from the Dummy
module (is_symmetric, is_homogeneous, and critical_params_proc) will be
fine with their default values in NewBlock, so we can delete those three lines
completely. Finally, let's replace the two instance parameters from Dummy with
our own new parameter: a floating point value named new_arg.
When you're done making the above changes, your package.xdc and NewBlock.xdc files should
have the following contents:
package ti.c6flo.misc.newblock
{
module NewBlock;
}
metaonly module NewBlock inherits ti.c6flo.IBlock
{
override config int allowed_inputs[] = [1];
override config int allowed_outputs[] = [1];
override config String palette_category = "Custom";
instance:
config float new_arg = 1.0;
}
Now there's only one step left before C6Flo will
recognize our new block: we need to build the JavaScript module. Simply open a
terminal window and navigate to our new block's folder (i.e. ti/c6flo/misc/newblock). There, run
the following command xdc all. If this
doesn't work, you may need to add your XDC Tools folder to the PATH environment
variable. This should generally be done for you when you install the XDC Tools.
The XDC Tools are automatically installed by CCS and/or C6Flo.
Once your module has built successfully, several
generated files and subfolders will appear in its folder. Your block will now
appear on the C6Flo palette the next time you restart CCS. However, before we
can use it to generate code, we must address the source code template files.
Just as block's module determines how it behaves in
the C6Flo system diagram view, its source code templates determine
how it contributes to the generated application code. The source code templates
are basically C-language source files that are copied in to various places in
the generated application code. Optionally, you can use JavaScript to enhance
the source code templates to make them adjust the generated source code
according the the block configuration. For most blocks, however, it's possible
to write valid, efficient templates using only the C language. The following
list summarizes the optional JavaScript elements that are allowed in source
code templates.
- By
default, text is copied as-is from the source code template into the
generated application source code.
- Lines
beginning with the percent
sign (%) are interpreted as JavaScript statements. The result of any
expression is not copied into the generated application source
code.
- Lines
between JavaScript
control braces (%{, %}) are conditionally copied to the generated application
source code.
- Lines
between JavaScript
control braces and double percent signs (%%{, %%}) are
interpreted as JavaScript statements and are not copied to the
generated application source code.
- Text
between `backward
apostrophes` is interpreted as a JavaScript expression. The result of this
expression is copied to the generated application code.
The following example demonstrates how JavaScript
elements in source code templates are translated during code generation. All of
the rules listed above are applied in this example.
% // this is some JavaScript code
// this is plain text
% var my_string = "// hello world";
% var display_string = true;
% if (display_string) {
`my_string`
% } else {
// (didn't display my_string)
% }
%%{
// more JavaScript
// ...
%%}
// this is plain text
// hello world
Each block must provide six source code templates
for use during code generation. The typedef.ht template
contributes a standard C typedef to the include/<Application
Name>.h header file. Similarly, struct.ct contributes
a struct definition and create.ct, init.ct, proc.ct, and ctrl.ct contribute four standard
functions to the <Application
Name>_blocks.c source file. For more information about these four
functions, please refer to the advanced sections of this help page, below.
For now, let's take a look at the source code
templates for our custom block, NewBlock. We copied these files from the dummy
block, and some of them are fine as-is. We can leave our create, initialize,
and control functions as "do nothing" code for the time being.
However, we will need to modify the typedef and struct templates since our new
block has different parameters than the dummy block. Simply change these
templates to look like the following:
typedef struct {
// standard elements (DO NOT CHANGE OR MOVE)
C6Flo_std_struct_obj std;
// extra elements
float new_arg;
} `this.c6flo_cg_obj_name`,
*`this.c6flo_cg_hdl_name`;
`this.c6flo_cg_obj_name` `this.c6flo_name`_obj = {
`this.c6flo_cg_std_struct`,
/* new_arg = */ `this.new_arg`
};
There are several important things to note, above.
First, these templates are C code with a few JavaScript values inserted using
the `backward apostrophes` notation. In
the JavaScript context, this refers to the instance of
our block module. Thus, this.new_arg retrieves
the value of the new_arg parameter
that we set in the C6Flo system diagram. Parameter values are typically placed
in the struct since a separate struct is always generated for each instance of
our block. This allows the function code to be multi-instance friendly, which
will reduce code size in the generated application. If we insert or otherwise
use our parameters directly in a function template, we will potentially need to
generate multiple copies of that function. For more information on that topic,
refer to the advanced section on critical parameters.
It's also important to note that there are
additional instance parameters being used that we did not explicitly define in
our block module: c6flo_cg_obj_name, c6flo_cg_hdl_name, c6flo_cg_obj_name, c6flo_name, and c6flo_cg_std_struct. Of these, c6flo_name is the label
text of the block instance and the other four are codegen
helpers. Codegen helpers are special parameters that are
generated automatically during code generation. We don't need to worry about
setting codegen helpers to the correct values, but it is important to leave
them in place in our source code templates.
Finally, note that our typedef and struct both
begin with pre-defined standard elements. These
elements house standard information about the block instance. It's important to
leave these elements in place at the start of the typedef and struct.
Now let's take a look at the source code template
for our process function. This is the primary function that applies our custom
algorithm, so it's time to decide what we want our block to do. One simple
example is to use our parameter to scale each incoming value. The source code
template to perform this task may look something like this:
`this.c6flo_cg_proc_fxn_prototype`
{
float *data = C6Flo_get_buf_ptr(inputs[0]);
int length = C6Flo_get_buf_length(inputs[0])
int i;
// scale input by arg
for (i = 0; i < length; i++)
data[i] *=
blockp->new_arg;
return C6Flo_EOK;
}
Note that the function prototype is set by another
codegen helper. This is necessary because C6Flo determines all function names
during code generation. For more information on the contents of the function
prototypes, including the standard inputs argument,
see the advanced section on the four standard functions. Also note
that we access our new_arg parameter
using the C instance struct instead of our JavaScript module. This allows our
function to be multi-instance friendly.
Finally, we use the macros C6Flo_get_buf_ptr and C6Flo_get_buf_length to access
our buffer pointer and element count, respectively. These are simple C macros
that appear in the generated include/<Application Name>.h header file.
They only serve to make it easier to access elements in inputs.
We've completed all of the work that will be
necessary to generate application code from our new block, but we're only
scratching the surface of C6Flo's capabilities. For detailed information on the
source code templates and code generation, refer to the advanced sections of
this help page.
Once your module is built and your source code
templates are in place, your new block is ready to be used with C6Flo. Restart CCS and open a C6Flo diagram.
You should see your new block in the Custom category.
You can drag the block onto any diagram and use it to generate code like any
other block.
<![if !vml]><![endif]>
|
Custom block on the standard C6Flo palette
|
This concludes the basic process for adding a
custom block to C6Flo. The following sections cover advanced features that are
not required by basic C6Flo blocks. Read on to learn more about how to maximize
the power and versatility of your custom blocks and how they fit in with the
overall DSP application.
The following tables list the standard parameters
that are defined for all blocks in IBlock.xdc.
Table 1: Module Parameters
|
||
Name
|
Type
|
Description
|
allowed_inputs
|
int[]
|
Number of inputs allowed by block
|
allowed_outputs
|
int[]
|
Number of outputs allowed by block
|
is_symmetric
|
Bool
|
true: number of inputs and outputs must match
false: number of inputs and outputs can differ |
allowed_input_types
|
String[]
|
Input datatypes allowed by block
|
allowed_output_types
|
String[]
|
Output datatypes allowed by block
|
is_homogeneous
|
Bool
|
true: input and output datatypes must match
false: input and output datatypes can differ |
is_framework
|
Bool
|
true: block is a framework block
false: block is not a framework block |
is_host_control
|
Bool
|
true: block is a host control block
false: block is not a host control block |
critical_params_create
|
String[]
|
Names of critical parameters for block create function
(empty: no critical parameters) |
critical_params_init
|
String[]
|
Names of critical parameters for block initialize
function
(empty: no critical parameters) |
critical_params_proc
|
String[]
|
Names of critical parameters for block process
function
(empty: no critical parameters) |
critical_params_ctrl
|
String[]
|
Names of critical parameters for block control
function
(empty: no critical parameters) |
resource_lib
|
String
|
Semicolon-separated list of library files
required by block
(specify path relative to C6Flo Resources folder) |
resource_h
|
String
|
Semicolon-separated list of header files required
by block
(specify path relative to C6Flo Resources folder) |
resource_tci
|
String
|
Semicolon-separated list of TCI files required by
block
(specify path relative to C6Flo Resources folder) |
compatible_with
|
String
|
Normal block: strictest DSP core compatibility
requirement for this block
Framework block: semicolon-separated list of all DSP cores compatibile with this device |
palette_category
|
String
|
Name of C6Flo palette category that contains this
block
|
Table 2: Instance Parameters
|
||
Name
|
Type
|
Description
|
c6flo_name
|
String
|
Label text assigned to block in system diagram
view
|
c6flo_input_count
|
int
|
Number of input connections
|
c6flo_output_count
|
int
|
Number of output connections
|
c6flo_input_type
|
String
|
Datatype of input connections
|
c6flo_output_type
|
String
|
Datatype of output connections
|
c6flo_id
|
int
|
ID value used to address this block via host
control
|
c6flo_buf_size_min
|
int
|
Minimum buffer size (in bytes) required by this
block
|
c6flo_buf_length_min
|
int
|
Minimum buffer length (element count) required by
this block
|
c6flo_buf_align_min
|
int
|
Minimum buffer alignment (in bytes) required by
this block
|
Table 3: Codegen Helpers
|
||
Name
|
Return Type
|
Description
|
c6flo_cg_version
|
int
|
Version of block
|
c6flo_cg_write_typedef_h
|
Bool
|
True: generate typedef for this block
False: do not generate typedef for this block |
c6flo_cg_write_create_c
|
Bool
|
True: generate create function for this block
False: do not generate create function for this block |
c6flo_cg_write_init_c
|
Bool
|
True: generate initialize function for this block
False: do not generate initialize function for this block |
c6flo_cg_write_proc_c
|
Bool
|
True: generate process function for this block
False: do not generate process function for this block |
c6flo_cg_write_ctrl_C
|
Bool
|
True: generate control function for this block
False: do not generate control function for this block |
c6flo_cg_module_name
|
String
|
Name of block module with path (ex.
ti.c6flo.misc.dummy.Dummy)
|
c6flo_cg_module_name_short
|
String
|
Name of block module without path (ex. Dummy)
|
c6flo_cg_module_name_c
|
String
|
Name of block module folder with underscore
separation (ex. ti_c6flo_misc_dummy)
|
c6flo_cg_package_dir
|
String
|
Name of block module folder (ex.
ti.c6flo.misc.dummy)
|
c6flo_cg_thread_name
|
String
|
Name of processing thread that contains this
block
|
c6flo_cg_app_name
|
String
|
Name of generated application that contains this
block
|
c6flo_cg_obj_name
|
String
|
Name of this block's instance struct
|
c6flo_cg_hdl_name
|
String
|
Name of this block's instance struct handle
|
c6flo_cg_create_fxn_name
|
String
|
Name of this block's create function
|
c6flo_cg_init_fxn_name
|
String
|
Name of this block's initialize function
|
c6flo_cg_proc_fxn_name
|
String
|
Name of this block's process function
|
c6flo_cg_ctrl_fxn_name
|
String
|
Name of this block's control function
|
c6flo_cg_create_fxn_prototype
|
String
|
Prototype for this block's create function
|
c6flo_cg_init_fxn_prototype
|
String
|
Prototype for this block's initialize function
|
c6flo_cg_proc_fxn_prototype
|
String
|
Prototype for this block's process function
|
c6flo_cg_ctrl_fxn_prototype
|
String
|
Prototype for this block's control function
|
c6flo_cg_std_struct
|
String
|
Contents of standard block struct elements
|
This section describes the four standard
functions that implement each block in a C6Flo-generated
application. Generally speaking, the create function
allocates any buffers or driver handles that are required by the block during
normal operation. The initialize function
prepares the block to run for the first time, and the process function is
called repeatedly to apply the block algorithm in the overall processing loop.
The control function is
called only in response to host control events. Usage notes and prototypes for
each function are listed below.
Create Function
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 Function
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 Function
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
- The
following table lists standard C macros that are useable in the process
function:
Macro
|
Description
|
C6Flo_get_buf_size(a)
|
Get size (in bytes) of buffer a (ex. inputs[0])
|
C6Flo_set_buf_size(a, b)
|
Set size of buffer a to value b
|
C6Flo_get_buf_length(a)
|
Get length (in elements) of buffer a
|
C6Flo_set_buf_length(a, b)
|
Set length of buffer a to value b
|
C6Flo_get_buf_ptr(a)
|
Get data pointer of buffer a (void *)
|
C6Flo_set_buf_ptr(a, b)
|
Set data pointer of buffer a to value b
|
Control Function
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
- The
following source fragment shows the standard message struct used by the
control function:
typedef struct {
Uint16 block_id;
Uint16 arg_count;
void *arg_buffer;
} C6Flo_HOST_msg_obj, *C6Flo_HOST_msg_hdl;
By default, C6Flo blocks will re-use the four
standard functions across multiple instances to minimize code size of the
generated application. However, this only works if all block instance
parameters are stored in the individual blocks' instance structs. If an
instance parameter is used directly in the source code template for any
function, that paramater becomes a critical parameter of the
function. Block authors are responsible for identifying critical functions and
indicating them in block's module definition.
It may sound like critical parameters should always
be avoided, but they allow sophisticated manipulation of the generated code
when used carefully. For example, consider the ti.c6flo.math.sum block.
Specifically, look at the source code template for its process function:
`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;
}
In this case, the number of inputs (this.c6flo_input_count) and input
data type (c6flo_input_type) are
critical parameters of the process function. The former allows the sum block to
generate clean code for any number of inputs rather than resorting to a nested
loop or large switch statement. The downside is that the function will be
duplicated for each sum block in the overall system that has a different number
of inputs. C6Flo will automatically generate the least number of function
variants required to satisfy all combinations of critical parameters for all
blocks in the system. For example, if there are two sum blocks with two inputs
and a third sum block with three inputs, two process functions will be
generated. However, if the sum blocks with two inputs use different data types,
then three process functions will be generated.
Critical parameters must be explicitly listed in
the block module definition using the critical_params_<function> parameter.
For example, ti/c6flo/math/sum/Sum.xdc contains the
following line:
override config String critical_params_proc[] =
["c6flo_input_count", "c6flo_input_type"];
The arrays can contain any number of parameter
names, but by default they are empty. The block author is responsible for
overriding the default arrays with the appropriate lists of critical
parameters.