Source code for padrick.Model.PortGroup

# Manuel Eggimann <meggimann@iis.ee.ethz.ch>
#
# Copyright (C) 2021-2022 ETH Zürich
# 
# 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.

from typing import Optional, List, Set, Mapping, Union, Dict

from padrick.Model.Constants import SYSTEM_VERILOG_IDENTIFIER
from padrick.Model.PadSignal import Signal, SignalDirection
from padrick.Model.Port import Port
from padrick.Model.SignalExpressionType import SignalExpressionType
from pydantic import BaseModel, constr, conint, validator, root_validator, Extra, conset

from padrick.Model.TemplatedIdentifier import TemplatedIdentifierType
from padrick.Model.TemplatedString import TemplatedStringType
from padrick.Model.UserAttrs import UserAttrs
from padrick.Model.Utilities import sort_signals, sort_ports, cached_property


[docs]class PortGroup(BaseModel): name: TemplatedIdentifierType description: Optional[TemplatedStringType] mux_groups: Optional[conset(TemplatedIdentifierType, min_items=1)] ports: List[Port] output_defaults: Union[SignalExpressionType, Mapping[Union[Signal, str], Optional[SignalExpressionType]]] = {} multiple: conint(ge=1) = 1 user_attr: Optional[UserAttrs] _method_cache = {}
[docs] class Config: extra = Extra.forbid underscore_attrs_are_private = True
[docs] @validator('output_defaults') def expand_default_value_for_connection_defaults(cls, output_defaults, values): if isinstance(output_defaults, SignalExpressionType): port_signals_pad2soc = set() for port in values.get('ports', []): port_signals_pad2soc.update(port.port_signals_pad2chip) output_defaults = {port_signal.name: output_defaults for port_signal in port_signals_pad2soc} return output_defaults
[docs] @root_validator(skip_on_failure=True) def check_all_pad2soc_ports_have_default(cls, values): port_signals_pad2soc = set() for port in values['ports']: port_signals_pad2soc.update(port.port_signals_pad2chip) for port in port_signals_pad2soc: if port not in values['output_defaults'] and port.name not in values['output_defaults']: raise ValueError(f"Found port signal {port.name} with direction pad2soc that does not specify a connection default.") return values
[docs] @validator('ports') def expand_multi_ports(cls, ports): """ Expand ports with muliple>1 into individual port objects replacing the '<>' token in name, description and signalexpression with the array index. """ expanded_ports = [] for port in ports: expanded_ports.extend(port.expand_port()) return expanded_ports
[docs] @validator('ports') def check_ports_are_unique(cls, ports): port_names_seen = set() for port in ports: if port.name in port_names_seen: raise ValueError(f"Duplicate port name {port.name}. Ports within a port group must be unique.") else: port_names_seen.add(port.name) return ports
[docs] @validator('ports') def check_port_signals_are_not_bidirectional(cls, v): port_signals = set() for port in v: port_signals.update(port.port_signals) # Check if there are entries with the same name but different direction. seen: Mapping[str, Signal] = {} for signal in port_signals: if signal.name in seen: if seen[signal.name].direction != signal.direction: raise ValueError(f"Found port signal {signal.name} that is used for both, input and output " f"pad_signals. " f"Bi-directional port signals are not supported.") else: seen[signal.name] = signal return v
[docs] @validator('ports') def check_pad2soc_ports_are_not_multiple_connected(cls, v): port_signals = set() for port in v: for port_signal in port.port_signals_pad2chip: if port_signal in port_signals: raise ValueError(f"Cannot connect pad2soc signal {port_signal.name} to multiple pad_signals. " f"Within a single port_group a port signal with direction pad2soc must only be" f"referenced in at most one port connection list. (Otherwise we would have driving conflicts).") else: port_signals.add(port_signal) return v
@cached_property def port_signals(self) -> List[Signal]: return sort_signals(set.union(*[set(port.port_signals) for port in self.ports])) @cached_property def port_signals_soc2pads(self) -> List[Signal]: return sort_signals(set([signal for signal in self.port_signals if signal.direction == SignalDirection.soc2pads])) @cached_property def port_signals_pads2soc(self) -> List[Signal]: return sort_signals(set([signal for signal in self.port_signals if signal.direction == SignalDirection.pads2soc]))
[docs] def get_ports_in_mux_groups(self, mux_groups: Set[str]) -> List[Port]: ports_in_mux_group = [port for port in self.ports if mux_groups.intersection(port.mux_groups)] return sort_ports(ports_in_mux_group)
[docs] def expand_port_group(self) -> List['PortGroup']: expanded_port_groups = [] for i in range(self.multiple): expanded_port_group: PortGroup = self.copy() expanded_port_group.name = expanded_port_group.name.evaluate_template(i) expanded_port_group.description = expanded_port_group.description.evaluate_template(i) if expanded_port_group.description else None expanded_port_group.user_attr = expanded_port_group.user_attr.expand_user_attrs(i) if expanded_port_group.user_attr else None expanded_port_group.mux_groups = set(map(lambda mux_group: mux_group.evaluate_template(i), expanded_port_group.mux_groups)) if expanded_port_group.mux_groups else None expanded_port_group.multiple = 1 expanded_port_groups.append(expanded_port_group) return expanded_port_groups