# Copyright lowRISC contributors. # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 """This contains a class which is used to help generate `top_{name}.h` and `top_{name}.h`. """ from collections import OrderedDict from typing import Dict, List, Optional, Tuple from mako.template import Template from .lib import get_base_and_size, Name from reggen.ip_block import IpBlock class MemoryRegion(object): def __init__(self, name: Name, base_addr: int, size_bytes: int): assert isinstance(base_addr, int) self.name = name self.base_addr = base_addr self.size_bytes = size_bytes self.size_words = (size_bytes + 3) // 4 def base_addr_name(self): return self.name + Name(["base", "addr"]) def offset_name(self): return self.name + Name(["offset"]) def size_bytes_name(self): return self.name + Name(["size", "bytes"]) def size_words_name(self): return self.name + Name(["size", "words"]) class CEnum(object): def __init__(self, name): self.name = name self.enum_counter = 0 self.finalized = False self.constants = [] def add_constant(self, constant_name, docstring=""): assert not self.finalized full_name = self.name + constant_name value = self.enum_counter self.enum_counter += 1 self.constants.append((full_name, value, docstring)) return full_name def add_last_constant(self, docstring=""): assert not self.finalized full_name = self.name + Name(["last"]) _, last_val, _ = self.constants[-1] self.constants.append((full_name, last_val, r"\internal " + docstring)) self.finalized = True def render(self): template = ("typedef enum ${enum.name.as_snake_case()} {\n" "% for name, value, docstring in enum.constants:\n" " ${name.as_c_enum()} = ${value}, /**< ${docstring} */\n" "% endfor\n" "} ${enum.name.as_c_type()};") return Template(template).render(enum=self) class CArrayMapping(object): def __init__(self, name, output_type_name): self.name = name self.output_type_name = output_type_name self.mapping = OrderedDict() def add_entry(self, in_name, out_name): self.mapping[in_name] = out_name def render_declaration(self): template = ( "extern const ${mapping.output_type_name.as_c_type()}\n" " ${mapping.name.as_snake_case()}[${len(mapping.mapping)}];") return Template(template).render(mapping=self) def render_definition(self): template = ( "const ${mapping.output_type_name.as_c_type()}\n" " ${mapping.name.as_snake_case()}[${len(mapping.mapping)}] = {\n" "% for in_name, out_name in mapping.mapping.items():\n" " [${in_name.as_c_enum()}] = ${out_name.as_c_enum()},\n" "% endfor\n" "};\n") return Template(template).render(mapping=self) class TopGenC: def __init__(self, top_info, name_to_block: Dict[str, IpBlock]): self.top = top_info self._top_name = Name(["top"]) + Name.from_snake_case(top_info["name"]) self._name_to_block = name_to_block # The .c file needs the .h file's relative path, store it here self.header_path = None self._init_plic_targets() self._init_plic_mapping() self._init_alert_mapping() self._init_pinmux_mapping() self._init_pwrmgr_wakeups() self._init_rstmgr_sw_rsts() self._init_pwrmgr_reset_requests() self._init_clkmgr_clocks() def devices(self) -> List[Tuple[Tuple[str, Optional[str]], MemoryRegion]]: '''Return a list of MemoryRegion objects for devices on the bus The list returned is pairs (full_if, region) where full_if is itself a pair (inst_name, if_name). inst_name is the name of some IP block instantiation. if_name is the name of the interface (may be None). region is a MemoryRegion object representing the device. ''' ret = [] # type: List[Tuple[Tuple[str, Optional[str]], MemoryRegion]] for inst in self.top['module']: block = self._name_to_block[inst['type']] for if_name, rb in block.reg_blocks.items(): full_if = (inst['name'], if_name) full_if_name = Name.from_snake_case(full_if[0]) if if_name is not None: full_if_name += Name.from_snake_case(if_name) name = self._top_name + full_if_name base, size = get_base_and_size(self._name_to_block, inst, if_name) region = MemoryRegion(name, base, size) ret.append((full_if, region)) return ret def memories(self): ret = [] for m in self.top["memory"]: ret.append((m["name"], MemoryRegion(self._top_name + Name.from_snake_case(m["name"]), int(m["base_addr"], 0), int(m["size"], 0)))) for inst in self.top['module']: if "memory" in inst: for if_name, val in inst["memory"].items(): base, size = get_base_and_size(self._name_to_block, inst, if_name) name = self._top_name + Name.from_snake_case(val["label"]) region = MemoryRegion(name, base, size) ret.append((val["label"], region)) return ret def _init_plic_targets(self): enum = CEnum(self._top_name + Name(["plic", "target"])) for core_id in range(int(self.top["num_cores"])): enum.add_constant(Name(["ibex", str(core_id)]), docstring="Ibex Core {}".format(core_id)) enum.add_last_constant("Final PLIC target") self.plic_targets = enum def _init_plic_mapping(self): """We eventually want to generate a mapping from interrupt id to the source peripheral. In order to do so, we generate two enums (one for interrupts, one for sources), and store the generated names in a dictionary that represents the mapping. PLIC Interrupt ID 0 corresponds to no interrupt, and so no peripheral, so we encode that in the enum as "unknown". The interrupts have to be added in order, with "none" first, to ensure that they get the correct mapping to their PLIC id, which is used for addressing the right registers and bits. """ sources = CEnum(self._top_name + Name(["plic", "peripheral"])) interrupts = CEnum(self._top_name + Name(["plic", "irq", "id"])) plic_mapping = CArrayMapping( self._top_name + Name(["plic", "interrupt", "for", "peripheral"]), sources.name) unknown_source = sources.add_constant(Name(["unknown"]), docstring="Unknown Peripheral") none_irq_id = interrupts.add_constant(Name(["none"]), docstring="No Interrupt") plic_mapping.add_entry(none_irq_id, unknown_source) # When we generate the `interrupts` enum, the only info we have about # the source is the module name. We'll use `source_name_map` to map a # short module name to the full name object used for the enum constant. source_name_map = {} for name in self.top["interrupt_module"]: source_name = sources.add_constant(Name.from_snake_case(name), docstring=name) source_name_map[name] = source_name sources.add_last_constant("Final PLIC peripheral") for intr in self.top["interrupt"]: # Some interrupts are multiple bits wide. Here we deal with that by # adding a bit-index suffix if "width" in intr and int(intr["width"]) != 1: for i in range(int(intr["width"])): name = Name.from_snake_case(intr["name"]) + Name([str(i)]) irq_id = interrupts.add_constant(name, docstring="{} {}".format( intr["name"], i)) source_name = source_name_map[intr["module_name"]] plic_mapping.add_entry(irq_id, source_name) else: name = Name.from_snake_case(intr["name"]) irq_id = interrupts.add_constant(name, docstring=intr["name"]) source_name = source_name_map[intr["module_name"]] plic_mapping.add_entry(irq_id, source_name) interrupts.add_last_constant("The Last Valid Interrupt ID.") self.plic_sources = sources self.plic_interrupts = interrupts self.plic_mapping = plic_mapping def _init_alert_mapping(self): """We eventually want to generate a mapping from alert id to the source peripheral. In order to do so, we generate two enums (one for alerts, one for sources), and store the generated names in a dictionary that represents the mapping. Alert Handler has no concept of "no alert", unlike the PLIC. The alerts have to be added in order, to ensure that they get the correct mapping to their alert id, which is used for addressing the right registers and bits. """ sources = CEnum(self._top_name + Name(["alert", "peripheral"])) alerts = CEnum(self._top_name + Name(["alert", "id"])) alert_mapping = CArrayMapping( self._top_name + Name(["alert", "for", "peripheral"]), sources.name) # When we generate the `alerts` enum, the only info we have about the # source is the module name. We'll use `source_name_map` to map a short # module name to the full name object used for the enum constant. source_name_map = {} for name in self.top["alert_module"]: source_name = sources.add_constant(Name.from_snake_case(name), docstring=name) source_name_map[name] = source_name sources.add_last_constant("Final Alert peripheral") for alert in self.top["alert"]: if "width" in alert and int(alert["width"]) != 1: for i in range(int(alert["width"])): name = Name.from_snake_case(alert["name"]) + Name([str(i)]) irq_id = alerts.add_constant(name, docstring="{} {}".format( alert["name"], i)) source_name = source_name_map[alert["module_name"]] alert_mapping.add_entry(irq_id, source_name) else: name = Name.from_snake_case(alert["name"]) alert_id = alerts.add_constant(name, docstring=alert["name"]) source_name = source_name_map[alert["module_name"]] alert_mapping.add_entry(alert_id, source_name) alerts.add_last_constant("The Last Valid Alert ID.") self.alert_sources = sources self.alert_alerts = alerts self.alert_mapping = alert_mapping def _init_pinmux_mapping(self): """Generate C enums for addressing pinmux registers and in/out selects. Inputs/outputs are connected in the order the modules are listed in the hjson under the "mio_modules" key. For each module, the corresponding inouts are connected first, followed by either the inputs or the outputs. Inputs: - Peripheral chooses register field (pinmux_peripheral_in) - Insel chooses MIO input (pinmux_insel) Outputs: - MIO chooses register field (pinmux_mio_out) - Outsel chooses peripheral output (pinmux_outsel) Insel and outsel have some special values which are captured here too. """ pinmux_info = self.top['pinmux'] pinout_info = self.top['pinout'] # Peripheral Inputs peripheral_in = CEnum(self._top_name + Name(['pinmux', 'peripheral', 'in'])) i = 0 for sig in pinmux_info['ios']: if sig['connection'] == 'muxed' and sig['type'] in ['inout', 'input']: index = Name([str(sig['idx'])]) if sig['idx'] != -1 else Name([]) name = Name.from_snake_case(sig['name']) + index peripheral_in.add_constant(name, docstring='Peripheral Input {}'.format(i)) i += 1 peripheral_in.add_last_constant('Last valid peripheral input') # Pinmux Input Selects insel = CEnum(self._top_name + Name(['pinmux', 'insel'])) insel.add_constant(Name(['constant', 'zero']), docstring='Tie constantly to zero') insel.add_constant(Name(['constant', 'one']), docstring='Tie constantly to one') i = 0 for pad in pinout_info['pads']: if pad['connection'] == 'muxed': insel.add_constant(Name([pad['name']]), docstring='MIO Pad {}'.format(i)) i += 1 insel.add_last_constant('Last valid insel value') # MIO Outputs mio_out = CEnum(self._top_name + Name(['pinmux', 'mio', 'out'])) i = 0 for pad in pinout_info['pads']: if pad['connection'] == 'muxed': mio_out.add_constant(Name.from_snake_case(pad['name']), docstring='MIO Pad {}'.format(i)) i += 1 mio_out.add_last_constant('Last valid mio output') # Pinmux Output Selects outsel = CEnum(self._top_name + Name(['pinmux', 'outsel'])) outsel.add_constant(Name(['constant', 'zero']), docstring='Tie constantly to zero') outsel.add_constant(Name(['constant', 'one']), docstring='Tie constantly to one') outsel.add_constant(Name(['constant', 'high', 'z']), docstring='Tie constantly to high-Z') i = 0 for sig in pinmux_info['ios']: if sig['connection'] == 'muxed' and sig['type'] in ['inout', 'output']: index = Name([str(sig['idx'])]) if sig['idx'] != -1 else Name([]) name = Name.from_snake_case(sig['name']) + index outsel.add_constant(name, docstring='Peripheral Output {}'.format(i)) i += 1 outsel.add_last_constant('Last valid outsel value') self.pinmux_peripheral_in = peripheral_in self.pinmux_insel = insel self.pinmux_mio_out = mio_out self.pinmux_outsel = outsel def _init_pwrmgr_wakeups(self): enum = CEnum(self._top_name + Name(["power", "manager", "wake", "ups"])) for signal in self.top["wakeups"]: enum.add_constant( Name.from_snake_case(signal["module"]) + Name.from_snake_case(signal["name"])) enum.add_last_constant("Last valid pwrmgr wakeup signal") self.pwrmgr_wakeups = enum # Enumerates the positions of all software controllable resets def _init_rstmgr_sw_rsts(self): sw_rsts = self.top['resets'].get_sw_resets() enum = CEnum(self._top_name + Name(["reset", "manager", "sw", "resets"])) for rst in sw_rsts: enum.add_constant(Name.from_snake_case(rst)) enum.add_last_constant("Last valid rstmgr software reset request") self.rstmgr_sw_rsts = enum def _init_pwrmgr_reset_requests(self): enum = CEnum(self._top_name + Name(["power", "manager", "reset", "requests"])) for signal in self.top["reset_requests"]: enum.add_constant( Name.from_snake_case(signal["module"]) + Name.from_snake_case(signal["name"])) enum.add_last_constant("Last valid pwrmgr reset_request signal") self.pwrmgr_reset_requests = enum def _init_clkmgr_clocks(self): """ Creates CEnums for accessing the software-controlled clocks in the design. The logic here matches the logic in topgen.py in how it instantiates the clock manager with the described clocks. We differentiate "gateable" clocks and "hintable" clocks because the clock manager has separate register interfaces for each group. """ clocks = self.top['clocks'] aon_clocks = set(src.name for src in clocks.all_srcs.values() if src.aon) gateable_clocks = CEnum(self._top_name + Name(["gateable", "clocks"])) hintable_clocks = CEnum(self._top_name + Name(["hintable", "clocks"])) # This replicates the behaviour in `topgen.py` in deriving `hints` and # `sw_clocks`. for group in clocks.groups.values(): for name, source in group.clocks.items(): if source.name not in aon_clocks: # All these clocks start with `clk_` which is redundant. clock_name = Name.from_snake_case(name).remove_part("clk") docstring = "Clock {} in group {}".format(name, group.name) if group.sw_cg == "yes": gateable_clocks.add_constant(clock_name, docstring) elif group.sw_cg == "hint": hintable_clocks.add_constant(clock_name, docstring) gateable_clocks.add_last_constant("Last Valid Gateable Clock") hintable_clocks.add_last_constant("Last Valid Hintable Clock") self.clkmgr_gateable_clocks = gateable_clocks self.clkmgr_hintable_clocks = hintable_clocks