- demystifyied dataset - matched filter - timing sync (whole sig, not symbol based) - EQ is worse, I guess due to symbol timing recovery Pulsef: - can be used as matched filter now, the only thing I really changed was the sampling behavior - before it assumed that the input is always fsym, now it can be anything... fdac is output frequency for both methods
192 lines
6.8 KiB
Matlab
192 lines
6.8 KiB
Matlab
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
|
|
|
|
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');
|
|
|
|
% Figure 2: Post-FFE coefficients (if available)
|
|
if ~isempty(postFFE)
|
|
showEQcoefficients('n1', postFFE.e, "displayname", 'Coefficients', 'fignum', 104);
|
|
end
|
|
|
|
try
|
|
figure(339);
|
|
showEQfilter(eq_.e_tr, 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 |