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_vnle, 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; ml_mlse_db_results.metrics.print; 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 = 0; p.run_vnle = 0; p.run_dfe = 0; p.run_vnle_mlse = 1; p.run_dbtgt = 0; p.run_ml_mlse = 0; % non-encoded and precoded branches p.run_ml_mlse_db = 0; % db_encoded: ML-based MLSE p.run_mlse_db = 1; % 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 = 150; 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