NR PDSCH Throughput with HARQ
This notebook demonstrates how to measure the physical downlink shared channel (PDSCH) throughput of a 5G New Radio (NR) link, as specified in the 3GPP NR standard. It is similar to the MATLAB example NR PDSCH Throughput and showcases the use of the following NeoRadium features:
Carrier and Bandwidth Part classes.
PDSCH and DMRS objects.
LDPC and HARQ capabilities.
CDL channel model and extraction of channel matrix and precoding matrix.
Resource grids, populating them, and applying channel models to them.
[1]:
import numpy as np
import scipy.io
import time
import matplotlib.pyplot as plt
from neoradium import Carrier, PDSCH, CdlChannel, AntennaPanel, random, SnrScheduler
[2]:
# Create a Carrier object with 52 resource blocks and 30KHz subcarrier spacing
carrier = Carrier(numRbs=52, spacing=30)
carrier.print()
bwp = carrier.curBwp # The only bandwidth part in the carrier
Carrier Properties:
Cell Id: 1
Bandwidth Parts: 1
Active BWP: 0
Bandwidth Part 0:
Resource Blocks: 52 RBs starting at 0 (624 subcarriers)
Subcarrier Spacing: 30 kHz
CP Type: normal
Interleaving: No
Bandwidth: 18.72 MHz
symbolsPerSlot: 14
slotsPerSubFrame: 2
nFFT: 1024
frameNo: 0
slotNo: 0
[3]:
# Initialize the modulation and code rate.
modulation = '16QAM'
coderate = 490/1024
# Create a PDSCH onject with 2 tramsmission layers
pdsch = PDSCH(bwp, numLayers=2)
pdsch.setDMRS(configType=2, additionalPos=2)
pdsch.print()
# Creating a HarqEntity object. (It also creates an LdpcCodec object internally):
harq = pdsch.getHarq(coderates = coderate, # Coderate(s) used by HARQ's internal LDPC codec
harqType = "IR", # "IR" -> "Incremental Redundancy", "CC" -> "Chase Combining"
numProc = 16) # Number of HARQ processes
harq.print()
# Create the CDL channel model
channel = CdlChannel(bwp, 'C', delaySpread=300, carrierFreq=4e9, dopplerShift=5,
txAntenna = AntennaPanel([1,4], polarization="x"), # 8 TX antennas
rxAntenna = AntennaPanel([1,1], polarization="+", beamWidth=[75,360]), # 2 RX antennas, Omni-directional
rxOrientation = [0,0,0]) # Default is [180,0,0]
print(channel)
PDSCH Properties:
mappingType: A
nID: 1
rnti: 1
numLayers: 2
numCodewords: 1
modulation: 16QAM
PRG Size: Wideband
portSet: 0 1
symSet: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
prbSet: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47 48 49 50 51
DMRS:
configType: 2
nIDs: []
scID: 0
sameSeq: True
symbols: Single
typeA1stPos: 2
additionalPos: 2
cdmGroups (port:cdm): 0:0 1:0
deltaShifts (port:cdm): 0:0 1:0
numCdmGroupsWithoutData: 1
symSet: 2 7 11
REs (before shift): 0 1 6 7
epreRatioDb: 0 (dB)
HARQ Entity Properties:
HARQ Type: IR
Num. Processes: 16
Num. Codewords: 1
RV sequence: [0, 2, 3, 1]
maxTries: 4
LDPC codec:
Num layers: 2
Num codewords: 1
numIter: 5
nRef: 0
Modulation: 16QAM
Coderate: 490/1024
TBS: 31240
numLayers: 2
Base Graph: 1
Code Block Size: 8448
Num Code Blocks: 4
Lifting Size: 384
CDL-C Channel Properties:
carrierFreq: 4 GHz
normalizeGains: True
normalizeOutput: True
txDir: Downlink
filterLen: 16 samples
delayQuantSize: 64
stopBandAtten: 80 dB
dopplerShift: 5 Hz
coherenceTime: 84.628 milliseconds
delaySpread: 300 ns
ueDirAZ: 0°, 90°
xPolPower: 7.00 dB
angleSpreads: 2° 15° 3° 7°
TX Antenna:
Total Elements: 8
spacing: 0.5𝜆, 0.5𝜆
shape: 1 rows x 4 columns
polarization: x
RX Antenna:
Total Elements: 2
spacing: 0.5𝜆, 0.5𝜆
shape: 1 rows x 1 columns
polarization: +
hasLOS: False
NLOS Paths (24):
Delays (ns): 0.000 62.97 66.57 69.87 65.28 190.9 193.4 196.8 197.5 238.0 246.3 280.0
368.5 392.4 651.1 813.1 1277. 1380. 1647. 1682. 1891. 1991. 2112. 2595.
Powers (dB): -4.40 -1.20 -3.50 -5.20 -2.50 0.000 -2.20 -3.90 -7.40 -7.10 -10.7 -11.1
-5.10 -6.80 -8.70 -13.2 -13.9 -13.9 -15.8 -17.1 -16.0 -15.7 -21.6 -22.8
AODs (Deg): -47 -23 -23 -23 -41 0 0 0 73 -64 80 -97
-55 -64 -78 103 99 89 -102 92 93 107 120 -124
AOAs (Deg): -101 120 120 120 -128 170 170 170 55 66 -48 47
68 -69 82 31 -16 4 -14 10 6 1 -22 34
ZODs (Deg): 97 99 99 99 101 99 99 99 105 95 106 94
104 104 93 104 95 93 92 107 93 93 105 108
ZOAs (Deg): 88 72 72 72 70 75 75 75 67 64 71 60
91 60 61 101 62 67 53 62 52 62 58 57
[4]:
# Print header lines:
print("SNR(dB) Tx Bits Rx Bits Throughput(%) TX Blocks RX Blocks BLER(%) Avg. Retransmissions time(Sec.)")
print("------- ---------- ---------- ------------- --------- --------- ------- -------------------- ----------")
txBlockSizes = pdsch.getTxBlockSize(coderate) # Calculate the Transport Block Size (TBS)
snrScheduler = SnrScheduler(0, 1, loSnrVal=0, hiSnrVal=100, fastStep=5) # Start at 0 dB, use increments of 1 dB
numSlots = 1000 # The number of slots transmitted for each SNR value
for snrDb in snrScheduler:
random.setSeed(123)
channel.restart() # Reset the channel and the bandwidth part associated with it
harq.reset() # Reset HARQ state and buffers
t0 = time.monotonic() # Start the timer
for s in range(numSlots): # The inner loop doing 'numSlot' transmissions
pdsch.initGrid() # Create and initialize PDSCH's internal grid
numBits = pdsch.getBitCapacity() # Total number of PDSCH data bits available in the resource grid
# Preparing the transport blocks
txBlocks = [] # Transport blocks, one per codeword.
for c in range(harq.numCW):
if harq.needNewData[c]: # New transmission.
txBlocks += [ random.bits(txBlockSizes[c]) ] # Create random bits for new transmissions
else: # Retransmission
txBlocks += [ None ] # Set transport block to None to indicate a retransmission
# The following function returns a coded, rate-matched bitstream, ready for transmission/retransmission
rateMatchedCodeBlocks = harq.encode(txBlocks, numBits)
pdsch.setPdschData(rateMatchedCodeBlocks) # Map/modulate the data to the resource grid
channelMatrix = channel.getChannelMatrix() # Get channel matrix (Assuming perfect channel estimation)
precoder = pdsch.getPrecodingMatrix(channelMatrix) # Get precoding matrix based on the channel matrix
txGrid = bwp.createGrid(len(channel.txAntenna)) # Create the transmitted resource grid
pdsch.precodeTo(txGrid, precoder) # Perform the precoding
rxGrid = txGrid.applyChannel(channelMatrix) # Apply the channel to the precoded resource (Freq. domain)
noisyRxGrid = rxGrid.addNoise(snrDb=snrDb) # Add noise
# Calculate the effective channel matrix with the precoding effect. This is assuming perfect channel
# knowledge. For practical channel estimation we can use the DMRS reference signals and use the
# "estimateChannel" method of the PDSCH object.
effChannelMatrix = channel.getEffChannel(channelMatrix, precoder)
# Use the effective channel matrix to equalize the received resource grid
eqGrid, llrScales = pdsch.equalize(noisyRxGrid, effChannelMatrix)
# Demodulate the equalized resource grid (eqGrid) to get the Log-Likelihood values
llrs = pdsch.getLLRs(eqGrid, llrScales)
# Use HARQ entity to decode the LLRs to transport blocks
decodedTxBlocks, crcMatches = harq.decode(llrs)
# Get the statistics from HARQ entity and print them:
print(f"{snrDb:^7.2f} {harq.totalTxBits:^10d} {harq.totalRxBits:^10d} {harq.throughput:^13.2f} "
f"{harq.totalTxBlocks:^9} {harq.totalRxBlocks:^9d} {harq.bler:^7.2f} {harq.meanRetransmissions:^20.3f} "
f"{time.monotonic()-t0:^-10.3f}", end="\r")
channel.goNext()
harq.goNext()
snrScheduler.setData(harq.throughput)
print("")
SNR(dB) Tx Bits Rx Bits Throughput(%) TX Blocks RX Blocks BLER(%) Avg. Retransmissions time(Sec.)
------- ---------- ---------- ------------- --------- --------- ------- -------------------- ----------
0.00 31240000 31240000 100.00 1000 1000 0.00 0.000 442.852
-5.00 31240000 27834840 89.10 1000 891 10.90 0.122 429.116
-6.00 31240000 26210360 83.90 1000 839 16.10 0.192 420.398
-7.00 31240000 24960760 79.90 1000 799 20.10 0.252 416.080
-8.00 31240000 22930160 73.40 1000 734 26.60 0.362 406.844
-9.00 31240000 15776200 50.50 1000 505 49.50 0.962 370.360
-10.00 31240000 14901480 47.70 1000 477 52.30 1.086 367.244
-11.00 31240000 13933040 44.60 1000 446 55.40 1.224 361.609
-12.00 31240000 12714680 40.70 1000 407 59.30 1.432 357.607
-13.00 31240000 9684400 31.00 1000 310 69.00 2.015 343.787
-14.00 31240000 6841560 21.90 1000 219 78.10 2.413 340.201
-15.00 31240000 4592280 14.70 1000 147 85.30 2.877 334.571
-16.00 31240000 2592920 8.30 1000 83 91.70 3.000 334.723
-17.00 31240000 0 0.00 1000 0 100.00 3.000 335.363
-18.00 31240000 0 0.00 1000 0 100.00 3.000 333.704
-4.00 31240000 29084440 93.10 1000 931 6.90 0.074 437.282
-3.00 31240000 30677680 98.20 1000 982 1.80 0.018 445.951
-2.00 31240000 31240000 100.00 1000 1000 0.00 0.000 448.183
-1.00 31240000 31240000 100.00 1000 1000 0.00 0.000 447.097
[5]:
# Draw the throughput graph
snrDbs, throughputs = snrScheduler.getSnrsAndData()
plt.figure(figsize=(12, 6))
plt.plot(snrDbs, throughputs)
plt.title("PDSCH throughput at different SNR values");
plt.grid()
plt.xlabel("SNR (dB)")
plt.xticks(snrDbs)
plt.ylabel("Throughput (%)")
plt.show()
[ ]: