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.

[2]:
import numpy as np
import scipy.io
import time
import matplotlib.pyplot as plt

from neoradium import Carrier, PDSCH, CdlChannel, AntennaPanel, LdpcEncoder, random, HarqEntity, SnrScheduler
[3]:
# Create a Carrier object with 51 resource blocks and 30KHz subcarrier spacing
carrier = Carrier(numRbs=51, 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:    51 RBs starting at 0 (612 subcarriers)
    Subcarrier Spacing: 30 kHz
    CP Type:            normal
    Bandwidth:          18.36 MHz
    symbolsPerSlot:     14
    slotsPerSubFrame:   2
    nFFT:               1024
    frameNo:            0
    slotNo:             0

[4]:
# Initialize the modulation and code rate.
modulation = '16QAM'
codeRate = 490/1024

# Create a PDSCH onject with 2 tramsmission layers
pdsch = PDSCH(bwp, interleavingBundleSize=0, numLayers=2, nID=carrier.cellId)
pdsch.setDMRS(prgSize=0, configType=2, additionalPos=2, otherCdmGroups=[])
pdsch.print()

# Create an LDPC encoder object using base graph 1 and passing modulation and code rate settings
ldpcEncoder = LdpcEncoder(baseGraphNo=1,  modulation=modulation, txLayers=pdsch.numLayers, targetRate=codeRate)

# HARQ configuration:
harqType = "IR"                                     # "IR" -> "Incremental Redundancy", "CC" -> "Chase Combining"
numProc = 16                                        # Number of HARQ processes
harq = HarqEntity(ldpcEncoder, harqType, numProc)   # Create the HARQ entity
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 antenna
                     rxAntenna = AntennaPanel([1,1], polarization="+", beamWidth=[75,360]),     # 2 RX antenna, 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
  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
  interleavingBundleSize: 0
  PRG Size: Wideband
  Bandwidth Part:
    Resource Blocks:    51 RBs starting at 0 (612 subcarriers)
    Subcarrier Spacing: 30 kHz
    CP Type:            normal
    Bandwidth:          18.36 MHz
    symbolsPerSlot:     14
    slotsPerSubFrame:   2
    nFFT:               1024
    frameNo:            0
    slotNo:             0
  DMRS:
    configType: 2
    nIDs: []
    scID: 0
    sameSeq: 1
    symbols: Single
    typeA1stPos: 2
    additionalPos: 2
    cdmGroups: [0, 0]
    deltaShifts: [0, 0]
    allCdmGroups: [0]
    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
  Encoder:
    Base Graph:         1
    Modulation:         16QAM
    Number of layers:   2
    Target Rate:        0.478515625
  Decoder:
    Base Graph:         1
    Modulation:         16QAM
    Number of layers:   2


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.0°, 90.0°
  Cross Pol. Power:     7 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  119  -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

[5]:
# Print header lines:
print("SNR(dB)  Tx Bits     Rx Bits     Throughput(%)  TX Blocks  RX Blocks  BLER(%)  Retry Mean  time(Sec.)")
print("-------  ----------  ----------  -------------  ---------  ---------  -------  ----------  ----------")

snrScheduler = SnrScheduler(6,1, loSnrVal=0, hiSnrVal=100)      # Start at 6 dB, use increments of 0.2 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.time()                                            # Start the timer
    for s in range(numSlots):                                   # The inner loop doing 'numSlot' transmissions
        grid = bwp.createGrid(pdsch.numLayers)                  # Create a resource grid with 2 layers
        pdsch.allocateResources(grid)                           # Allocat PDSCH resources including DMRS
        txBlockSizes = pdsch.getTxBlockSize(codeRate)           # Calculate the Transport Block Size (TBS)
        numBits = pdsch.getBitSizes(grid)                       # 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.getRateMatchedCodeBlocks(txBlocks, numBits)

        pdsch.populateGrid(grid, 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

        precodedGrid = grid.precode(precoder)                   # Perform precoding

        rxGrid = precodedGrid.applyChannel(channelMatrix)       # Apply the channel to the precoded resource (Freq. domain)
        noisyRxGrid = rxGrid.addNoise(snrDb=snrDb, useRxPower=True) # Add noise (Using RX power to calcualte Noise Power)

        # Calculate the channel matrix with the precoding effect. This is assuming perfect channel estimation. For practical
        # channel estimation we can use the DMRS reference signals and use the "estimateChannelLS" method of resource grid object.
        precodingChannelMatrix = channelMatrix @ precoder[None,...]

        # Use the channel matrix (with precoding effect) to equalize the received resource grid
        eqGrid, llrScales = noisyRxGrid.equalize(precodingChannelMatrix)

        # Demodulate the equalized resource grid (eqGrid) to get the Log-Likelihood values
        llrs = pdsch.getLLRsFromGrid(eqGrid, pdsch.dataIndices, llrScales)

        # Use HARQ entity to decode the LLRs to transport blocks
        decodedTxBlocks, blockErrors = harq.decodeLLRs(llrs, txBlockSizes, numIter=2)

        # Get the statistics from HARQ entity and print them:
        print("\r%-7d  %-10d  %-10d  %-13.2f  %-9d  %-9d  %-7.2f  %-10.2f  %-10.2f"
              %(snrDb, harq.totalTxBits, harq.totalRxBits, harq.throughput, harq.totalTxBlocks,
                harq.totalRxBlocks, harq.bler, harq.meanTries, time.time()-t0), end='')

        channel.goNext()
        harq.goNext()

    snrScheduler.setData(harq.throughput)
    print("")
SNR(dB)  Tx Bits     Rx Bits     Throughput(%)  TX Blocks  RX Blocks  BLER(%)  Retry Mean  time(Sec.)
-------  ----------  ----------  -------------  ---------  ---------  -------  ----------  ----------
6        30216000    3263328     10.80          1000       108        89.20    3.54        158.70
5        30216000    423024      1.40           1000       14         98.60    3.94        158.07
4        30216000    0           0.00           1000       0          100.00   4.00        159.37
3        30216000    0           0.00           1000       0          100.00   4.00        159.64
7        30216000    5166936     17.10          1000       171        82.90    3.03        161.71
8        30216000    6738168     22.30          1000       223        77.70    2.61        167.97
9        30216000    8218752     27.20          1000       272        72.80    2.19        173.80
10       30216000    10756896    35.60          1000       356        64.40    1.62        178.26
11       30216000    12751152    42.20          1000       422        57.80    1.33        184.86
12       30216000    13566984    44.90          1000       449        55.10    1.20        188.20
13       30216000    15198648    50.30          1000       503        49.70    0.98        194.23
14       30216000    18099384    59.90          1000       599        40.10    0.67        204.43
15       30216000    22208760    73.50          1000       735        26.50    0.36        222.47
16       30216000    24777120    82.00          1000       820        18.00    0.22        231.26
17       30216000    26499432    87.70          1000       877        12.30    0.14        238.67
18       30216000    27345480    90.50          1000       905        9.50     0.10        241.45
19       30216000    28644768    94.80          1000       948        5.20     0.05        246.40
20       30216000    29792976    98.60          1000       986        1.40     0.01        251.06
21       30216000    30095136    99.60          1000       996        0.40     0.00        252.46
22       30216000    30216000    100.00         1000       1000       0.00     0.00        252.83
23       30216000    30216000    100.00         1000       1000       0.00     0.00        252.05
[6]:
# 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()
../../../../_images/source_Playground_Notebooks_PDSCH_PDSCH-Throughput_5_0.png