Generated reference › THD — Control Systems/Spectral Measurements
kind: generated#block#control-systems-spectral-measurements

THD — Control Systems/Spectral Measurements

Control_Systems/Spectral_Measurements/THD · 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.

THD

Control Systems / Spectral Measurements

The total harmonic distortion of a one-sided power spectral density, in decibels: the summed power of the fundamental's harmonics over the power of the fundamental itself. r = 10·log₁₀(ΣPharm / Pfund), so a clean signal reads a large negative number. This is MATLAB's thd(Pxx, F, 'psd'), transcribed from its source.

DC is removed first. The fundamental is then the largest bin left, and each harmonic is sought at a multiple of its centroid frequency; every tone's power is integrated over a band – the run of bins that climbs to its peak and falls away from it – rather than read off one bin.

Ports

  • p – the one-sided power spectral density, an [N,1] column or a [1,N] row of 3 to 64 bins. Bin k sits at frequency k·Δ, so the first bin is DC. Values are expected non-negative, as MATLAB requires.
  • thd – the distortion in dB, a scalar, normally negative.
  • ffund – the fundamental's centroid frequency in the axis's own units, a scalar. Every harmonic is sought at a multiple of this, so it is the one number to check a surprising reading against.

Parameters

  • Bin Spacing – Δ, the frequency step between neighbouring bins, a single positive number. The whole frequency axis follows from it.
  • Harmonics – how many harmonics are summed, counting the fundamental itself as the first. A whole number between 2 and 12; MATLAB's default is 6, which is this block's default too. A harmonic whose frequency lands past the end of the axis is skipped, not clamped, so a coarse axis simply sums fewer of them and a reading can stop changing well before the count is reached.
  • 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 bin count, the spacing and the harmonic count are all settled before the run, so the whole search is unrolled at export time and no emitted core contains a loop or an array index that moves. This block takes 64 bins where its two siblings take 32, because it needs no noise floor and therefore no median – and it is the median that costs N² statements.

The three HDL targets are simulation-only: they carry the arithmetic in real and quantize only at the port boundary. A logarithm and a ratio of two run-time quantities do not belong in a Q16.16 datapath.

Simulink bridge

No equivalent, so nothing crosses in either direction. Measured rather than assumed: thd is a Signal Processing Toolbox function, that toolbox ships no Simulink library at all, and a find_system sweep of 2102 blocks across twenty DSP System Toolbox and Simulink library roots – at depth 6, under masks – matches nothing for snr, sinad, thd, sfdr, distortion, spurious or noise ratio. Mixed-Signal Blockset, which does carry measurement blocks of this kind, is not installed and is not one of the eight toolboxes this work covers.

Notes

  • Algebraic and stateless: the reading depends only on the spectrum presented this step.
  • The fundamental is NOT removed before the harmonics are sought, and that is deliberate – it is what thd.m does. A standing fundamental bounds the second harmonic's band on its left, so removing it first changes the answer: measured, −5.23 dB instead of MATLAB's −3.79 dB on the twelve-bin spectrum in the source.
  • A harmonic's band is not removed once counted either, so on a coarse axis the same bin can be counted as two harmonics. Measured: on [0 0 3 0 0 1 0 0] at Δ = 0.5 the second and third harmonics both resolve to 2.5 Hz, and the reading moves from −4.77 dB to −1.76 dB between two and four harmonics. SNR does remove each band, and its reading on that spectrum does not move at all.
  • It is not SNR or SINAD upside down. Those two put a noise median in the denominator and this one puts the fundamental there; all three share one decomposition, and only the stages that fire differ.
  • A spectrum with no harmonic inside the axis reports a floored reading rather than minus infinity, and a spectrum with no power at all has no fundamental: both are floored rather than left as a division by zero, because a real division by zero aborts a VHDL simulation instead of producing an infinity.

Code facts#

FactValue
registered typeControl_Systems/Spectral_Measurements/THD
familyControl_Systems/Spectral_Measurements
solver environment classICoreBlock_0_Control_Systems_1_Spectral_Measurements_2_THD
sourcesrc/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Spectral_Measurements/THD/ICoreBlock_0_Control_Systems_1_Spectral_Measurements_2_THD.cpp
headersrc/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Spectral_Measurements/THD/ICoreBlock_0_Control_Systems_1_Spectral_Measurements_2_THD.h
default size on canvas140 × 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
1inICoreDoublep
2outICoreDoublethd
3outICoreDoubleffund

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
Bin Spacing1—
Harmonics6—

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 Simulink equivalent. snr is a Signal Processing Toolbox MATLAB function, not a block; that toolbox ships no Simulink library, and a sweep of 2102 blocks across twenty DSP System Toolbox and Simulink library roots carries no signal-to-noise, SINAD, THD or SFDR measurement block

Catalog contract: src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h

Description vs code#

The checker has a blind spot here — it could not resolve something (a grouped port bullet, a computed config name), which is reported and never counted as a pass. A reader has to settle it:

  • B0 every stimulus in the sample errored — cross-checks skipped

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

THD -- total harmonic distortion, from a one-sided spectrum (MATLAB thd(Pxx, F, 'psd')) r = 10*log10( SUM(Pharm) / Pfund ) normally a negative number of dB

The same decomposition SNR and SINAD use -- ICoreSpectralDistortionSupport.h -- read out the other way up, and with TWO of its stages deliberately not firing. Both of those omissions change the answer, and both were measured rather than reasoned about:

⚠ THE FUNDAMENTAL IS NOT REMOVED. A tone's band is the run that climbs to its peak and falls away from it, so a standing fundamental BOUNDS the second harmonic's band on its left. On p1 below, removing it first answers -5.2288 dB where MATLAB answers -3.7919 dB. This was a real defect in this block's first reference, caught by the measurement and not by any test: the number it produced was entirely plausible.

⚠ A HARMONIC'S BAND IS NOT ZEROED ONCE COUNTED, so on a coarse axis the SAME BIN can be counted as two harmonics. On p3 the second and third harmonics both resolve to 2.5 Hz, and MATLAB's answer moves from -4.7712 dB at nHarm = 2 to -1.7609 dB at nHarm = 4 -- while SNR's, which does zero, does not move at all. A block that zeroed would report the nHarm = 2 number twice and look perfectly reasonable.

⚠ MEASURED AGAINST R2026a BEFORE ANY OF THIS WAS WRITTEN, and twice over -- run in the installed R2026a, reproduced by a Python stand-in, then reproduced again by compiling the shared reference OUTSIDE the app. Worst disagreement across the family's 35 readings: 9.9e-16 relative. The EMITTED statement list was then compiled as a C program and diffed against that reference over 140 values: bit-identical, 0.

p1 = [0.05 0.10 3.00 0.40 0.90 0.12 0.30 0.08 0.20 0.06 0.15 0.04], delta = 0.25 nHarm 2 -> -3.7919057265919864 dB nHarm 4 -> -1.7737047789485421 dB nHarm 6 -> -1.3180519556121699 dB fundamental at 0.52941176470588236 Hz p2 = [0.12 0.85 2.40 1.10 0.35 0.60 0.18 0.07], delta = 0.5 -> -5.0627948346087583 dB at every nHarm; the 3rd harmonic and up land past the end of the axis and are SKIPPED rather than clamped p3 = [0 0 3 0 0 1 0 0], delta = 0.5 nHarm 2 -> -4.7712125471966251 dB nHarm 4 and 6 -> -1.7609125905568126 dB p4 = sixteen bins at delta = 0.2 -> -4.6148 / -1.9713 / -1.9713 dB p5 = [2.50 0.80 0.30 1.90 0.25 0.40 0.15 0.06], delta = 0.5 -> -3.9794000867203758 dB

⚠ THIS BLOCK TAKES 64 BINS WHERE ITS TWO SIBLINGS TAKE 32, and the reason is worth knowing: it needs no noise floor, so it needs no MEDIAN, so the emitted body carries none of the O(N^2) rank selection that bounds the other two.

Sample results#

No stimulus produced a sampled output in this rig — Invalid input size at: ICore Blocks/Home/THD. That is a fact about the single-block rig, not a verdict on the block: an offline batch fit, a block whose output only appears at onSolverFinish, or one that needs a driven environment cannot be exercised alone.

Category unsampled · sample time 0.1 · 60 steps · commit 477e53523 · produced by docsSample --out <folder> --blocks SNR SINAD THD SFDR --steps 60

Sample data: docs/generated/samples/Control_Systems__Spectral_Measurements__THD.json