Calculating BER/BLER for PDSCH Communication with LDPC

This notebook shows how to calculate the bit error rate of PDSCH communication with LDPC channel coding.

[1]:
import numpy as np
import scipy.io
import time
import matplotlib.pyplot as plt

from neoradium import Carrier, PDSCH, CdlChannel, AntennaPanel, LdpcEncoder, Grid
from neoradium.utils import random

[2]:
numFrames = 4                               # Number of time-domain frames
ebNoDbs = range(8,14)                       # Eb/No values (in dB)
freqDomain = False                          # Set this to True to apply channel in frequency domain

modulation = "16QAM"
carrier = Carrier(numRbs=51, spacing=30)    # Create a carrier with 51 RBs and 30KHz subcarrier spacing
bwp = carrier.curBwp                        # The only bandwidth part in the carrier

# Create a PDSCH object
pdsch = PDSCH(bwp, interleavingBundleSize=0, numLayers=2, nID=carrier.cellId, modulation=modulation)
pdsch.setDMRS(prgSize=0, configType=2, additionalPos=2)     # Specify the DMRS configuration

# Create the LDPC encoder
codeRate = 490/1024
ldpcEncoder = LdpcEncoder(baseGraphNo=1, modulation=pdsch.modems[0].modulation,
                          txLayers=pdsch.numLayers, targetRate=codeRate)
ldpcDecoder = ldpcEncoder.getDecoder()

numSlots = bwp.slotsPerFrame*numFrames
results = {}
for chanEstMethod in ["Perfect", "LS"]:                 # Two channel estimation methods
    results[chanEstMethod] = {}
    print("\nSimulating end-to-end for \"%s\", with \"%s\" channel estimation, in %s domain."%
          (modulation, chanEstMethod, "frequency" if freqDomain else "time"))
    print("EB/No(dB)  Total Bits  Bit Errors  BER(%)  Total Blocks  Block Errors  BLER(%)  time(Sec.)")
    print("---------  ----------  ----------  ------  ------------  ------------  -------  ----------")
    for ebNoDb in ebNoDbs:                              # For each Eb/No value in ebNoDbs
        snrDb = ebNoDb + 10*np.log10(pdsch.modems[0].qm * codeRate * bwp.dataTimeRatio) # Eb/No -> SNR
        random.setSeed(123)                             # Making the results reproducible for each Eb/No
        t0 = time.time()                                # Start time for each Eb/No
        carrier.slotNo = 0

        # Creating a CdlChannel object:
        channel = CdlChannel('C', delaySpread=300, carrierFreq=4e9, dopplerShift=5,
                             txAntenna = AntennaPanel([2,4]),  # 8 TX antenna
                             rxAntenna = AntennaPanel([1,2]),  # 2 RX antenna
                             seed = 123,
                             timing = "nearest")

        blockErrors = 0
        totalBlocks = 0
        bitErrors = 0
        totalBits = 0

        for slotNo in range(numSlots):
            grid = pdsch.getGrid()                       # Create a resource grid already populated with DMRS
            txBlockSize = pdsch.getTxBlockSize(codeRate) # Calculate the Transport Block Size
            txBlock = random.bits(txBlockSize[0])        # Create random binary data
            numBits = pdsch.getBitSizes(grid)            # Actual number of bits available in the resource grid

            # Perform the segmentation, rate-matching, and encoding
            rateMatchedCodeWords = ldpcEncoder.getRateMatchedCodeWords(txBlock, numBits[0])

            pdsch.populateGrid(grid, rateMatchedCodeWords)   # Map/modulate the data to the resource grid

            # Store the indexes of the PDSCH data in pdschIndexes to be used later.
            pdschIndexes = pdsch.getReIndexes(grid, "PDSCH")

            # Getting the Precoding Matrix, and precoding the resource grid
            channelMatrix = channel.getChannelMatrix(bwp)           # Get the channel matrix
            precoder = pdsch.getPrecodingMatrix(channelMatrix)      # Get the precoder matrix from the PDSCH object
            precodedGrid = grid.precode(precoder)                   # Perform the precoding

            if freqDomain:
                rxGrid = precodedGrid.applyChannel(channelMatrix)   # Apply the channel in frequency domain
                rxGrid = rxGrid.addNoise(snrDb=snrDb)               # Add noise
            else:
                txWaveform = precodedGrid.ofdmModulate()            # OFDM Modulation
                maxDelay = channel.getMaxDelay()                    # Get the max. channel delay
                txWaveform = txWaveform.pad(maxDelay)               # Pad with zeros
                rxWaveform = channel.applyToSignal(txWaveform)      # Apply channel in time domain
                noisyRxWaveform = rxWaveform.addNoise(snrDb=snrDb, nFFT=bwp.nFFT)  # Add noise
                offset = channel.getTimingOffset()                  # Get timing info for synchronization
                syncedWaveform = noisyRxWaveform.sync(offset)       # Synchronization
                rxGrid = syncedWaveform.ofdmDemodulate(bwp)         # OFDM demodulation


            if chanEstMethod == "Perfect":                          # Perfect Channel Estimation
                estChannelMatrix = channelMatrix @ precoder[None,...]
            else:                                                   # LS + Interpolation Channel Estimation
                estChannelMatrix, noiseEst = rxGrid.estimateChannelLS(pdsch.dmrs, polarInt=False,
                                                                      kernel='linear')

            eqGrid, llrScales = rxGrid.equalize(estChannelMatrix)            # Equalization
            llrs = pdsch.getLLRsFromGrid(eqGrid, pdschIndexes, llrScales)    # Demodulation (to LLRs)
            rxCodedBlocks = ldpcDecoder.recoverRate(llrs[0], txBlockSize[0]) # Recovering Rate
            decodedBlocks = ldpcDecoder.decode(rxCodedBlocks, numIter=20)    # LDPC Decoding
            decodedTxBlockWithCRC, crcMatch = ldpcDecoder.checkCrcAndMerge(decodedBlocks) # Merge blocks
            decodedTxBlock = decodedTxBlockWithCRC[:-24]                     # remove transport block CRC
            blockErrors += len(crcMatch)-sum(crcMatch)                       # Number of Block errors
            bitErrors += np.abs(decodedTxBlock-txBlock).sum()                # Number of bit errors
            totalBlocks += len(crcMatch)
            totalBits += len(txBlock)

            ber = bitErrors*100/totalBits
            bler = blockErrors*100/totalBlocks
            print("\r  %3d      %8d    %8d    %6.2f    %8d      %8d     %6.2f     %6.2f"
                  %(ebNoDb, totalBits, bitErrors, ber, totalBlocks, blockErrors, bler, time.time()-t0), end='')

            carrier.goNext()
            channel.goNext()

        dt = time.time()-t0
        results[chanEstMethod][snrDb] = {"totalBits":  totalBits,
                                         "bitErrors":  bitErrors,
                                         "BER":        bitErrors*100/totalBits,
                                         "totalBlocks":totalBlocks,
                                         "blockErrors":blockErrors,
                                         "BLER":       blockErrors*100/totalBlocks,
                                         "Time":       dt}
        print("\r  %3d      %8d    %8d    %6.2f    %8d      %8d     %6.2f     %6.2f"
              %(ebNoDb, totalBits, bitErrors, ber, totalBlocks, blockErrors, bler, time.time()-t0))


Simulating end-to-end for "16QAM", with "Perfect" channel estimation, in time domain.
EB/No(dB)  Total Bits  Bit Errors  BER(%)  Total Blocks  Block Errors  BLER(%)  time(Sec.)
---------  ----------  ----------  ------  ------------  ------------  -------  ----------
    8       2417280      323481     13.38         320           320     100.00      50.77
    9       2417280       18663      0.77         320           145      45.31      52.54
   10       2417280           0      0.00         320             0       0.00      52.12
   11       2417280           0      0.00         320             0       0.00      53.20
   12       2417280           0      0.00         320             0       0.00      52.58
   13       2417280           0      0.00         320             0       0.00      53.00

Simulating end-to-end for "16QAM", with "LS" channel estimation, in time domain.
EB/No(dB)  Total Bits  Bit Errors  BER(%)  Total Blocks  Block Errors  BLER(%)  time(Sec.)
---------  ----------  ----------  ------  ------------  ------------  -------  ----------
    8       2417280      469554     19.42         320           320     100.00      54.17
    9       2417280      404225     16.72         320           320     100.00      53.96
   10       2417280      280051     11.59         320           319      99.69      53.13
   11       2417280      133393      5.52         320           160      50.00      55.77
   12       2417280        1518      0.06         320            12       3.75      53.92
   13       2417280           0      0.00         320             0       0.00      56.55
[3]:
# Compare the results in a couple of plots:
logGraph = False
def ebNo2Snr(ebNo): return ebNo + 10*np.log10(pdsch.modems[0].qm * codeRate * bwp.dataTimeRatio)

# Bit Error Rate:
for i,chanEstMethod in enumerate(['Perfect', 'LS']):
    bers = [results[chanEstMethod][ebNo2Snr(ebNoDb)]["BER"] for ebNoDb in ebNoDbs]
    plt.plot(ebNoDbs, bers, label=chanEstMethod)
plt.legend()
plt.title("Bit Error Rate for different mothods of Channel Estimation.");
plt.grid()
plt.xlabel("eb/No")
plt.xticks(ebNoDbs)
plt.ylabel("BER (%)")
if logGraph: plt.yscale('log')
plt.show()

# Block Error rate
for i,chanEstMethod in enumerate(['Perfect', 'LS']):
    blers = [results[chanEstMethod][ebNo2Snr(ebNoDb)]["BLER"] for ebNoDb in ebNoDbs]
    plt.plot(ebNoDbs, blers, label=chanEstMethod)
plt.legend()
plt.title("Block Error Rate for different mothods of Channel Estimation.");
plt.grid()
plt.xlabel("eb/No")
plt.xticks(ebNoDbs)
plt.ylabel("BLER (%)")
if logGraph: plt.yscale('log')
plt.show()

../../../../_images/source_Playground_Notebooks_PDSCH_PDSCH-BLER_3_0.png
../../../../_images/source_Playground_Notebooks_PDSCH_PDSCH-BLER_3_1.png
[ ]: