Compute the theoretical BER for AWGN channel and various constellation size

Based on the previous skeleton, we can now compute an iterative testbench to compute the Bit Error Rate for various constellation size, and compare the simulation with the theory. As a gentle reminder, the theoretical bit error rate can be approximated as

$\mathrm{BER} = \frac{ 4 \left( 1 - \frac{1}{\sqrt{M}} \right) }{ \log_2(M)} \times Q( \sqrt{ \frac{6 \log_2(M)}{2(M-1)} \frac{Eb}{N_0}}$

First of all let's call the modules

using DigitalComm 
using PGFPlotsX

We define first the main monte-carlo function that compute an elementary Tx-Rx link, and returns the number of error and number of bit computed (to be accumulated)

function monteCarlo(snr,mcs,nbSymb) 
    # Number of bits  
    nbBits    = nbSymb * Int(log2(mcs));
    # --- Binary sequence generation 
    bitSeq    = genBitSequence(nbBits);
    # --- QPSK mapping
    qamSeq    = bitMappingQAM(mcs,bitSeq);
    # ---------------------------------------------------- 
    # --- Channel  
    # ---------------------------------------------------- 
    #  --- AWGN
    # Theoretical power is 1 (normalized constellation)
    qamNoise,  = addNoise(qamSeq,snr,1);
    # ----------------------------------------------------
    # --- Rx Stage: SRRC
    # ----------------------------------------------------
    # --- Binary demapper
    bitDec  = bitDemappingQAM(mcs,qamNoise);
    # --- Error counter 
    nbE     = sum(xor.(bitDec,bitSeq));
    # --- Return Error and bits 
    return (nbE,nbBits);
end

A function to plot the BER versus the SNR, for different mcs and compare to theory

function doPlot(snrVect,ber,qamVect)
    a = 0;
    @pgf a = Axis({
        ymode     = "log",
        height      ="3in",
        width       ="4in",
        grid,
        xlabel      = "SNR [dB]",
        ylabel      = "Bit Error Rate ",
        ymax       = 1,
        ymin       = 10.0^(-5),
        title       = "AWGN BER for QAM",
        legend_style="{at={(0,0)},anchor=south west,legend cell align=left,align=left,draw=white!15!black}"
        },
        Plot({color="red",mark="square*"},Table([snrVect,ber[1,:]])),
        LegendEntry("QPSK"),
        Plot({color="green",mark="*"},Table([snrVect,ber[2,:]])),
        LegendEntry("16-QAM"),

        Plot({color="purple",mark="triangle*"},Table([snrVect,ber[3,:]])),
        LegendEntry("64-QAM"),
        Plot({color="blue",mark="diamond*"},Table([snrVect,ber[4,:]])),
        LegendEntry("256-QAM"),
    );
    # ---  Adding theoretical curve
    snrLin  = (10.0).^(snrVect/10)
    for qamScheme = qamVect
        ebNo    = snrLin / log2(qamScheme);
        # This approximation is only valid for high SNR (one symbol error is converted to one bit error with Gray coding).
        berTheo   = 4 * ( 1 - 1 / sqrt(qamScheme)) / log2(qamScheme) * qFunc.(sqrt.( 2*ebNo * 3 * log2(qamScheme) / (2*(qamScheme-1)  )));
        @pgf push!(a,Plot({color="black"},Table([snrVect,berTheo])));
    end
    display(a);
end

Then, the main routine to compute the BER for a given number of iterations and a range of SNR

function main() 
# --- Parameters 
nbIt            = 10000;          # Number of iterations  
nbSymb          = 1024;           # Number of symbols per iterations 
mcs             = [4,16,64,256];  # Constellation size 
snrRange        = (-1:26);        # SNR, expressed in dB 
# --- Init performance metrics
nbSNR           = length(snrRange);
ber             = zeros(Float64,length(mcs),nbSNR);
for iMcs = 1 : 1  : length(mcs)
    for iSNR = 1 : 1 : nbSNR
        # --- Create BER counters 
        nbE   = 0;
        nbB   = 0;
        for iN = 1 : 1 : nbIt
            # --- Elementary MC call 
            # Corresponds to a given SNR and a given iteration 
            # As we are ergodic in AWGN, it is only nbSymb*nbIt that matters for BER computation
            (a,b) = monteCarlo(snrRange[iSNR],mcs[iMcs],nbSymb); 
            # --- Update counters 
            nbE += a;     # Increment errors 
            nbB += b;     # Increment bit counters 
        end 
        ber[iMcs,iSNR] = nbE / nbB; 
    end
end 
# --- Plotting routine
doPlot(snrRange,ber,mcs);
end

The output plot is the following, showing adequacy between theory and practise for high SNR (the theoretical curve is under the assumption that one symbol error leads to one erroneous bit (gray coding) which is true only with intermediate noise levels). BER