FFT — Control Systems/Transforms
Control_Systems/Transforms/FFT · 1 input / 2 output port(s) at insert · exports to Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text
Description#
The block's own DESCRIPTION_HTML, rendered verbatim — the same text the config dialog's info panel and the library navigator show. Fix a wrong sentence in the block's .cpp (R-D9), never here.
FFT
Control Systems / Transforms
The complex forward discrete Fourier transform of the window on its
input, which is MATLAB's fft(u):
X[k] = Σₙ u[n]·e−2πi·kn/N
for k = 0 … N−1.
The answer is complex, so it leaves on two ports – the real part and the imaginary part – because an ICore signal carries doubles. If only the magnitude spectrum is wanted, FFT Magnitude computes it in one port and is bridged to Simulink; if only a few bins are wanted, Goertzel gives those bins without the rest.
Ports
- u – the window to transform, a real vector: an [N,1] column or a [1,N] row, N from 1 to 32. Entry n is u[n] in the sum above, oldest first.
- Re – the real part of X, one entry per bin, so N long and in the same orientation as the input.
- Im – the imaginary part of the same X, the same size.
Parameters
- Scaling – what multiplies the sum.
- None (fft) – the transform exactly as
fftdefines it. The default. - 1/N – the same spectrum divided by the window length, so bin 0
reads the MEAN of the window rather than its sum. This is Simulink's
Normalizeoption and the convention the inverse transform carries by default.
- None (fft) – the transform exactly as
- Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period.
N is not a parameter: it is the width of the input port, so the block and the signal reaching it cannot disagree about the transform length.
Code export
All ten targets: Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text.
The N×N twiddle pairs are structural and are inlined into the generated arithmetic: they follow from the window length and the scaling alone, so every target emits two real dot products per bin and computes no sine or cosine at run time – those happened at export.
The three HDL targets are genuine synthesizable Q16.16: only multiplies and adds, accumulated at double width and shifted back once per entry. Every twiddle has magnitude at most 1, so no part of the matrix quantizes away – but note that an unscaled transform can reach N·max|u| on bin 0, and the format saturates near ±32768.
Simulink bridge
No equivalent a bridge can write (Support::None), and the
reason is
about the wire rather than the block. dspxfrm3/FFT exists and
computes the same transform – simulated on an 8-point frame, it agreed with
fft exactly – but it carries its whole answer on one
complex port, and an ICore signal is real, so there is no port-to-port
mapping the bridge could write. A model that needs this block in Simulink pairs
dspxfrm3/FFT with simulink/Math Operations/Complex to
Real-Imag, whose two outputs reproduce this block's Re and Im ports
exactly (measured to 0.0). The bridge reports the block rather than dropping it,
and code export verification still covers it across all ten languages.
Notes
- Algebraic, with no state. The whole window arrives on the port, so one step is one transform and nothing carries over.
- Measured against R2026a. The coefficients reproduce
fftto 4.4e−16 over an 8-point window and to 6.7e−16 over a 5-point one, anddspxfrm3/FFTsimulated on an 8-point frame agreed withfftexactly. - A direct sum, not a radix-2 algorithm. The name is MATLAB's and what is guaranteed is its answer: N need not be a power of two, and the cost is N² products rather than N·log N. The 32-entry limit exists because those products are unrolled into the exported core.
- The angle is reduced modulo N before the trigonometry. kn reaches 961 at the longest window, and a cosine of ~6000 radians has already lost most of its accuracy to argument reduction; the exponential is periodic in kn with period N, so the reduced angle is the same number computed accurately.
- The exact inverse is the IFFT block, which takes Re and Im back.
- No state space. A fixed matrix over N presented samples is not an A/B/C/D pair evolving in time, so model reduction correctly declines to merge it.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Transforms/FFT |
| family | Control_Systems/Transforms |
| solver environment class | ICoreBlock_0_Control_Systems_1_Transforms_2_FFT |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Transforms/FFT/ICoreBlock_0_Control_Systems_1_Transforms_2_FFT.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Transforms/FFT/ICoreBlock_0_Control_Systems_1_Transforms_2_FFT.h |
| default size on canvas | 130 × 90 px |
| ports at insert | 1 in, 2 out |
| code generators implemented | Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text |
Ports#
| # | Direction | Signal type | Description label |
|---|---|---|---|
| 1 | in | ICoreDouble | u |
| 2 | out | ICoreDouble | Re |
| 3 | out | ICoreDouble | Im |
Ports the constructor creates. A block whose port list changes with its configuration adds or removes ports at load time; the count above is the one a freshly inserted block has.
Configuration variables#
| Config variable | Default | Simulink parameter |
|---|---|---|
Scaling | None (fft)%~%1/N~~None (fft) | — |
Every block also carries Sampling Time (s) from ICoreBlockSolverEnvironment: zero or less inherits the solver's rate, a positive value runs the block at that period.
Simulink bridge#
| support | Support::None |
| Simulink path | — |
| port-count rule | PortsParam::None |
SampleTime parameter | yes |
Caveat (shown to the user): no single-block equivalent, and the reason is the WIRE rather than the block. dspxfrm3/FFT exists in the DSP System Toolbox and computes the same transform -- simulated on an 8-point frame, it agreed with MATLAB's fft exactly -- but it carries its whole answer on ONE COMPLEX port, while this block presents the real and imaginary parts on two real ports because an ICore signal carries doubles. There is no port-to-port mapping to write. In Simulink, pair dspxfrm3/FFT with simulink/Math Operations/Complex to Real-Imag: its two outputs reproduce this block's Re and Im ports exactly, measured to 0.0. FFT Magnitude is the bridged block for the magnitude spectrum alone
Catalog contract: src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h
Description vs code#
The lists agree. check_block_descriptions.py finds no disagreement between the description's Ports, Parameters, Code export and Simulink bridge lists and the code's.
The verdict above is
tools/docs/check_block_descriptions.py(P7.1), which compares LISTS. It cannot read a sentence: "stateless" on a block with a state, an initial-value semantic the recursion does not implement, a "not synthesizable" caveat the HDL banner contradicts. That is the agent audit (P7.3) on BLOCK_DESCRIPTION_AUDIT.md, and this tool's green is not a substitute for one.
File banner (developer view)#
The top comment of the block's .cpp — the maths, the realization and the export strategy, addressed to whoever changes it. It must not contradict the description above (P7.5).
FFT -- the complex forward discrete Fourier transform of a window (MATLAB fft) X[k] = SUM over n of u[n] * exp(-2*pi*i*k*n/N), k = 0 .. N-1
The answer is COMPLEX and an ICore wire carries doubles, so it leaves on TWO ports, Re and Im. That is the arrangement Goertzel and Chirp-Z Transform already use, and it is also why this block is Support::None -- see the catalog entry below.
ONE MATRIX PRODUCT, TWICE. The twiddle pair cos/sin(-2*pi*k*n/N) depends on the window LENGTH and on nothing else, so the N*N pairs are derived once -- when the input port's size settles -- and every target inlines them as two real dot products per output entry. No emitted core computes a sine or a cosine; it multiplies and adds.
⚠ THE ANGLE IS REDUCED MODULO N BEFORE THE TRIG CALL. k*n reaches (N-1)^2 = 961 at the block's longest window, and cos() of an argument near 6000 radians has lost most of its low-order accuracy to argument reduction. exp(-2*pi*i*k*n/N) is periodic in k*n with period N, so ((k*n) mod N) is the SAME number computed from a small angle -- which is what lets this block match fft() to a few ulp at N = 32 instead of to 1e-12.
⚠ THIS IS A DIRECT SUM, NOT A RADIX-2 ALGORITHM. The name is MATLAB's, and what the block guarantees is fft()'s ANSWER: N need not be a power of two, the cost is N*N products, and the window is bounded at 32 because those products are UNROLLED into the exported core.
⚠ MEASURED AGAINST R2026a rather than asserted. The coefficients here were run through a stub harness on the same vectors a MATLAB batch was given: over the rig's 8-point window they reproduce fft() to 4.4e-16, over a 5-point window (NOT a power of two, which is the point of that case) to 6.7e-16, and over an 8-point COMPLEX window to 8.9e-16. Separately, dspxfrm3/FFT was SIMULATED on an 8-point frame and agreed with fft() EXACTLY, to 0.0.
ALGEBRAIC: the whole window is presented on the input port, so one step completes one transform and nothing is held between samples. No state space -- the map is linear in the input, but a fixed matrix over a WINDOW is not an A/B/C/D pair evolving in time.
Sample results#
| t | in ICoreDouble-Out-0 | out ICoreDouble-Out-0 | out ICoreDouble-Out-1 |
|---|---|---|---|
| 0 | -2 | -2 | 0 |
| 0.4 | 0.5 | 0.5 | 0 |
| 0.8 | -2 | -2 | 0 |
| 1.2 | 0.5 | 0.5 | 0 |
| 1.6 | -2 | -2 | 0 |
| 2 | 0.5 | 0.5 | 0 |
| 2.4 | -2 | -2 | 0 |
| 2.8 | 0.5 | 0.5 | 0 |
| 3.2 | -2 | -2 | 0 |
| 3.6 | 0.5 | 0.5 | 0 |
| 4 | -2 | -2 | 0 |
| 4.4 | 0.5 | 0.5 | 0 |
| 4.8 | -2 | -2 | 0 |
| 5.2 | 0.5 | 0.5 | 0 |
Every 4th of 60 samples, from the table stimulus.
The same rig also ran:
| Stimulus | What it is | Output range |
|---|---|---|
impulse | Impulse: one sample of 1 at k = 5, 0 elsewhere (Repeating Sequence Stair) | 0 … 1 |
ramp | Ramp: slope 1 from t = 0 | 0 … 5.9 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | -1 … 0.9996 |
step | Step: 0 -> 1 at t = 1 s | 0 … 1 |
Plotted: table — Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample
Category static · sample time 0.1 · 60 steps · commit 3c100aff6f27235305db4ad4d572f32e342718ad · produced by docsSample --out <folder> --blocks DCT IDCT FFT IFFT --steps 60 · data docs/generated/samples/Control_Systems__Transforms__FFT.json · the SVG is generated from those numbers by tools/docs/plot_svg.py, so it is a run and not a drawing (R-D10).