Upfirdn — Control Systems/Resampling
Control_Systems/Resampling/Upfirdn · 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.
Upfirdn
Control Systems / Resampling
A rational rate change by L/M in one stage: insert L−1 zeros between input samples, filter once, and keep one sample in every M, holding each kept sample until the next. One filter does both jobs – it removes the images the stuffing created AND the band the discarding would otherwise alias – which is why the three steps belong in one block rather than three.
v[k] = u[k] when k mod L = 0 and 0 otherwise; then w[k] = c0·v[k] + c1·v[k−1] + … + cn·v[k−n]; then y[k] = w[k] when k mod M = 0 and y[k] = y[k−1] in between. The sample index k counts from 0 at the start of the run, so the first tick both takes a sample and publishes one, and the filter's delay line starts empty.
The block runs at the fast rate – L times the input's – and its kept samples come out at the input rate times L/M. Drive it from a source running at L times this block's "Sampling Time (s)".
Ports
- Input – the signal u to resample, of any size [m,n]. Every entry is stuffed, filtered and kept on the same ticks, each carrying its own delay line; the entries do not interact.
- Output – the resampled-and-held signal y, the SAME size [m,n] as the input, at the block's own (fast) rate.
Parameters
- FIR Coefficients – c0…cn, the single filter's impulse response as a non-empty vector, first entry multiplying the current stuffed 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 [1 1 1], a boxcar of length L which at the default factors holds each input sample across its L stuffed slots – the crudest correct interpolation, and the easiest to read.
- Upsampling Factor – L, the number of ticks per input sample, a whole number of 1 or more. At L = 1 nothing is stuffed. Defaults to 3.
- Downsampling Factor – M, the number of ticks per kept sample, a whole number of 1 or more. At M = 1 every filtered sample is kept. 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 that is the fast rate: a new input sample is taken every Lth step of it and a sample is published every Mth.
Code export
All ten targets: Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text. The coefficients and both factors 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 rate change whose ratio 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. 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.
Two things every target writes the same way. The stuffed zero is shifted into the delay line explicitly, because a wire holds its upstream value and a body that read the port every tick would filter a held staircase. And the kept sample publishes the freshly computed sum rather than reading back the register it is storing into, because an HDL register write is deferred to the clock edge.
Simulink bridge
Neither imported nor exported, and the reason is that its counterpart has
no sample-based mode at all. dspmlti4/FIR Rate Conversion is a real DSP
System Toolbox block that computes exactly this, and it is named here rather than
declared absent – but its dialog carries no InputProcessing
parameter: it is frame-based, and under rateMode =
Enforce single-rate processing it requires an input frame whose row
count is a multiple of M, refusing a one-element signal in as many words. An ICore
wire carries one sample per tick, so there is no configuration of that block for a
bridge to name.
Reproduce it instead by running the SOURCE at L times this block's "Sampling
Time (s)" and reading the kept samples, which is what upfirdn returns.
Notes
- Stateful: the filter's delay line over the stuffed stream, the retained sample, and TWO phase counters – one mod L for which tick takes a sample, one mod M for which tick publishes. All start from zero at the beginning of every run, so a re-run reproduces the stream exactly.
- Discrete by nature – the phases advance 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. Both which sample enters the filter and which leaves it depend on WHEN it arrives, so the block is periodically time-varying and no single A/B/C/D describes it.
- Upfirdn vs. Interpolate vs. Decimate. All three are the same delay line with different things switched on: Interpolate stuffs and filters (M = 1), Decimate filters and keeps (L = 1), and this block does both with ONE filter between them – the only way to change the rate by a ratio that is not a whole number without filtering twice. At L = M it is a plain FIR filter with a longer name.
- The filter's gain is yours to choose. Stuffing divides the average power by L, so the response is usually given a passband gain of L to put it back – which is why the default [1 1 1] sums to 3 at the default L of 3 rather than to 1.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Resampling/Upfirdn |
| family | Control_Systems/Resampling |
| solver environment class | ICoreBlock_0_Control_Systems_1_Resampling_2_Upfirdn |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Resampling/Upfirdn/ICoreBlock_0_Control_Systems_1_Resampling_2_Upfirdn.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Resampling/Upfirdn/ICoreBlock_0_Control_Systems_1_Resampling_2_Upfirdn.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 | [1 1 1] | not crossed |
Upsampling Factor | 3 | not crossed |
Downsampling Factor | 2 | not crossed |
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 |
| deliberately not crossed | FIR Coefficients, Upsampling Factor, Downsampling Factor |
Caveat (shown to the user): dspmlti4/FIR Rate Conversion computes exactly this block's arithmetic, and it is named here rather than declared absent - but IT HAS NO SAMPLE-BASED MODE AT ALL. Its dialog carries no InputProcessing parameter: it is frame-based, and under rateMode = 'Enforce single-rate processing' it requires an input frame whose row count is a multiple of M. Measured on R2026a, driven one sample at a time, it refuses in as many words: "the number of rows in the input must be a multiple of the decimation factor 2 ... 'Output Port 1' of '.../In1' is a one dimensional vector with 1 elements". An ICore wire carries one sample per tick, so there is no configuration of that block for a bridge to name. Reproduce it by running the SOURCE at L times this block's "Sampling Time (s)" and reading the kept samples, which is what MATLAB's upfirdn returns
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).
Upfirdn block — zero-stuff by L, FIR filter, keep one sample in every M, in one stage A rational rate change, L/M, done the way it has to be done: ONE filter between the stuffing and the throwing away, so the images the stuffing created and the band the discarding would alias are removed by the same taps. The block runs at the FAST rate — the rate the stuffed stream has, L times the input's — and publishes one sample in every M:
v[k] = u[k] when k mod L = 0 (a new input sample enters) v[k] = 0 otherwise (a stuffed zero) w[k] = c0*v[k] + c1*v[k-1] + ... + cn*v[k-n] y[k] = w[k] when k mod M = 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). The retained samples therefore come out at the input rate times L/M, carried on a wire running at L times the input rate.
⚠ THE ARITHMETIC IS CHECKED AGAINST MATLAB'S
upfirdn, WHICH IS AN OUTSIDE SOURCE. Measured on R2026a: upfirdn(1..8, [1 2 3], 3, 2) = 1 3 4 3 9 8 5 15 12 7 21 16, and the same run's zero-stuffed convolution is 1 2 3 2 4 6 3 6 9 4 8 12 5 10 15 …, whose every second term IS that sequence. So the identity the block implements — upfirdn(x,h,L,M) = downsample(conv(upsample(x,L),h), M) — was measured rather than assumed, and the same run re-confirms the Interpolate block beside this one from a second source.⚠ ITS SIMULINK COUNTERPART HAS NO SAMPLE-BASED MODE AT ALL, WHICH IS A DIFFERENT REASON FROM THE ONE INTERPOLATE RECORDS.
dspmlti4/FIR Rate Conversioncomputes exactly this, and its dialog carries noInputProcessingparameter: it is frame-based, and underrateMode = 'Enforce single-rate processing'it refuses anything but a frame whose row count is a multiple of M. Driven one sample at a time it says so itself:When you set the 'Rate options' parameter to 'Enforce single-rate processing' and clear the 'Allow arbitrary frame length for fixed-size input signals' parameter, the number of rows in the input must be a multiple of the decimation factor 2. Error in port widths or dimensions. 'Output Port 1' of '…/In1' is a one dimensional vector with 1 elements.
An ICore wire carries one sample per tick, so there is no configuration of that block for a bridge to name. The entry is
Support::Noneand names it anyway, with the refusal as the reason.⚠ THE STUFFED ZERO IS TAKEN EXPLICITLY, NOT BY OMISSION — the same rule Interpolate carries.
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 |
ramp | Ramp: slope 1 from t = 0 | 0 … 5.7 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | -0.9962 … 0.9985 |
table | Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample | -2 … 3 |
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__Upfirdn.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).