[reggen] Pair up clock and reset signals
This commit doesn't really change anything of note (indeed, there are
no changes to the auto-generated SystemVerilog code!), but it changes
the reggen hjson schema so that clocks and resets that belong together
get bundled together.
The idea is that a followup can define idle signals for "hint" clocks
by adding them to the ClockingItems. This is much nicer than depending
on order (it avoids counting, plus not every clock is a "hint" clock,
so not every clock needs an idle signal).
Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/util/reggen/clocking.py b/util/reggen/clocking.py
new file mode 100644
index 0000000..4c2d31e
--- /dev/null
+++ b/util/reggen/clocking.py
@@ -0,0 +1,94 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+'''Code representing clocking or resets for an IP block'''
+
+from typing import Dict, List, Optional
+
+from .lib import check_keys, check_list, check_bool, check_optional_name
+
+
+class ClockingItem:
+ def __init__(self, clock: Optional[str], reset: Optional[str], primary: bool):
+ if primary:
+ assert clock is not None
+ assert reset is not None
+
+ self.clock = clock
+ self.reset = reset
+ self.primary = primary
+
+ @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', 'primary'])
+
+ clock = check_optional_name(rd.get('clock'), 'clock field of ' + what)
+ reset = check_optional_name(rd.get('reset'), 'reset field of ' + what)
+ primary = check_bool(rd.get('primary', only_item),
+ 'primary field of ' + what)
+
+ if primary:
+ if clock is None:
+ raise ValueError('No clock signal for primary '
+ f'clocking item at {what}.')
+ if reset is None:
+ raise ValueError('No reset signal for primary '
+ f'clocking item at {what}.')
+
+ return ClockingItem(clock, reset, primary)
+
+ def _asdict(self) -> Dict[str, object]:
+ ret = {} # type: Dict[str, object]
+ if self.clock is not None:
+ ret['clock'] = self.clock,
+ if self.reset is not None:
+ ret['reset'] = self.reset
+
+ ret['primary'] = self.primary
+ return ret
+
+
+class Clocking:
+ def __init__(self, items: List[ClockingItem], primary: ClockingItem):
+ assert items
+ self.items = items
+ self.primary = primary
+
+ @staticmethod
+ def from_raw(raw: object, where: str) -> 'Clocking':
+ what = f'clocking items at {where}'
+ raw_items = check_list(raw, what)
+ if not raw_items:
+ raise ValueError(f'Empty list of clocking items at {where}.')
+
+ just_one_item = len(raw_items) == 1
+
+ items = []
+ primaries = []
+ for idx, raw_item in enumerate(raw_items):
+ item_where = f'entry {idx} of {what}'
+ item = ClockingItem.from_raw(raw_item, just_one_item, item_where)
+ if item.primary:
+ primaries.append(item)
+ items.append(item)
+
+ if len(primaries) != 1:
+ raise ValueError('There should be exactly one primary clocking '
+ f'item at {where}, but we saw {len(primaries)}.')
+
+ return Clocking(items, primaries[0])
+
+ def other_clocks(self) -> List[str]:
+ ret = []
+ for item in self.items:
+ if not item.primary and item.clock is not None:
+ 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 reset_signals(self) -> List[str]:
+ return [item.reset for item in self.items if item.reset is not None]