[clkmgr] Avoid generating unused root-gated clocks
In top_earlgrey, the "io" clock is only used for feeding clock
dividers and its gated version ("clk_io_root") is unused. Teach
topgen.py to be more careful when constructing rg_srcs, so that it
only adds a clock name if that clock is either exposed in clocks_o or
really is a source for some other clock.
While getting my head around the code, I also rejigged things a bit to
make them easier to understand (and easier to construct
"rg_srcs_set"), adding some comments about what the different
dictionaries mean.
Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/util/topgen.py b/util/topgen.py
index 1a7f022..c4a9a6b 100755
--- a/util/topgen.py
+++ b/util/topgen.py
@@ -409,58 +409,82 @@
outputs = [hjson_out, rtl_out, pkg_out]
names = ['clkmgr.hjson', 'clkmgr.sv', 'clkmgr_pkg.sv']
- # clock classification
- grps = top['clocks']['groups']
+ # A dictionary of the aon attribute for easier lookup. src_aon_attr[C] is
+ # True if clock C is always-on and False otherwise.
+ src_aon_attr = {src['name']: (src['aon'] == 'yes')
+ for src in (top['clocks']['srcs'] +
+ top['clocks']['derived_srcs'])}
- ft_clks = OrderedDict()
- rg_clks = OrderedDict()
- sw_clks = OrderedDict()
- src_aon_attr = OrderedDict()
- hint_clks = OrderedDict()
+ # Classify the various clock signals. Here, we build the following
+ # dictionaries, each mapping the derived clock name to its source.
+ #
+ # ft_clks: Clocks fed through clkmgr but are 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
+ #
+ # rg_clks: 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.
+ #
+ # sw_clks: 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.
+ #
+ # hints: Non-feedthrough clocks that have "hint" software control (with a
+ # feedback mechanism to allow blocks to avoid being suspended when
+ # they are not idle).
+ ft_clks = {}
+ rg_clks = {}
+ sw_clks = {}
+ hints = {}
- # construct a dictionary of the aon attribute for easier lookup
- # ie, src_name_A: True, src_name_B: False
- for src in top['clocks']['srcs'] + top['clocks']['derived_srcs']:
- if src['aon'] == 'yes':
- src_aon_attr[src['name']] = True
- else:
- src_aon_attr[src['name']] = False
+ # We also build rg_srcs_set, which is the set of non-always-on clock sources
+ # that are exposed without division. 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_set = set()
- rg_srcs = [src for (src, attr) in src_aon_attr.items() if not attr]
- rg_srcs.sort()
+ for grp in top['clocks']['groups']:
+ if grp['name'] == 'powerup':
+ # All clocks in the "powerup" group are considered feed-throughs.
+ ft_clks.update(grp['clocks'])
+ continue
- # clocks fed through clkmgr but are 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 = OrderedDict([(clk, src) for grp in grps
- for (clk, src) in grp['clocks'].items()
- if src_aon_attr[src] or grp['name'] == 'powerup'])
+ for clk, src in grp['clocks'].items():
+ if src_aon_attr[src]:
+ # Any always-on clock is a feedthrough
+ ft_clks[clk] = src
+ continue
- # root-gate clocks
- rg_clks = OrderedDict([(clk, src) for grp in grps
- for (clk, src) in grp['clocks'].items()
- if grp['name'] != 'powerup' and
- grp['sw_cg'] == 'no' and not src_aon_attr[src]])
+ rg_srcs_set.add(src)
- # direct sw control clocks
- sw_clks = OrderedDict([(clk, src) for grp in grps
- for (clk, src) in grp['clocks'].items()
- if grp['sw_cg'] == 'yes' and not src_aon_attr[src]])
+ if grp['sw_cg'] == 'no':
+ # A non-feedthrough clock with no software control
+ rg_clks[clk] = src
+ continue
- # sw hint clocks
- hints = OrderedDict([(clk, src) for grp in grps
- for (clk, src) in grp['clocks'].items()
- if grp['sw_cg'] == 'hint' and not src_aon_attr[src]])
+ if grp['sw_cg'] == 'yes':
+ # A non-feedthrough clock with direct software control
+ sw_clks[clk] = src
+ continue
- # hint clocks dict
- for clk, src in hints.items():
- # the clock is constructed as clk_{src_name}_{module_name}.
- # so to get the module name we split from the right and pick the last entry
- hint_clks[clk] = OrderedDict()
- hint_clks[clk]['name'] = (clk.rsplit('_', 1)[-1])
- hint_clks[clk]['src'] = src
+ # 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'
+ hints[clk] = src
+ continue
+
+ # hint clocks dict.
+ #
+ # The clock is constructed as clk_{src_name}_{module_name}. So to get the
+ # module name we split from the right and pick the last entry
+ hint_clks = {clk: {'name': clk.rsplit('_', 1)[-1], 'src': src}
+ for clk, src in hints.items()}
+
+ # Define a canonical ordering for rg_srcs
+ rg_srcs = sorted(rg_srcs_set)
for idx, tpl in enumerate(tpls):
out = ""