|  | # Copyright lowRISC contributors. | 
|  | # Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
|  | # SPDX-License-Identifier: Apache-2.0 | 
|  | """ | 
|  | Generate C header from validated register JSON tree | 
|  | """ | 
|  |  | 
|  | import io | 
|  | import logging as log | 
|  | import sys | 
|  | import textwrap | 
|  | import warnings | 
|  | from typing import List, Optional, Set, TextIO | 
|  |  | 
|  |  | 
|  | from .field import Field | 
|  | from .ip_block import IpBlock | 
|  | from .params import LocalParam | 
|  | from .register import Register | 
|  | from .multi_register import MultiRegister | 
|  | from .signal import Signal | 
|  | from .window import Window | 
|  |  | 
|  |  | 
|  | def genout(outfile: TextIO, msg: str) -> None: | 
|  | outfile.write(msg) | 
|  |  | 
|  |  | 
|  | def to_snake_case(s: str) -> str: | 
|  | val = [] | 
|  | for i, ch in enumerate(s): | 
|  | if i > 0 and ch.isupper(): | 
|  | val.append('_') | 
|  | val.append(ch) | 
|  | return ''.join(val) | 
|  |  | 
|  |  | 
|  | def as_define(s: str) -> str: | 
|  | s = s.upper() | 
|  | r = '' | 
|  | for i in range(0, len(s)): | 
|  | r += s[i] if s[i].isalnum() else '_' | 
|  | return r | 
|  |  | 
|  |  | 
|  | def first_line(s: str) -> str: | 
|  | """Returns the first line of a multi-line string""" | 
|  | return s.splitlines()[0] | 
|  |  | 
|  |  | 
|  | def format_comment(s: str) -> str: | 
|  | """Formats a string to comment wrapped to an 80 character line width | 
|  |  | 
|  | Returns wrapped string including newline and // comment characters. | 
|  | """ | 
|  | return '\n'.join( | 
|  | textwrap.wrap( | 
|  | s, width=77, initial_indent='// ', subsequent_indent='// ')) + '\n' | 
|  |  | 
|  |  | 
|  | def gen_define(name: str, | 
|  | args: List[str], | 
|  | body: str, | 
|  | existing_defines: Set[str], | 
|  | indent: str = '  ') -> str: | 
|  | r"""Produces a #define string, will split into two lines if a single line | 
|  | has a width greater than 80 characters. Result includes newline. | 
|  |  | 
|  | Arguments: | 
|  | name - Name of the #define | 
|  | args - List of arguments for the define, provide an empty list if there are | 
|  | none | 
|  | body - Body of the #define | 
|  | existing_defines - set of already generated define names. | 
|  | Error if `name` is in `existing_defines`. | 
|  | indent - Gives string to prepend on any new lines produced by | 
|  | wrapping (default '  ') | 
|  |  | 
|  | Example result: | 
|  | name = 'A_MACRO' | 
|  | args = ['arg1', 'arg2'], | 
|  | body = 'arg1 + arg2 + 10' | 
|  |  | 
|  | #define A_MACRO(arg1, arg2) arg1 + arg2 + 10 | 
|  |  | 
|  | When the macro is wrapped the break happens after the argument list (or | 
|  | macro name if there is no argument list | 
|  |  | 
|  | #define A_MACRO(arg1, arg2) \ | 
|  | arg1 + arg2 + 10 | 
|  |  | 
|  | """ | 
|  |  | 
|  | if name in existing_defines: | 
|  | log.error("Duplicate #define for " + name) | 
|  | sys.exit(1) | 
|  |  | 
|  | if len(args) != 0: | 
|  | define_declare = '#define ' + name + '(' + ', '.join(args) + ')' | 
|  | else: | 
|  | define_declare = '#define ' + name | 
|  |  | 
|  | oneline_define = define_declare + ' ' + body | 
|  |  | 
|  | existing_defines.add(name) | 
|  |  | 
|  | if len(oneline_define) <= 80: | 
|  | return oneline_define + '\n' | 
|  |  | 
|  | return define_declare + ' \\\n' + indent + body + '\n' | 
|  |  | 
|  |  | 
|  | def gen_cdefine_register(outstr: TextIO, | 
|  | reg: Register, | 
|  | comp: str, | 
|  | width: int, | 
|  | rnames: Set[str], | 
|  | existing_defines: Set[str]) -> None: | 
|  | rname = reg.name | 
|  | offset = reg.offset | 
|  |  | 
|  | genout(outstr, format_comment(first_line(reg.desc))) | 
|  | defname = as_define(comp + '_' + rname) | 
|  | genout( | 
|  | outstr, | 
|  | gen_define(defname + '_REG_OFFSET', [], hex(offset), existing_defines)) | 
|  | genout( | 
|  | outstr, | 
|  | gen_define(defname + '_REG_RESVAL', [], | 
|  | hex(reg.resval), existing_defines)) | 
|  |  | 
|  | for field in reg.fields: | 
|  | dname = defname + '_' + as_define(field.name) | 
|  | field_width = field.bits.width() | 
|  |  | 
|  | if field_width == 1: | 
|  | # single bit | 
|  | genout( | 
|  | outstr, | 
|  | gen_define(dname + '_BIT', [], str(field.bits.lsb), | 
|  | existing_defines)) | 
|  | else: | 
|  | # multiple bits (unless it is the whole register) | 
|  | if field_width != width: | 
|  | mask = field.bits.bitmask() >> field.bits.lsb | 
|  | genout( | 
|  | outstr, | 
|  | gen_define(dname + '_MASK', [], hex(mask), | 
|  | existing_defines)) | 
|  | genout( | 
|  | outstr, | 
|  | gen_define(dname + '_OFFSET', [], str(field.bits.lsb), | 
|  | existing_defines)) | 
|  | genout( | 
|  | outstr, | 
|  | gen_define( | 
|  | dname + '_FIELD', [], | 
|  | '((bitfield_field32_t) {{ .mask = {dname}_MASK, .index = {dname}_OFFSET }})' | 
|  | .format(dname=dname), existing_defines)) | 
|  | if field.enum is not None: | 
|  | for enum in field.enum: | 
|  | ename = as_define(enum.name) | 
|  | value = hex(enum.value) | 
|  | genout( | 
|  | outstr, | 
|  | gen_define( | 
|  | defname + '_' + as_define(field.name) + | 
|  | '_VALUE_' + ename, [], value, existing_defines)) | 
|  | genout(outstr, '\n') | 
|  | return | 
|  |  | 
|  |  | 
|  | def gen_cdefine_window(outstr: TextIO, | 
|  | win: Window, | 
|  | comp: str, | 
|  | regwidth: int, | 
|  | rnames: Set[str], | 
|  | existing_defines: Set[str]) -> None: | 
|  | offset = win.offset | 
|  |  | 
|  | genout(outstr, format_comment('Memory area: ' + first_line(win.desc))) | 
|  | defname = as_define(comp + '_' + win.name) | 
|  | genout( | 
|  | outstr, | 
|  | gen_define(defname + '_REG_OFFSET', [], hex(offset), existing_defines)) | 
|  | items = win.items | 
|  | genout( | 
|  | outstr, | 
|  | gen_define(defname + '_SIZE_WORDS', [], str(items), existing_defines)) | 
|  | items = items * (regwidth // 8) | 
|  | genout( | 
|  | outstr, | 
|  | gen_define(defname + '_SIZE_BYTES', [], str(items), existing_defines)) | 
|  |  | 
|  | wid = win.validbits | 
|  | if (wid != regwidth): | 
|  | mask = (1 << wid) - 1 | 
|  | genout(outstr, | 
|  | gen_define(defname + '_MASK ', [], hex(mask), existing_defines)) | 
|  |  | 
|  |  | 
|  | def gen_cdefines_module_param(outstr: TextIO, | 
|  | param: LocalParam, | 
|  | module_name: str, | 
|  | existing_defines: Set[str]) -> None: | 
|  | # Presently there is only one type (int), however if the new types are | 
|  | # added, they potentially need to be handled differently. | 
|  | known_types = ["int", "int unsigned"] | 
|  | if param.param_type not in known_types: | 
|  | warnings.warn("Cannot generate a module define of type {}" | 
|  | .format(param.param_type)) | 
|  | return | 
|  |  | 
|  | if param.desc is not None: | 
|  | genout(outstr, format_comment(first_line(param.desc))) | 
|  | # Heuristic: if the name already has underscores, it's already snake_case, | 
|  | # otherwise, assume StudlyCaps and covert it to snake_case. | 
|  | param_name = param.name if '_' in param.name else to_snake_case(param.name) | 
|  | define_name = as_define(module_name + '_PARAM_' + param_name) | 
|  | if param.param_type == "int" or param.param_type == "int unsigned": | 
|  | define = gen_define(define_name, [], param.value, | 
|  | existing_defines) | 
|  |  | 
|  | genout(outstr, define) | 
|  | genout(outstr, '\n') | 
|  |  | 
|  |  | 
|  | def gen_cdefines_module_params(outstr: TextIO, | 
|  | module_data: IpBlock, | 
|  | module_name: str, | 
|  | register_width: int, | 
|  | existing_defines: Set[str]) -> None: | 
|  | module_params = module_data.params | 
|  |  | 
|  | for param in module_params.get_localparams(): | 
|  | gen_cdefines_module_param(outstr, param, module_name, existing_defines) | 
|  |  | 
|  | genout(outstr, format_comment(first_line("Register width"))) | 
|  | define_name = as_define(module_name + '_PARAM_REG_WIDTH') | 
|  | define = gen_define(define_name, [], str(register_width), existing_defines) | 
|  | genout(outstr, define) | 
|  | genout(outstr, '\n') | 
|  |  | 
|  |  | 
|  | def gen_multireg_field_defines(outstr: TextIO, | 
|  | regname: str, | 
|  | fields: List[Field], | 
|  | subreg_num: int, | 
|  | regwidth: int, | 
|  | existing_defines: Set[str]) -> None: | 
|  |  | 
|  | for each_field in fields: | 
|  | field_width = each_field.bits.width() | 
|  |  | 
|  | define_name = regname + '_' + as_define(each_field.name + "_FIELD_WIDTH") | 
|  | define = gen_define(define_name, [], str(field_width), existing_defines) | 
|  | genout(outstr, define) | 
|  |  | 
|  | define_name = regname + "_MULTIREG_COUNT" | 
|  | define = gen_define(define_name, [], str(subreg_num), existing_defines) | 
|  | genout(outstr, define) | 
|  |  | 
|  | genout(outstr, '\n') | 
|  |  | 
|  |  | 
|  | def gen_cdefine_multireg(outstr: TextIO, | 
|  | multireg: MultiRegister, | 
|  | component: str, | 
|  | regwidth: int, | 
|  | rnames: Set[str], | 
|  | existing_defines: Set[str]) -> None: | 
|  | comment = multireg.reg.desc + " (common parameters)" | 
|  | genout(outstr, format_comment(first_line(comment))) | 
|  | if len(multireg.reg.fields) >= 1: | 
|  | regname = as_define(component + '_' + multireg.reg.name) | 
|  | gen_multireg_field_defines(outstr, regname, multireg.reg.fields, | 
|  | len(multireg.regs), regwidth, existing_defines) | 
|  | else: | 
|  | log.warn("Fieldless multireg " + multireg.reg.name + | 
|  | " skip multireg specific data generation.") | 
|  |  | 
|  | for subreg in multireg.regs: | 
|  | gen_cdefine_register(outstr, subreg, component, regwidth, rnames, | 
|  | existing_defines) | 
|  |  | 
|  |  | 
|  | def gen_cdefines_interrupt_field(outstr: TextIO, | 
|  | interrupt: Signal, | 
|  | component: str, | 
|  | regwidth: int, | 
|  | existing_defines: Set[str]) -> None: | 
|  | fieldlsb = interrupt.bits.lsb | 
|  | iname = interrupt.name | 
|  | defname = as_define(component + '_INTR_COMMON_' + iname) | 
|  |  | 
|  | if interrupt.bits.width() == 1: | 
|  | # single bit | 
|  | genout( | 
|  | outstr, | 
|  | gen_define(defname + '_BIT', [], str(fieldlsb), existing_defines)) | 
|  | else: | 
|  | # multiple bits (unless it is the whole register) | 
|  | if interrupt.bits.width() != regwidth: | 
|  | mask = interrupt.bits.msb >> fieldlsb | 
|  | genout( | 
|  | outstr, | 
|  | gen_define(defname + '_MASK', [], hex(mask), existing_defines)) | 
|  | genout( | 
|  | outstr, | 
|  | gen_define(defname + '_OFFSET', [], str(fieldlsb), | 
|  | existing_defines)) | 
|  | genout( | 
|  | outstr, | 
|  | gen_define( | 
|  | defname + '_FIELD', [], | 
|  | '((bitfield_field32_t) {{ .mask = {dname}_MASK, .index = {dname}_OFFSET }})' | 
|  | .format(dname=defname), existing_defines)) | 
|  |  | 
|  |  | 
|  | def gen_cdefines_interrupts(outstr: TextIO, | 
|  | block: IpBlock, | 
|  | component: str, | 
|  | regwidth: int, | 
|  | existing_defines: Set[str]) -> None: | 
|  | # If no_auto_intr_regs is true, then we do not generate common defines, | 
|  | # because the bit offsets for a particular interrupt may differ between | 
|  | # the interrupt enable/state/test registers. | 
|  | if block.no_auto_intr: | 
|  | return | 
|  |  | 
|  | genout(outstr, format_comment(first_line("Common Interrupt Offsets"))) | 
|  | for intr in block.interrupts: | 
|  | gen_cdefines_interrupt_field(outstr, intr, component, regwidth, | 
|  | existing_defines) | 
|  | genout(outstr, '\n') | 
|  |  | 
|  |  | 
|  | def gen_cdefines(block: IpBlock, | 
|  | outfile: TextIO, | 
|  | src_lic: Optional[str], | 
|  | src_copy: str) -> int: | 
|  | rnames = block.get_rnames() | 
|  |  | 
|  | outstr = io.StringIO() | 
|  |  | 
|  | # This tracks the defines that have been generated so far, so we | 
|  | # can error if we attempt to duplicate a definition | 
|  | existing_defines = set()  # type: Set[str] | 
|  |  | 
|  | gen_cdefines_module_params(outstr, block, block.name, block.regwidth, | 
|  | existing_defines) | 
|  |  | 
|  | gen_cdefines_interrupts(outstr, block, block.name, block.regwidth, | 
|  | existing_defines) | 
|  |  | 
|  | for rb in block.reg_blocks.values(): | 
|  | for x in rb.entries: | 
|  | if isinstance(x, Register): | 
|  | gen_cdefine_register(outstr, x, block.name, block.regwidth, rnames, | 
|  | existing_defines) | 
|  | continue | 
|  |  | 
|  | if isinstance(x, MultiRegister): | 
|  | gen_cdefine_multireg(outstr, x, block.name, block.regwidth, rnames, | 
|  | existing_defines) | 
|  | continue | 
|  |  | 
|  | if isinstance(x, Window): | 
|  | gen_cdefine_window(outstr, x, block.name, block.regwidth, | 
|  | rnames, existing_defines) | 
|  | continue | 
|  |  | 
|  | generated = outstr.getvalue() | 
|  | outstr.close() | 
|  |  | 
|  | genout(outfile, '// Generated register defines for ' + block.name + '\n\n') | 
|  | if src_copy != '': | 
|  | genout(outfile, '// Copyright information found in source file:\n') | 
|  | genout(outfile, '// ' + src_copy + '\n\n') | 
|  | if src_lic is not None: | 
|  | genout(outfile, '// Licensing information found in source file:\n') | 
|  | for line in src_lic.splitlines(): | 
|  | genout(outfile, '// ' + line + '\n') | 
|  | genout(outfile, '\n') | 
|  |  | 
|  | # Header Include Guard | 
|  | genout(outfile, '#ifndef _' + as_define(block.name) + '_REG_DEFS_\n') | 
|  | genout(outfile, '#define _' + as_define(block.name) + '_REG_DEFS_\n\n') | 
|  |  | 
|  | # Header Extern Guard (so header can be used from C and C++) | 
|  | genout(outfile, '#ifdef __cplusplus\n') | 
|  | genout(outfile, 'extern "C" {\n') | 
|  | genout(outfile, '#endif\n') | 
|  |  | 
|  | genout(outfile, generated) | 
|  |  | 
|  | # Header Extern Guard | 
|  | genout(outfile, '#ifdef __cplusplus\n') | 
|  | genout(outfile, '}  // extern "C"\n') | 
|  | genout(outfile, '#endif\n') | 
|  |  | 
|  | # Header Include Guard | 
|  | genout(outfile, '#endif  // _' + as_define(block.name) + '_REG_DEFS_\n') | 
|  |  | 
|  | genout(outfile, '// End generated register defines for ' + block.name) | 
|  |  | 
|  | return 0 | 
|  |  | 
|  |  | 
|  | def test_gen_define() -> None: | 
|  | basic_oneline = '#define MACRO_NAME body\n' | 
|  | assert gen_define('MACRO_NAME', [], 'body', set()) == basic_oneline | 
|  |  | 
|  | basic_oneline_with_args = '#define MACRO_NAME(arg1, arg2) arg1 + arg2\n' | 
|  | assert (gen_define('MACRO_NAME', ['arg1', 'arg2'], 'arg1 + arg2', | 
|  | set()) == basic_oneline_with_args) | 
|  |  | 
|  | long_macro_name = 'A_VERY_VERY_VERY_VERY_VERY_VERY_VERY_VERY_VERY_VERY_VERY_LONG_MACRO_NAME' | 
|  |  | 
|  | multiline = ('#define ' + long_macro_name + ' \\\n' + | 
|  | '  a_fairly_long_body + something_else + 10\n') | 
|  |  | 
|  | assert (gen_define(long_macro_name, [], | 
|  | 'a_fairly_long_body + something_else + 10', | 
|  | set()) == multiline) | 
|  |  | 
|  | multiline_with_args = ('#define ' + long_macro_name + | 
|  | '(arg1, arg2, arg3) \\\n' + | 
|  | '  a_fairly_long_body + arg1 + arg2 + arg3\n') | 
|  |  | 
|  | assert (gen_define(long_macro_name, ['arg1', 'arg2', 'arg3'], | 
|  | 'a_fairly_long_body + arg1 + arg2 + arg3', | 
|  | set()) == multiline_with_args) | 
|  |  | 
|  | multiline_with_args_big_indent = ( | 
|  | '#define ' + long_macro_name + '(arg1, arg2, arg3) \\\n' + | 
|  | '    a_fairly_long_body + arg1 + arg2 + arg3\n') | 
|  |  | 
|  | assert (gen_define(long_macro_name, ['arg1', 'arg2', 'arg3'], | 
|  | 'a_fairly_long_body + arg1 + arg2 + arg3', | 
|  | set(), | 
|  | indent='    ') == multiline_with_args_big_indent) |