Generated reference › Hilbert Transform — Control Systems/Transforms
kind: generated#block#control-systems-transforms

Hilbert Transform — Control Systems/Transforms

Control_Systems/Transforms/Hilbert_Transform · 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.

Hilbert Transform

Control Systems / Transforms

Produces the analytic signal of a stream as two real ports: the input delayed, and its Hilbert transform. The transform is a fixed FIR filter,

h[n] = 2/(πm) for odd m = n − N/2 and 0 for even m, multiplied by a window, so

Im[k] = Σn h[n]·u[k−n] and Re[k] = u[k−N/2].

Re + i·Im is the analytic signal: its magnitude is the envelope and its angle the instantaneous phase. The delay on Re is not optional – the filter's group delay is exactly N/2 samples, and without the matching delay the two ports are not two halves of one signal.

Ports

  • u – the signal, scalar.
  • Re – the in-phase part, scalar: the input delayed by N/2 samples, where N is the Filter Order.
  • Im – the quadrature part, scalar: the Hilbert transform of the input, aligned with Re.

Parameters

  • Filter Order – N, a whole even number from 2 to 64. An odd order is refused rather than rounded: an odd-order Hilbert transformer has a group delay of a half sample, and no delay line can produce that, so the in-phase port could never be aligned. The default is 32. The order is what buys accuracy, and it buys it in a band rather than everywhere – see Notes. The upper bound is a limit on the size of the exported code, not on the mathematics.
  • Window – how the ideal taps are tapered.
    • Hamming – 0.54 − 0.46·cos(2πn/N), the default and the usual choice.
    • Rectangular – no taper. Shorter transition band, much larger ripple; useful mainly for comparison.
  • Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period.

Code export

All ten targets: Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text.

The taps are designed once when the configuration is read and inlined; no exported core computes a sine, a window or a reciprocal. Every even offset from the centre is exactly zero and none of those taps is emitted, which is a property of the transform rather than of this design, so an order-32 filter costs 16 multiply-accumulates per sample rather than 33, and the in-phase output is taken from the shift register rather than from the same sum, the centre tap being one of the zeros.

The three HDL targets are genuine synthesizable Q16.16: this is a fixed-coefficient FIR over a shift register, with no division, no trigonometry and nothing that leaves the fixed-point datapath.

Simulink bridge

No equivalent a bridge can write (Support::None), and the reason is the wire rather than the block – the same reason Transforms / FFT gives. dspxfrm3/Analytic Signal exists in the DSP System Toolbox and is the same operation: measured in R2026a, it is a masked subsystem holding a Delay of N/2 and one FIR, and its real output reproduces the input delayed by N/2 to 1.6×10−13. But it carries its whole answer on one complex port, and an ICore signal carries doubles, so this block presents Re and Im on two real ports and there is no port-to-port mapping to write. In Simulink, pair dspxfrm3/Analytic Signal with simulink/Math Operations/Complex to Real-Imag: its two outputs are this block's two ports.

Notes

  • Stateful (an N-deep shift register) and discrete by nature (setDiscreteOnlyBlock(true)).
  • This is an FIR and hilbert() is not, which is a real difference and not a detail of implementation. MATLAB's function zeroes the negative-frequency half of the whole record's spectrum and transforms back – acausal, and impossible for a block that sees one sample at a time. What a streaming block can do is filter, and that is what this one does.
  • An FIR Hilbert transformer is flat over a BAND, not everywhere, and this is the thing to get right before using it. It has no response at DC and none at the Nyquist frequency, and the order decides how close to each it gets. Measured at a 100 Hz sampling rate with the Hamming window: order 16 is within 1% of unity gain from 9.9 Hz to 40.1 Hz, and order 32 from 5.0 Hz to 45.0 Hz. A component outside that band comes out with the wrong amplitude, and no amount of care elsewhere repairs it.
  • The taps are a window design, and Simulink's are equiripple. dspxfrm3/Analytic Signal holds firpm(N, [0.05 0.95], [1 1], 'hilbert') – measured, digit for digit, at N = 10 and N = 16. That is a Parks–McClellan design, and it is better at a given order: on a two-tone signal at order 16 it is out by 0.339 against this block's 0.577 (peak 1.377), and at order 32 by 0.128 against 0.253. By order 64 they are level, 0.083 against 0.084. So raise the order if the accuracy matters; an equiripple option belongs with an equiripple designer, which this library does not have yet.
  • The first N samples of a run are a transient, as they are for any FIR: the shift register starts at zero, so the output is only the transform of the input once the window has filled.
  • The recursion was checked outside this tree. Every language's body and the block's own reference come from one derivation, so nothing inside the project could have caught an index slip in it. Reproduced in R2026a at orders 18 and 6 and with both windows, the quadrature output matches filter(h, 1, x) to 1.8×10−15 and the in-phase output is the input delayed by N/2 exactly.
  • Scalar only: one signal, one shift register. Wire one block per channel.
  • No state space. A linear FIR does have one, but this block's output is a pair of taps off a delay line rather than a state evolution written in that form, and it carries none.

Code facts#

FactValue
registered typeControl_Systems/Transforms/Hilbert_Transform
familyControl_Systems/Transforms
solver environment classICoreBlock_0_Control_Systems_1_Transforms_2_Hilbert_Transform
sourcesrc/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Transforms/Hilbert_Transform/ICoreBlock_0_Control_Systems_1_Transforms_2_Hilbert_Transform.cpp
headersrc/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Transforms/Hilbert_Transform/ICoreBlock_0_Control_Systems_1_Transforms_2_Hilbert_Transform.h
default size on canvas128 × 80 px
ports at insert1 in, 2 out
code generators implementedPython, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text

Ports#

#DirectionSignal typeDescription label
1inICoreDoubleu
2outICoreDoubleRe
3outICoreDoubleIm

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 variableDefaultSimulink parameter
Filter Order32—
WindowHamming%~%Rectangular~~Hamming—

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.

supportSupport::None
Simulink path—
port-count rulePortsParam::None
SampleTime parameteryes

Caveat (shown to the user): no single-block equivalent, and the reason is the WIRE rather than the block, as with Transforms/FFT. dspxfrm3/Analytic Signal exists in the DSP System Toolbox and is the same operation -- measured in R2026a, it is a masked subsystem holding a Delay of N/2 and one FIR, and its real output is the input delayed by N/2 to 1.6e-13 -- 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/Analytic Signal with simulink/Math Operations/Complex to Real-Imag: its two outputs are this block's Re and Im ports. Note also that the two do not share coefficients: that block's mask holds firpm(N, [0.05 0.95], [1 1], 'hilbert'), an equiripple design, where this block uses the window method

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

Hilbert Transform -- the analytic signal as two real ports, from one windowed-sinc FIR MATLAB's hilbert(x) returns x + i*H{x} for a WHOLE record, by zeroing the negative-frequency half of its spectrum. That is acausal and this block cannot be it. What it is instead is the classical FIR Hilbert transformer:

h[n] = 2 / (pi * m), m = n - N/2, for ODD m; h[n] = 0 for even m Im[k] = SUM(n) h[n] * u[k-n] the Hilbert transform Re[k] = u[k - N/2] the input, delayed to match it

with the taps multiplied by a window, and N the filter order.

⚠ THE DELAY IS NOT A CONVENIENCE, IT IS WHAT MAKES THE TWO PORTS ONE SIGNAL. A type III FIR of order N has a group delay of exactly N/2 samples at every frequency, so the in-phase path must be delayed by the same N/2 or the pair is not an analytic signal at all -- the magnitude of Re + i*Im would not be an envelope and its angle would not be a phase. N is therefore required to be EVEN: an odd order is a type IV filter whose group delay is a HALF-integer number of samples, and no delay line can produce that.

⚠ HALF THE TAPS ARE EXACTLY ZERO AND NONE OF THEM IS EMITTED. Every even offset from the centre contributes nothing -- a property of the transform, not of this design -- so an order-32 filter costs 16 multiply-accumulates per sample rather than 33, in all ten targets. The centre tap is one of the zeros, which is why Re has to come from the shift register rather than from the same accumulation.

⚠⚠ THE COEFFICIENTS ARE DELIBERATELY NOT dspxfrm3/Analytic Signal's, AND THIS WAS MEASURED RATHER THAN ASSUMED. That block is a masked subsystem holding a Delay and one DiscreteFir, and its mask workspace variable b is exactly

firpm(N, [0.05 0.95], [1 1], 'hilbert')

-- an equiripple Parks-McClellan design, confirmed digit for digit at N = 10 and N = 16 in R2026a. Parks-McClellan is an iterative exchange algorithm, and it belongs with an equiripple filter DESIGNER rather than with this block; this library does not have one yet, and when it does, this block can grow an equiripple option. Until then it designs by the window method and says so, with the gap given as a number rather than as a caveat: at order 16 the taps differ by up to 0.133 against a largest tap of 0.634, and against the ideal transform of a two-tone signal the window design is out by 0.577 where the equiripple one is out by 0.339 (peak 1.377). At order 64 the two are level -- 0.084 against 0.083.

Sample results#

Hilbert Transform — Step: 0 -> 1 at t = 1 sHilbert Transform — Step: 0 -> 1 at t = 1 s-1-0.500.51012345t (s)in ICoreDouble-Out-0out ICoreDouble-Out-0out ICoreDouble-Out-1

The same rig also ran:

StimulusWhat it isOutput range
impulseImpulse: one sample of 1 at k = 5, 0 elsewhere (Repeating Sequence Stair)0 … 1
rampRamp: slope 1 from t = 00 … 4.2
sineSine Wave: amplitude 1, 2 rad/s, no phase, no bias-0.9962 … 0.9996
tableRepeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample-2 … 3

Plotted: step — Step: 0 -> 1 at t = 1 s

Category dynamic · sample time 0.1 · 60 steps · commit 05a7af9545eeffe8cf9dc83de88a165a633fca2a · produced by docsSample --out <folder> --blocks Modulator Demodulator Hilbert_Transform --steps 60 · data docs/generated/samples/Control_Systems__Transforms__Hilbert_Transform.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).