LMS Filter — System Identification/Adaptive Filters
System_Identification/Adaptive_Filters/LMS_Filter · 2 input / 3 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.
LMS Filter
System Identification / Adaptive Filters
An adaptive FIR filter that tunes its own taps to make its output match a desired signal. Each step it forms the tap line x = [u[k], u[k−1], … u[k−L+1]], produces y = w′·x, measures e = d − y, and steps the weights along the instantaneous gradient: w[k+1] = w[k] + 2μ·e·x. Normalized LMS divides that step by the tap power, which makes the convergence rate independent of how loud the input is.
Ports
- u – the filter input, scalar. It is what fills the tap line, so the whole of x is this one signal delayed.
- d – the desired (reference) signal, scalar. What the filter is being asked to reproduce from u.
- y – the filter output w′·x, scalar, computed from the weights as they stood BEFORE this sample's update.
- e – the error d − y, scalar. This is the signal the filter is minimising, and in an interference-cancelling wiring it is the useful output rather than y.
- w – the weight vector, [L,1] from Filter Length, carrying the weights AFTER this sample's update. Published after rather than before deliberately: a dropped or mis-scaled step then shows on the first samples instead of hiding inside y until the filter converges somewhere different.
Parameters
- Filter Length – the number of taps L, a positive whole number. It sets the tap line depth, the width of the w port, and how far back in u the filter can reach.
- Step Size – μ, the adaptation gain, entering the update as 2μ. Larger converges faster and sits noisier at the solution; too large diverges. For plain LMS stability needs roughly μ < 1/(L·E[u²]); for NLMS the normalizer removes the signal-power term and 0 < μ < 1 is the usable band.
- Initial Weights – the seed for w. A scalar is broadcast to all L taps; a vector of exactly L entries is taken in order. Anything else is reported and stops the run.
- Adaptation Method – which update law runs:
- LMS – w += 2μ·e·x. Multiply-accumulate only, no division.
- Normalized LMS (NLMS) – w += 2μ·e·x ÷ max(x′x, Epsilon). The step is scaled by the current tap power, so a loud stretch of input does not overshoot and a quiet one does not stall.
- NLMS Epsilon – the FLOOR under the normalizer, not an added term: the divisor is max(x′x, Epsilon), so a silent tap line cannot divide by zero. Unused by the LMS method. It floors rather than regularizes so that on any tap line carrying real signal the value changes nothing at all.
- 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. L, μ, the method and Epsilon are fixed at export time, so every array bound is a compile-time constant and the weights are seeded from Initial Weights in each target – read from the configuration, never from the running filter, whose weights have already moved by the time an export is taken.
The three HDL targets depend on the method, which is the one thing worth knowing before choosing one:
- LMS exports as genuinely synthesizable Q16.16 fixed point. The update is multiply-accumulate with no division, so it stays in the same fixed-point datapath the signal ports already use.
- Normalized LMS exports as simulation-only: the normalizer is a data-dependent divide, so the recursion is carried in double arithmetic and quantized only on the way out to the fixed-point ports.
μ, Epsilon and the initial weights are baked into the body at export time rather than exposed as tunable parameters – they are structural here, since changing L changes every array bound in the emitted core.
Simulink bridge
Neither direction. The System Identification Toolbox is not installed on this machine, so a bridge could not be verified against the real block even if it were written; the bridge reports this block with that reason rather than silently dropping it.
Notes
- Stateful and discrete only. The state is the weight vector and the tap line. Both start from their seeds, and unlike the recursive identification blocks there is no warm-up: a tap line of zeros is exactly the definition of u[k] = 0 for k < 0, so the filter is correct from the first sample.
- No state space, deliberately. The output is a product of two things that both move – the weights and the signal – so the map is nonlinear and no A/B/C/D is true of it. Model reduction reports the block as unmergeable, which is the honest answer.
- What the filter converges to is set by the excitation. If u does not stir the part of the response d needs, no step size identifies it – pair this with a rich excitation such as a PRBS or multisine source.
Code facts#
| Fact | Value |
|---|---|
| registered type | System_Identification/Adaptive_Filters/LMS_Filter |
| family | System_Identification/Adaptive_Filters |
| solver environment class | ICoreBlock_0_System_Identification_1_Adaptive_Filters_2_LMS_Filter |
| source | src/ICoreSDK/ICoreBlockLibrary/Blocks/System_Identification/Adaptive_Filters/LMS_Filter/ICoreBlock_0_System_Identification_1_Adaptive_Filters_2_LMS_Filter.cpp |
| header | src/ICoreSDK/ICoreBlockLibrary/Blocks/System_Identification/Adaptive_Filters/LMS_Filter/ICoreBlock_0_System_Identification_1_Adaptive_Filters_2_LMS_Filter.h |
| default size on canvas | 100 × 80 px |
| ports at insert | 2 in, 3 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 | in | ICoreDouble | — |
| 3 | out | ICoreDouble | — |
| 4 | out | ICoreDouble | — |
| 5 | 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 |
|---|---|---|
Filter Length | 4 | — |
Step Size | 0.05 | — |
Initial Weights | 0 | — |
Adaptation Method | LMS%~%Normalized LMS (NLMS)~~LMS | — |
NLMS Epsilon | 1e-12 | — |
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): the System Identification Toolbox is not installed on this machine, so a bridge could not be verified against the real block even if it were written
Catalog contract: src/ICoreSDK/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.
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 | -5.611e15 … 4.866e14 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | -1.001 … 1.003 |
table | Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample | -2.679 … 3.14 |
Plotted: step — Step: 0 -> 1 at t = 1 s
Category dynamic · sample time 0.1 · 60 steps · commit ccf005c8 · produced by docsSample --out <folder> --steps 60 · data docs/generated/samples/System_Identification__Adaptive_Filters__LMS_Filter.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).