Ordnerstruktur
This commit is contained in:
@@ -344,14 +344,16 @@ classdef Signal
|
|||||||
obj
|
obj
|
||||||
options.fignum = 2025
|
options.fignum = 2025
|
||||||
options.displayname = "";
|
options.displayname = "";
|
||||||
options.color = [];
|
options.color = [];
|
||||||
options.linestyle = '-';
|
options.linestyle = '-';
|
||||||
options.normalizeToNyquist = 0;
|
options.normalizeToNyquist = 0;
|
||||||
options.addDCoffset = 0;
|
options.normalizeToSamplingRate = 0;
|
||||||
options.normalizeToDC = 0;
|
options.addDCoffset = 0;
|
||||||
options.normalizeTo0dB = 0;
|
options.normalizeToDC = 0;
|
||||||
options.max_num_lines = []; % Leave empty or omit to disable line rotation
|
options.normalizeTo0dB = 0;
|
||||||
options.fft_length = [];
|
options.show_onesided = false;
|
||||||
|
options.max_num_lines = []; % Leave empty or omit to disable line rotation
|
||||||
|
options.fft_length = [];
|
||||||
% --- NEW options ---
|
% --- NEW options ---
|
||||||
options.useWavelengthAxis (1,1) logical = false % plot x-axis in wavelength
|
options.useWavelengthAxis (1,1) logical = false % plot x-axis in wavelength
|
||||||
options.lambda0_nm (1,1) double = 1310 % center wavelength [nm]
|
options.lambda0_nm (1,1) double = 1310 % center wavelength [nm]
|
||||||
@@ -362,18 +364,21 @@ classdef Signal
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
if options.normalizeToNyquist == 0
|
useSamplingRateAxis = options.normalizeToSamplingRate ~= 0;
|
||||||
[p_lin,f_Hz] = pwelch(obj.signal, hanning(options.fft_length), ...
|
useRadPerSampleAxis = options.normalizeToNyquist ~= 0 && ~useSamplingRateAxis;
|
||||||
options.fft_length/2, options.fft_length, ...
|
|
||||||
obj.fs, "centered", "power", "mean");
|
if ~useRadPerSampleAxis && ~useSamplingRateAxis
|
||||||
f_GHz = f_Hz*1e-9; % keep frequency vector for frequency axis
|
[p_lin,f_Hz] = pwelch(obj.signal, hanning(options.fft_length), ...
|
||||||
else
|
options.fft_length/2, options.fft_length, ...
|
||||||
[p_lin,f_rad] = pwelch(obj.signal, hanning(options.fft_length), ...
|
obj.fs, "centered", "power", "mean");
|
||||||
options.fft_length/2, options.fft_length, ...
|
f_GHz = f_Hz*1e-9; % keep frequency vector for frequency axis
|
||||||
"centered", "power", "mean");
|
else
|
||||||
% In normalized mode, pwelch returns rad/sample centered on 0.
|
[p_lin,f_rad] = pwelch(obj.signal, hanning(options.fft_length), ...
|
||||||
% We'll keep f_rad for the x-axis in that mode.
|
options.fft_length/2, options.fft_length, ...
|
||||||
end
|
"centered", "power", "mean");
|
||||||
|
% In normalized modes, pwelch returns rad/sample centered on 0.
|
||||||
|
% Divide by 2*pi for the f/fs axis where Nyquist is 0.5.
|
||||||
|
end
|
||||||
|
|
||||||
% p_lin = movmean(p_lin,4);
|
% p_lin = movmean(p_lin,4);
|
||||||
|
|
||||||
@@ -387,45 +392,60 @@ classdef Signal
|
|||||||
end
|
end
|
||||||
|
|
||||||
% --- If requested, build wavelength axis from frequency offset ---
|
% --- If requested, build wavelength axis from frequency offset ---
|
||||||
if options.useWavelengthAxis && options.normalizeToNyquist == 0
|
if options.useWavelengthAxis && ~useRadPerSampleAxis && ~useSamplingRateAxis
|
||||||
c = physconst('LightSpeed'); % [m/s]
|
c = physconst('LightSpeed'); % [m/s]
|
||||||
lambda0_m = options.lambda0_nm*1e-9; % center wavelength [m]
|
lambda0_m = options.lambda0_nm*1e-9; % center wavelength [m]
|
||||||
f_c = c / lambda0_m; % carrier frequency [Hz]
|
f_c = c / lambda0_m; % carrier frequency [Hz]
|
||||||
|
|
||||||
% exact mapping
|
% exact mapping
|
||||||
f_abs = f_c + f_Hz; % absolute frequency [Hz]
|
f_abs = f_c + f_Hz; % absolute frequency [Hz]
|
||||||
lambda_m = c ./ f_abs; % wavelength [m]
|
lambda_m = c ./ f_abs; % wavelength [m]
|
||||||
lambda_nm = lambda_m * 1e9; % wavelength [nm]
|
lambda_nm = lambda_m * 1e9; % wavelength [nm]
|
||||||
|
|
||||||
% assign axis
|
% assign axis
|
||||||
x_vec = lambda_nm(:);
|
x_vec = lambda_nm(:);
|
||||||
x_label = "Wavelength [nm]";
|
x_label = "Wavelength [nm]";
|
||||||
|
dc_axis = f_Hz;
|
||||||
% Sort to ensure axis is ascending
|
|
||||||
[x_vec, sortIdx] = sort(x_vec, 'ascend');
|
% Sort to ensure axis is ascending
|
||||||
p_dbm = p_dbm(sortIdx, :);
|
[x_vec, sortIdx] = sort(x_vec, 'ascend');
|
||||||
else
|
p_dbm = p_dbm(sortIdx, :);
|
||||||
% Frequency or normalized axes
|
dc_axis = dc_axis(sortIdx);
|
||||||
if options.normalizeToNyquist == 0
|
else
|
||||||
x_vec = f_GHz;
|
% Frequency or normalized axes
|
||||||
x_label = "Frequency in GHz";
|
if ~useRadPerSampleAxis && ~useSamplingRateAxis
|
||||||
else
|
x_vec = f_GHz;
|
||||||
x_vec = f_rad; % normalized frequency in rad/sample
|
x_label = "Frequency in GHz";
|
||||||
x_label = "Normalized Frequency";
|
dc_axis = f_GHz;
|
||||||
end
|
elseif useSamplingRateAxis
|
||||||
end
|
x_vec = f_rad ./ (2*pi);
|
||||||
|
x_label = "Normalized Frequency f/fs";
|
||||||
figure(options.fignum);
|
dc_axis = x_vec;
|
||||||
ax = gca;
|
else
|
||||||
|
x_vec = f_rad; % normalized frequency in rad/sample
|
||||||
|
x_label = "Normalized Frequency [rad/sample]";
|
||||||
|
dc_axis = x_vec;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.show_onesided
|
||||||
|
keep_idx = dc_axis >= 0;
|
||||||
|
x_vec = x_vec(keep_idx);
|
||||||
|
dc_axis = dc_axis(keep_idx);
|
||||||
|
p_dbm = p_dbm(keep_idx, :);
|
||||||
|
end
|
||||||
|
|
||||||
|
figure(options.fignum);
|
||||||
|
ax = gca;
|
||||||
hold on
|
hold on
|
||||||
|
|
||||||
p_dbm = p_dbm+options.addDCoffset;
|
p_dbm = p_dbm+options.addDCoffset;
|
||||||
|
|
||||||
if options.normalizeToDC
|
if options.normalizeToDC
|
||||||
[~,min_idx]=min(abs(f_GHz));
|
[~,min_idx]=min(abs(dc_axis));
|
||||||
pow_at_dc = p_dbm(min_idx);
|
pow_at_dc = p_dbm(min_idx);
|
||||||
p_dbm = p_dbm-pow_at_dc;
|
p_dbm = p_dbm-pow_at_dc;
|
||||||
end
|
end
|
||||||
% p_dbm = movmean(p_dbm,10);
|
% p_dbm = movmean(p_dbm,10);
|
||||||
|
|
||||||
for s = 1:min(size(p_dbm))
|
for s = 1:min(size(p_dbm))
|
||||||
@@ -448,17 +468,27 @@ classdef Signal
|
|||||||
% Axis labels and limits
|
% Axis labels and limits
|
||||||
xlabel(x_label);
|
xlabel(x_label);
|
||||||
|
|
||||||
if options.useWavelengthAxis && options.normalizeToNyquist == 0
|
if options.useWavelengthAxis && ~useRadPerSampleAxis && ~useSamplingRateAxis
|
||||||
xlim([min(x_vec) max(x_vec)]);
|
xlim([min(x_vec) max(x_vec)]);
|
||||||
else
|
else
|
||||||
if options.normalizeToNyquist == 0
|
if ~useRadPerSampleAxis && ~useSamplingRateAxis
|
||||||
% Keep your existing freq handling (you can fine-tune as needed)
|
% Keep your existing freq handling (you can fine-tune as needed)
|
||||||
% xlim([-128 128]); % example for 256 GSa/s if desired
|
% xlim([-128 128]); % example for 256 GSa/s if desired
|
||||||
xlim([min(x_vec) max(x_vec)]);
|
xlim([min(x_vec) max(x_vec)]);
|
||||||
else
|
elseif useSamplingRateAxis
|
||||||
xlim([-pi, pi]);
|
if options.show_onesided
|
||||||
end
|
xlim([0, 0.5]);
|
||||||
end
|
else
|
||||||
|
xlim([-0.5, 0.5]);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if options.show_onesided
|
||||||
|
xlim([0, pi]);
|
||||||
|
else
|
||||||
|
xlim([-pi, pi]);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
ylabel(ylab);
|
ylabel(ylab);
|
||||||
|
|
||||||
|
|||||||
@@ -21,21 +21,22 @@ classdef Signalgenerator
|
|||||||
% "dimension", 2, ...
|
% "dimension", 2, ...
|
||||||
% "randkey", 3).process();
|
% "randkey", 3).process();
|
||||||
%
|
%
|
||||||
% % PRMS bit matrix for PAM-4 mapping. If length is omitted, the
|
% % PRMS bit matrix for PAM-4 mapping. M selects the mapper-ready bit
|
||||||
% % output length is derived from order, as in the old PAMsource.
|
% % shape. If length is omitted, the output length is derived from
|
||||||
|
% % order, as in the old PAMsource.
|
||||||
% bits = Signalgenerator( ...
|
% bits = Signalgenerator( ...
|
||||||
% "form", signalform.prms, ...
|
% "form", signalform.prms, ...
|
||||||
% "dimension", 2, ...
|
% "M", 4, ...
|
||||||
% "order", 7).process();
|
% "order", 7).process();
|
||||||
% symbols = PAMmapper(4, 0).map(bits);
|
% symbols = PAMmapper(4, 0).map(bits);
|
||||||
%
|
%
|
||||||
% % PRMS for PAM-6: 5 bits map to 2 PAM-6 symbols
|
% % PRMS for PAM-6: 5 bits map to 2 PAM-6 symbols. M = 6 returns
|
||||||
|
% % the 1-D bit vector expected by PAMmapper.
|
||||||
% bits = Signalgenerator( ...
|
% bits = Signalgenerator( ...
|
||||||
% "form", signalform.prms, ...
|
% "form", signalform.prms, ...
|
||||||
% "dimension", 5, ...
|
% "M", 6, ...
|
||||||
% "order", 7).process();
|
% "order", 7).process();
|
||||||
% bitVector = reshape(bits.signal.', [], 1);
|
% symbols = PAMmapper(6, 0).map(bits);
|
||||||
% symbols = PAMmapper(6, 0).map(bitVector);
|
|
||||||
%
|
%
|
||||||
% % Map, demap, and check BER
|
% % Map, demap, and check BER
|
||||||
% mapper = PAMmapper(8, 0);
|
% mapper = PAMmapper(8, 0);
|
||||||
@@ -54,6 +55,7 @@ classdef Signalgenerator
|
|||||||
fs
|
fs
|
||||||
fsig
|
fsig
|
||||||
dimension
|
dimension
|
||||||
|
M
|
||||||
order
|
order
|
||||||
randkey
|
randkey
|
||||||
skip
|
skip
|
||||||
@@ -72,6 +74,7 @@ classdef Signalgenerator
|
|||||||
options.fs double = 1000 %Hz sampling
|
options.fs double = 1000 %Hz sampling
|
||||||
options.fsig double = 50 % Hz fundamental frex e.g. of the sine or sawtooth
|
options.fsig double = 50 % Hz fundamental frex e.g. of the sine or sawtooth
|
||||||
options.dimension double = 1
|
options.dimension double = 1
|
||||||
|
options.M double = []
|
||||||
options.order double = 7
|
options.order double = 7
|
||||||
options.randkey double = 0
|
options.randkey double = 0
|
||||||
options.skip double = 0
|
options.skip double = 0
|
||||||
@@ -86,6 +89,11 @@ classdef Signalgenerator
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if ~isempty(obj.M)
|
||||||
|
obj.validate_pam_format();
|
||||||
|
obj.dimension = obj.mapper_bit_dimension();
|
||||||
|
end
|
||||||
|
|
||||||
if isempty(obj.length)
|
if isempty(obj.length)
|
||||||
obj.length = obj.default_length();
|
obj.length = obj.default_length();
|
||||||
end
|
end
|
||||||
@@ -136,7 +144,8 @@ classdef Signalgenerator
|
|||||||
case signalform.prms
|
case signalform.prms
|
||||||
signal = obj.build_prms_data();
|
signal = obj.build_prms_data();
|
||||||
end
|
end
|
||||||
|
|
||||||
|
signal = obj.format_for_mapper(signal);
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -169,12 +178,15 @@ classdef Signalgenerator
|
|||||||
% PAM-8: dimension = 3
|
% PAM-8: dimension = 3
|
||||||
%
|
%
|
||||||
% PAM-6 is special in this codebase: the mapper consumes 5 bits and
|
% PAM-6 is special in this codebase: the mapper consumes 5 bits and
|
||||||
% maps them to two PAM-6 symbols. Use dimension = 5, then flatten
|
% maps them to two PAM-6 symbols. Prefer M = 6 to generate the 1-D
|
||||||
% the output before calling PAMmapper(6, ...).map(...).
|
% vector expected by PAMmapper(6, ...).map(...). The legacy
|
||||||
|
% dimension = 5 path still returns the unflattened bit matrix.
|
||||||
%
|
%
|
||||||
% Relevant Signalgenerator options:
|
% Relevant Signalgenerator options:
|
||||||
% length Number of generated PRMS bit groups / rows.
|
% length Number of generated PRMS bit groups / rows.
|
||||||
% dimension Number of parallel bit streams per row.
|
% dimension Number of parallel bit streams per row.
|
||||||
|
% M Optional PAM format. When set, dimension is selected
|
||||||
|
% automatically and PAM-6 output is mapper-ready.
|
||||||
% order User-facing sequence order, matching the old
|
% order User-facing sequence order, matching the old
|
||||||
% PAMsource behavior. If length is omitted, it sets the
|
% PAMsource behavior. If length is omitted, it sets the
|
||||||
% output length. Internally it is converted to the PRMS
|
% output length. Internally it is converted to the PRMS
|
||||||
@@ -252,10 +264,17 @@ classdef Signalgenerator
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function validate_pam_format(obj)
|
||||||
|
if ~ismember(obj.M, [2 4 6 8 16])
|
||||||
|
error("Signalgenerator:InvalidPAMFormat", ...
|
||||||
|
"M must be one of 2, 4, 6, 8, or 16.");
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function length = default_length(obj)
|
function length = default_length(obj)
|
||||||
switch obj.form
|
switch obj.form
|
||||||
case {signalform.random, signalform.prms}
|
case {signalform.random, signalform.prms}
|
||||||
if obj.dimension == 5
|
if obj.is_pam6_shape()
|
||||||
length = 2^max(0, obj.order - 2);
|
length = 2^max(0, obj.order - 2);
|
||||||
else
|
else
|
||||||
length = 2^max(0, obj.order - 1);
|
length = 2^max(0, obj.order - 1);
|
||||||
@@ -266,7 +285,9 @@ classdef Signalgenerator
|
|||||||
end
|
end
|
||||||
|
|
||||||
function prms_order = internal_prms_order(obj)
|
function prms_order = internal_prms_order(obj)
|
||||||
if obj.dimension == 5
|
if ~isempty(obj.M)
|
||||||
|
bits_per_symbol = log2(obj.M);
|
||||||
|
elseif obj.dimension == 5
|
||||||
bits_per_symbol = log2(6);
|
bits_per_symbol = log2(6);
|
||||||
else
|
else
|
||||||
bits_per_symbol = obj.dimension;
|
bits_per_symbol = obj.dimension;
|
||||||
@@ -275,6 +296,25 @@ classdef Signalgenerator
|
|||||||
prms_order = max(1, floor(obj.order / bits_per_symbol));
|
prms_order = max(1, floor(obj.order / bits_per_symbol));
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function dimension = mapper_bit_dimension(obj)
|
||||||
|
if obj.M == 6
|
||||||
|
dimension = 5;
|
||||||
|
else
|
||||||
|
dimension = log2(obj.M);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function tf = is_pam6_shape(obj)
|
||||||
|
tf = (~isempty(obj.M) && obj.M == 6) || (isempty(obj.M) && obj.dimension == 5);
|
||||||
|
end
|
||||||
|
|
||||||
|
function signal = format_for_mapper(obj, signal)
|
||||||
|
if ~isempty(obj.M) && obj.M == 6
|
||||||
|
signal = reshape(signal.', [], 1);
|
||||||
|
signal = signal(1:end - mod(numel(signal), 5));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ arguments
|
|||||||
ref_symbols
|
ref_symbols
|
||||||
options.fignum (1,1) double = NaN % Default to NaN if not provided
|
options.fignum (1,1) double = NaN % Default to NaN if not provided
|
||||||
options.displayname (1,:) char = '' % Default to an empty string if not provided
|
options.displayname (1,:) char = '' % Default to an empty string if not provided
|
||||||
|
options.max_burst_length (1,1) double = 20
|
||||||
end
|
end
|
||||||
|
|
||||||
% Determine the figure number to use or create a new figure
|
% Determine the figure number to use or create a new figure
|
||||||
@@ -18,30 +19,17 @@ end
|
|||||||
eq_signal = PAMmapper(M,0).quantize(eq_signal);
|
eq_signal = PAMmapper(M,0).quantize(eq_signal);
|
||||||
end
|
end
|
||||||
|
|
||||||
diff_indices = eq_signal.signal == ref_symbols.signal;
|
errpos = find(eq_signal.signal(:) ~= ref_symbols.signal(:));
|
||||||
|
burst_len = 1:options.max_burst_length;
|
||||||
|
burst_count = count_error_bursts(errpos, options.max_burst_length);
|
||||||
|
|
||||||
|
bar(burst_len, burst_count, "DisplayName", options.displayname);
|
||||||
% Identify the start of new sequences (when the difference is not 1)
|
grid on;
|
||||||
sequence_starts = [1, find(diff_indices ~= 1) + 1]; % Include the first index
|
xlabel("Error burst length");
|
||||||
sequence_ends = [sequence_starts(2:end) - 1, length(errpos)]; % Calculate end indices
|
ylabel("Count");
|
||||||
|
title("Error burst count");
|
||||||
% Initialize burst count and print bursts longer than 10
|
if ~isempty(options.displayname)
|
||||||
burst_len = 1:10;
|
legend("show");
|
||||||
burst_count = zeros(length(burst_len),1);
|
|
||||||
for t = 1:numel(burst_len)
|
|
||||||
for i = 1:length(sequence_starts)
|
|
||||||
% Extract current sequence
|
|
||||||
current_burst = errpos(sequence_starts(i):sequence_ends(i));
|
|
||||||
% Check if the sequence length matches criterion
|
|
||||||
if length(current_burst) == burst_len(t)
|
|
||||||
burst_count(t) = burst_count(t) + 1;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
burst_symbols = burst_count .* burst_len';
|
end
|
||||||
burst_rate = burst_symbols ;%./ length(rx_signal);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -3,10 +3,14 @@ function burst_count = count_error_bursts(err_pos, max_burst_length)
|
|||||||
% max_burst_length: Maximum length for which bursts are counted (e.g., 10)
|
% max_burst_length: Maximum length for which bursts are counted (e.g., 10)
|
||||||
|
|
||||||
% Sort the error positions to ensure they're in increasing order
|
% Sort the error positions to ensure they're in increasing order
|
||||||
err_pos = sort(err_pos);
|
err_pos = sort(err_pos(:).');
|
||||||
|
|
||||||
% Initialize burst_count array to hold the counts for each burst length
|
% Initialize burst_count array to hold the counts for each burst length
|
||||||
burst_count = zeros(1, max_burst_length);
|
burst_count = zeros(1, max_burst_length);
|
||||||
|
|
||||||
|
if isempty(err_pos)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
% Find the differences between consecutive error positions
|
% Find the differences between consecutive error positions
|
||||||
diffs = diff(err_pos);
|
diffs = diff(err_pos);
|
||||||
|
|||||||
@@ -87,6 +87,53 @@ classdef Signal_test < IMDDTestCase
|
|||||||
testCase.verifyEqual(height(sig.logbook), 1);
|
testCase.verifyEqual(height(sig.logbook), 1);
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function spectrumCanUseSamplingRateNormalizedFrequencyAxis(testCase)
|
||||||
|
fig = figure("Visible", "off");
|
||||||
|
cleanup = onCleanup(@() close(fig));
|
||||||
|
fignum = fig.Number;
|
||||||
|
|
||||||
|
sig = Signal(randn(4096, 1), "fs", 8e9);
|
||||||
|
|
||||||
|
sig.spectrum( ...
|
||||||
|
"fignum", fignum, ...
|
||||||
|
"normalizeToSamplingRate", 1, ...
|
||||||
|
"normalizeTo0dB", 1, ...
|
||||||
|
"fft_length", 512);
|
||||||
|
|
||||||
|
ax = findobj(fig, "Type", "Axes");
|
||||||
|
lineObj = findobj(ax, "Type", "Line");
|
||||||
|
|
||||||
|
testCase.verifyEqual(ax.XLim, [-0.5 0.5], "AbsTol", 1e-12);
|
||||||
|
testCase.verifyEqual(string(ax.XLabel.String), "Normalized Frequency f/fs");
|
||||||
|
testCase.verifyGreaterThanOrEqual(min(lineObj.XData), -0.5);
|
||||||
|
testCase.verifyLessThanOrEqual(max(lineObj.XData), 0.5);
|
||||||
|
testCase.verifyLessThanOrEqual(abs(min(lineObj.XData) + 0.5), 1/512);
|
||||||
|
testCase.verifyLessThanOrEqual(abs(max(lineObj.XData) - 0.5), 1/512);
|
||||||
|
end
|
||||||
|
|
||||||
|
function spectrumCanShowOneSidedSamplingRateNormalizedAxis(testCase)
|
||||||
|
fig = figure("Visible", "off");
|
||||||
|
cleanup = onCleanup(@() close(fig));
|
||||||
|
fignum = fig.Number;
|
||||||
|
|
||||||
|
sig = Signal(randn(4096, 1), "fs", 8e9);
|
||||||
|
|
||||||
|
sig.spectrum( ...
|
||||||
|
"fignum", fignum, ...
|
||||||
|
"normalizeToSamplingRate", 1, ...
|
||||||
|
"show_onesided", true, ...
|
||||||
|
"normalizeTo0dB", 1, ...
|
||||||
|
"fft_length", 512);
|
||||||
|
|
||||||
|
ax = findobj(fig, "Type", "Axes");
|
||||||
|
lineObj = findobj(ax, "Type", "Line");
|
||||||
|
|
||||||
|
testCase.verifyEqual(ax.XLim, [0 0.5], "AbsTol", 1e-12);
|
||||||
|
testCase.verifyGreaterThanOrEqual(min(lineObj.XData), 0);
|
||||||
|
testCase.verifyLessThanOrEqual(max(lineObj.XData), 0.5);
|
||||||
|
testCase.verifyLessThanOrEqual(abs(max(lineObj.XData) - 0.5), 1/512);
|
||||||
|
end
|
||||||
|
|
||||||
function castingRoundTripPreservesSignalThroughAllDomains(testCase)
|
function castingRoundTripPreservesSignalThroughAllDomains(testCase)
|
||||||
infoIn = Informationsignal([0; 1; 1; 0]);
|
infoIn = Informationsignal([0; 1; 1; 0]);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ classdef Signalgenerator_test < IMDDTestCase
|
|||||||
testCase.verifyEqual(gen.fs, 1000);
|
testCase.verifyEqual(gen.fs, 1000);
|
||||||
testCase.verifyEqual(gen.fsig, 50);
|
testCase.verifyEqual(gen.fsig, 50);
|
||||||
testCase.verifyEqual(gen.dimension, 1);
|
testCase.verifyEqual(gen.dimension, 1);
|
||||||
|
testCase.verifyEmpty(gen.M);
|
||||||
testCase.verifyEqual(gen.order, 7);
|
testCase.verifyEqual(gen.order, 7);
|
||||||
testCase.verifyEqual(gen.randkey, 0);
|
testCase.verifyEqual(gen.randkey, 0);
|
||||||
testCase.verifyEqual(gen.skip, 0);
|
testCase.verifyEqual(gen.skip, 0);
|
||||||
@@ -102,6 +103,34 @@ classdef Signalgenerator_test < IMDDTestCase
|
|||||||
testCase.verifyEqual(pam6Gen.length, 2^(7 - 2));
|
testCase.verifyEqual(pam6Gen.length, 2^(7 - 2));
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function pamFormatSelectsMapperReadyDimension(testCase)
|
||||||
|
pam4Gen = Signalgenerator( ...
|
||||||
|
"form", signalform.prms, ...
|
||||||
|
"M", 4, ...
|
||||||
|
"order", 7);
|
||||||
|
pam6Gen = Signalgenerator( ...
|
||||||
|
"form", signalform.prms, ...
|
||||||
|
"M", 6, ...
|
||||||
|
"order", 7);
|
||||||
|
|
||||||
|
testCase.verifyEqual(pam4Gen.dimension, 2);
|
||||||
|
testCase.verifyEqual(pam4Gen.length, 2^(7 - 1));
|
||||||
|
testCase.verifyEqual(pam6Gen.dimension, 5);
|
||||||
|
testCase.verifyEqual(pam6Gen.length, 2^(7 - 2));
|
||||||
|
end
|
||||||
|
|
||||||
|
function pam6FormatReturnsMapperReadyVector(testCase)
|
||||||
|
gen = Signalgenerator( ...
|
||||||
|
"form", signalform.prms, ...
|
||||||
|
"M", 6, ...
|
||||||
|
"order", 7);
|
||||||
|
|
||||||
|
signal = gen.build_signal();
|
||||||
|
|
||||||
|
testCase.verifySize(signal, [5 * 2^(7 - 2), 1]);
|
||||||
|
testCase.verifyEqual(mod(numel(signal), 5), 0);
|
||||||
|
end
|
||||||
|
|
||||||
function processWrapsPrmsSignalInInformationsignal(testCase)
|
function processWrapsPrmsSignalInInformationsignal(testCase)
|
||||||
gen = Signalgenerator( ...
|
gen = Signalgenerator( ...
|
||||||
"form", signalform.prms, ...
|
"form", signalform.prms, ...
|
||||||
|
|||||||
18
Theory/Mapping_Coding/partial_response/duobinary_back2back.m
Normal file
18
Theory/Mapping_Coding/partial_response/duobinary_back2back.m
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
|
||||||
|
for M = [2 4 8]
|
||||||
|
bits = Signalgenerator("form", signalform.prms,"M", M,"order", 15).process();
|
||||||
|
|
||||||
|
symbols = PAMmapper(M,0).map(bits);
|
||||||
|
|
||||||
|
symbols_pre = Duobinary().precode(symbols);
|
||||||
|
|
||||||
|
symbols_db = Duobinary().encode(symbols_pre);
|
||||||
|
|
||||||
|
symbols_rx = Duobinary().decode(symbols_db);
|
||||||
|
|
||||||
|
% Vergleichen von b(n) und d_dec(n)
|
||||||
|
bits_rx = PAMmapper(M,0).demap(symbols_rx);
|
||||||
|
[~,~,ber,~] = calc_ber(bits.signal,bits_rx.signal,"skip_front",10,"skip_end",10,"returnErrorLocation",1);
|
||||||
|
disp(['BER: ',sprintf('%.1E',ber),' - - PAM-',num2str(M)]);
|
||||||
|
end
|
||||||
@@ -9,8 +9,7 @@ Tx_bits = Signalgenerator( ...
|
|||||||
"M", M, ...
|
"M", M, ...
|
||||||
"order", 15).process();
|
"order", 15).process();
|
||||||
|
|
||||||
%%%%% Duobinary %%%%%%
|
|
||||||
close all
|
|
||||||
Symbols_tx = PAMmapper(M,0,"eth_style",0).map(Tx_bits);
|
Symbols_tx = PAMmapper(M,0,"eth_style",0).map(Tx_bits);
|
||||||
Symbols_tx.fs = fsym;
|
Symbols_tx.fs = fsym;
|
||||||
|
|
||||||
@@ -29,8 +28,12 @@ end
|
|||||||
|
|
||||||
for n = 10
|
for n = 10
|
||||||
|
|
||||||
|
% Start from the transmitted symbol sequence for this impairment case.
|
||||||
Symbols_rx = Symbols_tx;
|
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;
|
pos = 1;
|
||||||
if n~=0
|
if n~=0
|
||||||
for pos = 1:n
|
for pos = 1:n
|
||||||
@@ -45,9 +48,17 @@ for n = 10
|
|||||||
end
|
end
|
||||||
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 = ~(Symbols_rx.signal == Symbols_tx.signal);
|
||||||
error_positions = find(error_positions==1);
|
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
|
switch precode
|
||||||
|
|
||||||
case db_mode.db_precoded
|
case db_mode.db_precoded
|
||||||
@@ -58,9 +69,12 @@ for n = 10
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
% Convert the recovered PAM symbols back to bits with the framework mapper.
|
||||||
Rx_bits = PAMmapper(M,0).demap(Symbols_rx);
|
Rx_bits = PAMmapper(M,0).demap(Symbols_rx);
|
||||||
|
|
||||||
%%%%% Check BER of Bit Sequence %%%%%
|
%%%%% 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);
|
[~,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)]);
|
% disp(['BER: ',sprintf('%.1E',ber),sprintf(' - Num. Err: %.1d',error_num(n+1)-2),' - - PAM-',num2str(M)]);
|
||||||
@@ -70,9 +84,12 @@ end
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
% Plot bit-level and symbol-level comparisons for a quick visual sanity check.
|
||||||
figure(3);
|
figure(3);
|
||||||
clf
|
clf
|
||||||
%sgtitle(['BER: ',num2str(ber),' // Error is at position: ',num2str(error_pos),''])
|
%sgtitle(['BER: ',num2str(ber),' // Error is at position: ',num2str(error_pos),''])
|
||||||
|
|
||||||
|
% Compare the first few transmitted and received bits.
|
||||||
subplot(2,2,1)
|
subplot(2,2,1)
|
||||||
hold on
|
hold on
|
||||||
title('First Bits')
|
title('First Bits')
|
||||||
@@ -80,6 +97,7 @@ stairs(Tx_bits.signal(100:150,1),'LineStyle','-','LineWidth',2,'DisplayName','Tx
|
|||||||
stairs(Rx_bits.signal(100:150,1),'LineWidth',2,'DisplayName','Rx Bits','LineStyle',':')
|
stairs(Rx_bits.signal(100:150,1),'LineWidth',2,'DisplayName','Rx Bits','LineStyle',':')
|
||||||
legend
|
legend
|
||||||
|
|
||||||
|
% Compare the last few transmitted and received bits.
|
||||||
subplot(2,2,2)
|
subplot(2,2,2)
|
||||||
title('Last Bits')
|
title('Last Bits')
|
||||||
hold on
|
hold on
|
||||||
@@ -87,6 +105,7 @@ stairs(Tx_bits.signal(end-50:end,1),'LineStyle','-','LineWidth',2,'DisplayName',
|
|||||||
stairs(Rx_bits.signal(end-50:end,1),'LineWidth',2,'DisplayName','Rx Bits','LineStyle',':')
|
stairs(Rx_bits.signal(end-50:end,1),'LineWidth',2,'DisplayName','Rx Bits','LineStyle',':')
|
||||||
legend
|
legend
|
||||||
|
|
||||||
|
% Compare the first few symbol decisions after the duobinary processing chain.
|
||||||
subplot(2,2,3)
|
subplot(2,2,3)
|
||||||
hold on
|
hold on
|
||||||
title('First Symbols Compare')
|
title('First Symbols Compare')
|
||||||
@@ -94,9 +113,10 @@ stairs(Symbols_tx.signal(1:100,1),'LineWidth',2,'DisplayName','Tx Symbols','Line
|
|||||||
stairs(Symbols_rx.signal(1:100,1),'LineStyle',':','LineWidth',2,'DisplayName','Rx Symbols');
|
stairs(Symbols_rx.signal(1:100,1),'LineStyle',':','LineWidth',2,'DisplayName','Rx Symbols');
|
||||||
legend
|
legend
|
||||||
|
|
||||||
|
% Compare the last few symbol decisions after the duobinary processing chain.
|
||||||
subplot(2,2,4)
|
subplot(2,2,4)
|
||||||
hold on
|
hold on
|
||||||
title('Last Symbols Compare')
|
title('Last Symbols Compare')
|
||||||
stairs(Symbols_tx.signal(end-50:end,1),'LineWidth',2,'DisplayName','Tx Symbols','LineStyle','-')
|
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');
|
stairs(Symbols_rx.signal(end-50:end,1),'LineStyle',':','LineWidth',2,'DisplayName','Rx Symbols');
|
||||||
legend
|
legend
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
M = 6;
|
|
||||||
data = [1,2,3,4,5,6];
|
|
||||||
|
|
||||||
M = 6;
|
|
||||||
|
|
||||||
bitpattern = [];
|
|
||||||
s = RandStream('twister','Seed',1);
|
|
||||||
for i = 1:log2(M)
|
|
||||||
N = 2^(12-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
|
|
||||||
|
|
||||||
bits = Informationsignal(bitpattern);
|
|
||||||
|
|
||||||
symbols = PAMmapper(M,0).map(bits);
|
|
||||||
symbols_tx_prec = Duobinary().precode(symbols);
|
|
||||||
|
|
||||||
% all possible transitions (for now 36, including the "edges"
|
|
||||||
% of the QAM 32 constellation)
|
|
||||||
states = PAMmapper(6,0,"eth_style",0).levels;
|
|
||||||
pam6transitions = combvec(states,states)'; % pam6transitions =
|
|
||||||
% [-5 -5;
|
|
||||||
% -3 -5;
|
|
||||||
% -1 -5; ...
|
|
||||||
pam6transitions_serial = reshape(pam6transitions',[],1);
|
|
||||||
|
|
||||||
data = pam6transitions_serial;
|
|
||||||
data = round(data);
|
|
||||||
b = min(data);
|
|
||||||
data = data - b;
|
|
||||||
data = data ./ 2;
|
|
||||||
% THIS WAS USED!
|
|
||||||
bk = zeros(size(data));
|
|
||||||
for k = 2:numel(data)
|
|
||||||
bk(k) = mod(data(k)-bk(k-1),M);
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
%% State Analysis
|
|
||||||
x = bk;%symbols_tx_prec.signal;
|
|
||||||
levels = sort(unique(x)).'; % or provide known 1x6 level values
|
|
||||||
|
|
||||||
[~,ix] = min(abs(x - levels),[],2);
|
|
||||||
x = levels(ix); % snapped/quantized
|
|
||||||
|
|
||||||
%% TRANSITION COUNTS & PROBABILITIES
|
|
||||||
K = numel(levels);
|
|
||||||
% map to state indices 1..K
|
|
||||||
[tf, idx] = ismember(x, levels);
|
|
||||||
idx = idx(:);
|
|
||||||
from = idx(1:end-1);
|
|
||||||
to = idx(2:end);
|
|
||||||
from = idx(1:2:end);
|
|
||||||
to = idx(2:2:end);
|
|
||||||
|
|
||||||
% counts C(from,to)
|
|
||||||
C = accumarray([from,to], 1, [K K], @sum, 0);
|
|
||||||
% row-stochastic transition matrix P(to|from)
|
|
||||||
rowSums = sum(C,2);
|
|
||||||
P = C ./ max(rowSums,1);
|
|
||||||
|
|
||||||
%% 1) HEATMAP (which transitions are more probable?)
|
|
||||||
figure('Name','Transition Probabilities (to | from)');
|
|
||||||
h = heatmap(levels, levels, P, 'Colormap', parula, 'ColorbarVisible','on');
|
|
||||||
colormap(gca,[[1,1,1];flip(cbrewer2('Spectral',100))]);clim([0,ceil(max(P(:))*10)/10]);
|
|
||||||
h.XLabel = 'From state (level)';
|
|
||||||
h.YLabel = 'To state (level)';
|
|
||||||
h.Title = 'P(to | from)';
|
|
||||||
@@ -0,0 +1,835 @@
|
|||||||
|
% Generalized duobinary partial-response example for PAM-M.
|
||||||
|
% Symbol-domain implementation for F(D) = (1 + D)^n, M in {2, 4, 8}.
|
||||||
|
|
||||||
|
clear; clc;
|
||||||
|
|
||||||
|
repoRoot = fileparts(fileparts(fileparts(mfilename("fullpath"))));
|
||||||
|
addpath(genpath(repoRoot));
|
||||||
|
|
||||||
|
rng(7);
|
||||||
|
M_list = [2,4,8];
|
||||||
|
pr_orders = [1,2,3];
|
||||||
|
Nsym = 100000;
|
||||||
|
userOrder = 17;
|
||||||
|
emulate_precoder = 0;
|
||||||
|
burst_lengths = 1:50;
|
||||||
|
injected_error_count = 100;
|
||||||
|
plot_burst_analysis = 1;
|
||||||
|
burst_randkey = 140;
|
||||||
|
burst_min_gap = 100;
|
||||||
|
|
||||||
|
fprintf('Generalized duobinary PR burst-resilience demo\n');
|
||||||
|
fprintf('emulate_precoder = %d\n', emulate_precoder);
|
||||||
|
fprintf('Injected precoder-symbol errors per burst length = %d\n', injected_error_count);
|
||||||
|
fprintf('Burst randkey = %d, minimum gap = %d symbols\n', burst_randkey, burst_min_gap);
|
||||||
|
|
||||||
|
burst_results = struct();
|
||||||
|
burst_pattern = buildCombinedBurstPattern( ...
|
||||||
|
Nsym, burst_lengths, injected_error_count, ...
|
||||||
|
"randkey", burst_randkey, ...
|
||||||
|
"min_gap", burst_min_gap);
|
||||||
|
|
||||||
|
for pr_order = pr_orders
|
||||||
|
fprintf('\nF(D) = (1 + D)^%d\n', pr_order);
|
||||||
|
|
||||||
|
for M = M_list
|
||||||
|
[txBits, txSymbols, mapper, d_tx] = generatePamSymbols(M, Nsym, userOrder);
|
||||||
|
|
||||||
|
if emulate_precoder
|
||||||
|
[~, y_amp, pr_alphabet, y_idx, a_ref] = emulatePrecoder(d_tx, M, [], pr_order);
|
||||||
|
refBits = demapSymbolIndices(a_ref, txSymbols, mapper, M);
|
||||||
|
else
|
||||||
|
a_ref = d_tx;
|
||||||
|
refBits = txBits;
|
||||||
|
u = precode(a_ref, M, [], pr_order);
|
||||||
|
[y_amp, pr_alphabet, y_idx] = encode(u, M, [], pr_order);
|
||||||
|
end
|
||||||
|
|
||||||
|
a_hat0 = memorylessdecode(y_amp, M, [], pr_order);
|
||||||
|
rxBits0 = demapSymbolIndices(a_hat0, txSymbols, mapper, M);
|
||||||
|
[~, errors0, ber0] = calc_ber(rxBits0.signal, refBits.signal);
|
||||||
|
|
||||||
|
assert(isequal(a_hat0, a_ref), ...
|
||||||
|
'Noiseless reconstruction failed for M=%d, order=%d.', M, pr_order);
|
||||||
|
assert(errors0 == 0 && ber0 == 0, ...
|
||||||
|
'Noiseless BER check failed for M=%d, order=%d.', M, pr_order);
|
||||||
|
assert(numel(pr_alphabet) == (M-1)*2^pr_order + 1, ...
|
||||||
|
'Wrong PR alphabet size for M=%d, order=%d.', M, pr_order);
|
||||||
|
|
||||||
|
results = simulatePrecoderErrorBursts( ...
|
||||||
|
a_ref, M, pr_order, txSymbols, mapper, refBits, ...
|
||||||
|
burst_pattern);
|
||||||
|
|
||||||
|
fprintf('M=%d before encode/decode: BER = %.3g (%d/%d bits), symbol errors = %d, bursts = %d, max burst = %d\n', ...
|
||||||
|
M, results.input_ber, results.input_bit_errors, results.input_checked_bits, ...
|
||||||
|
results.input_symbol_errors, results.input_burst_total, results.input_max_burst_length);
|
||||||
|
fprintf('M=%d after encode/decode: BER = %.3g (%d/%d bits), symbol errors = %d, bursts = %d, max burst = %d\n', ...
|
||||||
|
M, results.ber, results.bit_errors, results.checked_bits, ...
|
||||||
|
results.symbol_errors, results.output_burst_total, results.output_max_burst_length);
|
||||||
|
printBurstCountReport(results, M, pr_order);
|
||||||
|
|
||||||
|
burst_results(pr_order, M).results = results;
|
||||||
|
|
||||||
|
if plot_burst_analysis
|
||||||
|
plotPrecoderBurstAnalysis(results, pr_order, M);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if plot_burst_analysis
|
||||||
|
plotBurstSummary(burst_results, pr_orders, M_list);
|
||||||
|
end
|
||||||
|
|
||||||
|
% runDuobinaryPartialResponseTests();
|
||||||
|
|
||||||
|
function pam_levels = getPamLevels(M)
|
||||||
|
%GETPAMLEVELS Symmetric PAM amplitudes for natural indices 0...(M-1).
|
||||||
|
validatePamOrder(M);
|
||||||
|
pam_levels = -(M-1):2:(M-1);
|
||||||
|
end
|
||||||
|
|
||||||
|
function [txBits, txSymbols, mapper, a] = generatePamSymbols(M, Nsym, userOrder)
|
||||||
|
%GENERATEPAMSYMBOLS Framework-based bit generation and PAM mapping.
|
||||||
|
validatePamOrder(M);
|
||||||
|
|
||||||
|
txBits = Signalgenerator( ...
|
||||||
|
"form", signalform.prms, ...
|
||||||
|
"M", M, ...
|
||||||
|
"length", Nsym, ...
|
||||||
|
"order", userOrder).process();
|
||||||
|
|
||||||
|
mapper = PAMmapper(M, 0);
|
||||||
|
txSymbols = mapper.map(txBits);
|
||||||
|
a = amplitudeToSymbolIndex(txSymbols.signal .* mapper.scaling, M);
|
||||||
|
end
|
||||||
|
|
||||||
|
function rxBits = demapSymbolIndices(a_hat, txSymbolsTemplate, mapper, M)
|
||||||
|
%DEMAPSYMBOLINDICES Convert decoded indices back to framework PAM symbols.
|
||||||
|
rxSymbols = txSymbolsTemplate;
|
||||||
|
rxSymbols.signal = symbolIndexToAmplitude(a_hat, M) ./ mapper.scaling;
|
||||||
|
rxBits = mapper.demap(rxSymbols);
|
||||||
|
end
|
||||||
|
|
||||||
|
function results = simulatePrecoderErrorBursts(a_ref, M, pr_order, txSymbols, mapper, refBits, burst_pattern)
|
||||||
|
%SIMULATEPRECODERERRORBURSTS Inject deterministic symbol errors before PR encoding.
|
||||||
|
u_ref = precode(a_ref, M, [], pr_order);
|
||||||
|
|
||||||
|
results = struct();
|
||||||
|
results.burst_lengths = burst_pattern.burst_lengths(:);
|
||||||
|
results.injected_error_count_per_length = burst_pattern.error_count_per_length;
|
||||||
|
results.injected_total_errors = numel(burst_pattern.err_pos);
|
||||||
|
results.injected_burst_count = count_error_bursts( ...
|
||||||
|
burst_pattern.err_pos, max(burst_pattern.burst_lengths));
|
||||||
|
|
||||||
|
[u_err, injected_err_pos] = injectSymbolErrorBursts(u_ref, M, burst_pattern);
|
||||||
|
u_ref_bits = demapSymbolIndices(u_ref, txSymbols, mapper, M);
|
||||||
|
inputBits = demapSymbolIndices(u_err, txSymbols, mapper, M);
|
||||||
|
[inputCheckedBits, inputBitErrors, inputBer] = calc_ber( ...
|
||||||
|
inputBits.signal, u_ref_bits.signal, "returnErrorLocation", 1);
|
||||||
|
input_err_pos = find(u_err(:) ~= u_ref(:));
|
||||||
|
input_burst_lengths = getErrorBurstLengths(input_err_pos);
|
||||||
|
|
||||||
|
[y_amp_err, ~, y_idx_err] = encode(u_err, M, [], pr_order);
|
||||||
|
a_hat = memorylessdecode(y_amp_err, M, [], pr_order);
|
||||||
|
|
||||||
|
rxBits = demapSymbolIndices(a_hat, txSymbols, mapper, M);
|
||||||
|
[checkedBits, bitErrors, ber,errorIndice] = calc_ber( ...
|
||||||
|
rxBits.signal, refBits.signal, "returnErrorLocation", 1);
|
||||||
|
|
||||||
|
output_err_pos = find(a_hat(:) ~= a_ref(:));
|
||||||
|
output_burst_lengths = getErrorBurstLengths(output_err_pos);
|
||||||
|
|
||||||
|
results.input_symbol_errors = numel(input_err_pos);
|
||||||
|
results.input_symbol_ser = results.input_symbol_errors / numel(a_ref);
|
||||||
|
results.input_bit_errors = inputBitErrors;
|
||||||
|
results.input_checked_bits = inputCheckedBits;
|
||||||
|
results.input_ber = inputBer;
|
||||||
|
results.input_burst_count = count_error_bursts( ...
|
||||||
|
input_err_pos, max(burst_pattern.burst_lengths));
|
||||||
|
results.input_burst_total = numel(input_burst_lengths);
|
||||||
|
results.input_max_burst_length = 0;
|
||||||
|
if ~isempty(input_burst_lengths)
|
||||||
|
results.input_max_burst_length = max(input_burst_lengths);
|
||||||
|
end
|
||||||
|
|
||||||
|
results.symbol_errors = numel(output_err_pos);
|
||||||
|
results.symbol_ser = results.symbol_errors / numel(a_ref);
|
||||||
|
results.bit_errors = bitErrors;
|
||||||
|
results.checked_bits = checkedBits;
|
||||||
|
results.ber = ber;
|
||||||
|
results.output_burst_count = count_error_bursts( ...
|
||||||
|
output_err_pos, max(burst_pattern.burst_lengths));
|
||||||
|
results.output_burst_total = numel(output_burst_lengths);
|
||||||
|
results.output_max_burst_length = 0;
|
||||||
|
if ~isempty(output_burst_lengths)
|
||||||
|
results.output_max_burst_length = max(output_burst_lengths);
|
||||||
|
end
|
||||||
|
results.injected_err_pos = injected_err_pos;
|
||||||
|
results.output_err_pos = output_err_pos;
|
||||||
|
results.last_a_hat = a_hat;
|
||||||
|
results.last_a_ref = a_ref;
|
||||||
|
results.last_y_idx_err = y_idx_err;
|
||||||
|
end
|
||||||
|
|
||||||
|
function [u_err, err_pos] = injectSymbolErrorBursts(u_ref, M, burst_pattern)
|
||||||
|
%INJECTSYMBOLERRORBURSTS Apply one precomputed burst pattern in one operation.
|
||||||
|
validateSymbolVector(u_ref, M, 'u_ref');
|
||||||
|
|
||||||
|
original_size = size(u_ref);
|
||||||
|
u_work = double(u_ref(:));
|
||||||
|
N = numel(u_work);
|
||||||
|
err_pos = burst_pattern.err_pos(:);
|
||||||
|
|
||||||
|
if any(err_pos < 1) || any(err_pos > N)
|
||||||
|
error('injectSymbolErrorBursts:InvalidPattern', ...
|
||||||
|
'burst_pattern.err_pos contains positions outside the sequence.');
|
||||||
|
end
|
||||||
|
|
||||||
|
u_err = u_work;
|
||||||
|
u_err(err_pos) = mod(u_err(err_pos) + 1, M);
|
||||||
|
u_err = reshape(u_err, original_size);
|
||||||
|
end
|
||||||
|
|
||||||
|
function burst_pattern = buildCombinedBurstPattern(N, burst_lengths, error_count_per_length, options)
|
||||||
|
%BUILDCOMBINEDBURSTPATTERN One common sequence containing all burst lengths.
|
||||||
|
arguments
|
||||||
|
N (1,1) double
|
||||||
|
burst_lengths
|
||||||
|
error_count_per_length (1,1) double
|
||||||
|
options.randkey = []
|
||||||
|
options.min_gap (1,1) double = 20
|
||||||
|
options.max_placement_attempts (1,1) double = 20000
|
||||||
|
end
|
||||||
|
|
||||||
|
if any(burst_lengths < 1) || any(burst_lengths ~= floor(burst_lengths))
|
||||||
|
error('buildCombinedBurstPattern:InvalidBurstLengths', ...
|
||||||
|
'burst_lengths must contain positive integers.');
|
||||||
|
end
|
||||||
|
if ~(isscalar(error_count_per_length) && ...
|
||||||
|
error_count_per_length == floor(error_count_per_length) && ...
|
||||||
|
error_count_per_length >= 1)
|
||||||
|
error('buildCombinedBurstPattern:InvalidErrorCount', ...
|
||||||
|
'error_count_per_length must be a positive integer.');
|
||||||
|
end
|
||||||
|
if options.min_gap < 0 || options.min_gap ~= floor(options.min_gap)
|
||||||
|
error('buildCombinedBurstPattern:InvalidGap', ...
|
||||||
|
'min_gap must be a nonnegative integer.');
|
||||||
|
end
|
||||||
|
|
||||||
|
burst_lengths = burst_lengths(:).';
|
||||||
|
burst_plan = [];
|
||||||
|
burst_label_plan = [];
|
||||||
|
|
||||||
|
for burst_length = burst_lengths
|
||||||
|
remaining = error_count_per_length;
|
||||||
|
while remaining > 0
|
||||||
|
current_len = min(burst_length, remaining);
|
||||||
|
burst_plan(end + 1, 1) = current_len; %#ok<AGROW>
|
||||||
|
burst_label_plan(end + 1, 1) = burst_length; %#ok<AGROW>
|
||||||
|
remaining = remaining - current_len;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
required_span = sum(burst_plan) + max(0, numel(burst_plan) - 1) * options.min_gap;
|
||||||
|
|
||||||
|
if required_span > N
|
||||||
|
error('buildCombinedBurstPattern:SequenceTooShort', ...
|
||||||
|
'Sequence too short for the requested random burst pattern and gap.');
|
||||||
|
end
|
||||||
|
|
||||||
|
if isempty(options.randkey)
|
||||||
|
rs = [];
|
||||||
|
else
|
||||||
|
rs = RandStream('mt19937ar', 'Seed', options.randkey);
|
||||||
|
end
|
||||||
|
|
||||||
|
placement_order = randomPermutation(numel(burst_plan), rs);
|
||||||
|
burst_plan = burst_plan(placement_order);
|
||||||
|
burst_label_plan = burst_label_plan(placement_order);
|
||||||
|
|
||||||
|
burst_starts = zeros(numel(burst_plan), 1);
|
||||||
|
burst_ends = zeros(numel(burst_plan), 1);
|
||||||
|
|
||||||
|
for b = 1:numel(burst_plan)
|
||||||
|
burst_len = burst_plan(b);
|
||||||
|
placed = false;
|
||||||
|
|
||||||
|
for attempt = 1:options.max_placement_attempts
|
||||||
|
start_pos = randomInteger(1, N - burst_len + 1, rs);
|
||||||
|
end_pos = start_pos + burst_len - 1;
|
||||||
|
|
||||||
|
if isBurstPlacementFree(start_pos, end_pos, burst_starts(1:b-1), ...
|
||||||
|
burst_ends(1:b-1), options.min_gap)
|
||||||
|
burst_starts(b) = start_pos;
|
||||||
|
burst_ends(b) = end_pos;
|
||||||
|
placed = true;
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if ~placed
|
||||||
|
error('buildCombinedBurstPattern:PlacementFailed', ...
|
||||||
|
'Could not place all bursts. Reduce min_gap or error count.');
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
[burst_starts, sort_idx] = sort(burst_starts);
|
||||||
|
burst_ends = burst_ends(sort_idx);
|
||||||
|
burst_label_plan = burst_label_plan(sort_idx);
|
||||||
|
|
||||||
|
err_pos = [];
|
||||||
|
err_label = [];
|
||||||
|
for b = 1:numel(burst_starts)
|
||||||
|
current_pos = (burst_starts(b):burst_ends(b)).';
|
||||||
|
err_pos = [err_pos; current_pos]; %#ok<AGROW>
|
||||||
|
err_label = [err_label; repmat(burst_label_plan(b), numel(current_pos), 1)]; %#ok<AGROW>
|
||||||
|
end
|
||||||
|
|
||||||
|
burst_pattern = struct();
|
||||||
|
burst_pattern.err_pos = err_pos;
|
||||||
|
burst_pattern.err_label = err_label;
|
||||||
|
burst_pattern.burst_lengths = burst_lengths;
|
||||||
|
burst_pattern.error_count_per_length = error_count_per_length;
|
||||||
|
burst_pattern.randkey = options.randkey;
|
||||||
|
burst_pattern.min_gap = options.min_gap;
|
||||||
|
burst_pattern.burst_starts = burst_starts;
|
||||||
|
burst_pattern.burst_ends = burst_ends;
|
||||||
|
end
|
||||||
|
|
||||||
|
function tf = isBurstPlacementFree(start_pos, end_pos, burst_starts, burst_ends, min_gap)
|
||||||
|
%ISBURSTPLACEMENTFREE True when the candidate interval is gap-separated.
|
||||||
|
if isempty(burst_starts)
|
||||||
|
tf = true;
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
tf = all(end_pos + min_gap < burst_starts | start_pos - min_gap > burst_ends);
|
||||||
|
end
|
||||||
|
|
||||||
|
function p = randomPermutation(n, rs)
|
||||||
|
%RANDOMPERMUTATION randperm with optional RandStream.
|
||||||
|
if isempty(rs)
|
||||||
|
p = randperm(n);
|
||||||
|
else
|
||||||
|
p = randperm(rs, n);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function x = randomInteger(lo, hi, rs)
|
||||||
|
%RANDOMINTEGER randi with optional RandStream.
|
||||||
|
if isempty(rs)
|
||||||
|
x = randi([lo hi]);
|
||||||
|
else
|
||||||
|
x = randi(rs, [lo hi]);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function burst_lengths = getErrorBurstLengths(err_pos)
|
||||||
|
%GETERRORBURSTLENGTHS Return lengths of all contiguous error bursts.
|
||||||
|
err_pos = sort(err_pos(:).');
|
||||||
|
|
||||||
|
if isempty(err_pos)
|
||||||
|
burst_lengths = [];
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
diffs = diff(err_pos);
|
||||||
|
burst_starts = [1, find(diffs > 1) + 1];
|
||||||
|
burst_ends = [find(diffs > 1), numel(err_pos)];
|
||||||
|
burst_lengths = burst_ends - burst_starts + 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
function u = precode(a, M, init_state, pr_order)
|
||||||
|
%PRECODE Symbol-domain modulo precoder for F(D)=(1+D)^n.
|
||||||
|
% h = coefficients of (1 + D)^n
|
||||||
|
% u(k) = mod(a(k) - sum_{i=1}^n h(i+1) u(k-i), M)
|
||||||
|
if nargin < 4 || isempty(pr_order)
|
||||||
|
pr_order = 1;
|
||||||
|
end
|
||||||
|
if nargin < 3
|
||||||
|
init_state = [];
|
||||||
|
end
|
||||||
|
|
||||||
|
validateSymbolVector(a, M, 'a');
|
||||||
|
pr_order = validatePrOrder(pr_order);
|
||||||
|
state = validateInitState(init_state, M, pr_order);
|
||||||
|
|
||||||
|
original_size = size(a);
|
||||||
|
a_work = double(a(:).');
|
||||||
|
|
||||||
|
h = getPartialResponseTaps(pr_order);
|
||||||
|
u = zeros(size(a_work));
|
||||||
|
|
||||||
|
for k = 1:numel(a_work)
|
||||||
|
% Move the PR recursion to the transmitter.
|
||||||
|
feedback = sum(h(2:end) .* state);
|
||||||
|
u(k) = mod(a_work(k) - feedback, M);
|
||||||
|
state = [u(k) state(1:end-1)];
|
||||||
|
end
|
||||||
|
|
||||||
|
u = reshape(u, original_size);
|
||||||
|
end
|
||||||
|
|
||||||
|
function [y_amp, pr_alphabet, y_idx] = encode(u, M, init_state, pr_order)
|
||||||
|
%ENCODE Apply F(D)=(1+D)^n to an already precoded sequence.
|
||||||
|
if nargin < 4 || isempty(pr_order)
|
||||||
|
pr_order = 1;
|
||||||
|
end
|
||||||
|
if nargin < 3
|
||||||
|
init_state = [];
|
||||||
|
end
|
||||||
|
|
||||||
|
validateSymbolVector(u, M, 'u');
|
||||||
|
pr_order = validatePrOrder(pr_order);
|
||||||
|
state = validateInitState(init_state, M, pr_order);
|
||||||
|
|
||||||
|
original_size = size(u);
|
||||||
|
u_work = double(u(:).');
|
||||||
|
h = getPartialResponseTaps(pr_order);
|
||||||
|
pam_levels = getPamLevels(M);
|
||||||
|
pr_alphabet = getDuobinaryAlphabet(M, pr_order);
|
||||||
|
|
||||||
|
y_idx = zeros(size(u_work));
|
||||||
|
y_amp = zeros(size(u_work));
|
||||||
|
|
||||||
|
for k = 1:numel(u_work)
|
||||||
|
pr_state = [u_work(k) state];
|
||||||
|
y_idx(k) = sum(h .* pr_state);
|
||||||
|
y_amp(k) = sum(h .* pam_levels(pr_state + 1));
|
||||||
|
state = pr_state(1:end-1);
|
||||||
|
end
|
||||||
|
|
||||||
|
y_idx = reshape(y_idx, original_size);
|
||||||
|
y_amp = reshape(y_amp, original_size);
|
||||||
|
end
|
||||||
|
|
||||||
|
function [a_hat, y_hat_amp, y_hat_idx] = memorylessdecode(r, M, init_state, pr_order)
|
||||||
|
%MEMORYLESSDECODE Slice to PR alphabet, then decode modulo M.
|
||||||
|
% a_hat(k) = mod(y_hat_idx(k), M)
|
||||||
|
if nargin < 4 || isempty(pr_order)
|
||||||
|
pr_order = 1;
|
||||||
|
end
|
||||||
|
if nargin < 3
|
||||||
|
init_state = [];
|
||||||
|
end
|
||||||
|
|
||||||
|
validatePamOrder(M);
|
||||||
|
pr_order = validatePrOrder(pr_order);
|
||||||
|
validateRealVector(r, 'r');
|
||||||
|
validateInitState(init_state, M, pr_order);
|
||||||
|
|
||||||
|
original_size = size(r);
|
||||||
|
r_work = double(r(:).');
|
||||||
|
|
||||||
|
[pr_alphabet, idx_for_alphabet] = getDuobinaryAlphabet(M, pr_order);
|
||||||
|
|
||||||
|
y_hat_amp = zeros(size(r_work));
|
||||||
|
y_hat_idx = zeros(size(r_work));
|
||||||
|
|
||||||
|
for k = 1:numel(r_work)
|
||||||
|
[~, level_pos] = min(abs(r_work(k) - pr_alphabet));
|
||||||
|
y_hat_amp(k) = pr_alphabet(level_pos);
|
||||||
|
y_hat_idx(k) = idx_for_alphabet(level_pos);
|
||||||
|
end
|
||||||
|
|
||||||
|
a_hat = mod(y_hat_idx, M);
|
||||||
|
|
||||||
|
a_hat = reshape(a_hat, original_size);
|
||||||
|
y_hat_amp = reshape(y_hat_amp, original_size);
|
||||||
|
y_hat_idx = reshape(y_hat_idx, original_size);
|
||||||
|
end
|
||||||
|
|
||||||
|
function a_hat = slicePamAmplitude(r, M)
|
||||||
|
%SLICEPAMAMPLITUDE Nearest-neighbor PAM slicing to natural symbol indices.
|
||||||
|
validatePamOrder(M);
|
||||||
|
validateRealVector(r, 'r');
|
||||||
|
|
||||||
|
original_size = size(r);
|
||||||
|
r_work = double(r(:).');
|
||||||
|
pam_levels = getPamLevels(M);
|
||||||
|
a_hat = zeros(size(r_work));
|
||||||
|
|
||||||
|
for k = 1:numel(r_work)
|
||||||
|
[~, level_pos] = min(abs(r_work(k) - pam_levels));
|
||||||
|
a_hat(k) = level_pos - 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
a_hat = reshape(a_hat, original_size);
|
||||||
|
end
|
||||||
|
|
||||||
|
function plotPrecoderBurstAnalysis(results, pr_order, M)
|
||||||
|
%PLOTPRECODERBURSTANALYSIS Compact BER and burst-count visualization.
|
||||||
|
figure(300 + 10*pr_order + M);
|
||||||
|
clf;
|
||||||
|
tiledlayout(2, 1);
|
||||||
|
|
||||||
|
nexttile;
|
||||||
|
bar(results.burst_lengths, results.injected_burst_count);
|
||||||
|
grid on;
|
||||||
|
xlabel("Injected burst length");
|
||||||
|
ylabel("Count");
|
||||||
|
title(sprintf("PAM-%d, F(D)=(1+D)^%d", M, pr_order));
|
||||||
|
|
||||||
|
nexttile;
|
||||||
|
bar(1:numel(results.output_burst_count), results.output_burst_count);
|
||||||
|
grid on;
|
||||||
|
xlabel("Output error burst length");
|
||||||
|
ylabel("Count");
|
||||||
|
title("Output symbol-error burst count");
|
||||||
|
end
|
||||||
|
|
||||||
|
function printBurstCountReport(results, M, pr_order)
|
||||||
|
%PRINTBURSTCOUNTREPORT Paste-friendly burst-count report.
|
||||||
|
max_len = max([numel(results.input_burst_count), numel(results.output_burst_count)]);
|
||||||
|
lengths = 1:max_len;
|
||||||
|
input_counts = padCountVector(results.input_burst_count, max_len);
|
||||||
|
output_counts = padCountVector(results.output_burst_count, max_len);
|
||||||
|
|
||||||
|
fprintf('BURST_COUNT_REPORT pr_order=%d M=%d\n', pr_order, M);
|
||||||
|
fprintf('length : %s\n', formatIntegerVector(lengths));
|
||||||
|
fprintf('before_cnt : %s\n', formatIntegerVector(input_counts));
|
||||||
|
fprintf('after_cnt : %s\n', formatIntegerVector(output_counts));
|
||||||
|
fprintf('delta_cnt : %s\n', formatIntegerVector(output_counts - input_counts));
|
||||||
|
fprintf('before_total=%d before_max=%d after_total=%d after_max=%d\n', ...
|
||||||
|
results.input_burst_total, results.input_max_burst_length, ...
|
||||||
|
results.output_burst_total, results.output_max_burst_length);
|
||||||
|
end
|
||||||
|
|
||||||
|
function x = padCountVector(x, target_length)
|
||||||
|
%PADCOUNTVECTOR Pad count vector with trailing zeros.
|
||||||
|
x = reshape(x, 1, []);
|
||||||
|
if numel(x) < target_length
|
||||||
|
x(end+1:target_length) = 0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function s = formatIntegerVector(x)
|
||||||
|
%FORMATINTEGERVECTOR Format vector as comma-separated integers.
|
||||||
|
x = reshape(x, 1, []);
|
||||||
|
if isempty(x)
|
||||||
|
s = '';
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
parts = strings(size(x));
|
||||||
|
for k = 1:numel(x)
|
||||||
|
parts(k) = string(sprintf('%d', x(k)));
|
||||||
|
end
|
||||||
|
s = char(strjoin(parts, ', '));
|
||||||
|
end
|
||||||
|
|
||||||
|
function plotBurstSummary(burst_results, pr_orders, M_list)
|
||||||
|
%PLOTBURSTSUMMARY Show BER and output-burst metrics for every M/order pair.
|
||||||
|
ber_map = nan(numel(pr_orders), numel(M_list));
|
||||||
|
symbol_error_map = nan(size(ber_map));
|
||||||
|
max_burst_map = nan(size(ber_map));
|
||||||
|
|
||||||
|
for row = 1:numel(pr_orders)
|
||||||
|
pr_order = pr_orders(row);
|
||||||
|
for col = 1:numel(M_list)
|
||||||
|
M = M_list(col);
|
||||||
|
results = burst_results(pr_order, M).results;
|
||||||
|
ber_map(row, col) = results.ber;
|
||||||
|
symbol_error_map(row, col) = results.symbol_errors;
|
||||||
|
max_burst_map(row, col) = results.output_max_burst_length;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
figure(500);
|
||||||
|
clf;
|
||||||
|
tiledlayout(1, 3);
|
||||||
|
|
||||||
|
nexttile;
|
||||||
|
imagesc(M_list, pr_orders, ber_map);
|
||||||
|
colorbar;
|
||||||
|
xlabel("PAM order M");
|
||||||
|
ylabel("PR order n");
|
||||||
|
title("BER");
|
||||||
|
|
||||||
|
nexttile;
|
||||||
|
imagesc(M_list, pr_orders, symbol_error_map);
|
||||||
|
colorbar;
|
||||||
|
xlabel("PAM order M");
|
||||||
|
ylabel("PR order n");
|
||||||
|
title("Symbol errors");
|
||||||
|
|
||||||
|
nexttile;
|
||||||
|
imagesc(M_list, pr_orders, max_burst_map);
|
||||||
|
colorbar;
|
||||||
|
xlabel("PAM order M");
|
||||||
|
ylabel("PR order n");
|
||||||
|
title("Max output burst");
|
||||||
|
end
|
||||||
|
|
||||||
|
function [u, y_amp, pr_alphabet, y_idx, d_ref] = emulatePrecoder(d_tx, M, init_state, pr_order)
|
||||||
|
%EMULATEPRECODER PR precoder emulation by reference relabeling.
|
||||||
|
% The available sequence d_tx is interpreted as d_pre.
|
||||||
|
% d_ref(k) = sum_i h(i+1)*d_tx(k-i) mod M
|
||||||
|
% The waveform itself is generated by the ordinary precode/encode blocks.
|
||||||
|
if nargin < 4 || isempty(pr_order)
|
||||||
|
pr_order = 1;
|
||||||
|
end
|
||||||
|
if nargin < 3
|
||||||
|
init_state = [];
|
||||||
|
end
|
||||||
|
|
||||||
|
d_ref = precoderEmulationReference(d_tx, M, init_state, pr_order);
|
||||||
|
u = precode(d_ref, M, init_state, pr_order);
|
||||||
|
[y_amp, pr_alphabet, y_idx] = encode(u, M, init_state, pr_order);
|
||||||
|
|
||||||
|
assert(isequal(u, d_tx), ...
|
||||||
|
'Emulation relabeling did not reproduce the requested precoder output.');
|
||||||
|
end
|
||||||
|
|
||||||
|
function d_ref = precoderEmulationReference(d_tx, M, init_state, pr_order)
|
||||||
|
%PRECODEREMULATIONREFERENCE Reference implied by interpreting d_tx as u.
|
||||||
|
% For h(D)=(1+D)^n, d_ref(k)=mod(sum_i h(i+1) d_tx(k-i), M).
|
||||||
|
if nargin < 4 || isempty(pr_order)
|
||||||
|
pr_order = 1;
|
||||||
|
end
|
||||||
|
if nargin < 3
|
||||||
|
init_state = [];
|
||||||
|
end
|
||||||
|
|
||||||
|
validateSymbolVector(d_tx, M, 'd_tx');
|
||||||
|
pr_order = validatePrOrder(pr_order);
|
||||||
|
state = validateInitState(init_state, M, pr_order);
|
||||||
|
|
||||||
|
original_size = size(d_tx);
|
||||||
|
d_work = double(d_tx(:).');
|
||||||
|
h = getPartialResponseTaps(pr_order);
|
||||||
|
d_ref = zeros(size(d_work));
|
||||||
|
|
||||||
|
for k = 1:numel(d_work)
|
||||||
|
pr_state = [d_work(k) state];
|
||||||
|
d_ref(k) = mod(sum(h .* pr_state), M);
|
||||||
|
state = pr_state(1:end-1);
|
||||||
|
end
|
||||||
|
|
||||||
|
d_ref = reshape(d_ref, original_size);
|
||||||
|
end
|
||||||
|
|
||||||
|
function s = symbolIndexToAmplitude(a, M)
|
||||||
|
%SYMBOLINDEXTOAMPLITUDE Map natural PAM indices to amplitudes.
|
||||||
|
validateSymbolVector(a, M, 'a');
|
||||||
|
pam_levels = getPamLevels(M);
|
||||||
|
s = reshape(pam_levels(double(a(:)) + 1), size(a));
|
||||||
|
end
|
||||||
|
|
||||||
|
function a = amplitudeToSymbolIndex(s, M)
|
||||||
|
%AMPLITUDETOSYMBOLINDEX Map exact PAM amplitudes to natural indices.
|
||||||
|
validatePamOrder(M);
|
||||||
|
validateRealVector(s, 's');
|
||||||
|
|
||||||
|
original_size = size(s);
|
||||||
|
s_work = double(s(:).');
|
||||||
|
pam_levels = getPamLevels(M);
|
||||||
|
a = zeros(size(s_work));
|
||||||
|
|
||||||
|
for k = 1:numel(s_work)
|
||||||
|
match = find(pam_levels == s_work(k), 1);
|
||||||
|
if isempty(match)
|
||||||
|
error('amplitudeToSymbolIndex:InvalidAmplitude', ...
|
||||||
|
'All amplitudes must be valid PAM-%d levels.', M);
|
||||||
|
end
|
||||||
|
a(k) = match - 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
a = reshape(a, original_size);
|
||||||
|
end
|
||||||
|
|
||||||
|
function runDuobinaryPartialResponseTests()
|
||||||
|
%RUNDUOBINARYPARTIALRESPONSETESTS Minimal self-checks.
|
||||||
|
fprintf('\nRunning duobinary PR self-tests...\n');
|
||||||
|
|
||||||
|
testNoiselessReconstruction(2, 1);
|
||||||
|
testNoiselessReconstruction(4, 1);
|
||||||
|
testNoiselessReconstruction(8, 1);
|
||||||
|
testNoiselessReconstruction(4, 2);
|
||||||
|
testNoiselessReconstruction(8, 3);
|
||||||
|
testFrameworkNoiselessBer();
|
||||||
|
testFirstOrderPrecoderEmulation();
|
||||||
|
testAlphabetSize();
|
||||||
|
testNonzeroInitState();
|
||||||
|
|
||||||
|
fprintf('All duobinary PR self-tests passed.\n');
|
||||||
|
end
|
||||||
|
|
||||||
|
function testFrameworkNoiselessBer()
|
||||||
|
for M = [2 4 8]
|
||||||
|
for pr_order = [1 2 3]
|
||||||
|
[txBits, txSymbols, mapper, a] = generatePamSymbols(M, 1024, 9);
|
||||||
|
u = precode(a, M, [], pr_order);
|
||||||
|
y_amp = encode(u, M, [], pr_order);
|
||||||
|
a_hat = memorylessdecode(y_amp, M, [], pr_order);
|
||||||
|
rxBits = demapSymbolIndices(a_hat, txSymbols, mapper, M);
|
||||||
|
[~, errors, ber] = calc_ber(rxBits.signal, txBits.signal);
|
||||||
|
assert(errors == 0 && ber == 0, ...
|
||||||
|
'Framework BER check failed for M=%d, order=%d.', M, pr_order);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
fprintf('PASS: framework Signalgenerator/PAMmapper/calc_ber noiseless BER\n');
|
||||||
|
end
|
||||||
|
|
||||||
|
function testFirstOrderPrecoderEmulation()
|
||||||
|
for M = [2 4 8]
|
||||||
|
for pr_order = [1 2 3]
|
||||||
|
init_state = mod((1:pr_order) + M - 2, M);
|
||||||
|
[~, txSymbols, mapper, d_tx] = generatePamSymbols(M, 1024, 9);
|
||||||
|
|
||||||
|
[u, y_amp, ~, ~, d_ref] = emulatePrecoder(d_tx, M, init_state, pr_order);
|
||||||
|
assert(isequal(u, d_tx), ...
|
||||||
|
'Emulated precoder output mismatch for M=%d, order=%d.', M, pr_order);
|
||||||
|
|
||||||
|
d_hat = memorylessdecode(y_amp, M, init_state, pr_order);
|
||||||
|
assert(isequal(d_hat, d_ref), ...
|
||||||
|
'Emulated noiseless modulo decoding failed for M=%d, order=%d.', M, pr_order);
|
||||||
|
|
||||||
|
refBits = demapSymbolIndices(d_ref, txSymbols, mapper, M);
|
||||||
|
rxBits = demapSymbolIndices(d_hat, txSymbols, mapper, M);
|
||||||
|
[~, errors, ber] = calc_ber(rxBits.signal, refBits.signal);
|
||||||
|
assert(errors == 0 && ber == 0, ...
|
||||||
|
'Emulated framework BER failed for M=%d, order=%d.', M, pr_order);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
fprintf('PASS: precoder emulation and relabeled reference\n');
|
||||||
|
end
|
||||||
|
|
||||||
|
function testNoiselessReconstruction(M, pr_order)
|
||||||
|
a = randi([0 M-1], 1000, 1);
|
||||||
|
u = precode(a, M, [], pr_order);
|
||||||
|
y_amp = encode(u, M, [], pr_order);
|
||||||
|
a_hat = memorylessdecode(y_amp, M, [], pr_order);
|
||||||
|
assert(isequal(a_hat, a), ...
|
||||||
|
'Noiseless reconstruction failed for M=%d, order=%d.', M, pr_order);
|
||||||
|
fprintf('PASS: noiseless reconstruction for M=%d, order=%d\n', M, pr_order);
|
||||||
|
end
|
||||||
|
|
||||||
|
function testAlphabetSize()
|
||||||
|
for M = [2 4 8]
|
||||||
|
for pr_order = [1 2 3]
|
||||||
|
u = precode(0:M-1, M, [], pr_order);
|
||||||
|
[~, pr_alphabet] = encode(u, M, [], pr_order);
|
||||||
|
expected_size = (M-1)*2^pr_order + 1;
|
||||||
|
assert(numel(pr_alphabet) == expected_size, ...
|
||||||
|
'PR alphabet size failed for M=%d, order=%d.', M, pr_order);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
fprintf('PASS: PR alphabet has (M-1)*2^order+1 distinct levels\n');
|
||||||
|
end
|
||||||
|
|
||||||
|
function testNonzeroInitState()
|
||||||
|
for M = [2 4 8]
|
||||||
|
for pr_order = [1 2 3]
|
||||||
|
init_state = mod((1:pr_order) + M - 2, M);
|
||||||
|
a = randi([0 M-1], 1, 1000);
|
||||||
|
u = precode(a, M, init_state, pr_order);
|
||||||
|
y_amp = encode(u, M, init_state, pr_order);
|
||||||
|
a_hat = memorylessdecode(y_amp, M, init_state, pr_order);
|
||||||
|
assert(isequal(a_hat, a), ...
|
||||||
|
'Nonzero init_state failed for M=%d, order=%d.', M, pr_order);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
fprintf('PASS: random-state test with nonzero init_state\n');
|
||||||
|
end
|
||||||
|
|
||||||
|
function [pr_alphabet, idx_for_alphabet] = getDuobinaryAlphabet(M, pr_order)
|
||||||
|
%GETDUOBINARYALPHABET Build amplitude/index lookup tables from PAM sums.
|
||||||
|
validatePamOrder(M);
|
||||||
|
pr_order = validatePrOrder(pr_order);
|
||||||
|
|
||||||
|
h = getPartialResponseTaps(pr_order);
|
||||||
|
pam_levels = getPamLevels(M);
|
||||||
|
n_state = pr_order + 1;
|
||||||
|
n_combinations = M^n_state;
|
||||||
|
all_amp = zeros(1, n_combinations);
|
||||||
|
idx_to_amp = nan(1, (M-1)*sum(h) + 1);
|
||||||
|
|
||||||
|
for c = 0:n_combinations-1
|
||||||
|
state = zeros(1, n_state);
|
||||||
|
tmp = c;
|
||||||
|
|
||||||
|
for k = 1:n_state
|
||||||
|
state(k) = mod(tmp, M);
|
||||||
|
tmp = floor(tmp / M);
|
||||||
|
end
|
||||||
|
|
||||||
|
y_idx = sum(h .* state);
|
||||||
|
y_amp = sum(h .* pam_levels(state + 1));
|
||||||
|
|
||||||
|
all_amp(c + 1) = y_amp;
|
||||||
|
idx_to_amp(y_idx + 1) = y_amp;
|
||||||
|
end
|
||||||
|
|
||||||
|
pr_alphabet = unique(all_amp);
|
||||||
|
idx_for_alphabet = zeros(size(pr_alphabet));
|
||||||
|
|
||||||
|
for k = 1:numel(pr_alphabet)
|
||||||
|
idx_for_alphabet(k) = find(idx_to_amp == pr_alphabet(k), 1) - 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function h = getPartialResponseTaps(pr_order)
|
||||||
|
%GETPARTIALRESPONSETAPS Coefficients of F(D)=(1+D)^n.
|
||||||
|
pr_order = validatePrOrder(pr_order);
|
||||||
|
h = zeros(1, pr_order + 1);
|
||||||
|
|
||||||
|
for k = 0:pr_order
|
||||||
|
h(k + 1) = nchoosek(pr_order, k);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function validatePamOrder(M)
|
||||||
|
if ~(isscalar(M) && isnumeric(M) && any(M == [2 4 8]))
|
||||||
|
error('validatePamOrder:InvalidM', 'M must be one of [2 4 8].');
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function validateSymbolVector(a, M, name)
|
||||||
|
validatePamOrder(M);
|
||||||
|
if ~(isnumeric(a) && isreal(a) && isvector(a))
|
||||||
|
error('validateSymbolVector:InvalidVector', '%s must be a real numeric vector.', name);
|
||||||
|
end
|
||||||
|
if any(a(:) ~= floor(a(:))) || any(a(:) < 0) || any(a(:) > M-1)
|
||||||
|
error('validateSymbolVector:InvalidSymbols', ...
|
||||||
|
'%s must contain integers in [0, M-1].', name);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function validateRealVector(x, name)
|
||||||
|
if ~(isnumeric(x) && isreal(x) && isvector(x))
|
||||||
|
error('validateRealVector:InvalidVector', '%s must be a real numeric vector.', name);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function pr_order = validatePrOrder(pr_order)
|
||||||
|
if ~(isscalar(pr_order) && isnumeric(pr_order) && isreal(pr_order) && ...
|
||||||
|
pr_order == floor(pr_order) && pr_order >= 1)
|
||||||
|
error('validatePrOrder:InvalidOrder', ...
|
||||||
|
'pr_order must be a positive integer.');
|
||||||
|
end
|
||||||
|
pr_order = double(pr_order);
|
||||||
|
end
|
||||||
|
|
||||||
|
function init_state = validateInitState(init_state, M, pr_order)
|
||||||
|
validatePamOrder(M);
|
||||||
|
pr_order = validatePrOrder(pr_order);
|
||||||
|
|
||||||
|
if isempty(init_state)
|
||||||
|
init_state = zeros(1, pr_order);
|
||||||
|
end
|
||||||
|
|
||||||
|
if ~(isnumeric(init_state) && isreal(init_state) && isvector(init_state) && ...
|
||||||
|
numel(init_state) == pr_order)
|
||||||
|
error('validateInitState:InvalidState', ...
|
||||||
|
'init_state must contain pr_order integers in [0, M-1].');
|
||||||
|
end
|
||||||
|
|
||||||
|
if any(init_state(:) ~= floor(init_state(:))) || ...
|
||||||
|
any(init_state(:) < 0) || any(init_state(:) > M-1)
|
||||||
|
error('validateInitState:InvalidState', ...
|
||||||
|
'init_state must contain pr_order integers in [0, M-1].');
|
||||||
|
end
|
||||||
|
|
||||||
|
init_state = double(reshape(init_state, 1, []));
|
||||||
|
end
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
M = 4;
|
M = 8;
|
||||||
apply_precode_at_tx = 1;
|
apply_precode_at_tx = 1;
|
||||||
|
|
||||||
bits = Signalgenerator( ...
|
bits = Signalgenerator( ...
|
||||||
@@ -19,9 +19,7 @@ disp(['Tx Sequenz: -- RMS:',sprintf('%.1f',rms(symbols_tx.signal)),' - - Levels
|
|||||||
unique(symbols_tx.signal)
|
unique(symbols_tx.signal)
|
||||||
disp('- - - - - - - - - -');
|
disp('- - - - - - - - - -');
|
||||||
|
|
||||||
symbols_tx.signal = awgn(symbols_tx.signal,20,"measured",1);
|
|
||||||
show2Dconstellation(symbols_tx,symbols_tx,"displayname",'VNLE Out','fignum',2241);
|
show2Dconstellation(symbols_tx,symbols_tx,"displayname",'VNLE Out','fignum',2241);
|
||||||
|
|
||||||
|
|
||||||
if apply_precode_at_tx
|
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])
|
% Entschiedene Symbole codieren: d_DB(n) = d(n) + d(n-1) (im Fall von PAM4 7 level [0 1 2 3 4 5 6])
|
||||||
|
|||||||
@@ -12,29 +12,16 @@ fprintf("PAM | bits checked | errors | BER\n");
|
|||||||
fprintf("----+--------------+--------+-----\n");
|
fprintf("----+--------------+--------+-----\n");
|
||||||
|
|
||||||
for M = orders
|
for M = orders
|
||||||
if M == 6
|
|
||||||
bitsPerBlock = 5;
|
|
||||||
else
|
|
||||||
bitsPerBlock = log2(M);
|
|
||||||
end
|
|
||||||
|
|
||||||
bitSignal = Signalgenerator( ...
|
bitSignal = Signalgenerator( ...
|
||||||
"form", signalform.prms, ...
|
"form", signalform.prms, ...
|
||||||
"dimension", bitsPerBlock, ...
|
"M", M, ...
|
||||||
"order", userOrder).process();
|
"order", userOrder).process();
|
||||||
|
|
||||||
if M == 6
|
|
||||||
txBits = reshape(bitSignal.signal.', [], 1);
|
|
||||||
txBits = txBits(1:end - mod(numel(txBits), bitsPerBlock));
|
|
||||||
else
|
|
||||||
txBits = bitSignal.signal;
|
|
||||||
end
|
|
||||||
|
|
||||||
mapper = PAMmapper(M, 0);
|
mapper = PAMmapper(M, 0);
|
||||||
txSymbols = mapper.map(txBits);
|
txSymbols = mapper.map(bitSignal);
|
||||||
rxBits = mapper.demap(txSymbols);
|
rxBits = mapper.demap(txSymbols);
|
||||||
|
|
||||||
[checkedBits, errors, ber] = calc_ber(rxBits, txBits);
|
[checkedBits, errors, ber] = calc_ber(rxBits.signal, bitSignal.signal);
|
||||||
|
|
||||||
fprintf("%3d | %12d | %6d | %.1e\n", M, checkedBits, errors, ber);
|
fprintf("%3d | %12d | %6d | %.1e\n", M, checkedBits, errors, ber);
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user