summaryrefslogtreecommitdiff
path: root/LMSUpdate.m
diff options
context:
space:
mode:
authorDavit Grigoryan <[email protected]>2026-04-09 12:25:24 -0700
committerDavit Grigoryan <[email protected]>2026-04-22 02:37:26 -0700
commit14ca50edb249d5f4ef621903259d955deb3a2fc3 (patch)
treea1798764b97c2c351ea9c8ab1422886e9488d18b /LMSUpdate.m
public release v1.0HEADmain
Diffstat (limited to 'LMSUpdate.m')
-rw-r--r--LMSUpdate.m400
1 files changed, 400 insertions, 0 deletions
diff --git a/LMSUpdate.m b/LMSUpdate.m
new file mode 100644
index 0000000..d51984d
--- /dev/null
+++ b/LMSUpdate.m
@@ -0,0 +1,400 @@
+classdef (StrictDefaults) LMSUpdate < serdes.SerdesAbstractSystemObject & TriggeredComponent & handle
+ %LMSUpdate - Output Updated Weigths based on Given Samples
+ % obj = ADCBasedFFE returns a System Object, obj, that equalizes a
+ % demuxed signal with a feed-forward equalizer.
+ %
+ % LMSUpdate methods:
+ % step - Updates internal FFE Taps, DFE Taps, and Dlevs using
+ % sign-sign LMS Algorithm and outputs them
+ %
+ % LMSUpdate properties:
+ % NumPreTaps - Number of FFE Pre-Taps
+ % NumPostTaps - Number of FFE Post-Taps
+ %
+ % Init_MuFFE - Initial Learning Rate for updating FFE Taps
+ % Init_MuDFE - Initial Learning Rate for updating DFE Tap
+ % Init_MuDlev - Initial Learning Rate for updating Dlevs
+ %
+ % Init_Dlevs - Starting Dlev Values for better Adaptation
+ %
+ % StartFFEAdaptTime - After Going over this many Symbol Frames,
+ % start running ssLMS for FFE
+ % StartDFEAdaptTime - After Going over this many Symbol Frames,
+ % start running ssLMS for DFE
+ % StartDlevAdaptTime - After Going over this many Symbol Frames,
+ % start running ssLMS for Dlevs
+ %
+ % NumSymbolFramesAdaptation - After this many Symbol Frames,
+ % End ssLMS Adaptation
+ %
+ % LearningRatesAdjustTimes - Halve LMS Learning Rates after reaching
+ % this many Symbol Frames
+
+
+ %#codegen
+ properties (Nontunable)
+ NumPreTaps = 6;
+ NumPostTaps = 24;
+ Init_MuFFE = 0.005;
+ Init_MuDFE = 0.0025;
+ Init_MuDlev = 0.0005;
+
+ Init_Dlevs = [-0.2 -0.075 0.075 0.2];
+
+ StartFFEAdaptTime = 150;
+ StartDFEAdaptTime = 250;
+
+ WindowSize = 64;
+
+ NumSymbolFramesAdaptation = 96000/64;
+ LearningRatesAdjustTimes = [500, 850, 1250];
+
+ TogglingAlgorithm {mustBeMember(TogglingAlgorithm, ...
+ {'base', 'allzeros', 'mincoeff', 'leveraged', 'threshold', 'informedMinC', 'marginLock'} ...
+ )} = 'base';
+ end
+ properties (Hidden, SetAccess=private)
+ Mu
+ MuDFE
+ MuDlev
+
+ TapWeights
+ DFE_Tap
+
+ InitializedTaps
+
+ TogglingAlgoObj
+ FFE_TapsMask
+
+ IterCount
+
+ Dlev_0p5
+ Dlev_0p16
+ Dlev_m0p16
+ Dlev_m0p5
+
+ Votes
+
+ Vote_DFE_Tap
+
+ Votes_Dlev_0p5
+ Votes_Dlev_0p16
+ Votes_Dlev_m0p16
+ Votes_Dlev_m0p5
+
+ AdcLSB
+ AdcDynamicRange
+ AdcResolution
+
+ PrevAdcSamples_D0
+ PrevAdcSamples_D1
+ PrevAdcSamples_D2
+
+ PrevDFEOutput
+ PrevDesiredOutput
+
+ ErrorVec
+
+ Adapting
+ end
+ properties (Nontunable, Hidden)
+ NumberOfClocks = 1;
+ end
+
+ properties (SetAccess = immutable, Nontunable, Hidden)
+ IsLinear = true;
+ IsTimeInvariant = true;
+ end
+
+ methods
+ % Constructor
+ function obj = LMSUpdate(varargin)
+ % Support name-value pair arguments when constructing object
+ obj.BlockName = 'LMSUpdate';
+ setProperties(obj,nargin,varargin{:})
+ end
+ end
+ methods (Hidden)
+ % The below methods, getAMIParameters, getAMIInputNames and
+ % getAMIOutputNames are for use only within the serdesDesigner App
+ % and will not influence the AMI parameters in Simulink whatsoever.
+ function amiParameters = getAMIParameters(~)
+ amiParameters = {};
+ end
+ function names = getAMIInputNames(~)
+ names = {};
+ end
+ function names = getAMIOutputNames(~)
+ names = {};
+ end
+ end
+ methods(Access = public)
+ function setLearningRates(obj, newMu, newMuDFE, newMuDlev)
+ obj.Mu = newMu;
+ obj.MuDFE = newMuDFE;
+ obj.MuDlev = newMuDlev;
+ end
+ end
+ methods(Access = protected)
+ %% Common functions
+ function setupImpl(obj)
+ setupClock(obj)
+
+ obj.Mu = obj.Init_MuFFE;
+ obj.MuDFE = obj.Init_MuDFE;
+ obj.MuDlev = obj.Init_MuDlev;
+
+ obj.TapWeights = [zeros(1,obj.NumPreTaps) 1 zeros(1,obj.NumPostTaps)];
+ obj.DFE_Tap = 0;
+
+ obj.InitializedTaps = false;
+
+ obj.FFE_TapsMask = ones(1, obj.NumPreTaps + 1 + obj.NumPostTaps);
+
+ obj.IterCount = 0;
+
+ % hard-coded initial dlevs
+ obj.Dlev_0p5 = obj.Init_Dlevs(4);
+ obj.Dlev_0p16 = obj.Init_Dlevs(3);
+ obj.Dlev_m0p16 = obj.Init_Dlevs(2);
+ obj.Dlev_m0p5 = obj.Init_Dlevs(1);
+
+ obj.Votes = zeros(1, length(obj.TapWeights));
+
+ obj.Vote_DFE_Tap = 0;
+
+ obj.Votes_Dlev_0p5 = 0;
+ obj.Votes_Dlev_0p16 = 0;
+ obj.Votes_Dlev_m0p16 = 0;
+ obj.Votes_Dlev_m0p5 = 0;
+
+ obj.AdcDynamicRange = 0.35;
+ obj.AdcResolution = 6;
+ obj.AdcLSB = 2 * obj.AdcDynamicRange / (2^obj.AdcResolution - 1);
+
+ obj.PrevAdcSamples_D0 = zeros(1,64).';
+ obj.PrevAdcSamples_D1 = zeros(1,64).';
+ obj.PrevAdcSamples_D2 = zeros(1,64).';
+
+ obj.PrevDFEOutput = zeros(1,64).';
+ obj.PrevDesiredOutput = zeros(1,64).';
+
+ obj.ErrorVec = zeros(1,64);
+
+ obj.Adapting = 1;
+
+ obj.TogglingAlgoObj = TapTogglingFramework(obj.TogglingAlgorithm, obj);
+
+ % Print Config:
+ fprintf("[LMSUpdate] Toggling Algo: %s\n", obj.TogglingAlgorithm);
+ fprintf("[LMSUpdate] NumPreTaps=%.0f, NumPostTaps=%.0f\n", obj.NumPreTaps, obj.NumPostTaps);
+ fprintf("[LMSUpdate] WindowSize=%.0f, StartFFEAdaptTime=%.0f, StartDFEAdaptTime=%.0f\n", obj.WindowSize, obj.StartFFEAdaptTime, obj.StartDFEAdaptTime);
+ end
+
+ function [UpdatedTapWeights, UpdatedDFETap, UpdatedDlevs, Adapting, Dbg] = stepImpl(obj, AdcSamples, DFE_Output, Desired_Output, ClockIn, Init_FFE_Taps, Init_DFE_Tap)
+ if obj.DFE_Tap == 0 && Init_DFE_Tap ~= 0
+ obj.DFE_Tap = Init_DFE_Tap;
+ end
+
+ if ~obj.InitializedTaps
+ obj.TapWeights = Init_FFE_Taps;
+ obj.InitializedTaps = true;
+ end
+
+ %Triggered clock step
+ ClockStep(obj,ClockIn);
+
+
+ % Convert Decisions to ADC Samples
+ Sampled_Desired_Output = Desired_Output;
+
+ for i = 1:length(Desired_Output)
+ % Adjust Desired Outputs (Decision Symbols) to Current Dlevs
+ if Desired_Output(i) > 0.4
+ Sampled_Desired_Output(i) = obj.Dlev_0p5;
+ elseif Desired_Output(i) < 0.3 && Desired_Output(i) > 0
+ Sampled_Desired_Output(i) = obj.Dlev_0p16;
+ elseif Desired_Output(i) > -0.3 && Desired_Output(i) < 0
+ Sampled_Desired_Output(i) = obj.Dlev_m0p16;
+ else
+ Sampled_Desired_Output(i) = obj.Dlev_m0p5;
+ end
+
+ end
+
+
+ if obj.PhaseFallingIndex && obj.Adapting
+ obj.IterCount = obj.IterCount + 1;
+
+ % Extend ADC Samples to use later for updating FFE Taps
+ ExtendedAdcSamples = [obj.PrevAdcSamples_D1.' obj.PrevAdcSamples_D0.'];
+
+ obj.ErrorVec = Sampled_Desired_Output.' - DFE_Output.';
+
+ if ismember(obj.IterCount, obj.WindowSize * obj.LearningRatesAdjustTimes)
+ obj.Mu = obj.Mu / 10;
+ obj.MuDFE = obj.MuDFE / 10;
+ obj.MuDlev = obj.MuDlev / 10;
+ end
+
+ % FFE Taps Adaptation (First)
+ if obj.IterCount > obj.StartFFEAdaptTime * obj.WindowSize
+
+ for m = 1:(length(DFE_Output))
+ err = obj.ErrorVec(m);
+
+ for l = 1:length(obj.TapWeights)
+ extended_index = length(AdcSamples) + m - (l-1);
+ obj.Votes(l) = obj.Votes(l) + sign(err)*sign(ExtendedAdcSamples(extended_index));
+ end
+ end
+
+ if mod(obj.IterCount, obj.WindowSize) == 0
+ obj.Votes = obj.FFE_TapsMask .* obj.Votes;
+ DeltaWeights = 2 * obj.Mu * sign(obj.Votes);
+ DeltaWeights(obj.NumPreTaps+1) = 0; % fix the main cursor at 1
+ DeltaWeights(obj.NumPreTaps+2) = 0; % fix the first cursor to 0 (let DFE handle it)
+ obj.TapWeights = obj.TapWeights + DeltaWeights;
+
+ obj.Votes = zeros(1, length(obj.TapWeights));
+ end
+
+ end
+
+ % DFE Tap Adaptation (Second)
+ if obj.IterCount > obj.StartDFEAdaptTime * obj.WindowSize
+ for m = 1:(length(DFE_Output))
+ err = obj.ErrorVec(m);
+ prev_dfe_dec = obj.PrevDesiredOutput(m);
+
+ obj.Vote_DFE_Tap = obj.Vote_DFE_Tap + sign(err)*sign(prev_dfe_dec);
+ end
+
+ if mod(obj.IterCount, obj.WindowSize) == 0
+ obj.DFE_Tap = obj.DFE_Tap + obj.MuDFE * sign(obj.Vote_DFE_Tap);
+ obj.Vote_DFE_Tap = 0;
+ end
+ end
+
+ % Dlevs Adaptation (Always On)
+ if obj.IterCount >= obj.WindowSize
+
+ for m = 1:(length(DFE_Output))
+ e = obj.ErrorVec(m);
+
+ if Sampled_Desired_Output(m) == obj.Dlev_0p5
+ obj.Votes_Dlev_0p5 = obj.Votes_Dlev_0p5 + sign(e);
+ elseif Sampled_Desired_Output(m) == obj.Dlev_0p16
+ obj.Votes_Dlev_0p16 = obj.Votes_Dlev_0p16 + sign(e);
+ elseif Sampled_Desired_Output(m) == obj.Dlev_m0p16
+ obj.Votes_Dlev_m0p16 = obj.Votes_Dlev_m0p16 + sign(e);
+ elseif Sampled_Desired_Output(m) == obj.Dlev_m0p5
+ obj.Votes_Dlev_m0p5 = obj.Votes_Dlev_m0p5 + sign(e);
+ end
+ end
+
+ if mod(obj.IterCount, obj.WindowSize) == 0
+ muRate = obj.MuDlev;
+ if obj.IterCount >= (obj.StartFFEAdaptTime) * obj.WindowSize
+ muRate = muRate / 2;
+ end
+
+
+ obj.Dlev_0p5 = obj.Dlev_0p5 - muRate*sign(obj.Votes_Dlev_0p5);
+ obj.Dlev_0p16 = obj.Dlev_0p16 - muRate*sign(obj.Votes_Dlev_0p16);
+ obj.Dlev_m0p16 = obj.Dlev_m0p16 - muRate*sign(obj.Votes_Dlev_m0p16);
+ obj.Dlev_m0p5 = obj.Dlev_m0p5 - muRate*sign(obj.Votes_Dlev_m0p5);
+
+ obj.Votes_Dlev_0p5 = 0;
+ obj.Votes_Dlev_0p16 = 0;
+ obj.Votes_Dlev_m0p16 = 0;
+ obj.Votes_Dlev_m0p5 = 0;
+ end
+
+ end
+
+ % Latch Samples
+ obj.PrevAdcSamples_D2 = obj.PrevAdcSamples_D1;
+ obj.PrevAdcSamples_D1 = obj.PrevAdcSamples_D0;
+ obj.PrevAdcSamples_D0 = AdcSamples;
+
+ obj.PrevDFEOutput = DFE_Output;
+ obj.PrevDesiredOutput = Sampled_Desired_Output;
+
+ % End adaptation after certain period of time
+ if obj.IterCount >= round(obj.NumSymbolFramesAdaptation * obj.WindowSize)
+ obj.Adapting = 0;
+ end
+
+ obj.FFE_TapsMask = obj.TogglingAlgoObj.getMask(obj.TapWeights .* obj.FFE_TapsMask, obj);
+ end
+
+ %obj.TapWeights = obj.TapWeights .* obj.FFE_TapsMask;
+ UpdatedTapWeights = obj.TapWeights .* obj.FFE_TapsMask;
+
+ UpdatedDFETap = obj.DFE_Tap;
+
+ UpdatedDlevs = [obj.Dlev_m0p5 obj.Dlev_m0p16 obj.Dlev_0p16 obj.Dlev_0p5];
+
+ Adapting = obj.Adapting;
+ Dbg = obj.FFE_TapsMask';
+ end
+
+ function releaseImpl(obj)
+ % Print the final tap weights
+ fprintf('[LMSUpdate] Final FFE Weights: ');
+ for k = 1:numel(obj.TapWeights)
+ fprintf(' %6.9f', obj.TapWeights(k));
+ end
+ fprintf('\n');
+ fprintf("[LMSUpdate] Final DFE Tap: %6.16f", obj.DFE_Tap);
+ fprintf('\n');
+ fprintf('[LMSUpdate] Final Dlevs: ');
+ fprintf(' %6.9f %6.9f %6.9f %6.9f', ...
+ obj.Dlev_m0p5, obj.Dlev_m0p16, obj.Dlev_0p16, obj.Dlev_0p5);
+ fprintf('\n');
+ fprintf('[LMSUpdate] Final FFE Taps Mask: ');
+ for k = 1:numel(obj.FFE_TapsMask)
+ fprintf(' %1.0f', obj.FFE_TapsMask(k));
+ end
+ fprintf('\n');
+ end
+
+ %% Simulink functions
+ function icon = getIconImpl(~)
+ % Define icon for System block
+ icon = sprintf('LMS\nUpdate');
+ end
+ function [name1,name2,name3,name4,name5,name6] = getInputNamesImpl(~)
+ name1 = 'AdcSamples';
+ name2 = 'DFE_Output';
+ name3 = 'Desired_Output';
+ name4 = 'Demux Clock';
+ name5 = 'Init FFE Taps';
+ name6 = 'Init DFE Tap';
+ end
+ function [name1, name2, name3, name4, name5] = getOutputNamesImpl(~)
+ name1 = sprintf('Updated\nTapWeights');
+ name2 = sprintf('Updated\nDFE Tap');
+ name3 = sprintf('Updated\nDlevs');
+
+ name4 = 'Adapting';
+ name5 = 'Dbg';
+ end
+ function num = getNumInputsImpl(~)
+ num = 6;
+ end
+ end
+ methods(Static, Access=protected)
+ function group = getPropertyGroupsImpl(~)
+ % Define property section(s) for System block dialog
+ group = matlab.system.display.SectionGroup(...
+ 'Title','Main',...
+ 'PropertyList',{'NumPreTaps','NumPostTaps',...
+ 'Init_MuFFE','Init_MuDFE','Init_MuDlev','Init_Dlevs',...
+ 'StartFFEAdaptTime','StartDFEAdaptTime','WindowSize',...
+ 'NumSymbolFramesAdaptation','LearningRatesAdjustTimes', ...
+ 'TogglingAlgorithm'});
+ end
+ end
+end \ No newline at end of file