BLER Evaluation for PDSCH Communication with LDPC

This notebook demonstrates an end-to-end PDSCH communication pipeline for evaluating block error rate (BLER).

It compares two channel-estimation methods:

  • perfect channel knowledge

  • LS-based channel estimation

The notebook uses LDPC coding and measures BLER over multiple slots across a range of SNR values.

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

from neoradium import Carrier, PDSCH, CdlChannel, AntennaPanel, random, SnrScheduler
[2]:
numSlots = 200

# Using SnrScheduler class to automatically find the right SNR ranges
snrScheduler = SnrScheduler(0, 0.2, fastStep=5) # Start at 0 dB, use increments of 0.2 dB
freqDomain = True                               # Set to True to apply channel in frequency domain

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

# Create a PDSCH object
pdsch = PDSCH(bwp, numLayers=2, modulation=modulation)
pdsch.setDMRS(configType=2, additionalPos=1, symbols=2)     # DMRS configuration

# Create an LDPC codec
ldpc = pdsch.getLdpcCodec(coderates=490/1024)

results = {}
for chanEstMethod in ["Perfect channel knowledge", "LS channel estimation"]:    # Two channel estimation methods
    results[chanEstMethod] = {}
    print("\nSimulating end-to-end for %s using %s in the %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)                     # Make the results reproducible for each SNR
        t0 = time.monotonic()                   # Start time for each SNR
        carrier.slotNo = 0

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

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

        for slotNo in range(numSlots):
            pdsch.initGrid()                                # Create and initialize PDSCH's internal grid
            txBlock = random.bits(ldpc.txBlockSizes[0])     # Create random binary data
            numBits = pdsch.getBitCapacity()                # Number of bits available in the resource grid

            # Perform the segmentation, rate-matching, and encoding
            rateMatchedCodeBlocks = ldpc.encode(txBlock, numBits[0])

            pdsch.setPdschData(rateMatchedCodeBlocks)       # Map/modulate the data to the resource grid

            channelMatrix = channel.getChannelMatrix()              # Get the channel matrix
            precoder = pdsch.getPrecodingMatrix(channelMatrix)      # Get the precoding matrix

            txGrid = bwp.createGrid(len(channel.txAntenna))         # Create the transmitted resource grid
            pdsch.precodeTo(txGrid, precoder)                       # Perform the precoding

            if freqDomain:
                rxGrid = txGrid.applyChannel(channelMatrix)         # Apply the channel in the frequency domain
                rxGrid = rxGrid.addNoise(snrDb=snrDb)               # Add noise
            else:
                txWaveform = txGrid.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 the time domain
                noisyRxWaveform = rxWaveform.addNoise(snrDb=snrDb, bwp=bwp)     # Add noise
                offset = channel.getTimingOffset()                  # Get timing info for synchronization
                syncedWaveform = noisyRxWaveform.sync(offset)       # Synchronization
                rxGrid = syncedWaveform.ofdmDemodulate(bwp)         # OFDM demodulation


            if "Perfect" in chanEstMethod:
                estChannelMatrix = channel.getEffChannel(channelMatrix, precoder)   # Perfect channel knowledge
                eqGrid, llrScales = pdsch.equalize(rxGrid, estChannelMatrix)        # Equalization
            else:
                estChannelMatrix, errVar = pdsch.estimateChannel(rxGrid)            # LS channel estimation
                eqGrid, llrScales = pdsch.equalize(rxGrid, estChannelMatrix, errVar)# Equalization

            llrs = pdsch.getLLRs(eqGrid, llrScales)                 # Demodulation (to LLRs)
            decodedTxBlock, crcMatch = ldpc.decode(llrs[0])         # LDPC decoding
            blockErrors += 0 if crcMatch[0] else 1                  # Number of transport-block errors
            bitErrors += np.abs(decodedTxBlock-txBlock).sum()       # Count the number of bit errors
            totalBlocks += 1
            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.monotonic()-t0), end='')

            channel.goNext()

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

# Compare the results
logGraph = False
for i,chanEstMethod in enumerate(["Perfect channel knowledge", "LS channel estimation"]):
    plt.plot(results[chanEstMethod][0], results[chanEstMethod][1], label=chanEstMethod)
plt.legend()
plt.title("Block error rate (BLER) for different channel-estimation methods");
plt.grid()
plt.xlabel("SNR (dB)")
# plt.xticks(results[chanEstMethod][0])
plt.ylabel("BLER (%)")
if logGraph: plt.yscale('log')
plt.show()


Simulating end-to-end for 16QAM using Perfect channel knowledge in the frequency domain
SNR(dB)    Total Bits  Bit Errors  BER(%)  Total Blocks  Block Errors  BLER(%)  time(Sec.)
---------  ----------  ----------  ------  ------------  ------------  -------  ----------
    0.0     2766400           0      0.00         200             0       0.00       8.73
   -5.0     2766400           0      0.00         200             0       0.00       8.89
  -10.0     2766400           0      0.00         200             0       0.00       9.04
  -15.0     2766400      447991     16.19         200           200     100.00       9.16
  -12.4     2766400        3840      0.14         200           189      94.50       8.99
  -12.6     2766400        9633      0.35         200           199      99.50       9.13
  -12.8     2766400       22497      0.81         200           200     100.00       9.16
  -13.0     2766400       46315      1.67         200           200     100.00       9.13
  -12.2     2766400        1455      0.05         200           164      82.00       9.05
  -12.0     2766400         435      0.02         200           111      55.50       9.09
  -11.8     2766400         107      0.00         200            53      26.50       9.04
  -11.6     2766400          19      0.00         200            14       7.00       9.12
  -11.4     2766400           1      0.00         200             1       0.50       8.93
  -11.2     2766400           0      0.00         200             0       0.00       8.94
  -11.0     2766400           0      0.00         200             0       0.00       8.87

Simulating end-to-end for 16QAM using LS channel estimation in the frequency domain
SNR(dB)    Total Bits  Bit Errors  BER(%)  Total Blocks  Block Errors  BLER(%)  time(Sec.)
---------  ----------  ----------  ------  ------------  ------------  -------  ----------
    0.0     2766400           0      0.00         200             0       0.00       8.75
   -5.0     2766400           0      0.00         200             0       0.00       9.38
  -10.0     2766400           7      0.00         200             5       2.50       9.20
  -10.2     2766400          23      0.00         200            14       7.00       9.45
  -10.4     2766400         105      0.00         200            49      24.50       9.23
  -10.6     2766400         432      0.02         200           110      55.00       9.21
  -10.8     2766400        1350      0.05         200           159      79.50       9.21
  -11.0     2766400        3475      0.13         200           186      93.00       9.19
  -11.2     2766400        8502      0.31         200           198      99.00       9.49
  -11.4     2766400       19039      0.69         200           200     100.00       9.79
  -11.6     2766400       39436      1.43         200           200     100.00       9.54
   -9.8     2766400           3      0.00         200             3       1.50       9.44
   -9.6     2766400           1      0.00         200             1       0.50       9.62
   -9.4     2766400           0      0.00         200             0       0.00       9.27
   -9.2     2766400           0      0.00         200             0       0.00       9.24
../../../../_images/source_Playground_Notebooks_PDSCH_PDSCH-BLER_2_1.png
[ ]: