Main Content

Manual Fixed-Point Conversion Best Practices

Fixed-Point Designer™ software helps you design and convert your algorithms to fixed point. Whether you are simply designing fixed-point algorithms in MATLAB® or using Fixed-Point Designer in conjunction with MathWorks® code generation products, these best practices help you get from generic MATLAB code to an efficient fixed-point implementation. These best practices are also covered in this webinar: Manual Fixed-Point Conversion Best Practices Webinar

Create a Test File

A best practice for structuring your code is to separate your core algorithm from other code that you use to test and verify the results. Create a test file to call your original MATLAB algorithm and fixed-point versions of the algorithm. For example, as shown in the following table, you might set up some input data to feed into your algorithm, and then, after you process that data, create some plots to verify the results. Since you need to convert only the algorithmic portion to fixed-point, it is more efficient to structure your code so that you have a test file, in which you create your inputs, call your algorithm, and plot the results, and one (or more) algorithmic files, in which you do the core processing.

Original codeBest PracticeModified code
% TEST INPUT
x = randn(100,1);

% ALGORITHM
y = zeros(size(x));
y(1) = x(1);
for n=2:length(x)
  y(n)=y(n-1) + x(n);
end

% VERIFY RESULTS
yExpected=cumsum(x);
plot(y-yExpected)
title('Error')

Issue

Generation of test input and verification of results are intermingled with the algorithm code.

Fix

Create a test file that is separate from your algorithm. Put the algorithm in its own function.

Test file

% TEST INPUT
x = randn(100,1);

% ALGORITHM
y = cumulative_sum(x);

% VERIFY RESULTS
yExpected = cumsum(x);
plot(y-yExpected)
title('Error')

Algorithm in its own function

function y = cumulative_sum(x)
  y = zeros(size(x));
  y(1) = x(1);
  for n=2:length(x)
    y(n) = y(n-1) + x(n);
  end
end

You can use the test file to:

  • Verify that your floating-point algorithm behaves as you expect before you convert it to fixed point. The floating-point algorithm behavior is the baseline against which you compare the behavior of the fixed-point versions of your algorithm.

  • Propose fixed-point data types.

  • Compare the behavior of the fixed-point versions of your algorithm to the floating-point baseline.

Your test files should exercise the algorithm over its full operating range so that the simulation ranges are accurate. For example, for a filter, realistic inputs are impulses, sums of sinusoids, and chirp signals. With these inputs, using linear theory, you can verify that the outputs are correct. Signals that produce maximum output are useful for verifying that your system does not overflow. The quality of the proposed fixed-point data types depends on how well the test files cover the operating range of the algorithm with the accuracy that you want.

Prepare Your Algorithm for Code Acceleration or Code Generation

Using Fixed-Point Designer, you can:

  • Instrument your code and provide data type proposals to help you convert your algorithm to fixed point, using the following functions:

    • buildInstrumentedMex, which generates compiled C code that includes logging instrumentation.

    • showInstrumentationResults, which shows the results logged by the instrumented, compiled C code.

    • clearInstrumentationResults, which clears the logged instrumentation results from memory.

  • Accelerate your fixed-point algorithms by creating a MEX file using the fiaccel function.

Any MATLAB algorithms that you want to instrument using buildInstrumentedMex and any fixed-point algorithms that you want to accelerate using fiaccel must comply with code generation requirements and rules. To view the subset of the MATLAB language that is supported for code generation, see Functions and Objects Supported for C/C++ Code Generation.

To help you identify unsupported functions or constructs in your MATLAB code, use one of the following tools.

  • Add the %#codegen pragma to the top of your MATLAB file. The MATLAB code analyzer flags functions and constructs that are not available in the subset of the MATLAB language supported for code generation. This advice appears in real-time as you edit your code in the MATLAB editor.

    For more information, see Check Code Using the MATLAB Code Analyzer.

  • Use the Code Generation Readiness tool to generate a static report on your code. The report identifies calls to functions and the use of data types that are not supported for code generation. To generate a report for a function, myFunction1, at the command line, enter coder.screener('myFunction1').

    For more information, see Check Code Using the Code Generation Readiness Tool.

Check for Fixed-Point Support for Functions Used in Your Algorithm

Before you start your fixed-point conversion, identify which functions used in your algorithm are not supported for fixed point. Consider how you might replace them or otherwise modify your implementation to be more optimized for embedded targets. For example, you might need to find (or write your own) replacements for functions like log2, fft, and exp. Other functions like sin, cos, and sqrt may support fixed point, but for better efficiency, you may want to consider an alternative implementation like a lookup table or CORDIC-based algorithm.

If you cannot find a replacement immediately, you can continue converting the rest of your algorithm to fixed point by simply insulating any functions that don’t support fixed-point with a cast to double at the input, and a cast back to a fixed-point type at the output.

Original CodeBest PracticeModified Code
y = 1/exp(x);

Issue

The exp() function is not defined for fixed-point inputs.

Fix

Cast the input to double until you have a replacement. In this case, 1/exp(x) is more suitable for fixed-point growth than exp(x), so replace the whole expression with a 1/exp function, possibly as a lookup table.

y = 1/exp(double(x));

Manage Data Types and Control Bit Growth

The (:)= syntax is known as subscripted assignment. When you use this syntax, MATLAB overwrites the value of the left-hand side argument, but retains the existing data type and array size. This is particularly important in keeping fixed-point variables fixed point (as opposed to inadvertently turning them into doubles), and for preventing bit growth when you want to maintain a particular data type for the output.

Original CodeBest Practice Modified Code
acc = 0;
for n = 1:numel(x)
  acc = acc + x(n);
end

Issue

acc = acc + x(n)overwrites acc with acc + x(n). When you are using all double types, this behavior is fine. However, when you introduce fixed-point data types in your code, if acc is overwritten, the data type of acc might change.

Fix

To preserve the original data type of acc, assign into acc using acc(:)=. Using subscripted assignment casts the right-hand-side value into the same data type as acc and prevents bit growth.

acc = 0;
for n = 1:numel(x)
  acc(:) = acc + x(n);
end

For more information, see Controlling Bit Growth.

Separate Data Type Definitions from Algorithm

For instrumentation and code generation, create an entry-point function that calls the function that you want to convert to fixed point. You can then cast the function inputs to different data types. You can add calls to different variations of the function for comparison. By using an entry-point function, you can run both fixed-point and floating-point variants of your algorithm. You can also run different variants of fixed-point. This approach allows you to iterate on your code more quickly to arrive at the optimal fixed-point design.

This method of fixed-point conversion makes it easier for you to compare several different fixed-point implementations, and also allows you to easily retarget your algorithm to a different device.

To separate data type definitions from your algorithm:

  1. When a variable is first defined, use cast(x,'like',y) or zeros(m,n,'like',y) to cast it to your desired data type.

  2. Create a table of data type definitions, starting with original data types used in your code. Before converting to fixed point, create a data type table that uses all single data types to find type mismatches and other problems.

  3. Run your code connected to each table and look at the results to verify the connection.

Original CodeBest PracticeModified Code
% Algorithm
n = 128;
y = zeros(size(n));

Issue

The default data type in MATLAB is double-precision floating-point.

Fix

  1. Use cast(...,'like',...) and zeros(...'like',...) to programmatically specify types that are defined in a separate table.

  2. Create an original types table, usually in a separate function.

  3. Add single data types to your table to help verify the connection with your code.

% Algorithm
T = mytypes('double');
n = cast(128,'like',T.n);
y = zeros(size(n),'like',T.y);
function T = mytypes(dt)
  switch(dt)
    case 'double'
      T.n = double([]);
      T.y = double([]);
      
    case 'single'
      T.n = single([]);
      T.y = single([]);
  end
end

Separating data type specifications from algorithm code enables you to:

  • Reuse your algorithm code with different data types.

  • Keep your algorithm uncluttered with data type specifications and switch statements for different data types.

  • Improve readability of your algorithm code.

  • Switch between fixed-point and floating-point data types to compare baselines.

  • Switch between variations of fixed-point settings without changing the algorithm code.

Convert to Fixed Point

What Are Your Goals for Converting to Fixed Point?

Before you start the conversion, consider your goals for converting to fixed point. Are you implementing your algorithm in C or HDL? What are your target constraints? The answers to these questions determine many fixed-point properties such as the available word length, fraction length, and math modes, as well as available math libraries.

Build and Run an Instrumented MEX Function

Build and run an instrumented MEX function to get fixed-point types proposals using the buildInstrumentedMex and showInstrumentationResults functions. Test files should exercise your algorithm over its full operating range. The quality of the proposed fixed-point data types depends on how well the test file covers the operating range of the algorithm with the accuracy that you want. A simple set of test vectors may not exercise the full range of types, so use the proposals as a guideline for choosing an initial set of fixed-point types, and use your best judgement and experience in adjusting the types. If loop indices are used only as index variables, they are automatically converted to integer types, so you do not have to explicitly convert them to fixed point.

Algorithm Code Test File
function [y,z] = myfilter(b,x,z)
  y = zeros(size(x));
  for n = 1:length(x)
    z(:) = [x(n); z(1:end-1)];
    y(n) = b * z;
  end
end
% Test inputs
b = fir1(11,0.25);
t = linspace(0,10*pi,256)';
x = sin((pi/16)*t.^2);  % Linear chirp
z = zeros(size(b'));

% Build
buildInstrumentedMex myfilter ...
  -args {b,x,z} -histogram

% Run
[y,z] = myfilter_mex(b,x,z);

% Show
showInstrumentationResults myfilter_mex ...
  -defaultDT numerictype(1,16) -proposeFL

Create a Types Table

Create a types table using a structure with prototypes for the variables. The proposed types are computed from the simulation runs. A long simulation run with a wide range of expected data produces better proposals. You can use the proposed types or use your knowledge of the algorithm and implementation constraints to improve the proposals.

Because the data types, not the values, are used, specify the prototype values as empty ([]).

In some cases, it might be more efficient to leave some parts of the code in floating point. For example, when there is high dynamic range or that part of the code is sensitive to round-off errors.

Algorithm CodeTypes TablesTest File
function [y,z]=myfilter(b,x,z,T)
  y = zeros(size(x),'like',T.y);
  for n = 1:length(x)
    z(:) = [x(n); z(1:end-1)];
    y(n) = b * z;
  end
end
function T = mytypes(dt)
  switch dt
    case 'double'
      T.b = double([]);
      T.x = double([]);
      T.y = double([]);

     case 'fixed16'
      T.b = fi([],true,16,15);
      T.x = fi([],true,16,15);
      T.y = fi([],true,16,14);
  end
end
% Test inputs
b = fir1(11,0.25);
t = linspace(0,10*pi,256)';
x = sin((pi/16)*t.^2); 
% Linear chirp

% Cast inputs
T=mytypes('fixed16');
b=cast(b,'like',T.b);
x=cast(x,'like',T.x);
z=zeros(size(b'),'like',T.x);

% Run
[y,z] = myfilter(b,x,z,T);

Run With Fixed-Point Types and Compare Results

Create a test file to validate that the floating-point algorithm works as expected before converting it to fixed point. You can use the same test file to propose fixed-point data types, and to compare fixed-point results to the floating-point baseline after the conversion.

Optimize Data Types

Use Scaled Doubles

Use scaled doubles to detect potential overflows. Scaled doubles are a hybrid between floating-point and fixed-point numbers. Fixed-Point Designer stores them as doubles with the scaling, sign, and word length information retained. To use scaled doubles, you can use the data type override (DTO) property or you can set the 'DataType' property to 'ScaledDouble' in the fi or numerictype constructor.

To...Use...Example

Set data type override locally

numerictype DataType property

T.a = fi([],1,16,13,'DataType', 'ScaledDouble');
a = cast(pi, 'like', T.a)
a =
    3.1416

   DataTypeMode: Scaled double: binary point scaling
           Signedness: Signed
         WordLength: 16
     FractionLength: 13

Set data type override globally

fipref DataTypeOverride property

fipref('DataTypeOverride','ScaledDoubles')
T.a = fi([],1,16,13);
a =
  3.1416

  DataTypeMode:Scaled double: binary point scaling
    Signedness: Signed
    WordLength:16
FractionLength:13

For more information, see Scaled Doubles.

Use the Histogram to Fine-Tune Data Type Settings

To fine-tune fixed-point type settings, run the buildInstrumentedMex function with the –histogram flag and then run the generated MEX function with your desired test inputs. When you use the showInstrumentationResults to display the code generation report, the report displays a Histogram icon. Click the icon to open the NumericTypeScope and view the distribution of values observed in your simulation for the selected variable.

Overflows indicated in red in the Code Generation Report show in the "outside range" bin in the NumericTypeScope. Launch the NumericTypeScope for an associated variable or expression by clicking on the histogram view icon .

Explore Design Tradeoffs

Once you have your first set of fixed-point data types, you can then add different variations of fixed-point values to your types table. You can modify and iterate to avoid overflows, adjust fraction lengths, and change rounding methods to eliminate bias.

Algorithm CodeTypes TablesTest File
function [y,z] = myfilter(b,x,z,T)
  y = zeros(size(x),'like',T.y);
  for n = 1:length(x)
    z(:) = [x(n); z(1:end-1)];
    y(n) = b * z;
  end
end
function T = mytypes(dt)
  switch dt
    case 'double'
      T.b = double([]);
      T.x = double([]);
      T.y = double([]);

    case 'fixed8'
      T.b = fi([],true,8,7);
      T.x = fi([],true,8,7);
      T.y = fi([],true,8,6);

    case 'fixed16'
      T.b = fi([],true,16,15);
      T.x = fi([],true,16,15);
      T.y = fi([],true,16,14);
  end
end
function mytest
  % Test inputs
  b = fir1(11,0.25);
  t = linspace(0,10*pi,256)';
  x = sin((pi/16)*t.^2);  % Linear chirp
  
  % Run
  y0  = entrypoint('double',b,x);
  y8  = entrypoint('fixed8',b,x);
  y16 = entrypoint('fixed16',b,x);
  
  % Plot
  subplot(3,1,1)
  plot(t,x,'c',t,y0,'k')
  legend('Input','Baseline output')
  title('Baseline')
         
  subplot(3,2,3)
  plot(t,y8,'k')
  title('8-bit fixed-point output')
  subplot(3,2,4)
  plot(t,y0-double(y8),'r')
  title('8-bit fixed-point error')

  subplot(3,2,5)
  plot(t,y16,'k')
  title('16-bit fixed-point output')
  xlabel('Time (s)')
  subplot(3,2,6)
  plot(t,y0-double(y16),'r')
  title('16-bit fixed-point error')
  xlabel('Time (s)')
end

function [y,z] = entrypoint(dt,b,x)
  T = mytypes(dt);
  b = cast(b,'like',T.b);
  x = cast(x,'like',T.x);
  z = zeros(size(b'),'like',T.x);
  [y,z] = myfilter(b,x,z,T);
end

Optimize Your Algorithm

Use fimath to Get Natural Types for C or HDL

fimath properties define the rules for performing arithmetic operations on fi objects, including math, rounding, and overflow properties. You can use the fimath ProductMode and SumMode properties to retain natural data types for C and HDL. The KeepLSB setting for ProductMode and SumMode models the behavior of integer operations in the C language, while KeepMSB models the behavior of many DSP devices. Different rounding methods require different amounts of overhead code. Setting the RoundingMethod property to Floor, which is equivalent to two's complement truncation, provides the most efficient rounding implementation. Similarly, the standard method for handling overflows is to wrap using modulo arithmetic. Other overflow handling methods create costly logic. Whenever possible, set the OverflowAction to Wrap.

MATLAB CodeBest PracticeGenerated C Code
% Code being compiled

function y = adder(a,b)
  y = a + b;
end

With types defined with
default fimath settings:

T.a = fi([],1,16,0);
T.b = fi([],1,16,0);

a = cast(0,'like',T.a);
b = cast(0,'like',T.b);

Issue

Additional code is generated to implement saturation overflow, nearest rounding, and full-precision arithmetic.

int adder(short a, short b)
{
  int y;
  int i;
  int i1;
  int i2;
  int i3;
  i = a;
  i1 = b;
  if ((i & 65536) != 0) {
    i2 = i | -65536;
  } else {
    i2 = i & 65535;
  }

  if ((i1 & 65536) != 0) {
    i3 = i1 | -65536;
  } else {
    i3 = i1 & 65535;
  }

  i = i2 + i3;
  if ((i & 65536) != 0) {
    y = i | -65536;
  } else {
    y = i & 65535;
  }

  return y;
}

Code being compiled

function y = adder(a,b)
  y = a + b;
end

With types defined with fimath settings that match your processor types:

F = fimath(...
 'RoundingMethod','Floor', ...
 'OverflowAction','Wrap', ...
 'ProductMode','KeepLSB', ...
 'ProductWordLength',32, ...
 'SumMode','KeepLSB', ...
 'SumWordLength',32);

T.a = fi([],1,16,0,F);
T.b = fi([],1,16,0,F);
a = cast(0,'like',T.a);
b = cast(0,'like',T.b);

Fix

To make the generated code more efficient, choose fixed-point math settings that match your processor types.

int adder(short a, short b)
{
  return a + b;
}

Replace Built-in Functions With More Efficient Fixed-Point Implementations

Some MATLAB built-in functions can be made more efficient for fixed-point implementation. For example, you can replace a built-in function with a Lookup table implementation, or a CORDIC implementation, which requires only iterative shift-add operations.

Re-implement Division Operations Where Possible

Often, division is not fully supported by hardware and can result in slow processing. When your algorithm requires a division, consider replacing it with one of the following options:

  • Use bit shifting when the denominator is a power of two. For example, bitsra(x,3) instead of x/8.

  • Multiply by the inverse when the denominator is constant. For example, x*0.2 instead of x/5.

Eliminate Floating-Point Variables

For more efficient code, eliminate floating-point variables. The one exception to this is loop indices because they usually become integer types.