Uniform Decode — Control Systems/Quantization
Control_Systems/Quantization/Uniform_Decode · 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.
Uniform Decode
Control Systems / Quantization
Turns the whole-numbered codes of an N-bit uniform quantizer back into
real values on [−V, +V), entry by entry – the inverse of
Uniform Encode and MATLAB's udecode:
T = V·21−N, then y = (c + W)·T − V
with W = 2N−1 when the codes are signed and W = 0 when they are unsigned. A code that lies outside the format's range is first brought inside it, either by saturating or by wrapping it.
Ports
- c – the codes, of any size [m,n]. They arrive as ordinary real values, since every wire in this tree is a matrix of doubles, and are rounded to the nearest whole number before decoding – halves upward.
- y – the decoded values, the same size [m,n]. Each one lands on the bottom of its code's interval, not in the middle of it, which is what makes this the exact inverse of Uniform Encode's floor.
Parameters
- Number of Bits – N, a whole number from 2 to 32,
which is
udecode's own range. It fixes the number of codes at 2N and the step at 2V/2N. - Peak Value – V, the positive end of the decoded range. The codes span −V, +V).
- Input Format – how the incoming codes are numbered:
- unsigned – 0 … 2N−1, code 0 decoding to −V. The default.
- signed – −2N−1 … 2N−1−1, code 0 decoding to 0.
- Overflow Mode – what happens to a code outside that range:
- saturate – it is pinned at the nearest end of the range. The default, as in MATLAB.
- wrap – it is taken modulo 2N, so the code just past the top reappears at the bottom.
- 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.
N, V and both modes are structural: they fold into the constants T, W and the two range ends and are inlined into the arithmetic at export time rather than exposed as tunable parameters. Re-export after changing any of them.
The three HDL targets are genuinely synthesizable: the code is carried in an integer, where the clamp is two comparisons and the wrap is an integer modulo, and only the closing multiply-add runs in Q16.16. As on Uniform Encode, the datapath's whole-number range is ±32767, so a code fits it while N ≤ 15.
Simulink bridge
Import and export, mapped to DSP System Toolbox's
dspquant2/Uniform Decoder. "Number of Bits" to bits,
"Peak Value" to peak, and "Overflow Mode" to
overflowMode with saturate ↔ Saturate and
wrap ↔ Wrap, one option for one option. Simulink's
otype is always written as double, because that is the
only numeric type an ICore signal has.
"Input Format" does not cross, and cannot: Simulink reads the
signedness off the input signal's integer type rather than from a parameter, so
there is nothing on that side to map it to. Wire an int type into
the Simulink block to match signed and a uint type to match
unsigned.
That block has no SampleTime parameter (measured:
set_param refuses it), so the rate does not cross and a block
configured with an explicit positive period reports that it stayed on the ICore
side.
Notes
- Algebraic and stateless: the output depends on this sample alone.
- Not linear – a round, then a clamp or a modulo, then an affine map – so the block carries no state space and model reduction correctly reports it as unmergeable.
- The pair does not return the original value, and that is quantizing rather than a defect: encode then decode lands on the bottom of the code's own interval, up to one step below where it started.
- A code is rounded before it is decoded, with halves going up
(
floor(c + 0.5)) in all ten targets and in the simulation, so the rule is the same everywhere rather than each language's own.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Quantization/Uniform_Decode |
| family | Control_Systems/Quantization |
| solver environment class | ICoreBlock_0_Control_Systems_1_Quantization_2_Uniform_Decode |
| source | [src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Quantization/Uniform_Decode/ICoreBlock_0_Control_Systems_1_Quantization_2_Uniform_Decode.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Quantization/Uniform_Decode/ICoreBlock_0_Control_Systems_1_Quantization_2_Uniform_Decode.h |
| default size on canvas | 112 × 72 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 | c |
| 2 | out | ICoreDouble | y |
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 |
|---|---|---|
Number of Bits | 8 | bits |
Peak Value | 1 | peak |
Input Format | unsigned%~%signed~~unsigned | not crossed |
Overflow Mode | saturate%~%wrap~~saturate | overflowMode |
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 | dspquant2/Uniform\nDecoder |
| port-count rule | PortsParam::None |
SampleTime parameter | no — the counterpart defines none; the rate stays on the ICore side |
| deliberately not crossed | Input Format |
| always set | otype = double |
| ICore config | Simulink parameter | Value translation |
|---|---|---|
Number of Bits | bits | passes through |
Peak Value | peak | passes through |
Overflow Mode | overflowMode | saturate → Saturate, wrap → Wrap |
Caveat (shown to the user): the signedness of the codes is a PARAMETER here and a SIGNAL TYPE in Simulink: wire an int type into the Simulink block to match "signed" and a uint type to match "unsigned". The block has no SampleTime parameter either, so the rate stays on the ICore side
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).
Uniform Decode -- N-bit quantizer codes back onto the real interval [-V, +V) THE INVERSE OF UNIFORM ENCODE, and the streaming counterpart of MATLAB's udecode(). Read out of udecode.m in R2026a and reproduced term for term -- T = V*2^(1-N), y = (c + W)*T - V, with W = 2^(N-1) for signed codes and 0 for unsigned ones -- then checked against the block DSP System Toolbox ships, dspquant2/Uniform Decoder, whose four parameters this block's bridge maps.
⚠ WHAT CARRIES THE SIGNEDNESS DIFFERS BETWEEN THE TWO WORLDS. MATLAB and Simulink read it off the input's INTEGER TYPE: int8 is signed, uint8 is not, and the function refuses a double outright. Every ICore wire is a matrix of doubles and carries no such type, so this block is TOLD instead, by its own "Input Format" configuration -- which is why that config has no Simulink parameter behind it and is listed as one that deliberately does not cross. For the same reason the incoming value is ROUNDED to the nearest whole number first: a double wire can carry a value between two codes, and an integer wire cannot.
⚠ THE OUT-OF-RANGE RULE IS A CHOICE THE USER MAKES, not a default: saturate pins the code at the end of the range, wrap takes it modulo 2^N. Measured at N = 3, V = 1: the signed code 4 saturates to 0.75 and wraps to -1.
The three HDL targets are GENUINELY SYNTHESIZABLE: the code lives in an integer variable where the clamp and the modulo are ordinary integer operations, and only the final affine map runs in Q16.16.
Sample results#
| t | in ICoreDouble-Out-0 | out ICoreDouble-Out-0 |
|---|---|---|
| 0 | -2 | -1 |
| 0.4 | 0.5 | -0.9922 |
| 0.8 | -2 | -1 |
| 1.2 | 0.5 | -0.9922 |
| 1.6 | -2 | -1 |
| 2 | 0.5 | -0.9922 |
| 2.4 | -2 | -1 |
| 2.8 | 0.5 | -0.9922 |
| 3.2 | -2 | -1 |
| 3.6 | 0.5 | -0.9922 |
| 4 | -2 | -1 |
| 4.4 | 0.5 | -0.9922 |
| 4.8 | -2 | -1 |
| 5.2 | 0.5 | -0.9922 |
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) | -1 … -0.9922 |
ramp | Ramp: slope 1 from t = 0 | -1 … -0.9531 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | -1 … -0.9922 |
step | Step: 0 -> 1 at t = 1 s | -1 … -0.9922 |
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 a7ce80a788b799b1061c3aed0328d10563706ca6 · produced by docsSample --out <folder> --blocks Uniform_Encode Uniform_Decode Voltage_Controlled_Oscillator --steps 60 · data docs/generated/samples/Control_Systems__Quantization__Uniform_Decode.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).