1300 lines
48 KiB
Python
1300 lines
48 KiB
Python
# Copyright 2021 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import math
|
|
import time
|
|
from enum import Enum
|
|
|
|
import common
|
|
|
|
from autotest_lib.server.cros.cellular.simulation_utils.BaseSimulation import BaseSimulation
|
|
from autotest_lib.server.cros.cellular.simulation_utils import BaseCellularDut
|
|
|
|
|
|
class TransmissionMode(Enum):
|
|
""" Transmission modes for LTE (e.g., TM1, TM4, ...) """
|
|
TM1 = "TM1"
|
|
TM2 = "TM2"
|
|
TM3 = "TM3"
|
|
TM4 = "TM4"
|
|
TM7 = "TM7"
|
|
TM8 = "TM8"
|
|
TM9 = "TM9"
|
|
|
|
|
|
class MimoMode(Enum):
|
|
""" Mimo modes """
|
|
MIMO_1x1 = "1x1"
|
|
MIMO_2x2 = "2x2"
|
|
MIMO_4x4 = "4x4"
|
|
|
|
|
|
class SchedulingMode(Enum):
|
|
""" Traffic scheduling modes (e.g., STATIC, DYNAMIC) """
|
|
DYNAMIC = "DYNAMIC"
|
|
STATIC = "STATIC"
|
|
|
|
|
|
class DuplexMode(Enum):
|
|
""" DL/UL Duplex mode """
|
|
FDD = "FDD"
|
|
TDD = "TDD"
|
|
|
|
|
|
class ModulationType(Enum):
|
|
"""DL/UL Modulation order."""
|
|
QPSK = 'QPSK'
|
|
Q16 = '16QAM'
|
|
Q64 = '64QAM'
|
|
Q256 = '256QAM'
|
|
|
|
|
|
class LteSimulation(BaseSimulation):
|
|
""" Single-carrier LTE simulation. """
|
|
|
|
# Simulation config keywords contained in the test name
|
|
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_UL_PW = 'pul'
|
|
PARAM_DL_PW = 'pdl'
|
|
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_RRC_STATUS_CHANGE_TIMER = "rrcstatuschangetimer"
|
|
PARAM_DRX = 'drx'
|
|
|
|
# Test config keywords
|
|
KEY_TBS_PATTERN = "tbs_pattern_on"
|
|
KEY_DL_256_QAM = "256_qam_dl"
|
|
KEY_UL_64_QAM = "64_qam_ul"
|
|
|
|
# Units in which signal level is defined in DOWNLINK_SIGNAL_LEVEL_DICTIONARY
|
|
DOWNLINK_SIGNAL_LEVEL_UNITS = "RSRP"
|
|
|
|
# RSRP signal levels thresholds (as reported by Android) in dBm/15KHz.
|
|
# Excellent is set to -75 since callbox B Tx power is limited to -30 dBm
|
|
DOWNLINK_SIGNAL_LEVEL_DICTIONARY = {
|
|
'excellent': -75,
|
|
'high': -110,
|
|
'medium': -115,
|
|
'weak': -120,
|
|
'disconnected': -170
|
|
}
|
|
|
|
# Transmitted output power for the phone (dBm)
|
|
UPLINK_SIGNAL_LEVEL_DICTIONARY = {
|
|
'max': 24,
|
|
'high': 13,
|
|
'medium': 3,
|
|
'low': -20
|
|
}
|
|
|
|
# Bandwidth [MHz] to total RBs mapping
|
|
total_rbs_dictionary = {20: 100, 15: 75, 10: 50, 5: 25, 3: 15, 1.4: 6}
|
|
|
|
# Bandwidth [MHz] to RB group size
|
|
rbg_dictionary = {20: 4, 15: 4, 10: 3, 5: 2, 3: 2, 1.4: 1}
|
|
|
|
# Bandwidth [MHz] to minimum number of DL RBs that can be assigned to a UE
|
|
min_dl_rbs_dictionary = {20: 16, 15: 12, 10: 9, 5: 4, 3: 4, 1.4: 2}
|
|
|
|
# Bandwidth [MHz] to minimum number of UL RBs that can be assigned to a UE
|
|
min_ul_rbs_dictionary = {20: 8, 15: 6, 10: 4, 5: 2, 3: 2, 1.4: 1}
|
|
|
|
# Allowed bandwidth for each band.
|
|
allowed_bandwidth_dictionary = {
|
|
1: [5, 10, 15, 20],
|
|
2: [1.4, 3, 5, 10, 15, 20],
|
|
3: [1.4, 3, 5, 10, 15, 20],
|
|
4: [1.4, 3, 5, 10, 15, 20],
|
|
5: [1.4, 3, 5, 10],
|
|
7: [5, 10, 15, 20],
|
|
8: [1.4, 3, 5, 10],
|
|
10: [5, 10, 15, 20],
|
|
11: [5, 10],
|
|
12: [1.4, 3, 5, 10],
|
|
13: [5, 10],
|
|
14: [5, 10],
|
|
17: [5, 10],
|
|
18: [5, 10, 15],
|
|
19: [5, 10, 15],
|
|
20: [5, 10, 15, 20],
|
|
21: [5, 10, 15],
|
|
22: [5, 10, 15, 20],
|
|
24: [5, 10],
|
|
25: [1.4, 3, 5, 10, 15, 20],
|
|
26: [1.4, 3, 5, 10, 15],
|
|
27: [1.4, 3, 5, 10],
|
|
28: [3, 5, 10, 15, 20],
|
|
29: [3, 5, 10],
|
|
30: [5, 10],
|
|
31: [1.4, 3, 5],
|
|
32: [5, 10, 15, 20],
|
|
33: [5, 10, 15, 20],
|
|
34: [5, 10, 15],
|
|
35: [1.4, 3, 5, 10, 15, 20],
|
|
36: [1.4, 3, 5, 10, 15, 20],
|
|
37: [5, 10, 15, 20],
|
|
38: [20],
|
|
39: [5, 10, 15, 20],
|
|
40: [5, 10, 15, 20],
|
|
41: [5, 10, 15, 20],
|
|
42: [5, 10, 15, 20],
|
|
43: [5, 10, 15, 20],
|
|
44: [3, 5, 10, 15, 20],
|
|
45: [5, 10, 15, 20],
|
|
46: [10, 20],
|
|
47: [10, 20],
|
|
48: [5, 10, 15, 20],
|
|
49: [10, 20],
|
|
50: [3, 5, 10, 15, 20],
|
|
51: [3, 5],
|
|
52: [5, 10, 15, 20],
|
|
65: [5, 10, 15, 20],
|
|
66: [1.4, 3, 5, 10, 15, 20],
|
|
67: [5, 10, 15, 20],
|
|
68: [5, 10, 15],
|
|
69: [5],
|
|
70: [5, 10, 15],
|
|
71: [5, 10, 15, 20],
|
|
72: [1.4, 3, 5],
|
|
73: [1.4, 3, 5],
|
|
74: [1.4, 3, 5, 10, 15, 20],
|
|
75: [5, 10, 15, 20],
|
|
76: [5],
|
|
85: [5, 10],
|
|
252: [20],
|
|
255: [20]
|
|
}
|
|
|
|
# Peak throughput lookup tables for each TDD subframe
|
|
# configuration and bandwidth
|
|
# yapf: disable
|
|
tdd_config4_tput_lut = {
|
|
0: {
|
|
5: {'DL': 3.82, 'UL': 2.63},
|
|
10: {'DL': 11.31,'UL': 9.03},
|
|
15: {'DL': 16.9, 'UL': 20.62},
|
|
20: {'DL': 22.88, 'UL': 28.43}
|
|
},
|
|
1: {
|
|
5: {'DL': 6.13, 'UL': 4.08},
|
|
10: {'DL': 18.36, 'UL': 9.69},
|
|
15: {'DL': 28.62, 'UL': 14.21},
|
|
20: {'DL': 39.04, 'UL': 19.23}
|
|
},
|
|
2: {
|
|
5: {'DL': 5.68, 'UL': 2.30},
|
|
10: {'DL': 25.51, 'UL': 4.68},
|
|
15: {'DL': 39.3, 'UL': 7.13},
|
|
20: {'DL': 53.64, 'UL': 9.72}
|
|
},
|
|
3: {
|
|
5: {'DL': 8.26, 'UL': 3.45},
|
|
10: {'DL': 23.20, 'UL': 6.99},
|
|
15: {'DL': 35.35, 'UL': 10.75},
|
|
20: {'DL': 48.3, 'UL': 14.6}
|
|
},
|
|
4: {
|
|
5: {'DL': 6.16, 'UL': 2.30},
|
|
10: {'DL': 26.77, 'UL': 4.68},
|
|
15: {'DL': 40.7, 'UL': 7.18},
|
|
20: {'DL': 55.6, 'UL': 9.73}
|
|
},
|
|
5: {
|
|
5: {'DL': 6.91, 'UL': 1.12},
|
|
10: {'DL': 30.33, 'UL': 2.33},
|
|
15: {'DL': 46.04, 'UL': 3.54},
|
|
20: {'DL': 62.9, 'UL': 4.83}
|
|
},
|
|
6: {
|
|
5: {'DL': 6.13, 'UL': 4.13},
|
|
10: {'DL': 14.79, 'UL': 11.98},
|
|
15: {'DL': 23.28, 'UL': 17.46},
|
|
20: {'DL': 31.75, 'UL': 23.95}
|
|
}
|
|
}
|
|
|
|
tdd_config3_tput_lut = {
|
|
0: {
|
|
5: {'DL': 5.04, 'UL': 3.7},
|
|
10: {'DL': 15.11, 'UL': 17.56},
|
|
15: {'DL': 22.59, 'UL': 30.31},
|
|
20: {'DL': 30.41, 'UL': 41.61}
|
|
},
|
|
1: {
|
|
5: {'DL': 8.07, 'UL': 5.66},
|
|
10: {'DL': 24.58, 'UL': 13.66},
|
|
15: {'DL': 39.05, 'UL': 20.68},
|
|
20: {'DL': 51.59, 'UL': 28.76}
|
|
},
|
|
2: {
|
|
5: {'DL': 7.59, 'UL': 3.31},
|
|
10: {'DL': 34.08, 'UL': 6.93},
|
|
15: {'DL': 53.64, 'UL': 10.51},
|
|
20: {'DL': 70.55, 'UL': 14.41}
|
|
},
|
|
3: {
|
|
5: {'DL': 10.9, 'UL': 5.0},
|
|
10: {'DL': 30.99, 'UL': 10.25},
|
|
15: {'DL': 48.3, 'UL': 15.81},
|
|
20: {'DL': 63.24, 'UL': 21.65}
|
|
},
|
|
4: {
|
|
5: {'DL': 8.11, 'UL': 3.32},
|
|
10: {'DL': 35.74, 'UL': 6.95},
|
|
15: {'DL': 55.6, 'UL': 10.51},
|
|
20: {'DL': 72.72, 'UL': 14.41}
|
|
},
|
|
5: {
|
|
5: {'DL': 9.28, 'UL': 1.57},
|
|
10: {'DL': 40.49, 'UL': 3.44},
|
|
15: {'DL': 62.9, 'UL': 5.23},
|
|
20: {'DL': 82.21, 'UL': 7.15}
|
|
},
|
|
6: {
|
|
5: {'DL': 8.06, 'UL': 5.74},
|
|
10: {'DL': 19.82, 'UL': 17.51},
|
|
15: {'DL': 31.75, 'UL': 25.77},
|
|
20: {'DL': 42.12, 'UL': 34.91}
|
|
}
|
|
}
|
|
|
|
tdd_config2_tput_lut = {
|
|
0: {
|
|
5: {'DL': 3.11, 'UL': 2.55},
|
|
10: {'DL': 9.93, 'UL': 11.1},
|
|
15: {'DL': 13.9, 'UL': 21.51},
|
|
20: {'DL': 20.02, 'UL': 41.66}
|
|
},
|
|
1: {
|
|
5: {'DL': 5.33, 'UL': 4.27},
|
|
10: {'DL': 15.14, 'UL': 13.95},
|
|
15: {'DL': 33.84, 'UL': 19.73},
|
|
20: {'DL': 44.61, 'UL': 27.35}
|
|
},
|
|
2: {
|
|
5: {'DL': 6.87, 'UL': 3.32},
|
|
10: {'DL': 17.06, 'UL': 6.76},
|
|
15: {'DL': 49.63, 'UL': 10.5},
|
|
20: {'DL': 65.2, 'UL': 14.41}
|
|
},
|
|
3: {
|
|
5: {'DL': 5.41, 'UL': 4.17},
|
|
10: {'DL': 16.89, 'UL': 9.73},
|
|
15: {'DL': 44.29, 'UL': 15.7},
|
|
20: {'DL': 53.95, 'UL': 19.85}
|
|
},
|
|
4: {
|
|
5: {'DL': 8.7, 'UL': 3.32},
|
|
10: {'DL': 17.58, 'UL': 6.76},
|
|
15: {'DL': 51.08, 'UL': 10.47},
|
|
20: {'DL': 66.45, 'UL': 14.38}
|
|
},
|
|
5: {
|
|
5: {'DL': 9.46, 'UL': 1.55},
|
|
10: {'DL': 19.02, 'UL': 3.48},
|
|
15: {'DL': 58.89, 'UL': 5.23},
|
|
20: {'DL': 76.85, 'UL': 7.1}
|
|
},
|
|
6: {
|
|
5: {'DL': 4.74, 'UL': 3.9},
|
|
10: {'DL': 12.32, 'UL': 13.37},
|
|
15: {'DL': 27.74, 'UL': 25.02},
|
|
20: {'DL': 35.48, 'UL': 32.95}
|
|
}
|
|
}
|
|
|
|
tdd_config1_tput_lut = {
|
|
0: {
|
|
5: {'DL': 4.25, 'UL': 3.35},
|
|
10: {'DL': 8.38, 'UL': 7.22},
|
|
15: {'DL': 12.41, 'UL': 13.91},
|
|
20: {'DL': 16.27, 'UL': 24.09}
|
|
},
|
|
1: {
|
|
5: {'DL': 7.28, 'UL': 4.61},
|
|
10: {'DL': 14.73, 'UL': 9.69},
|
|
15: {'DL': 21.91, 'UL': 13.86},
|
|
20: {'DL': 27.63, 'UL': 17.18}
|
|
},
|
|
2: {
|
|
5: {'DL': 10.37, 'UL': 2.27},
|
|
10: {'DL': 20.92, 'UL': 4.66},
|
|
15: {'DL': 31.01, 'UL': 7.04},
|
|
20: {'DL': 42.03, 'UL': 9.75}
|
|
},
|
|
3: {
|
|
5: {'DL': 9.25, 'UL': 3.44},
|
|
10: {'DL': 18.38, 'UL': 6.95},
|
|
15: {'DL': 27.59, 'UL': 10.62},
|
|
20: {'DL': 34.85, 'UL': 13.45}
|
|
},
|
|
4: {
|
|
5: {'DL': 10.71, 'UL': 2.26},
|
|
10: {'DL': 21.54, 'UL': 4.67},
|
|
15: {'DL': 31.91, 'UL': 7.2},
|
|
20: {'DL': 43.35, 'UL': 9.74}
|
|
},
|
|
5: {
|
|
5: {'DL': 12.34, 'UL': 1.08},
|
|
10: {'DL': 24.78, 'UL': 2.34},
|
|
15: {'DL': 36.68, 'UL': 3.57},
|
|
20: {'DL': 49.84, 'UL': 4.81}
|
|
},
|
|
6: {
|
|
5: {'DL': 5.76, 'UL': 4.41},
|
|
10: {'DL': 11.68, 'UL': 9.7},
|
|
15: {'DL': 17.34, 'UL': 17.95},
|
|
20: {'DL': 23.5, 'UL': 23.42}
|
|
}
|
|
}
|
|
# yapf: enable
|
|
|
|
# Peak throughput lookup table dictionary
|
|
tdd_config_tput_lut_dict = {
|
|
'TDD_CONFIG1':
|
|
tdd_config1_tput_lut, # DL 256QAM, UL 64QAM & TBS turned OFF
|
|
'TDD_CONFIG2':
|
|
tdd_config2_tput_lut, # DL 256QAM, UL 64 QAM turned ON & TBS OFF
|
|
'TDD_CONFIG3':
|
|
tdd_config3_tput_lut, # DL 256QAM, UL 64QAM & TBS turned ON
|
|
'TDD_CONFIG4':
|
|
tdd_config4_tput_lut # DL 256QAM, UL 64 QAM turned OFF & TBS ON
|
|
}
|
|
|
|
class BtsConfig(BaseSimulation.BtsConfig):
|
|
""" 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_modulation_order: a string indicating a DL modulation scheme
|
|
ul_modulation_order: a string indicating an UL modulation scheme
|
|
tbs_pattern_on: a boolean indicating whether full allocation mode
|
|
should be used or not
|
|
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
|
|
"""
|
|
|
|
def __init__(self):
|
|
""" Initialize the base station config by setting all its
|
|
parameters to None. """
|
|
super(LteSimulation.BtsConfig, self).__init__()
|
|
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_modulation_order = None
|
|
self.ul_modulation_order = None
|
|
self.tbs_pattern_on = 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 __init__(self, simulator, log, dut, test_config, calibration_table):
|
|
""" Initializes the simulator for a single-carrier LTE simulation.
|
|
|
|
Loads a simple LTE simulation environment with 1 basestation.
|
|
|
|
Args:
|
|
simulator: a cellular simulator controller
|
|
log: a logger handle
|
|
dut: a device handler implementing BaseCellularDut
|
|
test_config: test configuration obtained from the config file
|
|
calibration_table: a dictionary containing path losses for
|
|
different bands.
|
|
|
|
"""
|
|
|
|
super(LteSimulation, self).__init__(simulator, log, dut, test_config,
|
|
calibration_table)
|
|
|
|
self.dut.set_preferred_network_type(
|
|
BaseCellularDut.PreferredNetworkType.LTE_ONLY)
|
|
|
|
# Get TBS pattern setting from the test configuration
|
|
if self.KEY_TBS_PATTERN not in test_config:
|
|
self.log.warning("The key '{}' is not set in the config file. "
|
|
"Setting to true by default.".format(
|
|
self.KEY_TBS_PATTERN))
|
|
self.primary_config.tbs_pattern_on = test_config.get(
|
|
self.KEY_TBS_PATTERN, True)
|
|
|
|
# Get the 256-QAM setting from the test configuration
|
|
if self.KEY_DL_256_QAM not in test_config:
|
|
self.log.warning("The key '{}' is not set in the config file. "
|
|
"Setting to false by default.".format(
|
|
self.KEY_DL_256_QAM))
|
|
|
|
self.dl_256_qam = test_config.get(self.KEY_DL_256_QAM, False)
|
|
|
|
if self.dl_256_qam:
|
|
if not self.simulator.LTE_SUPPORTS_DL_256QAM:
|
|
self.log.warning("The key '{}' is set to true but the "
|
|
"simulator doesn't support that modulation "
|
|
"order.".format(self.KEY_DL_256_QAM))
|
|
self.dl_256_qam = False
|
|
else:
|
|
self.primary_config.dl_modulation_order = ModulationType.Q256
|
|
|
|
else:
|
|
self.log.warning(
|
|
'dl modulation 256QAM is not specified in config, '
|
|
'setting to default value 64QAM')
|
|
self.primary_config.dl_modulation_order = ModulationType.Q64
|
|
# Get the 64-QAM setting from the test configuration
|
|
if self.KEY_UL_64_QAM not in test_config:
|
|
self.log.warning("The key '{}' is not set in the config file. "
|
|
"Setting to false by default.".format(
|
|
self.KEY_UL_64_QAM))
|
|
|
|
self.ul_64_qam = test_config.get(self.KEY_UL_64_QAM, False)
|
|
|
|
if self.ul_64_qam:
|
|
if not self.simulator.LTE_SUPPORTS_UL_64QAM:
|
|
self.log.warning("The key '{}' is set to true but the "
|
|
"simulator doesn't support that modulation "
|
|
"order.".format(self.KEY_UL_64_QAM))
|
|
self.ul_64_qam = False
|
|
else:
|
|
self.primary_config.ul_modulation_order = ModulationType.Q64
|
|
else:
|
|
self.log.warning('ul modulation 64QAM is not specified in config, '
|
|
'setting to default value 16QAM')
|
|
self.primary_config.ul_modulation_order = ModulationType.Q16
|
|
|
|
self.simulator.configure_bts(self.primary_config)
|
|
|
|
def setup_simulator(self):
|
|
""" Do initial configuration in the simulator. """
|
|
self.simulator.setup_lte_scenario()
|
|
|
|
def parse_parameters(self, parameters):
|
|
""" Configs an LTE simulation using a list of parameters.
|
|
|
|
Calls the parent method first, then consumes parameters specific to LTE.
|
|
|
|
Args:
|
|
parameters: list of parameters
|
|
"""
|
|
|
|
# Instantiate a new configuration object
|
|
new_config = self.BtsConfig()
|
|
|
|
# Setup band
|
|
|
|
values = self.consume_parameter(parameters, self.PARAM_BAND, 1)
|
|
|
|
if not values:
|
|
raise ValueError(
|
|
"The test name needs to include parameter '{}' followed by "
|
|
"the required band number.".format(self.PARAM_BAND))
|
|
|
|
new_config.band = values[1]
|
|
|
|
# Set TDD-only configs
|
|
if self.get_duplex_mode(new_config.band) == DuplexMode.TDD:
|
|
|
|
# Sub-frame DL/UL config
|
|
values = self.consume_parameter(parameters,
|
|
self.PARAM_FRAME_CONFIG, 1)
|
|
if not values:
|
|
raise ValueError(
|
|
"When a TDD band is selected the frame "
|
|
"structure has to be indicated with the '{}' "
|
|
"parameter followed by a number from 0 to 6.".format(
|
|
self.PARAM_FRAME_CONFIG))
|
|
|
|
new_config.dlul_config = int(values[1])
|
|
|
|
# Special Sub-Frame configuration
|
|
values = self.consume_parameter(parameters, self.PARAM_SSF, 1)
|
|
|
|
if not values:
|
|
self.log.warning(
|
|
'The {} parameter was not provided. Setting '
|
|
'Special Sub-Frame config to 6 by default.'.format(
|
|
self.PARAM_SSF))
|
|
new_config.ssf_config = 6
|
|
else:
|
|
new_config.ssf_config = int(values[1])
|
|
|
|
# Setup bandwidth
|
|
|
|
values = self.consume_parameter(parameters, self.PARAM_BW, 1)
|
|
|
|
if not values:
|
|
raise ValueError(
|
|
"The test name needs to include parameter {} followed by an "
|
|
"int value (to indicate 1.4 MHz use 14).".format(
|
|
self.PARAM_BW))
|
|
|
|
bw = float(values[1])
|
|
|
|
if bw == 14:
|
|
bw = 1.4
|
|
|
|
new_config.bandwidth = bw
|
|
|
|
# Setup mimo mode
|
|
|
|
values = self.consume_parameter(parameters, self.PARAM_MIMO, 1)
|
|
|
|
if not values:
|
|
raise ValueError(
|
|
"The test name needs to include parameter '{}' followed by the "
|
|
"mimo mode.".format(self.PARAM_MIMO))
|
|
|
|
for mimo_mode in MimoMode:
|
|
if values[1] == mimo_mode.value:
|
|
new_config.mimo_mode = mimo_mode
|
|
break
|
|
else:
|
|
raise ValueError("The {} parameter needs to be followed by either "
|
|
"1x1, 2x2 or 4x4.".format(self.PARAM_MIMO))
|
|
|
|
if (new_config.mimo_mode == MimoMode.MIMO_4x4
|
|
and not self.simulator.LTE_SUPPORTS_4X4_MIMO):
|
|
raise ValueError("The test requires 4x4 MIMO, but that is not "
|
|
"supported by the cellular simulator.")
|
|
|
|
# Setup transmission mode
|
|
|
|
values = self.consume_parameter(parameters, self.PARAM_TM, 1)
|
|
|
|
if not values:
|
|
raise ValueError(
|
|
"The test name needs to include parameter {} followed by an "
|
|
"int value from 1 to 4 indicating transmission mode.".
|
|
format(self.PARAM_TM))
|
|
|
|
for tm in TransmissionMode:
|
|
if values[1] == tm.value[2:]:
|
|
new_config.transmission_mode = tm
|
|
break
|
|
else:
|
|
raise ValueError("The {} parameter needs to be followed by either "
|
|
"TM1, TM2, TM3, TM4, TM7, TM8 or TM9.".format(
|
|
self.PARAM_MIMO))
|
|
|
|
# Setup scheduling mode
|
|
|
|
values = self.consume_parameter(parameters, self.PARAM_SCHEDULING, 1)
|
|
|
|
if not values:
|
|
new_config.scheduling_mode = SchedulingMode.STATIC
|
|
self.log.warning(
|
|
"The test name does not include the '{}' parameter. Setting to "
|
|
"static by default.".format(self.PARAM_SCHEDULING))
|
|
elif values[1] == self.PARAM_SCHEDULING_DYNAMIC:
|
|
new_config.scheduling_mode = SchedulingMode.DYNAMIC
|
|
elif values[1] == self.PARAM_SCHEDULING_STATIC:
|
|
new_config.scheduling_mode = SchedulingMode.STATIC
|
|
else:
|
|
raise ValueError(
|
|
"The test name parameter '{}' has to be followed by either "
|
|
"'dynamic' or 'static'.".format(self.PARAM_SCHEDULING))
|
|
|
|
if new_config.scheduling_mode == SchedulingMode.STATIC:
|
|
|
|
values = self.consume_parameter(parameters, self.PARAM_PATTERN, 2)
|
|
|
|
if not values:
|
|
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 '{}' parameter followed by two ints separated by an "
|
|
"underscore indicating downlink and uplink percentages."
|
|
.format(self.PARAM_PATTERN, self.PARAM_PATTERN))
|
|
dl_pattern = 100
|
|
ul_pattern = 100
|
|
else:
|
|
dl_pattern = int(values[1])
|
|
ul_pattern = int(values[2])
|
|
|
|
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.")
|
|
|
|
new_config.dl_rbs, new_config.ul_rbs = (
|
|
self.allocation_percentages_to_rbs(
|
|
new_config.bandwidth, new_config.transmission_mode,
|
|
dl_pattern, ul_pattern))
|
|
|
|
# Look for a DL MCS configuration in the test parameters. If it is
|
|
# not present, use a default value.
|
|
dlmcs = self.consume_parameter(parameters, self.PARAM_DL_MCS, 1)
|
|
|
|
if dlmcs:
|
|
new_config.dl_mcs = int(dlmcs[1])
|
|
else:
|
|
self.log.warning(
|
|
'The test name does not include the {} parameter. Setting '
|
|
'to the max value by default'.format(
|
|
self.PARAM_DL_MCS))
|
|
if self.dl_256_qam and new_config.bandwidth == 1.4:
|
|
new_config.dl_mcs = 26
|
|
elif (not self.dl_256_qam
|
|
and self.primary_config.tbs_pattern_on
|
|
and new_config.bandwidth != 1.4):
|
|
new_config.dl_mcs = 28
|
|
else:
|
|
new_config.dl_mcs = 27
|
|
|
|
# Look for an UL MCS configuration in the test parameters. If it is
|
|
# not present, use a default value.
|
|
ulmcs = self.consume_parameter(parameters, self.PARAM_UL_MCS, 1)
|
|
|
|
if ulmcs:
|
|
new_config.ul_mcs = int(ulmcs[1])
|
|
else:
|
|
self.log.warning(
|
|
'The test name does not include the {} parameter. Setting '
|
|
'to the max value by default'.format(
|
|
self.PARAM_UL_MCS))
|
|
if self.ul_64_qam:
|
|
new_config.ul_mcs = 28
|
|
else:
|
|
new_config.ul_mcs = 23
|
|
|
|
# Configure the simulation for DRX mode
|
|
|
|
drx = self.consume_parameter(parameters, self.PARAM_DRX, 5)
|
|
|
|
if drx and len(drx) == 6:
|
|
new_config.drx_connected_mode = True
|
|
new_config.drx_on_duration_timer = drx[1]
|
|
new_config.drx_inactivity_timer = drx[2]
|
|
new_config.drx_retransmission_timer = drx[3]
|
|
new_config.drx_long_cycle = drx[4]
|
|
try:
|
|
long_cycle = int(drx[4])
|
|
long_cycle_offset = int(drx[5])
|
|
if long_cycle_offset in range(0, long_cycle):
|
|
new_config.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"))
|
|
new_config.drx_long_cycle_offset = 0
|
|
|
|
except ValueError:
|
|
self.log.error(("cDRX long cycle and long cycle offset "
|
|
"must be integers. Disabling cDRX mode."))
|
|
new_config.drx_connected_mode = False
|
|
else:
|
|
self.log.warning(("DRX mode was not configured properly. "
|
|
"Please provide the following 5 values: "
|
|
"1) DRX on duration timer "
|
|
"2) Inactivity timer "
|
|
"3) Retransmission timer "
|
|
"4) Long DRX cycle duration "
|
|
"5) Long DRX cycle offset "
|
|
"Example: drx_2_6_16_20_0"))
|
|
|
|
# Setup LTE RRC status change function and timer for LTE idle test case
|
|
values = self.consume_parameter(parameters,
|
|
self.PARAM_RRC_STATUS_CHANGE_TIMER, 1)
|
|
if not values:
|
|
self.log.info(
|
|
"The test name does not include the '{}' parameter. Disabled "
|
|
"by default.".format(self.PARAM_RRC_STATUS_CHANGE_TIMER))
|
|
self.simulator.set_lte_rrc_state_change_timer(False)
|
|
else:
|
|
timer = int(values[1])
|
|
self.simulator.set_lte_rrc_state_change_timer(True, timer)
|
|
self.rrc_sc_timer = timer
|
|
|
|
# Channel Control Indicator
|
|
values = self.consume_parameter(parameters, self.PARAM_CFI, 1)
|
|
|
|
if not values:
|
|
self.log.warning('The {} parameter was not provided. Setting '
|
|
'CFI to BESTEFFORT.'.format(self.PARAM_CFI))
|
|
new_config.cfi = 'BESTEFFORT'
|
|
else:
|
|
new_config.cfi = values[1]
|
|
|
|
# PHICH group size
|
|
values = self.consume_parameter(parameters, self.PARAM_PHICH, 1)
|
|
|
|
if not values:
|
|
self.log.warning('The {} parameter was not provided. Setting '
|
|
'PHICH group size to 1 by default.'.format(
|
|
self.PARAM_PHICH))
|
|
new_config.phich = '1'
|
|
else:
|
|
if values[1] == '16':
|
|
new_config.phich = '1/6'
|
|
elif values[1] == '12':
|
|
new_config.phich = '1/2'
|
|
elif values[1] in ['1/6', '1/2', '1', '2']:
|
|
new_config.phich = values[1]
|
|
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
|
|
values = self.consume_parameter(parameters, self.PARAM_PAGING, 1)
|
|
|
|
if not values:
|
|
self.log.warning('The {} parameter was not provided. Setting '
|
|
'paging cycle duration to 1280 ms by '
|
|
'default.'.format(self.PARAM_PAGING))
|
|
new_config.paging_cycle = 1280
|
|
else:
|
|
try:
|
|
new_config.paging_cycle = int(values[1])
|
|
except ValueError:
|
|
raise ValueError(
|
|
'The {} parameter has to be followed by the paging cycle '
|
|
'duration in milliseconds.'.format(self.PARAM_PAGING))
|
|
|
|
# Get uplink power
|
|
|
|
ul_power = self.get_uplink_power_from_parameters(parameters)
|
|
|
|
# Power is not set on the callbox until after the simulation is
|
|
# started. Saving this value in a variable for later
|
|
self.sim_ul_power = ul_power
|
|
|
|
# Get downlink power
|
|
|
|
dl_power = self.get_downlink_power_from_parameters(parameters)
|
|
|
|
# Power is not set on the callbox until after the simulation is
|
|
# started. Saving this value in a variable for later
|
|
self.sim_dl_power = dl_power
|
|
|
|
# Setup the base station with the obtained configuration and then save
|
|
# these parameters in the current configuration object
|
|
self.simulator.configure_bts(new_config)
|
|
self.primary_config.incorporate(new_config)
|
|
|
|
# Now that the band is set, calibrate the link if necessary
|
|
self.load_pathloss_if_required()
|
|
|
|
def calibrated_downlink_rx_power(self, bts_config, rsrp):
|
|
""" LTE simulation overrides this method so that it can convert from
|
|
RSRP to total signal power transmitted from the basestation.
|
|
|
|
Args:
|
|
bts_config: the current configuration at the base station
|
|
rsrp: desired rsrp, contained in a key value pair
|
|
"""
|
|
|
|
power = self.rsrp_to_signal_power(rsrp, bts_config)
|
|
|
|
self.log.info(
|
|
"Setting downlink signal level to {} RSRP ({} dBm)".format(
|
|
rsrp, power))
|
|
|
|
# Use parent method to calculate signal level
|
|
return super(LteSimulation,
|
|
self).calibrated_downlink_rx_power(bts_config, power)
|
|
|
|
def downlink_calibration(self, rat=None, power_units_conversion_func=None):
|
|
""" Computes downlink path loss and returns the calibration value.
|
|
|
|
See base class implementation for details.
|
|
|
|
Args:
|
|
rat: ignored, replaced by 'lteRsrp'
|
|
power_units_conversion_func: ignored, replaced by
|
|
self.rsrp_to_signal_power
|
|
|
|
Returns:
|
|
Downlink calibration value and measured DL power. Note that the
|
|
phone only reports RSRP of the primary chain
|
|
"""
|
|
|
|
return super().downlink_calibration(
|
|
rat='lteDbm',
|
|
power_units_conversion_func=self.rsrp_to_signal_power)
|
|
|
|
def rsrp_to_signal_power(self, rsrp, bts_config):
|
|
""" Converts rsrp to total band signal power
|
|
|
|
RSRP is measured per subcarrier, so total band power needs to be
|
|
multiplied by the number of subcarriers being used.
|
|
|
|
Args:
|
|
rsrp: desired rsrp in dBm
|
|
bts_config: a base station configuration object
|
|
Returns:
|
|
Total band signal power in dBm
|
|
"""
|
|
|
|
bandwidth = bts_config.bandwidth
|
|
|
|
if bandwidth == 20: # 100 RBs
|
|
power = rsrp + 30.79
|
|
elif bandwidth == 15: # 75 RBs
|
|
power = rsrp + 29.54
|
|
elif bandwidth == 10: # 50 RBs
|
|
power = rsrp + 27.78
|
|
elif bandwidth == 5: # 25 RBs
|
|
power = rsrp + 24.77
|
|
elif bandwidth == 3: # 15 RBs
|
|
power = rsrp + 22.55
|
|
elif bandwidth == 1.4: # 6 RBs
|
|
power = rsrp + 18.57
|
|
else:
|
|
raise ValueError("Invalid bandwidth value.")
|
|
|
|
return power
|
|
|
|
def maximum_downlink_throughput(self):
|
|
""" Calculates maximum achievable downlink throughput in the current
|
|
simulation state.
|
|
|
|
Returns:
|
|
Maximum throughput in mbps.
|
|
|
|
"""
|
|
|
|
return self.bts_maximum_downlink_throughtput(self.primary_config)
|
|
|
|
def bts_maximum_downlink_throughtput(self, bts_config):
|
|
""" Calculates maximum achievable downlink throughput for a single
|
|
base station from its configuration object.
|
|
|
|
Args:
|
|
bts_config: a base station configuration object.
|
|
|
|
Returns:
|
|
Maximum throughput in mbps.
|
|
|
|
"""
|
|
if bts_config.mimo_mode == MimoMode.MIMO_1x1:
|
|
streams = 1
|
|
elif bts_config.mimo_mode == MimoMode.MIMO_2x2:
|
|
streams = 2
|
|
elif bts_config.mimo_mode == MimoMode.MIMO_4x4:
|
|
streams = 4
|
|
else:
|
|
raise ValueError('Unable to calculate maximum downlink throughput '
|
|
'because the MIMO mode has not been set.')
|
|
|
|
bandwidth = bts_config.bandwidth
|
|
rb_ratio = bts_config.dl_rbs / self.total_rbs_dictionary[bandwidth]
|
|
mcs = bts_config.dl_mcs
|
|
|
|
max_rate_per_stream = None
|
|
|
|
tdd_subframe_config = bts_config.dlul_config
|
|
duplex_mode = self.get_duplex_mode(bts_config.band)
|
|
|
|
if duplex_mode == DuplexMode.TDD:
|
|
if self.dl_256_qam:
|
|
if mcs == 27:
|
|
if bts_config.tbs_pattern_on:
|
|
max_rate_per_stream = self.tdd_config_tput_lut_dict[
|
|
'TDD_CONFIG3'][tdd_subframe_config][bandwidth][
|
|
'DL']
|
|
else:
|
|
max_rate_per_stream = self.tdd_config_tput_lut_dict[
|
|
'TDD_CONFIG2'][tdd_subframe_config][bandwidth][
|
|
'DL']
|
|
else:
|
|
if mcs == 28:
|
|
if bts_config.tbs_pattern_on:
|
|
max_rate_per_stream = self.tdd_config_tput_lut_dict[
|
|
'TDD_CONFIG4'][tdd_subframe_config][bandwidth][
|
|
'DL']
|
|
else:
|
|
max_rate_per_stream = self.tdd_config_tput_lut_dict[
|
|
'TDD_CONFIG1'][tdd_subframe_config][bandwidth][
|
|
'DL']
|
|
|
|
elif duplex_mode == DuplexMode.FDD:
|
|
if (not self.dl_256_qam and bts_config.tbs_pattern_on
|
|
and mcs == 28):
|
|
max_rate_per_stream = {
|
|
3: 9.96,
|
|
5: 17.0,
|
|
10: 34.7,
|
|
15: 52.7,
|
|
20: 72.2
|
|
}.get(bandwidth, None)
|
|
if (not self.dl_256_qam and bts_config.tbs_pattern_on
|
|
and mcs == 27):
|
|
max_rate_per_stream = {
|
|
1.4: 2.94,
|
|
}.get(bandwidth, None)
|
|
elif (not self.dl_256_qam and not bts_config.tbs_pattern_on
|
|
and mcs == 27):
|
|
max_rate_per_stream = {
|
|
1.4: 2.87,
|
|
3: 7.7,
|
|
5: 14.4,
|
|
10: 28.7,
|
|
15: 42.3,
|
|
20: 57.7
|
|
}.get(bandwidth, None)
|
|
elif self.dl_256_qam and bts_config.tbs_pattern_on and mcs == 27:
|
|
max_rate_per_stream = {
|
|
3: 13.2,
|
|
5: 22.9,
|
|
10: 46.3,
|
|
15: 72.2,
|
|
20: 93.9
|
|
}.get(bandwidth, None)
|
|
elif self.dl_256_qam and bts_config.tbs_pattern_on and mcs == 26:
|
|
max_rate_per_stream = {
|
|
1.4: 3.96,
|
|
}.get(bandwidth, None)
|
|
elif (self.dl_256_qam and not bts_config.tbs_pattern_on
|
|
and mcs == 27):
|
|
max_rate_per_stream = {
|
|
3: 11.3,
|
|
5: 19.8,
|
|
10: 44.1,
|
|
15: 68.1,
|
|
20: 88.4
|
|
}.get(bandwidth, None)
|
|
elif (self.dl_256_qam and not bts_config.tbs_pattern_on
|
|
and mcs == 26):
|
|
max_rate_per_stream = {
|
|
1.4: 3.96,
|
|
}.get(bandwidth, None)
|
|
|
|
if not max_rate_per_stream:
|
|
raise NotImplementedError(
|
|
"The calculation for tbs pattern = {} "
|
|
"and mcs = {} is not implemented.".format(
|
|
"FULLALLOCATION"
|
|
if bts_config.tbs_pattern_on else "OFF", mcs))
|
|
|
|
return max_rate_per_stream * streams * rb_ratio
|
|
|
|
def maximum_uplink_throughput(self):
|
|
""" Calculates maximum achievable uplink throughput in the current
|
|
simulation state.
|
|
|
|
Returns:
|
|
Maximum throughput in mbps.
|
|
|
|
"""
|
|
|
|
return self.bts_maximum_uplink_throughtput(self.primary_config)
|
|
|
|
def bts_maximum_uplink_throughtput(self, bts_config):
|
|
""" Calculates maximum achievable uplink throughput for the selected
|
|
basestation from its configuration object.
|
|
|
|
Args:
|
|
bts_config: an LTE base station configuration object.
|
|
|
|
Returns:
|
|
Maximum throughput in mbps.
|
|
|
|
"""
|
|
|
|
bandwidth = bts_config.bandwidth
|
|
rb_ratio = bts_config.ul_rbs / self.total_rbs_dictionary[bandwidth]
|
|
mcs = bts_config.ul_mcs
|
|
|
|
max_rate_per_stream = None
|
|
|
|
tdd_subframe_config = bts_config.dlul_config
|
|
duplex_mode = self.get_duplex_mode(bts_config.band)
|
|
|
|
if duplex_mode == DuplexMode.TDD:
|
|
if self.ul_64_qam:
|
|
if mcs == 28:
|
|
if bts_config.tbs_pattern_on:
|
|
max_rate_per_stream = self.tdd_config_tput_lut_dict[
|
|
'TDD_CONFIG3'][tdd_subframe_config][bandwidth][
|
|
'UL']
|
|
else:
|
|
max_rate_per_stream = self.tdd_config_tput_lut_dict[
|
|
'TDD_CONFIG2'][tdd_subframe_config][bandwidth][
|
|
'UL']
|
|
else:
|
|
if mcs == 23:
|
|
if bts_config.tbs_pattern_on:
|
|
max_rate_per_stream = self.tdd_config_tput_lut_dict[
|
|
'TDD_CONFIG4'][tdd_subframe_config][bandwidth][
|
|
'UL']
|
|
else:
|
|
max_rate_per_stream = self.tdd_config_tput_lut_dict[
|
|
'TDD_CONFIG1'][tdd_subframe_config][bandwidth][
|
|
'UL']
|
|
|
|
elif duplex_mode == DuplexMode.FDD:
|
|
if mcs == 23 and not self.ul_64_qam:
|
|
max_rate_per_stream = {
|
|
1.4: 2.85,
|
|
3: 7.18,
|
|
5: 12.1,
|
|
10: 24.5,
|
|
15: 36.5,
|
|
20: 49.1
|
|
}.get(bandwidth, None)
|
|
elif mcs == 28 and self.ul_64_qam:
|
|
max_rate_per_stream = {
|
|
1.4: 4.2,
|
|
3: 10.5,
|
|
5: 17.2,
|
|
10: 35.3,
|
|
15: 53.0,
|
|
20: 72.6
|
|
}.get(bandwidth, None)
|
|
|
|
if not max_rate_per_stream:
|
|
raise NotImplementedError(
|
|
"The calculation fir mcs = {} is not implemented.".format(
|
|
"FULLALLOCATION"
|
|
if bts_config.tbs_pattern_on else "OFF", mcs))
|
|
|
|
return max_rate_per_stream * rb_ratio
|
|
|
|
def allocation_percentages_to_rbs(self, bw, tm, 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:
|
|
bw: the bandwidth for the which the RB configuration is requested
|
|
tm: the transmission in which the base station will be operating
|
|
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 = self.total_rbs_dictionary[bw]
|
|
min_dl_rbs = self.min_dl_rbs_dictionary[bw]
|
|
min_ul_rbs = self.min_ul_rbs_dictionary[bw]
|
|
|
|
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 tm == TransmissionMode.TM3 or tm == 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 / self.rbg_dictionary[bw]) *
|
|
self.rbg_dictionary[bw])
|
|
|
|
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
|
|
|
|
def calibrate(self, band):
|
|
""" Calculates UL and DL path loss if it wasn't done before
|
|
|
|
Before running the base class implementation, configure the base station
|
|
to only use one downlink antenna with maximum bandwidth.
|
|
|
|
Args:
|
|
band: the band that is currently being calibrated.
|
|
"""
|
|
|
|
# Save initial values in a configuration object so they can be restored
|
|
restore_config = self.BtsConfig()
|
|
restore_config.mimo_mode = self.primary_config.mimo_mode
|
|
restore_config.transmission_mode = self.primary_config.transmission_mode
|
|
restore_config.bandwidth = self.primary_config.bandwidth
|
|
|
|
# Set up a temporary calibration configuration.
|
|
temporary_config = self.BtsConfig()
|
|
temporary_config.mimo_mode = MimoMode.MIMO_1x1
|
|
temporary_config.transmission_mode = TransmissionMode.TM1
|
|
temporary_config.bandwidth = max(
|
|
self.allowed_bandwidth_dictionary[int(band)])
|
|
self.simulator.configure_bts(temporary_config)
|
|
self.primary_config.incorporate(temporary_config)
|
|
|
|
super().calibrate(band)
|
|
|
|
# Restore values as they were before changing them for calibration.
|
|
self.simulator.configure_bts(restore_config)
|
|
self.primary_config.incorporate(restore_config)
|
|
|
|
def start_traffic_for_calibration(self):
|
|
"""
|
|
If TBS pattern is set to full allocation, there is no need to start
|
|
IP traffic.
|
|
"""
|
|
if not self.primary_config.tbs_pattern_on:
|
|
super().start_traffic_for_calibration()
|
|
|
|
def stop_traffic_for_calibration(self):
|
|
"""
|
|
If TBS pattern is set to full allocation, IP traffic wasn't started
|
|
"""
|
|
if not self.primary_config.tbs_pattern_on:
|
|
super().stop_traffic_for_calibration()
|
|
|
|
def get_duplex_mode(self, band):
|
|
""" Determines if the band uses FDD or TDD duplex mode
|
|
|
|
Args:
|
|
band: a band number
|
|
Returns:
|
|
an variable of class DuplexMode indicating if band is FDD or TDD
|
|
"""
|
|
|
|
if 33 <= int(band) <= 46:
|
|
return DuplexMode.TDD
|
|
else:
|
|
return DuplexMode.FDD
|
|
|
|
def get_measured_ul_power(self, samples=5, wait_after_sample=3):
|
|
""" Calculates UL power using measurements from the callbox and the
|
|
calibration data.
|
|
|
|
Args:
|
|
samples: the numble of samples to average
|
|
wait_after_sample: time in seconds to wait in between samples
|
|
|
|
Returns:
|
|
the ul power at the UE antenna ports in dBs
|
|
"""
|
|
ul_power_sum = 0
|
|
samples_left = samples
|
|
|
|
while samples_left > 0:
|
|
ul_power_sum += self.simulator.get_measured_pusch_power()
|
|
samples_left -= 1
|
|
time.sleep(wait_after_sample)
|
|
|
|
# Got enough samples, return calibrated average
|
|
if self.dl_path_loss:
|
|
return ul_power_sum / samples + self.ul_path_loss
|
|
else:
|
|
self.log.warning('No uplink calibration data. Returning '
|
|
'uncalibrated values as measured by the '
|
|
'callbox.')
|
|
return ul_power_sum / samples
|
|
|
|
def send_sms(self, sms_message):
|
|
""" Sets the SMS message for the simulation. """
|
|
self.simulator.send_sms(sms_message)
|