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()
[ ]: