pusch_dmrs_config.py (12376B)
1 # 2 # SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 # SPDX-License-Identifier: Apache-2.0 4 # 5 """PUSCH DMRS configuration for the nr (5G) sub-package of the Sionna library. 6 """ 7 # pylint: disable=line-too-long 8 9 from collections.abc import Sequence 10 import numpy as np 11 from .config import Config 12 13 class PUSCHDMRSConfig(Config): 14 """ 15 The PUSCHDMRSConfig objects sets parameters related to the generation 16 of demodulation reference signals (DMRS) for a physical uplink shared 17 channel (PUSCH), as described in Section 6.4.1.1 [3GPP38211]_. 18 19 All configurable properties can be provided as keyword arguments during the 20 initialization or changed later. 21 22 Example 23 ------- 24 >>> dmrs_config = PUSCHDMRSConfig(config_type=2) 25 >>> dmrs_config.additional_position = 1 26 """ 27 28 def __init__(self, **kwargs): 29 self._name = "PUSCH DMRS Configuration" 30 super().__init__(**kwargs) 31 self.check_config() 32 33 #-----------------------------# 34 #---Configurable parameters---# 35 #-----------------------------# 36 37 #---config_type---# 38 @property 39 def config_type(self): 40 """ 41 int, 1 (default) | 2 : DMRS configuration type 42 43 The configuration type determines the frequency density of 44 DMRS signals. With configuration type 1, six subcarriers per PRB are 45 used for each antenna port, with configuration type 2, four 46 subcarriers are used. 47 """ 48 self._ifndef("config_type", 1) 49 return self._config_type 50 51 @config_type.setter 52 def config_type(self, value): 53 assert value in [1,2], "config_type must be in [1,2]" 54 self._config_type = value 55 56 #---type_a_position---# 57 @property 58 def type_a_position(self): 59 """ 60 int, 2 (default) | 3 : Position of first DMRS OFDM symbol 61 62 Defines the position of the first DMRS symbol within a slot. 63 This parameter only applies if the property 64 :class:`~sionna.nr.PUSCHConfig.mapping_type` of 65 :class:`~sionna.nr.PUSCHConfig` is equal to "A". 66 """ 67 self._ifndef("type_a_position", 2) 68 return self._type_a_position 69 70 @type_a_position.setter 71 def type_a_position(self, value): 72 assert value in [2,3], "type_a_position must be in [2,3]" 73 self._type_a_position = value 74 75 #---additional_position---# 76 @property 77 def additional_position(self): 78 """ 79 int, 0 (default) | 1 | 2 | 3 : Maximum number of additional DMRS positions 80 81 The actual number of used DMRS positions depends on 82 the length of the PUSCH symbol allocation. 83 """ 84 self._ifndef("additional_position", 0) 85 return self._additional_position 86 87 @additional_position.setter 88 def additional_position(self, value): 89 assert value in [0,1,2,3], "additional_position must be in [0,1,2,3]" 90 self._additional_position = value 91 92 #---length---# 93 @property 94 def length(self): 95 """ 96 int, 1 (default) | 2 : Number of front-loaded DMRS symbols 97 A value of 1 corresponds to "single-symbol" DMRS, a value 98 of 2 corresponds to "double-symbol" DMRS. 99 """ 100 self._ifndef("length", 1) 101 return self._length 102 103 @length.setter 104 def length(self, value): 105 assert value in [1,2], "Invalid DMRS length" 106 self._length = value 107 108 #---dmrs_port_set---# 109 @property 110 def dmrs_port_set(self): 111 """ 112 list, [] (default) | [0,...,11] : List of used DMRS antenna ports 113 114 The elements in this list must all be from the list of 115 `allowed_dmrs_ports` which depends on the `config_type` as well as 116 the `length`. If set to `[]`, the port set will be equal to 117 [0,...,num_layers-1], where 118 :class:`~sionna.nr.PUSCHConfig.num_layers` is a property of the 119 parent :class:`~sionna.nr.PUSCHConfig` instance. 120 """ 121 self._ifndef("dmrs_port_set", []) 122 return self._dmrs_port_set 123 124 @dmrs_port_set.setter 125 def dmrs_port_set(self, value): 126 if isinstance(value, int): 127 value = [value] 128 elif isinstance(value, Sequence): 129 value = list(value) 130 else: 131 raise ValueError("dmrs_port_set must be an integer or list") 132 self._dmrs_port_set = value 133 134 #---n_id---# 135 @property 136 def n_id(self): 137 r""" 138 2-tuple, None (default), [[0,...,65535], [0,...,65535]]: Scrambling 139 identities 140 141 Defines the scrambling identities :math:`N_\text{ID}^0` and 142 :math:`N_\text{ID}^1` as a 2-tuple of integers. If `None`, 143 the property :class:`~sionna.nr.CarrierConfig.n_cell_id` of the 144 :class:`~sionna.nr.CarrierConfig` is used. 145 """ 146 self._ifndef("n_id", None) 147 return self._n_id 148 149 @n_id.setter 150 def n_id(self, value): 151 if value is None: 152 self._n_id = None 153 elif isinstance(value, int): 154 assert value in list(range(65536)), "n_id must be in [0, 65535]" 155 self._n_id = [value, value] 156 else: 157 assert len(value)==2, "n_id must be either [] or a two-tuple" 158 for e in value: 159 assert e in list(range(65536)), "Each element of n_id must be in [0, 65535]" 160 self._n_id = value 161 162 #---n_scid---# 163 @property 164 def n_scid(self): 165 r""" 166 int, 0 (default) | 1 : DMRS scrambling initialization 167 :math:`n_\text{SCID}` 168 """ 169 self._ifndef("n_scid", 0) 170 return self._n_scid 171 172 @n_scid.setter 173 def n_scid(self, value): 174 assert value in [0, 1], "n_scid must be 0 or 1" 175 self._n_scid = value 176 177 #---num_cdm_groups_without_data---# 178 @property 179 def num_cdm_groups_without_data(self): 180 """ 181 int, 2 (default) | 1 | 3 : Number of CDM groups without data 182 183 This parameter controls how many REs are available for data 184 transmission in a DMRS symbol. It should be greater or equal to 185 the maximum configured number of CDM groups. A value of 186 1 corresponds to CDM group 0, a value of 2 corresponds to 187 CDM groups 0 and 1, and a value of 3 corresponds to 188 CDM groups 0, 1, and 2. 189 """ 190 self._ifndef("num_cdm_groups_without_data", 2) 191 return self._num_cdm_groups_without_data 192 193 @num_cdm_groups_without_data.setter 194 def num_cdm_groups_without_data(self, value): 195 assert value in [1,2,3], \ 196 "num_cdm_groups_without_data must be in [1,2,3]" 197 self._num_cdm_groups_without_data = value 198 199 #-----------------------------# 200 #---Read-only parameters------# 201 #-----------------------------# 202 203 @property 204 def allowed_dmrs_ports(self): 205 """ 206 list, [0,...,max_num_dmrs_ports-1], read-only : List of nominal antenna 207 ports 208 209 The maximum number of allowed antenna ports `max_num_dmrs_ports` 210 depends on the DMRS `config_type` and `length`. It can be 211 equal to 4, 6, 8, or 12. 212 """ 213 if self.length==1: 214 if self.config_type==1: 215 if self.num_cdm_groups_without_data==1: 216 return [0,1] 217 else: 218 return [0,1,2,3] 219 #max_num_dmrs_ports = self.num_cdm_groups_without_data*2 220 elif self.config_type==2: 221 if self.num_cdm_groups_without_data==1: 222 return [0,1] 223 elif self.num_cdm_groups_without_data==2: 224 return [0,1,2,3] 225 else: 226 return [0,1,2,3,4,5] 227 #max_num_dmrs_ports = self.num_cdm_groups_without_data*2 228 elif self.length==2: 229 if self.config_type==1: 230 if self.num_cdm_groups_without_data==1: 231 return [0,1,4,5] 232 else: 233 return [0,1,2,3,4,5,6,7] 234 #max_num_dmrs_ports = self.num_cdm_groups_without_data*4 235 elif self.config_type==2: 236 if self.num_cdm_groups_without_data==1: 237 return [0,1,6,7] 238 elif self.num_cdm_groups_without_data==2: 239 return [0,1,2,3,6,7,8,9] 240 else: 241 return [0,1,2,3,4,5,6,7,8,9,10,11] 242 #max_num_dmrs_ports = self.num_cdm_groups_without_data*4 243 #return list(range(max_num_dmrs_ports)) 244 245 @property 246 def cdm_groups(self): 247 r""" 248 list, elements in [0,1,2], read-only : List of CDM groups 249 :math:`\lambda` for all ports 250 in the `dmrs_port_set` as defined in 251 Table 6.4.1.1.3-1 or 6.4.1.1.3-2 [3GPP38211]_ 252 253 Depends on the `config_type`. 254 """ 255 if self.config_type==1: 256 cdm_groups = [0,0,1,1,0,0,1,1] 257 else: 258 cdm_groups = [0,0,1,1,2,2,0,0,1,1,2,2] 259 return [cdm_groups[port] for port in self.dmrs_port_set] 260 261 @property 262 def deltas(self): 263 r""" 264 list, elements in [0,1,2,4], read-only : List of delta (frequency) 265 shifts :math:`\Delta` for all ports in the `port_set` as defined in 266 Table 6.4.1.1.3-1 or 6.4.1.1.3-2 [3GPP38211]_ 267 268 Depends on the `config_type`. 269 """ 270 if self.config_type==1: 271 deltas = [0,0,1,1,0,0,1,1] 272 else: 273 deltas = [0,0,2,2,4,4,0,0,2,2,4,4] 274 return [deltas[port] for port in self.dmrs_port_set] 275 276 @property 277 def w_f(self): 278 r""" 279 matrix, elements in [-1,1], read-only : Frequency weight vectors 280 :math:`w_f(k')` for all ports in the port set as defined in 281 Table 6.4.1.1.3-1 or 6.4.1.1.3-2 [3GPP38211]_ 282 """ 283 if self.config_type==1: 284 w_f = np.array([[1, 1,1, 1,1, 1,1, 1], 285 [1,-1,1,-1,1,-1,1,-1]]) 286 else: # config_type == 2 287 w_f = np.array([[1, 1,1, 1,1, 1,1, 1,1, 1,1, 1], 288 [1,-1,1,-1,1,-1,1,-1,1,-1,1,-1]]) 289 return w_f[:, self.dmrs_port_set] 290 291 @property 292 def w_t(self): 293 r""" 294 matrix, elements in [-1,1], read-only : Time weight vectors 295 :math:`w_t(l')` for all ports in the port set as defined in 296 Table 6.4.1.1.3-1 or 6.4.1.1.3-2 [3GPP38211]_ 297 """ 298 if self.config_type==1: 299 w_t = np.array([[1,1,1,1, 1, 1, 1, 1], 300 [1,1,1,1,-1,-1,-1,-1]]) 301 else: # config_type == 2 302 w_t = np.array([[1,1,1,1,1,1, 1, 1, 1, 1, 1, 1], 303 [1,1,1,1,1,1,-1,-1,-1,-1,-1,-1]]) 304 return w_t[:, self.dmrs_port_set] 305 306 @property 307 def beta(self): 308 r""" 309 float, read-only : Ratio of PUSCH energy per resource element 310 (EPRE) to DMRS EPRE :math:`\beta^{\text{DMRS}}_\text{PUSCH}` 311 Table 6.2.2-1 [3GPP38214]_ 312 """ 313 if self.num_cdm_groups_without_data==1: 314 return 1.0 315 elif self.num_cdm_groups_without_data==2: 316 return np.sqrt(2) 317 elif self.num_cdm_groups_without_data==3: 318 if self.config_type==2: 319 return np.sqrt(3) 320 321 #-------------------# 322 #---Class methods---# 323 #-------------------# 324 325 def check_config(self): 326 """Test if configuration is valid""" 327 328 if self.length==2: 329 assert self.additional_position in [0, 1], \ 330 "additional_position must be in [0, 1] for length==2" 331 332 for p in self.dmrs_port_set: 333 assert p in self.allowed_dmrs_ports,\ 334 f"Unallowed DMRS port {p}. Not in {self.allowed_dmrs_ports}." 335 336 if self.config_type==1: 337 assert self.num_cdm_groups_without_data in [1, 2], \ 338 "num_cdm_groups_without_data must be in [1,2] for config_type 1" 339 340 attr_list = ["config_type", 341 "type_a_position", 342 "additional_position", 343 "length", 344 "dmrs_port_set", 345 "n_id", 346 "n_scid", 347 "num_cdm_groups_without_data" 348 ] 349 for attr in attr_list: 350 value = getattr(self, attr) 351 setattr(self, attr, value)