|  | # Copyright lowRISC contributors. | 
|  | # Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
|  | # SPDX-License-Identifier: Apache-2.0 | 
|  |  | 
|  | from typing import Dict, List, NamedTuple, Tuple | 
|  |  | 
|  | from .lib import Name | 
|  |  | 
|  |  | 
|  | def _yn_to_bool(yn: object) -> bool: | 
|  | yn_str = str(yn) | 
|  | if yn_str.lower() == 'yes': | 
|  | return True | 
|  | if yn_str.lower() == 'no': | 
|  | return False | 
|  | raise ValueError('Unknown yes/no value: {!r}.'.format(yn)) | 
|  |  | 
|  |  | 
|  | def _bool_to_yn(val: bool) -> str: | 
|  | return 'yes' if val else 'no' | 
|  |  | 
|  |  | 
|  | def _to_int(val: object) -> int: | 
|  | if isinstance(val, int): | 
|  | return val | 
|  | return int(str(val)) | 
|  |  | 
|  |  | 
|  | def _check_choices(val: str, what: str, choices: List[str]) -> str: | 
|  | if val in choices: | 
|  | return val | 
|  | raise ValueError('{} is {!r}, which is not one of the expected values: {}.' | 
|  | .format(what, val, choices)) | 
|  |  | 
|  |  | 
|  | class SourceClock: | 
|  | '''A clock source (input to the top-level)''' | 
|  | def __init__(self, raw: Dict[str, object]): | 
|  | self.name = str(raw['name']) | 
|  | self.aon = _yn_to_bool(raw['aon']) | 
|  | self.freq = _to_int(raw['freq']) | 
|  | self.ref = raw.get('ref', False) | 
|  |  | 
|  | def _asdict(self) -> Dict[str, object]: | 
|  | return { | 
|  | 'name': self.name, | 
|  | 'aon': _bool_to_yn(self.aon), | 
|  | 'freq': str(self.freq), | 
|  | 'ref': self.ref | 
|  | } | 
|  |  | 
|  |  | 
|  | class DerivedSourceClock(SourceClock): | 
|  | '''A derived source clock (divided down from some other clock)''' | 
|  | def __init__(self, | 
|  | raw: Dict[str, object], | 
|  | sources: Dict[str, SourceClock]): | 
|  | super().__init__(raw) | 
|  | self.div = _to_int(raw['div']) | 
|  | self.src = sources[str(raw['src'])] | 
|  |  | 
|  | def _asdict(self) -> Dict[str, object]: | 
|  | ret = super()._asdict() | 
|  | ret['div'] = str(self.div) | 
|  | ret['src'] = self.src.name | 
|  | return ret | 
|  |  | 
|  |  | 
|  | class ClockSignal: | 
|  | '''A clock signal in the design''' | 
|  | def __init__(self, name: str, src: SourceClock): | 
|  | self.name = name | 
|  | self.src = src | 
|  | self.endpoints = []  # type: List[Tuple[str, str]] | 
|  |  | 
|  | def add_endpoint(self, ep_name: str, ep_port: str) -> None: | 
|  | self.endpoints.append((ep_name, ep_port)) | 
|  |  | 
|  |  | 
|  | class Group: | 
|  | def __init__(self, | 
|  | raw: Dict[str, object], | 
|  | what: str): | 
|  | self.name = str(raw['name']) | 
|  | self.src = str(raw['src']) | 
|  | self.sw_cg = _check_choices(str(raw['sw_cg']), 'sw_cg for ' + what, | 
|  | ['yes', 'no', 'hint']) | 
|  | if self.src == 'yes' and self.sw_cg != 'no': | 
|  | raise ValueError(f'Clock group {self.name} has an invalid ' | 
|  | f'combination of src and sw_cg: {self.src} and ' | 
|  | f'{self.sw_cg}, respectively.') | 
|  |  | 
|  | self.unique = _yn_to_bool(raw.get('unique', 'no')) | 
|  | if self.sw_cg == 'no' and self.unique: | 
|  | raise ValueError(f'Clock group {self.name} has an invalid ' | 
|  | f'combination with sw_cg of {self.sw_cg} and ' | 
|  | f'unique set.') | 
|  |  | 
|  | self.clocks = {}  # type: Dict[str, ClockSignal] | 
|  |  | 
|  | def add_clock(self, clk_name: str, src: SourceClock) -> ClockSignal: | 
|  | # Duplicates are ok, so long as they have the same source. | 
|  | sig = self.clocks.get(clk_name) | 
|  | if sig is not None: | 
|  | if sig.src is not src: | 
|  | raise ValueError(f'Cannot add clock {clk_name} to group ' | 
|  | f'{self.name} with source {src.name}: the ' | 
|  | f'clock is there already with source ' | 
|  | f'{sig.src.name}.') | 
|  | else: | 
|  | sig = ClockSignal(clk_name, src) | 
|  | self.clocks[clk_name] = sig | 
|  |  | 
|  | return sig | 
|  |  | 
|  | def _asdict(self) -> Dict[str, object]: | 
|  | return { | 
|  | 'name': self.name, | 
|  | 'src': self.src, | 
|  | 'sw_cg': self.sw_cg, | 
|  | 'unique': _bool_to_yn(self.unique), | 
|  | 'clocks': {name: sig.src.name | 
|  | for name, sig in self.clocks.items()} | 
|  | } | 
|  |  | 
|  |  | 
|  | class TypedClocks(NamedTuple): | 
|  | # External clocks that are consumed only inside the clkmgr and are fed from | 
|  | # an external ast source. | 
|  | ast_clks: Dict[str, ClockSignal] | 
|  |  | 
|  | # Clocks fed through clkmgr but not disturbed in any way. This maintains | 
|  | # the clocking structure consistency. This includes two groups of clocks: | 
|  | # | 
|  | #   - Clocks fed from the always-on source | 
|  | #   - Clocks fed to the powerup group | 
|  | ft_clks: Dict[str, ClockSignal] | 
|  |  | 
|  | # Non-feedthrough clocks that have no software control. These clocks are | 
|  | # root-gated and the root-gated clock is then exposed directly in clocks_o. | 
|  | rg_clks: Dict[str, ClockSignal] | 
|  |  | 
|  | # Non-feedthrough clocks that have direct software control. These are | 
|  | # root-gated, but (unlike rg_clks) then go through a second clock gate | 
|  | # which is controlled by software. | 
|  | sw_clks: Dict[str, ClockSignal] | 
|  |  | 
|  | # Non-feedthrough clocks that have "hint" software control (with a feedback | 
|  | # mechanism to allow blocks to avoid being suspended when they are not | 
|  | # idle). | 
|  | hint_clks: Dict[str, ClockSignal] | 
|  |  | 
|  | # A list of the of non-always-on clock sources that are exposed without | 
|  | # division, sorted by name. This doesn't include clock sources that are | 
|  | # only used to derive divided clocks (we might gate the divided clocks, but | 
|  | # don't bother gating the upstream source). | 
|  | rg_srcs: List[str] | 
|  |  | 
|  | # A diction of the clock families. | 
|  | # The key for each is root clock, while the list contains all the clocks | 
|  | # of the family, inclusive of itself. | 
|  | # For example | 
|  | # 'io': ['io', 'io_div2', 'io_div4'] | 
|  | parent_child_clks: Dict[str, List] | 
|  |  | 
|  | def all_clocks(self) -> Dict[str, ClockSignal]: | 
|  | ret = {} | 
|  | ret.update(self.ft_clks) | 
|  | ret.update(self.hint_clks) | 
|  | ret.update(self.rg_clks) | 
|  | ret.update(self.sw_clks) | 
|  | return ret | 
|  |  | 
|  | def hint_names(self) -> Dict[str, str]: | 
|  | '''Return a dictionary with hint names for the hint clocks | 
|  |  | 
|  | These are used as enum items that name the clock hint signals. The | 
|  | insertion ordering in this dictionary is important because it gives the | 
|  | mapping from enum name to index. | 
|  |  | 
|  | ''' | 
|  | # A map from endpoint to the list of hint clocks that it uses. | 
|  | ep_to_hints = {} | 
|  | for sig in self.hint_clks.values(): | 
|  | for ep, port_name in sig.endpoints: | 
|  | ep_to_hints.setdefault(ep, []).append(sig.name) | 
|  |  | 
|  | # A map from hint clock name to the associated enumeration name which | 
|  | # will appear in hint_names_e in clkmgr_pkg.sv. Note that this is | 
|  | # ordered alphabetically by endpoint: the precise ordering shouldn't | 
|  | # matter, but it's probably nicer to keep endpoints' signals together. | 
|  | hint_names = {} | 
|  | for ep, clks in sorted(ep_to_hints.items()): | 
|  | for clk in sorted(clks): | 
|  | # Remove any "clk" prefix | 
|  | clk_name = Name.from_snake_case(clk).remove_part('clk') | 
|  | hint_name = Name(['hint']) + clk_name | 
|  | hint_names[clk] = hint_name.as_camel_case() | 
|  |  | 
|  | return hint_names | 
|  |  | 
|  |  | 
|  | class Clocks: | 
|  | '''Clock connections for the chip''' | 
|  | def __init__(self, raw: Dict[str, object]): | 
|  | self.hier_paths = {} | 
|  | assert isinstance(raw['hier_paths'], dict) | 
|  | for grp_src, path in raw['hier_paths'].items(): | 
|  | self.hier_paths[str(grp_src)] = str(path) | 
|  |  | 
|  | assert isinstance(raw['srcs'], list) | 
|  | self.srcs = {} | 
|  | for r in raw['srcs']: | 
|  | clk = SourceClock(r) | 
|  | self.srcs[clk.name] = clk | 
|  |  | 
|  | self.derived_srcs = {} | 
|  | assert isinstance(raw['derived_srcs'], list) | 
|  | for r in raw['derived_srcs']: | 
|  | clk = DerivedSourceClock(r, self.srcs) | 
|  | self.derived_srcs[clk.name] = clk | 
|  |  | 
|  | self.all_srcs = self.srcs.copy() | 
|  | self.all_srcs.update(self.derived_srcs) | 
|  |  | 
|  | self.groups = {} | 
|  | assert isinstance(raw['groups'], list) | 
|  | for idx, raw_grp in enumerate(raw['groups']): | 
|  | assert isinstance(raw_grp, dict) | 
|  | grp = Group(raw_grp, f'clocks.groups[{idx}]') | 
|  | self.groups[grp.name] = grp | 
|  |  | 
|  | def _asdict(self) -> Dict[str, object]: | 
|  | return { | 
|  | 'hier_paths': self.hier_paths, | 
|  | 'srcs': list(self.srcs.values()), | 
|  | 'derived_srcs': list(self.derived_srcs.values()), | 
|  | 'groups': list(self.groups.values()) | 
|  | } | 
|  |  | 
|  | def add_clock_to_group(self, | 
|  | grp: Group, | 
|  | clk_name: str, | 
|  | src_name: str) -> ClockSignal: | 
|  | src = self.all_srcs.get(src_name) | 
|  | if src is None: | 
|  | raise ValueError(f'Cannot add clock {clk_name} to group ' | 
|  | f'{grp.name}: the given source name is ' | 
|  | f'{src_name}, which is unknown.') | 
|  | return grp.add_clock(clk_name, src) | 
|  |  | 
|  | def get_clock_by_name(self, name: str) -> object: | 
|  | ret = self.all_srcs.get(name) | 
|  | if ret is None: | 
|  | raise ValueError(f'{name} is not a valid clock') | 
|  | return ret | 
|  |  | 
|  | def reset_signals(self) -> List[str]: | 
|  | '''Return the list of clock reset signal names | 
|  |  | 
|  | These signals are inputs to the clock manager (from the reset | 
|  | manager) | 
|  |  | 
|  | ''' | 
|  | ret = [] | 
|  | for src in self.srcs.values(): | 
|  | ret.append(f'rst_{src.name}_ni') | 
|  | for src in self.derived_srcs.values(): | 
|  | ret.append(f'rst_{src.name}_ni') | 
|  | return ret | 
|  |  | 
|  | def typed_clocks(self) -> TypedClocks: | 
|  | '''Split the clocks by type''' | 
|  | ast_clks = {} | 
|  | ft_clks = {} | 
|  | rg_clks = {} | 
|  | sw_clks = {} | 
|  | hint_clks = {} | 
|  | rg_srcs_set = set() | 
|  | parent_child_clks = {} | 
|  |  | 
|  | for grp in self.groups.values(): | 
|  | if grp.name == 'powerup': | 
|  | # All clocks in the "powerup" group are considered feed-throughs. | 
|  | ft_clks.update(grp.clocks) | 
|  | continue | 
|  |  | 
|  | for clk, sig in grp.clocks.items(): | 
|  | if grp.src == "ext": | 
|  | ast_clks[clk] = sig | 
|  | continue | 
|  |  | 
|  | if sig.src.aon: | 
|  | # Any always-on clock is a feedthrough | 
|  | ft_clks[clk] = sig | 
|  | continue | 
|  |  | 
|  | rg_srcs_set.add(sig.src.name) | 
|  |  | 
|  | if grp.sw_cg == 'no': | 
|  | # A non-feedthrough clock with no software control | 
|  | rg_clks[clk] = sig | 
|  | continue | 
|  |  | 
|  | if grp.sw_cg == 'yes': | 
|  | # A non-feedthrough clock with direct software control | 
|  | sw_clks[clk] = sig | 
|  | continue | 
|  |  | 
|  | # The only other valid value for the sw_cg field is "hint", which | 
|  | # means a non-feedthrough clock with "hint" software control. | 
|  | assert grp.sw_cg == 'hint' | 
|  | hint_clks[clk] = sig | 
|  | continue | 
|  |  | 
|  | # Define a canonical ordering for rg_srcs | 
|  | rg_srcs = list(sorted(rg_srcs_set)) | 
|  |  | 
|  | # Define a list for each "family" of clocks | 
|  | for name, clk in self.srcs.items(): | 
|  | if not clk.aon: | 
|  | parent_child_clks[name] = [name]; | 
|  |  | 
|  | for name, clk in self.derived_srcs.items(): | 
|  | parent_child_clks[clk.src.name].append(name) | 
|  |  | 
|  |  | 
|  | return TypedClocks(ast_clks=ast_clks, | 
|  | ft_clks=ft_clks, | 
|  | rg_clks=rg_clks, | 
|  | sw_clks=sw_clks, | 
|  | hint_clks=hint_clks, | 
|  | rg_srcs=rg_srcs, | 
|  | parent_child_clks=parent_child_clks) | 
|  |  | 
|  | def make_clock_to_group(self) -> Dict[str, Group]: | 
|  | '''Return a map from clock name to the group containing the clock''' | 
|  | c2g = {} | 
|  | for grp in self.groups.values(): | 
|  | for clk_name in grp.clocks.keys(): | 
|  | c2g[clk_name] = grp | 
|  | return c2g | 
|  |  | 
|  | def all_derived_srcs(self) -> List[str]: | 
|  | '''Return a list of all the clocks used as the source for derived clocks''' | 
|  |  | 
|  | srcs = [] | 
|  |  | 
|  | for derived in self.derived_srcs.values(): | 
|  | if derived.src.name not in srcs: | 
|  | srcs.append(derived.src.name) | 
|  |  | 
|  | return srcs |