domingo, 2 de septiembre de 2012

Números Complejos en C6Flo


Complex Numbers in C6Flo

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]>Complex data buffer with interleave<![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]>Complex data buffers for real and imaginary values<![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]>C6Flo screenshot: custom block on standard palette<![endif]>
Custom block on the standard C6Flo palette

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]>C6Flo screenshot: dummy block in system<![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]>C6Flo screenshot: custom block on standard palette<![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.