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

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 code | Best Practice | Modified 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') |
Generation of test input and verification of results are intermingled with the algorithm code.
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.

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.

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 Code | Best Practice | Modified Code |
---|---|---|

y = 1/exp(x); |
The
Cast
the input to double until you have a replacement. In this case, | y = 1/exp(double(x)); |

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 Code | Best Practice | Modified Code |
---|---|---|

acc = 0; for n = 1:numel(x) acc = acc + x(n); end |
To
preserve the original data type of | acc = 0; for n = 1:numel(x) acc(:) = acc + x(n); end |

For more information, see Controlling Bit Growth.

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:

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.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.

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

Original code | Best Practice | Modified code |
---|---|---|

% Algorithm n = 128; y = zeros(size(n)); |
The default data type in MATLAB is double-precision floating-point.
Use `cast(...,'like',...)` and`zeros(...'like',...)` to programmatically specify types that are defined in a separate table.Create an original types table, usually in a separate function. 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.

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 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 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 Code | Types Tables | Test 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); |

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.

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 |
| 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','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.

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 .

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 Code | Types Tables | Test 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 |

`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 Code | Best Practice | Generated 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); |
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); |
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; } |

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.

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`

.

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