# 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, Set, Tuple import hjson # type: ignore from .alert import Alert from .bus_interfaces import BusInterfaces from .clocking import Clocking, ClockingItem from .inter_signal import InterSignal from .lib import (check_keys, check_name, check_int, check_bool, check_list, check_optional_str) from .params import ReggenParams, LocalParam from .reg_block import RegBlock from .signal import Signal REQUIRED_FIELDS = { 'name': ['s', "name of the component"], 'clocking': ['l', "clocking for the device"], 'bus_interfaces': ['l', "bus interfaces 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"], 'expose_reg_if': ['pb', 'if set, expose reg interface in reg2hw signal'], '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. " ], 'param_list': ['lp', "list of parameters of the IP"], 'regwidth': ['d', "width of registers in bits (default 32)"], 'reset_request_list': ['l', 'list of signals requesting reset'], 'scan': ['pb', 'Indicates the module have `scanmode_i`'], 'scan_reset': ['pb', 'Indicates the module have `scan_rst_ni`'], 'scan_en': ['pb', 'Indicates the module has `scan_en_i`'], '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: def __init__(self, name: str, regwidth: int, params: ReggenParams, reg_blocks: Dict[Optional[str], RegBlock], interrupts: Sequence[Signal], no_auto_intr: bool, alerts: List[Alert], no_auto_alert: bool, scan: bool, inter_signals: List[InterSignal], bus_interfaces: BusInterfaces, hier_path: Optional[str], clocking: Clocking, xputs: Tuple[Sequence[Signal], Sequence[Signal], Sequence[Signal]], wakeups: Sequence[Signal], reset_requests: Sequence[Signal], expose_reg_if: bool, scan_reset: bool, scan_en: bool): assert reg_blocks # Check that register blocks are in bijection with device interfaces reg_block_names = reg_blocks.keys() dev_if_names = [] # type: List[Optional[str]] dev_if_names += bus_interfaces.named_devices if bus_interfaces.has_unnamed_device: dev_if_names.append(None) assert set(reg_block_names) == set(dev_if_names) self.name = name self.regwidth = regwidth self.reg_blocks = reg_blocks 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_interfaces = bus_interfaces self.hier_path = hier_path self.clocking = clocking self.xputs = xputs self.wakeups = wakeups self.reset_requests = reset_requests self.expose_reg_if = expose_reg_if self.scan_reset = scan_reset self.scan_en = scan_en @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 = ReggenParams.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 init_block = 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)) init_block.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)) init_block.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) reg_blocks = RegBlock.build_blocks(init_block, rd['registers']) 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_interfaces = (BusInterfaces. from_raw(rd['bus_interfaces'], 'bus_interfaces field of ' + where)) inter_signals += bus_interfaces.inter_signals() hier_path = check_optional_str(rd.get('hier_path', None), 'hier_path field of ' + what) clocking = Clocking.from_raw(rd['clocking'], 'clocking field of ' + what) 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', [])) expose_reg_if = check_bool(rd.get('expose_reg_if', False), 'expose_reg_if field of ' + what) scan_reset = check_bool(rd.get('scan_reset', False), 'scan_reset field of ' + what) scan_en = check_bool(rd.get('scan_en', False), 'scan_en field of ' + what) # Check that register blocks are in bijection with device interfaces reg_block_names = reg_blocks.keys() dev_if_names = [] # type: List[Optional[str]] dev_if_names += bus_interfaces.named_devices if bus_interfaces.has_unnamed_device: dev_if_names.append(None) if set(reg_block_names) != set(dev_if_names): raise ValueError("IP block {} defines device interfaces, named {} " "but its registers don't match (they are keyed " "by {})." .format(name, dev_if_names, list(reg_block_names))) return IpBlock(name, regwidth, params, reg_blocks, interrupts, no_auto_intr, alerts, no_auto_alert, scan, inter_signals, bus_interfaces, hier_path, clocking, xputs, wakeups, rst_reqs, expose_reg_if, scan_reset, scan_en) @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', encoding='utf-8') as handle: return IpBlock.from_text(handle.read(), param_defaults, 'file at {!r}'.format(path)) def _asdict(self) -> Dict[str, object]: ret = { 'name': self.name, 'regwidth': self.regwidth } if len(self.reg_blocks) == 1 and None in self.reg_blocks: ret['registers'] = self.reg_blocks[None].as_dicts() else: ret['registers'] = {k: v.as_dicts() for k, v in self.reg_blocks.items()} 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 ret['bus_interfaces'] = self.bus_interfaces.as_dicts() if self.hier_path is not None: ret['hier_path'] = self.hier_path ret['clocking'] = self.clocking.items 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 ret['scan_en'] = self.scan_en return ret def get_rnames(self) -> Set[str]: ret = set() # type: Set[str] for rb in self.reg_blocks.values(): ret = ret.union(set(rb.name_to_offset.keys())) return ret def get_signals_as_list_of_dicts(self) -> List[Dict[str, object]]: '''Look up and return signal by name''' result = [] for iodir, xput in zip(('inout', 'input', 'output'), self.xputs): for sig in xput: result.append(sig.as_nwt_dict(iodir)) return result def get_signal_by_name_as_dict(self, name: str) -> Dict[str, object]: '''Look up and return signal by name''' sig_list = self.get_signals_as_list_of_dicts() for sig in sig_list: if sig['name'] == name: return sig else: raise ValueError("Signal {} does not exist in IP block {}" .format(name, self.name)) def has_shadowed_reg(self) -> bool: '''Return boolean indication whether reg block contains shadowed registers''' for rb in self.reg_blocks.values(): if rb.has_shadowed_reg(): return True # if we are here, then no one has has a shadowed register return False def get_primary_clock(self) -> ClockingItem: '''Return primary clock of an block''' return self.clocking.primary