blob: c02252aeb375d3f6152e11ba4c91adbbdffb68c1 [file] [log] [blame]
# 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