classdef ML_MLSE < handle % Implementation of plain and simple FFE. % 1) Training mode (stable performance when you use NLMS) % 2) Decision directed mode %LMS: mu in order of 0.0001 for acceptable convergence speed %NLMS: mu in order of 0.01 for acceptable convergence speed %RLS: mu is lambda -> 0.99 -> 1 (has a strong dependency on this! use a loop to find out best values) % FFE("epochs_tr",5,"epochs_dd",2,"len_tr",2^13,"mu_dd",mu_dd,"mu_tr",mu_tr,"order",25,"sps",2,"decide",0, "adaption",adaption_method(adaption),"dd_mode",use_dd_mode); properties sps % usually 2 order e e_tr error len_tr mu_tr epochs_tr dd_mode % 1 or 0 to set DD-mode on or off mu_dd %weight update in dd mode epochs_dd constellation L %viterbi memory length alpha DIR DIR_flip trellis_states traceback_depth end methods function obj = ML_MLSE(options) arguments(Input) options.sps = 2; options.order = 15; options.len_tr = 4096; options.mu_tr = 0; options.epochs_tr = 5; options.dd_mode = 1; options.mu_dd = 1e-5; options.epochs_dd = 5; options.traceback_depth = 1024; options.L = 1 end fn = fieldnames(options); for n = 1:numel(fn) obj.(fn{n}) = options.(fn{n}); end obj.e = zeros(obj.order,1); obj.error = 0; end function [X,X_viterbi] = process(obj, X, D) % actual processing of the signal (steps 1. - 3.) % 1 normalize RMS X = X.normalize("mode","rms"); obj.constellation = unique(D.signal); if length(X)/length(D) ~= obj.sps warning('Signal length does not fit to reference!'); end % Training Mode n = obj.len_tr; training = 1; obj.equalize(X.signal, D.signal,obj.mu_tr,obj.epochs_dd,n,training); obj.e_tr = obj.e; % Decision Directed Mode n = X.length; training = 0; [y,y_vit]=obj.equalize(X.signal, D.signal,obj.mu_dd,obj.epochs_dd,n,training); X_viterbi = X; X.signal = y; X.fs = D.fs; %change sampling frequency of outgoing signal from fdac e.g. 2 sps to symbol spaced = fsym lbdesc = [num2str(obj.order),' tap FFE']; X = X.logbookentry(lbdesc); % append to logbook X_viterbi.signal = y_vit; X_viterbi.fs = D.fs; %change sampling frequency of outgoing signal from fdac e.g. 2 sps to symbol spaced = fsym lbdesc = [num2str(obj.order),'order FFE + PF + Viterbi']; X_viterbi = X_viterbi.logbookentry(lbdesc); % append to logbook end function [y,y_vit] = equalize(obj,x,d,mu,epochs,N,training) % ============================================================== % FFE + Whitening + Viterbi Equalizer (reference implementation) % ============================================================== % --- Input padding and preallocation x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)]; N_ = N / obj.sps; y = zeros(N_,1); y_white = zeros(N_,1); for epoch = 1:epochs % ============================================================== % INITIALIZATION (only before final epoch and detection mode) % ============================================================== if epoch == epochs && ~training % --- Parameters S = numel(unique(d)); L = obj.L; nStates = S^L; nFeasible = S^(L-1)*S; % --- Trellis setup obj.DIR = arburg(y-d, L); obj.DIR_flip = flip(obj.DIR); obj.trellis_states = reshape(unique(d),1,[]); pre_comb_mat = repmat(obj.trellis_states, L, 1); pre_comb_cell = mat2cell(pre_comb_mat, ones(1,L), size(pre_comb_mat,2)); combs = fliplr(combvec(pre_comb_cell{:}).'); first_sym = combs(:,1); last_sym = combs(:,end); nStates = size(combs,1); noise_free_received = inf(nStates,nStates); valid = false(nStates); for from = 1:nStates for to = 1:nStates if all(combs(to,2:end) == combs(from,1:end-1)) noise_free_received(to,from) = ... dot(combs(to,:), obj.DIR_flip(end:-1:2)) + last_sym(from)*obj.DIR_flip(1); valid(to,from) = true; end end end nf_vec = noise_free_received(valid); [valid_to, valid_from] = find(valid); from_per_to = arrayfun(@(to)find(valid(to,:)), 1:nStates, 'UniformOutput', false); % --- Noise stats y_ideal = conv(d(:), obj.DIR(:), "same"); sigma2 = mean(abs(y - y_ideal).^2); inv2s2 = 1/(2*sigma2); % --- Vector initialization bm_vec = zeros(1,nFeasible); pm = zeros(nStates,1); pm_next = zeros(nStates,1); bm_fw = zeros(nStates,nStates,length(y)); zi = zeros(max(numel(obj.DIR)-1,0),1); end % ============================================================== % RUNTIME LOOP (FFE update + Viterbi detection in last epoch) % ============================================================== symbol = 0; for sample = 1:obj.sps:N symbol = symbol + 1; % --- FFE output U = x(obj.order+sample-1:-1:sample); y(symbol,1) = obj.e.' * U; % --- Decision if training d_hat(symbol,1) = d(symbol); else [~,symbol_idx] = min(abs(y(symbol) - obj.constellation)); d_hat(symbol,1) = obj.constellation(symbol_idx); end % --- LMS weight update err(symbol) = d_hat(symbol) - y(symbol); obj.e = obj.e + mu * (err(symbol) * U); % --- Whitening + Viterbi (final epoch only) if epoch == epochs && ~training [y_white(symbol), zi] = filter(obj.DIR,1,y(symbol), zi); if symbol == 1 pm = -inf(nStates,nStates); pm(:,1:nStates) = 0; else bm_vec = -(y_white(symbol) - nf_vec).^2 * inv2s2; bm_mat = -inf(nStates,nStates); bm_mat(valid) = bm_vec; pm_new = pm + bm_mat; [pm_survive(:,symbol), pm_survivor_fw_idx(:,symbol)] = max(pm_new,[],2); pm = repmat(pm_survive(:,symbol).', nStates,1); end % --- Traceback if mod(symbol,obj.traceback_depth) == 0 [~,viterbi_path(symbol)] = max(pm_survive(:,symbol)); for n = symbol:-1:symbol-obj.traceback_depth+2 viterbi_path(n-1) = pm_survivor_fw_idx(viterbi_path(n),n); end end end end % --- Final reconstruction if epoch == epochs && ~training y_vit = first_sym(viterbi_path); end end end end end