| #!/usr/bin/env python3 |
| # |
| # Copyright 2021 - The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the 'License'); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an 'AS IS' BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import acts.controllers.cellular_lib.BaseCellConfig as base_cell |
| import acts.controllers.cellular_lib.LteSimulation as lte_sim |
| import math |
| |
| |
| class LteCellConfig(base_cell.BaseCellConfig): |
| """ Extension of the BaseBtsConfig to implement parameters that are |
| exclusive to LTE. |
| |
| Attributes: |
| band: an integer indicating the required band number. |
| dlul_config: an integer indicating the TDD config number. |
| ssf_config: an integer indicating the Special Sub-Frame config. |
| bandwidth: a float indicating the required channel bandwidth. |
| mimo_mode: an instance of LteSimulation.MimoMode indicating the |
| required MIMO mode for the downlink signal. |
| transmission_mode: an instance of LteSimulation.TransmissionMode |
| indicating the required TM. |
| scheduling_mode: an instance of LteSimulation.SchedulingMode |
| indicating whether to use Static or Dynamic scheduling. |
| dl_rbs: an integer indicating the number of downlink RBs |
| ul_rbs: an integer indicating the number of uplink RBs |
| dl_mcs: an integer indicating the MCS for the downlink signal |
| ul_mcs: an integer indicating the MCS for the uplink signal |
| dl_256_qam_enabled: a boolean indicating if 256 QAM is enabled |
| ul_64_qam_enabled: a boolean indicating if 256 QAM is enabled |
| mac_padding: a boolean indicating whether RBs should be allocated |
| when there is no user data in static scheduling |
| dl_channel: an integer indicating the downlink channel number |
| cfi: an integer indicating the Control Format Indicator |
| paging_cycle: an integer indicating the paging cycle duration in |
| milliseconds |
| phich: a string indicating the PHICH group size parameter |
| drx_connected_mode: a boolean indicating whether cDRX mode is |
| on or off |
| drx_on_duration_timer: number of PDCCH subframes representing |
| DRX on duration |
| drx_inactivity_timer: number of PDCCH subframes to wait before |
| entering DRX mode |
| drx_retransmission_timer: number of consecutive PDCCH subframes |
| to wait for retransmission |
| drx_long_cycle: number of subframes representing one long DRX cycle. |
| One cycle consists of DRX sleep + DRX on duration |
| drx_long_cycle_offset: number representing offset in range |
| 0 to drx_long_cycle - 1 |
| """ |
| PARAM_FRAME_CONFIG = "tddconfig" |
| PARAM_BW = "bw" |
| PARAM_SCHEDULING = "scheduling" |
| PARAM_SCHEDULING_STATIC = "static" |
| PARAM_SCHEDULING_DYNAMIC = "dynamic" |
| PARAM_PATTERN = "pattern" |
| PARAM_TM = "tm" |
| PARAM_BAND = "band" |
| PARAM_MIMO = "mimo" |
| PARAM_DL_MCS = 'dlmcs' |
| PARAM_UL_MCS = 'ulmcs' |
| PARAM_SSF = 'ssf' |
| PARAM_CFI = 'cfi' |
| PARAM_PAGING = 'paging' |
| PARAM_PHICH = 'phich' |
| PARAM_DRX = 'drx' |
| PARAM_PADDING = 'mac_padding' |
| PARAM_DL_256_QAM_ENABLED = "256_qam_dl_enabled" |
| PARAM_UL_64_QAM_ENABLED = "64_qam_ul_enabled" |
| PARAM_DL_EARFCN = 'dl_earfcn' |
| |
| def __init__(self, log): |
| """ Initialize the base station config by setting all its |
| parameters to None. |
| Args: |
| log: logger object. |
| """ |
| super().__init__(log) |
| self.band = None |
| self.dlul_config = None |
| self.ssf_config = None |
| self.bandwidth = None |
| self.mimo_mode = None |
| self.transmission_mode = None |
| self.scheduling_mode = None |
| self.dl_rbs = None |
| self.ul_rbs = None |
| self.dl_mcs = None |
| self.ul_mcs = None |
| self.dl_256_qam_enabled = None |
| self.ul_64_qam_enabled = None |
| self.mac_padding = None |
| self.dl_channel = None |
| self.cfi = None |
| self.paging_cycle = None |
| self.phich = None |
| self.drx_connected_mode = None |
| self.drx_on_duration_timer = None |
| self.drx_inactivity_timer = None |
| self.drx_retransmission_timer = None |
| self.drx_long_cycle = None |
| self.drx_long_cycle_offset = None |
| |
| def configure(self, parameters): |
| """ Configures an LTE cell using a dictionary of parameters. |
| |
| Args: |
| parameters: a configuration dictionary |
| """ |
| # Setup band |
| if self.PARAM_BAND not in parameters: |
| raise ValueError( |
| "The configuration dictionary must include a key '{}' with " |
| "the required band number.".format(self.PARAM_BAND)) |
| |
| self.band = parameters[self.PARAM_BAND] |
| |
| if self.PARAM_DL_EARFCN not in parameters: |
| band = int(self.band) |
| channel = int(lte_sim.LteSimulation.LOWEST_DL_CN_DICTIONARY[band] + |
| lte_sim.LteSimulation.LOWEST_DL_CN_DICTIONARY[band + |
| 1]) / 2 |
| self.log.warning( |
| "Key '{}' was not set. Using center band channel {} by default." |
| .format(self.PARAM_DL_EARFCN, channel)) |
| self.dl_channel = channel |
| else: |
| self.dl_channel = parameters[self.PARAM_DL_EARFCN] |
| |
| # Set TDD-only configs |
| if self.get_duplex_mode() == lte_sim.DuplexMode.TDD: |
| |
| # Sub-frame DL/UL config |
| if self.PARAM_FRAME_CONFIG not in parameters: |
| raise ValueError("When a TDD band is selected the frame " |
| "structure has to be indicated with the '{}' " |
| "key with a value from 0 to 6.".format( |
| self.PARAM_FRAME_CONFIG)) |
| |
| self.dlul_config = int(parameters[self.PARAM_FRAME_CONFIG]) |
| |
| # Special Sub-Frame configuration |
| if self.PARAM_SSF not in parameters: |
| self.log.warning( |
| 'The {} parameter was not provided. Setting ' |
| 'Special Sub-Frame config to 6 by default.'.format( |
| self.PARAM_SSF)) |
| self.ssf_config = 6 |
| else: |
| self.ssf_config = int(parameters[self.PARAM_SSF]) |
| |
| # Setup bandwidth |
| if self.PARAM_BW not in parameters: |
| raise ValueError( |
| "The config dictionary must include parameter {} with an " |
| "int value (to indicate 1.4 MHz use 14).".format( |
| self.PARAM_BW)) |
| |
| bw = float(parameters[self.PARAM_BW]) |
| |
| if abs(bw - 14) < 0.00000000001: |
| bw = 1.4 |
| |
| self.bandwidth = bw |
| |
| # Setup mimo mode |
| if self.PARAM_MIMO not in parameters: |
| raise ValueError( |
| "The config dictionary must include parameter '{}' with the " |
| "mimo mode.".format(self.PARAM_MIMO)) |
| |
| for mimo_mode in lte_sim.MimoMode: |
| if parameters[self.PARAM_MIMO] == mimo_mode.value: |
| self.mimo_mode = mimo_mode |
| break |
| else: |
| raise ValueError("The value of {} must be one of the following:" |
| "1x1, 2x2 or 4x4.".format(self.PARAM_MIMO)) |
| |
| # Setup transmission mode |
| if self.PARAM_TM not in parameters: |
| raise ValueError( |
| "The config dictionary must include key {} with an " |
| "int value from 1 to 4 indicating transmission mode.".format( |
| self.PARAM_TM)) |
| |
| for tm in lte_sim.TransmissionMode: |
| if parameters[self.PARAM_TM] == tm.value[2:]: |
| self.transmission_mode = tm |
| break |
| else: |
| raise ValueError( |
| "The {} key must have one of the following values:" |
| "1, 2, 3, 4, 7, 8 or 9.".format(self.PARAM_TM)) |
| |
| # Setup scheduling mode |
| if self.PARAM_SCHEDULING not in parameters: |
| self.scheduling_mode = lte_sim.SchedulingMode.STATIC |
| self.log.warning( |
| "The test config does not include the '{}' key. Setting to " |
| "static by default.".format(self.PARAM_SCHEDULING)) |
| elif parameters[ |
| self.PARAM_SCHEDULING] == self.PARAM_SCHEDULING_DYNAMIC: |
| self.scheduling_mode = lte_sim.SchedulingMode.DYNAMIC |
| elif parameters[self.PARAM_SCHEDULING] == self.PARAM_SCHEDULING_STATIC: |
| self.scheduling_mode = lte_sim.SchedulingMode.STATIC |
| else: |
| raise ValueError("Key '{}' must have a value of " |
| "'dynamic' or 'static'.".format( |
| self.PARAM_SCHEDULING)) |
| |
| if self.scheduling_mode == lte_sim.SchedulingMode.STATIC: |
| |
| if self.PARAM_PADDING not in parameters: |
| self.log.warning( |
| "The '{}' parameter was not set. Enabling MAC padding by " |
| "default.".format(self.PARAM_PADDING)) |
| self.mac_padding = True |
| else: |
| self.mac_padding = parameters[self.PARAM_PADDING] |
| |
| if self.PARAM_PATTERN not in parameters: |
| self.log.warning( |
| "The '{}' parameter was not set, using 100% RBs for both " |
| "DL and UL. To set the percentages of total RBs include " |
| "the '{}' key with a list of two ints indicating downlink " |
| "and uplink percentages.".format(self.PARAM_PATTERN, |
| self.PARAM_PATTERN)) |
| dl_pattern = 100 |
| ul_pattern = 100 |
| else: |
| dl_pattern = int(parameters[self.PARAM_PATTERN][0]) |
| ul_pattern = int(parameters[self.PARAM_PATTERN][1]) |
| |
| if not (0 <= dl_pattern <= 100 and 0 <= ul_pattern <= 100): |
| raise ValueError( |
| "The scheduling pattern parameters need to be two " |
| "positive numbers between 0 and 100.") |
| |
| self.dl_rbs, self.ul_rbs = (self.allocation_percentages_to_rbs( |
| dl_pattern, ul_pattern)) |
| |
| # Check if 256 QAM is enabled for DL MCS |
| if self.PARAM_DL_256_QAM_ENABLED not in parameters: |
| self.log.warning("The key '{}' is not set in the test config. " |
| "Setting to false by default.".format( |
| self.PARAM_DL_256_QAM_ENABLED)) |
| |
| self.dl_256_qam_enabled = parameters.get( |
| self.PARAM_DL_256_QAM_ENABLED, False) |
| |
| # Look for a DL MCS configuration in the test parameters. If it is |
| # not present, use a default value. |
| if self.PARAM_DL_MCS in parameters: |
| self.dl_mcs = int(parameters[self.PARAM_DL_MCS]) |
| else: |
| self.log.warning( |
| 'The test config does not include the {} key. Setting ' |
| 'to the max value by default'.format(self.PARAM_DL_MCS)) |
| if self.dl_256_qam_enabled and self.bandwidth == 1.4: |
| self.dl_mcs = 26 |
| elif (not self.dl_256_qam_enabled and self.mac_padding |
| and self.bandwidth != 1.4): |
| self.dl_mcs = 28 |
| else: |
| self.dl_mcs = 27 |
| |
| # Check if 64 QAM is enabled for UL MCS |
| if self.PARAM_UL_64_QAM_ENABLED not in parameters: |
| self.log.warning("The key '{}' is not set in the config file. " |
| "Setting to false by default.".format( |
| self.PARAM_UL_64_QAM_ENABLED)) |
| |
| self.ul_64_qam_enabled = parameters.get( |
| self.PARAM_UL_64_QAM_ENABLED, False) |
| |
| # Look for an UL MCS configuration in the test parameters. If it is |
| # not present, use a default value. |
| if self.PARAM_UL_MCS in parameters: |
| self.ul_mcs = int(parameters[self.PARAM_UL_MCS]) |
| else: |
| self.log.warning( |
| 'The test config does not include the {} key. Setting ' |
| 'to the max value by default'.format(self.PARAM_UL_MCS)) |
| if self.ul_64_qam_enabled: |
| self.ul_mcs = 28 |
| else: |
| self.ul_mcs = 23 |
| |
| # Configure the simulation for DRX mode |
| if self.PARAM_DRX in parameters and len( |
| parameters[self.PARAM_DRX]) == 5: |
| self.drx_connected_mode = True |
| self.drx_on_duration_timer = parameters[self.PARAM_DRX][0] |
| self.drx_inactivity_timer = parameters[self.PARAM_DRX][1] |
| self.drx_retransmission_timer = parameters[self.PARAM_DRX][2] |
| self.drx_long_cycle = parameters[self.PARAM_DRX][3] |
| try: |
| long_cycle = int(parameters[self.PARAM_DRX][3]) |
| long_cycle_offset = int(parameters[self.PARAM_DRX][4]) |
| if long_cycle_offset in range(0, long_cycle): |
| self.drx_long_cycle_offset = long_cycle_offset |
| else: |
| self.log.error( |
| ("The cDRX long cycle offset must be in the " |
| "range 0 to (long cycle - 1). Setting " |
| "long cycle offset to 0")) |
| self.drx_long_cycle_offset = 0 |
| |
| except ValueError: |
| self.log.error(("cDRX long cycle and long cycle offset " |
| "must be integers. Disabling cDRX mode.")) |
| self.drx_connected_mode = False |
| else: |
| self.log.warning( |
| ("DRX mode was not configured properly. " |
| "Please provide a list with the following values: " |
| "1) DRX on duration timer " |
| "2) Inactivity timer " |
| "3) Retransmission timer " |
| "4) Long DRX cycle duration " |
| "5) Long DRX cycle offset " |
| "Example: [2, 6, 16, 20, 0].")) |
| |
| # Channel Control Indicator |
| if self.PARAM_CFI not in parameters: |
| self.log.warning('The {} parameter was not provided. Setting ' |
| 'CFI to BESTEFFORT.'.format(self.PARAM_CFI)) |
| self.cfi = 'BESTEFFORT' |
| else: |
| self.cfi = parameters[self.PARAM_CFI] |
| |
| # PHICH group size |
| if self.PARAM_PHICH not in parameters: |
| self.log.warning('The {} parameter was not provided. Setting ' |
| 'PHICH group size to 1 by default.'.format( |
| self.PARAM_PHICH)) |
| self.phich = '1' |
| else: |
| if parameters[self.PARAM_PHICH] == '16': |
| self.phich = '1/6' |
| elif parameters[self.PARAM_PHICH] == '12': |
| self.phich = '1/2' |
| elif parameters[self.PARAM_PHICH] in ['1/6', '1/2', '1', '2']: |
| self.phich = parameters[self.PARAM_PHICH] |
| else: |
| raise ValueError('The {} parameter can only be followed by 1,' |
| '2, 1/2 (or 12) and 1/6 (or 16).'.format( |
| self.PARAM_PHICH)) |
| |
| # Paging cycle duration |
| if self.PARAM_PAGING not in parameters: |
| self.log.warning('The {} parameter was not provided. Setting ' |
| 'paging cycle duration to 1280 ms by ' |
| 'default.'.format(self.PARAM_PAGING)) |
| self.paging_cycle = 1280 |
| else: |
| try: |
| self.paging_cycle = int(parameters[self.PARAM_PAGING]) |
| except ValueError: |
| raise ValueError( |
| 'The {} key has to be followed by the paging cycle ' |
| 'duration in milliseconds.'.format(self.PARAM_PAGING)) |
| |
| def get_duplex_mode(self): |
| """ Determines if the cell uses FDD or TDD duplex mode |
| |
| Returns: |
| an variable of class DuplexMode indicating if band is FDD or TDD |
| """ |
| if 33 <= int(self.band) <= 46: |
| return lte_sim.DuplexMode.TDD |
| else: |
| return lte_sim.DuplexMode.FDD |
| |
| def allocation_percentages_to_rbs(self, dl, ul): |
| """ Converts usage percentages to number of DL/UL RBs |
| |
| Because not any number of DL/UL RBs can be obtained for a certain |
| bandwidth, this function calculates the number of RBs that most |
| closely matches the desired DL/UL percentages. |
| |
| Args: |
| dl: desired percentage of downlink RBs |
| ul: desired percentage of uplink RBs |
| Returns: |
| a tuple indicating the number of downlink and uplink RBs |
| """ |
| |
| # Validate the arguments |
| if (not 0 <= dl <= 100) or (not 0 <= ul <= 100): |
| raise ValueError("The percentage of DL and UL RBs have to be two " |
| "positive between 0 and 100.") |
| |
| # Get min and max values from tables |
| max_rbs = lte_sim.TOTAL_RBS_DICTIONARY[self.bandwidth] |
| min_dl_rbs = lte_sim.MIN_DL_RBS_DICTIONARY[self.bandwidth] |
| min_ul_rbs = lte_sim.MIN_UL_RBS_DICTIONARY[self.bandwidth] |
| |
| def percentage_to_amount(min_val, max_val, percentage): |
| """ Returns the integer between min_val and max_val that is closest |
| to percentage/100*max_val |
| """ |
| |
| # Calculate the value that corresponds to the required percentage. |
| closest_int = round(max_val * percentage / 100) |
| # Cannot be less than min_val |
| closest_int = max(closest_int, min_val) |
| # RBs cannot be more than max_rbs |
| closest_int = min(closest_int, max_val) |
| |
| return closest_int |
| |
| # Calculate the number of DL RBs |
| |
| # Get the number of DL RBs that corresponds to |
| # the required percentage. |
| desired_dl_rbs = percentage_to_amount(min_val=min_dl_rbs, |
| max_val=max_rbs, |
| percentage=dl) |
| |
| if self.transmission_mode == lte_sim.TransmissionMode.TM3 or \ |
| self.transmission_mode == lte_sim.TransmissionMode.TM4: |
| |
| # For TM3 and TM4 the number of DL RBs needs to be max_rbs or a |
| # multiple of the RBG size |
| |
| if desired_dl_rbs == max_rbs: |
| dl_rbs = max_rbs |
| else: |
| dl_rbs = (math.ceil( |
| desired_dl_rbs / lte_sim.RBG_DICTIONARY[self.bandwidth]) * |
| lte_sim.RBG_DICTIONARY[self.bandwidth]) |
| |
| else: |
| # The other TMs allow any number of RBs between 1 and max_rbs |
| dl_rbs = desired_dl_rbs |
| |
| # Calculate the number of UL RBs |
| |
| # Get the number of UL RBs that corresponds |
| # to the required percentage |
| desired_ul_rbs = percentage_to_amount(min_val=min_ul_rbs, |
| max_val=max_rbs, |
| percentage=ul) |
| |
| # Create a list of all possible UL RBs assignment |
| # The standard allows any number that can be written as |
| # 2**a * 3**b * 5**c for any combination of a, b and c. |
| |
| def pow_range(max_value, base): |
| """ Returns a range of all possible powers of base under |
| the given max_value. |
| """ |
| return range(int(math.ceil(math.log(max_value, base)))) |
| |
| possible_ul_rbs = [ |
| 2 ** a * 3 ** b * 5 ** c for a in pow_range(max_rbs, 2) |
| for b in pow_range(max_rbs, 3) |
| for c in pow_range(max_rbs, 5) |
| if 2 ** a * 3 ** b * 5 ** c <= max_rbs] # yapf: disable |
| |
| # Find the value in the list that is closest to desired_ul_rbs |
| differences = [abs(rbs - desired_ul_rbs) for rbs in possible_ul_rbs] |
| ul_rbs = possible_ul_rbs[differences.index(min(differences))] |
| |
| # Report what are the obtained RB percentages |
| self.log.info("Requested a {}% / {}% RB allocation. Closest possible " |
| "percentages are {}% / {}%.".format( |
| dl, ul, round(100 * dl_rbs / max_rbs), |
| round(100 * ul_rbs / max_rbs))) |
| |
| return dl_rbs, ul_rbs |