Welch PSD — Control Systems/Spectral Measurements
Control_Systems/Spectral_Measurements/Welch_PSD · 1 input / 1 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.
Welch PSD
Control Systems / Spectral Measurements
The one-sided power spectral density of the signal, by Welch's method – the average of K windowed periodograms taken from overlapping segments of a running window:
P(f) = (1 ÷ (K·fs·Σw²)) · Σk |FFT(w·segmentk)|²
This is MATLAB's pwelch. Averaging is what separates it from a
single periodogram: one periodogram's variance does not fall however long the
record, and K of them averaged reduce it by K.
Ports
- u – the sampled signal. Scalar: one channel and its own window.
- p – the density, [L/2+1, 1], one-sided with DC first and Nyquist last. This is the vector every other block in this family takes on its own p port, so it wires straight into Band Power, Occupied Bandwidth, Spectral Entropy and the rest.
Parameters
- Segment Length – L, the transform length. An even whole number from 4 to 32; even because a one-sided spectrum needs a Nyquist bin. Bounded above because the transform is unrolled at export and its cost grows as L².
- Segments – K, how many are averaged, from 1 to 8. More segments cut the variance and lengthen the window the block looks back over. K = 1 is allowed and is an ordinary windowed periodogram – it is not refused here, unlike on Magnitude-Squared Coherence, because a single periodogram is still a meaningful density.
- Segment Overlap – how far each segment is advanced:
- None (0%) – segments do not share samples.
- Half (50%) – MATLAB's default, and the one that recovers the most information from a given window.
- Three quarters (75%) – more segments from the same samples, at the cost of correlating them.
- Window – Hamming (MATLAB's default), Hann or
Rectangular. These are the symmetric windows, which is what
MATLAB's
hamming()andhann()return. - Bin Spacing – Δf, the frequency step between output bins, in hertz. The sampling rate follows as fs = L·Δf and is what makes the answer a density rather than a power. It is the same parameter, under the same name, that Band Power and the rest of this family read.
- 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.
No sine, no cosine and no division survives into a generated core. The window, the transform's basis and the whole 1÷(K·fs·Σw²) scale are fixed by the configuration, so they are computed once at export time and a core carries the resulting numbers: two dot products per bin per segment, a square each, and one scale. All five settings are structural – they decide how many multiplies and how many outputs the core contains – so re-export after changing any of them.
The three HDL targets are genuine synthesizable Q16.16. ⚠ What to watch there is range rather than precision: the block squares, so an input that reaches ±180 already reaches the format's ceiling before any scaling, and a small Bin Spacing divides the answer down into the quantum from the other end. Scale the signal into roughly ±1 first.
Simulink bridge
None (Support::None). pwelch is a Signal
Processing Toolbox function and that toolbox ships no Simulink library, so
there is no path a diagram could name. The bridge reports this block rather than
dropping it silently, and it therefore has no parity testbench; code
export verification still covers it across all ten languages.
Notes
- Stateful, and discrete by nature
(
setDiscreteOnlyBlock(true)): one shift register of L + (K−1)·hop samples. - ⚠ It is the producer this family was missing. Band Power, Occupied Bandwidth, Mean Frequency, Spectral Entropy, SNR, THD and the rest all take a spectrum on a p port and answer a measurement about it; this block makes that vector.
- ⚠ It is not FFT Magnitude, and the difference is not cosmetic. FFT Magnitude emits a single periodogram: full length rather than one-sided, unwindowed, and with no scaling at all, so it is a spectrum but not a density – and its variance does not fall however long the record is. Averaging K windowed segments is exactly what fixes that, and it is the whole point of Welch's method.
- ⚠ Three conventions are MATLAB's and each is a silent wrong answer if
guessed. The divisor is K·fs·Σw²
and not the segment length; the interior bins are doubled while DC and
Nyquist are not; and the windows are the symmetric ones, so
hamming(8)starts at 0.08. All three were measured against R2026a rather than remembered – see this block's source banner for the five-bin comparison, which agrees to 6.9e−18. - The window is zero-prefilled, so the first L + (K−1)·hop − 1 spectra of a run are a startup transient.
- Scalar only. One channel and its own history; wire one block per channel.
- No state space. |X|² is not linear in the window, so model reduction correctly declines to merge it.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Spectral_Measurements/Welch_PSD |
| family | Control_Systems/Spectral_Measurements |
| solver environment class | ICoreBlock_0_Control_Systems_1_Spectral_Measurements_2_Welch_PSD |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Spectral_Measurements/Welch_PSD/ICoreBlock_0_Control_Systems_1_Spectral_Measurements_2_Welch_PSD.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Spectral_Measurements/Welch_PSD/ICoreBlock_0_Control_Systems_1_Spectral_Measurements_2_Welch_PSD.h |
| default size on canvas | 140 × 76 px |
| ports at insert | 1 in, 1 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 | p |
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 |
|---|---|---|
Segment Length | 8 | — |
Segments | 4 | — |
Segment Overlap | Half (50%)%~%None (0%)%~%Three quarters (75%)~~Half (50%) | — |
Window | Hamming%~%Hann%~%Rectangular~~Hamming | — |
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): pwelch is a Signal Processing Toolbox function, not a Simulink library block -- that toolbox ships no Simulink library at all -- so there is no path a diagram could name; the block is reported rather than dropped when a model crosses
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).
Welch PSD -- the one-sided power spectral density of a stream, MATLAB's pwelch Pxx(f) = ( 1 / (K * fs * SUM w[n]^2) ) * SUM(k = 1..K) | FFT(w .* segment_k) |^2
with the interior bins doubled to fold the negative frequencies onto the positive ones. The window runs over the last L + (K-1)*hop samples, split into K overlapping segments.
MEASURED AGAINST R2026a AND THE FORMULATION IS WHAT WAS CHECKED, not just the answer. At L = 8, K = 4, 50 % overlap, a Hamming window and fs = 100, over the twenty-sample record
x = [1.2 0.7 2.5 0.3 0.12 0.8 1.1 -0.22 0.45 1.7 0.35 0.62 0.09 -1.4 0.66 0.2 -0.9 1.05 0.4 -0.55]
pwelch(x, hamming(8), 4, 8, 100) answers
[0.013796868366291216 0.0089225319785261516 0.015031589143953035 0.017827976986234836 0.0018547979135150844]
and the expression above, written out segment by segment, reproduces all five bins to 6.9e-18. That is what pins the three conventions a reader would otherwise guess: the divisor is K * fs * SUM(w^2) and NOT the window length; the interior bins are doubled and DC and Nyquist are not; and MATLAB's hamming(L) is the SYMMETRIC window, denominator L-1, so hamming(8) starts at 0.08 -- the same convention Magnitude_Squared_Coherence already uses.
ONE SOLVE, THEN ARITHMETIC. The window, the DFT basis and the whole scale are fixed by the configuration, so a generated core carries numbers: no sine, no cosine, no division. What does survive is a square per bin per segment, so the three HDL targets are genuine Q16.16 with a RANGE to watch rather than a precision problem.
Sample results#
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 … 0.01028 |
ramp | Ramp: slope 1 from t = 0 | 0 … 15.85 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | 0 … 0.312 |
table | Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample | 2.869e-4 … 0.5499 |
Plotted: step — Step: 0 -> 1 at t = 1 s
Category dynamic · sample time 0.1 · 60 steps · commit b4ab100a9db12a091c23261d81c2f3298500a06a · produced by docsSample --out <folder> --blocks Welch_PSD --steps 60 · data docs/generated/samples/Control_Systems__Spectral_Measurements__Welch_PSD.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).