10. MIMO-OFDM in Spatial Multiplexing Mode in 5G Networks

In this project we will demonstrate the transmission and reception of the data from base-station (BS) using low cost SDRs. The whole simulation consists of two parts:

Transmitter side:

  1. Generate the SSB block

  2. Generate the transport block ==> Process it with PDSCH chain ==> Create the slot resource grids

  3. Generate the resource grid and load SSB + PDSCH both into it.

  4. Pass the resource grid through the OFDM modulator: Generate the time domain I/Q samples.

  5. Pass it to the SDR and radiate the signal to the medium.

Receiver side:

  1. Sample the medium to receive the samples and store thm in the buffer.

  2. Downlink synchronization

    • This is performed using synchronization signal block (SSB).

    • Helps with

      • time/frame synchronization.

      • Coarse CFO correction.

      • Cell ID detection.

      • Decoding MIB information.

  3. Data transmission and decoding

SSB Transmitter

10. Import Python Libraries

[1]:
# %matplotlib widget

import os
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.animation as animation

import numpy as np
import adi

# from IPython.display import display, HTML
# display(HTML("<style>.container { width:98% !important; }</style>"))

10. 5G Toolkit Libraries

[2]:
import sys
sys.path.append("../../../../")

from toolkit5G.SequenceGeneration import PSS, SSS, DMRS
from toolkit5G.PhysicalChannels   import PBCH
from toolkit5G.ResourceMapping    import SSB_Grid, ResourceMapperSSB
from toolkit5G.OFDM               import OFDMModulator
from toolkit5G.MIMOProcessing     import AnalogBeamforming
from toolkit5G.Configurations     import TimeFrequency5GParameters, GenerateValidSSBParameters

from toolkit5G.SequenceGeneration import PSS, SSS, DMRS
from toolkit5G.PhysicalChannels   import PBCH, PBCHDecoder
from toolkit5G.ResourceMapping    import SSB_Grid, ResourceMapperSSB
from toolkit5G.OFDM               import OFDMModulator, OFDMDemodulator
from toolkit5G.MIMOProcessing     import AnalogBeamforming, ReceiveCombining
from toolkit5G.ReceiverAlgorithms import PSSDetection, SSSDetection, ChannelEstimationAndEqualization, DMRSParameterDetection
from toolkit5G.Configurations     import TimeFrequency5GParameters, GenerateValidSSBParameters


from toolkit5G.PhysicalChannels.PDSCH import ComputeTransportBlockSize
from toolkit5G.PhysicalChannels       import PDSCHLowerPhy, PDSCHUpperPhy, PDSCHDecoderLowerPhy, PDSCHDecoderUpperPhy
from toolkit5G.Configurations         import PDSCHLowerPhyConfiguration, PDSCHUpperPhyConfiguration
from toolkit5G.SymbolMapping          import Mapper, Demapper
from toolkit5G.Scrambler              import DeScrambler, Scrambler
from toolkit5G.SymbolMapping          import Demapper
from toolkit5G.PhysicalChannels.PDSCH import LayerDemapper

from toolkit5G.PhysicalChannels   import PBCHDecoder
from toolkit5G.OFDM               import OFDMDemodulator
from toolkit5G.ReceiverAlgorithms import PSSDetection, SSSDetection, ChannelEstimationAndEqualizationPBCH, DMRSParameterDetection, CarrierFrequencyOffsetEstimation, ChannelEstimationAndEqualizationPDSCH

from toolkit5G.PhysicalChannels       import PDSCHLowerPhy, PDSCHUpperPhy, PDSCHDecoderLowerPhy, PDSCHDecoderUpperPhy

10. Emulation Parameters

[3]:
# Carrier Frequency
carrierFrequency = 1*10**9
numBatches       = 9        # Number of batches considered for simulation
scs              = 30*10**3   # Subcarrier Spacing for simulation
numBSs           = 1          # Number of BSs considered for simulation
bandwidth        = 30*10**6

# Number of UEs considered for simulation
numUEs           = numBatches # For now we are assuming that the numbatches are captured via numUEs
numRB            = 85         # Number of Resource mapping considered for simulation | # 1 RB = 12 subcarrier
slotNumber       = int(np.random.randint(0,2**(scs/15000)*10)) # Index of the slot considered for simulation
Nfft             = 1024       # FFTSize

Nt               = 1

enablePrecoding  = False
enableCombining  = False


print("************ Simulation Parameters *************")
print()
print("     numBatches: "+str(numBatches))
print("          numRB: "+str(numRB))
print("       fft Size: "+str(Nfft))
print("         numBSs: "+str(numBSs))
print("         numUEs: "+str(numUEs))
print("            scs: "+str(scs))
print("     slotNumber: "+str(slotNumber))
print("Num of Antennas: "+str(Nt))
print()
print("********************************************")
************ Simulation Parameters *************

     numBatches: 9
          numRB: 85
       fft Size: 1024
         numBSs: 1
         numUEs: 9
            scs: 30000
     slotNumber: 3
Num of Antennas: 1

********************************************

10. PDSCH Transmitter Implementation

PDSCH Transmitter

10. Generate the PDSCH Resource Grid

[5]:

pdschUpperPhy = PDSCHUpperPhy(symbolsPerSlot = numSymbols, numRB = numRB, mcsIndex = mcsIndex, numlayers = numlayers, scalingField = scalingField, additionalOverhead = additionalOverhead, dmrsREs = dmrsREs, numTBs=numTBs, pdschTable = mcsTable, verbose = False) codeword = pdschUpperPhy(tblock = [None, None], rvid = [0, 0], enableLBRM = [False, False], numBatch = numBatches, numBSs = numBSs) rnti = np.random.randint(65536, size=numBSs*numBatches) nID = np.random.randint(1024, size=numBSs*numBatches) bits2 = codeword[1] if numTBs == 2 else None pdschLowerPhyChain = PDSCHLowerPhy(pdschMappingType, configurationType, dmrsTypeAPosition, maxLength, dmrsAdditionalPosition, l0, ld, l1) resourceGrid = pdschLowerPhyChain(codeword[0], numRB, rank, slotNumber, scramblingID, nSCID, rnti, nID, modOrder, startSymbol, bits2 = bits2) ## Load the resource Grid into the transmision Grid txGrid = np.zeros(resourceGrid.shape[0:-1]+(Nfft,), dtype= np.complex64) bwpOffset = np.int32(0.5*(Nfft-numRB*12)) txGrid[...,bwpOffset:bwpOffset+numRB*12] = np.sqrt(1/rank)*resourceGrid txGrid = txGrid/(np.linalg.norm(np.abs(txGrid), axis=-1)[...,np.newaxis] + 0.0001) pdschLowerPhyChain.displayDMRSGrid(), pdschLowerPhyChain.displayResourceGrid()

../../../../_images/api_Content_Codes_Tutorial-10%5BDL-MIMO%5D_10.MIMO-OFDM_Spatial_Multiplexing_in_5G_Networks_9_1.png
[5]:
((<Figure size 640x480 with 2 Axes>,
  array([<Axes: title={'center': 'Port number: 0'}, xlabel='OFDM Symbol-Index', ylabel='Subcarrier-Index'>,
         <Axes: title={'center': 'Port number: 1'}, xlabel='OFDM Symbol-Index', ylabel='Subcarrier-Index'>],
        dtype=object)),
 None)
../../../../_images/api_Content_Codes_Tutorial-10%5BDL-MIMO%5D_10.MIMO-OFDM_Spatial_Multiplexing_in_5G_Networks_9_3.png
[6]:
txGrid.shape
[6]:
(9, 1, 2, 14, 1024)
[7]:
# Plot Resource Grid
#################################################################
fig, ax = plt.subplots()
plt.imshow(np.abs(txGrid[0,0,0]), cmap = 'hot', interpolation='nearest', aspect = "auto")
ax = plt.gca();
ax.grid(color='c', linestyle='-', linewidth=1)
ax.set_xlabel("Subcarrier-Index (k)")
ax.set_ylabel("OFDM Symbol Index (n)")
ax.set_title("Heat map of Transmit Grid")
# Gridlines based on minor ticks
plt.show()
../../../../_images/api_Content_Codes_Tutorial-10%5BDL-MIMO%5D_10.MIMO-OFDM_Spatial_Multiplexing_in_5G_Networks_11_0.png

10. SSB Transmitter Implementation

SSB Transmitter

10. Generate the SSB Resource Grid

[8]:
## This class fetches valid set of 5G parameters for the system configurations
nSymbolFrame   = 14
numOFDMSymbols = 14
tfParams    = TimeFrequency5GParameters(bandwidth, scs)
tfParams(nSymbolFrame, typeCP = "normal")
nRB         = tfParams.numRBs        # SSB Grid size (Number of RBs considered for SSB transition)
Neff        = nRB*12
lengthCP    = (tfParams.lengthCP).astype(np.int32)    # CP length
#___________________________________________________________________

#### Generate MIB Information
lamda                           = 3e8/carrierFrequency
nSCSOffset                      = 1
ssbParameters                   = GenerateValidSSBParameters(carrierFrequency, nSCSOffset, "caseA")

systemFrameNumber               = ssbParameters.systemFrameNumber
subCarrierSpacingCommon         = scs
ssbSubCarrierOffset             = ssbParameters.ssbSubCarrierOffset
DMRSTypeAPosition               = ssbParameters.DMRSTypeAPosition
controlResourceSet0             = ssbParameters.controlResourceSet0
searchSpace0                    = ssbParameters.searchSpace0

isPairedBand                    = ssbParameters.isPairedBand
nSCSOffset                      = ssbParameters.nSCSOffset
choiceBit                       = ssbParameters.choiceBit
ssbType                         = ssbParameters.ssbType
nssbCandidatesInHrf             = ssbParameters.nssbCandidatesInHrf
ssbIndex                        = ssbParameters.ssbIndex
hrfBit                          = ssbParameters.hrfBit
cellBarred                      = ssbParameters.cellBarred
intraFrequencyReselection       = ssbParameters.intraFrequencyReselection
withSharedSpectrumChannelAccess = ssbParameters.withSharedSpectrumChannelAccess

Nsc_ssb                         = 240
Nsymb_ssb                       = 4
#___________________________________________________________________________


N_ID2        = np.random.randint(3)

# Generate PSS sequence
pssObject    = PSS(N_ID2);
pssSequence  = pssObject()

N_ID1        = np.random.randint(336)
N_ID         = 3*N_ID1 + N_ID2

# Generate SSS sequence
sssObject    = SSS(N_ID1, N_ID2);
sssSequence  = sssObject()

# Generate DMRS sequence
dmrsLen      = 144;
dmrsObject   = DMRS("PBCH", N_ID, ssbIndex, nssbCandidatesInHrf, hrfBit)
# dmrsSeq = dmrs.getSequence("tensorflow")
dmrsSequence = dmrsObject(dmrsLen)


# Generate PBCH symbols
pbchObject   = PBCH(carrierFrequency, choiceBit, subCarrierSpacingCommon, DMRSTypeAPosition,
                   controlResourceSet0, searchSpace0, cellBarred, intraFrequencyReselection,
                   systemFrameNumber, ssbSubCarrierOffset, hrfBit, ssbIndex, N_ID,
                   nssbCandidatesInHrf)

pbchSymbols  = pbchObject()

## Generate SSB Object
ssbObject    = SSB_Grid(N_ID, True)
ssb          = ssbObject(pssSequence, sssSequence, dmrsSequence, pbchSymbols)  # generating SSB using PSS,SSS, PBCH payload and DMRS.

## Loading SSB to Resource Grid
ssbPositionInBurst    = np.zeros(nssbCandidatesInHrf, dtype=int)
ssbPositionInBurst[0] = 1

ssbRGobject    = ResourceMapperSSB(ssbType, carrierFrequency, isPairedBand, withSharedSpectrumChannelAccess)

offsetInRBs    = int((nRB-20)/2)
ssbGrid        = ssbRGobject(ssb[0], ssbPositionInBurst, offsetInSubcarriers = ssbSubCarrierOffset,
                      offsetInRBs = offsetInRBs, numRBs = nRB)[0:14]         # SSB Grid of size 14 X numbers of RBs x 12.
fig, ax = ssbObject.displayGrid(option=1)


firstSymbolIndex   = int(2)
numofGuardCarriers = (int((Nfft - Neff)/2), int((Nfft - Neff)/2))
offsetToPointA     = 0
firstSCIndex       = int(numofGuardCarriers[0] + offsetToPointA)

ssbResGrid = np.zeros((1, numOFDMSymbols, Nfft), dtype= np.complex64)
ssbResGrid[..., firstSCIndex:firstSCIndex+ssbGrid.shape[-1]] = ssbGrid

#__________________________________________________

../../../../_images/api_Content_Codes_Tutorial-10%5BDL-MIMO%5D_10.MIMO-OFDM_Spatial_Multiplexing_in_5G_Networks_13_0.png

10. Precoding and Beamforming Architecture

MIMO Architecture
[9]:
## Concatenate the SSB and PDSCH Grid
X                 = np.concatenate([ssbResGrid.repeat(2,0),
                                    txGrid[:,0].transpose(1,0,2,3).reshape(rank,-1,Nfft)], axis=1)

## OFDM Modulation at Transmitter
#####################################
modulator = OFDMModulator(lengthCP[1])
x_time    = modulator(X)
#______________________________________________________

# Plot Resource Grid
#################################################################
fig, ax = plt.subplots()
plt.imshow(np.abs(X[1]).T, cmap = 'hot', interpolation='nearest', aspect = "auto")
ax = plt.gca();
ax.grid(color='c', linestyle='-', linewidth=1)
ax.set_xlabel("Subcarrier-Index (k)")
ax.set_ylabel("OFDM Symbol Index (n)")
ax.set_title("Heat map of Transmit Grid")
# Gridlines based on minor ticks
plt.show()
../../../../_images/api_Content_Codes_Tutorial-10%5BDL-MIMO%5D_10.MIMO-OFDM_Spatial_Multiplexing_in_5G_Networks_15_0.png

10. SDR-Setup Configurations

[10]:
## SDR Parameters
sample_rate         = Nfft*scs

# Pulse Shaping
numSamplesPerSymbol = 1

# number of samples returned per call to rx()
buffer_size         = int(Nfft*1.2*numSamplesPerSymbol*112)

# Basic SDR Setup
sdr = adi.ad9361("ip:192.168.2.1")
sdr.tx_enabled_channels   = [0,1] # Enable 2 transmit chains
sdr.rx_enabled_channels   = [0,1]
sdr.sample_rate           = int(sample_rate)

# Config Tx
sdr.tx_rf_bandwidth       = int(sample_rate) # filter cutoff, just set it to the same as sample rate
sdr.tx_lo                 = int(carrierFrequency)
sdr.tx_hardwaregain_chan0 = 0 # Increase to increase tx power, valid range is -90 to 0 dB

# # Config Rx
# sdr.gain_control_mode_chan0 = 'manual'
# sdr.rx_hardwaregain_chan0   = 40.0      # dB
# # The receive gain on the Pluto has a range from 0 to 74.5 dB.

sdr.gain_control_mode_chan0 = 'slow_attack'
# # AGC modes:
#     # 1. "manual"
#     # 2. "slow_attack"
#     # 3. "fast_attack"

sdr.rx_lo           = int(carrierFrequency)
sdr.rx_rf_bandwidth = int(sample_rate) # filter width, just set it to the same as sample rate for now
sdr.rx_buffer_size  = int(4*buffer_size)

10. Transmission: SDR RF Transmitter

10. Precoding and Beamforming the OFDM signal

Precoding-and-Beamforming
[11]:
sdr.tx_destroy_buffer()

# Start the transmitter
sdr.tx_cyclic_buffer = True  # Enable cyclic buffers
scalingFactor = 1.9*2**16
numRepetition = 1

# sdr.tx([scalingFactor*(x_time[0] +    x_time[1]).repeat(numRepetition),  # Transmitting via-RF channel-1
#         scalingFactor*(x_time[0] - 1j*x_time[1]).repeat(numRepetition)]) # Transmitting via-RF channel-2

sdr.tx([scalingFactor*(x_time[0]).repeat(numRepetition),  # Transmitting via-RF channel-1
        scalingFactor*(x_time[1]).repeat(numRepetition)]) # Transmitting via-RF channel-2
[12]:
np.abs(scalingFactor*x_time.repeat(numRepetition)).max(), np.abs(scalingFactor*x_time.repeat(numRepetition)).min()
[12]:
(13793.594910929532, 0.0)

10. Receiver Implementation: SSB

Receiver_Implementation

10. Reception: SDR RF Receiver

[13]:

# Clear buffer just to be safe for i in range (0, 10): raw_data = sdr.rx() # Receive samples rx_samples = sdr.rx() # # # Stop transmitting # sdr.tx_destroy_buffer()

10. Time Synchronization: Based on PSS Correlation

[14]:
## PSS Detection: Based on time domain PSS Correlation
# pssPeakIndices, pssCorrelation, rN_ID2 = pssDetection(r, Nfft, lengthCP = lengthCP[1],
#                                                       N_ID2 = None, freqOffset = ssboffset,
#                                                       height = 0.75, prominence = 0.65, width=10)
## PSS Detection: Based on time domain PSS Correlation
# pssDetection   = PSSDetection("correlation", "threshold")
pssDetection   = PSSDetection("largestPeak")
ssboffset      = int((Nfft-Neff)/2+ssbRGobject.startingSubcarrierIndices)
pssPeakIndices, pssCorrelation, rN_ID2, freqOffset = pssDetection(rx_samples[1], Nfft, lengthCP = lengthCP[1],
                                                                  nID2 = None, freqOffset = ssboffset)

if(pssPeakIndices > rx_samples[1].size - 140*(Nfft + lengthCP[1])):
    pssPeakIndices = pssPeakIndices - 140*(Nfft + lengthCP[1])
## PSS Detection Plot
#################################################################
fig, ax = plt.subplots(figsize = (10.5, 4))

# single line
ax.plot(pssCorrelation)
ax.vlines(x = pssPeakIndices, ymin = 0*pssCorrelation[pssPeakIndices],
           ymax = pssCorrelation[pssPeakIndices], colors = 'purple')
ax.set_ylim([0,np.max(pssCorrelation)*1.1])
ax.set_xlabel("Time Samples Index")
ax.set_ylabel("Amplitude of Time Domain Correlation")
ax.set_title("Amplitude (of Time Domain Correlation) vs Time-samples")
plt.show()
#________________________________________________________________
**(rasterOffset, PSS-ID) (402, 0)
**(rasterOffset, PSS-ID) (402, 1)
**(rasterOffset, PSS-ID) (402, 2)
../../../../_images/api_Content_Codes_Tutorial-10%5BDL-MIMO%5D_10.MIMO-OFDM_Spatial_Multiplexing_in_5G_Networks_24_1.png

10. PBCH Receiver

  1. OFDM Demodulation

  2. SSS Dtection

  3. DMRS Parameters Detection

  4. Channel Estimation and PBCH Equalization

  5. PBCH Decoding and MIB Extraction

[15]:
## OFDM Demodulator Object
ofdmDemodulator = OFDMDemodulator(Nfft, lengthCP[1])
pssStartIndex   = pssPeakIndices
# pssStartIndex   = pssPeakIndices[0][0]
rxGrid          = ofdmDemodulator((rx_samples[0].reshape(1,-1))[...,pssStartIndex:(pssStartIndex+4*(Nfft+lengthCP[1]))])

ssbSCSoffset    = int((Nfft-Neff)/2+ssbRGobject.startingSubcarrierIndices)
ssbEstimate     = rxGrid[:,:,ssbSCSoffset:(ssbSCSoffset+240)]

nssbCandidatesInHrf = 4
dmrsLen             = 144
## N_ID_1 Estimation: SSS based
sssDetection   = SSSDetection(method="channelAssisted", nID2=rN_ID2)
rN_ID1         = sssDetection(ssbEstimate[0])
rN_ID          = 3*rN_ID1 + rN_ID2

## Generate SSB object to get DMRS and PBCH Indices
rxSSBobject    = SSB_Grid(rN_ID)
rxDMRSIndices  = rxSSBobject.dmrsIndices

## Generate DMRS sequence
dmrsDetection  = DMRSParameterDetection(int(rN_ID), nssbCandidatesInHrf)
rssbIndex, rHrfBit = dmrsDetection(ssbEstimate[0])
rxDMRSobject   = DMRS("PBCH", int(rN_ID), int(rssbIndex), nssbCandidatesInHrf, rHrfBit)
rxDMRSseq      = rxDMRSobject(dmrsLen)

# ## Estimating the channel at DMRS (t-f) location, interpolting for data (t-f) location and equalizing the symbols
# ## Object for Channel Estimation
# chanEst        = ChannelEstimationAndEqualization(estimatorType = "ZF", interpolatorType = "NN")
# rxPBCHIndices  = rxSSBobject.pbchIndices
# pbchEstimate   = chanEst(ssbEstimate, rxDMRSseq, rxDMRSIndices, rxPBCHIndices, 10)

chanEst        = ChannelEstimationAndEqualizationPBCH(estimatorType = "ZF", interpolatorType = "Linear", isUEmobile=True)
pbchEstimate   = chanEst(ssbEstimate, rxDMRSseq, rN_ID)

## PBCH Chain for Decoding information
polarDecoder    = "SCL"
symbolDemapper  = "maxlog"
# extractMIBinfo = False
extractMIBinfo  = True
# carrierFreq, cellID, nssbCandidatesInHrf, ssbIndex, polarDecType, symbolDemapperType
pbchDecoder     = PBCHDecoder(carrierFrequency, int(rN_ID), nssbCandidatesInHrf, rssbIndex, polarDecoder, symbolDemapper)
rxMIB, check    = pbchDecoder(pbchEstimate, 100, extractMIBinfo)
<frozen toolkit5G.ChannelCoder.PolarCoder.polarDecoder>:494: UserWarning: Required ressource allocation is large for the selected blocklength. Consider option `cpu_only=True`.

10. SSB Grid: Transmitter and Receiver

[16]:
# Plot SSB
fig, ax = plt.subplots(1,2, figsize = (9,3))
ax[0].imshow(np.abs(ssbEstimate[0]), cmap = 'hot', interpolation='nearest', aspect = "auto")
ax[0].grid(color='c', linestyle='-', linewidth=1)
ax[0].set_xlabel("Subcarrier-Index (k)")
ax[0].set_ylabel("Normalized Magnitude")

ax[1].imshow(np.abs(ssb[0]), cmap = 'hot', interpolation='nearest', aspect = "auto")
ax[1].grid(color='c', linestyle='-', linewidth=1)
ax[1].set_xlabel("Subcarrier-Index (k)")
ax[1].set_ylabel("Normalized Magnitude")
plt.title("Heat-map of Received and Transmitted SSB Grid")

plt.show()
../../../../_images/api_Content_Codes_Tutorial-10%5BDL-MIMO%5D_10.MIMO-OFDM_Spatial_Multiplexing_in_5G_Networks_28_0.png

10. Spectrum: Transmitted Grid and Received Grid

[17]:
# Plot SSB
fig, ax = plt.subplots(figsize = (9,5))
ax.plot(np.abs(rxGrid[0][0])/np.abs(rxGrid[0][0]).max())
ax.plot(np.abs(X[0,2])/np.abs(X[0,2]).max())
ax.grid(color='c', linestyle='-', linewidth=1)
ax.set_xlabel("Subcarrier-Index (k)")
ax.set_ylabel("Normalized Magnitude")
ax.set_title("Magnitude Spreactrum of Transmitted and Received $1^{st}$ SSB OFDM Symbol")
plt.show()
../../../../_images/api_Content_Codes_Tutorial-10%5BDL-MIMO%5D_10.MIMO-OFDM_Spatial_Multiplexing_in_5G_Networks_30_0.png

10. PBCH Decoding and Constellation

[18]:
fig, ax = plt.subplots()
ax.scatter(np.real(pbchEstimate), np.imag(pbchEstimate), s = 12, label = "Equalized Symbols")
ax.scatter(np.real(pbchSymbols), np.imag(pbchSymbols), s = 12, label = "Transmitted Symbols")
ax.grid()
ax.axhline(y=0, ls=":", c="k", lw = 2); ax.axvline(x=0, ls=":", c="k", lw = 2)
ax.set_xlim([-1,1]); ax.set_ylim([-1,1])
ax.set_xlabel("Real {x}"); ax.set_ylabel("Imag {x}")
ax.set_title("Constellation Diagram: QPSK")
ax.legend(loc = "best")
plt.show()
../../../../_images/api_Content_Codes_Tutorial-10%5BDL-MIMO%5D_10.MIMO-OFDM_Spatial_Multiplexing_in_5G_Networks_32_0.png
[19]:
pbchDecoder.mibRx.displayParameters(0)
Carrier Frequency:      1000000000
ChoiceBit:              0
nSsbCandidatesInHrf:    4
subCarrierSpacingCommon:30000
DMRSTypeAPosition:      typeA
controlResourceSet0:    14
searchSpace0:           0
cellBarred:             barred
intraFreqReselection:   allowed
systemFrameNumber:      803
ssbSubCarrierOffset:    10
HRFBit:                 1
iSSBindex:              0
[20]:
pbchObject.mib.displayParameters(0)
Carrier Frequency:      1000000000
ChoiceBit:              0
nSsbCandidatesInHrf:    4
subCarrierSpacingCommon:30000
DMRSTypeAPosition:      typeA
controlResourceSet0:    14
searchSpace0:           0
cellBarred:             barred
intraFreqReselection:   allowed
systemFrameNumber:      803
ssbSubCarrierOffset:    10
HRFBit:                 1
iSSBindex:              0

10. Performance Verification

[21]:
if (rN_ID == N_ID):
    print("[Success]: Cell-IDs correctly detected!")
else:
    if (rN_ID1 != N_ID1 and rN_ID2 != N_ID2):
        print("[Failed]: Receiver couldn't detect the Cell-ID1 and cell-ID2 correctly!")
    elif(rN_ID1 != N_ID1):
        print("[Failed]: Receiver couldn't detect the Cell-ID1 correctly!")
    else:
        print("[Failed]: Receiver couldn't detect the cell-ID2 correctly!")

if (rssbIndex == ssbIndex[0]):
    print("[Success]: DMRS parameters correctly detected!")
else:
    print("[Failed]: Receiver couldn't detect the ssbIndex correctly!")

## Computing BER: Coded and Uncoded
numUEs = 1
nBatch = 1
uncodedBER     = np.zeros((numUEs, nBatch))
codedBER       = np.zeros((numUEs, nBatch))

bitEst         = pbchDecoder.llr.copy()
bitEst[pbchDecoder.llr  > 0]   = 1
bitEst[pbchDecoder.llr  < 0]   = 0
uncodedBER = np.mean(np.abs(bitEst - pbchObject.scr2bits[0]))
codedBER   = np.mean(np.abs(pbchDecoder.pbchDeInterleavedBits - pbchObject.mibSequence[0]))

print(" (uncoded-BER, codedBER): "+str((uncodedBER, codedBER)))
[Success]: Cell-IDs correctly detected!
[Success]: DMRS parameters correctly detected!
 (uncoded-BER, codedBER): (0.0, 0.0)

10. PDSCH Receiver Implementation

10. Extract PDSCH Resource Grid

[22]:
## OFDM Demodulator Object
ofdmDemodulator = OFDMDemodulator(Nfft, lengthCP[1])
pdschStartIndex = pssStartIndex+12*(Nfft+lengthCP[1])
# pssStartIndex   = pssPeakIndices[0][0]
rxGrid          = ofdmDemodulator(np.array(rx_samples)[...,pdschStartIndex:(pdschStartIndex+14*numBatches*(Nfft+lengthCP[1]))])

pdschGrid       = rxGrid[:,:,bwpOffset:bwpOffset+numRB*12].reshape(rank,numBatches,14,-1).transpose(1,0,2,3)

# Plot SSB
fig, ax = plt.subplots()
plt.imshow(np.abs(pdschGrid[0,0]), cmap = 'hot', interpolation='nearest', aspect = "auto")
ax = plt.gca();
ax.grid(color='c', linestyle='-', linewidth=1)
ax.set_xlabel("Subcarrier-Index (k)")
ax.set_ylabel("Normalized Magnitude")
ax.set_title("Heat-map of Received and SSB Grid")
plt.show()
../../../../_images/api_Content_Codes_Tutorial-10%5BDL-MIMO%5D_10.MIMO-OFDM_Spatial_Multiplexing_in_5G_Networks_38_0.png

10. PDSCH Receiver

PDSCH Receiver
[23]:
# Channel Estimation and Equalization
chEst = ChannelEstimationAndEqualizationPDSCH(slotNumber, scramblingID, nSCID, rnti, nID,
                                              pdschMappingType, configurationType, dmrsTypeAPosition,
                                              maxLength, dmrsAdditionalPosition, l0, ld, l1,
                                              startSymbol, numTargetBits, modOrder)
interpolator = "linear" # Can be "nearest" | "linear" | "cubicspline"
polyOrder    = 3 # Required only for Spline, Will be ignored for other interpolators
rsymbols     = chEst(pdschGrid, interpolator)

#### Layer Demapping
layDemap     = LayerDemapper(numTBs, rank)
rlmSym       = layDemap(rsymbols)  # received symbols

######## Demapping.
demapMethod  = "app" # Demapping method
constType    = "qam"  # Symbol mapping type
modOrder     = modOrder       # Mordulation order or number of bits per symbol
snr          = 10
hard_out     = False
demapper     = Demapper(demapMethod, constType, modOrder, hard_out = hard_out)

## Computed Received bits
rxLLRs      = demapper([np.complex64(rlmSym), 1/snr])

## Descrambling
if hard_out:

    scrObject = Scrambler("PDSCH", rnti, 0, nID)
    rxBits    = scrObject(rxLLRs)

else:
    scrObject = DeScrambler("PDSCH", rnti, 0, nID)
    rscrBits  = scrObject(rxLLRs)
    rxBits    = np.where(rscrBits>0, 1, 0)

# PDSCH Upper Physical layer Decoder
pdschUpPhyDec = PDSCHDecoderUpperPhy(numTBs = numTBs, mcsIndex = mcsIndex, symbolsPerSlot= numSymbols,
                                     numRB  = numRB, numLayers = numlayers, scalingField = scalingField,
                                     additionalOverhead = additionalOverhead, dmrsREs = dmrsREs,
                                     enableLBRM = [False, False], pdschTable = mcsTable, rvid = [0, 0], verbose=False)
rbits         = pdschUpPhyDec([rscrBits])

10. Constellation Diagram

[24]:
fig, ax = plt.subplots()
ax.set_aspect(True)
ax.scatter(np.real(rsymbols),
           np.imag(rsymbols), s=24, label="Received Constellation")
# equalizedSamples = rsymbols.flatten()
# indices = np.random.choice(equalizedSamples.size, size = 10000, replace = False)
# ax.scatter(np.real(equalizedSamples[indices]),
#            np.imag(equalizedSamples[indices]), s=24, label="Received Constellation")
ax.axhline(y=0, ls=":", c="k"); ax.axvline(x=0, ls=":", c="k")
# ax.set_xlim([-1.5,1.5]); ax.set_ylim([-1.5,1.5])
ax.set_xlim([-2,2]); ax.set_ylim([-2,2])
# ax.set_xlabel("Real {x}"); ax.set_ylabel("Imag {x}")
# if modOrder ==2:
#     ax.set_title("Constellation Diagram: QPSK")
# else:
#     ax.set_title("Constellation Diagram: "+str(pow(2,modOrder))+str(" QAM"))

ax.grid();
ax.legend(loc = "best")
plt.show()
../../../../_images/api_Content_Codes_Tutorial-10%5BDL-MIMO%5D_10.MIMO-OFDM_Spatial_Multiplexing_in_5G_Networks_42_0.png

10. Key Performance Indicators

  1. Data-rate Indicators

    • Thoughput (Bits per seconds)

    • Spectral Efficency (Bits per second per Hertz)

  2. Reliability

    • Block Error Rate (BLER)

    • Bit Error Rate (BER)

[25]:
# Reliability Metrics
codedBER          = np.mean(np.abs(rbits[0][:,np.newaxis]-pdschUpperPhy.tblock1))
uncodedBER        = np.mean(np.abs(codeword[0][:,0,:] - rxBits))
# bler              = 1-np.mean(pdschUpPhyDec.crcCheckTBs)
bler              = 1-np.mean(pdschUpPhyDec.crcCheckforCBs)

# Data rate Metrics
slotDuration      = 10**(-3)*15000/(scs)
maxThroughput     = (14*numRB*12)*modOrder*codeRate/slotDuration
throughput        = (1-bler)*numBatches*pdschUpperPhy.tbLen1/(slotDuration*(numBatches+1))
spectalEfficiency = throughput/bandwidth

print()
print("         Throughput: "+str(throughput/10**6)+" Mbps")
print("Spectral Efficiency: "+str(spectalEfficiency)+" bits per second per Hz")
print()
print("*********************** Reliability ***********************")
print("   Block Error Rate: "+str(bler))
print("     Bit Error Rate: "+str(codedBER))
print()


         Throughput: 105.0912 Mbps
Spectral Efficiency: 3.50304 bits per second per Hz

*********************** Reliability ***********************
   Block Error Rate: 0.0
     Bit Error Rate: 0.0

10. Quasi-realtime simulation: MIMO in Spatial Multiplexing Mode

[26]:
# function that draws each frame of the animation
numSamples   = Nfft + lengthCP[1]

#
pssDetection = PSSDetection("largestPeak")
ssboffset    = int((Nfft-Neff)/2+ssbRGobject.startingSubcarrierIndices)

# Channel Estimation and Equalization
chEst = ChannelEstimationAndEqualizationPDSCH(slotNumber, scramblingID, nSCID, rnti, nID,
                                              pdschMappingType, configurationType, dmrsTypeAPosition,
                                              maxLength, dmrsAdditionalPosition, l0, ld, l1,
                                              startSymbol, numTargetBits, modOrder)
interpolator = "linear" # Can be "nearest" | "linear" | "cubicspline"
polyOrder    = 3 # Required only for Spline, Will be ignored for other interpolators

demapMethod  = "app" # Demapping method
constType    = "qam"  # Symbol mapping type
modOrder     = modOrder       # Mordulation order or number of bits per symbol
snr          = 10
hard_out     = False


def animate(i):
    # Receive samples
    # Clear buffer just to be safe
    for i in range (0, 10):
        raw_data = sdr.rx()

    # Receive samples
    rx_samples = sdr.rx()

    pssDetection   = PSSDetection("largestPeak")
    pssPeakIndices, pssCorrelation, rN_ID2, freqOffset = pssDetection(rx_samples[1], Nfft, lengthCP = lengthCP[1],
                                                                      nID2 = None, freqOffset = ssboffset)

    if(pssPeakIndices > rx_samples[1].size - 140*(Nfft + lengthCP[1])):
        pssPeakIndices = pssPeakIndices - 140*(Nfft + lengthCP[1])

    ofdmDemodulator = OFDMDemodulator(Nfft, lengthCP[1])
    pdschStartIndex = pssPeakIndices+12*(Nfft+lengthCP[1])
    # pssStartIndex   = pssPeakIndices[0][0]
    rxGrid          = ofdmDemodulator(np.array(rx_samples)[...,pdschStartIndex:(pdschStartIndex+14*numBatches*(Nfft+lengthCP[1]))])

    pdschGrid       = rxGrid[:,:,bwpOffset:bwpOffset+numRB*12].reshape(rank,numBatches,14,-1).transpose(1,0,2,3)

    # Channel Estimation and Equalization
    rsymbols     = chEst(pdschGrid, interpolator)

    #### Layer Demapping
    layDemap     = LayerDemapper(numTBs, rank)
    rlmSym       = layDemap(rsymbols)  # received symbols

    ######## Demapping.
    demapper     = Demapper(demapMethod, constType, modOrder, hard_out = hard_out)

    ## Computed Received bits
    rxLLRs      = demapper([np.complex64(rlmSym), 1/snr])

    ## Descrambling
    if hard_out:

        scrObject = Scrambler("PDSCH", rnti, 0, nID)
        rxBits    = scrObject(rxLLRs)

    else:
        scrObject = DeScrambler("PDSCH", rnti, 0, nID)
        rscrBits  = scrObject(rxLLRs)
        rxBits    = np.where(rscrBits>0, 1, 0)

    # PDSCH Upper Physical layer Decoder
    pdschUpPhyDec = PDSCHDecoderUpperPhy(numTBs = numTBs, mcsIndex = mcsIndex, symbolsPerSlot= numSymbols,
                                         numRB  = numRB, numLayers = numlayers, scalingField = scalingField,
                                         additionalOverhead = additionalOverhead, dmrsREs = dmrsREs,
                                         enableLBRM = [False, False], pdschTable = mcsTable, rvid = [0, 0], verbose=False)
    rbits         = pdschUpPhyDec([rscrBits])

    # Reliability Metrics
    codedBER          = np.mean(np.abs(rbits[0][:,np.newaxis]-pdschUpperPhy.tblock1))
    uncodedBER        = np.mean(np.abs(codeword[0][:,0,:] - rxBits))
    # bler              = 1-np.mean(pdschUpPhyDec.crcCheckTBs)
    bler              = 1-np.mean(pdschUpPhyDec.crcCheckforCBs)

    # Data rate Metrics
    throughput        = (1-bler)*numBatches*pdschUpperPhy.tbLen1/(slotDuration*(numBatches+1))
    spectalEfficiency = throughput/bandwidth

    ax[0].clear()
    ax[0].set_xlim([-2.5, 2.5])
    ax[0].set_ylim([-2.5, 2.5])
    ax[0].scatter(np.real(rsymbols.flatten()), np.imag(rsymbols.flatten()), s=24, label="Received Constellation")

#     indices = np.random.choice(rsymbols.size, size = 10000, replace = False)
#     equalizedSamples = rsymbols.flatten()[indices]
#     ax[0].scatter(np.real(equalizedSamples), np.imag(equalizedSamples), s=24, label="Received Constellation")
    ax[0].axhline(y=0, ls=":", c="k");
    ax[0].axvline(x=0, ls=":", c="k")

    ax[0].set_xlabel("Real {x}")
    ax[0].set_ylabel("Imag {x}")

    if modOrder ==2:
        ax[0].set_title("Constellation Diagram: QPSK")
    else:
        ax[0].set_title("Constellation Diagram: "+str(pow(2,modOrder))+str(" QAM"))

    ax[0].grid()
    ax[0].legend(loc = "best")

    data[0] = throughput/maxThroughput
    data[1] = 1 - data[0]
    print(data)

    ax[1].clear()
    ax[1].pie(data, wedgeprops=dict(width=0.33, edgecolor='r'), startangle=90, colors = colors[0:2])
    ax[1].text(-0.5, 0.05, r'Throughput', fontsize=12)
    ax[1].text(-0.5, -0.2, r'$'+str(np.round(throughput/10**6, 2))+'$ Mbps', fontsize=11)
    ax[1].set_title("Throughput Performance")

    data[0] = bler
    data[1] = 1-bler

    ax[2].clear()
    ax[2].pie(data, wedgeprops=dict(width=0.33, edgecolor='k'), startangle=90, colors = colors[2:4])
    ax[2].text(-0.25, 0.05, r'BLER', fontsize=12)
    ax[2].text(-0.25, -0.2, r'$'+str(np.round(bler,2))+'$', fontsize=12)
    ax[2].set_title("BLER Performance")


# Plot SSB
fig, ax = plt.subplots(1,3, figsize=(10, 3.3), subplot_kw=dict(aspect="equal"))
ax[0].set_aspect(True)
ax[1].set_aspect(True)
ax[2].set_aspect(True)

scale = 100
slotDuration  = 10**(-3)*15000/(scs)
maxThroughput = (14*numRB*12)*modOrder*codeRate*rank/slotDuration
data    = [0.75,0.25]

cmap    = plt.get_cmap("tab20c")
colors  = cmap(np.arange(4)*3)

#####################
# run the animation
#####################
# frames= 20 means 20 times the animation function is called.
# interval=500 means 500 milliseconds between each frame.
# repeat=False means that after all the frames are drawn, the animation will not repeat.
# Note: plt.show() line is always called after the FuncAnimation line.

anim = animation.FuncAnimation(fig, animate, frames=100, interval=1, repeat=False, blit=True)

# saving to mp4 using ffmpeg writer
plt.show()

anim.save("MIMO_Constellation.gif", fps = 10)

# writervideo = animation.FFMpegWriter(fps=60)
# anim.save('MIMO_Constellation.mp4', writer=writervideo)

MIMO_Constellation
[ ]: