Add partial response symbol mapping and IM/DD system example

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

View File

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

View File

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

View File

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

View File

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