Decimate — Control Systems/Resampling
Control_Systems/Resampling/Decimate · 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.
Decimate
Control Systems / Resampling
Low-pass filters a stream and then keeps one sample in every D, holding each retained sample until the next one, so the output runs at the block's own rate rather than at a slower one. The filter runs on every sample; only its publication is decimated:
w[k] = c0·u[k] + c1·u[k−1] + … + cn·u[k−n], then y[k] = w[k] when k mod D = 0 and y[k] = y[k−1] on the D−1 samples in between. The sample index k counts from 0 at the start of the run, so the very first sample is always a retained one, and the filter's delay line starts empty.
The filter is what separates this block from Downsample: anything above half the retained rate folds back into the band when it is not removed first.
Ports
- Input – the signal u to filter and decimate, of any size [m,n]. Every entry is filtered and retained on the same samples, each carrying its own delay line; the entries do not interact.
- Output – the filtered-and-held signal y, the SAME size [m,n] as the input.
Parameters
- FIR Coefficients – c0…cn, the anti-alias filter's impulse response as a non-empty vector, first entry multiplying the current sample and last the oldest. This block does not design the filter: pair it with FIR Window Design, or pass a response designed elsewhere. Defaults to [0.25 0.5 0.25], the three-tap half-band smoother that suits the default factor of 2.
- Decimation Factor – D, the number of samples in one period, a whole number of 1 or more. At D = 1 every filtered sample is published and the block is a plain FIR filter. Defaults to 2.
- Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period. On this block it also sets what one "sample" means: D counts steps of this rate, so the retained stream comes out at one Dth of it.
Code export
All ten targets: Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text. The coefficients and the factor are baked into the core at export time rather than exposed as tunable parameters: the tap count also fixes how many state words the core carries, and a decimator whose period could be retuned on a built core would be a different block.
The three HDL targets are not simulation-only. The arithmetic is one multiply-accumulate over a short constant tap list, which the Q16.16 datapath carries without difficulty; what those columns do carry is the port's own quantization, which is a property of an HDL signal rather than of this block. Measured over a 1001-sample export-verification run at four taps and factor 3, the three of them come back at 0.0031–0.0036 % residual while the software targets are exact or within one double rounding (0 to 1.2×10 −14 %). The delay line is emitted unrolled, so a long filter on a large signal is a large entity: the state word count is n×m×p for a filter with n taps behind the current sample on an [m,p] signal, plus one word per signal entry for the retained sample.
What every target writes the same way is the retained sample: it publishes the freshly computed sum directly rather than reading back the register it is storing into, because an HDL register write is deferred to the clock edge and reading it in the same tick would return the previous retained sample.
Simulink bridge
Import and export, mapped to dspmlti4/FIR Decimation – the DSP
System Toolbox block, not a core Simulink one. "FIR Coefficients" to
h and "Decimation Factor" to D, both pass-through.
Five of that block's parameters are always emitted with a fixed value, because
this block offers no choice behind them: FilterSource =
Dialog parameters (the taps are a config here, never a port and never
a filter object), filtStruct = Direct form,
InputProcessing = Elements as channels (sample based),
framing = Enforce single-rate processing and
outputBufInitCond = 0. That middle pair is what makes
the crossing exact: it is the one configuration in which the Simulink block's
output port runs at its input's rate, which is the only kind of block an ICore
wire can carry. The last one is pinned rather than mapped because it is
unobservable in that configuration – with no phase offset the first sample
is itself a retained one, and a run at 0 and a run at −9 are identical to
the digit.
"Sampling Time (s)" does not cross. dspmlti4/FIR Decimation defines no
SampleTime parameter at all – verified against the R2026a block
dialog – and set_param on a parameter a block does not define
is a hard error in MATLAB that aborts the whole generated script rather
than degrading. The rate stays on the ICore side, and a block configured with an
explicit positive rate reports that it did not cross.
Notes
- Stateful: the filter's delay line, the retained sample and the phase counter. All three start from zero at the beginning of every run, so a re-run reproduces the stream exactly.
- Discrete by nature – the phase advances once per sample, so the block always takes its period from its own "Sampling Time (s)" and is never pushed through a continuous solver's intermediate stages.
- No state space, deliberately. The filter alone would be linear, but which sample is published depends on WHEN it arrives, so no single A/B/C/D describes the block and fabricating one would let the model-reduction commands absorb it into a neighbour as a filter that publishes everything.
- It is not MATLAB's
decimatefunction, which filters with a forward-and-backward pass that is not causal and then returns a SHORTER vector. A stream can carry neither, so the filter here is the ordinary causal one and the retained samples are held.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Resampling/Decimate |
| family | Control_Systems/Resampling |
| solver environment class | ICoreBlock_0_Control_Systems_1_Resampling_2_Decimate |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Resampling/Decimate/ICoreBlock_0_Control_Systems_1_Resampling_2_Decimate.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Resampling/Decimate/ICoreBlock_0_Control_Systems_1_Resampling_2_Decimate.h |
| default size on canvas | 100 × 70 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 | — |
| 2 | out | ICoreDouble | — |
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 |
|---|---|---|
FIR Coefficients | [0.25 0.5 0.25] | h |
Decimation Factor | 2 | D |
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::Both |
| Simulink path | dspmlti4/FIR Decimation |
| port-count rule | PortsParam::None |
SampleTime parameter | no — the counterpart defines none; the rate stays on the ICore side |
| always set | FilterSource = Dialog parameters, filtStruct = Direct form, InputProcessing = Elements as channels (sample based), framing = Enforce single-rate processing, outputBufInitCond = 0 |
| ICore config | Simulink parameter | Value translation |
|---|---|---|
FIR Coefficients | h | passes through |
Decimation Factor | D | passes through |
Caveat (shown to the user): dspmlti4/FIR Decimation has NO SampleTime parameter (verified against the R2026a block dialog), so "Sampling Time (s)" does not cross. The entry also pins the block to sample-based, single-rate processing - the one configuration whose output port runs at its input's rate, and therefore the only one an ICore wire can carry - and pins outputBufInitCond to 0, which that configuration makes unobservable: with no phase offset the first sample is itself a retained one, measured identical at 0 and at -9
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).
Decimate block — anti-alias FIR, then keep one sample in every D and hold it A single-rate decimator with a filter in front of it. The FIR runs at the FULL input rate, every sample; only its PUBLICATION is decimated, so the output carries the retained samples at the same rate the block runs at rather than at a slower one:
w[k] = c0*u[k] + c1*u[k-1] + ... + cn*u[k-n] the anti-alias filter, every sample y[k] = w[k] when k mod D = 0 (a retained sample) y[k] = y[k-1] otherwise (a hold sample)
k counts SAMPLES from the start of the run, beginning at 0, and the filter's delay line starts empty (zeros), exactly as the reference block's does.
⚠ THE SEMANTICS ARE MEASURED AGAINST R2026a, NOT INFERRED FROM THE FUNCTION
decimate. That function low-pass filters withfiltfilt— a forward-and-backward pass that is not causal and cannot be run on a stream at all — and then returns a SHORTER vector. A wire can do neither. What this block reproduces is the real Simulink block dspmlti4/FIR Decimation driven with FilterSource = 'Dialog parameters', InputProcessing = 'Elements as channels (sample based)' and framing = 'Enforce single-rate processing', which is the one configuration of it whose output port runs at the input's own rate. Measured with u = 1..12, h = [1 2 3] and D = 3, that block answers1 1 1 16 16 16 34 34 34 52 52 52
which is the recursion above term for term: 1*1 at k = 0, then 4 + 2*3 + 3*2 = 16 at k = 3.
⚠ ITS
outputBufInitCondIS NOT CARRIED HERE, AND THE REASON IS A MEASUREMENT. The real block offers a seed for the samples before the first retained one, but this configuration has none: there is no phase offset, so sample 0 is itself a retained sample and the seed is never published. Measured by running the same stimulus at outputBufInitCond = 0 and again at -9: the two runs are identical, to the digit. A config carrying a value nothing can observe would be worse than no config, so the entry pins the parameter to 0 instead.⚠ THE RETAINED SAMPLE PUBLISHES THE FRESHLY COMPUTED SUM, NOT THE HELD REGISTER — the same rule Downsample is written to, and for the same reason. A held value written on a clocked tick is a REGISTERED assignment, so reading it back in the same tick returns its pre-clock contents: a body that stored the sum and then emitted the register would publish the PREVIOUS retained sample and lag the reference by a whole interval on one sample in D. Every one of the ten targets writes the same branch.
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.5 |
ramp | Ramp: slope 1 from t = 0 | 0 … 5.7 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | -0.99 … 0.9886 |
table | Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample | -1.125 … 1.5 |
Plotted: step — Step: 0 -> 1 at t = 1 s
Category dynamic · sample time 0.1 · 60 steps · commit 9237993cf · produced by docsSample --out <folder> --blocks Decimate Interpolate Upfirdn --steps 60 · data docs/generated/samples/Control_Systems__Resampling__Decimate.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).