Physical Channels

Physical Downlink Shared Channel

The module pdsch.py implements the PDSCH class which encapsulates Physical Downlink Shared Channel. It is a downlink channel that deliver user data from gNB to UE. PDSCH occupies a grid of Resource Blocks (RBs) within a slot. Usually one or more OFDM symbols are used for PDCCH and the remaining resources are available for PDSCH.

The gNB schedules PDSCH resources for UEs based on their channel quality, data requirements, and fairness considerations. PDSCH uses LDPC (Low-Density Parity-Check) coding to provide forward error correction, enhancing the robustness of data transmission over the wireless channel.

In MIMO (Multiple Input Multiple Output) systems, a PDSCH is distributed among multiple layers. PDSCH includes Demodulation Reference Signals (DMRS) to assist the UE in channel estimation and demodulation, ensuring accurate data reception. It may also include Phase Tracking Reference Signals (PTRS) which enables suppression of phase noise and common phase error, particularly important at high carrier frequencies such as millimeter wave.

class neoradium.pdsch.PDSCH(bwp, **kwargs)

This class encapsulates the configuration and functionality of a Physical Downlink Shared Channel (PDSCH) which delivers user data transmitted from gNB to UE.

Parameters:
  • bwp (BandwidthPart) – The BandwidthPart object that represents the resources used by this PDSCH for transmission of user data from gNB to UE.

  • kwargs (dict) –

    A set of optional arguments.

    mappingType:

    The mapping type used by this PDSCH and its associated DMRS object. It is a text string that can be either 'A' or 'B'. The default is 'A'.

    In mapping type 'A', the first DMRS OFDM symbol index is 2 or 3 and DMRS is mapped relative to the start of slot boundary, regardless of where in the slot the actual data transmission starts. The user data in this case usually occupies most of the slot.

    In mapping type 'B', the first DMRS OFDM symbol is the first OFDM symbol of the data allocation, that is, the DMRS location is not given relative to the slot boundary but relative to where the user data is located. The user data in this case usually occupies a small fraction of the slot to support very low latency.

    numLayers:

    The number of transmission layers for this PDSCH. It must be an integer between 1 and 8, with 1 being the default.

    modulation:

    A text string or a tuple or list of 2 text strings specifying the modulation scheme used for data transmitted in this PDSCH based on 3GPP TR 38.211, Table 7.3.1.2-1. The default is '16QAM'. Here is a list of supported Modulation Schemes:

    Modulation Scheme

    Modulation Order (qm)

    BPSK

    1

    QPSK

    2

    16QAM

    4

    64QAM

    6

    256QAM

    8

    1024QAM

    10

    If modulation is a text string, and there are two code-words in this PDSCH, the same modulation scheme is used for both code-words. If there are two code-words in this PDSCH, and you want to use different modulation schemes for the two code-words, then you can specify two different modulation schemes in a tuple or list of text strings. For example:

    # Using "QPSK" for the first code-word and "16QAM" for the second code-word
    modulation = ("QPSK", "16QAM")
    

    The specified modulation scheme(s) are used to create one or two Modem objects.

    reservedRbSets:

    A list of ReservedRbSet objects that are used to reserve the specified resource blocks (RBs) at the specified OFDM symbols based on the patterns defined in the ReservedRbSet objects. The default is an empty list which means no reserved RBs.

    reservedReMap:

    The map of additional reserved resource elements (REs) as a 3D list of the form portNo x symNo x reNo. The default is an empty list which means no reserved REs. The following additional rules makes configuring reserved REs both easy and flexible.

    • If reservedReMap has data for only one port, it means that the map is the same for all ports.

    • If in a port, the reserved REs are given only for a single symbol, it is assumed the same REs are reserved in all symbols.

    Here are some examples:

    # REs 5, 17, and 29 are reserved on all ports and all symbols
    reservedReMap = [[[5,17,29]]]
    
    # REs 5, 17, and 29 are reserved on all ports on OFDM symbol 2 only
    reservedReMap = [[ [], [], [5,17,29], [], [], [], [], [], [], [], [], [], [], [] ]]
    
    # REs 5, 17, and 29 are reserved on all symbols of port index 1 (assuming we have 3 ports)
    reservedReMap = [[], [[5,17,29]], []]
    
    sliv:

    Start and Length Indicator Value. If specified, it is used to determine the start and length of consecutive OFDM symbols used by this PDSCH based on 3GPP TS 38.214, Section 5.1.2.1. The default is None. See Specifying the OFDM symbols below for more information.

    symStart:

    The index of the first OFDM symbol used for this PDSCH. The default is None. See Specifying the OFDM symbols below for more information.

    symLen:

    The number of consecutive OFDM symbols used by this PDSCH starting at symStart. The default is None. See Specifying the OFDM symbols below for more information.

    symSet:

    A list of OFDM symbol indexes that are used by this PDSCH. See Specifying the OFDM symbols below for more information.

    prbSet:

    The list of physical resource blocks (PRBs) used by this PDSCH. The default is all the RBs in the BandwidthPart object bwp

    interleavingBundleSize:

    The bundle size of interleaving process. It can be one of 0 (default), 2, or 4. The value 0 means interleaving is disabled (default). See 3GPP TS 38.211, Section 7.3.1.6 for more information.

    rnti:

    The Radio Network Temporary Identifier. The default is 1. It is used with nID below to initialize a golden sequence used for the scrambling process. See 3GPP TS 38.211, Section 7.3.1.1 for more information.

    nID:

    The scrambling identity. The default is 1. It is used with rnti to initialize a golden sequence used for the scrambling process. See 3GPP TS 38.211, Section 7.3.1.1 for more information.

    prgSize:

    The size of Precoding RB Groups (PRGs). It can be one of 0 (default), 2, or 4. The value 0 means Wideband Precoding which means the same precoding is used for the whole bandwidth of this PDSCH. See 3GPP TS 38.214, Section 5.1.2.3 for more information.

Specifying the OFDM symbols:

You can specify the OFDM symbols used by this PDSCH in different ways:

  • If sliv is specified, it is used to determine the start and length of consecutive OFDM symbols used by this PDSCH based on 3GPP TS 38.214, Section 5.1.2.1. In this case, the parameters symStart, symLen, and symSet are ignored.

  • If sliv is not specified and both symStart and symLen are specified, they are used to determine the OFDM symbols used by this PDSCH. In this case the parameter symSet is ignored.

  • If sliv, symStart, and symLen are not specified but symSet is specified, it is used to determine the OFDM symbols used by this PDSCH.

  • If neither of sliv, symStart, symLen, and symSet are specified, the OFDM symbols are automatically assigned based on mappingType and cpType parameter of the BandwidthPart object bwp.

Other Properties:

numCW:

The number of code-words derived from the numLayers parameter. It is either 1 or 2.

modems:

A list of one or two (depending on numCW) Modem object(s) used internally for modulation/demodulation of the code-words.

dmrs:

The DMRS object associated with this PDSCH. You can use setDMRS() method to set the DMRS object associated with this PDSCH.

portSet:

The list of ports used by this PDSCH and its associated DMRS object. By default, this is set to the number of layers specified by numLayers. This can be changed by the DMRS configuration.

slotNo:

This returns the slotNo property of the Carrier object containing bwp.

frameNo:

This returns the frameNo property of the Carrier object containing bwp.

slotNoInFrame:

This returns the slotNoInFrame property of the Carrier object containing bwp.

slotMap:

This is the map of resource blocks used for each OFDM symbol in this PDSCH in the form of a 2D list (A list of lists). Each element in the list corresponds to one OFDM symbol. If an OFDM symbol has no resource blocks allocation in this PDSCH, its corresponding element is an empty list. Otherwise, it is a list of indexes of all resource blocks used in that OFDM symbol in the order they should be allocated (based on the interleaving process). Note that the reserved resource blocks specified by reservedRbSets above are not included in the slotMap.

The notebook PDSCH end-to-end communication shows how to create an end-to-end pipeline of PDSCH communication.

print(indent=0, title='PDSCH Properties:', getStr=False)

Prints the properties of this PDSCH object.

Parameters:
  • indent (int (default: 0)) – The number of indentation characters.

  • title (str or None (default: None)) – If specified, it is used as a title for the printed information.

  • getStr (Boolean (default: False)) – If True, it returns the information in a text string instead of printing the information.

Returns:

If the getStr parameter is True, then this function returns the information in a text string. Otherwise, nothing is returned.

Return type:

None or str

setDMRS(**kwargs)

Creates and initializes a DMRS object associated with this PDSCH object.

Parameters:

kwargs (dict) – A dictionary of parameters passed directly to the constructor of the DMRS class. Please refer to this class for a list of parameters that can be used to configure DMRS.

setPTRS(**kwargs)

Creates and initializes a PTRS object associated with this PDSCH object. Please note that you must first use the setDMRS() function to initialize the DMRS object before calling this function.

Parameters:

kwargs (dict) – A dictionary of parameters passed directly to the constructor of the PTRS class. Please refer to this class for a list of parameters that can be used to configure PTRS.

getGrid(useReDesc=False)

Creates a Grid object for this PDSCH and a populates it with the configured DMRS and PTRS reference signals. It also marks all the resources corresponding to the reservedRbSets and reservedReMap parameters as reserved in the newly created resource grid.

The returned resource grid contains all reference signals and is ready to be populated with the user data (See populateGrid() method).

Parameters:

useReDesc (Boolean (default: False)) – If True, the resource grid will also include additional fields that describe the content of each resource element (RE). This can be used during the debugging to make sure the resources are allocated correctly.

Returns:

A Grid object representing the resource grid for this PDSCH pre-populated with reference signals.

Return type:

Grid

getReIndexes(grid, reTypeStr)

Returns the indexes of all resource elements of the specified resource grid that are allocated for this PDSCH with the content type specified by the reTypeStr.

This is similar to the getReIndexes() of the Grid class with two main differences:

  • This function considers only the resource elements in grid that are assigned to this PDSCH.

  • The indexes are ordered based on the interleaving configuration given by interleavingBundleSize according to 3GPP TS 38.214, Section 5.1.4.1.

For example, the following code first gets the indexes of all DMRS values in myPdsch and uses the returned indexes to retrieve the DMRS values.

myGrid = myPdsch.getGrid()
dmrsIdx = myPdsch.getReIndexes(myGrid, "DMRS")  # Get the indexes of all DMRS values
dmrsValues = myGrid[indexes]                    # Get all DMRS values as a 1-D array.
Parameters:
  • grid (Grid) – The resource grid associated with this PDSCH. This can be obtained using the getGrid function for example.

  • reTypeStr (str) –

    The content type of the desired resource elements in grid that are used by this PDSCH. Here is a list of values that can be used:

    ”UNASSIGNED”:

    The un-assigned resource elements.

    ”RESERVED”:

    The reserved resource elements. This includes the REs reserved by reservedRbSets or reservedReMap parameters of this PDSCH.

    ”NO_DATA”:

    The resource elements that should not contain any data. For example when the corresponding REs in a different layer is used for transmission of data for a different UE. (See otherCdmGroups parameter of DMRS class)

    ”DMRS”:

    The resource elements used for DMRS.

    ”PTRS”:

    The resource elements used for PTRS.

    ”CSIRS_NZP”:

    The resource elements used for Zero-Power (ZP) CSI-RS (See csirs).

    ”CSIRS_ZP”:

    The resource elements used for Non-Zero-Power (NZP) CSI-RS (See csirs).

    ”PDSCH”:

    The resource elements used for user data in a Physical Downlink Shared Channel (PDSCH)

    ”PDCCH”:

    The resource elements used for user data in a Physical Downlink Control Channel (PDCCH)

    ”PUSCH”:

    The resource elements used for user data in a Physical Uplink Shared Channel (PUSCH)

    ”PUCCH”:

    The resource elements used for user data in a Physical Uplink Control Channel (PUCCH)

Returns:

A tuple of three 1-D numpy arrays specifying a list of locations in grid. This value can be used directly to access REs at the specified locations. (See the above example)

Return type:

3-tuple

getNumREsFromIndexes(indexes)

Returns the number of resource elements included in indexes for each code-word. The returned value is a list of one or two integers depending on number of code-words (numCW).

Parameters:

indexes (3-tuple) – A tuple of 3 lists specifying locations of a set of resource elements in the resource grid. For example this can be obtained using the getReIndexes() function.

Returns:

A list of one or two integers depending on number of code-words (numCW), indicating the number of resource elements (REs) included in indexes for each code-word.

Return type:

list

getBitSizes(grid, reTypeStr='UNASSIGNED')

Returns total number of bits corresponding to the resource elements in grid assigned to this PDSCH with content type specified by reTypeStr for each code-word. The returned value is a list of one or two integers depending on number of code-words (numCW).

The default value of reTypeStr="UNASSIGNED" is for a common use case where we want to get total number of bits available in this PDSCH for user data after setting aside the REs for DMRS, PTRS, and the reserved resources.

Parameters:
  • grid (Grid()) – The resource grid associated with this PDSCH. This can be obtained using the getGrid() function for example.

  • reTypeStr (str (default: "UNASSIGNED")) – The content type of the desired resource elements used to count the returned number of bits. The default values of "UNASSIGNED" causes this function to return total number of (unassigned) bits that are available for user data. Please refer to the getReIndexes() function for a list of values that can used for reTypeStr.

Returns:

A list of one or two integers depending on number of code-words (numCW), indicating the number of bits allocated for reTypeStr for each code-word.

Return type:

list

populateGrid(grid, bits=None)

Populates the resource grid specified by grid with the user data provided in bits.

This function performs the following operations:

Scrambling:

Scrambling of the specified bits using the rnti and nID properties of this PDSCH. These properties are used to initialize a golden sequence which is then used for the scrambling process according to 3GPP TS 38.211, Section 7.3.1.1. The data bits for each code-word are scrambled separately.

Modulation:

Converting the scrambled binary data stream into complex symbols for each resource elements assigned for user data. The modulation process is performed by the Modem objects in the modems list of this PDSCH. The modulation for each code-word is performed separately by its own dedicated Modem object.

Layer Mapping:

Distributing the modulated complex symbols across one or more transmission layers of this PDSCH according to 3GPP TS 38.211, Section 7.3.1.3.

Interleaving:

Converting Virtual Resource Blocks (VRBs) to Physical Resource Blocks (PRBs). If enabled, the resources are re-ordered based on the interleaving configuration given by interleavingBundleSize according to 3GPP TS 38.214, Section 5.1.4.1.

Parameters:
  • grid (Grid) – The Grid object that gets populated with the user data bits.

  • bits (list, tuple, numpy array, or None (default: None)) –

    Specifies the user data bits that are used to populate the specified resource grid. It can be one of the following:

    tuple of numpy arrays:

    Depending on the number of code-words (numCW), the tuple can have one or two 1D numpy arrays of bits each specifying the user data bits for each code-word.

    numpy array:

    A one or two dimensional numpy array. It is a 1D array, only if we have one code-word and the given numpy array is used for the single code-word. The 2D numpy array can be used for one or two code-word cases. The first dimension of the numpy array in this case should match the number of code-words (numCW).

    list of numpy arrays:

    Depending on the number of code-words (numCW), the list can have one or two 1D numpy arrays of bits each specifying the user data bits for each code-word.

    None:

    If this is None, grid data is not updated. This is used for the (rare) case where we only want to update the resource element descriptions in the grid object. See the useReDesc parameter of the Grid class for more information.

getLLRsFromGrid(rxGrid, pdschIndexes, llrScales=None, noiseVar=None, useMax=True)

This method is used at the receiving side where the Log-likelihood-Ratios (LLRs) are extracted from the received resource grid rxGrid. This is in some sense the opposite of the populateGrid() method since it does the following:

Deinterleaving:

Converting Physical Resource Blocks (PRBs) to Virtual Resource Blocks (VRBs). If enabled, the resources are re-ordered based on the interleaving configuration given by interleavingBundleSize according to 3GPP TS 38.214, Section 5.1.4.1 so get the data in its original order.

Layer Demapping:

Extracting the modulated complex symbols for each code-word from different layers of this PDSCH according to 3GPP TS 38.211, Section 7.3.1.3.

Demodulation:

Converting complex symbols to Log-likelihood-Ratios (LLRs) using the Modem objects in the modems list of this PDSCH. The demodulation for each code-word is performed separately by its own dedicated Modem object. This produces one or two sets of LLRs for each code-word.

Descrambling:

The descrambling of the demodulated LLRs using the rnti and nID properties of this PDSCH. These properties are used to initialize a golden sequence which is then used for the descrambling process according to 3GPP TS 38.211, Section 7.3.1.1. The LLRs for each code-word are descrambled separately.

This function returns a list of one or two numpy arrays representing the LLRs for each code-word.

Parameters:
  • rxGrid (Grid) – The equalized received resource grid associated with this PDSCH. Usually this is the Grid object obtained after equalization in the receiver pipeline (See the equalize() function).

  • pdschIndexes (3-tuple) – A tuple of 3 lists specifying locations of the set of resource elements in rxGrid that are assigned to the user data. The function getReIndexes() is usually used to obtain this.

  • llrScales (3-D numpy array) – The Log-Likelihood Ratios (LLR) scaling factors which are used by demodulating process when extracting Log-Likelihood Ratios (LLRs) from the equalized resource grid. The shape of this array must be the same shape as rxGrid.

  • noiseVar (float or None (default: None)) – The variance of the Additive White Gaussian Noise (AWGN) present in the received resource grid. If this is not provided (noiseVar=None), This function uses the noiseVar property of the rxGrid object.

  • useMax (Boolean (default: True)) – If True, this implementation uses the Max function in the calculation of the LLR values. This is faster but uses an approximation and is slightly less accurate than the actual Log Likelihood method which uses logarithm and exponential functions. If False, the slower more accurate method is used.

Returns:

A list of one or two numpy arrays each representing the LLRs for each code-word.

Return type:

list

getHardBitsFromGrid(rxGrid, pdschIndexes, llrScales=None, noiseVar=None, useMax=True)

This method first calls the getLLRsFromGrid() function above and then uses hard-decisions on the returned LLRs to get the output user bits.

This can be used when there is no channel coding in the communication pipeline. It returns a list of one or two numpy arrays of bits for each code-word.

Parameters:
  • rxGrid (Grid) – The equalized received resource grid associated with this PDSCH. Usually this is the Grid object obtained after equalization in the receiver pipeline (See the equalize() function).

  • pdschIndexes (3-tuple) – A tuple of 3 lists specifying locations of the set of resource elements in rxGrid that are assigned to the user data. The function getReIndexes() is usually used to obtain this.

  • llrScales (3-D numpy array) – The Log-Likelihood Ratios (LLR) scaling factors which are used by demodulating process when extracting Log-Likelihood Ratios (LLRs) from the equalized resource grid. The shape of this array must be the same shape as rxGrid.

  • noiseVar (float or None (default: None)) – The variance of the Additive White Gaussian Noise (AWGN) present in the received resource grid. If this is not provided (noiseVar=None), This function uses the noiseVar property of the rxGrid object.

  • useMax (Boolean (default: True)) – If True, this implementation uses the Max function in the calculation of the LLR values. This is faster but uses an approximation and is slightly less accurate than the actual Log Likelihood method which uses logarithm and exponential functions. If False, the slower more accurate method is used.

Returns:

A list of one or two numpy arrays of bits for each code-word.

Return type:

list

getDataSymbols(grid)

This is a helper function that returns the modulated complex symbols for all user data in grid for this PDSCH object. The following code shows 2 different ways to do this:

# Getting the indexes of user data in "grid" and then using them to get "dataSymbols1":
dataReIndexes = myPdsch.getReIndexes(grid, "PDSCH")
dataSymbols1 = grid[ dataReIndexes ]

# Using the "getDataSymbols" function:
dataSymbols2 = myPdsch.getDataSymbols(grid)

assert np.all(dataSymbols1==dataSymbols2)             # The results are the same
Parameters:

grid (Grid) – The resource grid associated with this PDSCH containing the user data.

Returns:

A 1D numpy array of modulated complex symbols corresponding to the user data in grid.

Return type:

Numpy array

getPrecodingMatrix(channelMatrix)

This function calculates the precoding matrix that can be applied to a resource grid. This function supports Precoding RB groups (PRGs) which means different precoding matrixes could be applied to different groups of subcarriers in the resource grid. See 3GPP TS 38.214, Section 5.1.2.3 for more details. The prgSize property of PDSCH determines what type of precoding matrix is returned by this function:

Wideband:

If prgSize is set to zero, a single Nt x Nl, matrix is returned where Nt is the number of transmitter antenna and Nl is the number of layers in this PDSCH. In this case the same precoding is applied to all subcarriers of the resource grid.

Using PRGs:

If prgSize is set to 2 or 4, a list of tuples of the form (groupRBs, groupF) is returned. For each entry in the list, the Nt x Nl precoding matrix groupF is applied to all subcarriers of the resource blocks listed in groupRBs.

Note

It is assumed that the channelMatrix is obtained based on the same BandwidthPart object as the one used by this PDSCH.

Parameters:

channelMatrix (Numpy array) – An L x K x Nr x Nt complex numpy array representing the channel matrix. It can be the actual channel matrix obtained directly from a channel model using the getChannelMatrix() method (perfect estimation), or an estimated channel matrix obtained using the estimateChannelLS() method.

Returns:

Depending on the prgSize property of this PDSCH, the returned value can be:

Numpy Array:

If prgSize is set to zero, a single Wideband Nt x Nl, matrix is returned where Nt is the number of transmitter antenna and Nl is the number of layers in this PDSCH. In this case the same precoding is applied to all subcarriers of the resource grid.

list of tuples:

If prgSize is set to 2 or 4, a list of tuples of the form (groupRBs, groupF) is returned. For each entry in the list, the Nt x Nl precoding matrix groupF is applied to all subcarriers of the resource blocks listed in groupRBs.

Return type:

Numpy array or list of tuples

getTxBlockSize(codeRates, xOverhead=0, scaleFactor=1.0)

This function calculates the transport block size based on the desired code rate (codeRates), the number of additional overhead resource elements (xOverhead), and the scaling factor (scaleFactor). It returns a list of one or two integer values specifying the size of transport blocks for each code-word. This implementation is based on 3GPP TS 38.214, Section 5.1.3.2.

Parameters:
  • codeRates (float, list, numpy array, or tuple) – If codeRates is a float value, it specifies the same code-rate for all code-words. If it is a list, numpy array, or tuple, it should contain one or two code-rate values for each code-word. This is the value \(R\) in 3GPP TS 38.214, Section 5.1.3.2.

  • xOverhead (int (default: 0)) – The number of additional overhead resource elements (REs) that should be considered when calculating the transport block size. This is the value \(N^{PRB}_{oh}\) in 3GPP TS 38.214, Section 5.1.3.2.

  • scaleFactor (float (default: 1.0)) – The scaling factor which must be one of: 0.25, 0.5, or 1.0. This is the value \(S\) in 3GPP TS 38.214, Table 5.1.3.2-2.

Returns:

A list of one or two integers depending on number of code-words (numCW), indicating the transport block size for each code-word.

Return type:

list