summaryrefslogtreecommitdiff
path: root/PAM4BERChecker.m
blob: 5fc558e083357e18b9bd274f61e0b8e1712d8524 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
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