• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3#   Copyright 2023 - 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"""Provides utilities for generating CMW500 CA scenarios based on band/mimo combinations.
17
18NOTE: Currently does not support:
19    1. Scenarios requiring multiple CMW500s
20    2. SUA coprocessing
21    3. Fading scenarios
22"""
23
24from collections import defaultdict
25from collections import namedtuple
26
27# Maps CMW scenario names to antenna (MIMO) configuration.
28SCENARIO_NAME_MAPPING = {
29    # 1CC
30    "SCEL": (1, ),
31    "TRO": (2, ),
32    "AD": (4, ),
33    # 2CC
34    "CATR": (1, 1),
35    "CAFR": (2, 2),
36    "BF": (4, 2),
37    "BFSM4": (2, 4),
38    "BH": (4, 4),
39    # 3CC
40    "CC": (1, 1, 1),
41    "CF": (2, 2, 2),
42    "CCMP": (2, 1, 1),
43    "CCMS1": (1, 2, 1),
44    # 4CC
45    "DD": (1, 1, 1, 1),
46    "DH": (2, 2, 2, 2),
47    # 5CC - 8CC require multiple CMWs
48}
49
50# Maps antenna combinations to CMW scenario name.
51SCENARIO_ANTENNA_MAPPING = {v: k for k, v in SCENARIO_NAME_MAPPING.items()}
52
53
54def get_scenario(bands, antennas):
55    """Gets a compatible scenario for the given band/antenna combination.
56
57    Args:
58        bands: a list defining the bands to use for each CC.
59        antennas: a list of integers defining the number of antennas to use for each CC.
60
61    Returns:
62        CMW500Scenario: the generated scenario.
63
64    Raises:
65        ValueError: if there is no scenario available for the given antenna/band combination.
66    """
67    antennas = tuple(antennas)
68    if not antennas in SCENARIO_ANTENNA_MAPPING:
69        raise ValueError(
70            "No CMW scenario matches antenna combination: {}".format(antennas))
71
72    generator = CMW500ScenarioGenerator()
73    port_configs = [generator.get_next(b, a) for b, a in zip(bands, antennas)]
74
75    scenario_name = SCENARIO_ANTENNA_MAPPING[antennas]
76    return CMW500Scenario(scenario_name, port_configs)
77
78
79def get_antennas(name):
80    """Gets the antenna combination mimo corresponding to a scenario name.
81
82    Args:
83        name: a string defining the scenario name.
84
85    Returns:
86        antennas: a list of integers defining the number of antennas for each CC.
87
88    Raises:
89        ValueError: if the scenario name is unknown.
90    """
91    if not name in SCENARIO_NAME_MAPPING:
92        raise ValueError("Unknown scenario name: {}".format(name))
93
94    return list(SCENARIO_NAME_MAPPING[name])
95
96
97class CMW500Scenario(object):
98    """A routed scenario in a CMW500."""
99
100    def __init__(self, name, configs):
101        """Initialize a CMW500 scenario from a name and PortConfiguration list.
102
103        Args:
104            name: a string defining the CMW500 scenario name.
105            configs: list(list(PortConfigurations)) defining the configurations for each CC.
106        """
107        self.name = name
108        self.configs = configs
109
110    @property
111    def routing(self):
112        """Gets the CMW routing text for the scenario.
113
114        Returns:
115            routing: a string defining the routing text for the CMW scenario command.
116        """
117        routing = []
118        i = 1
119        for carrier_config in self.configs:
120            routing.append("SUA{}".format(i))
121            # Assume PCC antenna & uplink are always on port 1
122            if i == 1:
123                routing.append("RF1C")
124                routing.append("RX1")
125            for config in carrier_config:
126                routing.append("RF{}C".format(config.port_id))
127                routing.append("TX{}".format(config.converter_id))
128            i += 1
129        return ",".join(routing)
130
131
132# A port/converter combination for a single callbox port in a component carrier.
133PortConfiguration = namedtuple("PortConfiguration", "port_id converter_id")
134
135
136class CMW500ScenarioGenerator(object):
137    """Class that is responsible for generating port/converter configurations for cmw500.
138
139    Generator prioritizes using the fewest total number of antenna ports it can.
140
141    Generation rules:
142        - There are 4 'converters'
143        - Each converter can be used up to a maximum of twice (if both CCs use the same band)
144        - Each converter has 2 potental antenna ports that it can be used with
145    """
146
147    # Maps converters to possible antenna ports.
148    PORT_MAPPING = {1: (1, 2), 2: (3, 4), 3: (1, 2), 4: (3, 4)}
149
150    # Maps antennas to possible converters.
151    CONVERTER_MAPPING = {1: (1, 3), 2: (1, 3), 3: (2, 4), 4: (2, 4)}
152
153    def __init__(self):
154        self.used_once = defaultdict(list)
155        self.free_converters = set([1, 2, 3, 4])
156
157    def get_next(self, band, antenna_count):
158        """Generates a routing configuration for the next component carrier in the sequence.
159
160        Returns:
161            configs a list of PortConfigurations defining the configuration to use for each port
162
163        Raises:
164            ValueError: if the generator fails to find a compatible scenario routing.
165        """
166        if antenna_count < 1:
167            raise ValueError("antenna_count must be greater than 0")
168
169        configs = []
170        free_ports = [1, 3, 2, 4]
171        converters_temp = []
172        # First, try to reuse previously used converters where we can.
173        for converter in self.used_once[band]:
174            port = next(
175                (a for a in self.PORT_MAPPING[converter] if a in free_ports),
176                None,
177            )
178            if port is None:
179                # No port available to be used with this converter, save for later.
180                converters_temp.append(converter)
181                continue
182
183            free_ports.remove(port)
184            configs.append(PortConfiguration(port, converter))
185            if len(configs) == antenna_count:
186                break
187
188        self.used_once[band] = converters_temp
189        if len(configs) == antenna_count:
190            return configs
191
192        # Try to use unused converters.
193        for port in free_ports:
194            converter = next(
195                (c for c in self.CONVERTER_MAPPING[port]
196                 if c in self.free_converters),
197                None,
198            )
199            if converter is None:
200                continue
201
202            # Save converter to reuse later.
203            self.free_converters.remove(converter)
204            self.used_once[band].append(converter)
205            configs.append(PortConfiguration(port, converter))
206            if len(configs) == antenna_count:
207                break
208
209        if len(configs) != antenna_count:
210            raise ValueError(
211                "Unable to generate CMW500 scenario for requested combination")
212
213        return configs
214