1#!/usr/bin/env python3 2# 3# Copyright 2021 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the 'License'); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an 'AS IS' BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import acts.controllers.cellular_lib.BaseCellConfig as base_cell 18import acts.controllers.cellular_lib.LteSimulation as lte_sim 19import math 20 21 22class LteCellConfig(base_cell.BaseCellConfig): 23 """ Extension of the BaseBtsConfig to implement parameters that are 24 exclusive to LTE. 25 26 Attributes: 27 band: an integer indicating the required band number. 28 dlul_config: an integer indicating the TDD config number. 29 ssf_config: an integer indicating the Special Sub-Frame config. 30 bandwidth: a float indicating the required channel bandwidth. 31 mimo_mode: an instance of LteSimulation.MimoMode indicating the 32 required MIMO mode for the downlink signal. 33 transmission_mode: an instance of LteSimulation.TransmissionMode 34 indicating the required TM. 35 scheduling_mode: an instance of LteSimulation.SchedulingMode 36 indicating whether to use Static or Dynamic scheduling. 37 dl_rbs: an integer indicating the number of downlink RBs 38 ul_rbs: an integer indicating the number of uplink RBs 39 dl_mcs: an integer indicating the MCS for the downlink signal 40 ul_mcs: an integer indicating the MCS for the uplink signal 41 dl_256_qam_enabled: a boolean indicating if 256 QAM is enabled 42 ul_64_qam_enabled: a boolean indicating if 256 QAM is enabled 43 mac_padding: a boolean indicating whether RBs should be allocated 44 when there is no user data in static scheduling 45 dl_channel: an integer indicating the downlink channel number 46 cfi: an integer indicating the Control Format Indicator 47 paging_cycle: an integer indicating the paging cycle duration in 48 milliseconds 49 phich: a string indicating the PHICH group size parameter 50 drx_connected_mode: a boolean indicating whether cDRX mode is 51 on or off 52 drx_on_duration_timer: number of PDCCH subframes representing 53 DRX on duration 54 drx_inactivity_timer: number of PDCCH subframes to wait before 55 entering DRX mode 56 drx_retransmission_timer: number of consecutive PDCCH subframes 57 to wait for retransmission 58 drx_long_cycle: number of subframes representing one long DRX cycle. 59 One cycle consists of DRX sleep + DRX on duration 60 drx_long_cycle_offset: number representing offset in range 61 0 to drx_long_cycle - 1 62 """ 63 PARAM_FRAME_CONFIG = "tddconfig" 64 PARAM_BW = "bw" 65 PARAM_SCHEDULING = "scheduling" 66 PARAM_SCHEDULING_STATIC = "static" 67 PARAM_SCHEDULING_DYNAMIC = "dynamic" 68 PARAM_PATTERN = "pattern" 69 PARAM_TM = "tm" 70 PARAM_BAND = "band" 71 PARAM_MIMO = "mimo" 72 PARAM_DL_MCS = "dlmcs" 73 PARAM_UL_MCS = "ulmcs" 74 PARAM_SSF = "ssf" 75 PARAM_CFI = "cfi" 76 PARAM_PAGING = "paging" 77 PARAM_PHICH = "phich" 78 PARAM_DRX = "drx" 79 PARAM_PADDING = "mac_padding" 80 PARAM_DL_256_QAM_ENABLED = "256_qam_dl_enabled" 81 PARAM_UL_64_QAM_ENABLED = "64_qam_ul_enabled" 82 PARAM_DL_EARFCN = "dl_earfcn" 83 PARAM_TA = "tracking_area" 84 PARAM_DISABLE_ALL_UL_SUBFRAMES = "disable_all_ul_subframes" 85 86 def __init__(self, log): 87 """ Initialize the base station config by setting all its 88 parameters to None. 89 Args: 90 log: logger object. 91 """ 92 super().__init__(log) 93 self.band = None 94 self.dlul_config = None 95 self.ssf_config = None 96 self.bandwidth = None 97 self.mimo_mode = None 98 self.transmission_mode = None 99 self.scheduling_mode = None 100 self.dl_rbs = None 101 self.ul_rbs = None 102 self.dl_mcs = None 103 self.ul_mcs = None 104 self.dl_256_qam_enabled = None 105 self.ul_64_qam_enabled = None 106 self.mac_padding = None 107 self.dl_channel = None 108 self.cfi = None 109 self.paging_cycle = None 110 self.phich = None 111 self.drx_connected_mode = None 112 self.drx_on_duration_timer = None 113 self.drx_inactivity_timer = None 114 self.drx_retransmission_timer = None 115 self.drx_long_cycle = None 116 self.drx_long_cycle_offset = None 117 self.tracking_area = None 118 self.disable_all_ul_subframes = None 119 120 def __str__(self): 121 return str(vars(self)) 122 123 def configure(self, parameters): 124 """ Configures an LTE cell using a dictionary of parameters. 125 126 Args: 127 parameters: a configuration dictionary 128 """ 129 # Setup band 130 if self.PARAM_BAND not in parameters: 131 raise ValueError( 132 "The configuration dictionary must include a key '{}' with " 133 "the required band number.".format(self.PARAM_BAND)) 134 135 self.band = parameters[self.PARAM_BAND] 136 137 if self.PARAM_DL_EARFCN not in parameters: 138 band = int(self.band) 139 channel = int(lte_sim.LteSimulation.LOWEST_DL_CN_DICTIONARY[band] + 140 lte_sim.LteSimulation.LOWEST_DL_CN_DICTIONARY[band + 141 1]) / 2 142 self.log.warning( 143 "Key '{}' was not set. Using center band channel {} by default." 144 .format(self.PARAM_DL_EARFCN, channel)) 145 self.dl_channel = channel 146 else: 147 self.dl_channel = parameters[self.PARAM_DL_EARFCN] 148 149 # Set TDD-only configs 150 if self.get_duplex_mode() == lte_sim.DuplexMode.TDD: 151 152 # Sub-frame DL/UL config 153 if self.PARAM_FRAME_CONFIG not in parameters: 154 raise ValueError("When a TDD band is selected the frame " 155 "structure has to be indicated with the '{}' " 156 "key with a value from 0 to 6.".format( 157 self.PARAM_FRAME_CONFIG)) 158 159 self.dlul_config = int(parameters[self.PARAM_FRAME_CONFIG]) 160 161 # Special Sub-Frame configuration 162 if self.PARAM_SSF not in parameters: 163 self.log.warning( 164 'The {} parameter was not provided. Setting ' 165 'Special Sub-Frame config to 6 by default.'.format( 166 self.PARAM_SSF)) 167 self.ssf_config = 6 168 else: 169 self.ssf_config = int(parameters[self.PARAM_SSF]) 170 171 # Setup bandwidth 172 if self.PARAM_BW not in parameters: 173 raise ValueError( 174 "The config dictionary must include parameter {} with an " 175 "int value (to indicate 1.4 MHz use 14).".format( 176 self.PARAM_BW)) 177 178 bw = float(parameters[self.PARAM_BW]) 179 180 if abs(bw - 14) < 0.00000000001: 181 bw = 1.4 182 183 self.bandwidth = bw 184 185 # Setup mimo mode 186 if self.PARAM_MIMO not in parameters: 187 raise ValueError( 188 "The config dictionary must include parameter '{}' with the " 189 "mimo mode.".format(self.PARAM_MIMO)) 190 191 for mimo_mode in lte_sim.MimoMode: 192 if parameters[self.PARAM_MIMO] == mimo_mode.value: 193 self.mimo_mode = mimo_mode 194 break 195 else: 196 raise ValueError("The value of {} must be one of the following:" 197 "1x1, 2x2 or 4x4.".format(self.PARAM_MIMO)) 198 199 # Setup transmission mode 200 if self.PARAM_TM not in parameters: 201 raise ValueError( 202 "The config dictionary must include key {} with an " 203 "int value from 1 to 4 indicating transmission mode.".format( 204 self.PARAM_TM)) 205 206 for tm in lte_sim.TransmissionMode: 207 if parameters[self.PARAM_TM] == tm.value[2:]: 208 self.transmission_mode = tm 209 break 210 else: 211 raise ValueError( 212 "The {} key must have one of the following values:" 213 "1, 2, 3, 4, 7, 8 or 9.".format(self.PARAM_TM)) 214 215 # Setup scheduling mode 216 if self.PARAM_SCHEDULING not in parameters: 217 self.scheduling_mode = lte_sim.SchedulingMode.STATIC 218 self.log.warning( 219 "The test config does not include the '{}' key. Setting to " 220 "static by default.".format(self.PARAM_SCHEDULING)) 221 elif parameters[ 222 self.PARAM_SCHEDULING] == self.PARAM_SCHEDULING_DYNAMIC: 223 self.scheduling_mode = lte_sim.SchedulingMode.DYNAMIC 224 elif parameters[self.PARAM_SCHEDULING] == self.PARAM_SCHEDULING_STATIC: 225 self.scheduling_mode = lte_sim.SchedulingMode.STATIC 226 else: 227 raise ValueError("Key '{}' must have a value of " 228 "'dynamic' or 'static'.".format( 229 self.PARAM_SCHEDULING)) 230 231 if self.scheduling_mode == lte_sim.SchedulingMode.STATIC: 232 233 if self.PARAM_PADDING not in parameters: 234 self.log.warning( 235 "The '{}' parameter was not set. Enabling MAC padding by " 236 "default.".format(self.PARAM_PADDING)) 237 self.mac_padding = True 238 else: 239 self.mac_padding = parameters[self.PARAM_PADDING] 240 241 if self.PARAM_PATTERN not in parameters: 242 self.log.warning( 243 "The '{}' parameter was not set, using 100% RBs for both " 244 "DL and UL. To set the percentages of total RBs include " 245 "the '{}' key with a list of two ints indicating downlink " 246 "and uplink percentages.".format(self.PARAM_PATTERN, 247 self.PARAM_PATTERN)) 248 dl_pattern = 100 249 ul_pattern = 100 250 else: 251 dl_pattern = int(parameters[self.PARAM_PATTERN][0]) 252 ul_pattern = int(parameters[self.PARAM_PATTERN][1]) 253 254 if not (0 <= dl_pattern <= 100 and 0 <= ul_pattern <= 100): 255 raise ValueError( 256 "The scheduling pattern parameters need to be two " 257 "positive numbers between 0 and 100.") 258 259 self.dl_rbs, self.ul_rbs = (self.allocation_percentages_to_rbs( 260 dl_pattern, ul_pattern)) 261 262 # Check if 256 QAM is enabled for DL MCS 263 if self.PARAM_DL_256_QAM_ENABLED not in parameters: 264 self.log.warning("The key '{}' is not set in the test config. " 265 "Setting to false by default.".format( 266 self.PARAM_DL_256_QAM_ENABLED)) 267 268 self.dl_256_qam_enabled = parameters.get( 269 self.PARAM_DL_256_QAM_ENABLED, False 270 ) 271 272 self.disable_all_ul_subframes = parameters.get( 273 self.PARAM_DISABLE_ALL_UL_SUBFRAMES, False 274 ) 275 276 # Look for a DL MCS configuration in the test parameters. If it is 277 # not present, use a default value. 278 if self.PARAM_DL_MCS in parameters: 279 self.dl_mcs = int(parameters[self.PARAM_DL_MCS]) 280 else: 281 self.log.warning( 282 'The test config does not include the {} key. Setting ' 283 'to the max value by default'.format(self.PARAM_DL_MCS)) 284 if self.dl_256_qam_enabled and self.bandwidth == 1.4: 285 self.dl_mcs = 26 286 elif (not self.dl_256_qam_enabled and self.mac_padding 287 and self.bandwidth != 1.4): 288 self.dl_mcs = 28 289 else: 290 self.dl_mcs = 27 291 292 # Check if 64 QAM is enabled for UL MCS 293 if self.PARAM_UL_64_QAM_ENABLED not in parameters: 294 self.log.warning("The key '{}' is not set in the config file. " 295 "Setting to false by default.".format( 296 self.PARAM_UL_64_QAM_ENABLED)) 297 298 self.ul_64_qam_enabled = parameters.get( 299 self.PARAM_UL_64_QAM_ENABLED, False) 300 301 # Look for an UL MCS configuration in the test parameters. If it is 302 # not present, use a default value. 303 if self.PARAM_UL_MCS in parameters: 304 self.ul_mcs = int(parameters[self.PARAM_UL_MCS]) 305 else: 306 self.log.warning( 307 'The test config does not include the {} key. Setting ' 308 'to the max value by default'.format(self.PARAM_UL_MCS)) 309 if self.ul_64_qam_enabled: 310 self.ul_mcs = 28 311 else: 312 self.ul_mcs = 23 313 314 # Configure the simulation for DRX mode 315 if self.PARAM_DRX in parameters and len( 316 parameters[self.PARAM_DRX]) == 5: 317 self.drx_connected_mode = True 318 self.drx_on_duration_timer = parameters[self.PARAM_DRX][0] 319 self.drx_inactivity_timer = parameters[self.PARAM_DRX][1] 320 self.drx_retransmission_timer = parameters[self.PARAM_DRX][2] 321 self.drx_long_cycle = parameters[self.PARAM_DRX][3] 322 try: 323 long_cycle = int(parameters[self.PARAM_DRX][3]) 324 long_cycle_offset = int(parameters[self.PARAM_DRX][4]) 325 if long_cycle_offset in range(0, long_cycle): 326 self.drx_long_cycle_offset = long_cycle_offset 327 else: 328 self.log.error( 329 ("The cDRX long cycle offset must be in the " 330 "range 0 to (long cycle - 1). Setting " 331 "long cycle offset to 0")) 332 self.drx_long_cycle_offset = 0 333 334 except ValueError: 335 self.log.error(("cDRX long cycle and long cycle offset " 336 "must be integers. Disabling cDRX mode.")) 337 self.drx_connected_mode = False 338 else: 339 self.log.warning( 340 ("DRX mode was not configured properly. " 341 "Please provide a list with the following values: " 342 "1) DRX on duration timer " 343 "2) Inactivity timer " 344 "3) Retransmission timer " 345 "4) Long DRX cycle duration " 346 "5) Long DRX cycle offset " 347 "Example: [2, 6, 16, 20, 0].")) 348 349 # Channel Control Indicator 350 if self.PARAM_CFI not in parameters: 351 self.log.warning('The {} parameter was not provided. Setting ' 352 'CFI to BESTEFFORT.'.format(self.PARAM_CFI)) 353 self.cfi = 'BESTEFFORT' 354 else: 355 self.cfi = parameters[self.PARAM_CFI] 356 357 # PHICH group size 358 if self.PARAM_PHICH not in parameters: 359 self.log.warning('The {} parameter was not provided. Setting ' 360 'PHICH group size to 1 by default.'.format( 361 self.PARAM_PHICH)) 362 self.phich = '1' 363 else: 364 if parameters[self.PARAM_PHICH] == '16': 365 self.phich = '1/6' 366 elif parameters[self.PARAM_PHICH] == '12': 367 self.phich = '1/2' 368 elif parameters[self.PARAM_PHICH] in ['1/6', '1/2', '1', '2']: 369 self.phich = parameters[self.PARAM_PHICH] 370 else: 371 raise ValueError('The {} parameter can only be followed by 1,' 372 '2, 1/2 (or 12) and 1/6 (or 16).'.format( 373 self.PARAM_PHICH)) 374 375 # Paging cycle duration 376 if self.PARAM_PAGING not in parameters: 377 self.log.warning('The {} parameter was not provided. Setting ' 378 'paging cycle duration to 1280 ms by ' 379 'default.'.format(self.PARAM_PAGING)) 380 self.paging_cycle = 1280 381 else: 382 try: 383 self.paging_cycle = int(parameters[self.PARAM_PAGING]) 384 except ValueError: 385 raise ValueError( 386 'The {} key has to be followed by the paging cycle ' 387 'duration in milliseconds.'.format(self.PARAM_PAGING)) 388 389 if self.PARAM_TA in parameters: 390 self.tracking_area = int(parameters[self.PARAM_TA]) 391 392 def get_duplex_mode(self): 393 """ Determines if the cell uses FDD or TDD duplex mode 394 395 Returns: 396 an variable of class DuplexMode indicating if band is FDD or TDD 397 """ 398 if 33 <= int(self.band) <= 46: 399 return lte_sim.DuplexMode.TDD 400 else: 401 return lte_sim.DuplexMode.FDD 402 403 def allocation_percentages_to_rbs(self, dl, ul): 404 """ Converts usage percentages to number of DL/UL RBs 405 406 Because not any number of DL/UL RBs can be obtained for a certain 407 bandwidth, this function calculates the number of RBs that most 408 closely matches the desired DL/UL percentages. 409 410 Args: 411 dl: desired percentage of downlink RBs 412 ul: desired percentage of uplink RBs 413 Returns: 414 a tuple indicating the number of downlink and uplink RBs 415 """ 416 417 # Validate the arguments 418 if (not 0 <= dl <= 100) or (not 0 <= ul <= 100): 419 raise ValueError("The percentage of DL and UL RBs have to be two " 420 "positive between 0 and 100.") 421 422 # Get min and max values from tables 423 max_rbs = lte_sim.TOTAL_RBS_DICTIONARY[self.bandwidth] 424 min_dl_rbs = lte_sim.MIN_DL_RBS_DICTIONARY[self.bandwidth] 425 min_ul_rbs = lte_sim.MIN_UL_RBS_DICTIONARY[self.bandwidth] 426 427 def percentage_to_amount(min_val, max_val, percentage): 428 """ Returns the integer between min_val and max_val that is closest 429 to percentage/100*max_val 430 """ 431 432 # Calculate the value that corresponds to the required percentage. 433 closest_int = round(max_val * percentage / 100) 434 # Cannot be less than min_val 435 closest_int = max(closest_int, min_val) 436 # RBs cannot be more than max_rbs 437 closest_int = min(closest_int, max_val) 438 439 return closest_int 440 441 # Calculate the number of DL RBs 442 443 # Get the number of DL RBs that corresponds to 444 # the required percentage. 445 desired_dl_rbs = percentage_to_amount(min_val=min_dl_rbs, 446 max_val=max_rbs, 447 percentage=dl) 448 449 if self.transmission_mode == lte_sim.TransmissionMode.TM3 or \ 450 self.transmission_mode == lte_sim.TransmissionMode.TM4: 451 452 # For TM3 and TM4 the number of DL RBs needs to be max_rbs or a 453 # multiple of the RBG size 454 455 if desired_dl_rbs == max_rbs: 456 dl_rbs = max_rbs 457 else: 458 dl_rbs = (math.ceil( 459 desired_dl_rbs / lte_sim.RBG_DICTIONARY[self.bandwidth]) * 460 lte_sim.RBG_DICTIONARY[self.bandwidth]) 461 462 else: 463 # The other TMs allow any number of RBs between 1 and max_rbs 464 dl_rbs = desired_dl_rbs 465 466 # Calculate the number of UL RBs 467 468 # Get the number of UL RBs that corresponds 469 # to the required percentage 470 desired_ul_rbs = percentage_to_amount(min_val=min_ul_rbs, 471 max_val=max_rbs, 472 percentage=ul) 473 474 # Create a list of all possible UL RBs assignment 475 # The standard allows any number that can be written as 476 # 2**a * 3**b * 5**c for any combination of a, b and c. 477 478 def pow_range(max_value, base): 479 """ Returns a range of all possible powers of base under 480 the given max_value. 481 """ 482 return range(int(math.ceil(math.log(max_value, base)))) 483 484 possible_ul_rbs = [ 485 2 ** a * 3 ** b * 5 ** c for a in pow_range(max_rbs, 2) 486 for b in pow_range(max_rbs, 3) 487 for c in pow_range(max_rbs, 5) 488 if 2 ** a * 3 ** b * 5 ** c <= max_rbs] # yapf: disable 489 490 # Find the value in the list that is closest to desired_ul_rbs 491 differences = [abs(rbs - desired_ul_rbs) for rbs in possible_ul_rbs] 492 ul_rbs = possible_ul_rbs[differences.index(min(differences))] 493 494 # Report what are the obtained RB percentages 495 self.log.info("Requested a {}% / {}% RB allocation. Closest possible " 496 "percentages are {}% / {}%.".format( 497 dl, ul, round(100 * dl_rbs / max_rbs), 498 round(100 * ul_rbs / max_rbs))) 499 500 return dl_rbs, ul_rbs 501