nonlinear MLSE investigation trying hard to implment ML-based pre Equalization to find the branch metrics
233 lines
8.3 KiB
Matlab
233 lines
8.3 KiB
Matlab
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 |