Files
imdd_silas/Functions/EQ_recipes/dsp_400g_recipe.m
2026-07-20 10:22:11 +02:00

450 lines
16 KiB
Matlab

function output = dsp_400g_recipe(Scpe_sig_raw, Symbols, Tx_bits, options)
%dsp_400g_recipe Run 400G equalizer schemes from a synchronized scope signal.
arguments
Scpe_sig_raw
Symbols
Tx_bits
options.fsym
options.M
options.duob_mode
options.dataTable table
options.userParameters struct = struct()
options.preprocess_mode string = "auto"
options.tx_pulseformer = []
options.debug_plots (1,1) logical = false
end
p = defaultRecipeParameters();
p.preprocess_mode = options.preprocess_mode;
p = applyUserParameters(p, options.userParameters);
output = struct();
Scpe_sig = preprocessSignal(Scpe_sig_raw, Symbols, options.fsym, ...
"mode", p.preprocess_mode, ...
"tx_pulseformer", options.tx_pulseformer, ...
"debug_plots", options.debug_plots);
[Scpe_sig, Symbols, Tx_bits] = alignDspInputs(Scpe_sig, Symbols, Tx_bits, p.eq_sps);
if isempty(p.ml_mlse_len_tr)
p.ml_mlse_len_tr = floor(length(Scpe_sig) / 4);
end
if p.plot_input_signal
showLevelScatter(Scpe_sig.normalize("mode","rms"), Symbols, ...
"fsym", options.fsym, ...
"fignum", p.input_plot_fignum, ...
"normalize", true);
end
if options.duob_mode ~= db_mode.db_encoded
if p.run_ffe
eq_ffe = FFE("epochs_tr", p.epochs_tr, ...
"epochs_dd", p.epochs_dd, ...
"len_tr", p.len_tr, ...
"mu_dd", p.ffe_mu_dd, ...
"mu_tr", p.ffe_mu_tr, ...
"order", p.ffe_order(1), ...
"sps", p.eq_sps, ...
"decide", false, ...
"optmize_mus", p.optimize_mus, ...
"dd_mode", p.dd_mode, ...
"adaption_technique", p.ffe_adaption, ...
"dc_tracking_mu", p.mu_dc);
[ffe_results, equalized_signal] = runFfe(eq_ffe, "FFE", ...
Scpe_sig, Symbols, Tx_bits, options);
ffe_results.config.equalizer_structure = equalizer_structure.ffe;
ffe_results.recipe_config = collectRecipeConfig("ffe", eq_ffe, p, options);
output.ffe_package = ffe_results;
if p.plot_output_signals
plotEqSignals(equalized_signal, Symbols, options, p.output_plot_fignum, -1);
end
end
if p.run_vnle
% eq_vnle = VNLE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2, ...
% "mu_dd",[0.0004 0.0005 0.0006],"mu_tr",[0.0001 0.0008 0.001], ...
% "order",[150,5,5],"sps",2,"decide",1, ...
% "optmize_mus",1,"mu_optimization_len",2^15);
eq_vnle = EQ("Ne", p.vnle_ffe_order, ...
"Nb", p.vnle_dfe_order, ...
"training_length", p.len_tr, ...
"training_loops", p.epochs_tr, ...
"dd_loops", p.epochs_dd, ...
"K", p.eq_K, ...
"DCmu", p.mu_dc, ...
"DDmu", [p.eq_mu_ffe p.mu_dfe], ...
"DFEmu", p.dfe_mu_feedback, ...
"FFEmu", 0, ...
"plotfinal", 0, ...
"ideal_dfe", false);
[vnle_results, equalized_signal] = runFfe(eq_vnle, "VNLE", ...
Scpe_sig, Symbols, Tx_bits, options);
vnle_results.config.equalizer_structure = equalizer_structure.ffe;
ffe_results.recipe_config = collectRecipeConfig("vnle", eq_ffe, p, options);
output.vnle_package = vnle_results;
if p.plot_output_signals
plotEqSignals(equalized_signal, Symbols, options, p.output_plot_fignum, -1);
end
end
if p.run_dfe
eq_dfe = EQ("Ne", p.dfe_ffe_order, ...
"Nb", p.dfe_feedback_order, ...
"training_length", p.len_tr, ...
"training_loops", p.epochs_tr, ...
"dd_loops", p.epochs_dd, ...
"K", p.eq_K, ...
"DCmu", p.mu_dc, ...
"DDmu", [p.eq_mu_ffe p.mu_dfe], ...
"DFEmu", p.dfe_mu_feedback, ...
"FFEmu", 0, ...
"plotfinal", 0, ...
"ideal_dfe", false);
dfe_results = ffe(eq_dfe, options.M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", options.duob_mode, ...
"showAnalysis", options.debug_plots, ...
"postFFE", [], ...
"eth_style_symbol_mapping", 0);
dfe_results.config.equalizer_structure = equalizer_structure.dfe;
dfe_results.metrics.print("description", resultDescription("DFE", options));
dfe_results.recipe_config = collectRecipeConfig("dfe", eq_dfe, p, options);
output.dfe_package = dfe_results;
end
if p.run_vnle_mlse
eq_vnle = EQ("Ne", p.vnle_ffe_order, ...
"Nb", p.vnle_dfe_order, ...
"training_length", p.len_tr, ...
"training_loops", p.epochs_tr, ...
"dd_loops", p.epochs_dd, ...
"K", p.eq_K, ...
"DCmu", p.mu_dc, ...
"DDmu", [p.eq_mu_ffe p.mu_dfe], ...
"DFEmu", p.dfe_mu_feedback, ...
"FFEmu", 0, ...
"plotfinal", 0, ...
"ideal_dfe", false);
pf = Postfilter("ncoeff", p.pf_ncoeffs, "useBurg", true);
mlse = buildMlse(options.M, options.duob_mode, p, "pf_mlse");
[vnle_results, mlse_results] = vnle_postfilter_mlse(eq_vnle, pf, mlse, ...
options.M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", options.duob_mode, ...
"showAnalysis", options.debug_plots, ...
"postFFE", [], ...
"eth_style_symbol_mapping", 0);
vnle_results.config.equalizer_structure = equalizer_structure.vnle;
vnle_results.recipe_config = collectRecipeConfig("vnle", eq_vnle, p, options);
mlse_results.config.equalizer_structure = equalizer_structure.vnle_pf_mlse;
mlse_results.recipe_config = collectRecipeConfig("vnle_pf_mlse", eq_vnle, p, options);
vnle_results.metrics.print("description", resultDescription("VNLE", options));
mlse_results.metrics.print("description", resultDescription("VNLE + PF + MLSE", options));
output.vnle_package = vnle_results;
output.mlse_package = mlse_results;
end
if p.run_dbtgt
eq_dbtgt = EQ("Ne", p.dbtgt_ffe_order, ...
"Nb", p.dbtgt_dfe_order, ...
"training_length", p.len_tr, ...
"training_loops", p.epochs_tr, ...
"dd_loops", p.epochs_dd, ...
"K", p.eq_K, ...
"DCmu", p.mu_dc, ...
"DDmu", [p.eq_mu_ffe p.mu_dfe], ...
"DFEmu", p.dfe_mu_feedback, ...
"FFEmu", 0, ...
"plotfinal", 0, ...
"ideal_dfe", true);
mlse_db = buildMlse(options.M, options.duob_mode, p, "db_target");
dbtgt_results = runDuobinaryTarget(eq_dbtgt, mlse_db, ...
Scpe_sig, Symbols, Tx_bits, options, p);
dbtgt_results.config.equalizer_structure = equalizer_structure.vnle_db_mlse;
dbtgt_results.recipe_config = collectRecipeConfig("vnle_db_mlse", eq_dbtgt, p, options);
dbtgt_results.metrics.print("description", resultDescription("VNLE DB target + MLSE", options));
output.dbtgt_package = dbtgt_results;
end
if p.run_ml_mlse
ml_mlse_equalizer = ML_MLSE("epochs_tr", p.ml_mlse_epochs_tr, ...
"epochs_dd", p.ml_mlse_epochs_dd, ...
"len_tr", p.ml_mlse_len_tr, ...
"mu_dd", p.ml_mlse_mu_dd, ...
"mu_tr", p.ml_mlse_mu_tr, ...
"order", p.ml_mlse_order, ...
"sps", p.eq_sps, ...
"traceback_depth", p.ml_mlse_traceback_depth, ...
"L", p.ml_mlse_L, ...
"delta", p.ml_mlse_delta, ...
"adaptive_mu", p.ml_mlse_adaptive_mu);
ml_mlse_results = ml_mlse(ml_mlse_equalizer, options.M, ...
Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", options.duob_mode);
ml_mlse_results.config.equalizer_structure = equalizer_structure.ml_mlse;
ml_mlse_results.recipe_config = collectRecipeConfig("ml_mlse", ml_mlse_equalizer, p, options);
output.mlmlse_package = ml_mlse_results;
end
else
if p.run_ml_mlse_db
ml_mlse_db_equalizer = ML_MLSE_DUOBINARY("epochs_tr", p.ml_mlse_epochs_tr, ...
"epochs_dd", p.ml_mlse_epochs_dd, ...
"len_tr", p.ml_mlse_len_tr, ...
"mu_dd", p.ml_mlse_mu_dd, ...
"mu_tr", p.ml_mlse_mu_tr, ...
"order", p.ml_mlse_order, ...
"sps", p.eq_sps, ...
"traceback_depth", p.ml_mlse_traceback_depth, ...
"L", p.ml_mlse_L, ...
"delta", p.ml_mlse_delta, ...
"adaptive_mu", p.ml_mlse_adaptive_mu);
% Use a precoded reference for the detector; the received waveform remains encoded.
ref_sym = PAMmapper(options.M,0).map(Tx_bits);
Symbols_precoded = Duobinary().precode(ref_sym); % precoded
ml_mlse_db_results = ml_mlse(ml_mlse_db_equalizer, options.M, ...
Scpe_sig, Symbols_precoded, Tx_bits, ...
"precode_mode", db_mode.db_precoded);
ml_mlse_db_results.config.equalizer_structure = equalizer_structure.ml_mlse;
ml_mlse_db_results.config.comment = 'function: ML-based MLSE; duobinary encoded';
ml_mlse_db_results.recipe_config = collectRecipeConfig("ml_mlse_db", ml_mlse_db_equalizer, p, options);
output.mlmlse_db_package = ml_mlse_db_results;
end
if p.run_mlse_db
eq_db_enc = EQ("Ne", p.dbtgt_ffe_order, ...
"Nb", p.dbtgt_dfe_order, ...
"training_length", p.len_tr, ...
"training_loops", p.epochs_tr, ...
"dd_loops", p.epochs_dd, ...
"K", p.eq_K, ...
"DCmu", p.mu_dc, ...
"DDmu", [p.eq_mu_ffe p.mu_dfe], ...
"DFEmu", p.dfe_mu_feedback, ...
"FFEmu", 0, ...
"plotfinal", 0, ...
"ideal_dfe", true);
mlse_db_enc = MLSE("DIR", [1,1], ...
"duobinary_output", 0, ...
"M", options.M, ...
"trellis_states", PAMmapper(options.M,0).levels);
if isempty(p.decoding_mode)
mlse_db_results = duobinary_signaling(eq_db_enc, mlse_db_enc, ...
options.M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", options.duob_mode, ...
"showAnalysis", options.debug_plots, ...
"postFFE", []);
mlse_db_results.metrics.print("description", resultDescription(["DB Encoded; MLSE"], options));
else
mlse_db_results = duobinary_signaling(eq_db_enc, mlse_db_enc, ...
options.M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", options.duob_mode, ...
"showAnalysis", options.debug_plots, ...
"postFFE", [],"decoding_mode",p.decoding_mode);
mlse_db_results.metrics.print("description", resultDescription(["DB Encoded "+string(p.decoding_mode)], options));
end
mlse_db_results.config.equalizer_structure = equalizer_structure.db_encoded;
mlse_db_results.recipe_config = collectRecipeConfig("mlse_db", eq_db_enc, p, options);
output.mlse_db_package = mlse_db_results;
end
end
end
function p = defaultRecipeParameters()
p = struct();
p.run_ffe = false;
p.run_vnle = false;
p.run_dfe = false;
p.run_vnle_mlse = false;
p.run_dbtgt = false;
p.run_ml_mlse = true; % non-encoded and precoded branches
p.run_ml_mlse_db = true; % db_encoded: ML-based MLSE
p.run_mlse_db = true; % db_encoded: conventional MLSE
p.preprocess_mode = "auto";
p.plot_input_signal = false;
p.plot_output_signals = false;
p.input_plot_fignum = 400;
p.output_plot_fignum = 410;
p.eq_sps = 2;
p.eq_K = 2;
p.len_tr = 4096*2;
p.epochs_tr = 5;
p.epochs_dd = 5;
p.dd_mode = true;
p.optimize_mus = true;
p.ffe_order = [50, 0, 0];
p.dfe_ffe_order = [50, 5, 5];
p.vnle_ffe_order = [50, 5, 5];
p.dbtgt_ffe_order = [50, 5, 5];
p.vnle_dfe_order = [0, 0, 0];
p.dbtgt_dfe_order = [0, 0, 0];
p.dfe_feedback_order = [2, 0, 0];
p.eq_mu_ffe = [0.0001, 0.0008, 0.001];
p.ffe_mu_tr = 0.4;
p.ffe_mu_dd = 0.1;
p.ffe_adaption = "nlms";
p.mu_dfe = 0.0004;
p.mu_dc = 1.021e-05;
p.dfe_mu_feedback = 0.005;
p.pf_ncoeffs = 1;
p.use_viterbi = false;
p.mlse_scale_mode = 2;
p.mlse_trellis_state_mode = 2;
p.dbtgt_trellis_state_mode = 3;
p.decoding_mode = [];
p.ml_mlse_mu_tr = 0.03;
p.ml_mlse_mu_dd = 0.03;
p.ml_mlse_epochs_tr = 100;
p.ml_mlse_epochs_dd = 1;
p.ml_mlse_len_tr = [];
p.ml_mlse_order = 11;
p.ml_mlse_traceback_depth = 256;
p.ml_mlse_L = 1;
p.ml_mlse_delta = 4;
p.ml_mlse_adaptive_mu = false;
end
function p = applyUserParameters(p, userParameters)
if isempty(userParameters)
return
end
paramNames = fieldnames(userParameters);
for paramIdx = 1:numel(paramNames)
paramName = paramNames{paramIdx};
if ~isfield(p, paramName)
warning("dsp_400g_recipe:UnknownUserParameter", ...
"Ignoring unknown user parameter '%s'.", paramName);
continue
end
p.(paramName) = userParameters.(paramName);
end
end
function [Scpe_sig, Symbols, Tx_bits] = alignDspInputs(Scpe_sig, Symbols, Tx_bits, sps)
nSymbols = min(length(Symbols), floor(length(Scpe_sig) / sps));
if nSymbols <= 0
error("dsp_400g_recipe:EmptyAlignedSignal", ...
"No overlapping samples remain after preprocessing and synchronization.");
end
Scpe_sig.signal = real(Scpe_sig.signal(1:sps*nSymbols));
Symbols.signal = Symbols.signal(1:nSymbols,:);
% if isprop(Tx_bits, "signal")
% Tx_bits.signal = Tx_bits.signal(1:nSymbols,:); <- THIS IS WRONG!!
% end
end
function [ffe_results, equalized_signal] = runFfe(eq_ffe, description, ...
Scpe_sig, Symbols, Tx_bits, options)
[ffe_results, equalized_signal] = ffe(eq_ffe, options.M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", options.duob_mode, ...
"showAnalysis", options.debug_plots, ...
"postFFE", [], ...
"eth_style_symbol_mapping", 0);
ffe_results.metrics.print("description", resultDescription(description, options));
end
function dbtgt_results = runDuobinaryTarget(eq_dbtgt, mlse_db, ...
Scpe_sig, Symbols, Tx_bits, options, p)
if isempty(p.decoding_mode)
dbtgt_results = duobinary_target(eq_dbtgt, mlse_db, options.M, ...
Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", options.duob_mode, ...
"showAnalysis", options.debug_plots, ...
"postFFE", []);
else
dbtgt_results = duobinary_target(eq_dbtgt, mlse_db, options.M, ...
Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", options.duob_mode, ...
"showAnalysis", options.debug_plots, ...
"postFFE", [], ...
"decoding_mode", p.decoding_mode);
end
end
function mlse = buildMlse(M, duobMode, p, mode)
if p.use_viterbi
mlse = MLSE_viterbi("duobinary_output", 0, ...
"M", M, ...
"trellis_states", PAMmapper(M,0).levels);
return
end
if duobMode == db_mode.no_db && M == 6
trellisExclusion = true;
else
trellisExclusion = false;
end
switch string(mode)
case "db_target"
mlse = MLSE("DIR", [1,1], ...
"duobinary_output", 0, ...
"M", M, ...
"trellis_states", PAMmapper(M,0).levels, ...
"scale_mode", p.mlse_scale_mode, ...
"trellis_exclusion", trellisExclusion, ...
"trellis_state_mode", p.dbtgt_trellis_state_mode);
otherwise
mlse = MLSE("duobinary_output", 0, ...
"M", M, ...
"trellis_states", PAMmapper(M,0).levels, ...
"scale_mode", p.mlse_scale_mode, ...
"trellis_exclusion", trellisExclusion, ...
"trellis_state_mode", p.mlse_trellis_state_mode);
end
end
function config = collectRecipeConfig(storageName, eqObject, p, options)
config = struct();
config.storage_name = char(storageName);
config.recipe = "dsp_400g_recipe";
config.eq_class = class(eqObject);
config.run_id = options.dataTable.run_id;
config.user_parameters = options.userParameters;
config.parameters = p;
end
function description = resultDescription(prefix, options)
dt = options.dataTable;
description = sprintf('%s; run %d; PAM-%d; %.0f GBd; %.0f km', ...
prefix, dt.run_id, dt.pam_level, dt.symbolrate * 1e-9, dt.fiber_length);
end
function plotEqSignals(equalized_signal, Symbols, options, fignum, output_scale)
showLevelScatter(equalized_signal .* output_scale, Symbols, ...
"fsym", options.fsym, ...
"fignum", fignum + 1, ...
"normalize", true);
end