[util] clkmgr groups enhancements

- This commit is necessary to address #8405 and #8037

Modules are now allowed to delcare a different clock group per clock.
This allows for some flexibility in how things are hooked-up.

Further the idea of "internal" clocks are introduced for clocks that
are generated entirely inside a module but still used with the regfile.

This is especially useful for creating CDC structures for blocks like
clkmgr that has locally generated clocks.

Signed-off-by: Timothy Chen <timothytim@google.com>
diff --git a/hw/ip/clkmgr/data/clkmgr.hjson.tpl b/hw/ip/clkmgr/data/clkmgr.hjson.tpl
index 949ff1c..f75e6d0 100644
--- a/hw/ip/clkmgr/data/clkmgr.hjson.tpl
+++ b/hw/ip/clkmgr/data/clkmgr.hjson.tpl
@@ -9,8 +9,11 @@
   scan: "true",
   clocking: [
     {clock: "clk_i", reset: "rst_ni", primary: true},
-% for rst in clocks.reset_signals():
-    {reset: "${rst}"},
+% for src in clocks.srcs.values():
+    {clock: "clk_${src.name}_i", reset: "rst_${src.name}_ni"},
+% endfor
+% for src in clocks.derived_srcs.values():
+    {clock: "clk_${src.name}_i", reset: "rst_${src.name}_ni", internal: true},
 % endfor
   ]
   bus_interfaces: [
@@ -88,16 +91,6 @@
       package: ""
     },
 
-  // All clock inputs
-% for src in clocks.srcs.values():
-    { struct:  "logic",
-      type:    "uni",
-      name:    "clk_${src.name}",
-      act:     "rcv",
-      package: "",
-    },
-% endfor
-
   // Exported clocks
 % for intf in cfg['exported_clks']:
     { struct:  "clkmgr_${intf}_out",
diff --git a/hw/top_earlgrey/data/top_earlgrey.hjson b/hw/top_earlgrey/data/top_earlgrey.hjson
index cbfe9bb..a481df1 100644
--- a/hw/top_earlgrey/data/top_earlgrey.hjson
+++ b/hw/top_earlgrey/data/top_earlgrey.hjson
@@ -87,6 +87,7 @@
 
     groups: [
       // the powerup group is used exclusively by clk/pwr/rstmgr/pinmux
+      { name: "ast",     src:"ext", sw_cg: "no"                   }
       { name: "powerup", src:"top", sw_cg: "no"                   }
       { name: "trans",   src:"top", sw_cg: "hint", unique: "yes", }
       { name: "infra",   src:"top", sw_cg: "no",                  }
@@ -361,7 +362,25 @@
     },
     { name: "clkmgr_aon",
       type: "clkmgr",
-      clock_srcs: {clk_i: "io_div4"},
+      clock_srcs: {
+        clk_i: "io_div4",
+        clk_main_i: {
+          group: "ast",
+          clock: "main"
+        },
+        clk_io_i: {
+          group: "ast",
+          clock: "io"
+        },
+        clk_usb_i: {
+          group: "ast",
+          clock: "usb"
+        },
+        clk_aon_i: {
+          group: "ast",
+          clock: "aon"
+        }
+      },
       clock_group: "powerup",
       reset_connections: {rst_ni: "por_io_div4",
                           rst_main_ni: "por",
@@ -897,10 +916,6 @@
         'ast.ram_1p_cfg'                  : 'ram_1p_cfg',
         'ast.ram_2p_cfg'                  : 'ram_2p_cfg',
         'ast.rom_cfg'                     : 'rom_cfg',
-        'clkmgr_aon.clk_main'             : 'clk_main',  // clock inputs
-        'clkmgr_aon.clk_io'               : 'clk_io',    // clock inputs
-        'clkmgr_aon.clk_usb'              : 'clk_usb',   // clock inputs
-        'clkmgr_aon.clk_aon'              : 'clk_aon',   // clock inputs
         'clkmgr_aon.jitter_en'            : 'clk_main_jitter_en',
         'clkmgr_aon.ast_clk_byp_req'      : 'ast_clk_byp_req',
         'clkmgr_aon.ast_clk_byp_ack'      : 'ast_clk_byp_ack',
diff --git a/hw/top_earlgrey/dv/tb/tb.sv b/hw/top_earlgrey/dv/tb/tb.sv
index 1f3b210..2275662 100644
--- a/hw/top_earlgrey/dv/tb/tb.sv
+++ b/hw/top_earlgrey/dv/tb/tb.sv
@@ -47,7 +47,7 @@
 
   // internal clocks and resets
   // cpu clock cannot reference cpu_hier since cpu clocks are forced off in stub_cpu mode
-  wire cpu_clk = `CLKMGR_HIER.clocks_o.clk_proc_main;
+  wire cpu_clk = `CPU_HIER.clk_i;
   wire cpu_rst_n = `CPU_HIER.rst_ni;
   wire alert_handler_clk = `ALERT_HANDLER_HIER.clk_i;
 
diff --git a/hw/top_englishbreakfast/data/top_englishbreakfast.hjson b/hw/top_englishbreakfast/data/top_englishbreakfast.hjson
index 4720f0d..bd1f1be 100644
--- a/hw/top_englishbreakfast/data/top_englishbreakfast.hjson
+++ b/hw/top_englishbreakfast/data/top_englishbreakfast.hjson
@@ -85,6 +85,8 @@
     // The proc group is not peripheral, and directly hardwired
 
     groups: [
+      // the ast group is used to represent input clocks to the top
+      { name: "ast",     src:"ext", sw_cg: "no"                   }
       // the powerup group is used exclusively by clk/pwr/rstmgr/pinmux
       { name: "powerup", src:"top", sw_cg: "no"                   }
       { name: "trans",   src:"top", sw_cg: "hint", unique: "yes", }
@@ -245,7 +247,25 @@
     },
     { name: "clkmgr_aon",
       type: "clkmgr",
-      clock_srcs: {clk_i: "io_div4"},
+      clock_srcs: {
+        clk_i: "io_div4",
+        clk_main_i: {
+          group: "ast",
+          clock: "main"
+        },
+        clk_io_i: {
+          group: "ast",
+          clock: "io"
+        },
+        clk_usb_i: {
+          group: "ast",
+          clock: "usb"
+        },
+        clk_aon_i: {
+          group: "ast",
+          clock: "aon"
+        }
+      },
       clock_group: "powerup",
       reset_connections: {rst_ni: "por_io_div4",
                           rst_main_ni: "por",
@@ -592,10 +612,6 @@
         'ast.ram_1p_cfg'               : 'ram_1p_cfg',
         'ast.ram_2p_cfg'               : 'ram_2p_cfg',
         'ast.rom_cfg'                  : 'rom_cfg',
-        'clkmgr_aon.clk_main'          : 'clk_main',  // clock inputs
-        'clkmgr_aon.clk_io'            : 'clk_io',    // clock inputs
-        'clkmgr_aon.clk_usb'           : 'clk_usb',   // clock inputs
-        'clkmgr_aon.clk_aon'           : 'clk_aon',   // clock inputs
         'clkmgr_aon.jitter_en'         : 'clk_main_jitter_en',
         'clkmgr_aon.ast_clk_byp_req'   : 'ast_clk_byp_req',
         'clkmgr_aon.ast_clk_byp_ack'   : 'ast_clk_byp_ack',
diff --git a/util/reggen/clocking.py b/util/reggen/clocking.py
index b2330c4..3bec741 100644
--- a/util/reggen/clocking.py
+++ b/util/reggen/clocking.py
@@ -16,6 +16,7 @@
                  reset: Optional[str],
                  idle: Optional[str],
                  primary: bool,
+                 internal: bool,
                  clock_base_name: Optional[str]):
         if primary:
             assert clock is not None
@@ -26,17 +27,23 @@
         self.reset = reset
         self.primary = primary
         self.idle = idle
+        # Internal means this clock is generated completely internal to the module
+        # and not supplied by the top level.
+        # However, the IpBlock may need to be aware of this clock for CDC purposes
+        self.internal = internal
 
     @staticmethod
     def from_raw(raw: object, only_item: bool, where: str) -> 'ClockingItem':
         what = f'clocking item at {where}'
-        rd = check_keys(raw, what, [], ['clock', 'reset', 'idle', 'primary'])
+        rd = check_keys(raw, what, [], ['clock', 'reset', 'idle', 'primary', 'internal'])
 
         clock = check_optional_name(rd.get('clock'), 'clock field of ' + what)
         reset = check_optional_name(rd.get('reset'), 'reset field of ' + what)
         idle = check_optional_name(rd.get('idle'), 'idle field of ' + what)
         primary = check_bool(rd.get('primary', only_item),
                              'primary field of ' + what)
+        internal = check_bool(rd.get('internal', False),
+                              'internal field of ' + what)
 
         match = re.match(r'^clk_([A-Za-z0-9_]+)_i', str(clock))
         if not clock or clock in ['clk_i', 'scan_clk_i']:
@@ -55,7 +62,7 @@
                 raise ValueError('No reset signal for primary '
                                  f'clocking item at {what}.')
 
-        return ClockingItem(clock, reset, idle, primary, clock_base_name)
+        return ClockingItem(clock, reset, idle, primary, internal, clock_base_name)
 
     def _asdict(self) -> Dict[str, object]:
         ret = {}  # type: Dict[str, object]
@@ -107,8 +114,12 @@
                 ret.append(item.clock)
         return ret
 
-    def clock_signals(self) -> List[str]:
-        return [item.clock for item in self.items if item.clock is not None]
+    def clock_signals(self, ret_internal: bool = True) -> List[str]:
+        # By default clock_signals returns all clocks, including internal clocks.
+        # If the ret_internal input is set to false, then only externally supplied
+        # clocks are returned.
+        return [item.clock for item in self.items if item.clock is not None and
+                (ret_internal or not item.internal)]
 
     def reset_signals(self) -> List[str]:
         return [item.reset for item in self.items if item.reset is not None]
@@ -117,8 +128,8 @@
         ret = None
         for item in self.items:
             if name == item.clock:
-                 ret = item
-                 break
+                ret = item
+                break
 
         if ret is None:
             raise ValueError(f'The requested clock {name} does not exist.')
diff --git a/util/topgen/clocks.py b/util/topgen/clocks.py
index ea03010..02406ec 100644
--- a/util/topgen/clocks.py
+++ b/util/topgen/clocks.py
@@ -125,6 +125,10 @@
 
 
 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:
     #
@@ -260,6 +264,7 @@
 
     def typed_clocks(self) -> TypedClocks:
         '''Split the clocks by type'''
+        ast_clks = {}
         ft_clks = {}
         rg_clks = {}
         sw_clks = {}
@@ -273,6 +278,10 @@
                 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
@@ -299,7 +308,8 @@
         # Define a canonical ordering for rg_srcs
         rg_srcs = list(sorted(rg_srcs_set))
 
-        return TypedClocks(ft_clks=ft_clks,
+        return TypedClocks(ast_clks=ast_clks,
+                           ft_clks=ft_clks,
                            rg_clks=rg_clks,
                            sw_clks=sw_clks,
                            hint_clks=hint_clks,
diff --git a/util/topgen/merge.py b/util/topgen/merge.py
index 2f4fe7c..0ecb3be 100644
--- a/util/topgen/merge.py
+++ b/util/topgen/merge.py
@@ -528,6 +528,9 @@
     generate the (templated) clkmgr code. This runs before we load up IP blocks
     with reggen, so can only see top-level configuration.
 
+    By default each end point (peripheral, memory etc) is in the same clock group.
+    However, it is possible to define the group attribute per clock if required.
+
     '''
     clocks = top['clocks']
     assert isinstance(clocks, Clocks)
@@ -540,42 +543,52 @@
         # Ensure each module has a default case
         export_if = ep.get('clock_reset_export', [])
 
-        # if no clock group assigned, default is unique
+        # The clock group attribute in an end point sets the defaut
+        # group for every clock in that end point.
+        #
+        # However, the end point can also override specific clocks to
+        # different groups inside clock_srcs.  This is generally not
+        # recommended as it is better to stay consistent.  However
+        # if needed, the method is available.
         ep['clock_group'] = 'secure' if 'clock_group' not in ep else ep[
             'clock_group']
         ep_grp = ep['clock_group']
 
         # end point names and clocks
         ep_name = ep['name']
-        ep_clks = []
-
-        group = clocks.groups[ep_grp]
 
         for port, clk in ep['clock_srcs'].items():
-            ep_clks.append(clk)
+
+            # If the value of a particular connection is a dict,
+            # there are additional attributes to explore
+            if isinstance(clk, str):
+                group_name = ep_grp
+                src_name = clk
+            else:
+                assert isinstance(clk, Dict)
+                group_name = clk.get('group', ep_grp)
+                src_name = clk['clock']
+
+            group = clocks.groups[group_name]
 
             name = ''
             hier_name = clocks.hier_paths[group.src]
 
             if group.src == 'ext':
-                # clock comes from top ports
-                if clk == 'main':
-                    name = "i"
-                else:
-                    name = "{}_i".format(clk)
+                name = "{}_i".format(src_name)
 
             elif group.unique:
                 # new unqiue clock name
-                name = "{}_{}".format(clk, ep_name)
+                name = "{}_{}".format(src_name, ep_name)
 
             else:
                 # new group clock name
-                name = "{}_{}".format(clk, ep_grp)
+                name = "{}_{}".format(src_name, group_name)
 
             clk_name = "clk_" + name
 
             # add clock to a particular group
-            clk_sig = clocks.add_clock_to_group(group, clk_name, clk)
+            clk_sig = clocks.add_clock_to_group(group, clk_name, src_name)
             clk_sig.add_endpoint(ep_name, port)
 
             # add clock connections
diff --git a/util/topgen/templates/toplevel.sv.tpl b/util/topgen/templates/toplevel.sv.tpl
index 820ae5d..b974d25 100644
--- a/util/topgen/templates/toplevel.sv.tpl
+++ b/util/topgen/templates/toplevel.sv.tpl
@@ -93,6 +93,11 @@
 
 % endif
 
+  // All externally supplied clocks
+  % for clk in top['clocks'].typed_clocks().ast_clks:
+  input ${clk},
+  % endfor
+
   // All clocks forwarded to ast
   output clkmgr_pkg::clkmgr_out_t clks_ast_o,
   output rstmgr_pkg::rstmgr_out_t rsts_ast_o,
diff --git a/util/topgen/validate.py b/util/topgen/validate.py
index cce14a3..68409ec 100644
--- a/util/topgen/validate.py
+++ b/util/topgen/validate.py
@@ -735,7 +735,7 @@
     # (generated by topgen for a crossbar)
     if isinstance(inst, IpBlock):
         name = inst.name
-        clock_signals = inst.clocking.clock_signals()
+        clock_signals = inst.clocking.clock_signals(False)
     else:
         name = inst['name']
         clock_signals = ([inst.get('clock_primary', 'rst_ni')] +
@@ -757,9 +757,12 @@
                   (prefix, name))
         [log.error("%s" % port) for port in missing_port]
 
-    missing_net = [
-        net for port, net in top['clock_srcs'].items() if net not in clock_srcs
-    ]
+    missing_net = []
+    for port, net in top['clock_srcs'].items():
+        net_name = net['clock'] if isinstance(net, Dict) else net
+
+        if net_name not in clock_srcs:
+            missing_net.append(net)
 
     if missing_net:
         error += 1