duobinary cleanup (currently it is a mess)

This commit is contained in:
Silas Oettinghaus
2026-02-23 09:17:26 +01:00
parent c0a0a415a8
commit 18ccaf8c12
9 changed files with 364 additions and 251 deletions

View File

@@ -462,10 +462,19 @@ classdef PAMmapper
end
function [Signal_out] = quantize(obj,Signal_in)
function [Signal_out] = quantize(obj,Signal_in,options)
arguments
obj
Signal_in
options.custom_const = []
end
if isempty(options.custom_const)
constellation = obj.get_levels();
constellation = constellation ./ obj.scaling;
else
constellation = options.custom_const;
end
issignalclass = 0;
if isa(Signal_in,'Signal')

View File

@@ -7,11 +7,11 @@ classdef Metricstruct
eqParam_id (1,1) double {mustBeNumeric} = NaN
date_of_processing (1,1) datetime = datetime('now')
numBits (1,1) double {mustBeInteger, mustBeNonnegative} = 0
BER (1,1) double {mustBeNumeric, mustBeNonnegative} = 0
numBitErr (1,1) double {mustBeInteger, mustBeNonnegative} = 0
BER_precoded (1,1) double {mustBeNumeric, mustBeNonnegative} = 0
numBitErr_precoded (1,1) double {mustBeInteger, mustBeNonnegative} = 0
numBits (1,1) double = 0
BER (1,1) double = 0
numBitErr (1,1) double = 0
BER_precoded (1,1) double = 0
numBitErr_precoded (1,1) double = 0
SNR (1,1) double {mustBeNumeric} = NaN
SNR_level (:,1) double {mustBeNumeric} = []

8
Datatypes/db_decoder.m Normal file
View File

@@ -0,0 +1,8 @@
classdef db_decoder < int32
enumeration
sequencedetection (0) % use MLSE for decoding
memoryless (1) % use modulo
end
end

View File

@@ -11,8 +11,10 @@ arguments
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);
@@ -21,17 +23,23 @@ db_ref_constellation = unique(db_ref_sequence.signal);
if ~isempty(options.postFFE)
[eq_signal,eq_noise] = options.postFFE.process(eq_signal,db_ref_sequence);
end
mlse_.DIR = [1,1];
%
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);
pam_sig_sd = mlse_.process(eq_signal);
else
[mlse_sig_sd,LLR,GMI_MLSE] = mlse_.process(eq_signal,tx_symbols);
[pam_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(pam_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;
end
mlse_sig_hd = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).quantize(mlse_sig_sd);
% precoding to mitigate error propagation, most prominently used in
% combination with duobinary signaling to avoid catastrophic error
@@ -42,58 +50,62 @@ switch options.precode_mode
% TX Data is not precoded:
% A) Emulate diff precoding
mlse_sig_hd_precoded = Duobinary().encode(mlse_sig_hd,"M",M);
mlse_sig_hd_precoded = Duobinary().decode(mlse_sig_hd_precoded,"M",M);
if options.decoding_mode == db_decoder.sequencedetection
pam_sig_hd_precoded = Duobinary().encode(pam_sig_hd,"M",M);
pam_sig_hd_precoded = Duobinary().decode(pam_sig_hd_precoded,"M",M);
else
pam_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);
rx_bits_mlse = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(pam_sig_hd_precoded);
[bits_db,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(mlse_sig_hd);
if options.decoding_mode == db_decoder.sequencedetection
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);
else
ber_db = NaN;
errors_db = NaN;
end
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!
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 = 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.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(mlse_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');
if options.decoding_mode == db_decoder.sequencedetection
pam_sig_hd_decoded = Duobinary().encode(pam_sig_hd,"M",M);
pam_sig_hd_decoded = Duobinary().decode(pam_sig_hd_decoded,"M",M);
else
pam_sig_hd_decoded = pam_sig_hd;
end
% M = numel(unique(tx_symbols.signal));
rx_bits = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(mlse_sig_hd);
rx_bits_mlse_decoded = PAMmapper(M,0,"eth_style",options.eth_style_symbol_mapping).demap(pam_sig_hd_decoded);
[bits_db,errors_db_diff_precoded,ber_db_diff_precoded,a] = calc_ber(rx_bits_mlse_decoded.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
burst_db_precoded = count_error_bursts(a, 40);
[bits_db,errors_db,ber_db,errorIndice_db] = calc_ber(rx_bits.signal,tx_bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1);
% B) Omit the Coding by comparing with demapped TX symbol sequence
if options.decoding_mode == db_decoder.sequencedetection
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);
else
ber_db = NaN;
errors_db = NaN;
end
end
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;
@@ -101,6 +113,12 @@ 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 = NaN;
air_mlse = NaN;
end
db_results = struct();
db_results.metrics = Metricstruct;
@@ -116,7 +134,7 @@ 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;
db_results.metrics.Alpha = NaN;
% Create DB results structure
db_results.config = Equalizerstruct();

View File

@@ -4,11 +4,11 @@ if 1
uloops = struct;
uloops.precomp = [1];
uloops.bitrate = [300].*1e9; %[300,330,360,390,420,450,480] [224,336,360,390,420,448] for MPI
uloops.bitrate = [360].*1e9; %[300,330,360,390,420,450,480] [224,336,360,390,420,448] for MPI
% uloops.laser_wavelength = [1293,1297.5,1302,1306.5,1310,1313.4,1318,1322.7,1327.4];
uloops.laser_wavelength = [1293];
uloops.laser_wavelength = [1290];
uloops.M = [4];
uloops.link_length = [0:2:10]; % 1,2,3,5,6,8,10
uloops.link_length = [0]; % 1,2,3,5,6,8,10
uloops.alpha = [0];
wh = DataStorage(uloops);
@@ -19,21 +19,23 @@ if 1
end
%%
figure
hold on
for alpha = uloops.alpha
a=wh.getStoValue('ber',1, [300].*1e9 , 1293, 4, uloops.link_length,alpha);
ffe = cellfun(@(x) x.ffe_results.metrics.BER, a);
plot(uloops.link_length,ffe,'DisplayName',sprintf('Alpha: %d',alpha),'LineStyle','-','HandleVisibility','on');
end
set(gca, 'YScale', 'log');
ylim([5e-5 0.4]);
yline([3.8e-3, 2e-2],'HandleVisibility','off');
legend
beautifyBERplot()
ylabel('BER');
% figure
% hold on
%
% a=wh.getStoValue('ber',1, [360].*1e9 , 1290, 4, uloops.link_length,alpha);
% ffe = cellfun(@(x) x.ffe_results.metrics.BER, a);
% plot(uloops.link_length,ffe,'DisplayName',sprintf('FFE'),'LineStyle','-','HandleVisibility','on');
% dbt = cellfun(@(x) x.dbt_results.metrics.BER, a);
% plot(uloops.link_length,dbt,'DisplayName',sprintf('DBt'),'LineStyle','-','HandleVisibility','on');
%
%
% set(gca, 'YScale', 'log');
% ylim([5e-5 0.4]);
% yline([3.8e-3, 2e-2],'HandleVisibility','off');
% legend
% beautifyBERplot()
% ylabel('BER');
%

View File

@@ -3,66 +3,59 @@ function [output] = imdd_model(varargin)
simulation_mode = 1;
%%% Change folder
curFolder = pwd;
funcFolder=fileparts(mfilename('fullpath'));
if ~isempty(funcFolder)
cd(funcFolder);
end
% curFolder = pwd;
% funcFolder=fileparts(mfilename('fullpath'));
% if ~isempty(funcFolder)
% cd(funcFolder);
% end
%%% Run parameters
% TX
M = 4;
fsym = 180e9;
apply_pulsef = 1;
fdac = 256e9;
fadc = 256e9;
% --- TX Architecture ---
M = 4; % PAM order
fsym = 180e9; % Symbol rate
apply_pulsef = 1; % Pulse shaping flag
fdac = 256e9; % DAC sampling rate
fadc = 256e9; % ADC sampling rate
random_key = 1;
rcalpha = 0.05; % Roll-off factor
kover = 16; % Oversampling factor
duob_mode = db_mode.db_precoded;
rcalpha = 0.05;
kover = 16;
% --- TX Optics (EML / Laser) ---
vbias_rel = 0.5;
u_pi = 3;
vbias = -vbias_rel * u_pi;
laser_wavelength = 1293;
laser_linewidth = 0;
tx_bw_nyquist = 0.8;
% Channel
% --- Channel ---
link_length = 1;
alpha = 0;
% RX
rop = -8;
% --- RX & Equalization ---
rop = -2;
rx_bw_nyquist = 0.8;
len_tr = 4096 * 2;
% VNLE / FFE / DFE Orders
vnle_order1 = 50;
vnle_order2 = 7;
vnle_order3 = 7;
vnle_order = [vnle_order1, vnle_order2, vnle_order3];
dfe_order = [0 0 0];
dfe_order = [0, 0, 0];
dfe_ = sum(dfe_order) > 0;
pf_ncoeffs = 1;
alpha = 0;
len_tr = 4096*2;
% Equalizer Step Sizes
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_ffe = [mu_ffe1, mu_ffe3, mu_ffe3];
mu_dfe = 0.0004;
dfe_ = sum(dfe_order)>0;
duob_mode = db_mode.no_db;
mu_dc = 0.005;
%%% change specific parameter if given in varargin
% Parse optional input arguments
@@ -109,12 +102,12 @@ Pform = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rc","pulselength",1
'duobinary_mode',duob_mode,...
"mrds_code",0,"mrds_blocklength",512).process();
Digi_sig.spectrum("displayname",'Digi Spectrum','fignum',10,'normalizeTo0dB',1);
% Digi_sig.spectrum("displayname",'Digi Spectrum','fignum',10,'normalizeTo0dB',1);
%%%%% 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',100,'normalizeTo0dB',0);
El_sig.spectrum("displayname",'Digi Spectrum','fignum',100,'normalizeTo0dB',1);
% El_sig = El_sig.setPower(0,"dBm");
%%%%% Low-pass el. components %%%%%%
@@ -132,7 +125,7 @@ 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.spectrum("displayname",'Opt Spectrum','fignum',10,'normalizeTo0dB',1);
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);
@@ -150,7 +143,6 @@ Rx_sig = Filter('filtdegree',4,"f_cutoff",rx_bwl,"fs",fdac*kover,"filterType",fi
% %%%%%% Low-pass Scope %%%%%%
Lp_scpe = Filter('filtdegree',4,"f_cutoff",110e9,"fs",fadc,"filterType",filtertypes.butterworth,"active",true);
% Rx_sig.spectrum("displayname",'Analog Rx Spectrum','fignum',100,'normalizeTo0dB',1);
%%%%%% Scope %%%%%%
Scpe_sig = Scope("fsimu",fdac*kover,"fadc",fadc,...
@@ -165,15 +157,18 @@ 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,~] = Scpe_sig.tsynch("reference",Symbols,"fs_ref",fsym,"debug_plots",0);
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.spectrum("displayname",'Filtered Digital Spectrum','fignum',100,'normalizeTo0dB',1);
%%% EQUALIZING
if 0
% -------------------- FFE --------------------
ffe_order = [50, 0, 0];
eq_ffe = EQ("Ne",ffe_order,"Nb",[0,0,0], ...
@@ -186,7 +181,8 @@ output.ffe_results = ffe(eq_ffe,M,Scpe_sig,Symbols,Tx_bits, ...
"eth_style_symbol_mapping",0);
output.ffe_results.metrics.print
end
if 0
% -------------------- DFE --------------------
eq_dfe = EQ("Ne",ffe_order,"Nb",[2,0,0], ...
"training_length",len_tr,"training_loops",5,"dd_loops",5, ...
@@ -198,7 +194,8 @@ output.dfe_results = ffe(eq_dfe,M,Scpe_sig,Symbols,Tx_bits, ...
"eth_style_symbol_mapping",0);
output.dfe_results.metrics.print("description",'DFE');
end
if 0
% -------------------- VNLE + MLSE --------------------
pf_ncoeffs = 1;
@@ -213,18 +210,20 @@ 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', 0, "postFFE", [], "eth_style_symbol_mapping", 0);
output.mlse_results.metrics.print("description",'MLSE');
end
if 1
% -------------------- DB target --------------------
%% -------------------- 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);
"K",2,"DCmu",mu_dc,"DDmu",[mu_ffe mu_dfe],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",1,"DB_aim",0);
output.dbt_results = duobinary_target(eq_,mlse_db_, M, Scpe_sig, Symbols, Tx_bits, ...
"precode_mode", duob_mode, 'showAnalysis', 0, "postFFE", []);
"precode_mode", duob_mode, 'showAnalysis', 1, "postFFE", [],"decoding_mode","memoryless");
output.dbt_results.metrics.print("description",'Duobinary');
end
disp('- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ')
fprintf('\n')

View File

@@ -4,7 +4,7 @@ freqresp = ChannelFreqResp("Nacq",1024,"Navg",64,"Ncp",70,"f_ref",256e9);
%
Digi_sig = freqresp.buildOFDM();
Digi_sig.spectrum("fignum",1112,"displayname",['maxamp:',num2str(maxamp)]);
Digi_sig.spectrum("fignum",1112);
Digi_sig = Filter('filtdegree',3,"f_cutoff",70e9,"fs",256e9,"filterType",filtertypes.butterworth,"active",true).process(Digi_sig);

View File

@@ -1,7 +1,7 @@
M = 6;
apply_precode = 1;
M = 4;
apply_precode = 0;
bitpattern = [];
s = RandStream('twister','Seed',1);
@@ -51,6 +51,8 @@ disp(['BER: ',sprintf('%.1E',ber),' - - PAM-',num2str(M)]);
figure(200)
clf
hold on
start = 100;
burstwidth = 10;
idxs = start-10:start+burstwidth+10;
scatter(idxs,d.signal(idxs),'DisplayName',['Orig Signal'],'Marker','o');
scatter(idxs,d_burst.signal(idxs),'DisplayName',['Error Signal'],'Marker','x');

View File

@@ -0,0 +1,75 @@
% einstellungen
M = 4;
apply_precode_at_tx = 0;
emulate_precode = 1;
% daten erzeugen
bitpattern = [];
s = RandStream('twister','Seed',1);
for i = 1:log2(M)
N = 2^(17-1); %length of prbs
bitpattern(:,i) = randi(s,[0 1], N, 1);
end
if M == 6
bitpattern = reshape(bitpattern',[],1);
bitpattern = bitpattern(1:end-mod(length(bitpattern),5));
end
tx_bits = Informationsignal(bitpattern);
tx_symbols = PAMmapper(M,0).map(tx_bits);
if apply_precode_at_tx
tx_symbols = Duobinary().precode(tx_symbols);
if emulate_precode
% ansatz 1B) Omit precode (einfach precoded empfangen, nichts weiter machen und normal prozessieren - möglich weil konstellation sich nicht ändert)
rx_symbols = tx_symbols;
bits_rx = PAMmapper(M,0).demap(rx_symbols);
% beachten, dass man die tx_bits anpassen muss, da man ja mit precoded symbolen vergleicht
tx_bits = PAMmapper(M,0).demap(tx_symbols);
else
% ansatz 1A) Precode normal:
% 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(tx_symbols);
% Entschiedene codierte Symbole decodieren: d_dec(n) = d_DB(n) mod4
rx_symbols = Duobinary().decode(symbols_db);
bits_rx = PAMmapper(M,0).demap(rx_symbols);
end
[~,~,ber,~] = calc_ber(tx_bits.signal,bits_rx.signal,"skip_front",0,"skip_end",0,"returnErrorLocation",1);
disp(['BER: ',sprintf('%.1E',ber)]);
assert(ber == 0)
else
if emulate_precode
% emulate precode:
% 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(tx_symbols); % das wäre der FFE mit db target
% Entschiedene codierte Symbole decodieren: d_dec(n) = d_DB(n) mod4
rx_symbols = Duobinary().decode(symbols_db); % modulo
bits_rx = PAMmapper(M,0).demap(rx_symbols); % demappen
% ref symbole precoden, auch hier muss man wieder etwas fummeln wegen der emulation:
% encode + decode == remove precoding (mathematisch die gleiche operation)
tx_symbols_ref = Duobinary().encode(tx_symbols);
tx_symbols_ref = Duobinary().decode(tx_symbols_ref);
tx_bits = PAMmapper(M,0).demap(tx_symbols_ref);
else
% normal detection without any precode stuff
symbols_db = Duobinary().encode(tx_symbols);
rx_symbols = tx_symbols;
bits_rx = PAMmapper(M,0).demap(rx_symbols);
end
[~,~,ber,~] = calc_ber(tx_bits.signal,bits_rx.signal,"skip_front",1,"skip_end",1,"returnErrorLocation",1);
disp(['BER: ',sprintf('%.1E',ber)]);
assert(ber == 0)
end