Downsample — Control Systems/Resampling
Control_Systems/Resampling/Downsample · 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.
Downsample
Control Systems / Resampling
Keeps one sample in every K and holds it until the next one is kept, so the output runs at the block's own rate rather than at a slower one:
y[k] = u[k] when k mod K = offset, and y[k] = y[k−1] on the K−1 samples in between. Before the first kept sample – that is, while k < offset – the block emits its Initial Condition. The sample index k counts from 0 at the start of the run.
Ports
- Input – the signal u to decimate, of any size [m,n]. Every entry is latched and held on the same samples; the block does not stagger them and the entries do not interact.
- Output – the decimated-and-held signal y, the SAME size [m,n] as the input.
Parameters
- Downsampling Factor – K, the number of samples in one period. A whole number of 1 or more; at K = 1 every sample is kept and the block is a wire. Defaults to 2.
- Sample Offset – which sample of each period is the kept one, as a whole number from 0 to K−1. At 0 the very first sample of the run is kept and the initial condition is never seen; at a larger value the block emits the initial condition for exactly that many samples first. Defaults to 0.
- Initial Condition – a scalar, what the block emits before the first kept sample. It is broadcast to every entry of the signal, so one value seeds a matrix signal whatever its size. Defaults to 0, and is only ever visible when the offset is nonzero.
- 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: K counts steps of this rate, so the retained stream comes out at one Kth of it.
Code export
All ten targets: Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text. The factor and the offset are baked into the core at export time rather than exposed as tunable parameters: they are structural, and a decimator whose period could be retuned on a built core would be a different block. The initial condition is baked in for the same reason – it seeds a state rather than scaling arithmetic.
The three HDL targets are not simulation-only here: the block does no arithmetic at all, so a core is an enable-gated register plus a small counter, and the Q-format word is copied rather than computed on – not one bit of it is disturbed between the input port and the output. What those columns do still carry is the port's own quantization, which is a property of an HDL signal and not of this block: measured over a 1001-sample export-verification run, the three of them come back at 0.00076–0.00095 % residual, the same figures a block that only passes its input through scores in the same run, while all seven software targets are exact at residual 0.
What every target does have to write the same way is the kept sample: it publishes the input directly rather than reading the register it is storing into, because an HDL register write is deferred to the clock edge and reading it back in the same tick would return the PREVIOUS kept sample.
Simulink bridge
Import and export, mapped to dspsigops/Downsample – the DSP System
Toolbox block, not a core Simulink one. "Downsampling Factor" to
DownsamplingFactor, "Sample Offset" to SampleOffset and
"Initial Condition" to InitialConditions, all three pass-through.
Three of that block's parameters are always emitted with a fixed value,
because this block offers no choice behind them:
FactorSource = Dialog parameter (the factor is a config
here, never a port), InputProcessing = Elements as channels
(sample based) and RateOptions = Enforce single-rate
processing. That last 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.
"Sampling Time (s)" does not cross. dspsigops/Downsample 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 retained sample and the phase counter. Both start from the configured initial condition and 0 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.
- Downsample vs. Zero-Order Hold. Both hold a value between updates, and the difference is what decides when an update happens. Zero-Order Hold updates on its own "Sampling Time (s)" – a period in seconds – and has neither a phase nor a seed. Downsample updates every Kth sample of the rate it is running at, counts that phase from the start of the run, and carries an initial condition for the samples before the first update. Reach for the first to model a sampler and for this one to decimate a stream by a whole factor.
- No anti-alias filter. This block keeps samples; it does not filter first, so anything above half the retained rate folds back into the band exactly as it does in the Simulink block. Put a low-pass in front of it if that matters.
- It is not MATLAB's
downsamplefunction, which returns a SHORTER vector. A wire cannot get shorter, so the retained samples are held instead – which is what the Simulink block does, and what this block is compared against.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Resampling/Downsample |
| family | Control_Systems/Resampling |
| solver environment class | ICoreBlock_0_Control_Systems_1_Resampling_2_Downsample |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Resampling/Downsample/ICoreBlock_0_Control_Systems_1_Resampling_2_Downsample.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Resampling/Downsample/ICoreBlock_0_Control_Systems_1_Resampling_2_Downsample.h |
| default size on canvas | 90 × 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 |
|---|---|---|
Downsampling Factor | 2 | DownsamplingFactor |
Sample Offset | 0 | SampleOffset |
Initial Condition | 0 | InitialConditions |
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 | dspsigops/Downsample |
| port-count rule | PortsParam::None |
SampleTime parameter | no — the counterpart defines none; the rate stays on the ICore side |
| always set | FactorSource = Dialog parameter, InputProcessing = Elements as channels (sample based), RateOptions = Enforce single-rate processing |
| ICore config | Simulink parameter | Value translation |
|---|---|---|
Downsampling Factor | DownsamplingFactor | passes through |
Sample Offset | SampleOffset | passes through |
Initial Condition | InitialConditions | passes through |
Caveat (shown to the user): dspsigops/Downsample 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
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).
Downsample block — keep every Kth sample and hold it, with a phase offset A single-rate decimator. It latches its input on one sample in every K and holds that value on the K-1 samples in between, so the output carries the retained samples at the SAME rate the block runs at rather than at a slower one:
y[k] = u[k] when k mod K = offset (a latch sample) y[k] = y[k-1] otherwise (a hold sample) y[k] = x0 before the first latch sample, i.e. while k < offset
k counts SAMPLES from the start of the run, beginning at 0.
⚠ THE SEMANTICS ARE MEASURED AGAINST R2026a, NOT INFERRED FROM THE FUNCTION
downsample. The MATLAB function returns a SHORTER vector; a wire cannot. What this block reproduces is the real Simulink block dspsigops/Downsample driven with InputProcessing = 'Elements as channels (sample based)' and RateOptions = 'Enforce single-rate processing', which is the one configuration of it whose output port runs at the input's own rate. Driven with u = 1..12 at K = 3 that block answersoffset 0 : 1 1 1 4 4 4 7 7 7 10 10 10 offset 1 : 0 2 2 2 5 5 5 8 8 8 11 11 (0 = InitialConditions) offset 2 : 0 0 3 3 3 6 6 6 9 9 9 12
and with InitialConditions = -9 the first row of offset 1 becomes -9. That is the recursion above, term for term, and it is what the block's Simulink entry claims.
⚠ THE LATCH SAMPLE PUBLISHES u, NOT THE REGISTER — and that is a rule the three HDL targets impose on the other seven rather than a choice. A held value written on a clocked tick is a REGISTERED assignment, so reading the register back in the same tick returns its PRE-clock contents: a body that stored u and then emitted the register would emit the PREVIOUS retained sample on every latch, and lag the reference by a whole interval on one sample in K. So every target writes the same branch — publish u on a latch sample, publish the register otherwise — and no target ever reads a value it is in the middle of writing.
DOWNSAMPLE vs. ZERO-ORDER HOLD. Both hold a value between updates and they are the most confusable pair in the library. The difference is what decides WHEN an update happens: Zero-Order Hold updates on its own "Sampling Time (s)", a PERIOD IN SECONDS, and has no phase and no initial condition; Downsample updates every Kth sample of whatever rate it is running at, counts that phase from the start of the run, and carries a seed for the samples before the first update. Reach for Zero-Order Hold to model a sampler, and for Downsample to decimate a stream by a whole factor.
Sample results#
| t | in ICoreDouble-Out-0 | out ICoreDouble-Out-0 |
|---|---|---|
| 0 | -2 | -2 |
| 0.4 | 0.5 | 0.5 |
| 0.8 | -2 | -2 |
| 1.2 | 0.5 | 0.5 |
| 1.6 | -2 | -2 |
| 2 | 0.5 | 0.5 |
| 2.4 | -2 | -2 |
| 2.8 | 0.5 | 0.5 |
| 3.2 | -2 | -2 |
| 3.6 | 0.5 | 0.5 |
| 4 | -2 | -2 |
| 4.4 | 0.5 | 0.5 |
| 4.8 | -2 | -2 |
| 5.2 | 0.5 | 0.5 |
Every 4th of 60 samples, from the table stimulus.
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 |
ramp | Ramp: slope 1 from t = 0 | 0 … 5.8 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | -0.9962 … 0.9996 |
step | Step: 0 -> 1 at t = 1 s | 0 … 1 |
Plotted: table — Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample
Category static · sample time 0.1 · 60 steps · commit 513f52f58 · produced by docsSample --out <folder> --blocks Downsample Upsample --steps 60 · data docs/generated/samples/Control_Systems__Resampling__Downsample.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).