pusch_config.py (37683B)
1 # 2 # SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 # SPDX-License-Identifier: Apache-2.0 4 # 5 """PUSCH configuration for the nr (5G) sub-package of the Sionna library. 6 """ 7 # pylint: disable=line-too-long 8 9 import numpy as np 10 from .utils import generate_prng_seq 11 from .config import Config 12 from sionna import nr 13 from .utils import calculate_tb_size 14 15 class PUSCHConfig(Config): 16 """ 17 The PUSCHConfig objects sets parameters for a physical uplink shared 18 channel (PUSCH), as described in Sections 6.3 and 6.4 [3GPP38211]_. 19 20 All configurable properties can be provided as keyword arguments during the 21 initialization or changed later. 22 23 Parameters 24 ---------- 25 carrier_config : :class:`~sionna.nr.CarrierConfig` or `None` 26 An instance of :class:`~sionna.nr.CarrierConfig`. If `None`, a 27 :class:`~sionna.nr.CarrierConfig` instance with default settings 28 will be created. 29 30 pusch_dmrs_config : :class:`~sionna.nr.PUSCHDMRSConfig` or `None` 31 An instance of :class:`~sionna.nr.PUSCHDMRSConfig`. If `None`, a 32 :class:`~sionna.nr.PUSCHDMRSConfig` instance with default settings 33 will be created. 34 35 Example 36 ------- 37 >>> pusch_config = PUSCHConfig(mapping_type="B") 38 >>> pusch_config.dmrs.config_type = 2 39 >>> pusch_config.carrier.subcarrier_spacing = 30 40 """ 41 def __init__(self, 42 carrier_config=None, 43 pusch_dmrs_config=None, 44 tb_config=None, 45 **kwargs): 46 super().__init__(**kwargs) 47 self._name = "PUSCH Configuration" 48 self.carrier = carrier_config 49 self.dmrs = pusch_dmrs_config 50 self.tb = tb_config 51 self.check_config() 52 53 #-----------------------------# 54 #---Configurable parameters---# 55 #-----------------------------# 56 57 #---carrier---# 58 @property 59 def carrier(self): 60 """ 61 :class:`~sionna.nr.CarrierConfig` : Carrier configuration 62 """ 63 return self._carrier 64 65 @carrier.setter 66 def carrier(self, value): 67 if value is None: 68 value = nr.CarrierConfig() 69 else: 70 assert isinstance(value, nr.CarrierConfig), \ 71 "carrier must be an instance of CarrierConfig" 72 self._carrier = value 73 74 #---dmrs---# 75 @property 76 def dmrs(self): 77 """ 78 :class:`~sionna.nr.PUSCHDMRSConfig` : PUSCH DMRS configuration 79 """ 80 return self._dmrs 81 82 @dmrs.setter 83 def dmrs(self, value): 84 if value is None: 85 value = nr.PUSCHDMRSConfig() 86 else: 87 assert isinstance(value, nr.PUSCHDMRSConfig), \ 88 "pusch_dmrs_config must be an instance of PUSCHDMRSConfig" 89 self._dmrs = value 90 91 #---transport block---# 92 @property 93 def tb(self): 94 """ 95 :class:`~sionna.nr.TBConfig` : Transport block configuration 96 """ 97 return self._tb 98 99 @tb.setter 100 def tb(self, value): 101 if value is None: 102 value = nr.TBConfig(channel_type="PUSCH") 103 else: 104 assert isinstance(value, nr.TBConfig), \ 105 "tb must be an instance of TBConfig" 106 assert value.channel_type=="PUSCH",\ 107 'TBConfig must be configured for "PUSCH"' 108 self._tb = value 109 110 #---n_size_bwp---# 111 @property 112 def n_size_bwp(self): 113 r""" 114 int, None (default), [1,...,275] : Number of resource blocks in the 115 bandwidth part (BWP) :math:`N^{\text{size},\mu}_{\text{BWP},i}` 116 117 If set to `None`, the property 118 :class:`~sionna.nr.CarrierConfig.n_size_grid` of 119 `carrier` will be used. 120 """ 121 self._ifndef("n_size_bwp", None) 122 return self._n_size_bwp 123 124 @n_size_bwp.setter 125 def n_size_bwp(self, value): 126 if value is not None: 127 assert value in range(1,276),\ 128 "n_size_bwp must be in the range from 1 to 275" 129 self._n_size_bwp = value 130 131 #---n_start_bwp---# 132 @property 133 def n_start_bwp(self): 134 r""" 135 int, 0 (default) | [0,...,2199] : Start of BWP relative to 136 common resource block (CRB) 0 137 :math:`N^{\text{start},\mu}_{\text{BWP},i}` 138 """ 139 self._ifndef("n_start_bwp", 0) 140 return self._n_start_bwp 141 142 @n_start_bwp.setter 143 def n_start_bwp(self, value): 144 assert value in range(0,2474), \ 145 "n_start_bwp must be in the range from 0 to 2473" 146 self._n_start_bwp = value 147 148 149 #---num-layers---# 150 @property 151 def num_layers(self): 152 r""" 153 int, 1 (default) | 2 | 3 | 4: Number of transmission layers 154 :math:`\nu` 155 156 Must be smaller than or equal to 157 :class:`~sionna.nr.PUSCHConfig.num_antenna_ports`. 158 """ 159 self._ifndef("num_layers", 1) 160 return self._num_layers 161 162 @num_layers.setter 163 def num_layers(self, value): 164 assert value in [1,2,3,4], "num_layers must be in [1,...,4]" 165 self._num_layers = value 166 167 #---num-antenna-ports---# 168 @property 169 def num_antenna_ports(self): 170 """ 171 int, 1 (default) | 2 | 4: Number of antenna ports 172 173 Must be larger than or equal to 174 :class:`~sionna.nr.PUSCHConfig.num_layers`. 175 """ 176 self._ifndef("num_antenna_ports", 1) 177 return self._num_antenna_ports 178 179 @num_antenna_ports.setter 180 def num_antenna_ports(self, value): 181 assert value in [1,2,4], "num_antenna_ports must be in [1,2,4]" 182 self._num_antenna_ports = value 183 184 #---mapping_type---# 185 @property 186 def mapping_type(self): 187 """ 188 string, "A" (default) | "B": Mapping type 189 """ 190 self._ifndef("mapping_type", "A") 191 return self._mapping_type 192 193 @mapping_type.setter 194 def mapping_type(self, value): 195 assert value in ["A","B"], "mapping_type must be A or B" 196 self._mapping_type = value 197 198 #---symbol_allocation---# 199 @property 200 def symbol_allocation(self): 201 """ 202 2-tuple, int, [0, 14] (default) : PUSCH symbol allocation 203 204 The first elements denotes the start of the symbol allocation. 205 The second denotes the positive number of allocated OFDM symbols. 206 For `mapping_type` "A", the first element must be zero. 207 For `mapping_type` "B", the first element must be in 208 [0,...,13]. The second element must be such that the index 209 of the last allocated OFDM symbol is not larger than 13 210 (for "normal" cyclic prefix) or 11 (for "extended" cyclic prefix). 211 """ 212 self._ifndef("symbol_allocation", [0, 14]) 213 return self._symbol_allocation 214 215 @symbol_allocation.setter 216 def symbol_allocation(self, value): 217 assert len(value)==2, "symbol_allocation must have two elements" 218 self._symbol_allocation = value 219 220 #---n_rnti---# 221 @property 222 def n_rnti(self): 223 r""" 224 int, 1 (default), [0,...,65535] : Radio network temporary identifier 225 :math:`n_\text{RNTI}` 226 """ 227 self._ifndef("n_rnti", 1) 228 return self._n_rnti 229 230 @n_rnti.setter 231 def n_rnti(self, value): 232 if value is not None: 233 assert value in range(65536), "n_rnti must be in [0, 65535]" 234 self._n_rnti = value 235 236 #---transform_precoding---# 237 @property 238 def precoding(self): 239 """ 240 str, "non-codebook" (default), "codebook" : PUSCH 241 transmission scheme 242 """ 243 self._ifndef("precoding", "non-codebook") 244 return self._precoding 245 246 @precoding.setter 247 def precoding(self, value): 248 assert value in ["codebook", "non-codebook"], \ 249 "Unknown value for precoding" 250 self._precoding = value 251 252 #---transform_precoding---# 253 @property 254 def transform_precoding(self): 255 """ 256 bool, False (default): Use transform precoding 257 """ 258 self._ifndef("transform_precoding", False) 259 return self._transform_precoding 260 261 @transform_precoding.setter 262 def transform_precoding(self, value): 263 assert isinstance(value, bool), \ 264 """transform_precoding must be bool""" 265 self._transform_precoding = value 266 267 #---tpmi---# 268 @property 269 def tpmi(self): 270 r""" 271 int, 0 (default) | [0,...,27] : Transmit precoding matrix indicator 272 273 The allowed value depends on the number of layers and 274 the number of antenna ports according to Table 6.3.1.5-1 275 until Table 6.3.1.5-7 [3GPP38211]_. 276 """ 277 self._ifndef("tpmi", 0) 278 return self._tpmi 279 280 @tpmi.setter 281 def tpmi(self, value): 282 assert value in range(28), "tpmi must be in [0,...,27]" 283 self._tpmi = value 284 285 #-----------------------------# 286 #---Read-only parameters------# 287 #-----------------------------# 288 289 @property 290 def frequency_hopping(self): 291 """ 292 str, "neither" (default), read-only : Frequency hopping configuration 293 """ 294 return "neither" 295 296 @property 297 def l_0(self): 298 r""" 299 int, read-only : Position of the first DMRS symbol :math:`l_0` 300 relative to the reference `l_ref`. 301 """ 302 if self.mapping_type=="A": 303 return self.dmrs.type_a_position 304 elif self.mapping_type=="B": 305 return 0 306 307 @property 308 def l_d(self): 309 r""" 310 int, read-only : Length of the symbol allocation :math:`l_\text{d}` 311 """ 312 return self.symbol_allocation[1] 313 314 @property 315 def l_ref(self): 316 r""" 317 int, read-only: Reference OFDM symbol index used for DMRS 318 generation 319 """ 320 if self.mapping_type=="A": 321 return 0 322 elif self. mapping_type=="B": 323 return self.symbol_allocation[0] 324 325 @property 326 def l_prime(self): 327 r""" 328 list, elements in [0,1], read-only : List of possible values of 329 :math:`l'` used for DMRS generation 330 """ 331 if self.dmrs.length==1: 332 return [0] 333 elif self.dmrs.length==2: 334 return [0, 1] 335 336 @property 337 def l_bar(self): 338 r""" 339 list, elements in [0,...,11], read-only : List of possible values of 340 :math:`\bar{l}` used for DMRS generation 341 342 Defined in Tables 6.4.1.1.3-3 and 6.4.1.1.3-4 [3GPP38211]_. 343 """ 344 l_0 = self.l_0 345 ind = 0 if self.l_d<4 else self.l_d-3 346 if self.mapping_type=="A": 347 if self.dmrs.length==1: 348 l_bar = [ 349 [[], [], [], []], 350 [[l_0], [l_0], [l_0], [l_0]], 351 [[l_0], [l_0], [l_0], [l_0]], 352 [[l_0], [l_0], [l_0], [l_0]], 353 [[l_0], [l_0], [l_0], [l_0]], 354 [[l_0], [l_0, 7], [l_0, 7], [l_0, 7]], 355 [[l_0], [l_0, 7], [l_0, 7], [l_0, 7]], 356 [[l_0], [l_0, 9], [l_0, 6, 9], [l_0, 6, 9]], 357 [[l_0], [l_0, 9], [l_0, 6, 9], [l_0, 6, 9]], 358 [[l_0], [l_0, 9], [l_0, 6, 9], [l_0, 5, 8, 11]], 359 [[l_0], [l_0, 11], [l_0, 7, 11], [l_0, 5, 8, 11]], 360 [[l_0], [l_0, 11], [l_0, 7, 11], [l_0, 5, 8, 11]] 361 ] 362 else: 363 l_bar = [ 364 [[], []], 365 [[l_0], [l_0]], 366 [[l_0], [l_0]], 367 [[l_0], [l_0]], 368 [[l_0], [l_0]], 369 [[l_0], [l_0]], 370 [[l_0], [l_0]], 371 [[l_0], [l_0, 8]], 372 [[l_0], [l_0, 8]], 373 [[l_0], [l_0, 8]], 374 [[l_0], [l_0, 10]], 375 [[l_0], [l_0, 10]], 376 ] 377 else: # mapping_type == "B" 378 if self.dmrs.length==1: 379 l_bar = [ 380 [[l_0], [l_0], [l_0], [l_0]], 381 [[l_0], [l_0], [l_0], [l_0]], 382 [[l_0], [l_0, 4], [l_0, 4], [l_0, 4]], 383 [[l_0], [l_0, 4], [l_0, 4], [l_0, 4]], 384 [[l_0], [l_0, 4], [l_0, 4], [l_0, 4]], 385 [[l_0], [l_0, 6], [l_0, 3, 6], [l_0, 3, 6]], 386 [[l_0], [l_0, 6], [l_0, 3, 6], [l_0, 3, 6]], 387 [[l_0], [l_0, 8], [l_0, 4, 8], [l_0, 3, 6, 9]], 388 [[l_0], [l_0, 8], [l_0, 4, 8], [l_0, 3, 6, 9]], 389 [[l_0], [l_0, 10], [l_0, 5, 10], [l_0, 3, 6, 9]], 390 [[l_0], [l_0, 10], [l_0, 5, 10], [l_0, 3, 6, 9]], 391 [[l_0], [l_0, 10], [l_0, 5, 10], [l_0, 3, 6, 9]] 392 ] 393 else: 394 l_bar = [ 395 [[], []], 396 [[], []], 397 [[l_0], [l_0]], 398 [[l_0], [l_0]], 399 [[l_0], [l_0]], 400 [[l_0], [l_0, 5]], 401 [[l_0], [l_0, 5]], 402 [[l_0], [l_0, 7]], 403 [[l_0], [l_0, 7]], 404 [[l_0], [l_0, 9]], 405 [[l_0], [l_0, 9]], 406 [[l_0], [l_0, 9]], 407 ] 408 409 return l_bar[ind][self.dmrs.additional_position] 410 411 @property 412 def l(self): 413 r""" 414 list, int, read-only : List of possible values of the OFDM symbol 415 indices :math:`l` carrying DMRS relative to :math:`l_0` 416 """ 417 l = [] 418 for l_bar in self.l_bar: 419 for l_prime in self.l_prime: 420 l.append(l_bar + l_prime) 421 return l 422 423 @property 424 def n(self): 425 """ 426 list, int, read-only: List of possible values of n 427 used for DMRS generation 428 """ 429 if self.dmrs.config_type==1: 430 n_max = self.num_resource_blocks*12//4 -1 431 else: # config_type == 2 432 n_max = self.num_resource_blocks*12//6 -1 433 return list(range(n_max+1)) 434 435 @property 436 def dmrs_symbol_indices(self): 437 """ 438 list, int, read-only: Indices of DMRS symbols within a slot 439 """ 440 return [l + self.l_ref for l in self.l] 441 442 @property 443 def num_resource_blocks(self): 444 """ 445 int, read-only : Number of allocated resource blocks for the 446 PUSCH transmissions. 447 """ 448 if self.n_size_bwp is None: 449 return self.carrier.n_size_grid 450 else: 451 return self.n_size_bwp 452 453 @property 454 def num_subcarriers(self): 455 """ 456 int, read-only : Number of allocated subcarriers for the 457 PUSCH transmissions 458 """ 459 return 12*self.num_resource_blocks 460 461 @property 462 def num_res_per_prb(self): 463 """ 464 int, read-only : Number of resource elements per PRB 465 available for data 466 """ 467 # Number of DMRS symbols 468 num_dmrs = len(self.dmrs_symbol_indices) 469 470 # Number of non-DMRS symbols 471 num_data = self.symbol_allocation[1] - num_dmrs 472 473 # Number of REs on DMRS symbols 474 if self.dmrs.config_type==1: 475 num_res_dmrs = 12 - 6*self.dmrs.num_cdm_groups_without_data 476 else: # dmrs.config_type == 2 477 num_res_dmrs = 12 - 4*self.dmrs.num_cdm_groups_without_data 478 479 # Number of REs on data symbols 480 num_res_data = 12 481 482 return num_data*num_res_data + num_dmrs*num_res_dmrs 483 484 @property 485 def dmrs_mask(self): 486 """ 487 bool, [num_subcarriers, num_symbols_per_slot], read-only : Masked 488 resource elements in the resource grid. `True` corresponds to 489 resource elements on which no data is transmitted. 490 """ 491 mask = np.zeros([self.num_subcarriers, 492 self.carrier.num_symbols_per_slot], 493 dtype=bool) 494 495 num_cdm_groups = self.dmrs.num_cdm_groups_without_data 496 if self.dmrs.config_type==1: 497 cdm_ind = np.zeros([6, num_cdm_groups], np.int32) 498 for i in range(num_cdm_groups): 499 cdm_ind[:,i] = np.arange(i, 12, 2) 500 else: 501 cdm_ind = np.zeros([4, num_cdm_groups], np.int32) 502 for i in range(num_cdm_groups): 503 cdm_ind[:,i] = np.array([0,1, 6, 7])+2*i 504 505 for i in self.dmrs_symbol_indices: 506 for j in range(self.num_resource_blocks): 507 for k in range(num_cdm_groups): 508 mask[cdm_ind[:, k] + 12*j, i] = True 509 return mask 510 511 @property 512 def dmrs_grid(self): 513 # pylint: disable=line-too-long 514 """ 515 complex, [num_dmrs_ports, num_subcarriers, num_symbols_per_slot], read-only : Empty 516 resource grid for each DMRS port, filled with DMRS signals 517 518 This property returns for each configured DMRS port an empty 519 resource grid filled with DMRS signals as defined in 520 Section 6.4.1.1 [3GPP38211]. Not all possible options are implemented, 521 e.g., frequency hopping and transform precoding are not available. 522 523 This property provides the *unprecoded* DMRS for each configured DMRS port. 524 Precoding might be applied to map the DMRS to the antenna ports. However, 525 in this case, the number of DMRS ports cannot be larger than the number of 526 layers. 527 """ 528 # Check configuration 529 self.check_config() 530 531 # Configure DMRS ports set if it has not been set 532 reset_dmrs_port_set = False 533 if len(self.dmrs.dmrs_port_set)==0: 534 self.dmrs.dmrs_port_set = list(range(self.num_layers)) 535 reset_dmrs_port_set = True 536 537 # Generate empty resource grid for each port 538 a_tilde = np.zeros([len(self.dmrs.dmrs_port_set), 539 self.num_subcarriers, 540 self.carrier.num_symbols_per_slot], 541 dtype=complex) 542 543 # For every l_bar 544 for l_bar in self.l_bar: 545 546 # For every l_prime 547 for l_prime in self.l_prime: 548 549 # Compute c_init 550 l = l_bar + l_prime 551 c_init = self.c_init(l) 552 553 # Generate RNG 554 c = generate_prng_seq(2*self.num_subcarriers, c_init=c_init) 555 556 # Map to QAM 557 r = 1/np.sqrt(2)*((1-2*c[::2]) + 1j*(1-2*c[1::2])) 558 559 # For every port in the dmrs port set 560 for j_ind, _ in enumerate(self.dmrs.dmrs_port_set): 561 562 # For every n 563 for n in self.n: 564 565 # For every k_prime 566 for k_prime in [0, 1]: 567 568 if self.dmrs.config_type==1: 569 k = 4*n + 2*k_prime + \ 570 self.dmrs.deltas[j_ind] 571 else: # config_type == 2 572 k = 6*n + k_prime + \ 573 self.dmrs.deltas[j_ind] 574 575 a_tilde[j_ind, k, self.l_ref+l] = \ 576 r[2*n + k_prime] * \ 577 self.dmrs.w_f[k_prime][j_ind] * \ 578 self.dmrs.w_t[l_prime][j_ind] 579 580 # Amplitude scaling 581 a = self.dmrs.beta*a_tilde 582 583 # Reset DMRS port set if it was not set 584 if reset_dmrs_port_set: 585 self.dmrs.dmrs_port_set = [] 586 587 return a 588 589 @property 590 def dmrs_grid_precoded(self): 591 if self.precoding=="non-codebook": 592 return None 593 594 w = np.expand_dims(np.expand_dims(self.precoding_matrix, 0), 0) 595 a = np.expand_dims(np.transpose(self.dmrs_grid, [1,2,0]),-1) 596 a = np.squeeze(np.matmul(w, a), -1) 597 a = np.transpose(a, [2, 0, 1]) 598 599 return a 600 601 @property 602 def precoding_matrix(self): 603 r""" 604 nd_array, complex, [num_antenna_ports, numLayers] : Precoding matrix 605 :math:`\mathbf{W}` as defined in 606 Tables 6.3.1.5-1 to 6.3.1.5-7 [3GPP38211]_. 607 608 Only relevant if :class:`~sionna.nr.PUSCHCONFIG.precoding` 609 is "codebook". 610 """ 611 if self.precoding=="non-codebook": 612 return None 613 if self.num_antenna_ports==1: 614 return None 615 w = None 616 if self.num_layers==1: 617 618 # Table 6.3.1.5-1 619 if self.num_antenna_ports==2: 620 w = np.zeros([6,2,1], complex) 621 622 #TPMI index 0-5 623 w[:,0,0] = [1, 0, 1, 1, 1, 1] 624 w[:,1,0] = [0, 1, 1, -1, 1j,-1j] 625 626 w /= np.sqrt(2) 627 628 # Table 6.3.1.5-3 629 elif self.num_antenna_ports==4: 630 w = np.zeros([28,4,1], complex) 631 632 # TPMI index 0-7 633 w[:8,0,0] = [ 1, 0, 0, 0, 1, 1, 1, 1] 634 w[:8,1,0] = [ 0, 1, 0, 0, 0, 0, 0, 0] 635 w[:8,2,0] = [ 0, 0, 1, 0, 1, -1, 1j,-1j] 636 w[:8,3,0] = [ 0, 0, 0, 1, 0, 0, 0, 0] 637 638 # TPMI index 8-15 639 w[8:16,0,0] = [ 0, 0, 0, 0, 1, 1, 1, 1] 640 w[8:16,1,0] = [ 1, 1, 1, 1, 1, 1, 1, 1] 641 w[8:16,2,0] = [ 0, 0, 0, 0, 1, 1j, -1,-1j] 642 w[8:16,3,0] = [ 1, -1, 1j,-1j, 1, 1j, -1,-1j] 643 644 # TPMI index 16-23 645 w[16:24,0,0] = [ 1, 1, 1, 1, 1, 1, 1, 1] 646 w[16:24,1,0] = [ 1j, 1j, 1j, 1j, -1, -1, -1, -1] 647 w[16:24,2,0] = [ 1, 1j, -1,-1j, 1, 1j, -1,-1j] 648 w[16:24,3,0] = [ 1j, -1,-1j, 1, -1,-1j, 1, 1j] 649 650 # TPMI index 24-27 651 w[24:28,0,0] = [ 1, 1, 1, 1] 652 w[24:28,1,0] = [-1j,-1j,-1j,-1j] 653 w[24:28,2,0] = [ 1, 1j, -1,-1j] 654 w[24:28,3,0] = [-1j, 1, 1j, -1] 655 656 w /= 2 657 658 elif self.num_layers==2: 659 660 # Table 6.3.1.5-4 661 if self.num_antenna_ports==2: 662 w = np.zeros([3,2,2], complex) 663 664 # TPMI index 0-2 665 w[0] = [[ 1, 0], [ 0, 1]] 666 w[0] /= np.sqrt(2) 667 w[1] = [[ 1, 1], [ 1, -1]] 668 w[1] /= 2 669 w[2] = [[ 1, 1], [ 1j,-1j]] 670 w[2] /= 2 671 672 # Table 6.3.1.5-5 673 elif self.num_antenna_ports==4: 674 w = np.zeros([22,4,2], complex) 675 676 # TPMI index 0-21 677 w[0] = [[ 1, 0], [ 0, 1], [ 0, 0], [ 0, 0]] 678 w[0] /= 2 679 w[1] = [[ 1, 0], [ 0, 0], [ 0, 1], [ 0, 0]] 680 w[1] /= 2 681 w[2] = [[ 1, 0], [ 0, 0], [ 0, 0], [ 0, 1]] 682 w[2] /= 2 683 w[3] = [[ 0, 0], [ 1, 0], [ 0, 1], [ 0, 0]] 684 w[3] /= 2 685 w[4] = [[ 0, 0], [ 1, 0], [ 0, 0], [ 0, 1]] 686 w[4] /= 2 687 w[5] = [[ 0, 0], [ 0, 0], [ 1, 0], [ 0, 1]] 688 w[5] /= 2 689 w[6] = [[ 1, 0], [ 0, 1], [ 1, 0], [ 0,-1j]] 690 w[6] /= 2 691 w[7] = [[ 1, 0], [ 0, 1], [ 1, 0], [ 0, 1j]] 692 w[7] /= 2 693 w[8] = [[ 1, 0], [ 0, 1], [-1j, 0], [ 0, 1]] 694 w[8] /= 2 695 w[9] = [[ 1, 0], [ 0, 1], [-1j, 0], [ 0, -1]] 696 w[9] /= 2 697 w[10] = [[ 1, 0], [ 0, 1], [ -1, 0], [ 0,-1j]] 698 w[10] /= 2 699 w[11] = [[ 1, 0], [ 0, 1], [ -1, 0], [ 0, 1j]] 700 w[11] /= 2 701 w[12] = [[ 1, 0], [ 0, 1], [ 1j, 0], [ 0, 1]] 702 w[12] /= 2 703 w[13] = [[ 1, 0], [ 0, 1], [ 1j, 0], [ 0, -1]] 704 w[13] /= 2 705 w[14] = [[ 1, 1], [ 1, 1], [ 1, -1], [ 1, -1]] 706 w[14] /= 2*np.sqrt(2) 707 w[15] = [[ 1, 1], [ 1, 1], [ 1j,-1j], [ 1j,-1j]] 708 w[15] /= 2*np.sqrt(2) 709 w[16] = [[ 1, 1], [ 1j, 1j], [ 1, -1], [ 1j,-1j]] 710 w[16] /= 2*np.sqrt(2) 711 w[17] = [[ 1, 1], [ 1j, 1j], [ 1j,-1j], [ -1, 1]] 712 w[17] /= 2*np.sqrt(2) 713 w[18] = [[ 1, 1], [ -1, -1], [ 1, -1], [ -1, 1]] 714 w[18] /= 2*np.sqrt(2) 715 w[19] = [[ 1, 1], [ -1, -1], [ 1j,-1j], [-1j, 1j]] 716 w[19] /= 2*np.sqrt(2) 717 w[20] = [[ 1, 1], [-1j,-1j], [ 1, -1], [-1j, 1j]] 718 w[20] /= 2*np.sqrt(2) 719 w[21] = [[ 1, 1], [-1j,-1j], [1j,-1j], [ 1, -1]] 720 w[21] /= 2*np.sqrt(2) 721 722 elif self.num_layers==3: 723 724 # Table 6.3.1.5-6 725 if self.num_antenna_ports==4: 726 w = np.zeros([7,4,3], complex) 727 728 #TPMI index 0-6 729 w[0] = [[ 1, 0, 0], 730 [ 0, 1, 0], 731 [ 0, 0, 1], 732 [ 0, 0, 0]] 733 w[0] /= 2 734 735 w[1] = [[ 1, 0, 0], 736 [ 0, 1, 0], 737 [ 1, 0, 0], 738 [ 0, 0, 1]] 739 w[1] /= 2 740 741 w[2] = [[ 1, 0, 0], 742 [ 0, 1, 0], 743 [ -1, 0, 0], 744 [ 0, 0, 1]] 745 w[2] /= 2 746 747 w[3] = [[ 1, 1, 1], 748 [ 1, -1, 1], 749 [ 1, 1, -1], 750 [ 1, -1, -1]] 751 w[3] /= (2*np.sqrt(3)) 752 753 w[4] = [[ 1, 1, 1], 754 [ 1, -1, 1], 755 [ 1j, 1j,-1j], 756 [ 1j,-1j,-1j]] 757 w[4] /= (2*np.sqrt(3)) 758 759 w[5] = [[ 1, 1, 1], 760 [ -1, 1, -1], 761 [ 1, 1, -1], 762 [ -1, 1, 1]] 763 w[5] /= (2*np.sqrt(3)) 764 765 w[6] = [[ 1, 1, 1], 766 [ -1, 1, -1], 767 [ 1j, 1j,-1j], 768 [-1j, 1j, 1j]] 769 w[6] /= (2*np.sqrt(3)) 770 771 elif self.num_layers==4: 772 773 # Table 6.3.1.5-7 774 if self.num_antenna_ports==4: 775 w = np.zeros([5,4,4], complex) 776 777 # TPMI index 0-4 778 w[0] = [[ 1, 0, 0, 0], 779 [ 0, 1, 0, 0], 780 [ 0, 0, 1, 0], 781 [ 0, 0, 0, 1]] 782 w[0] /= 2 783 784 w[1] = [[ 1, 1, 0, 0], 785 [ 0, 0, 1, 1], 786 [ 1, -1, 0, 0], 787 [ 0, 0, 1, -1]] 788 w[1] /= 2*np.sqrt(2) 789 790 w[2] = [[ 1, 1, 0, 0], 791 [ 0, 0, 1, 1], 792 [ 1j,-1j, 0, 0], 793 [ 0, 0, 1j,-1j]] 794 w[2] /= 2*np.sqrt(2) 795 796 w[3] = [[ 1, 1, 1, 1], 797 [ 1, -1, 1, -1], 798 [ 1, 1, -1, -1], 799 [ 1, -1, -1, 1]] 800 w[3] /= 4 801 802 w[4] = [[ 1, 1, 1, 1], 803 [ 1, -1, 1, -1], 804 [ 1j, 1j,-1j,-1j], 805 [ 1j,-1j,-1j, 1j]] 806 w[4] /= 4 807 808 if w is None: 809 return w 810 else: 811 return w[self.tpmi] 812 813 @property 814 def num_ov(self): 815 r""" 816 int, 0 (default), read-only: Number of unused resource elements due to additional overhead as specified by higher layer.""" 817 return 0 818 819 @property 820 def num_coded_bits(self): 821 r""" 822 int, read-only: Number of coded bits that fit into one PUSCH slot.""" 823 824 # compute number of data symbols 825 n_re_per_prb = self.num_res_per_prb - self.num_ov 826 827 # number of allocated REs 828 n_re = n_re_per_prb * self.num_resource_blocks 829 830 # total number of bits per slot 831 num_coded_bits = int(self.tb.tb_scaling * self.tb.num_bits_per_symbol \ 832 * self.num_layers * n_re) 833 834 return num_coded_bits 835 836 @property 837 def tb_size(self): 838 r"""int, read-only: Transport block size, i.e., how many information bits can be encoded into a slot for the given slot configuration.""" 839 840 # compute number of data symbols per prb 841 n_re_per_prb = self.num_res_per_prb - self.num_ov 842 843 # number of allocated REs 844 # the max. number of REs per PRB is limited to 156 in 38.214 845 n_re = min(156, n_re_per_prb) * self.num_resource_blocks 846 847 # include tb_scaling as defined in Tab. 5.1.3.2-2 38.214 848 target_tb_size = int(self.tb.target_coderate * self.tb.tb_scaling \ 849 * n_re * self.tb.num_bits_per_symbol * self.num_layers) 850 851 # and run tb_size calculation to account for quantization 852 tb_size, _, _, _, _,_ = calculate_tb_size( 853 target_tb_size=target_tb_size, 854 num_coded_bits=self.num_coded_bits, 855 target_coderate=self.tb.target_coderate, 856 modulation_order=self.tb.num_bits_per_symbol, 857 verbose=False) 858 859 return tb_size 860 861 #-------------------# 862 #---Class methods---# 863 #-------------------# 864 865 def c_init(self, l): 866 # pylint: disable=line-too-long 867 r"""Compute RNG initialization :math:`c_\text{init}` as in Section 6.4.1.1.1.1 [3GPP38211]_ 868 869 Input 870 ----- 871 l : int 872 OFDM symbol index relative to a reference :math:`l` 873 874 Output 875 ------ 876 c_init : int 877 RNG initialization value 878 """ 879 num_symbols_per_slot = self.carrier.num_symbols_per_slot 880 slot_number = self.carrier.slot_number 881 882 lambda_bar = 0 883 n_scid_bar = self.dmrs.n_scid 884 if self.dmrs.n_id is None: 885 n_id = self.carrier.n_cell_id 886 else: 887 n_id = self.dmrs.n_id[n_scid_bar] 888 889 c_init = np.mod(2**17 * (num_symbols_per_slot * slot_number + l + 1) \ 890 * (2*n_id + 1) \ 891 + 2**17 * np.floor(lambda_bar/2) \ 892 + 2*n_id + n_scid_bar 893 , 2**31) 894 895 return int(c_init) 896 897 def show(self): 898 """Print all properties of the PUSCHConfig and children""" 899 self.carrier.show() 900 Config.show(self) 901 self.dmrs.show() 902 self.tb.show() 903 904 def check_config(self): 905 """Test if the compound configuration is valid""" 906 907 self.carrier.check_config() 908 self.dmrs.check_config() 909 if self.precoding=="codebook": 910 # Check that dmrs_port_set matches number of layers 911 if len(self.dmrs.dmrs_port_set)>0: 912 assert len(self.dmrs.dmrs_port_set)==self.num_layers, \ 913 "num_layers must be equal to the number of dmrs ports" 914 915 # Check that num_layers<=num_antenna_ports 916 assert self.num_layers <= self.num_antenna_ports,\ 917 "num_layers must be <= num_antenna_ports" 918 919 # Check that more than one antenna port is available 920 assert self.num_antenna_ports>=2, \ 921 "precoding requires two or more antenna ports" 922 else: 923 # Check that num_layers==num_antenna_ports 924 assert self.num_layers == self.num_antenna_ports,\ 925 "num_layers must be == num_antenna_ports" 926 927 # Check Tables 6.4.1.1.3-3/4 are valid 928 if self.dmrs.length==1: 929 if self.mapping_type=="A": 930 assert self.symbol_allocation[1]>=4, \ 931 "Symbol allocation is too short" 932 else: 933 assert self.dmrs.additional_position<2, \ 934 "dmrs.additional_position must be <2 for this dmrs.length" 935 assert self.symbol_allocation[1]>=4, "Symbol allocation too short" 936 if self.mapping_type=="B": 937 assert self.symbol_allocation[1]>=5, \ 938 "Symbol allocation is too short" 939 940 # Check type_a and additional_position position 941 if self.mapping_type=="A": 942 if self.dmrs.additional_position==3: 943 assert self.dmrs.type_a_position==2,\ 944 "additional_position=3 only allowed for type_a_position=2" 945 946 # Check for valid TMPI 947 if self.num_layers==1: 948 if self.num_antenna_ports==2: 949 assert self.tpmi in range(6), "tpmi must be in [0,...,5]" 950 elif self.num_antenna_ports==4: 951 assert self.tpmi in range(28), "tpmi must be in [0,...,27]" 952 elif self.num_layers==2: 953 if self.num_antenna_ports==2: 954 assert self.tpmi in range(3), "tpmi must be in [0,...,2]" 955 elif self.num_antenna_ports==4: 956 assert self.tpmi in range(22), "tpmi must be in [0,...,21]" 957 elif self.num_layers==3: 958 assert self.tpmi in range(7), "tpmi must be in [0,...,6]" 959 elif self.num_layers==4: 960 assert self.tpmi in range(5), "tpmi must be in [0,...,4]" 961 962 # Check that symbol allocation is valid 963 if self.carrier.cyclic_prefix=="normal": 964 max_length = 14 965 else: # cyclic_prefix == "extended" 966 max_length = 12 967 if self.mapping_type=="A": 968 assert self.symbol_allocation[0]==0, \ 969 "symbol_allocation[0] must be 0 for mapping_type A" 970 assert 4<=self.symbol_allocation[1]<=max_length, \ 971 "symbol_allocation[1] must be in [4, 14 (or 12)]" 972 if self.dmrs.length==2: 973 assert self.symbol_allocation[1]>=4, \ 974 "symbol_allocation[1] must be >=4 for dmrs.length==2" 975 elif self.mapping_type=="B": 976 assert 0<=self.symbol_allocation[0]<=13, \ 977 "symbol_allocation[0] must be in [0,13] for mapping_type B" 978 assert 1<=self.symbol_allocation[1]<=max_length, \ 979 "symbol_allocation[1] must be in [1, 14 (or 12)]" 980 if self.dmrs.length==2: 981 assert self.symbol_allocation[1]>=5, \ 982 "symbol_allocation[1] must be >=5 for dmrs.length==2" 983 assert self.symbol_allocation[0] \ 984 + self.symbol_allocation[1]<=max_length, \ 985 "symbol_allocation[0]+symbol_allocation[1] must be < 14 (or 12)" 986 987 attr_list = ["n_size_bwp", 988 "n_start_bwp", 989 "num_layers", 990 "mapping_type", 991 "symbol_allocation", 992 "n_rnti", 993 "precoding", 994 "transform_precoding", 995 "tpmi" 996 ] 997 for attr in attr_list: 998 value = getattr(self, attr) 999 setattr(self, attr, value) 1000 1001 # check that TBConfig is configured for "PUSCH" 1002 assert self.tb.channel_type=="PUSCH", \ 1003 'TB_config must be configured for "PUSCH" transmission.' 1004 1005 # Check that the number of DMRS ports equals the number of layers 1006 # if dmrs_port_set has been set. Otherwise, this is 1007 # automatically ensured. 1008 if len(self.dmrs.dmrs_port_set)>0: 1009 assert self.num_layers==len(self.dmrs.dmrs_port_set), \ 1010 "num_layers must equal the number of DMRS ports" 1011 1012 return True 1013 1014 def check_pusch_configs(pusch_configs): 1015 1016 # Check that pusch_configs is a list 1017 assert isinstance(pusch_configs, list), \ 1018 """pusch_configs must be a Sequence of instances of PUSCHConfig""" 1019 1020 # Iterate through all pusch_configs and check their type and validity 1021 for pusch_config in pusch_configs: 1022 assert isinstance(pusch_config, PUSCHConfig), \ 1023 """All elements of pusch_configs must be instances of PUSCHConfig""" 1024 1025 pusch_config.check_config() 1026 1027 # Create dictionary with extracted configuration parameters 1028 pc = pusch_configs[0] 1029 carrier = pc.carrier 1030 1031 params = { 1032 "num_bits_per_symbol" : pc.tb.num_bits_per_symbol, 1033 "num_tx" : len(pusch_configs), 1034 "num_layers" : pc.num_layers, 1035 "num_subcarriers" : pc.num_subcarriers, 1036 "num_ofdm_symbols" : pc.symbol_allocation[1], 1037 "subcarrier_spacing" : pc.carrier.subcarrier_spacing*1e3, 1038 "num_antenna_ports" : pc.num_antenna_ports, 1039 "precoding" : pc.precoding, 1040 "precoding_matrices" : [], 1041 "pusch_config" : pc, 1042 "carrier_config" : pc.carrier, 1043 "num_coded_bits" : pc.num_coded_bits, 1044 "target_coderate" : pc.tb.target_coderate, 1045 "n_id" : [], 1046 "n_rnti" : [], 1047 "tb_size" : pc.tb_size, 1048 "dmrs_length" : pc.dmrs.length, 1049 "dmrs_additional_position" : pc.dmrs.additional_position, 1050 "num_cdm_groups_without_data" : pc.dmrs.num_cdm_groups_without_data 1051 } 1052 params["bandwidth"] = params["num_subcarriers"]*params["subcarrier_spacing"] 1053 params["cyclic_prefix_length"] = np.ceil(carrier.cyclic_prefix_length * 1054 params["bandwidth"]) 1055 1056 for pusch_config in pusch_configs: 1057 if params["precoding"]=="codebook": 1058 params["precoding_matrices"].append(pusch_config.precoding_matrix) 1059 1060 # n_rnti and n_id are given per tx 1061 if pusch_config.tb.n_id is None: 1062 params["n_id"].append(pusch_config.carrier.n_cell_id) 1063 else: 1064 params["n_id"].append(pusch_config.tb.n_id) 1065 params["n_rnti"].append(pusch_config.n_rnti) 1066 1067 return params