restructure and organize
This commit is contained in:
106
Functions/EQ_blocks/dsp_runid.m
Normal file
106
Functions/EQ_blocks/dsp_runid.m
Normal file
@@ -0,0 +1,106 @@
|
||||
function output = dsp_runid(run_id, options)
|
||||
|
||||
arguments
|
||||
run_id
|
||||
options.append_to_db = 0;
|
||||
options.max_occurences = 4;
|
||||
options.userParameters = struct();
|
||||
options.database_type
|
||||
options.dataBase
|
||||
options.server = "134.245.243.254";
|
||||
options.port = 3306;
|
||||
options.user = "silas";
|
||||
options.password = "silas";
|
||||
options.load_file_path = struct();
|
||||
options.storage_path
|
||||
options.mode
|
||||
options.recipe = @dsp_scope_signal;
|
||||
options.debug_plots (1,1) logical = false;
|
||||
end
|
||||
|
||||
try
|
||||
output = initializeOutput();
|
||||
database = [];
|
||||
inputSource = normalizeDspInputSource(options.mode);
|
||||
|
||||
if inputSource == "run_id" || options.append_to_db
|
||||
database = DBHandler("dataBase", [options.dataBase], "type", options.database_type, ...
|
||||
"user", options.user, "password", options.password, ...
|
||||
"server", options.server, "port", options.port);
|
||||
end
|
||||
|
||||
switch inputSource
|
||||
case "run_id"
|
||||
dspInput = loadDspInputFromRunId(run_id, database, options);
|
||||
case "file_paths"
|
||||
dspInput = loadDspInputFromFilePaths(run_id, options);
|
||||
end
|
||||
|
||||
options.max_occurences = min(options.max_occurences, length(dspInput.Scpe_cell));
|
||||
for r = 1:options.max_occurences
|
||||
|
||||
%%%%%%%% CORE EQUALIZATION CALL (Scpe, Symbols, Bits, 'Options') %%%%%%%
|
||||
|
||||
dspOutput = options.recipe(dspInput.Scpe_cell{r}, dspInput.Symbols, dspInput.Tx_bits, ...
|
||||
"fsym", dspInput.fsym, ...
|
||||
"M", dspInput.M, ...
|
||||
"duob_mode", dspInput.duob_mode, ...
|
||||
"dataTable", dspInput.dataTable, ...
|
||||
"userParameters", options.userParameters, ...
|
||||
"debug_plots", options.debug_plots);
|
||||
|
||||
%%%%%%%% CORE EQUALIZATION CALL %%%%%%%
|
||||
|
||||
fieldNames = fieldnames(dspOutput);
|
||||
for fieldIdx = 1:numel(fieldNames)
|
||||
fieldName = fieldNames{fieldIdx};
|
||||
output.(fieldName){r} = dspOutput.(fieldName);
|
||||
end
|
||||
if options.append_to_db
|
||||
appendDspOutputToDatabase(database, run_id, dspOutput);
|
||||
end
|
||||
end
|
||||
|
||||
catch ME
|
||||
save('workerError.mat', 'ME');
|
||||
rethrow(ME);
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function output = initializeOutput()
|
||||
output.ffe_package = {};
|
||||
output.dfe_package = {};
|
||||
output.mlse_package = {};
|
||||
output.vnle_package = {};
|
||||
output.dbtgt_package = {};
|
||||
output.dbenc_package = {};
|
||||
output.mlmlse_package = {};
|
||||
end
|
||||
|
||||
function inputSource = normalizeDspInputSource(mode)
|
||||
mode = string(mode);
|
||||
|
||||
switch mode
|
||||
case {"load_run_id", "run_id"}
|
||||
inputSource = "run_id";
|
||||
case {"load_files", "file_paths"}
|
||||
inputSource = "file_paths";
|
||||
otherwise
|
||||
error('dsp_runid:UnsupportedMode', ...
|
||||
'Mode "%s" is not supported. Use "run_id" or "file_paths".', mode);
|
||||
end
|
||||
end
|
||||
|
||||
function appendDspOutputToDatabase(database, run_id, dspOutput)
|
||||
packageNames = fieldnames(dspOutput);
|
||||
|
||||
for i = 1:numel(packageNames)
|
||||
package = dspOutput.(packageNames{i});
|
||||
if isempty(package)
|
||||
continue
|
||||
end
|
||||
|
||||
database.addProcessingResult(run_id, package.metrics, package.config);
|
||||
end
|
||||
end
|
||||
206
Functions/EQ_blocks/dsp_scope_signal.m
Normal file
206
Functions/EQ_blocks/dsp_scope_signal.m
Normal file
@@ -0,0 +1,206 @@
|
||||
function output = dsp_scope_signal(Scpe_sig_raw, Symbols, Tx_bits, options)
|
||||
%DSP_SCOPE_SIGNAL Run the common DSP chain starting from a scope signal.
|
||||
|
||||
arguments
|
||||
Scpe_sig_raw
|
||||
Symbols
|
||||
Tx_bits
|
||||
options.fsym
|
||||
options.M
|
||||
options.duob_mode
|
||||
options.userParameters struct = struct()
|
||||
options.preprocess_mode string = "auto"
|
||||
options.tx_pulseformer = []
|
||||
options.debug_plots (1,1) logical = false
|
||||
end
|
||||
|
||||
output.ffe_package = [];
|
||||
output.dfe_package = [];
|
||||
output.mlse_package = [];
|
||||
output.vnle_package = [];
|
||||
output.dbtgt_package = [];
|
||||
output.dbenc_package = [];
|
||||
output.mlmlse_package = [];
|
||||
|
||||
fsym = options.fsym;
|
||||
M = options.M;
|
||||
duob_mode = options.duob_mode;
|
||||
|
||||
len_tr = 4096*2;
|
||||
|
||||
ffe_order_ffe = [50, 0, 0];
|
||||
ffe_order_dfe = [50, 5, 5];
|
||||
ffe_order_vnle = [50, 5, 5];
|
||||
ffe_order_dbtgt = [50, 5, 5];
|
||||
dfe_order_vnle = [0, 0, 0];
|
||||
dfe_order_dbtgt = [0, 0, 0];
|
||||
dfe_feedback_order = [2, 0, 0];
|
||||
pf_ncoeffs = 1;
|
||||
mu_ffe = [0.0001, 0.0008, 0.001];
|
||||
mu_dfe = 0.0004;
|
||||
mu_dc = 0.005;
|
||||
dc_buffer_len = 1; %#ok<NASGU>
|
||||
|
||||
mu_tr = 0;
|
||||
mu_dd = 0.05;
|
||||
adaption = 1;
|
||||
use_dd_mode = 1;
|
||||
|
||||
use_ffe = 1;
|
||||
use_dfe = 1;
|
||||
use_vnle_mlse = 1;
|
||||
use_dbtgt = 1;
|
||||
use_dbenc = 0;
|
||||
use_ml_mlse = 0;
|
||||
showAnalysis = 0;
|
||||
decoding_mode = [];
|
||||
|
||||
addProcessingResultToDatabase = 0; %#ok<NASGU>
|
||||
|
||||
paramStruct = options.userParameters;
|
||||
if ~isempty(paramStruct)
|
||||
paramNames = fieldnames(paramStruct);
|
||||
for i = 1:numel(paramNames)
|
||||
thisName = paramNames{i};
|
||||
thisValue = paramStruct.(thisName);
|
||||
eval([thisName ' = thisValue;']);
|
||||
end
|
||||
end
|
||||
|
||||
pf_ = Postfilter("ncoeff", pf_ncoeffs, "useBurg", 1); %#ok<NASGU>
|
||||
mlse_ = MLSE("duobinary_output", 0, 'M', M, 'trellis_states', PAMmapper(M,0).levels); %#ok<NASGU>
|
||||
|
||||
eq_post = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",2001,"sps",1,"decide",0,"adaption_technique","lms","mu_dc",mu_dc); %#ok<NASGU>
|
||||
eq_post = 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_technique",adaption_method(adaption),"dd_mode",use_dd_mode,"mu_dc",mu_dc); %#ok<NASGU>
|
||||
|
||||
mlse_db_enc = MLSE("DIR", [1,1], "duobinary_output", 0, "M", M, "trellis_states", PAMmapper(M,0).levels); %#ok<NASGU>
|
||||
eq_db_enc = EQ("Ne", ffe_order_dbtgt, "Nb", dfe_order_dbtgt, "training_length", len_tr, ...
|
||||
"training_loops", 5, "dd_loops", 5, "K", 2, "DCmu", mu_dc, ...
|
||||
"DDmu", [mu_ffe mu_dfe], "DFEmu", 0.005, "FFEmu", 0, "plotfinal", 0, "ideal_dfe", 1);
|
||||
|
||||
Scpe_sig = preprocessSignal(Scpe_sig_raw, Symbols, fsym, ...
|
||||
"mode", options.preprocess_mode, ...
|
||||
"tx_pulseformer", options.tx_pulseformer, ...
|
||||
"debug_plots", 0);
|
||||
|
||||
% Scpe_sig.spectrum("fignum",200,"normalizeTo0dB",1,"displayname",'Rx','addDCoffset',-6);
|
||||
% ylim([-30,3]);
|
||||
% xlim([-5,100]);
|
||||
|
||||
Scpe_sig.signal = Scpe_sig.signal(1:2*Symbols.length);
|
||||
Scpe_sig.signal = real(Scpe_sig.signal);
|
||||
|
||||
if duob_mode ~= db_mode.db_encoded
|
||||
if use_ffe
|
||||
eq_ffe = EQ("Ne",ffe_order_ffe,"Nb",[0,0,0],"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",0);
|
||||
eq_ffe = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-1,"mu_tr",0.4,"order",ffe_order_ffe(1),...
|
||||
"sps",2,"decide",0,"optmize_mus",0,"dd_mode",1,"adaption_technique","nlms","mu_dc",1.021e-05);
|
||||
|
||||
ffe_results = ffe(eq_ffe, M, Scpe_sig, Symbols, Tx_bits, ...
|
||||
"precode_mode", duob_mode, ...
|
||||
'showAnalysis', options.debug_plots, ...
|
||||
"postFFE", [], ...
|
||||
"eth_style_symbol_mapping", 0);
|
||||
|
||||
ffe_results.config.equalizer_structure = "ffe";
|
||||
ffe_results.metrics.print("description",'FFE');
|
||||
output.ffe_package = ffe_results;
|
||||
end
|
||||
|
||||
if use_dfe
|
||||
eq_dfe = EQ("Ne",ffe_order_dfe,"Nb",dfe_feedback_order,"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",0);
|
||||
|
||||
dfe_results = ffe(eq_dfe, M, Scpe_sig, Symbols, Tx_bits, ...
|
||||
"precode_mode", duob_mode, ...
|
||||
'showAnalysis', options.debug_plots, ...
|
||||
"postFFE", [], ...
|
||||
"eth_style_symbol_mapping", 0);
|
||||
|
||||
dfe_results.config.equalizer_structure = "dfe";
|
||||
dfe_results.metrics.print("description",'DFE');
|
||||
output.dfe_package = dfe_results;
|
||||
end
|
||||
|
||||
if use_vnle_mlse
|
||||
eq_ = EQ("Ne",ffe_order_vnle,"Nb",dfe_order_vnle,"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",0);
|
||||
pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
|
||||
|
||||
useviterbi = 0;
|
||||
if useviterbi
|
||||
mlse_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
|
||||
else
|
||||
if duob_mode == db_mode.no_db && M == 6
|
||||
trellexlusion = 1;
|
||||
else
|
||||
trellexlusion = 0;
|
||||
end
|
||||
|
||||
mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',trellexlusion,'trellis_state_mode',2);
|
||||
end
|
||||
|
||||
[ffe_results, mlse_results] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, Scpe_sig, Symbols, Tx_bits, ...
|
||||
"precode_mode", duob_mode, ...
|
||||
'showAnalysis', showAnalysis, ...
|
||||
"postFFE", [], ...
|
||||
"eth_style_symbol_mapping", 0);
|
||||
|
||||
ffe_results.config.equalizer_structure = "vnle";
|
||||
ffe_results.metrics.print("description",'VNLE');
|
||||
mlse_results.metrics.print("description",'MLSE');
|
||||
|
||||
output.mlse_package = mlse_results;
|
||||
output.vnle_package = ffe_results;
|
||||
end
|
||||
|
||||
if use_ml_mlse
|
||||
mu_ml = 0.01;
|
||||
training_epochs = 100;
|
||||
ml_mlse_equalizer = ML_MLSE("epochs_tr",training_epochs,"epochs_dd",1, ...
|
||||
"len_tr",length(Scpe_sig)/4,"mu_dd",mu_ml,"mu_tr",mu_ml,"order",11,"sps",2, ...
|
||||
"traceback_depth",128,"L",1,"delta",4,"adaptive_mu",0);
|
||||
|
||||
ml_mlse_results = ml_mlse(ml_mlse_equalizer, M, Scpe_sig, Symbols, Tx_bits, "precode_mode", duob_mode);
|
||||
output.mlmlse_package = ml_mlse_results;
|
||||
end
|
||||
|
||||
if use_dbtgt
|
||||
useviterbi = 0;
|
||||
if useviterbi
|
||||
mlse_db_ = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
|
||||
else
|
||||
if duob_mode == db_mode.no_db && M == 6
|
||||
trellexlusion = 1;
|
||||
else
|
||||
trellexlusion = 0;
|
||||
end
|
||||
mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,"trellis_states",PAMmapper(M,0).levels,'scale_mode',2,'trellis_exclusion',trellexlusion,'trellis_state_mode',3);
|
||||
end
|
||||
|
||||
eq_ = EQ("Ne",ffe_order_dbtgt,"Nb",dfe_order_dbtgt,"training_length",len_tr,"training_loops",5,"dd_loops",5,"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",1);
|
||||
|
||||
if isempty(decoding_mode)
|
||||
dbt_results = duobinary_target(eq_, mlse_db_, M, Scpe_sig, Symbols, Tx_bits, ...
|
||||
"precode_mode", duob_mode, ...
|
||||
'showAnalysis', showAnalysis, ...
|
||||
"postFFE", []);
|
||||
else
|
||||
dbt_results = duobinary_target(eq_, mlse_db_, M, Scpe_sig, Symbols, Tx_bits, ...
|
||||
"precode_mode", duob_mode, ...
|
||||
'showAnalysis', showAnalysis, ...
|
||||
"postFFE", [], ...
|
||||
"decoding_mode", decoding_mode);
|
||||
end
|
||||
|
||||
dbt_results.metrics.print("description",'Duob. Target');
|
||||
output.dbtgt_package = dbt_results;
|
||||
end
|
||||
end
|
||||
|
||||
if duob_mode == db_mode.db_encoded
|
||||
mlse_db_enc = MLSE_viterbi("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels); %#ok<NASGU>
|
||||
mlse_db_enc = MLSE("DIR", [1,1], "duobinary_output", 0, "M", M, "trellis_states", PAMmapper(M,0).levels);
|
||||
|
||||
db_results = duobinary_signaling(eq_db_enc, mlse_db_enc, M, Scpe_sig, Symbols, Tx_bits, "precode_mode",duob_mode, "showAnalysis",showAnalysis,"postFFE",[]);
|
||||
output.dbenc_package = db_results;
|
||||
end
|
||||
end
|
||||
149
Functions/EQ_blocks/duobinary_signaling.m
Normal file
149
Functions/EQ_blocks/duobinary_signaling.m
Normal file
@@ -0,0 +1,149 @@
|
||||
function [db_results] = duobinary_signaling(eq_, mlse_, M, rx_signal, tx_symbols, tx_bits, options)
|
||||
% DUOBINARY_SIGNALING Processes signals through duobinary signaling
|
||||
%
|
||||
% Inputs:
|
||||
% eq_ - Equalizer object
|
||||
% mlse_ - MLSE object
|
||||
% M - Modulation order
|
||||
% rx_signal - Received signal
|
||||
% tx_symbols - Transmitted symbols
|
||||
% tx_bits - Transmitted bits
|
||||
% options - Optional parameters
|
||||
%
|
||||
% Outputs:
|
||||
% db_results - Results from duobinary signaling processing
|
||||
|
||||
arguments
|
||||
eq_
|
||||
mlse_
|
||||
M
|
||||
rx_signal
|
||||
tx_symbols
|
||||
tx_bits
|
||||
options.precode_mode db_mode
|
||||
options.showAnalysis = 0
|
||||
options.eth_style_symbol_mapping = 0
|
||||
options.postFFE = []
|
||||
options.database = []
|
||||
end
|
||||
|
||||
%% Process signals through equalizer
|
||||
[eq_signal, eq_noise] = eq_.process(rx_signal, tx_symbols);
|
||||
|
||||
% Apply post-FFE if provided
|
||||
if ~isempty(options.postFFE)
|
||||
[eq_signal, eq_noise] = options.postFFE.process(eq_signal, tx_symbols);
|
||||
end
|
||||
|
||||
if isa(mlse_,'MLSE_viterbi')
|
||||
[mlse_signal] = mlse_.process(eq_signal);
|
||||
else
|
||||
|
||||
% Aufpassen mit welcher Sequenz man hier vergleicht für LLR stuff...
|
||||
% gespeichtere "Symbols" sind schon DB codiert, das wollen wir hier
|
||||
% nicht! Sondern die precoded aber nicht db-encoded müssen als ref in
|
||||
% die LLR berechnung gehen!
|
||||
ref_sym = PAMmapper(M,0).map(tx_bits); %ist klar
|
||||
ref_sym_dpc = Duobinary().precode(ref_sym); % precoded
|
||||
% ref_sym_dbenc = Duobinary().encode(ref_sym_dpc); %encoded - das wurde gesendet!
|
||||
% ref_sym_dec = Duobinary().decode(ref_sym_dbenc); %ref_sym wieder zurück!
|
||||
|
||||
mlse_.trellis_states = PAMmapper(M,0).levels;
|
||||
mlse_.trellis_state_mode = 1;
|
||||
[mlse_signal,LLR,GMI_MLSE] = mlse_.process(eq_signal,ref_sym_dpc);
|
||||
end
|
||||
|
||||
|
||||
|
||||
% tx_symbols_ = Duobinary().decode(tx_symbols);
|
||||
% [mlse_signal,~,GMI_MLSE] = mlse_.process(eq_signal,tx_symbols);
|
||||
|
||||
% Apply duobinary encoding and decoding
|
||||
mlse_signal = Duobinary().encode(mlse_signal);
|
||||
mlse_signal = Duobinary().decode(mlse_signal);
|
||||
|
||||
% Demap symbols to bits
|
||||
rx_bits = PAMmapper(M, 0, "eth_style", options.eth_style_symbol_mapping).demap(mlse_signal);
|
||||
|
||||
%% Calculate BER and metrics
|
||||
[bits_db, errors_db, ber_db, error_pos] = calc_ber(rx_bits.signal, tx_bits.signal, "skip_front", 100, "skip_end", 150, "returnErrorLocation", 1);
|
||||
|
||||
% Calculate performance metrics after duobinary FFE!
|
||||
[snr, snr_lvl] = calc_snr(tx_symbols.signal, eq_noise.signal); %SNR of duobinary sequence - not directly comparable to
|
||||
[gmi] = calc_air(eq_signal, tx_symbols, "skip_front", 10000, "skip_end", 10000);
|
||||
air = tx_symbols.fs .* floor(log2(double(M))*10)/10 .* gmi ./ log2(double(M));
|
||||
[evm_total, evm_lvl] = calc_evm(eq_signal, tx_symbols);
|
||||
[std_total, std_lvl] = calc_std(eq_signal, tx_symbols);
|
||||
[std_rxraw_total, std_rxraw_lvl] = calc_std(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols);
|
||||
|
||||
%% Prepare output structure
|
||||
% Determine postFFE order
|
||||
if ~isempty(options.postFFE)
|
||||
npostFFE = options.postFFE.order;
|
||||
else
|
||||
npostFFE = 0;
|
||||
end
|
||||
|
||||
% Create results structure
|
||||
db_results = struct();
|
||||
db_results.metrics = Metricstruct;
|
||||
db_results.metrics.result_id = NaN;
|
||||
db_results.metrics.run_id = NaN;
|
||||
db_results.metrics.eqParam_id = NaN;
|
||||
db_results.metrics.date_of_processing = datetime('now');
|
||||
db_results.metrics.BER = ber_db;
|
||||
db_results.metrics.numBits = bits_db;
|
||||
db_results.metrics.numBitErr = errors_db;
|
||||
db_results.metrics.SNR = snr;
|
||||
db_results.metrics.SNR_level = snr_lvl;
|
||||
db_results.metrics.STD = std_total;
|
||||
db_results.metrics.STD_level = std_lvl;
|
||||
db_results.metrics.STDrx = std_rxraw_total;
|
||||
db_results.metrics.STDrx_level = std_rxraw_lvl;
|
||||
db_results.metrics.GMI = gmi;
|
||||
db_results.metrics.AIR = air;
|
||||
db_results.metrics.EVM = evm_total;
|
||||
db_results.metrics.EVM_level = evm_lvl;
|
||||
db_results.metrics.MLSE_dir = mlse_.DIR;
|
||||
|
||||
% Create configuration structure
|
||||
eq_.e = [];
|
||||
eq_.e2 = [];
|
||||
eq_.e3 = [];
|
||||
db_results.config = Equalizerstruct();
|
||||
db_results.config.eq = jsonencode(eq_);
|
||||
mlse_.DIR = [];
|
||||
db_results.config.mlse = jsonencode(mlse_);
|
||||
db_results.config.equalizer_structure = int32(equalizer_structure.db_encoded);
|
||||
db_results.config.comment = 'function: duobinary_signaling';
|
||||
|
||||
%% Display analysis if requested
|
||||
if options.showAnalysis
|
||||
displayAnalysis(eq_noise, eq_signal, rx_signal, eq_, tx_symbols, M, options.postFFE);
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
%% Helper Function
|
||||
function displayAnalysis(eq_noise, eq_signal, rx_signal, eq_, tx_symbols, M, postFFE)
|
||||
% Display analysis plots and metrics
|
||||
figure(336);
|
||||
showEQNoisePSD(eq_noise, "fignum", 336, "displayname", 'Residual Noise after Duobinary');
|
||||
|
||||
if ~isempty(postFFE)
|
||||
showEQcoefficients('n1', postFFE.e, "displayname", 'Coefficients', 'fignum', 338);
|
||||
end
|
||||
|
||||
showEQfilter(eq_.e, eq_signal.fs.*2);
|
||||
|
||||
figure(341); clf;
|
||||
showLevelHistogram(eq_signal, tx_symbols, "fignum", 341);
|
||||
|
||||
warning off
|
||||
figure(400); clf;
|
||||
showLevelScatter(eq_signal, tx_symbols, "fignum", 400);
|
||||
|
||||
figure(401); clf;
|
||||
showLevelScatter(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols, "fignum", 401);
|
||||
warning on
|
||||
end
|
||||
189
Functions/EQ_blocks/duobinary_target.m
Normal file
189
Functions/EQ_blocks/duobinary_target.m
Normal file
@@ -0,0 +1,189 @@
|
||||
function [db_results] = duobinary_target(eq_, mlse_,M, rx_signal, tx_symbols, tx_bits, options)
|
||||
|
||||
arguments
|
||||
eq_
|
||||
mlse_
|
||||
M
|
||||
rx_signal
|
||||
tx_symbols
|
||||
tx_bits
|
||||
options.precode_mode db_mode
|
||||
options.showAnalysis = 0;
|
||||
options.eth_style_symbol_mapping = 0;
|
||||
options.postFFE = [];
|
||||
options.decoding_mode db_decoder = db_decoder.sequencedetection;
|
||||
end
|
||||
|
||||
|
||||
|
||||
%Duobinary Targeting
|
||||
db_ref_sequence = Duobinary().encode(tx_symbols);
|
||||
db_ref_constellation = unique(db_ref_sequence.signal);
|
||||
[eq_signal, eq_noise] = eq_.process(rx_signal,db_ref_sequence);
|
||||
|
||||
if ~isempty(options.postFFE)
|
||||
[eq_signal,eq_noise] = options.postFFE.process(eq_signal,db_ref_sequence);
|
||||
end
|
||||
%
|
||||
|
||||
switch options.decoding_mode
|
||||
case db_decoder.sequencedetection %MLSE
|
||||
mlse_.DIR = [1,1];
|
||||
if isa(mlse_,'MLSE_viterbi')
|
||||
mlse_sig_sd = mlse_.process(eq_signal);
|
||||
else
|
||||
[mlse_sig_sd,LLR,GMI_MLSE] = mlse_.process(eq_signal,tx_symbols);
|
||||
end
|
||||
pam_sig_hd = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).quantize(mlse_sig_sd);
|
||||
case db_decoder.memoryless %DB Target FFE
|
||||
% Hard decision on FFE output
|
||||
eq_signal_hd = PAMmapper(M, 0).quantize(eq_signal,'custom_const',db_ref_constellation.');
|
||||
eq_signal_hd = Duobinary().decode(eq_signal_hd);
|
||||
pam_sig_hd = eq_signal_hd;
|
||||
% tx_symbols = Duobinary().encode(tx_symbols);
|
||||
% tx_symbols = Duobinary().decode(tx_symbols.*db_const_scale_factor);
|
||||
end
|
||||
|
||||
% precoding to mitigate error propagation, most prominently used in
|
||||
% combination with duobinary signaling to avoid catastrophic error
|
||||
% behavior (see J.W.M. Bergmans, Digital Baseband Transmission and Recording -> partial response signaling)
|
||||
switch options.precode_mode
|
||||
|
||||
case db_mode.no_db
|
||||
% TX Data is not precoded:
|
||||
|
||||
% A) Emulate diff precoding
|
||||
switch options.decoding_mode
|
||||
case db_decoder.sequencedetection %MLSE
|
||||
mlse_sig_hd_precoded = Duobinary().encode(pam_sig_hd);
|
||||
mlse_sig_hd_precoded = Duobinary().decode(mlse_sig_hd_precoded);
|
||||
case db_decoder.memoryless %DB Target FFE
|
||||
% mlse_sig_hd_precoded = Duobinary().encode(pam_sig_hd);
|
||||
% mlse_sig_hd_precoded = Duobinary().decode(mlse_sig_hd_precoded);
|
||||
mlse_sig_hd_precoded = pam_sig_hd;
|
||||
end
|
||||
|
||||
tx_symbols_precoded = Duobinary().encode(tx_symbols);
|
||||
tx_symbols_precoded = Duobinary().decode(tx_symbols_precoded);
|
||||
|
||||
tx_bits_precoded = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(tx_symbols_precoded);
|
||||
rx_bits_mlse = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(mlse_sig_hd_precoded);
|
||||
|
||||
[~,errors_db_diff_precoded,ber_db_diff_precoded,~] = calc_ber(rx_bits_mlse.signal,tx_bits_precoded.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
|
||||
|
||||
%B) Just determine BER
|
||||
rx_bits_mlse = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(pam_sig_hd);
|
||||
tx_bits = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(tx_symbols);
|
||||
[bits_mlse,errors_mlse,ber_db,~] = calc_ber(rx_bits_mlse.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
|
||||
|
||||
case db_mode.db_precoded
|
||||
|
||||
% Daten SIND TATSÄCHLICH precoded auf TX Seite:
|
||||
|
||||
% A) Decode at Rx if no DB targeting was applied (we are in VNLE or MLSE EQ structure here!
|
||||
switch options.decoding_mode
|
||||
case db_decoder.sequencedetection %MLSE
|
||||
mlse_sig_hd_decoded = Duobinary().encode(pam_sig_hd,"M",M);
|
||||
mlse_sig_hd_decoded = Duobinary().decode(mlse_sig_hd_decoded,"M",M);
|
||||
case db_decoder.memoryless %DB Target FFE
|
||||
mlse_sig_hd_decoded = pam_sig_hd;
|
||||
end
|
||||
|
||||
tx_symbols_precoded = Duobinary().encode(tx_symbols,"M",M);
|
||||
tx_symbols_precoded = Duobinary().decode(tx_symbols_precoded,"M",M);
|
||||
|
||||
tx_bits_precoded = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(tx_symbols_precoded);
|
||||
rx_bits_mlse_decoded = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(mlse_sig_hd_decoded);
|
||||
|
||||
[~,errors_db_diff_precoded,ber_db_diff_precoded,a] = calc_ber(rx_bits_mlse_decoded.signal,tx_bits_precoded.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
|
||||
burst_db_precoded = count_error_bursts(a, 40);
|
||||
% B) Omit the Coding by comparing with demapped TX symbol sequence
|
||||
|
||||
tx_bits = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(tx_symbols);
|
||||
rx_bits_mlse = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(pam_sig_hd);
|
||||
[bits_db,errors_db,ber_db,a] = calc_ber(rx_bits_mlse.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
|
||||
burst_db = count_error_bursts(a, 40);
|
||||
|
||||
cols = linspecer(8);
|
||||
figure();hold on;
|
||||
stem(1:40,burst_db,'LineWidth',1,'Color',cols(4,:),'Marker','_','DisplayName','w/o diff. precoder');
|
||||
stem(1:40,burst_db_precoded,'LineWidth',1,'Color',cols(3,:),'Marker','.','LineStyle','-','DisplayName','w diff. precoder');
|
||||
xlabel('Bit Error Burst Length')
|
||||
ylabel('Occurence')
|
||||
set(gca, 'yscale', 'log');
|
||||
end
|
||||
|
||||
% M = numel(unique(tx_symbols.signal));
|
||||
rx_bits = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(pam_sig_hd);
|
||||
|
||||
[bits_db,errors_db,ber_db,errorIndice_db] = calc_ber(rx_bits.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
|
||||
|
||||
alpha = arburg(eq_noise.signal,1);%pf_.coefficients(2);
|
||||
alpha = alpha(2);
|
||||
|
||||
switch options.decoding_mode
|
||||
case db_decoder.sequencedetection %MLSE
|
||||
if isa(mlse_,'MLSE_viterbi')
|
||||
gmi_mlse = NaN;
|
||||
air_mlse = NaN;
|
||||
else
|
||||
gmi_mlse = GMI_MLSE;
|
||||
air_mlse = tx_symbols.fs .* floor(log2(double(M))*10)/10 .* gmi_mlse ./ log2(double(M));
|
||||
end
|
||||
case db_decoder.memoryless %DB Target FFE
|
||||
% [gmi] = calc_air(eq_signal_sd, tx_symbols, "skip_front", 10000, "skip_end", 10000);
|
||||
[gmi] = calc_ngmi(eq_signal,tx_symbols);
|
||||
gmi_mlse = max(gmi,0);
|
||||
air_mlse = tx_symbols.fs .* floor(log2(double(M))*10)/10 .* gmi ./ log2(double(M));
|
||||
end
|
||||
|
||||
db_results = struct();
|
||||
db_results.metrics = Metricstruct;
|
||||
db_results.metrics.result_id = NaN;
|
||||
db_results.metrics.run_id = NaN;
|
||||
db_results.metrics.eqParam_id = NaN;
|
||||
db_results.metrics.date_of_processing = datetime('now');
|
||||
db_results.metrics.BER = ber_db;
|
||||
db_results.metrics.numBits = bits_db;
|
||||
db_results.metrics.numBitErr = errors_db;
|
||||
db_results.metrics.BER_precoded = ber_db_diff_precoded;
|
||||
db_results.metrics.numBitErr_precoded = errors_db_diff_precoded;
|
||||
db_results.metrics.GMI = gmi_mlse;
|
||||
db_results.metrics.AIR = air_mlse;
|
||||
db_results.metrics.MLSE_dir = mlse_.DIR;
|
||||
db_results.metrics.Alpha = alpha;
|
||||
|
||||
% Create DB results structure
|
||||
db_results.config = Equalizerstruct();
|
||||
eq_.e = [];
|
||||
eq_.e2 = [];
|
||||
eq_.e3 = [];
|
||||
db_results.config.eq = jsonencode(eq_);
|
||||
% mlse_.DIR = [];
|
||||
db_results.config.mlse = jsonencode(mlse_);
|
||||
db_results.config.equalizer_structure = int32(equalizer_structure.vnle_db_mlse);
|
||||
db_results.config.comment = 'function: Duobinary tgt. (VNLE -> MLSE)';
|
||||
|
||||
if options.showAnalysis
|
||||
|
||||
eq_signal.eye(eq_signal.fs,M,"fignum",249);
|
||||
|
||||
|
||||
eq_noise = eq_noise - mean(eq_noise.signal);
|
||||
|
||||
rx_signal.spectrum("normalizeTo0dB",1,"fignum",250,"displayname","Rx Spectrum");
|
||||
|
||||
Duobinary().encode(tx_symbols).spectrum("normalizeTo0dB",1,"fignum",250,"displayname","DB encoded reference");
|
||||
|
||||
showEQNoisePSD(eq_noise,"fignum",250,"displayname",'Duobinary Target Noise after Equalization');
|
||||
|
||||
fprintf('DB tgt BER: %.2e \n',ber_db);
|
||||
|
||||
figure(341); clf;
|
||||
tx_symbols_uncoded = Duobinary().decode(db_ref_sequence);
|
||||
showLevelHistogram(eq_signal, db_ref_sequence, "fignum", 341,"ref_symbol_uncoded",tx_symbols_uncoded);
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
199
Functions/EQ_blocks/ffe.m
Normal file
199
Functions/EQ_blocks/ffe.m
Normal file
@@ -0,0 +1,199 @@
|
||||
function [ffe_results] = ffe(eq_, M, rx_signal, tx_symbols, tx_bits, options)
|
||||
% FFE Processes signals through FFE equalizer
|
||||
%
|
||||
% Inputs:
|
||||
% eq_ - Equalizer object
|
||||
% M - Modulation order
|
||||
% rx_signal - Received signal
|
||||
% tx_symbols - Transmitted symbols
|
||||
% tx_bits - Transmitted bits
|
||||
% options - Optional parameters
|
||||
%
|
||||
% Outputs:
|
||||
% ffe_results - Results from FFE processing
|
||||
|
||||
arguments
|
||||
eq_
|
||||
M
|
||||
rx_signal
|
||||
tx_symbols
|
||||
tx_bits
|
||||
options.precode_mode db_mode
|
||||
options.showAnalysis = 0;
|
||||
options.eth_style_symbol_mapping = 0;
|
||||
options.postFFE = [];
|
||||
options.database = [];
|
||||
end
|
||||
|
||||
%% Process signals through equalizer
|
||||
% FFE or VNLE
|
||||
[eq_signal_sd, eq_noise] = eq_.process(rx_signal, tx_symbols);
|
||||
|
||||
% Apply post-FFE if provided
|
||||
if ~isempty(options.postFFE)
|
||||
tic
|
||||
[eq_signal_sd, eq_noise] = options.postFFE.process(eq_signal_sd, tx_symbols);
|
||||
toc
|
||||
end
|
||||
|
||||
try
|
||||
ch_coefficients = arburg(eq_noise.signal,1);
|
||||
channel_alpha = ch_coefficients(2);
|
||||
end
|
||||
|
||||
% Hard decision on FFE output
|
||||
eq_signal_hd = PAMmapper(M, 0).quantize(eq_signal_sd);
|
||||
|
||||
%% Calculate BER based on precoding mode
|
||||
[bits, errors, ber, error_pos, errors_precoded, ber_precoded] = calculateBER(eq_signal_hd, tx_symbols, tx_bits, options.precode_mode, M, options.eth_style_symbol_mapping);
|
||||
|
||||
%% Calculate performance metrics
|
||||
[snr, snr_lvl] = calc_snr(tx_symbols.signal, eq_noise.signal);
|
||||
% [gmi] = calc_air(eq_signal_sd, tx_symbols, "skip_front", 10000, "skip_end", 10000);
|
||||
[gmi] = calc_ngmi(eq_signal_sd,tx_symbols);
|
||||
gmi = max(gmi,0);
|
||||
|
||||
air = tx_symbols.fs .* floor(log2(double(M))*10)/10 .* gmi ./ log2(double(M));
|
||||
[evm_total, evm_lvl] = calc_evm(eq_signal_sd, tx_symbols);
|
||||
[std_total, std_lvl] = calc_std(eq_signal_sd, tx_symbols);
|
||||
[std_rxraw_total, std_rxraw_lvl] = calc_std(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols);
|
||||
|
||||
%% Display analysis if requested
|
||||
if options.showAnalysis
|
||||
displayAnalysis(eq_noise, eq_signal_sd, rx_signal, eq_, tx_symbols, M, options.postFFE);
|
||||
end
|
||||
|
||||
|
||||
%% Prepare output structure
|
||||
% Determine postFFE order
|
||||
if ~isempty(options.postFFE)
|
||||
npostFFE = options.postFFE.order;
|
||||
else
|
||||
npostFFE = 0;
|
||||
end
|
||||
|
||||
% Create FFE results structure
|
||||
ffe_results = struct();
|
||||
try
|
||||
eq_.e = [];
|
||||
eq_.e2 = [];
|
||||
eq_.e3 = [];
|
||||
eq_.b = [];
|
||||
eq_.b2 = [];
|
||||
eq_.b3 = [];
|
||||
end
|
||||
try
|
||||
eq_.mu_optimization = [];
|
||||
end
|
||||
|
||||
ffe_results.config = Equalizerstruct();
|
||||
ffe_results.config.eq = jsonencode(eq_);
|
||||
ffe_results.config.equalizer_structure = int32(equalizer_structure.ffe);
|
||||
ffe_results.config.comment = 'function: ffe';
|
||||
|
||||
ffe_results.metrics = Metricstruct;
|
||||
ffe_results.metrics.result_id = NaN;
|
||||
ffe_results.metrics.run_id = NaN;
|
||||
ffe_results.metrics.eqParam_id = NaN;
|
||||
ffe_results.metrics.date_of_processing = datetime('now');
|
||||
ffe_results.metrics.BER = ber;
|
||||
ffe_results.metrics.numBits = bits;
|
||||
ffe_results.metrics.numBitErr = errors;
|
||||
ffe_results.metrics.BER_precoded = ber_precoded;
|
||||
ffe_results.metrics.numBitErr_precoded = errors_precoded;
|
||||
ffe_results.metrics.SNR = snr;
|
||||
ffe_results.metrics.SNR_level = snr_lvl;
|
||||
ffe_results.metrics.STD = std_total;
|
||||
ffe_results.metrics.STD_level = std_lvl;
|
||||
ffe_results.metrics.STDrx = std_rxraw_total;
|
||||
ffe_results.metrics.STDrx_level = std_rxraw_lvl;
|
||||
ffe_results.metrics.GMI = gmi;
|
||||
ffe_results.metrics.AIR = air;
|
||||
ffe_results.metrics.EVM = evm_total;
|
||||
ffe_results.metrics.EVM_level = evm_lvl;
|
||||
ffe_results.metrics.Alpha = channel_alpha;
|
||||
|
||||
|
||||
end
|
||||
|
||||
%% Helper Functions
|
||||
function [bits, errors, ber, error_pos, errors_precoded, ber_precoded] = calculateBER(eq_signal_hd, tx_symbols, tx_bits, precode_mode, M, eth_style)
|
||||
% Calculate BER based on precoding mode
|
||||
mapper = PAMmapper(M, 0, "eth_style", eth_style);
|
||||
|
||||
switch precode_mode
|
||||
case db_mode.no_db
|
||||
% TX Data is not precoded
|
||||
% A) Emulate diff precoding
|
||||
eq_signal_hd_precoded = Duobinary().encode(eq_signal_hd, "M", M);
|
||||
eq_signal_hd_precoded = Duobinary().decode(eq_signal_hd_precoded, "M", M);
|
||||
|
||||
tx_symbols_precoded = Duobinary().encode(tx_symbols);
|
||||
tx_symbols_precoded = Duobinary().decode(tx_symbols_precoded);
|
||||
|
||||
tx_bits_precoded = mapper.demap(tx_symbols_precoded);
|
||||
|
||||
rx_bits = mapper.demap(eq_signal_hd_precoded);
|
||||
[~, errors_precoded, ber_precoded, ~] = calc_ber(rx_bits.signal, tx_bits_precoded.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
|
||||
|
||||
% B) Just determine BER
|
||||
rx_bits = mapper.demap(eq_signal_hd);
|
||||
[bits, errors, ber, error_pos] = calc_ber(rx_bits.signal, tx_bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
|
||||
|
||||
case db_mode.db_precoded
|
||||
% Data is precoded on TX side
|
||||
% A) Decode at Rx if no DB targeting was applied
|
||||
eq_signal_hd_decoded = Duobinary().encode(eq_signal_hd, "M", M);
|
||||
eq_signal_hd_decoded = Duobinary().decode(eq_signal_hd_decoded, "M", M);
|
||||
rx_bits_decoded = mapper.demap(eq_signal_hd_decoded);
|
||||
[~, errors_precoded, ber_precoded, ~] = calc_ber(rx_bits_decoded.signal, tx_bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
|
||||
|
||||
% B) Omit the Coding by comparing with demapped TX symbol sequence
|
||||
tx_bits_demapped = mapper.demap(tx_symbols);
|
||||
rx_bits = mapper.demap(eq_signal_hd);
|
||||
[bits, errors, ber, error_pos] = calc_ber(rx_bits.signal, tx_bits_demapped.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
|
||||
end
|
||||
end
|
||||
|
||||
function displayAnalysis(eq_noise, eq_signal_sd, rx_signal, eq_, tx_symbols, M, postFFE)
|
||||
% Display analysis plots and metrics
|
||||
|
||||
% Initialize figure handles
|
||||
% Corrected line - added tx_symbols as second positional argument
|
||||
% showLevelScatter(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols, "fignum", 100);
|
||||
|
||||
warning off
|
||||
showLevelScatter(eq_signal_sd, tx_symbols, "fignum", 101);
|
||||
figure(gcf);hold on; plot(((1:length(eq_noise.signal)) / eq_noise.fs) * 1e6,movmean(eq_noise.signal,2000,1), 'LineWidth',3,'Color','black')
|
||||
warning on
|
||||
|
||||
showLevelHistogram(eq_signal_sd, tx_symbols, "fignum", 102);
|
||||
|
||||
showEQNoisePSD(eq_noise, "fignum", 103, "displayname", 'Residual Noise after FFE');
|
||||
|
||||
% showEQcoefficients('n1', eq_.e, "displayname", 'Coefficients', 'fignum', 104);
|
||||
|
||||
% Figure 2: Post-FFE coefficients (if available)
|
||||
if ~isempty(postFFE)
|
||||
showEQcoefficients('n1', postFFE.e, "displayname", 'Coefficients', 'fignum', 104);
|
||||
end
|
||||
|
||||
try
|
||||
figure(339);hold on
|
||||
showEQfilter(eq_.e, eq_signal_sd.fs.*2,"displayname",'training','fignum',339);
|
||||
legend on
|
||||
end
|
||||
|
||||
show2Dconstellation(eq_signal_sd, tx_symbols,"displayname",'Visualization of symbol correlation','fignum',340);
|
||||
|
||||
% try
|
||||
% figure(240); hold on; plot(pow2db(movmean(eq_.debug_struct.error_tr',100)));ylim([-30,3]);title('error training');
|
||||
%
|
||||
% figure(241); hold on; plot(pow2db(movmean(eq_.debug_struct.update_tr',100)));title('update step training');
|
||||
%
|
||||
% figure(242); hold on; plot(pow2db(movmean(eq_.debug_struct.update',1000)));title('update step dd');
|
||||
% end
|
||||
|
||||
% eq_signal_sd.eye(eq_signal_sd.fs,M,"displayname",'Eye','fignum',105);
|
||||
|
||||
end
|
||||
211
Functions/EQ_blocks/ffe_db.m
Normal file
211
Functions/EQ_blocks/ffe_db.m
Normal file
@@ -0,0 +1,211 @@
|
||||
function [ffe_results] = ffe_db(eq_, M, rx_signal, tx_symbols, tx_bits, options)
|
||||
% FFE Processes signals through FFE equalizer
|
||||
%
|
||||
% Inputs:
|
||||
% eq_ - Equalizer object
|
||||
% M - Modulation order
|
||||
% rx_signal - Received signal
|
||||
% tx_symbols - Transmitted symbols
|
||||
% tx_bits - Transmitted bits
|
||||
% options - Optional parameters
|
||||
%
|
||||
% Outputs:
|
||||
% ffe_results - Results from FFE processing
|
||||
|
||||
arguments
|
||||
eq_
|
||||
M
|
||||
rx_signal
|
||||
tx_symbols
|
||||
tx_bits
|
||||
options.db_target = 0;
|
||||
options.precode_mode db_mode
|
||||
options.showAnalysis = 0;
|
||||
options.eth_style_symbol_mapping = 0;
|
||||
options.postFFE = [];
|
||||
options.database = [];
|
||||
end
|
||||
|
||||
%% Process signals through equalizer
|
||||
% FFE or VNLE
|
||||
if options.db_target
|
||||
tx_symbols_ref = Duobinary().encode(tx_symbols);
|
||||
db_ref_constellation = unique(tx_symbols_ref.signal);
|
||||
[eq_signal_sd, eq_noise] = eq_.process(rx_signal, tx_symbols_ref);
|
||||
else
|
||||
[eq_signal_sd, eq_noise] = eq_.process(rx_signal, tx_symbols);
|
||||
end
|
||||
|
||||
% Apply post-FFE if provided
|
||||
if ~isempty(options.postFFE)
|
||||
tic
|
||||
[eq_signal_sd, eq_noise] = options.postFFE.process(eq_signal_sd, tx_symbols);
|
||||
toc
|
||||
end
|
||||
|
||||
try
|
||||
ch_coefficients = arburg(eq_noise.signal,1);
|
||||
channel_alpha = ch_coefficients(2);
|
||||
end
|
||||
|
||||
% Hard decision on FFE output
|
||||
if options.db_target
|
||||
eq_signal_hd = PAMmapper(M, 0).quantize(eq_signal_sd,'custom_const',db_ref_constellation.');
|
||||
else
|
||||
eq_signal_hd = PAMmapper(M, 0).quantize(eq_signal_sd);
|
||||
end
|
||||
|
||||
if options.db_target
|
||||
eq_signal_hd = Duobinary().decode(eq_signal_hd);
|
||||
tx_symbols = Duobinary().encode(tx_symbols);
|
||||
tx_symbols = Duobinary().decode(tx_symbols);
|
||||
end
|
||||
|
||||
%% Calculate BER based on precoding mode
|
||||
[bits, errors, ber, error_pos, errors_precoded, ber_precoded] = calculateBER(eq_signal_hd, tx_symbols, tx_bits, options.precode_mode, M, options.eth_style_symbol_mapping);
|
||||
|
||||
%% Calculate performance metrics
|
||||
[snr, snr_lvl] = calc_snr(tx_symbols.signal, eq_noise.signal);
|
||||
% [gmi] = calc_air(eq_signal_sd, tx_symbols, "skip_front", 10000, "skip_end", 10000);
|
||||
[gmi] = calc_ngmi(eq_signal_sd,tx_symbols);
|
||||
gmi = max(gmi,0);
|
||||
|
||||
air = tx_symbols.fs .* floor(log2(double(M))*10)/10 .* gmi ./ log2(double(M));
|
||||
[evm_total, evm_lvl] = calc_evm(eq_signal_sd, tx_symbols);
|
||||
[std_total, std_lvl] = calc_std(eq_signal_sd, tx_symbols);
|
||||
[std_rxraw_total, std_rxraw_lvl] = calc_std(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols);
|
||||
|
||||
%% Display analysis if requested
|
||||
if options.showAnalysis
|
||||
displayAnalysis(eq_noise, eq_signal_sd, rx_signal, eq_, tx_symbols, M, options.postFFE);
|
||||
end
|
||||
|
||||
|
||||
%% Prepare output structure
|
||||
% Determine postFFE order
|
||||
if ~isempty(options.postFFE)
|
||||
npostFFE = options.postFFE.order;
|
||||
else
|
||||
npostFFE = 0;
|
||||
end
|
||||
|
||||
% Create FFE results structure
|
||||
ffe_results = struct();
|
||||
try
|
||||
eq_.e = [];
|
||||
eq_.e2 = [];
|
||||
eq_.e3 = [];
|
||||
eq_.b = [];
|
||||
eq_.b2 = [];
|
||||
eq_.b3 = [];
|
||||
end
|
||||
|
||||
ffe_results.config = Equalizerstruct();
|
||||
ffe_results.config.eq = jsonencode(eq_);
|
||||
ffe_results.config.equalizer_structure = int32(equalizer_structure.ffe);
|
||||
ffe_results.config.comment = 'function: ffe';
|
||||
|
||||
ffe_results.metrics = Metricstruct;
|
||||
ffe_results.metrics.result_id = NaN;
|
||||
ffe_results.metrics.run_id = NaN;
|
||||
ffe_results.metrics.eqParam_id = NaN;
|
||||
ffe_results.metrics.date_of_processing = datetime('now');
|
||||
ffe_results.metrics.BER = ber;
|
||||
ffe_results.metrics.numBits = bits;
|
||||
ffe_results.metrics.numBitErr = errors;
|
||||
ffe_results.metrics.BER_precoded = ber_precoded;
|
||||
ffe_results.metrics.numBitErr_precoded = errors_precoded;
|
||||
ffe_results.metrics.SNR = snr;
|
||||
ffe_results.metrics.SNR_level = snr_lvl;
|
||||
ffe_results.metrics.STD = std_total;
|
||||
ffe_results.metrics.STD_level = std_lvl;
|
||||
ffe_results.metrics.STDrx = std_rxraw_total;
|
||||
ffe_results.metrics.STDrx_level = std_rxraw_lvl;
|
||||
ffe_results.metrics.GMI = gmi;
|
||||
ffe_results.metrics.AIR = air;
|
||||
ffe_results.metrics.EVM = evm_total;
|
||||
ffe_results.metrics.EVM_level = evm_lvl;
|
||||
ffe_results.metrics.Alpha = channel_alpha;
|
||||
|
||||
|
||||
end
|
||||
|
||||
%% Helper Functions
|
||||
function [bits, errors, ber, error_pos, errors_precoded, ber_precoded] = calculateBER(eq_signal_hd, tx_symbols, tx_bits, precode_mode, M, eth_style)
|
||||
% Calculate BER based on precoding mode
|
||||
mapper = PAMmapper(M, 0, "eth_style", eth_style);
|
||||
|
||||
switch precode_mode
|
||||
case db_mode.no_db
|
||||
% TX Data is not precoded
|
||||
% A) Emulate diff precoding
|
||||
eq_signal_hd_precoded = Duobinary().encode(eq_signal_hd, "M", M);
|
||||
eq_signal_hd_precoded = Duobinary().decode(eq_signal_hd_precoded, "M", M);
|
||||
|
||||
tx_symbols_precoded = Duobinary().encode(tx_symbols);
|
||||
tx_symbols_precoded = Duobinary().decode(tx_symbols_precoded);
|
||||
|
||||
tx_bits_precoded = mapper.demap(tx_symbols_precoded);
|
||||
|
||||
rx_bits = mapper.demap(eq_signal_hd_precoded);
|
||||
[~, errors_precoded, ber_precoded, ~] = calc_ber(rx_bits.signal, tx_bits_precoded.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
|
||||
|
||||
% B) Just determine BER
|
||||
rx_bits = mapper.demap(eq_signal_hd);
|
||||
tx_bits = mapper.demap(tx_symbols);
|
||||
[bits, errors, ber, error_pos] = calc_ber(rx_bits.signal, tx_bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
|
||||
|
||||
case db_mode.db_precoded
|
||||
% Data is precoded on TX side
|
||||
% A) Decode at Rx if no DB targeting was applied
|
||||
eq_signal_hd_decoded = Duobinary().encode(eq_signal_hd, "M", M);
|
||||
eq_signal_hd_decoded = Duobinary().decode(eq_signal_hd_decoded, "M", M);
|
||||
rx_bits_decoded = mapper.demap(eq_signal_hd_decoded);
|
||||
[~, errors_precoded, ber_precoded, ~] = calc_ber(rx_bits_decoded.signal, tx_bits.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
|
||||
|
||||
% B) Omit the Coding by comparing with demapped TX symbol sequence
|
||||
tx_bits_demapped = mapper.demap(tx_symbols);
|
||||
rx_bits = mapper.demap(eq_signal_hd);
|
||||
[bits, errors, ber, error_pos] = calc_ber(rx_bits.signal, tx_bits_demapped.signal, "skip_front", 10, "skip_end", 10, "returnErrorLocation", 1);
|
||||
end
|
||||
end
|
||||
|
||||
function displayAnalysis(eq_noise, eq_signal_sd, rx_signal, eq_, tx_symbols, M, postFFE)
|
||||
% Display analysis plots and metrics
|
||||
|
||||
% Initialize figure handles
|
||||
% Corrected line - added tx_symbols as second positional argument
|
||||
% showLevelScatter(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols, "fignum", 100);
|
||||
|
||||
warning off
|
||||
showLevelScatter(eq_signal_sd, tx_symbols, "fignum", 101);
|
||||
figure(gcf);hold on; plot(((1:length(eq_noise.signal)) / eq_noise.fs) * 1e6,movmean(eq_noise.signal,2000,1), 'LineWidth',3,'Color','black')
|
||||
warning on
|
||||
|
||||
showLevelHistogram(eq_signal_sd, tx_symbols, "fignum", 102);
|
||||
|
||||
showEQNoisePSD(eq_noise, "fignum", 103, "displayname", 'Residual Noise after FFE');
|
||||
|
||||
% Figure 2: Post-FFE coefficients (if available)
|
||||
if ~isempty(postFFE)
|
||||
showEQcoefficients('n1', postFFE.e, "displayname", 'Coefficients', 'fignum', 104);
|
||||
end
|
||||
|
||||
try
|
||||
figure(339);hold on
|
||||
showEQfilter(eq_.e, eq_signal_sd.fs.*2,"displayname",'training','fignum',339);
|
||||
% showEQfilter(eq_.e, eq_signal_sd.fs.*2,"displayname",'dec. directed','fignum',339);
|
||||
legend on
|
||||
end
|
||||
|
||||
% try
|
||||
% figure(240); hold on; plot(pow2db(movmean(eq_.debug_struct.error_tr',100)));ylim([-30,3]);title('error training');
|
||||
%
|
||||
% figure(241); hold on; plot(pow2db(movmean(eq_.debug_struct.update_tr',100)));title('update step training');
|
||||
%
|
||||
% figure(242); hold on; plot(pow2db(movmean(eq_.debug_struct.update',1000)));title('update step dd');
|
||||
% end
|
||||
|
||||
eq_signal_sd.eye(eq_signal_sd.fs,M,"displayname",'Eye','fignum',105);
|
||||
|
||||
end
|
||||
149
Functions/EQ_blocks/ml_mlse.m
Normal file
149
Functions/EQ_blocks/ml_mlse.m
Normal file
@@ -0,0 +1,149 @@
|
||||
function [ml_mlse_results] = ml_mlse(eq_, M, rx_signal, tx_symbols, tx_bits, options)
|
||||
%
|
||||
%
|
||||
% Inputs:
|
||||
% eq_ - Equalizer object
|
||||
% M - Modulation order
|
||||
% rx_signal - Received signal
|
||||
% tx_symbols - Transmitted symbols
|
||||
% tx_bits - Transmitted bits
|
||||
% options - Optional parameters
|
||||
%
|
||||
% Outputs:
|
||||
% ffe_results - Results from FFE processing
|
||||
|
||||
arguments
|
||||
eq_
|
||||
M
|
||||
rx_signal
|
||||
tx_symbols
|
||||
tx_bits
|
||||
options.precode_mode db_mode
|
||||
options.eth_style_symbol_mapping = 0;
|
||||
options.postFFE = [];
|
||||
|
||||
end
|
||||
|
||||
%% Process signals through equalizer
|
||||
|
||||
[eq_signal_hd,y_ref] = eq_.process(rx_signal,tx_symbols);
|
||||
|
||||
%% Calculate BER based on precoding mode
|
||||
[bits, errors, ber, error_pos, errors_precoded, ber_precoded] = calculateBER(eq_signal_hd, tx_symbols, tx_bits, options.precode_mode, M, options.eth_style_symbol_mapping);
|
||||
|
||||
|
||||
% Create FFE results structure
|
||||
ml_mlse_results = struct();
|
||||
try
|
||||
eq_.e = [];
|
||||
eq_.e2 = [];
|
||||
eq_.e3 = [];
|
||||
eq_.b = [];
|
||||
eq_.b2 = [];
|
||||
eq_.b3 = [];
|
||||
end
|
||||
|
||||
ml_mlse_results.config = Equalizerstruct();
|
||||
|
||||
eq_small = strip_eq(eq_, 10);
|
||||
fn = fieldnames(eq_small);
|
||||
for k = 1:numel(fn)
|
||||
if issparse(eq_small.(fn{k}))
|
||||
eq_small.(fn{k}) = full(eq_small.(fn{k}));
|
||||
end
|
||||
end
|
||||
json_str = jsonencode(eq_small);
|
||||
|
||||
ml_mlse_results.config.eq = jsonencode(eq_);
|
||||
ml_mlse_results.config.equalizer_structure = int32(equalizer_structure.ml_mlse);
|
||||
ml_mlse_results.config.comment = 'function: ML-based MLSE';
|
||||
|
||||
ml_mlse_results.metrics = Metricstruct;
|
||||
% ml_mlse_results.metrics.result_id = NaN;
|
||||
% ml_mlse_results.metrics.run_id = NaN;
|
||||
% ml_mlse_results.metrics.eqParam_id = NaN;
|
||||
ml_mlse_results.metrics.date_of_processing = datetime('now');
|
||||
ml_mlse_results.metrics.BER = ber;
|
||||
ml_mlse_results.metrics.numBits = bits;
|
||||
ml_mlse_results.metrics.numBitErr = errors;
|
||||
ml_mlse_results.metrics.BER_precoded = ber_precoded;
|
||||
ml_mlse_results.metrics.numBitErr_precoded = errors_precoded;
|
||||
% ml_mlse_results.metrics.SNR = NaN;
|
||||
% ml_mlse_results.metrics.SNR_level = NaN;
|
||||
% ml_mlse_results.metrics.STD = NaN;
|
||||
% ml_mlse_results.metrics.STD_level = NaN;
|
||||
% ml_mlse_results.metrics.STDrx = NaN;
|
||||
% ml_mlse_results.metrics.STDrx_level = NaN;
|
||||
% ml_mlse_results.metrics.GMI = NaN;
|
||||
% ml_mlse_results.metrics.AIR = NaN;
|
||||
% ml_mlse_results.metrics.EVM = NaN;
|
||||
% ml_mlse_results.metrics.EVM_level = NaN;
|
||||
% ml_mlse_results.metrics.Alpha = NaN;
|
||||
|
||||
|
||||
end
|
||||
|
||||
%% Helper Functions
|
||||
function [bits, errors, ber, error_pos, errors_precoded, ber_precoded] = calculateBER(eq_signal_hd, tx_symbols, tx_bits, precode_mode, M, eth_style)
|
||||
% Calculate BER based on precoding mode
|
||||
mapper = PAMmapper(M, 0, "eth_style", eth_style);
|
||||
|
||||
switch precode_mode
|
||||
case db_mode.no_db
|
||||
% TX Data is not precoded
|
||||
% A) Emulate diff precoding
|
||||
eq_signal_hd_precoded = Duobinary().encode(eq_signal_hd, "M", M);
|
||||
eq_signal_hd_precoded = Duobinary().decode(eq_signal_hd_precoded, "M", M);
|
||||
|
||||
tx_symbols_precoded = Duobinary().encode(tx_symbols);
|
||||
tx_symbols_precoded = Duobinary().decode(tx_symbols_precoded);
|
||||
|
||||
tx_bits_precoded = mapper.demap(tx_symbols_precoded);
|
||||
|
||||
rx_bits = mapper.demap(eq_signal_hd_precoded);
|
||||
[~, errors_precoded, ber_precoded, ~] = calc_ber(rx_bits.signal, tx_bits_precoded.signal, "skip_front", 150, "skip_end", 150, "returnErrorLocation", 1);
|
||||
|
||||
% B) Just determine BER
|
||||
rx_bits = mapper.demap(eq_signal_hd);
|
||||
[bits, errors, ber, error_pos] = calc_ber(rx_bits.signal, tx_bits.signal, "skip_front", 150, "skip_end", 150, "returnErrorLocation", 1);
|
||||
|
||||
case db_mode.db_precoded
|
||||
% Data is precoded on TX side
|
||||
% A) Decode at Rx if no DB targeting was applied
|
||||
eq_signal_hd_decoded = Duobinary().encode(eq_signal_hd, "M", M);
|
||||
eq_signal_hd_decoded = Duobinary().decode(eq_signal_hd_decoded, "M", M);
|
||||
rx_bits_decoded = mapper.demap(eq_signal_hd_decoded);
|
||||
[~, errors_precoded, ber_precoded, ~] = calc_ber(rx_bits_decoded.signal, tx_bits.signal, "skip_front", 30000, "skip_end", 150, "returnErrorLocation", 1);
|
||||
|
||||
% B) Omit the Coding by comparing with demapped TX symbol sequence
|
||||
tx_bits_demapped = mapper.demap(tx_symbols);
|
||||
rx_bits = mapper.demap(eq_signal_hd);
|
||||
[bits, errors, ber, error_pos] = calc_ber(rx_bits.signal, tx_bits_demapped.signal, "skip_front", 30000, "skip_end", 150, "returnErrorLocation", 1);
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function eq_out = strip_eq(eq_, max_elems)
|
||||
% strip_eq removes all large fields from the ML_MLSE object
|
||||
% eq_out = strip_eq(eq_, max_elems)
|
||||
% max_elems ... maximum number of elements to keep (default = 10)
|
||||
|
||||
if nargin < 2
|
||||
max_elems = 10; % default threshold
|
||||
end
|
||||
|
||||
props = properties(eq_);
|
||||
for i = 1:numel(props)
|
||||
val = eq_.(props{i});
|
||||
if ~isempty(val)
|
||||
% Count total number of elements
|
||||
if numel(val) > max_elems
|
||||
eq_.(props{i}) = [];
|
||||
end
|
||||
end
|
||||
if issparse(val)
|
||||
eq_.(props{i}) = find(eq_.(props{i}));
|
||||
end
|
||||
end
|
||||
eq_out = eq_;
|
||||
end
|
||||
146
Functions/EQ_blocks/preprocessSignal.m
Normal file
146
Functions/EQ_blocks/preprocessSignal.m
Normal file
@@ -0,0 +1,146 @@
|
||||
function Scpe_sig = preprocessSignal(Scpe_sig, Symbols, fsym, options)
|
||||
% PREPROCESSSIGNAL Performs standard preprocessing on a signal
|
||||
%
|
||||
% Inputs:
|
||||
% Scpe_sig - Input signal
|
||||
% Symbols - Reference symbols for synchronization
|
||||
% fsym - Symbol frequency
|
||||
%
|
||||
% Outputs:
|
||||
% Scpe_sig - Preprocessed signal
|
||||
|
||||
arguments
|
||||
Scpe_sig
|
||||
Symbols
|
||||
fsym
|
||||
options.mode string = "auto"
|
||||
options.tx_pulseformer = []
|
||||
options.debug_plots (1,1) logical = false
|
||||
options.apply_gaussian_filter (1,1) logical = true
|
||||
options.gaussian_cutoff_factor (1,1) double = 0.52
|
||||
end
|
||||
|
||||
preprocessMode = resolvePreprocessMode(options.mode, options.tx_pulseformer, Symbols);
|
||||
targetFs = 2 * fsym;
|
||||
|
||||
switch preprocessMode
|
||||
case "matched_filter"
|
||||
matchedPulseformer = buildMatchedPulseformer(options.tx_pulseformer, Symbols, fsym, targetFs);
|
||||
Scpe_sig = matchedPulseformer.process(Scpe_sig);
|
||||
case "resample"
|
||||
Scpe_sig = Scpe_sig.resample("fs_out", targetFs);
|
||||
otherwise
|
||||
error('preprocessSignal:InvalidMode', 'Unsupported preprocessing mode "%s".', preprocessMode);
|
||||
end
|
||||
|
||||
[Scpe_sig, Scpe_cell] = Scpe_sig.tsynch("reference", Symbols, "fs_ref", fsym, "debug_plots", options.debug_plots);
|
||||
% Scpe_sig = Scpe_cell{1};
|
||||
|
||||
% Apply Gaussian filter
|
||||
if options.apply_gaussian_filter
|
||||
Scpe_sig = Filter('filtdegree', 8, "f_cutoff", Symbols.fs.*options.gaussian_cutoff_factor, ...
|
||||
"fs", Scpe_sig.fs, "filterType", filtertypes.gaussian, ...
|
||||
"active", true).process(Scpe_sig);
|
||||
end
|
||||
|
||||
%Remove DC offset
|
||||
Scpe_sig = Scpe_sig - mean(Scpe_sig.signal);
|
||||
|
||||
end
|
||||
|
||||
function preprocessMode = resolvePreprocessMode(requestedMode, txPulseformer, Symbols)
|
||||
requestedMode = string(requestedMode);
|
||||
if requestedMode ~= "auto"
|
||||
preprocessMode = requestedMode;
|
||||
return
|
||||
end
|
||||
|
||||
pulseformer = txPulseformer;
|
||||
if isempty(pulseformer)
|
||||
pulseformer = findSignalPulseformer(Symbols);
|
||||
end
|
||||
|
||||
if isRootRaisedCosinePulseformer(pulseformer)
|
||||
preprocessMode = "matched_filter";
|
||||
else
|
||||
preprocessMode = "resample";
|
||||
end
|
||||
end
|
||||
|
||||
function tf = isRootRaisedCosinePulseformer(pulseformer)
|
||||
tf = false;
|
||||
if isempty(pulseformer)
|
||||
return
|
||||
end
|
||||
|
||||
pulseValue = readPulseformerField(pulseformer, 'pulse', []);
|
||||
if isempty(pulseValue)
|
||||
return
|
||||
end
|
||||
|
||||
pulseValue = normalizePulseValue(pulseValue);
|
||||
tf = pulseValue == pulseform.rrc;
|
||||
end
|
||||
|
||||
function matchedPulseformer = buildMatchedPulseformer(txPulseformer, Symbols, fsym, targetFs)
|
||||
pulseformerMeta = txPulseformer;
|
||||
if isempty(pulseformerMeta)
|
||||
pulseformerMeta = findSignalPulseformer(Symbols);
|
||||
end
|
||||
|
||||
pulseValue = normalizePulseValue(readPulseformerField(pulseformerMeta, 'pulse', pulseform.rrc));
|
||||
alphaValue = readPulseformerField(pulseformerMeta, 'alpha', 0.05);
|
||||
pulseLengthValue = readPulseformerField(pulseformerMeta, 'pulselength', 16);
|
||||
|
||||
if isempty(alphaValue)
|
||||
alphaValue = 0.05;
|
||||
end
|
||||
|
||||
matchedPulseformer = Pulseformer( ...
|
||||
"fsym", fsym, ...
|
||||
"fdac", targetFs, ...
|
||||
"pulse", pulseValue, ...
|
||||
"pulselength", pulseLengthValue, ...
|
||||
"alpha", alphaValue, ...
|
||||
"matched", 1);
|
||||
end
|
||||
|
||||
function value = readPulseformerField(pulseformerMeta, fieldName, defaultValue)
|
||||
value = defaultValue;
|
||||
if isempty(pulseformerMeta)
|
||||
return
|
||||
end
|
||||
|
||||
if isstruct(pulseformerMeta) && isfield(pulseformerMeta, fieldName)
|
||||
candidate = pulseformerMeta.(fieldName);
|
||||
if ~isempty(candidate)
|
||||
value = candidate;
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if isobject(pulseformerMeta) && isprop(pulseformerMeta, fieldName)
|
||||
candidate = pulseformerMeta.(fieldName);
|
||||
if ~isempty(candidate)
|
||||
value = candidate;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function pulseValue = normalizePulseValue(rawValue)
|
||||
if isa(rawValue, 'pulseform')
|
||||
pulseValue = rawValue;
|
||||
return
|
||||
end
|
||||
|
||||
if iscell(rawValue)
|
||||
rawValue = rawValue{1};
|
||||
end
|
||||
|
||||
if isstring(rawValue) || ischar(rawValue)
|
||||
pulseValue = pulseform.(char(string(rawValue)));
|
||||
return
|
||||
end
|
||||
|
||||
pulseValue = pulseform(rawValue);
|
||||
end
|
||||
308
Functions/EQ_blocks/vnle_postfilter_mlse.m
Normal file
308
Functions/EQ_blocks/vnle_postfilter_mlse.m
Normal file
@@ -0,0 +1,308 @@
|
||||
function [ffe_results, mlse_results] = vnle_postfilter_mlse(eq_, pf_, mlse_, M, rx_signal, tx_symbols, tx_bits, options)
|
||||
% VNLE_POSTFILTER_MLSE Processes signals through VNLE, postfilter, and MLSE
|
||||
%
|
||||
% Inputs:
|
||||
% eq_ - Equalizer object
|
||||
% pf_ - Postfilter object
|
||||
% mlse_ - MLSE object
|
||||
% M - Modulation order
|
||||
% rx_signal - Received signal
|
||||
% tx_symbols - Transmitted symbols
|
||||
% tx_bits - Transmitted bits
|
||||
% options - Optional parameters
|
||||
%
|
||||
% Outputs:
|
||||
% ffe_results - Results from FFE/VNLE processing
|
||||
% mlse_results - Results from MLSE processing
|
||||
|
||||
arguments
|
||||
eq_
|
||||
pf_
|
||||
mlse_
|
||||
M
|
||||
rx_signal
|
||||
tx_symbols
|
||||
tx_bits
|
||||
options.precode_mode db_mode
|
||||
options.showAnalysis = 0;
|
||||
options.eth_style_symbol_mapping = 0;
|
||||
options.postFFE = [];
|
||||
options.database = [];
|
||||
end
|
||||
|
||||
%% Process signals through equalizers
|
||||
% FFE or VNLE
|
||||
[eq_signal_sd, eq_noise] = eq_.process(rx_signal, tx_symbols);
|
||||
|
||||
% Apply post-FFE if provided (does not work properly at the moment...very sad)
|
||||
if ~isempty(options.postFFE)
|
||||
tic
|
||||
[eq_signal_sd, eq_noise] = options.postFFE.process(eq_signal_sd, tx_symbols);
|
||||
toc
|
||||
end
|
||||
|
||||
% Hard decision on VNLE output
|
||||
eq_signal_hd = PAMmapper(M, 0).quantize(eq_signal_sd);
|
||||
|
||||
% Process through postfilter and MLSE
|
||||
|
||||
[mlse_sig_sd,whitened_noise] = pf_.process(eq_signal_sd, eq_noise);
|
||||
|
||||
[tx_symbols_pr,~] = pf_.process(tx_symbols, eq_noise);
|
||||
|
||||
if 0 %tx_symbols.fs > 190e9
|
||||
if pf_.ncoeff == 1
|
||||
if pf_.coefficients(2) < 0
|
||||
% coeff is negative for too high/ bad VNLE convergence
|
||||
pf_.coefficients(2) = 0.9;
|
||||
|
||||
end
|
||||
else
|
||||
%long memory / pf respinse - not sure what to set here in a worst
|
||||
%case :-)
|
||||
|
||||
end
|
||||
%do it again:
|
||||
pf_.useBurg = 0;
|
||||
[mlse_sig_sd,whitened_noise] = pf_.process(eq_signal_sd, eq_noise);
|
||||
end
|
||||
|
||||
mlse_.DIR = pf_.coefficients;
|
||||
|
||||
GMI_MLSE = NaN;
|
||||
if isa(mlse_,'MLSE_viterbi')
|
||||
[mlse_sig_sd] = mlse_.process(mlse_sig_sd);
|
||||
else
|
||||
[mlse_sig_sd,LLR,GMI_MLSE] = mlse_.process(mlse_sig_sd,tx_symbols);
|
||||
end
|
||||
|
||||
mlse_sig_hd = PAMmapper(M, 0, "eth_style", options.eth_style_symbol_mapping).quantize(mlse_sig_sd);
|
||||
|
||||
%% Calculate BER based on precoding mode
|
||||
[numbits, errors, bers, ~] = calculateBER(eq_signal_hd, mlse_sig_hd, tx_symbols, tx_bits, options.precode_mode, M, options.eth_style_symbol_mapping);
|
||||
|
||||
%% Calculate performance metrics
|
||||
% VNLE metrics
|
||||
[snr_vnle, snr_vnle_lvl] = calc_snr(tx_symbols.signal, eq_noise.signal);
|
||||
% [gmi_vnle] = calc_air(eq_signal_sd, tx_symbols, "skip_front", 10000, "skip_end", 10000);
|
||||
|
||||
%calculate bitwise GMI
|
||||
[gmi_vnle] = calc_ngmi(eq_signal_sd,tx_symbols);
|
||||
gmi_vnle = max(gmi_vnle,0);
|
||||
|
||||
air_vnle = tx_symbols.fs .* floor(log2(double(M))*10)/10 .* gmi_vnle ./ log2(double(M));
|
||||
[evm_vnle_total, evm_vnle_lvl] = calc_evm(eq_signal_sd, tx_symbols);
|
||||
[std_vnle_total, std_vnle_lvl] = calc_std(eq_signal_sd, tx_symbols);
|
||||
[std_rxraw_total, std_rxraw_lvl] = calc_std(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols);
|
||||
|
||||
% MLSE metrics
|
||||
alpha = arburg(eq_noise.signal,1);%pf_.coefficients(2);
|
||||
alpha = alpha(2);
|
||||
gmi_mlse = max(GMI_MLSE,0);
|
||||
air_mlse = tx_symbols.fs .* floor(log2(double(M))*10)/10 .* gmi_mlse ./ log2(double(M));
|
||||
|
||||
%% Display analysis if requested
|
||||
if options.showAnalysis
|
||||
displayAnalysis(eq_noise,whitened_noise, eq_signal_sd, rx_signal, eq_, pf_, mlse_, tx_symbols, M, options.postFFE);
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
%% Prepare output structures
|
||||
% Determine postFFE order
|
||||
if ~isempty(options.postFFE)
|
||||
npostFFE = options.postFFE.order;
|
||||
else
|
||||
npostFFE = 0;
|
||||
end
|
||||
|
||||
ffe_results = struct();
|
||||
ffe_results.metrics = Metricstruct;
|
||||
ffe_results.metrics.result_id = NaN;
|
||||
ffe_results.metrics.run_id = NaN;
|
||||
ffe_results.metrics.eqParam_id = NaN;
|
||||
ffe_results.metrics.date_of_processing = datetime('now');
|
||||
ffe_results.metrics.BER = bers.vnle;
|
||||
ffe_results.metrics.numBits = numbits.vnle;
|
||||
ffe_results.metrics.numBitErr = errors.vnle;
|
||||
ffe_results.metrics.BER_precoded = bers.vnle_precoded;
|
||||
ffe_results.metrics.numBitErr_precoded = errors.vnle_precoded;
|
||||
ffe_results.metrics.SNR = snr_vnle;
|
||||
ffe_results.metrics.SNR_level = snr_vnle_lvl;
|
||||
ffe_results.metrics.STD = std_vnle_total;
|
||||
ffe_results.metrics.STD_level = std_vnle_lvl;
|
||||
ffe_results.metrics.STDrx = std_rxraw_total;
|
||||
ffe_results.metrics.STDrx_level = std_rxraw_lvl;
|
||||
ffe_results.metrics.GMI = gmi_vnle;
|
||||
ffe_results.metrics.AIR = air_vnle;
|
||||
ffe_results.metrics.EVM = evm_vnle_total;
|
||||
ffe_results.metrics.EVM_level = evm_vnle_lvl;
|
||||
ffe_results.metrics.Alpha = alpha;
|
||||
|
||||
try
|
||||
eq_.e = [];
|
||||
eq_.e2 = [];
|
||||
eq_.e3 = [];
|
||||
end
|
||||
|
||||
ffe_results.config = Equalizerstruct();
|
||||
ffe_results.config.eq = jsonencode(eq_);
|
||||
ffe_results.config.equalizer_structure = int32(equalizer_structure.vnle);
|
||||
ffe_results.config.comment = 'function: vnle_postfilter_mlse - FFE part';
|
||||
|
||||
mlse_results = struct();
|
||||
mlse_results.metrics = Metricstruct;
|
||||
mlse_results.metrics.result_id = NaN;
|
||||
mlse_results.metrics.run_id = NaN;
|
||||
mlse_results.metrics.eqParam_id = NaN;
|
||||
mlse_results.metrics.date_of_processing = datetime('now');
|
||||
mlse_results.metrics.BER = bers.mlse;
|
||||
mlse_results.metrics.numBits = numbits.mlse;
|
||||
mlse_results.metrics.numBitErr = errors.mlse;
|
||||
mlse_results.metrics.BER_precoded = bers.mlse_precoded;
|
||||
mlse_results.metrics.numBitErr_precoded = errors.mlse_precoded;
|
||||
% mlse_results.metrics.SNR = NaN;
|
||||
mlse_results.metrics.GMI = gmi_mlse;
|
||||
mlse_results.metrics.AIR = air_mlse;
|
||||
% mlse_results.metrics.EVM = NaN;
|
||||
% mlse_results.metrics.EVM_level = NaN;
|
||||
mlse_results.metrics.Alpha = alpha;
|
||||
mlse_results.metrics.MLSE_dir = mlse_.DIR;
|
||||
|
||||
% Create MLSE results structure
|
||||
mlse_results.config = Equalizerstruct();
|
||||
mlse_results.config.eq = jsonencode(eq_);
|
||||
mlse_.DIR = length(mlse_.DIR)-1;
|
||||
mlse_results.config.mlse = jsonencode(mlse_);
|
||||
mlse_results.config.equalizer_structure = int32(equalizer_structure.vnle_pf_mlse);
|
||||
mlse_results.config.comment = 'function: vnle_postfilter_mlse - MLSE part';
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
%% Helper Functions
|
||||
function [numbits, errors, ber, error_locations] = calculateBER(eq_signal_hd, mlse_sig_hd, tx_symbols, tx_bits, precode_mode, M, eth_style)
|
||||
% Initialize output structure
|
||||
numbits = struct('vnle', 0, 'mlse', 0);
|
||||
errors = struct('vnle', 0, 'mlse', 0, 'vnle_precoded', 0, 'mlse_precoded', 0);
|
||||
ber = struct('vnle', 0, 'mlse', 0, 'vnle_precoded', 0, 'mlse_precoded', 0);
|
||||
error_locations = struct('vnle', [], 'mlse', []);
|
||||
|
||||
% PAM mapper for demapping
|
||||
mapper = PAMmapper(M, 0, "eth_style", eth_style);
|
||||
|
||||
switch precode_mode
|
||||
case db_mode.no_db
|
||||
% TX Data is not precoded
|
||||
% A) Emulate diff precoding
|
||||
eq_signal_hd_precoded = Duobinary().encode(eq_signal_hd, "M", M);
|
||||
eq_signal_hd_precoded = Duobinary().decode(eq_signal_hd_precoded, "M", M);
|
||||
|
||||
mlse_sig_hd_precoded = Duobinary().encode(mlse_sig_hd, "M", M);
|
||||
mlse_sig_hd_precoded = Duobinary().decode(mlse_sig_hd_precoded, "M", M);
|
||||
|
||||
tx_symbols_precoded = Duobinary().encode(tx_symbols);
|
||||
tx_symbols_precoded = Duobinary().decode(tx_symbols_precoded);
|
||||
|
||||
tx_bits_precoded = mapper.demap(tx_symbols_precoded);
|
||||
|
||||
rx_bits_vnle = mapper.demap(eq_signal_hd_precoded);
|
||||
[~, errors.vnle_precoded, ber.vnle_precoded, ~] = calc_ber(rx_bits_vnle.signal, tx_bits_precoded.signal, "skip_front", 100, "skip_end", 150, "returnErrorLocation", 1);
|
||||
|
||||
rx_bits_mlse = mapper.demap(mlse_sig_hd_precoded);
|
||||
[~, errors.mlse_precoded, ber.mlse_precoded, ~] = calc_ber(rx_bits_mlse.signal, tx_bits_precoded.signal, "skip_front", 100, "skip_end", 150, "returnErrorLocation", 1);
|
||||
|
||||
% B) Just determine BER
|
||||
rx_bits_vnle = mapper.demap(eq_signal_hd);
|
||||
[numbits.vnle, errors.vnle, ber.vnle, error_locations.vnle] = calc_ber(rx_bits_vnle.signal, tx_bits.signal, "skip_front", 100, "skip_end", 150, "returnErrorLocation", 1);
|
||||
|
||||
rx_bits_mlse = mapper.demap(mlse_sig_hd);
|
||||
[numbits.mlse, errors.mlse, ber.mlse, error_locations.mlse] = calc_ber(rx_bits_mlse.signal, tx_bits.signal, "skip_front", 100, "skip_end", 150, "returnErrorLocation", 1);
|
||||
|
||||
case db_mode.db_precoded
|
||||
% Data is precoded on TX side
|
||||
% A) Decode at Rx if no DB targeting was applied
|
||||
eq_signal_hd_decoded = Duobinary().encode(eq_signal_hd, "M", M);
|
||||
eq_signal_hd_decoded = Duobinary().decode(eq_signal_hd_decoded, "M", M);
|
||||
rx_bits_vnle_decoded = mapper.demap(eq_signal_hd_decoded);
|
||||
[~, errors.vnle_precoded, ber.vnle_precoded, ~] = calc_ber(rx_bits_vnle_decoded.signal, tx_bits.signal, "skip_front", 100, "skip_end", 150, "returnErrorLocation", 1);
|
||||
|
||||
mlse_sig_hd_decoded = Duobinary().encode(mlse_sig_hd, "M", M);
|
||||
mlse_sig_hd_decoded = Duobinary().decode(mlse_sig_hd_decoded, "M", M);
|
||||
rx_bits_mlse_decoded = mapper.demap(mlse_sig_hd_decoded);
|
||||
[~, errors.mlse_precoded, ber.mlse_precoded, err_loc_precoded] = calc_ber(rx_bits_mlse_decoded.signal, tx_bits.signal, "skip_front", 100, "skip_end", 150, "returnErrorLocation", 1);
|
||||
|
||||
% B) Omit the Coding by comparing with demapped TX symbol sequence
|
||||
tx_bits_demapped = mapper.demap(tx_symbols);
|
||||
rx_bits_vnle = mapper.demap(eq_signal_hd);
|
||||
[numbits.vnle, errors.vnle, ber.vnle, error_locations.vnle] = calc_ber(rx_bits_vnle.signal, tx_bits_demapped.signal, "skip_front", 100, "skip_end", 150, "returnErrorLocation", 1);
|
||||
|
||||
rx_bits_mlse = mapper.demap(mlse_sig_hd);
|
||||
[numbits.mlse, errors.mlse, ber.mlse, error_locations.mlse] = calc_ber(rx_bits_mlse.signal, tx_bits_demapped.signal, "skip_front", 100, "skip_end", 150, "returnErrorLocation", 1);
|
||||
end
|
||||
|
||||
% cols = linspecer(8);
|
||||
% burst_precoded = count_error_bursts(err_loc_precoded, 40);
|
||||
% burst_normal = count_error_bursts(error_locations.mlse, 40);
|
||||
% figure();hold on;
|
||||
% stem(1:40,burst_normal,'LineWidth',1,'Color',cols(1,:),'Marker','_','DisplayName','w/o diff. precoder');
|
||||
% stem(1:40,burst_precoded,'LineWidth',1,'Color',cols(2,:),'Marker','.','LineStyle','-','DisplayName','w diff. precoder');
|
||||
% xlabel('Bit Error Burst Length')
|
||||
% ylabel('Occurence')
|
||||
% set(gca, 'yscale', 'log');
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
function displayAnalysis(eq_noise, whitened_noise, eq_signal_sd, rx_signal, eq_, pf_, mlse_, tx_symbols, M, postFFE)
|
||||
|
||||
% rx_signal.spectrum("displayname",'Rx Signal','fignum',100,'normalizeTo0dB',1);
|
||||
|
||||
% Display analysis plots and metrics
|
||||
figure(336);
|
||||
hold on;
|
||||
eq_signal_sd.spectrum("displayname",'Equalized Signal','fignum',336,'normalizeTo0dB',0);
|
||||
eq_noise.spectrum("displayname",'Equalized Signal','fignum',336,'normalizeTo0dB',0);
|
||||
|
||||
showEQNoisePSD(eq_noise, "fignum", 338, "displayname", 'Residual Noise after VNLE', 'postfilter_taps', pf_.coefficients);
|
||||
|
||||
for t = 1:4
|
||||
|
||||
pf_.ncoeff = t;
|
||||
[~,~] = pf_.process(eq_signal_sd, eq_noise);
|
||||
showEQNoisePSD(eq_noise, "fignum", 339, "displayname", 'Residual Noise after VNLE', 'postfilter_taps', pf_.coefficients);
|
||||
end
|
||||
|
||||
|
||||
tx_symbols.spectrum("displayname",'Equalized Signal','fignum',340,'normalizeTo0dB',1);
|
||||
if ~isempty(postFFE)
|
||||
showEQcoefficients('n1', postFFE.e, "displayname", 'Coefficients', 'fignum', 341);
|
||||
end
|
||||
|
||||
showEQcoefficients('n1', eq_.e,'n2', eq_.e2,'n3', eq_.e3, "displayname", 'Coefficients', 'fignum', 339);
|
||||
showEQfilter(eq_.e, eq_signal_sd.fs.*2);
|
||||
|
||||
figure(340); clf;
|
||||
eq_signal_sd.eye(eq_signal_sd.fs, M, "fignum", 342);
|
||||
|
||||
figure(341); clf;
|
||||
showLevelHistogram(eq_signal_sd, tx_symbols, "fignum", 343);
|
||||
|
||||
warning off
|
||||
showLevelScatter(eq_signal_sd, tx_symbols, "fignum", 344);
|
||||
% showLevelScatter(rx_signal.resample("fs_out", tx_symbols.fs), tx_symbols, "fignum", 401);
|
||||
drawnow;
|
||||
warning on
|
||||
|
||||
|
||||
whitened_noise.spectrum("displayname",'after postfilter','fignum',345);
|
||||
eq_noise.spectrum("displayname",'before postfilter','fignum',345);
|
||||
|
||||
end
|
||||
Reference in New Issue
Block a user