[reggen] Define a class wrapping the top-level IP block
The bulk of this patch is in ip_block.py, which defines the IpBlock class.
This object replaces the top-level dictionary that we were parsing.
Client code then replaces something like this:
obj = hjson.load(hjson_file.open('r'),
use_decimal=True,
object_pairs_hook=OrderedDict)
if validate.validate(obj, params=[]) != 0:
log.info("Parsing %s configuration failed." % hjson_file)
sys.exit(1)
with
obj = IpBlock.from_path(str(hjson_file), [])
where obj is now an IpBlock object instead of a dict.
Other than some pesky rewrites in the various gen_FOO scripts and
template files, the other big change on the reggen side was to replace
the hierarchical "Block" class that was defined in data.py. Now, we
have a Top class (created by topgen code) and a Top can contain
multiple blocks. We've also now got some validation logic to make sure
that the sub-blocks and memories don't overlap: I'm not sure that was
there before.
As well as changing how we load files (as described above), topgen
also needed a bit of work. We now have to convert various objects to
dicts in the merge stage. (Before, we cloned the dictionaries and
added some keys; now we construct the new dictionary explicitly).
The idea is that in time we'll start to generate objects instead of
dicts in topgen as well. As a bonus, we should be able to get rid of
some of the spurious "dump & load" logic found there.
Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/util/reggen/ip_block.py b/util/reggen/ip_block.py
new file mode 100644
index 0000000..5d7fe3d
--- /dev/null
+++ b/util/reggen/ip_block.py
@@ -0,0 +1,319 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+'''Code representing an IP block for reggen'''
+
+from typing import Dict, List, Optional, Sequence, Tuple
+
+import hjson # type: ignore
+
+from .alert import Alert
+from .block import Block
+from .inter_signal import InterSignal
+from .lib import (check_keys, check_name, check_int, check_bool,
+ check_list, check_optional_str, check_name_list)
+from .params import Params, LocalParam
+from .reg_block import RegBlock
+from .signal import Signal
+
+
+REQUIRED_FIELDS = {
+ 'name': ['s', "name of the component"],
+ 'clock_primary': ['s', "name of the primary clock"],
+ 'bus_device': ['s', "name of the bus interface for the device"],
+ 'registers': [
+ 'l',
+ "list of register definition groups and "
+ "offset control groups"
+ ]
+}
+
+OPTIONAL_FIELDS = {
+ 'alert_list': ['lnw', "list of peripheral alerts"],
+ 'available_inout_list': ['lnw', "list of available peripheral inouts"],
+ 'available_input_list': ['lnw', "list of available peripheral inputs"],
+ 'available_output_list': ['lnw', "list of available peripheral outputs"],
+ 'bus_host': ['s', "name of the bus interface as host"],
+ 'hier_path': [
+ None,
+ 'additional hierarchy path before the reg block instance'
+ ],
+ 'interrupt_list': ['lnw', "list of peripheral interrupts"],
+ 'inter_signal_list': ['l', "list of inter-module signals"],
+ 'no_auto_alert_regs': [
+ 's', "Set to true to suppress automatic "
+ "generation of alert test registers. "
+ "Defaults to true if no alert_list is present. "
+ "Otherwise this defaults to false. "
+ ],
+ 'no_auto_intr_regs': [
+ 's', "Set to true to suppress automatic "
+ "generation of interrupt registers. "
+ "Defaults to true if no interrupt_list is present. "
+ "Otherwise this defaults to false. "
+ ],
+ 'other_clock_list': ['l', "list of other chip clocks needed"],
+ 'other_reset_list': ['l', "list of other resets"],
+ 'param_list': ['lp', "list of parameters of the IP"],
+ 'regwidth': ['d', "width of registers in bits (default 32)"],
+ 'reset_primary': ['s', "primary reset used by the module"],
+ 'reset_request_list': ['l', 'list of signals requesting reset'],
+ 'scan': ['pb', 'Indicates the module have `scanmode_i`'],
+ 'scan_reset': ['pb', 'Indicates the module have `test_rst_ni`'],
+ 'SPDX-License-Identifier': [
+ 's', "License ientifier (if using pure json) "
+ "Only use this if unable to put this "
+ "information in a comment at the top of the "
+ "file."
+ ],
+ 'wakeup_list': ['lnw', "list of peripheral wakeups"]
+}
+
+
+class IpBlock(Block):
+ def __init__(self,
+ name: str,
+ regwidth: int,
+ params: Params,
+ regs: RegBlock,
+ interrupts: Sequence[Signal],
+ no_auto_intr: bool,
+ alerts: List[Alert],
+ no_auto_alert: bool,
+ scan: bool,
+ inter_signals: List[InterSignal],
+ bus_device: Optional[str],
+ bus_host: Optional[str],
+ hier_path: Optional[str],
+ clock_signals: List[str],
+ reset_signals: List[str],
+ xputs: Tuple[Sequence[Signal],
+ Sequence[Signal],
+ Sequence[Signal]],
+ wakeups: Sequence[Signal],
+ reset_requests: Sequence[Signal],
+ scan_reset: bool):
+ assert clock_signals
+ assert reset_signals
+
+ super().__init__(name, regwidth, regs)
+
+ self.params = params
+ self.interrupts = interrupts
+ self.no_auto_intr = no_auto_intr
+ self.alerts = alerts
+ self.no_auto_alert = no_auto_alert
+ self.scan = scan
+ self.inter_signals = inter_signals
+ self.bus_device = bus_device
+ self.bus_host = bus_host
+ self.hier_path = hier_path
+ self.clock_signals = clock_signals
+ self.reset_signals = reset_signals
+ self.xputs = xputs
+ self.wakeups = wakeups
+ self.reset_requests = reset_requests
+ self.scan_reset = scan_reset
+
+ @staticmethod
+ def from_raw(param_defaults: List[Tuple[str, str]],
+ raw: object,
+ where: str) -> 'IpBlock':
+
+ rd = check_keys(raw, 'block at ' + where,
+ list(REQUIRED_FIELDS.keys()),
+ list(OPTIONAL_FIELDS.keys()))
+
+ name = check_name(rd['name'], 'name of block at ' + where)
+
+ what = '{} block at {}'.format(name, where)
+
+ r_regwidth = rd.get('regwidth')
+ if r_regwidth is None:
+ regwidth = 32
+ else:
+ regwidth = check_int(r_regwidth, 'regwidth field of ' + what)
+ if regwidth <= 0:
+ raise ValueError('Invalid regwidth field for {}: '
+ '{} is not positive.'
+ .format(what, regwidth))
+
+ params = Params.from_raw('parameter list for ' + what,
+ rd.get('param_list', []))
+ try:
+ params.apply_defaults(param_defaults)
+ except (ValueError, KeyError) as err:
+ raise ValueError('Failed to apply defaults to params: {}'
+ .format(err)) from None
+
+ regs = RegBlock(regwidth, params)
+
+ interrupts = Signal.from_raw_list('interrupt_list for block {}'
+ .format(name),
+ rd.get('interrupt_list', []))
+ alerts = Alert.from_raw_list('alert_list for block {}'
+ .format(name),
+ rd.get('alert_list', []))
+
+ no_auto_intr = check_bool(rd.get('no_auto_intr_regs', not interrupts),
+ 'no_auto_intr_regs field of ' + what)
+
+ no_auto_alert = check_bool(rd.get('no_auto_alert_regs', not alerts),
+ 'no_auto_alert_regs field of ' + what)
+
+ if interrupts and not no_auto_intr:
+ if interrupts[-1].bits.msb >= regwidth:
+ raise ValueError("Interrupt list for {} is too wide: "
+ "msb is {}, which doesn't fit with a "
+ "regwidth of {}."
+ .format(what,
+ interrupts[-1].bits.msb, regwidth))
+ regs.make_intr_regs(interrupts)
+
+ if alerts:
+ if not no_auto_alert:
+ if len(alerts) > regwidth:
+ raise ValueError("Interrupt list for {} is too wide: "
+ "{} alerts don't fit with a regwidth of {}."
+ .format(what, len(alerts), regwidth))
+ regs.make_alert_regs(alerts)
+
+ # Generate a NumAlerts parameter
+ existing_param = params.get('NumAlerts')
+ if existing_param is not None:
+ if ((not isinstance(existing_param, LocalParam) or
+ existing_param.param_type != 'int' or
+ existing_param.value != str(len(alerts)))):
+ raise ValueError('Conflicting definition of NumAlerts '
+ 'parameter.')
+ else:
+ params.add(LocalParam(name='NumAlerts',
+ desc='Number of alerts',
+ param_type='int',
+ value=str(len(alerts))))
+
+ scan = check_bool(rd.get('scan', False), 'scan field of ' + what)
+
+ regs.add_raw_registers(rd['registers'])
+ regs.validate()
+
+ r_inter_signals = check_list(rd.get('inter_signal_list', []),
+ 'inter_signal_list field')
+ inter_signals = [
+ InterSignal.from_raw('entry {} of the inter_signal_list field'
+ .format(idx + 1),
+ entry)
+ for idx, entry in enumerate(r_inter_signals)
+ ]
+
+ bus_device = check_optional_str(rd.get('bus_device', None),
+ 'bus_device field of ' + what)
+ bus_host = check_optional_str(rd.get('bus_host', None),
+ 'bus_host field of ' + what)
+
+ if bus_device == "tlul":
+ # Add to inter_module_signal
+ port_name = "tl" if bus_host in ["none", "", None] else "tl_d"
+ inter_signals.append(InterSignal(port_name, None, 'tl', 'tlul_pkg',
+ 'req_rsp', 'rsp', 1, None))
+
+ if bus_host == "tlul":
+ inter_signals.append(InterSignal('tl_h', None, 'tl', 'tlul_pkg',
+ 'req_rsp', 'rsp', 1, None))
+
+ hier_path = check_optional_str(rd.get('hier_path', None),
+ 'hier_path field of ' + what)
+
+ clock_primary = check_name(rd['clock_primary'],
+ 'clock_primary field of ' + what)
+ other_clock_list = check_name_list(rd.get('other_clock_list', []),
+ 'other_clock_list field of ' + what)
+ clock_signals = [clock_primary] + other_clock_list
+
+ reset_primary = check_name(rd.get('reset_primary', 'rst_ni'),
+ 'reset_primary field of ' + what)
+ other_reset_list = check_name_list(rd.get('other_reset_list', []),
+ 'other_reset_list field of ' + what)
+ reset_signals = [reset_primary] + other_reset_list
+
+ xputs = (
+ Signal.from_raw_list('available_inout_list for block ' + name,
+ rd.get('available_inout_list', [])),
+ Signal.from_raw_list('available_input_list for block ' + name,
+ rd.get('available_input_list', [])),
+ Signal.from_raw_list('available_output_list for block ' + name,
+ rd.get('available_output_list', []))
+ )
+ wakeups = Signal.from_raw_list('wakeup_list for block ' + name,
+ rd.get('wakeup_list', []))
+ rst_reqs = Signal.from_raw_list('reset_request_list for block ' + name,
+ rd.get('reset_request_list', []))
+
+ scan_reset = check_bool(rd.get('scan_reset', False),
+ 'scan_reset field of ' + what)
+
+ return IpBlock(name, regwidth, params, regs,
+ interrupts, no_auto_intr, alerts, no_auto_alert,
+ scan, inter_signals, bus_device, bus_host,
+ hier_path, clock_signals, reset_signals,
+ xputs, wakeups, rst_reqs, scan_reset)
+
+ @staticmethod
+ def from_text(txt: str,
+ param_defaults: List[Tuple[str, str]],
+ where: str) -> 'IpBlock':
+ '''Load an IpBlock from an hjson description in txt'''
+ return IpBlock.from_raw(param_defaults,
+ hjson.loads(txt, use_decimal=True),
+ where)
+
+ @staticmethod
+ def from_path(path: str,
+ param_defaults: List[Tuple[str, str]]) -> 'IpBlock':
+ '''Load an IpBlock from an hjson description in a file at path'''
+ with open(path, 'r') as handle:
+ return IpBlock.from_text(handle.read(), param_defaults,
+ 'file at {!r}'.format(path))
+
+ def _asdict(self) -> Dict[str, object]:
+ ret = super()._asdict()
+ ret['param_list'] = self.params.as_dicts()
+ ret['interrupt_list'] = self.interrupts
+ ret['no_auto_intr_regs'] = self.no_auto_intr
+ ret['alert_list'] = self.alerts
+ ret['no_auto_alert_regs'] = self.no_auto_alert
+ ret['scan'] = self.scan
+ ret['inter_signal_list'] = self.inter_signals
+
+ if self.bus_device is not None:
+ ret['bus_device'] = self.bus_device
+ if self.bus_host is not None:
+ ret['bus_host'] = self.bus_host
+ if self.hier_path is not None:
+ ret['hier_path'] = self.hier_path
+
+ ret['clock_primary'] = self.clock_signals[0]
+ if len(self.clock_signals) > 1:
+ ret['other_clock_list'] = self.clock_signals[1:]
+
+ ret['reset_primary'] = self.reset_signals[0]
+ if len(self.reset_signals) > 1:
+ ret['other_reset_list'] = self.reset_signals[1:]
+
+ inouts, inputs, outputs = self.xputs
+ if inouts:
+ ret['available_inout_list'] = inouts
+ if inputs:
+ ret['available_input_list'] = inputs
+ if outputs:
+ ret['available_output_list'] = outputs
+
+ if self.wakeups:
+ ret['wakeup_list'] = self.wakeups
+ if self.reset_requests:
+ ret['reset_request_list'] = self.reset_requests
+
+ ret['scan_reset'] = self.scan_reset
+
+ return ret