summaryrefslogtreecommitdiff
path: root/PAM4BERChecker.m
diff options
context:
space:
mode:
Diffstat (limited to 'PAM4BERChecker.m')
-rw-r--r--PAM4BERChecker.m220
1 files changed, 220 insertions, 0 deletions
diff --git a/PAM4BERChecker.m b/PAM4BERChecker.m
new file mode 100644
index 0000000..5fc558e
--- /dev/null
+++ b/PAM4BERChecker.m
@@ -0,0 +1,220 @@
+classdef (StrictDefaults) PAM4BERChecker < matlab.System & matlab.system.mixin.Propagates
+ % PAM4BERCheckerWithIgnore
+ % Calculates Bit Error Rate (BER) for PAM4 signals.
+ %
+ % UPDATED:
+ % 1. Includes Clock input for edge-triggered checking.
+ % 2. Auto-detects if TxSymsIn is Voltage or Integer.
+ % 3. Adds TxDelay to compensate for Channel/DFE latency.
+ % 4. FIXED: Codegen error by ensuring TxBuffer state remains constant size.
+ %
+ % Inputs:
+ % TxSymsIn: Transmitted symbols (Integers 0-3 OR Voltages).
+ % RxDecisions: Decision voltages from the DFE/Receiver.
+ % ClockIn: The Demux Clock signal.
+ %
+ % Outputs:
+ % TotalBitErrors: Cumulative bit errors counted *after* the ignore period.
+ % BER: Bit Error Rate calculated *after* the ignore period.
+
+ %#codegen
+
+ properties (Nontunable)
+ % DemuxWidth
+ % Width of the parallel data path (must match your DFE/Bridge)
+ DemuxWidth = 32;
+
+ % IgnoreSymbols
+ % Number of symbols to ignore at the start of the simulation
+ % (allows equalizer/CDR to adapt before counting errors).
+ IgnoreSymbols = 1000;
+
+ % TxDelay
+ % Number of symbols to delay the Tx stream to align with Rx.
+ % (Use this to compensate for Channel + DFE Latency)
+ TxDelay = 0;
+ end
+
+ properties (Access = private)
+ % Hardcoded thresholds based on standard PAM4 levels [-0.5, -0.16, 0.16, 0.5]
+ ThreshLower = -0.3333; % -1/3
+ ThreshMid = 0.0;
+ ThreshUpper = 0.3333; % 1/3
+
+ % Internal State Counters
+ ErrorCount; % Errors counted after the ignore period
+ ValidBitCount; % Bits processed after the ignore period
+ SymbolsProcessed; % Total symbols seen since simulation start
+
+ % Edge Detection State
+ PreviousClock;
+
+ % Buffer for Tx Delay
+ % Must maintain CONSTANT size [TxDelay x 1] for Code Generation
+ TxBuffer;
+ end
+
+ methods (Access = protected)
+ function setupImpl(obj)
+ obj.ErrorCount = 0;
+ obj.ValidBitCount = 0;
+ obj.SymbolsProcessed = 0;
+ obj.PreviousClock = 0;
+
+ % Initialize buffer with zeros equal to the requested delay.
+ % The size of this property is locked at setup for Codegen.
+ obj.TxBuffer = zeros(obj.TxDelay, 1);
+ end
+
+ function [TotalBitErrors, BER] = stepImpl(obj, TxSymsIn, RxDecisions, ClockIn)
+ % Detect Rising Edge of the Clock
+ isFallingEdge = (ClockIn < 0.5) && (obj.PreviousClock >= 0.5);
+ obj.PreviousClock = ClockIn;
+
+ if isFallingEdge
+
+ % 1. Handle Tx Delay (Fixed-Size State Pattern)
+ % Concatenate the History (TxBuffer) with New Data (TxSymsIn)
+ % combinedTx size = TxDelay + InputSize
+ combinedTx = [obj.TxBuffer; TxSymsIn(:)];
+
+ % Extract the oldest 'DemuxWidth' symbols for processing.
+ % NOTE: Logic assumes size(TxSymsIn) == DemuxWidth.
+ % If inputs are larger/smaller, ensure DemuxWidth parameter matches.
+ currentTxRaw = combinedTx(1:obj.DemuxWidth);
+
+ % Update the History (State)
+ % We save the remaining tail as the new history.
+ % New Size = (TxDelay + DemuxWidth) - DemuxWidth = TxDelay
+ % This ensures obj.TxBuffer size never changes, fixing the Codegen error.
+ obj.TxBuffer = combinedTx(obj.DemuxWidth+1:end);
+
+ % 2. Auto-Detect Tx Format (Voltage vs Integer)
+ % If we see negative values, it's definitely Voltage.
+ if any(currentTxRaw < 0)
+ % Slice Voltage -> Integer
+ txInts = obj.sliceVoltages(currentTxRaw);
+ else
+ % Assume it's already Integer (0, 1, 2, 3)
+ txInts = currentTxRaw;
+ end
+
+ % 3. Check Ignore Period
+ startIdx = 1;
+ if obj.SymbolsProcessed < obj.IgnoreSymbols
+ remainingToIgnore = obj.IgnoreSymbols - obj.SymbolsProcessed;
+ if remainingToIgnore >= obj.DemuxWidth
+ startIdx = obj.DemuxWidth + 1; % Ignore all
+ else
+ startIdx = remainingToIgnore + 1; % Ignore partial
+ end
+ end
+
+ % Update symbol counter
+ obj.SymbolsProcessed = obj.SymbolsProcessed + obj.DemuxWidth;
+
+ % 4. Process Valid Portion
+ currentBlockErrors = 0;
+ currentBlockBits = 0;
+
+ if startIdx <= obj.DemuxWidth
+ % Slice inputs to valid range
+ validTx = txInts(startIdx:end);
+ validRxRaw = RxDecisions(startIdx:end);
+
+ % Slice Rx Voltages into Integers (0, 1, 2, 3)
+ validRxInts = obj.sliceVoltages(validRxRaw);
+
+ % Convert both to Bits (Gray Coded)
+ [txMSB, txLSB] = obj.mapSymbolsToGrayBits(validTx);
+ [rxMSB, rxLSB] = obj.mapSymbolsToGrayBits(validRxInts);
+
+ % Count Errors
+ validLength = length(validTx);
+ for i = 1:validLength
+ if txMSB(i) ~= rxMSB(i)
+ currentBlockErrors = currentBlockErrors + 1;
+ end
+ if txLSB(i) ~= rxLSB(i)
+ currentBlockErrors = currentBlockErrors + 1;
+ end
+ end
+
+ currentBlockBits = validLength * 2;
+ end
+
+ % 5. Update States
+ obj.ErrorCount = obj.ErrorCount + currentBlockErrors;
+ obj.ValidBitCount = obj.ValidBitCount + currentBlockBits;
+ end
+
+ % 6. Output Results
+ TotalBitErrors = uint32(obj.ErrorCount);
+ if obj.ValidBitCount > 0
+ BER = single(obj.ErrorCount / obj.ValidBitCount);
+ else
+ BER = single(0);
+ end
+ end
+
+ function rxInts = sliceVoltages(obj, rxVoltages)
+ % Helper to slice float voltages into 0-3 integers
+ len = length(rxVoltages);
+ rxInts = zeros(len, 1);
+ for i = 1:len
+ val = rxVoltages(i);
+ if val < obj.ThreshLower
+ rxInts(i) = 0;
+ elseif val < obj.ThreshMid
+ rxInts(i) = 1;
+ elseif val < obj.ThreshUpper
+ rxInts(i) = 2;
+ else
+ rxInts(i) = 3;
+ end
+ end
+ end
+
+ function [msb, lsb] = mapSymbolsToGrayBits(~, symbols)
+ % Maps integer symbols (0-3) to MSB and LSB bits using Gray Coding.
+ msb = zeros(size(symbols));
+ lsb = zeros(size(symbols));
+
+ % MSB is 1 for symbols 2 and 3
+ msb(symbols == 2 | symbols == 3) = 1;
+
+ % LSB is 1 for symbols 1 and 2
+ lsb(symbols == 1 | symbols == 2) = 1;
+ end
+
+ function resetImpl(obj)
+ obj.ErrorCount = 0;
+ obj.ValidBitCount = 0;
+ obj.SymbolsProcessed = 0;
+ obj.PreviousClock = 0;
+ % Reset buffer to fixed size
+ obj.TxBuffer = zeros(obj.TxDelay, 1);
+ end
+
+ %% Simulink Interface Definitions
+ function [sz1, sz2] = getOutputSizeImpl(~)
+ sz1 = [1 1]; sz2 = [1 1];
+ end
+ function [dt1, dt2] = getOutputDataTypeImpl(~)
+ dt1 = 'uint32'; dt2 = 'single';
+ end
+ function [cp1, cp2] = isOutputComplexImpl(~)
+ cp1 = false; cp2 = false;
+ end
+ function [fx1, fx2] = isOutputFixedSizeImpl(~)
+ fx1 = true; fx2 = true;
+ end
+
+ function [n1, n2, n3] = getInputNamesImpl(~)
+ n1 = 'TxSyms'; n2 = 'RxDec'; n3 = 'Clock';
+ end
+ function [n1, n2] = getOutputNamesImpl(~)
+ n1 = 'TotalErrors'; n2 = 'BER';
+ end
+ end
+end \ No newline at end of file