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      51.98
   -5.0     2766400           0      0.00         200             0       0.00      51.49
  -10.0     2766400           0      0.00         200             0       0.00      51.57
  -15.0     2766400      447996     16.19         200           200     100.00      51.67
  -12.4     2766400        3840      0.14         200           189      94.50      51.34
  -12.6     2766400        9633      0.35         200           199      99.50      50.98
  -12.8     2766400       22497      0.81         200           200     100.00      51.51
  -13.0     2766400       46315      1.67         200           200     100.00      50.99
  -12.2     2766400        1455      0.05         200           164      82.00      50.97
  -12.0     2766400         435      0.02         200           111      55.50      50.76
  -11.8     2766400         107      0.00         200            53      26.50      50.86
  -11.6     2766400          19      0.00         200            14       7.00      50.66
  -11.4     2766400           1      0.00         200             1       0.50      50.85
  -11.2     2766400           0      0.00         200             0       0.00      50.88
  -11.0     2766400           0      0.00         200             0       0.00      50.85

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      50.81
   -5.0     2766400           0      0.00         200             0       0.00      51.16
  -10.0     2766400           7      0.00         200             5       2.50      50.58
  -10.2     2766400          23      0.00         200            14       7.00      51.12
  -10.4     2766400         105      0.00         200            49      24.50      51.40
  -10.6     2766400         432      0.02         200           110      55.00      50.92
  -10.8     2766400        1350      0.05         200           159      79.50      51.08
  -11.0     2766400        3475      0.13         200           186      93.00      50.83
  -11.2     2766400        8502      0.31         200           198      99.00      50.86
  -11.4     2766400       19039      0.69         200           200     100.00      50.79
  -11.6     2766400       39436      1.43         200           200     100.00      51.23
   -9.8     2766400           3      0.00         200             3       1.50      50.60
   -9.6     2766400           1      0.00         200             1       0.50      50.57
   -9.4     2766400           0      0.00         200             0       0.00      50.52
   -9.2     2766400           0      0.00         200             0       0.00      50.75
../../../../_images/source_Playground_Notebooks_PDSCH_PDSCH-BLER_2_1.png
[ ]: