diff options
Diffstat (limited to 'PAM4BERChecker.m')
| -rw-r--r-- | PAM4BERChecker.m | 220 |
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 |
