Source code for sionna.nr.pusch_config

#
# SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
"""PUSCH configuration for the nr (5G) sub-package of the Sionna library.
"""
# pylint: disable=line-too-long

import numpy as np
from .utils import generate_prng_seq
from .config import Config
from sionna import nr
from .utils import calculate_tb_size

[docs] class PUSCHConfig(Config): """ The PUSCHConfig objects sets parameters for a physical uplink shared channel (PUSCH), as described in Sections 6.3 and 6.4 [3GPP38211]_. All configurable properties can be provided as keyword arguments during the initialization or changed later. Parameters ---------- carrier_config : :class:`~sionna.nr.CarrierConfig` or `None` An instance of :class:`~sionna.nr.CarrierConfig`. If `None`, a :class:`~sionna.nr.CarrierConfig` instance with default settings will be created. pusch_dmrs_config : :class:`~sionna.nr.PUSCHDMRSConfig` or `None` An instance of :class:`~sionna.nr.PUSCHDMRSConfig`. If `None`, a :class:`~sionna.nr.PUSCHDMRSConfig` instance with default settings will be created. Example ------- >>> pusch_config = PUSCHConfig(mapping_type="B") >>> pusch_config.dmrs.config_type = 2 >>> pusch_config.carrier.subcarrier_spacing = 30 """ def __init__(self, carrier_config=None, pusch_dmrs_config=None, tb_config=None, **kwargs): super().__init__(**kwargs) self._name = "PUSCH Configuration" self.carrier = carrier_config self.dmrs = pusch_dmrs_config self.tb = tb_config self.check_config() #-----------------------------# #---Configurable parameters---# #-----------------------------# #---carrier---# @property def carrier(self): """ :class:`~sionna.nr.CarrierConfig` : Carrier configuration """ return self._carrier @carrier.setter def carrier(self, value): if value is None: value = nr.CarrierConfig() else: assert isinstance(value, nr.CarrierConfig), \ "carrier must be an instance of CarrierConfig" self._carrier = value #---dmrs---# @property def dmrs(self): """ :class:`~sionna.nr.PUSCHDMRSConfig` : PUSCH DMRS configuration """ return self._dmrs @dmrs.setter def dmrs(self, value): if value is None: value = nr.PUSCHDMRSConfig() else: assert isinstance(value, nr.PUSCHDMRSConfig), \ "pusch_dmrs_config must be an instance of PUSCHDMRSConfig" self._dmrs = value #---transport block---# @property def tb(self): """ :class:`~sionna.nr.TBConfig` : Transport block configuration """ return self._tb @tb.setter def tb(self, value): if value is None: value = nr.TBConfig(channel_type="PUSCH") else: assert isinstance(value, nr.TBConfig), \ "tb must be an instance of TBConfig" assert value.channel_type=="PUSCH",\ 'TBConfig must be configured for "PUSCH"' self._tb = value #---n_size_bwp---# @property def n_size_bwp(self): r""" int, None (default), [1,...,275] : Number of resource blocks in the bandwidth part (BWP) :math:`N^{\text{size},\mu}_{\text{BWP},i}` If set to `None`, the property :class:`~sionna.nr.CarrierConfig.n_size_grid` of `carrier` will be used. """ self._ifndef("n_size_bwp", None) return self._n_size_bwp @n_size_bwp.setter def n_size_bwp(self, value): if value is not None: assert value in range(1,276),\ "n_size_bwp must be in the range from 1 to 275" self._n_size_bwp = value #---n_start_bwp---# @property def n_start_bwp(self): r""" int, 0 (default) | [0,...,2199] : Start of BWP relative to common resource block (CRB) 0 :math:`N^{\text{start},\mu}_{\text{BWP},i}` """ self._ifndef("n_start_bwp", 0) return self._n_start_bwp @n_start_bwp.setter def n_start_bwp(self, value): assert value in range(0,2474), \ "n_start_bwp must be in the range from 0 to 2473" self._n_start_bwp = value #---num-layers---# @property def num_layers(self): r""" int, 1 (default) | 2 | 3 | 4: Number of transmission layers :math:`\nu` Must be smaller than or equal to :class:`~sionna.nr.PUSCHConfig.num_antenna_ports`. """ self._ifndef("num_layers", 1) return self._num_layers @num_layers.setter def num_layers(self, value): assert value in [1,2,3,4], "num_layers must be in [1,...,4]" self._num_layers = value #---num-antenna-ports---# @property def num_antenna_ports(self): """ int, 1 (default) | 2 | 4: Number of antenna ports Must be larger than or equal to :class:`~sionna.nr.PUSCHConfig.num_layers`. """ self._ifndef("num_antenna_ports", 1) return self._num_antenna_ports @num_antenna_ports.setter def num_antenna_ports(self, value): assert value in [1,2,4], "num_antenna_ports must be in [1,2,4]" self._num_antenna_ports = value #---mapping_type---# @property def mapping_type(self): """ string, "A" (default) | "B": Mapping type """ self._ifndef("mapping_type", "A") return self._mapping_type @mapping_type.setter def mapping_type(self, value): assert value in ["A","B"], "mapping_type must be A or B" self._mapping_type = value #---symbol_allocation---# @property def symbol_allocation(self): """ 2-tuple, int, [0, 14] (default) : PUSCH symbol allocation The first elements denotes the start of the symbol allocation. The second denotes the positive number of allocated OFDM symbols. For `mapping_type` "A", the first element must be zero. For `mapping_type` "B", the first element must be in [0,...,13]. The second element must be such that the index of the last allocated OFDM symbol is not larger than 13 (for "normal" cyclic prefix) or 11 (for "extended" cyclic prefix). """ self._ifndef("symbol_allocation", [0, 14]) return self._symbol_allocation @symbol_allocation.setter def symbol_allocation(self, value): assert len(value)==2, "symbol_allocation must have two elements" self._symbol_allocation = value #---n_rnti---# @property def n_rnti(self): r""" int, 1 (default), [0,...,65535] : Radio network temporary identifier :math:`n_\text{RNTI}` """ self._ifndef("n_rnti", 1) return self._n_rnti @n_rnti.setter def n_rnti(self, value): if value is not None: assert value in range(65536), "n_rnti must be in [0, 65535]" self._n_rnti = value #---transform_precoding---# @property def precoding(self): """ str, "non-codebook" (default), "codebook" : PUSCH transmission scheme """ self._ifndef("precoding", "non-codebook") return self._precoding @precoding.setter def precoding(self, value): assert value in ["codebook", "non-codebook"], \ "Unknown value for precoding" self._precoding = value #---transform_precoding---# @property def transform_precoding(self): """ bool, False (default): Use transform precoding """ self._ifndef("transform_precoding", False) return self._transform_precoding @transform_precoding.setter def transform_precoding(self, value): assert isinstance(value, bool), \ """transform_precoding must be bool""" self._transform_precoding = value #---tpmi---# @property def tpmi(self): r""" int, 0 (default) | [0,...,27] : Transmit precoding matrix indicator The allowed value depends on the number of layers and the number of antenna ports according to Table 6.3.1.5-1 until Table 6.3.1.5-7 [3GPP38211]_. """ self._ifndef("tpmi", 0) return self._tpmi @tpmi.setter def tpmi(self, value): assert value in range(28), "tpmi must be in [0,...,27]" self._tpmi = value #-----------------------------# #---Read-only parameters------# #-----------------------------# @property def frequency_hopping(self): """ str, "neither" (default), read-only : Frequency hopping configuration """ return "neither" @property def l_0(self): r""" int, read-only : Position of the first DMRS symbol :math:`l_0` relative to the reference `l_ref`. """ if self.mapping_type=="A": return self.dmrs.type_a_position elif self.mapping_type=="B": return 0 @property def l_d(self): r""" int, read-only : Length of the symbol allocation :math:`l_\text{d}` """ return self.symbol_allocation[1] @property def l_ref(self): r""" int, read-only: Reference OFDM symbol index used for DMRS generation """ if self.mapping_type=="A": return 0 elif self. mapping_type=="B": return self.symbol_allocation[0] @property def l_prime(self): r""" list, elements in [0,1], read-only : List of possible values of :math:`l'` used for DMRS generation """ if self.dmrs.length==1: return [0] elif self.dmrs.length==2: return [0, 1] @property def l_bar(self): r""" list, elements in [0,...,11], read-only : List of possible values of :math:`\bar{l}` used for DMRS generation Defined in Tables 6.4.1.1.3-3 and 6.4.1.1.3-4 [3GPP38211]_. """ l_0 = self.l_0 ind = 0 if self.l_d<4 else self.l_d-3 if self.mapping_type=="A": if self.dmrs.length==1: l_bar = [ [[], [], [], []], [[l_0], [l_0], [l_0], [l_0]], [[l_0], [l_0], [l_0], [l_0]], [[l_0], [l_0], [l_0], [l_0]], [[l_0], [l_0], [l_0], [l_0]], [[l_0], [l_0, 7], [l_0, 7], [l_0, 7]], [[l_0], [l_0, 7], [l_0, 7], [l_0, 7]], [[l_0], [l_0, 9], [l_0, 6, 9], [l_0, 6, 9]], [[l_0], [l_0, 9], [l_0, 6, 9], [l_0, 6, 9]], [[l_0], [l_0, 9], [l_0, 6, 9], [l_0, 5, 8, 11]], [[l_0], [l_0, 11], [l_0, 7, 11], [l_0, 5, 8, 11]], [[l_0], [l_0, 11], [l_0, 7, 11], [l_0, 5, 8, 11]] ] else: l_bar = [ [[], []], [[l_0], [l_0]], [[l_0], [l_0]], [[l_0], [l_0]], [[l_0], [l_0]], [[l_0], [l_0]], [[l_0], [l_0]], [[l_0], [l_0, 8]], [[l_0], [l_0, 8]], [[l_0], [l_0, 8]], [[l_0], [l_0, 10]], [[l_0], [l_0, 10]], ] else: # mapping_type == "B" if self.dmrs.length==1: l_bar = [ [[l_0], [l_0], [l_0], [l_0]], [[l_0], [l_0], [l_0], [l_0]], [[l_0], [l_0, 4], [l_0, 4], [l_0, 4]], [[l_0], [l_0, 4], [l_0, 4], [l_0, 4]], [[l_0], [l_0, 4], [l_0, 4], [l_0, 4]], [[l_0], [l_0, 6], [l_0, 3, 6], [l_0, 3, 6]], [[l_0], [l_0, 6], [l_0, 3, 6], [l_0, 3, 6]], [[l_0], [l_0, 8], [l_0, 4, 8], [l_0, 3, 6, 9]], [[l_0], [l_0, 8], [l_0, 4, 8], [l_0, 3, 6, 9]], [[l_0], [l_0, 10], [l_0, 5, 10], [l_0, 3, 6, 9]], [[l_0], [l_0, 10], [l_0, 5, 10], [l_0, 3, 6, 9]], [[l_0], [l_0, 10], [l_0, 5, 10], [l_0, 3, 6, 9]] ] else: l_bar = [ [[], []], [[], []], [[l_0], [l_0]], [[l_0], [l_0]], [[l_0], [l_0]], [[l_0], [l_0, 5]], [[l_0], [l_0, 5]], [[l_0], [l_0, 7]], [[l_0], [l_0, 7]], [[l_0], [l_0, 9]], [[l_0], [l_0, 9]], [[l_0], [l_0, 9]], ] return l_bar[ind][self.dmrs.additional_position] @property def l(self): r""" list, int, read-only : List of possible values of the OFDM symbol indices :math:`l` carrying DMRS relative to :math:`l_0` """ l = [] for l_bar in self.l_bar: for l_prime in self.l_prime: l.append(l_bar + l_prime) return l @property def n(self): """ list, int, read-only: List of possible values of n used for DMRS generation """ if self.dmrs.config_type==1: n_max = self.num_resource_blocks*12//4 -1 else: # config_type == 2 n_max = self.num_resource_blocks*12//6 -1 return list(range(n_max+1)) @property def dmrs_symbol_indices(self): """ list, int, read-only: Indices of DMRS symbols within a slot """ return [l + self.l_ref for l in self.l] @property def num_resource_blocks(self): """ int, read-only : Number of allocated resource blocks for the PUSCH transmissions. """ if self.n_size_bwp is None: return self.carrier.n_size_grid else: return self.n_size_bwp @property def num_subcarriers(self): """ int, read-only : Number of allocated subcarriers for the PUSCH transmissions """ return 12*self.num_resource_blocks @property def num_res_per_prb(self): """ int, read-only : Number of resource elements per PRB available for data """ # Number of DMRS symbols num_dmrs = len(self.dmrs_symbol_indices) # Number of non-DMRS symbols num_data = self.symbol_allocation[1] - num_dmrs # Number of REs on DMRS symbols if self.dmrs.config_type==1: num_res_dmrs = 12 - 6*self.dmrs.num_cdm_groups_without_data else: # dmrs.config_type == 2 num_res_dmrs = 12 - 4*self.dmrs.num_cdm_groups_without_data # Number of REs on data symbols num_res_data = 12 return num_data*num_res_data + num_dmrs*num_res_dmrs @property def dmrs_mask(self): """ bool, [num_subcarriers, num_symbols_per_slot], read-only : Masked resource elements in the resource grid. `True` corresponds to resource elements on which no data is transmitted. """ mask = np.zeros([self.num_subcarriers, self.carrier.num_symbols_per_slot], dtype=bool) num_cdm_groups = self.dmrs.num_cdm_groups_without_data if self.dmrs.config_type==1: cdm_ind = np.zeros([6, num_cdm_groups], np.int32) for i in range(num_cdm_groups): cdm_ind[:,i] = np.arange(i, 12, 2) else: cdm_ind = np.zeros([4, num_cdm_groups], np.int32) for i in range(num_cdm_groups): cdm_ind[:,i] = np.array([0,1, 6, 7])+2*i for i in self.dmrs_symbol_indices: for j in range(self.num_resource_blocks): for k in range(num_cdm_groups): mask[cdm_ind[:, k] + 12*j, i] = True return mask @property def dmrs_grid(self): # pylint: disable=line-too-long """ complex, [num_dmrs_ports, num_subcarriers, num_symbols_per_slot], read-only : Empty resource grid for each DMRS port, filled with DMRS signals This property returns for each configured DMRS port an empty resource grid filled with DMRS signals as defined in Section 6.4.1.1 [3GPP38211]. Not all possible options are implemented, e.g., frequency hopping and transform precoding are not available. This property provides the *unprecoded* DMRS for each configured DMRS port. Precoding might be applied to map the DMRS to the antenna ports. However, in this case, the number of DMRS ports cannot be larger than the number of layers. """ # Check configuration self.check_config() # Configure DMRS ports set if it has not been set reset_dmrs_port_set = False if len(self.dmrs.dmrs_port_set)==0: self.dmrs.dmrs_port_set = list(range(self.num_layers)) reset_dmrs_port_set = True # Generate empty resource grid for each port a_tilde = np.zeros([len(self.dmrs.dmrs_port_set), self.num_subcarriers, self.carrier.num_symbols_per_slot], dtype=complex) # For every l_bar for l_bar in self.l_bar: # For every l_prime for l_prime in self.l_prime: # Compute c_init l = l_bar + l_prime c_init = self.c_init(l) # Generate RNG c = generate_prng_seq(2*self.num_subcarriers, c_init=c_init) # Map to QAM r = 1/np.sqrt(2)*((1-2*c[::2]) + 1j*(1-2*c[1::2])) # For every port in the dmrs port set for j_ind, _ in enumerate(self.dmrs.dmrs_port_set): # For every n for n in self.n: # For every k_prime for k_prime in [0, 1]: if self.dmrs.config_type==1: k = 4*n + 2*k_prime + \ self.dmrs.deltas[j_ind] else: # config_type == 2 k = 6*n + k_prime + \ self.dmrs.deltas[j_ind] a_tilde[j_ind, k, self.l_ref+l] = \ r[2*n + k_prime] * \ self.dmrs.w_f[k_prime][j_ind] * \ self.dmrs.w_t[l_prime][j_ind] # Amplitude scaling a = self.dmrs.beta*a_tilde # Reset DMRS port set if it was not set if reset_dmrs_port_set: self.dmrs.dmrs_port_set = [] return a @property def dmrs_grid_precoded(self): if self.precoding=="non-codebook": return None w = np.expand_dims(np.expand_dims(self.precoding_matrix, 0), 0) a = np.expand_dims(np.transpose(self.dmrs_grid, [1,2,0]),-1) a = np.squeeze(np.matmul(w, a), -1) a = np.transpose(a, [2, 0, 1]) return a @property def precoding_matrix(self): r""" nd_array, complex, [num_antenna_ports, numLayers] : Precoding matrix :math:`\mathbf{W}` as defined in Tables 6.3.1.5-1 to 6.3.1.5-7 [3GPP38211]_. Only relevant if :class:`~sionna.nr.PUSCHCONFIG.precoding` is "codebook". """ if self.precoding=="non-codebook": return None if self.num_antenna_ports==1: return None w = None if self.num_layers==1: # Table 6.3.1.5-1 if self.num_antenna_ports==2: w = np.zeros([6,2,1], complex) #TPMI index 0-5 w[:,0,0] = [1, 0, 1, 1, 1, 1] w[:,1,0] = [0, 1, 1, -1, 1j,-1j] w /= np.sqrt(2) # Table 6.3.1.5-3 elif self.num_antenna_ports==4: w = np.zeros([28,4,1], complex) # TPMI index 0-7 w[:8,0,0] = [ 1, 0, 0, 0, 1, 1, 1, 1] w[:8,1,0] = [ 0, 1, 0, 0, 0, 0, 0, 0] w[:8,2,0] = [ 0, 0, 1, 0, 1, -1, 1j,-1j] w[:8,3,0] = [ 0, 0, 0, 1, 0, 0, 0, 0] # TPMI index 8-15 w[8:16,0,0] = [ 0, 0, 0, 0, 1, 1, 1, 1] w[8:16,1,0] = [ 1, 1, 1, 1, 1, 1, 1, 1] w[8:16,2,0] = [ 0, 0, 0, 0, 1, 1j, -1,-1j] w[8:16,3,0] = [ 1, -1, 1j,-1j, 1, 1j, -1,-1j] # TPMI index 16-23 w[16:24,0,0] = [ 1, 1, 1, 1, 1, 1, 1, 1] w[16:24,1,0] = [ 1j, 1j, 1j, 1j, -1, -1, -1, -1] w[16:24,2,0] = [ 1, 1j, -1,-1j, 1, 1j, -1,-1j] w[16:24,3,0] = [ 1j, -1,-1j, 1, -1,-1j, 1, 1j] # TPMI index 24-27 w[24:28,0,0] = [ 1, 1, 1, 1] w[24:28,1,0] = [-1j,-1j,-1j,-1j] w[24:28,2,0] = [ 1, 1j, -1,-1j] w[24:28,3,0] = [-1j, 1, 1j, -1] w /= 2 elif self.num_layers==2: # Table 6.3.1.5-4 if self.num_antenna_ports==2: w = np.zeros([3,2,2], complex) # TPMI index 0-2 w[0] = [[ 1, 0], [ 0, 1]] w[0] /= np.sqrt(2) w[1] = [[ 1, 1], [ 1, -1]] w[1] /= 2 w[2] = [[ 1, 1], [ 1j,-1j]] w[2] /= 2 # Table 6.3.1.5-5 elif self.num_antenna_ports==4: w = np.zeros([22,4,2], complex) # TPMI index 0-21 w[0] = [[ 1, 0], [ 0, 1], [ 0, 0], [ 0, 0]] w[0] /= 2 w[1] = [[ 1, 0], [ 0, 0], [ 0, 1], [ 0, 0]] w[1] /= 2 w[2] = [[ 1, 0], [ 0, 0], [ 0, 0], [ 0, 1]] w[2] /= 2 w[3] = [[ 0, 0], [ 1, 0], [ 0, 1], [ 0, 0]] w[3] /= 2 w[4] = [[ 0, 0], [ 1, 0], [ 0, 0], [ 0, 1]] w[4] /= 2 w[5] = [[ 0, 0], [ 0, 0], [ 1, 0], [ 0, 1]] w[5] /= 2 w[6] = [[ 1, 0], [ 0, 1], [ 1, 0], [ 0,-1j]] w[6] /= 2 w[7] = [[ 1, 0], [ 0, 1], [ 1, 0], [ 0, 1j]] w[7] /= 2 w[8] = [[ 1, 0], [ 0, 1], [-1j, 0], [ 0, 1]] w[8] /= 2 w[9] = [[ 1, 0], [ 0, 1], [-1j, 0], [ 0, -1]] w[9] /= 2 w[10] = [[ 1, 0], [ 0, 1], [ -1, 0], [ 0,-1j]] w[10] /= 2 w[11] = [[ 1, 0], [ 0, 1], [ -1, 0], [ 0, 1j]] w[11] /= 2 w[12] = [[ 1, 0], [ 0, 1], [ 1j, 0], [ 0, 1]] w[12] /= 2 w[13] = [[ 1, 0], [ 0, 1], [ 1j, 0], [ 0, -1]] w[13] /= 2 w[14] = [[ 1, 1], [ 1, 1], [ 1, -1], [ 1, -1]] w[14] /= 2*np.sqrt(2) w[15] = [[ 1, 1], [ 1, 1], [ 1j,-1j], [ 1j,-1j]] w[15] /= 2*np.sqrt(2) w[16] = [[ 1, 1], [ 1j, 1j], [ 1, -1], [ 1j,-1j]] w[16] /= 2*np.sqrt(2) w[17] = [[ 1, 1], [ 1j, 1j], [ 1j,-1j], [ -1, 1]] w[17] /= 2*np.sqrt(2) w[18] = [[ 1, 1], [ -1, -1], [ 1, -1], [ -1, 1]] w[18] /= 2*np.sqrt(2) w[19] = [[ 1, 1], [ -1, -1], [ 1j,-1j], [-1j, 1j]] w[19] /= 2*np.sqrt(2) w[20] = [[ 1, 1], [-1j,-1j], [ 1, -1], [-1j, 1j]] w[20] /= 2*np.sqrt(2) w[21] = [[ 1, 1], [-1j,-1j], [1j,-1j], [ 1, -1]] w[21] /= 2*np.sqrt(2) elif self.num_layers==3: # Table 6.3.1.5-6 if self.num_antenna_ports==4: w = np.zeros([7,4,3], complex) #TPMI index 0-6 w[0] = [[ 1, 0, 0], [ 0, 1, 0], [ 0, 0, 1], [ 0, 0, 0]] w[0] /= 2 w[1] = [[ 1, 0, 0], [ 0, 1, 0], [ 1, 0, 0], [ 0, 0, 1]] w[1] /= 2 w[2] = [[ 1, 0, 0], [ 0, 1, 0], [ -1, 0, 0], [ 0, 0, 1]] w[2] /= 2 w[3] = [[ 1, 1, 1], [ 1, -1, 1], [ 1, 1, -1], [ 1, -1, -1]] w[3] /= (2*np.sqrt(3)) w[4] = [[ 1, 1, 1], [ 1, -1, 1], [ 1j, 1j,-1j], [ 1j,-1j,-1j]] w[4] /= (2*np.sqrt(3)) w[5] = [[ 1, 1, 1], [ -1, 1, -1], [ 1, 1, -1], [ -1, 1, 1]] w[5] /= (2*np.sqrt(3)) w[6] = [[ 1, 1, 1], [ -1, 1, -1], [ 1j, 1j,-1j], [-1j, 1j, 1j]] w[6] /= (2*np.sqrt(3)) elif self.num_layers==4: # Table 6.3.1.5-7 if self.num_antenna_ports==4: w = np.zeros([5,4,4], complex) # TPMI index 0-4 w[0] = [[ 1, 0, 0, 0], [ 0, 1, 0, 0], [ 0, 0, 1, 0], [ 0, 0, 0, 1]] w[0] /= 2 w[1] = [[ 1, 1, 0, 0], [ 0, 0, 1, 1], [ 1, -1, 0, 0], [ 0, 0, 1, -1]] w[1] /= 2*np.sqrt(2) w[2] = [[ 1, 1, 0, 0], [ 0, 0, 1, 1], [ 1j,-1j, 0, 0], [ 0, 0, 1j,-1j]] w[2] /= 2*np.sqrt(2) w[3] = [[ 1, 1, 1, 1], [ 1, -1, 1, -1], [ 1, 1, -1, -1], [ 1, -1, -1, 1]] w[3] /= 4 w[4] = [[ 1, 1, 1, 1], [ 1, -1, 1, -1], [ 1j, 1j,-1j,-1j], [ 1j,-1j,-1j, 1j]] w[4] /= 4 if w is None: return w else: return w[self.tpmi] @property def num_ov(self): r""" int, 0 (default), read-only: Number of unused resource elements due to additional overhead as specified by higher layer.""" return 0 @property def num_coded_bits(self): r""" int, read-only: Number of coded bits that fit into one PUSCH slot.""" # compute number of data symbols n_re_per_prb = self.num_res_per_prb - self.num_ov # number of allocated REs n_re = n_re_per_prb * self.num_resource_blocks # total number of bits per slot num_coded_bits = int(self.tb.tb_scaling * self.tb.num_bits_per_symbol \ * self.num_layers * n_re) return num_coded_bits @property def tb_size(self): r"""int, read-only: Transport block size, i.e., how many information bits can be encoded into a slot for the given slot configuration.""" # compute number of data symbols per prb n_re_per_prb = self.num_res_per_prb - self.num_ov # number of allocated REs # the max. number of REs per PRB is limited to 156 in 38.214 n_re = min(156, n_re_per_prb) * self.num_resource_blocks # include tb_scaling as defined in Tab. 5.1.3.2-2 38.214 target_tb_size = int(self.tb.target_coderate * self.tb.tb_scaling \ * n_re * self.tb.num_bits_per_symbol * self.num_layers) # and run tb_size calculation to account for quantization tb_size, _, _, _, _,_ = calculate_tb_size( target_tb_size=target_tb_size, num_coded_bits=self.num_coded_bits, target_coderate=self.tb.target_coderate, modulation_order=self.tb.num_bits_per_symbol, verbose=False) return tb_size #-------------------# #---Class methods---# #-------------------#
[docs] def c_init(self, l): # pylint: disable=line-too-long r"""Compute RNG initialization :math:`c_\text{init}` as in Section 6.4.1.1.1.1 [3GPP38211]_ Input ----- l : int OFDM symbol index relative to a reference :math:`l` Output ------ c_init : int RNG initialization value """ num_symbols_per_slot = self.carrier.num_symbols_per_slot slot_number = self.carrier.slot_number lambda_bar = 0 n_scid_bar = self.dmrs.n_scid if self.dmrs.n_id is None: n_id = self.carrier.n_cell_id else: n_id = self.dmrs.n_id[n_scid_bar] c_init = np.mod(2**17 * (num_symbols_per_slot * slot_number + l + 1) \ * (2*n_id + 1) \ + 2**17 * np.floor(lambda_bar/2) \ + 2*n_id + n_scid_bar , 2**31) return int(c_init)
[docs] def show(self): """Print all properties of the PUSCHConfig and children""" self.carrier.show() Config.show(self) self.dmrs.show() self.tb.show()
def check_config(self): """Test if the compound configuration is valid""" self.carrier.check_config() self.dmrs.check_config() if self.precoding=="codebook": # Check that dmrs_port_set matches number of layers if len(self.dmrs.dmrs_port_set)>0: assert len(self.dmrs.dmrs_port_set)==self.num_layers, \ "num_layers must be equal to the number of dmrs ports" # Check that num_layers<=num_antenna_ports assert self.num_layers <= self.num_antenna_ports,\ "num_layers must be <= num_antenna_ports" # Check that more than one antenna port is available assert self.num_antenna_ports>=2, \ "precoding requires two or more antenna ports" else: # Check that num_layers==num_antenna_ports assert self.num_layers == self.num_antenna_ports,\ "num_layers must be == num_antenna_ports" # Check Tables 6.4.1.1.3-3/4 are valid if self.dmrs.length==1: if self.mapping_type=="A": assert self.symbol_allocation[1]>=4, \ "Symbol allocation is too short" else: assert self.dmrs.additional_position<2, \ "dmrs.additional_position must be <2 for this dmrs.length" assert self.symbol_allocation[1]>=4, "Symbol allocation too short" if self.mapping_type=="B": assert self.symbol_allocation[1]>=5, \ "Symbol allocation is too short" # Check type_a and additional_position position if self.mapping_type=="A": if self.dmrs.additional_position==3: assert self.dmrs.type_a_position==2,\ "additional_position=3 only allowed for type_a_position=2" # Check for valid TMPI if self.num_layers==1: if self.num_antenna_ports==2: assert self.tpmi in range(6), "tpmi must be in [0,...,5]" elif self.num_antenna_ports==4: assert self.tpmi in range(28), "tpmi must be in [0,...,27]" elif self.num_layers==2: if self.num_antenna_ports==2: assert self.tpmi in range(3), "tpmi must be in [0,...,2]" elif self.num_antenna_ports==4: assert self.tpmi in range(22), "tpmi must be in [0,...,21]" elif self.num_layers==3: assert self.tpmi in range(7), "tpmi must be in [0,...,6]" elif self.num_layers==4: assert self.tpmi in range(5), "tpmi must be in [0,...,4]" # Check that symbol allocation is valid if self.carrier.cyclic_prefix=="normal": max_length = 14 else: # cyclic_prefix == "extended" max_length = 12 if self.mapping_type=="A": assert self.symbol_allocation[0]==0, \ "symbol_allocation[0] must be 0 for mapping_type A" assert 4<=self.symbol_allocation[1]<=max_length, \ "symbol_allocation[1] must be in [4, 14 (or 12)]" if self.dmrs.length==2: assert self.symbol_allocation[1]>=4, \ "symbol_allocation[1] must be >=4 for dmrs.length==2" elif self.mapping_type=="B": assert 0<=self.symbol_allocation[0]<=13, \ "symbol_allocation[0] must be in [0,13] for mapping_type B" assert 1<=self.symbol_allocation[1]<=max_length, \ "symbol_allocation[1] must be in [1, 14 (or 12)]" if self.dmrs.length==2: assert self.symbol_allocation[1]>=5, \ "symbol_allocation[1] must be >=5 for dmrs.length==2" assert self.symbol_allocation[0] \ + self.symbol_allocation[1]<=max_length, \ "symbol_allocation[0]+symbol_allocation[1] must be < 14 (or 12)" attr_list = ["n_size_bwp", "n_start_bwp", "num_layers", "mapping_type", "symbol_allocation", "n_rnti", "precoding", "transform_precoding", "tpmi" ] for attr in attr_list: value = getattr(self, attr) setattr(self, attr, value) # check that TBConfig is configured for "PUSCH" assert self.tb.channel_type=="PUSCH", \ 'TB_config must be configured for "PUSCH" transmission.' # Check that the number of DMRS ports equals the number of layers # if dmrs_port_set has been set. Otherwise, this is # automatically ensured. if len(self.dmrs.dmrs_port_set)>0: assert self.num_layers==len(self.dmrs.dmrs_port_set), \ "num_layers must equal the number of DMRS ports" return True
def check_pusch_configs(pusch_configs): # Check that pusch_configs is a list assert isinstance(pusch_configs, list), \ """pusch_configs must be a Sequence of instances of PUSCHConfig""" # Iterate through all pusch_configs and check their type and validity for pusch_config in pusch_configs: assert isinstance(pusch_config, PUSCHConfig), \ """All elements of pusch_configs must be instances of PUSCHConfig""" pusch_config.check_config() # Create dictionary with extracted configuration parameters pc = pusch_configs[0] carrier = pc.carrier params = { "num_bits_per_symbol" : pc.tb.num_bits_per_symbol, "num_tx" : len(pusch_configs), "num_layers" : pc.num_layers, "num_subcarriers" : pc.num_subcarriers, "num_ofdm_symbols" : pc.symbol_allocation[1], "subcarrier_spacing" : pc.carrier.subcarrier_spacing*1e3, "num_antenna_ports" : pc.num_antenna_ports, "precoding" : pc.precoding, "precoding_matrices" : [], "pusch_config" : pc, "carrier_config" : pc.carrier, "num_coded_bits" : pc.num_coded_bits, "target_coderate" : pc.tb.target_coderate, "n_id" : [], "n_rnti" : [], "tb_size" : pc.tb_size, "dmrs_length" : pc.dmrs.length, "dmrs_additional_position" : pc.dmrs.additional_position, "num_cdm_groups_without_data" : pc.dmrs.num_cdm_groups_without_data } params["bandwidth"] = params["num_subcarriers"]*params["subcarrier_spacing"] params["cyclic_prefix_length"] = np.ceil(carrier.cyclic_prefix_length * params["bandwidth"]) for pusch_config in pusch_configs: if params["precoding"]=="codebook": params["precoding_matrices"].append(pusch_config.precoding_matrix) # n_rnti and n_id are given per tx if pusch_config.tb.n_id is None: params["n_id"].append(pusch_config.carrier.n_cell_id) else: params["n_id"].append(pusch_config.tb.n_id) params["n_rnti"].append(pusch_config.n_rnti) return params