Calculating BER/BLER for PDSCH Communication with LDPC

This notebook shows how to calculate the BLock Error Rate (BLER) of PDSCH communication with LDPC channel coding.

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

from neoradium import Carrier, PDSCH, CdlChannel, AntennaPanel, LdpcEncoder, Grid, random, SnrScheduler

[2]:
numSlots = 200
snrScheduler = SnrScheduler(6,0.2)         # Start at 6 dB, use increments of 0.2 dB
freqDomain = True                          # Set 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()

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("SNR(dB)    Total Bits  Bit Errors  BER(%)  Total Blocks  Block Errors  BLER(%)  time(Sec.)")
    print("---------  ----------  ----------  ------  ------------  ------------  -------  ----------")
    snrScheduler.reset()
    for snrDb in snrScheduler:
        random.setSeed(123)                             # Making the results reproducible for each SNR
        t0 = time.time()                                # Start time for each SNR
        carrier.slotNo = 0

        # Creating a CdlChannel object:
        channel = CdlChannel(bwp, 'C', delaySpread=300, carrierFreq=4e9, dopplerShift=5,
                             txAntenna = AntennaPanel([2,4], polarization="x"),  # 16 TX antenna
                             rxAntenna = AntennaPanel([1,2], polarization="x"))  # 4 RX antenna

        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
            rateMatchedCodeBlocks = ldpcEncoder.getRateMatchedCodeBlocks(txBlock, numBits[0])

            pdsch.populateGrid(grid, rateMatchedCodeBlocks)         # 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()              # 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, useRxPower=True)  # 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, bwp=bwp, useRxPower=True)    # 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 = channel.getEffChannel(channelMatrix, precoder)
            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  %5.1f    %8d    %8d    %6.2f    %8d      %8d     %6.2f     %6.2f"
                  %(snrDb, totalBits, bitErrors, ber, totalBlocks, blockErrors, bler, time.time()-t0), end='')

            channel.goNext()

        dt = time.time()-t0
        snrScheduler.setData(bler, ber)
        print("")
    results[chanEstMethod] = snrScheduler.getSnrsAndData()

Simulating end-to-end for "16QAM", with "Perfect" channel estimation, in frequency domain.
SNR(dB)    Total Bits  Bit Errors  BER(%)  Total Blocks  Block Errors  BLER(%)  time(Sec.)
---------  ----------  ----------  ------  ------------  ------------  -------  ----------
    6.0     6043200           0      0.00         800             0       0.00     106.77
    5.8     6043200          15      0.00         800             2       0.25     108.88
    5.6     6043200       12405      0.21         800            60       7.50     110.18
    5.4     6043200      131738      2.18         800           385      48.12     111.34
    5.2     6043200      413528      6.84         800           723      90.38     111.92
    5.0     6043200      664822     11.00         800           797      99.62     117.09
    4.8     6043200      792561     13.11         800           800     100.00     117.29
    4.6     6043200      871869     14.43         800           800     100.00     116.15
    6.2     6043200           0      0.00         800             0       0.00     115.56

Simulating end-to-end for "16QAM", with "LS" channel estimation, in frequency domain.
SNR(dB)    Total Bits  Bit Errors  BER(%)  Total Blocks  Block Errors  BLER(%)  time(Sec.)
---------  ----------  ----------  ------  ------------  ------------  -------  ----------
    6.0     6043200     1136698     18.81         800           800     100.00     115.43
    6.2     6043200     1094907     18.12         800           800     100.00     115.90
    6.6     6043200      994696     16.46         800           800     100.00     115.50
    7.0     6043200      780972     12.92         800           755      94.38     115.47
    6.8     6043200      911237     15.08         800           795      99.38     116.53
    6.4     6043200     1048762     17.35         800           800     100.00     115.67
    7.2     6043200      622829     10.31         800           604      75.50     115.42
    7.4     6043200      532087      8.80         800           437      54.62     115.56
    7.6     6043200      498663      8.25         800           403      50.38     114.96
    7.8     6043200      471762      7.81         800           400      50.00     116.14
    8.0     6043200      441682      7.31         800           400      50.00     116.42
    8.2     6043200      398856      6.60         800           400      50.00     115.79
    8.4     6043200      327425      5.42         800           390      48.75     115.41
    8.6     6043200      218633      3.62         800           333      41.62     115.23
    8.8     6043200      113470      1.88         800           213      26.62     115.19
    9.0     6043200       45144      0.75         800           108      13.50     114.76
    9.2     6043200        8927      0.15         800            35       4.38     115.15
    9.4     6043200         733      0.01         800             5       0.62     116.84
    9.6     6043200           0      0.00         800             0       0.00     117.23
    9.8     6043200           0      0.00         800             0       0.00     112.52
[3]:
# Compare the results in a couple of plots:
logGraph = False

# Bit Error Rate:
for i,chanEstMethod in enumerate(['Perfect', 'LS']):
    plt.plot(results[chanEstMethod][0], results[chanEstMethod][2], label=chanEstMethod)
plt.legend()
plt.title("Bit Error Rate for different methods of Channel Estimation.");
plt.grid()
plt.xlabel("SNR (dB)")
# plt.xticks(results[chanEstMethod][0])
plt.ylabel("BER (%)")
if logGraph: plt.yscale('log')
plt.show()

# Block Error rate
for i,chanEstMethod in enumerate(['Perfect', 'LS']):
    plt.plot(results[chanEstMethod][0], results[chanEstMethod][1], label=chanEstMethod)
plt.legend()
plt.title("Block Error Rate for different methods of Channel Estimation.");
plt.grid()
plt.xlabel("SNR (dB)")
# plt.xticks(results[chanEstMethod][0])
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
[ ]: