SINAD — Control Systems/Spectral Measurements
Control_Systems/Spectral_Measurements/SINAD · 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.
SINAD
Control Systems / Spectral Measurements
The signal-to-noise-and-distortion ratio of a one-sided power spectral
density, in decibels: the power of the fundamental tone over the power of
everything else that is not DC – noise and harmonic distortion
together.
r = 10·log₁₀(Pfund / Pnoise+dist).
This is MATLAB's sinad(Pxx, F, 'psd'), transcribed from its
source.
DC and the fundamental are each measured over a band – the run of bins that climbs to a peak and falls away from it – and removed. Everything left is the denominator: its floor is the median of the surviving bins, written back into the removed ones and then capped bin by bin at the original density.
Ports
- p – the one-sided power spectral density, an [N,1] column or a [1,N] row of 3 to 32 bins. Bin k sits at frequency k·Δ, so the first bin is DC. Values are expected non-negative, as MATLAB requires.
- sinad – the ratio in dB, a scalar.
- ndpow – the integrated noise-and-distortion power in dB, a scalar: the denominator above, reported on its own.
Parameters
- Bin Spacing – Δ, the frequency step between neighbouring bins, a single positive number. The whole frequency axis follows from it.
- Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period.
There is deliberately no harmonic count: nothing in this measurement looks for a harmonic, so there is nothing for a count to bound. SNR and THD both take one.
Code export
All ten targets: Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text. The decomposition is unrolled at export time, so no emitted core contains a loop, a moving array index, or a sort: the median is found by rank selection, which costs N² comparisons and is why the window is capped at 32 bins rather than the 64 the rest of the family allows.
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, and VHDL has no real minimum before 2008, so the smaller
of two values is spelled arithmetically there – which is not bit-exact, and
is why those three columns are compared in a band.
Simulink bridge
No equivalent, so nothing crosses in either direction. Measured rather
than assumed: sinad 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.
- It is SNR with the harmonic stage switched off, and they share one implementation. THD is the third reading of the same decomposition.
- It is NOT always the lower of the two. Counting the harmonics as noise raises this denominator, but removing them also takes those bins out of the set SNR's noise median is drawn from, which can raise SNR's denominator by more. Measured on the twelve-bin spectrum in the source at six harmonics: SNR reads −2.01 dB where this block reads +1.75 dB.
- The DC bin is doubled before anything looks at the spectrum, and the DC component is then the larger of bins 0 and 1 rather than bin 0.
- A spectrum with no power at all has no fundamental. Both readings are
then floored rather than left as a division by zero, because a
realdivision by zero aborts a VHDL simulation instead of producing an infinity.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Spectral_Measurements/SINAD |
| family | Control_Systems/Spectral_Measurements |
| solver environment class | ICoreBlock_0_Control_Systems_1_Spectral_Measurements_2_SINAD |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Spectral_Measurements/SINAD/ICoreBlock_0_Control_Systems_1_Spectral_Measurements_2_SINAD.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Spectral_Measurements/SINAD/ICoreBlock_0_Control_Systems_1_Spectral_Measurements_2_SINAD.h |
| default size on canvas | 140 × 80 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 | p |
| 2 | out | ICoreDouble | sinad |
| 3 | out | ICoreDouble | ndpow |
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 |
|---|---|---|
Bin Spacing | 1 | — |
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 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:
B0every 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).
SINAD -- signal to noise AND distortion, from a one-sided spectrum (MATLAB sinad(Pxx, F, 'psd')) r = 10*log10( Pfund / (noise + distortion) )
⚠ IT IS SNR WITH ONE STAGE SWITCHED OFF. sinad.m is snr.m without the harmonic loop, and this block is the shared decomposition in ICoreSpectralDistortionSupport.h asked for without harmonic removal -- so the two readings cannot drift apart, and on any signal that distorts this one is the LOWER. It follows that the block takes no harmonic count: nothing here looks for a harmonic, so there is nothing for a count to bound.
⚠ MEASURED AGAINST R2026a BEFORE ANY OF THIS WAS WRITTEN, and twice over -- the five spectra were run through sinad in the installed R2026a, reproduced by a Python stand-in, and then reproduced again by compiling the shared reference OUTSIDE the app. Worst disagreement over the family's 35 readings: 9.9e-16 relative, all of it summation order in a dot product. 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 -> 1.7545305984913238 dB, distortion power -2.4603413413483968 dB p2 = [0.12 0.85 2.40 1.10 0.35 0.60 0.18 0.07], delta = 0.5 -> 3.5757402489482719 dB, distortion power -0.73143291050307679 dB p3 = [0 0 3 0 0 1 0 0], delta = 0.5 -> 1.7609125905568124 dB, distortion power EXACTLY 0 dB p4 = sixteen bins at delta = 0.2 -> 1.4966369549314373 dB p5 = [2.50 0.80 0.30 1.90 0.25 0.40 0.15 0.06], delta = 0.5 -> 1.9889955154538785 dB
⚠ EVERY ONE OF THOSE FIVE IS HIGHER THAN THIS FAMILY'S SNR READING ON THE SAME SPECTRUM EXCEPT p1 AND p4, AND THAT IS THE FINDING WORTH HAVING. "SINAD counts more as noise, so it must read lower" is true of the NUMERATOR's neighbourhood and false overall, because removing a harmonic band also removes those bins from the set the noise MEDIAN is drawn from -- and a median taken over fewer, quieter bins can be LOWER, which lifts SNR's denominator. On p1 at nHarm = 6 that effect is worth 3.8 dB in the wrong direction: SNR reads -2.01 dB where SINAD reads +1.75 dB. So the inequality holds per spectrum only once the median is accounted for, and a test that asserted it would fail on two of five ordinary spectra.
⚠ p3 IS THE EDGE THAT ANSWERS 0 dB EXACTLY, because its surviving bins integrate to exactly 1.0 at this spacing. A block whose floor under log10 was too coarse answers something else.
The three HDL targets are SIMULATION-ONLY: a base-10 logarithm, a division of two run-time quantities, and a min/max spelled as arithmetic because VHDL has no real
minimumbefore
Sample results#
No stimulus produced a sampled output in this rig — Invalid input size at: ICore Blocks/Home/SINAD. 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__SINAD.json