restructure and organize

This commit is contained in:
Silas Oettinghaus
2026-06-22 22:58:35 +02:00
parent 3603fd7853
commit c4f75b7ec4
10 changed files with 108 additions and 116 deletions

View 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

View 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

View 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

View 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
View 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

View 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

View 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

View 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

View 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