Add partial response symbol mapping and IM/DD system example

- Implemented a new MATLAB script for partial response symbol mapping using Duobinary precoding.
- Added functions for printing constellation bit mapping and partial response state tables.
- Created a minimal example for an IM/DD system, including signal generation, modulation, and equalization processes.
- Integrated various components such as pulse shaping, optical modulation, and receiver processing.
- Included detailed configurations for parameters like bias, link length, and filter settings.
This commit is contained in:
Silas Oettinghaus
2026-04-24 09:04:44 +02:00
parent 952178341e
commit 910486f536
7 changed files with 382 additions and 193 deletions

View File

@@ -6,12 +6,12 @@ classdef Partialresponse
end end
methods methods
function obj = Partialresponse(options) function obj = Partialresponse(varargin)
arguments parser = inputParser;
options.order (1,1) double = 1 parser.addParameter("order", 1);
end parser.parse(varargin{:});
obj.order = options.order; obj.order = parser.Results.order;
end end
function signal = precode(obj, signal, options) function signal = precode(obj, signal, options)

View File

@@ -1,23 +1,21 @@
for M = [2 4 8] for M = [2 4 8]
bits = Signalgenerator("form", signalform.prms,"M", M,"order", 15).process(); bits = Signalgenerator("form", signalform.prms,"M", M,"order", 16).process();
mapper = PAMmapper(M,0); mapper = PAMmapper(M,0);
symbols = mapper.map(bits); symbols = mapper.map(bits);
symbols_pre = Duobinary().precode(symbols); db = Duobinary();
pr1 = Partialresponse();
symbols_db = Duobinary().encode(symbols_pre);
symbols_rx = Duobinary().decode(symbols_db);
symbols_pre = db.precode(symbols);
symbols_db = db.encode(symbols_pre);
symbols_rx = db.decode(symbols_db);
bits_rx = PAMmapper(M,0).demap(symbols_rx); bits_rx = PAMmapper(M,0).demap(symbols_rx);
[~,~,ber,~] = calc_ber(bits.signal,bits_rx.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1); [~,~,ber,~] = calc_ber(bits.signal,bits_rx.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1);
disp(['Class BER: ',sprintf('%.1E',ber),' - PAM-',num2str(M)]); disp(['Class BER: ',sprintf('%.1E',ber),' - PAM-',num2str(M)]);
symbols_pre_pr = Partialresponse("order",1).precode(symbols,"M",M); symbols_pre_pr = pr1.precode(symbols,"M",M);
symbols_db_pr = Partialresponse("order",1).encode(symbols_pre_pr,"M",M); symbols_db_pr = pr1.encode(symbols_pre_pr,"M",M);
symbols_rx_pr = Partialresponse("order",1).decode(symbols_db_pr,"M",M); symbols_rx_pr = pr1.decode(symbols_db_pr,"M",M);
bits_rx_pr = mapper.demap(symbols_rx_pr); bits_rx_pr = mapper.demap(symbols_rx_pr);
[~,~,ber_pr,~] = calc_ber(bits.signal,bits_rx_pr.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1); [~,~,ber_pr,~] = calc_ber(bits.signal,bits_rx_pr.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1);
disp(['Partialresponse BER: ',sprintf('%.1E',ber_pr),' - PAM-',num2str(M)]); disp(['Partialresponse BER: ',sprintf('%.1E',ber_pr),' - PAM-',num2str(M)]);
@@ -37,4 +35,43 @@ for M = [2 4 8]
disp(['Decode match: ',num2str(dec_match), ... disp(['Decode match: ',num2str(dec_match), ...
' | max abs diff: ',sprintf('%.3g',dec_maxdiff)]); ' | max abs diff: ',sprintf('%.3g',dec_maxdiff)]);
disp('---'); disp('---');
f = figure("Name",sprintf("PAM-%d Partial-Response Histograms",M));
t = tiledlayout(f,1,4,"TileSpacing","compact","Padding","compact");
title(t,sprintf("PAM-%d Symbol Histograms",M));
nexttile;
plotDiscreteHistogram(symbols.signal,sprintf("PAM-%d",M));
nexttile;
plotDiscreteHistogram(symbols_db_pr.signal,"Duobinary 1st Order");
nexttile;
plotDiscreteHistogram(getEncodedSymbols(symbols,M,2),"Duobinary 2nd Order");
nexttile;
plotDiscreteHistogram(getEncodedSymbols(symbols,M,3),"Duobinary 3rd Order");
end
function y = getEncodedSymbols(symbols, M, order)
pr = Partialresponse("order",order);
y = pr.encode(pr.precode(symbols,"M",M),"M",M).signal;
end
function plotDiscreteHistogram(x, plot_title)
levels = unique(x(:)).';
counts = zeros(size(levels));
for idx = 1:numel(levels)
counts(idx) = sum(abs(x - levels(idx)) < 1e-12);
end
bar(levels, counts, 0.9, "FaceColor", [0.2 0.45 0.75], "EdgeColor", "none");
grid on;
box on;
xlabel("Level");
ylabel("Count");
title(plot_title);
xticks(levels);
end end

View File

@@ -1,122 +0,0 @@
useprbs = 1;
M = 2;
randkey = 1;
datarate = 448e9;
fsym = round(datarate / log2(M)) ;
Tx_bits = Signalgenerator( ...
"form", signalform.prms, ...
"M", M, ...
"order", 15).process();
Symbols_tx = PAMmapper(M,0,"eth_style",0).map(Tx_bits);
Symbols_tx.fs = fsym;
precode = db_mode.db_precoded;
%%% precode
switch precode
case db_mode.db_precoded
Symbols_tx = Duobinary().precode(Symbols_tx);
case db_mode.db_encoded
Symbols_tx = Duobinary().precode(Symbols_tx);
Symbols_tx = Duobinary().encode(Symbols_tx);
case db_mode.no_db
end
for n = 10
% Start from the transmitted symbol sequence for this impairment case.
Symbols_rx = Symbols_tx;
% Insert n deterministic symbol errors near the beginning of the sequence.
% Each replacement is chosen from another symbol position with a different
% amplitude, so this produces actual symbol decisions errors.
pos = 1;
if n~=0
for pos = 1:n
po = randi(100);
a = Symbols_rx.signal(100+pos) == Symbols_tx.signal(100+po);
while a == 1
po = po+1;
po = randi(100);
a = Symbols_rx.signal(100+pos) == Symbols_tx.signal(100+po);
end
Symbols_rx.signal(100+pos) = Symbols_tx.signal(100+po);
end
end
% Keep track of the positions where the artificial symbol errors were
% inserted before duobinary encoding/decoding is applied.
error_positions = ~(Symbols_rx.signal == Symbols_tx.signal);
error_positions = find(error_positions==1);
% Receiver-side duobinary processing depends on what was already done at
% the transmitter side:
% - db_precoded: the sequence is precoded only, so emulate the channel PR
% encoder first and then apply memoryless duobinary decoding.
% - db_encoded: the sequence already contains duobinary levels, so only
% the memoryless duobinary decoder is applied.
switch precode
case db_mode.db_precoded
Symbols_rx = Duobinary().encode(Symbols_rx);
Symbols_rx = Duobinary().decode(Symbols_rx);
case db_mode.db_encoded
Symbols_rx = Duobinary().decode(Symbols_rx);
end
% Convert the recovered PAM symbols back to bits with the framework mapper.
Rx_bits = PAMmapper(M,0).demap(Symbols_rx);
%%%%% Check BER of Bit Sequence %%%%%
% Skip a few edge samples because the first/last symbols can include
% state/transient effects from the duobinary recursion.
[~,error_num(n+1),ber,error_pos] = calc_ber(Tx_bits.signal,Rx_bits.signal,"skip_front",10,"skip_end",10,"returnErrorLocation",1);
% disp(['BER: ',sprintf('%.1E',ber),sprintf(' - Num. Err: %.1d',error_num(n+1)-2),' - - PAM-',num2str(M)]);
fprintf('n: %d - Num. Err: %.1d \n',n,error_num(n+1));
end
% Plot bit-level and symbol-level comparisons for a quick visual sanity check.
figure(3);
clf
%sgtitle(['BER: ',num2str(ber),' // Error is at position: ',num2str(error_pos),''])
% Compare the first few transmitted and received bits.
subplot(2,2,1)
hold on
title('First Bits')
stairs(Tx_bits.signal(100:150,1),'LineStyle','-','LineWidth',2,'DisplayName','Tx Bits');
stairs(Rx_bits.signal(100:150,1),'LineWidth',2,'DisplayName','Rx Bits','LineStyle',':')
legend
% Compare the last few transmitted and received bits.
subplot(2,2,2)
title('Last Bits')
hold on
stairs(Tx_bits.signal(end-50:end,1),'LineStyle','-','LineWidth',2,'DisplayName','Tx Bits');
stairs(Rx_bits.signal(end-50:end,1),'LineWidth',2,'DisplayName','Rx Bits','LineStyle',':')
legend
% Compare the first few symbol decisions after the duobinary processing chain.
subplot(2,2,3)
hold on
title('First Symbols Compare')
stairs(Symbols_tx.signal(1:100,1),'LineWidth',2,'DisplayName','Tx Symbols','LineStyle','-')
stairs(Symbols_rx.signal(1:100,1),'LineStyle',':','LineWidth',2,'DisplayName','Rx Symbols');
legend
% Compare the last few symbol decisions after the duobinary processing chain.
subplot(2,2,4)
hold on
title('Last Symbols Compare')
stairs(Symbols_tx.signal(end-50:end,1),'LineWidth',2,'DisplayName','Tx Symbols','LineStyle','-')
stairs(Symbols_rx.signal(end-50:end,1),'LineStyle',':','LineWidth',2,'DisplayName','Rx Symbols');
legend

View File

@@ -0,0 +1,147 @@
M = 4;
useUnitRmsScaling = false;
bits = Signalgenerator("form", signalform.prms,"M", M,"order", 16).process();
mapper = PAMmapper(M,0);
symbols = mapper.map(bits);
[constellation, bitmap] = mapper.getConstellationBitMapping();
printConstellationBitMapping(M, constellation, bitmap);
printPartialResponseStateTable(M, bitmap, "Duobinary", 1, useUnitRmsScaling);
db = Duobinary();
pr1 = Partialresponse();
symbols_pre = db.precode(symbols);
symbols_db = db.encode(symbols_pre);
symbols_rx = db.decode(symbols_db);
bits_rx = PAMmapper(M,0).demap(symbols_rx);
[~,~,ber,~] = calc_ber(bits.signal,bits_rx.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1);
disp(['Class BER: ',sprintf('%.1E',ber),' - PAM-',num2str(M)]);
function printConstellationBitMapping(M, constellation, bitmap)
fprintf("\n=== PAM-%d Bit Mapping ===\n", M);
bitLabels = string(cellstr(char(bitmap + '0')));
if size(constellation, 2) == 1
mappingTable = table(constellation(:), bitLabels, ...
'VariableNames', {'Symbol', 'Bits'});
else
mappingTable = table(constellation(:,1), constellation(:,2), bitLabels, ...
'VariableNames', {'Symbol_1', 'Symbol_2', 'Bits'});
end
disp(mappingTable);
fprintf("Number of constellation points: %d\n\n", size(constellation, 1));
end
function printPartialResponseStateTable(M, bitmap, label, order, useUnitRmsScaling)
if useUnitRmsScaling
scalingLabel = "unit-RMS scaling";
encodedScaling = getEncodedScaling(M, order);
else
scalingLabel = "raw encoder levels";
encodedScaling = 1;
end
fprintf("=== %s Effective State Table (order = %d, %s) ===\n", label, order, scalingLabel);
mapper = PAMmapper(M, 0);
h = arrayfun(@(k) nchoosek(order, k), 0:order);
center = ((M - 1) * sum(h)) / 2;
if useUnitRmsScaling
pamScaling = mapper.scaling;
else
pamScaling = 1;
end
numStates = M^order;
numInputs = M;
prevStateLabels = strings(numStates * numInputs, 1);
bitLabels = strings(numStates * numInputs, 1);
aAmp = zeros(numStates * numInputs, 1);
uIdx = zeros(numStates * numInputs, 1);
uAmp = zeros(numStates * numInputs, 1);
yAmp = zeros(numStates * numInputs, 1);
nextStateLabels = strings(numStates * numInputs, 1);
row = 1;
for stateIdx = 0:(numStates - 1)
state = indexToState(stateIdx, M, order);
for a = 0:(numInputs - 1)
u = mod(a - sum(h(2:end) .* state), M);
y = (sum(h .* [u, state]) - center) / encodedScaling;
prevStateLabels(row) = formatState(state);
bitLabels(row) = bitsFromIndex(bitmap, a + 1);
aAmp(row) = indexToPamAmplitude(a, pamScaling, M);
uIdx(row) = u;
uAmp(row) = indexToPamAmplitude(u, pamScaling, M);
yAmp(row) = y;
nextStateLabels(row) = formatState([u, state(1:end-1)]);
row = row + 1;
end
end
stateTable = table(prevStateLabels, bitLabels, aAmp, uIdx, uAmp, yAmp, nextStateLabels, ...
'VariableNames', {'PrevState', 'Bits', 'InputSymbol', 'PrecodedIndex', 'PrecodedSymbol', 'EncodedLevel', 'NextState'});
disp(stateTable);
fprintf("States: %d, transitions: %d\n\n", numStates, height(stateTable));
end
function state = indexToState(stateIdx, M, order)
state = zeros(1, order);
tmp = stateIdx;
for k = 1:order
state(k) = mod(tmp, M);
tmp = floor(tmp / M);
end
end
function label = formatState(state)
if isempty(state)
label = "[]";
else
label = "[" + join(string(state), " ") + "]";
end
end
function bitLabel = bitsFromIndex(bitmap, idx)
bitLabel = string(char(bitmap(idx, :) + '0'));
end
function amplitude = indexToPamAmplitude(a, scaling, M)
amplitude = (2 * a - (M - 1)) / scaling;
end
function scaling = getEncodedScaling(M, order)
h = arrayfun(@(k) nchoosek(order, k), 0:order);
numCombinations = M^(order + 1);
y = zeros(numCombinations, 1);
for n = 0:(numCombinations - 1)
state = zeros(1, order + 1);
tmp = n;
for k = 1:numel(state)
state(k) = mod(tmp, M);
tmp = floor(tmp / M);
end
y(n + 1) = sum(h .* state);
end
center = ((M - 1) * sum(h)) / 2;
scaling = sqrt(mean((y - center).^2));
end

View File

@@ -1,55 +0,0 @@
M = 8;
apply_precode_at_tx = 1;
bits = Signalgenerator( ...
"form", signalform.prms, ...
"M", M, ...
"order", 15).process();
symbols = PAMmapper(M,0).map(bits);
if apply_precode_at_tx
symbols_tx = Duobinary().precode(symbols);
else
symbols_tx = symbols;
end
disp(['Tx Sequenz: -- RMS:',sprintf('%.1f',rms(symbols_tx.signal)),' - - Levels -',num2str(numel(unique(symbols_tx.signal)))]);
unique(symbols_tx.signal)
disp('- - - - - - - - - -');
show2Dconstellation(symbols_tx,symbols_tx,"displayname",'VNLE Out','fignum',2241);
if apply_precode_at_tx
% Entschiedene Symbole codieren: d_DB(n) = d(n) + d(n-1) (im Fall von PAM4 7 level [0 1 2 3 4 5 6])
symbols_db = Duobinary().encode(symbols_tx);
disp(['DB encoded -- RMS:',sprintf('%.1f',rms(symbols_db.signal)),' - - Levels -',num2str(numel(unique(symbols_db.signal)))]);
unique(symbols_db.signal)
disp('- - - - - - - - - -');
% Entschiedene codierte Symbole decodieren: d_dec(n) = d_DB(n) mod4
symbols_rx = Duobinary().decode(symbols_db);
else
symbols_db = Duobinary().encode(symbols_tx);
symbols_rx = Duobinary().decode(symbols_db);
end
% Vergleichen von b(n) und d_dec(n)
bits_rx = PAMmapper(M,0).demap(symbols_rx);
disp(['Wieder normal -- RMS:',sprintf('%.1f',rms(symbols_rx.signal)),' - - Levels -',num2str(numel(unique(symbols_rx.signal)))]);
unique(symbols_rx.signal)
disp('- - - - - - - - - -');
[~,~,ber,~] = calc_ber(bits.signal,bits_rx.signal,"skip_front",10,"skip_end",10,"returnErrorLocation",1);
disp(['BER: ',sprintf('%.1E',ber),' - - PAM-',num2str(M)]);
figure()
subplot(1,2,1)
histogram(symbols_tx.signal,100,'Normalization','count')
subplot(1,2,2)
histogram(symbols_db.signal,100,'Normalization','count')

View File

@@ -0,0 +1,182 @@
% minimal example IM/DD
M = 4;
fsym = 112e9;
apply_pulsef = 1;
fdac = 256e9;
fadc = 256e9;
random_key = 1;
rcalpha = 0.05;
kover = 16;
duob_mode = db_mode.no_db;
vbias_rel = 0.5;
u_pi = 3;
vbias = -vbias_rel*u_pi;
laser_wavelength = 1293;
laser_linewidth = 0;
tx_bw_nyquist = 0.8;
% Channel
link_length = 1;
% RX
rop = -9;
rx_bw_nyquist = 0.8;
vnle_order1 = 50;
vnle_order2 = 7;
vnle_order3 = 7;
vnle_order=[vnle_order1,vnle_order2,vnle_order3];
dfe_order = [0 0 0];
pf_ncoeffs = 1;
alpha = 0;
len_tr = 4096*2;
mu_ffe1 = 0.0001;
mu_ffe2 = 0.0008;
mu_ffe3 = 0.001;
mu_dc = 0.005;
% mu_dc = 0;
mu_ffe = [mu_ffe1 mu_ffe3 mu_ffe3];
mu_dfe = 0.0004;
Pform = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rrc","pulselength",16,"alpha",rcalpha);
[Digi_sig,Symbols,Tx_bits] = PAMsource(...
"fsym",fsym,"M",M,"order",18,"useprbs",0,...
"fs_out",fdac,...
"applyclipping",0,"clipfactor",1.5,...
"applypulseform",apply_pulsef,"pulseformer",Pform,...
"randkey",random_key,...
'duobinary_mode',duob_mode,...
"mrds_code",0,"mrds_blocklength",512).process();
%%%%% AWG
% El_sig = M8199A("kover",kover).process(Digi_sig);
El_sig = AWG("fdac",fdac,"f_cutoff",fsym,"lpf_active",0,"kover",kover,"bit_resolution",12,"upsampling_method","samplehold","precomp_sinc_rolloff",1).process(Digi_sig);
El_sig.spectrum("displayname",'Digi Spectrum','fignum',1,'normalizeTo0dB',1);
xlim([0,130]);
ylim([-30,5]);
% El_sig = El_sig.setPower(0,"dBm");
%%%%% Electrical Driver Amplifier %%%%%%
% El_sig = Amplifier("amp_mode","ideal_no_noise","gain_mode","gain","amplification_db",3).process(El_sig);
El_sig = El_sig.normalize("mode","oneone");
scaling = 0.6*(u_pi/2-abs(vbias-u_pi/2));
El_sig = El_sig .* scaling;
%%%%% MODULATE E/O CONVERSION %%%%%%
[Opt_sig] = EML("mode",eml_mode.im_cosinus,"power",3,"fsimu",El_sig.fs,"lambda",laser_wavelength,"bias",vbias,"u_pi",u_pi,"linewidth",laser_linewidth,"randomkey",random_key+1,"alpha",alpha).process(El_sig);
Opt_sig.spectrum("displayname",'Opt Spectrum','fignum',10,'normalizeTo0dB',1);
% Opt_sig.eye(fsym,M,"displayname",'eye adter modulator','fignum',2026);
Opt_sig = Fiber("fsimu",Opt_sig.fs,"fiber_length",link_length,"alpha",0.3,"D",0,"lambda0",1310,"gamma",0,"Dslope",0.07).process(Opt_sig);
%%%%%% ROP %%%%%%
Rx_sig = Amplifier("amp_mode","ideal_no_noise","gain_mode","output_power","amplification_db",rop).process(Opt_sig);
%%%%%% PD Square Law %%%%%%
Rx_sig = Photodiode("fsimu",fdac*kover,"dark_current",2e-08,"responsivity",1,"temperature",20,"nep",1.8e-11).process(Rx_sig);
%%%%%% Low-pass RX (PD, El. Connectors and Scope %%%%%%
rx_bwl = 80e9;
Rx_sig = Filter('filtdegree',4,"f_cutoff",rx_bwl,"fs",fdac*kover,"filterType",filtertypes.butterworth,"active",true).process(Rx_sig);
% %%%%%% Low-pass Scope %%%%%%
Lp_scpe = Filter('filtdegree',4,"f_cutoff",110e9,"fs",fadc,"filterType",filtertypes.butterworth,"active",true);
%%%%%% Scope %%%%%%
Scpe_sig = Scope("fsimu",fdac*kover,"fadc",fadc,...
"delay",0,"fixed_delay",0,"filtertype",filtertypes.butterworth,...
"samplingdelay",0,"rand_samplingdelay",0,"freq_offset",0,"samp_jitter",0,...
"adcresolution",8,"quantbuffer",0.1,'block_dc',1,'lpf_active',1,'H_lpf',Lp_scpe).process(Rx_sig);
%%
% 1) matched filter
% pulse is symmetric, hence we can use pulsef firectly as matched filter.
% It feels off (bit I think correct) that the fsym is now the output freq.!!
% -> output 2 sps to omit timing recovery!?
Pform = Pulseformer("fsym",fsym,"fdac",2*fsym,"pulse","rrc","pulselength",16,"alpha",rcalpha,"matched",1);
Scpe_sig = Pform.process(Scpe_sig);
Scpe_sig.spectrum("displayname",'Signal after matched filter','fignum',1,'normalizeTo0dB',1);
%
% %%
% %%%%%% Sample to 2x fsym %%%%%%
% Scpe_sig = Scpe_sig.resample("fs_out",2*fsym);
% Scpe_sig.signal = Scpe_sig.signal(1:2*length(Symbols));
%%
%%%%%% Sync Rx signal with reference %%%%%%
[Scpe_sig,~] = Scpe_sig.tsynch("reference",Symbols,"fs_ref",fsym,"debug_plots",1);
Scpe_sig.spectrum("displayname",'Opt Spectrum','fignum',11,'normalizeTo0dB',1);
% Scpe_sig = Filter('filtdegree',4,"f_cutoff",Symbols.fs.*0.5,"fs",Scpe_sig.fs,"filterType",filtertypes.gaussian,"active",true).process(Scpe_sig);
Scpe_sig = Scpe_sig - mean(Scpe_sig.signal);
Scpe_sig.signal = Scpe_sig.signal(1:2*length(Symbols));
%%
% -------------------- FFE --------------------
ffe_order = [50, 0, 0];
eq_ = EQ("Ne",ffe_order,"Nb",[2,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("epochs_tr",4,"epochs_dd",5,"len_tr",4096,"mu_dd",0.01,"mu_tr",0.01,"order",50,"sps",2,"decide",0, "adaption",adaption_method.nlms,"dd_mode",1);
eq_ = FFE_DFE("epochs_tr",5,"epochs_dd",5,"len_tr",512,"ffe_mu_dd",1e-4,"dfe_mu_dd",5e-4,"ffe_mu_tr",0,"dfe_mu_tr",0,"ffe_order",99,"dfe_order",99,"sps",2,"decide",0);
output.ffe_results = ffe(eq_,M,Scpe_sig,Symbols,Tx_bits, ...
"precode_mode",duob_mode,'showAnalysis',1,"postFFE",[], ...
"eth_style_symbol_mapping",0);
output.ffe_results.metrics.print("description",'DFE');
%%
% -------------------- VNLE + MLSE --------------------
pf_ncoeffs = 1;
ffe_order3 = [50, 5, 5];
eq_v = EQ("Ne",ffe_order3,"Nb",dfe_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",1);
pf_ = Postfilter("ncoeff",pf_ncoeffs,"useBurg",1);
mlse_ = MLSE("duobinary_output",0,'M',M,'trellis_states',PAMmapper(M,0).levels);
[output.vnle_results, output.mlse_results] = vnle_postfilter_mlse(eq_v, pf_, mlse_, M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", duob_mode, 'showAnalysis', 1, "postFFE", [], "eth_style_symbol_mapping", 0);
output.mlse_results.metrics.print("description",'MLSE');
%%
% -------------------- DB target --------------------
mlse_db_ = MLSE("DIR",[1,1],"duobinary_output",0,"M",M,'trellis_states',PAMmapper(M,0).levels);
ffe_order = [50, 5, 5];
eq_ = EQ("Ne",ffe_order,"Nb",dfe_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",1);
output.dbt_results = duobinary_target(eq_,mlse_db_, M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", duob_mode, 'showAnalysis', 0, "postFFE", []);
output.dbt_results.metrics.print("description",'Duobinary');