Main Content

Fixed-Point Multiplication Helper Functions in Generated Code

This example shows how to control the generation of multiplication helper functions in the generated code.

In this example you will learn:

  • Under what circumstances these helpers are generated

  • What approaches are available to eliminate them

Example 1 - Helper Function Due to Range of Output

The following model creates helper functions for multiplication because the range of the ideal product exceeds the range of a single word data type.

open_system('fxpdemo_mulhelpers_example1');
set_param('fxpdemo_mulhelpers_example1', 'SimulationCommand', 'Update');

Generate code for the model and review it:

evalc('slbuild(''fxpdemo_mulhelpers_example1'');'); % Suppress output
fid = fopen('fxpdemo_mulhelpers_example1_grt_rtw/fxpdemo_mulhelpers_example1.c') ; ctext = fread(fid, '*char')'; fclose(fid);

Two helper functions were generated: mul_wide_u32() and mul_u32_loSR().

The step function calls the outer helper function mul_u32_loSR():

match = regexp(ctext, 'void fxpdemo_mulhelpers_example1_step.*?\n\}', 'match'); disp(match{1});
void fxpdemo_mulhelpers_example1_step(void)
{
  /* Product: '<Root>/MulHelper1' incorporates:
   *  Inport: '<Root>/In1'
   *  Inport: '<Root>/In2'
   */
  Y = mul_u32_loSR(U1, U2, 5U);
}

The outer helper function calls an inner helper function mul_wide_u32():

match = regexp(ctext, 'uint32_T mul_u32_loSR.*?\n\}', 'match'); disp(match{1});
uint32_T mul_u32_loSR(uint32_T a, uint32_T b, uint32_T aShift)
{
  uint32_T result;
  uint32_T u32_chi;
  mul_wide_u32(a, b, &u32_chi, &result);
  return u32_chi << (32U - aShift) | result >> aShift;
}

The inner helper function mul_wide_u32() is also generated in the file:

match = regexp(ctext, 'void mul_wide_u32.*?\n\}', 'match'); disp(match{1});
void mul_wide_u32(uint32_T in0, uint32_T in1, uint32_T *ptrOutBitsHi, uint32_T
                  *ptrOutBitsLo)
{
  uint32_T in0Hi;
  uint32_T in0Lo;
  uint32_T in1Hi;
  uint32_T in1Lo;
  uint32_T outBitsLo;
  uint32_T productHiLo;
  uint32_T productLoHi;
  in0Hi = in0 >> 16U;
  in0Lo = in0 & 65535U;
  in1Hi = in1 >> 16U;
  in1Lo = in1 & 65535U;
  productHiLo = in0Hi * in1Lo;
  productLoHi = in0Lo * in1Hi;
  in0Lo *= in1Lo;
  in1Lo = 0U;
  outBitsLo = (productLoHi << 16U) + in0Lo;
  if (outBitsLo < in0Lo) {
    in1Lo = 1U;
  }

  in0Lo = outBitsLo;
  outBitsLo += productHiLo << 16U;
  if (outBitsLo < in0Lo) {
    in1Lo++;
  }

  *ptrOutBitsHi = (((productLoHi >> 16U) + (productHiLo >> 16U)) + in0Hi * in1Hi)
    + in1Lo;
  *ptrOutBitsLo = outBitsLo;
}

Why Was a Helper Function Generated for Example 1?

Multiplying two 32-bit numbers yields a 64-bit ideal result. In example1, the type of the ideal result is uint64.

The actual output type of ufix32_E5 uses only bits 5 to 36 from these 64 bits.

bits = 0:63;
ideal_product_bits = ones(1,64);
most_significant_word = [ones(1,32) NaN*ones(1,32)];
least_significant_word = [NaN*ones(1,32) ones(1,32)];
output_bits = [NaN*ones(1, 27) ones(1,32) NaN*ones(1,5)];

plot(bits, ideal_product_bits, '.r');
set(gca, 'YLim', [-1 5]);
set(gca, 'YTick', 1:3);
set(gca, 'YTickLabel', {'ideal result', 'MSB | LSB', 'output data type'});
set(gca, 'XTick', 1:64);

% "Move" interesting tick labels so they're readable and show
set(gca, 'xTickLabel', {'63','','','','','','','',  '','','','','','','','', ...
                          '','','','','','','','',  '','','','','','','32','', ...
                          '31','','','','','','','',  '','','','','','','','', ...
                          '','','','','','','','',  '','','','','','','','0'});
set(get(gca,'XLabel'),'String','Bit positions')
hold on
plot(bits, most_significant_word*2, '.b');
plot(bits, least_significant_word*2, '.m');
plot(bits, output_bits*3, '.g');
legend('ideal result', 'most significant word', 'least significant word', 'output ufix32\_E5') % Escape _ to avoid subscripting the E
plot([31.5 31.5], [0 4]); % MSB-LSB boundary
clear bits ideal_product_bits most_significant_word least_significant_word output_bits

The output data type includes bits from both most and least significant words of the ideal product type. Even though the input and output data types are single word, it takes two words to construct the ideal result, so it can be converted to the output type. Helper functions are generated to manipulate this wide ideal product.

Why Are There Two Helper Functions?

The combination of the double-word multiplication and the cast is encoded in the two helper functions. The inner function, mul_wide_u32() performs long multiplication of two 32-bit operands into a 64-bit result. The outer function mul_u32_loSR() performs data type conversion from the raw uint64 result to the desired output data type.

close_system('fxpdemo_mulhelpers_example1', 0);
close all;

Example 2: Helper Functions Due to Saturation

The following model creates helper functions for multiplication, in order to implement saturation.

open_system('fxpdemo_mulhelpers_example2');
set_param('fxpdemo_mulhelpers_example2', 'SimulationCommand', 'Update');

evalc('slbuild(''fxpdemo_mulhelpers_example2'');'); % Suppress output
fid = fopen('fxpdemo_mulhelpers_example2_grt_rtw/fxpdemo_mulhelpers_example2.c') ; ctext = fread(fid, '*char')'; fclose(fid);
match = regexp(ctext, 'void fxpdemo_mulhelpers_example2_step.*?\n\}', 'match'); disp(match{1});
void fxpdemo_mulhelpers_example2_step(void)
{
  /* Product: '<Root>/MulHelper1' incorporates:
   *  Inport: '<Root>/In1'
   *  Inport: '<Root>/In2'
   */
  Y = mul_us32_sat(U1, U2);
}

This model does not have the range problem that the model in example 1 had. The output type fits within the least significant word of the ideal product.

In this model, the ideal type can be negative but the output is unsigned. To saturate negative values to 0, the sign bit needs to be computed. This sign bit resides in the most significant word of the ideal result. Coder again generates two helpers, one to compute the raw 64-bit result, and one to perform a saturating cast.

close_system('fxpdemo_mulhelpers_example2', 0);

Avoiding Helper Functions by Changing Numerical Properties

It is possible to avoid generating the multiplication helpers by:

  • Using wrapping multiplications

  • Using output types that don't exceed the range of the least significant word

  • Multiplying smaller data types with a single word ideal product

open_system('fxpdemo_mulhelpers_example3');
set_param('fxpdemo_mulhelpers_example3', 'SimulationCommand', 'Update');

evalc('slbuild(''fxpdemo_mulhelpers_example3'');'); % Suppress output
fid = fopen('fxpdemo_mulhelpers_example3_grt_rtw/fxpdemo_mulhelpers_example3.c') ; ctext = fread(fid, '*char')'; fclose(fid);
match = regexp(ctext, 'void fxpdemo_mulhelpers_example3_step.*?\n\}', 'match'); disp(match{1});
void fxpdemo_mulhelpers_example3_step(void)
{
  /* Product: '<Root>/Mul1' incorporates:
   *  Inport: '<Root>/In1'
   *  Inport: '<Root>/In2'
   */
  Y1 = (U1 * U2) << 3;

  /* Product: '<Root>/Mul2' incorporates:
   *  Inport: '<Root>/In3'
   *  Inport: '<Root>/In4'
   */
  Y2 = (uint16_T)(((uint32_T)U3 * U4) >> 5);
}
close_system('fxpdemo_mulhelpers_example3', 0);

Avoiding Helper Functions by Specifying Design Ranges

If large multiplicands are a necessity but the range of possible values on the operands is small enough, it may be possible to avoid the helper functions by specifying design ranges.

Note: This feature requires an Embedded Coder license.

Let's review a model similar to the one in example 1:

open_system('fxpdemo_mulhelpers_example4');
set_param('fxpdemo_mulhelpers_example4', 'SimulationCommand', 'Update');

Note that we selected the 'Optimize using the specified minimum and maximum values' checkbox on the optimization pane of the model configuration dialog.

get_param(bdroot, 'UseSpecifiedMinMax')
ans =

    'on'

Examine the step function in the generated code:

evalc('slbuild(''fxpdemo_mulhelpers_example4'');'); % Suppress output
fid = fopen('fxpdemo_mulhelpers_example4_ert_rtw/fxpdemo_mulhelpers_example4.c') ; ctext = fread(fid, '*char')'; fclose(fid);
match = regexp(ctext, 'void fxpdemo_mulhelpers_example4_step.*?\n\}', 'match'); disp(match{1});
void fxpdemo_mulhelpers_example4_step(void)
{
  /* Product: '<Root>/Mul1' incorporates:
   *  Inport: '<Root>/In1'
   *  Inport: '<Root>/In2'
   */
  Y = (U1 * U2) >> 5;
}

The range of the possible values of the ideal product is from 0 to 500. A single word data type is sufficient to express this product.

close_system('fxpdemo_mulhelpers_example4', 0);

Caution: If, while running the generated code, values on the signals exceed the design ranges, incorrect results may be computed.

Avoiding Helper Functions Using CRL Operator Replacement

You can customize code generation by replacing the cumbersome multiplication operators with your own C macros or custom functions. You may want to do this if, for example, your CPU has resources to implement wide multiplications more efficiently.

References:

clear ctext fid match
clear ans