regtool: add support to generate reg const as Rust (.rs)
Add -R and --rust option in regtool.py to generate Rust file.
Add regs-rust target in hw/Makefile to generate reg constant Rust
file for ALL ips. If a TOCK_ROOT variable is specified, e.g.
"TOCK_ROOT=~/ti50/third_party/tock/tock make -C hw regs-rust",
the Rust files will also be copied to
$(TOCK_ROOT)/chips/lowrisc/src/reg_constants folder.
BUG=none
TEST=regtool.py -R -o uart_regs.rs uart.hjson
make -C hw regs-rust
TOCK_ROOT=~/ti50/third_party/tock/tock make -C hw regs-rust
Signed-off-by: Chia-Chi Teng <ccteng@google.com>
Change-Id: I5f668d51e1f42e6541a41f3e565848230d6c07b6
diff --git a/util/reggen/gen_rust.py b/util/reggen/gen_rust.py
new file mode 100644
index 0000000..09d2f7f
--- /dev/null
+++ b/util/reggen/gen_rust.py
@@ -0,0 +1,364 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+"""
+Generate Rust constants from validated register JSON tree
+"""
+
+import io
+import logging as log
+import sys
+import textwrap
+import warnings
+from typing import 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.
+ """
+ comment = textwrap.wrap(s,
+ width=77,
+ initial_indent='// ',
+ subsequent_indent='// ')
+ return '\n'.join(comment) + '\n'
+
+
+def data_type(name: str, val: int, as_hex: bool) -> str:
+ """ Returns proper data type for name-value pair. """
+
+ if name.endswith("_OFFSET") or name.endswith("_BASE_ADDR"):
+ return "usize"
+
+ if val.bit_length() > 32:
+ log.error(name + " value exceeds 32 bit " + str(val))
+ sys.exit(1)
+
+ if not as_hex and val < 0:
+ return "i32"
+
+ return "u32"
+
+
+def gen_const(outstr: TextIO,
+ name: str,
+ suffix: str,
+ val: int,
+ existing_defines: Set[str],
+ as_hex: bool = False) -> str:
+ r"""Produces a pub const string. Result includes newline.
+
+ Arguments:
+ name - Name of the constant
+ val - Value of the constant
+ existing_defines - set of already generated define names.
+ Error if `name` is in `existing_defines`.
+
+ Example result:
+ name = 'A_NAME'
+ val = '10'
+
+ pub const A_NAME: u32 = 10
+
+ """
+
+ suffix = '' if not suffix.strip() else '_' + suffix
+ name = name + suffix
+ if name in existing_defines:
+ log.error("Duplicate pub const for " + name)
+ sys.exit(1)
+
+ define_declare = 'pub const ' + name + ': ' + data_type(name, val, as_hex)
+
+ val_str = hex(val) if as_hex else str(val)
+ oneline_define = define_declare + ' = ' + val_str + ';'
+
+ existing_defines.add(name)
+
+ output = oneline_define + '\n'
+ genout(outstr, output)
+ return output
+
+
+def gen_const_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)
+ gen_const(outstr, defname, 'REG_OFFSET', offset, existing_defines, True)
+
+ for field in reg.fields:
+ dname = defname + '_' + as_define(field.name)
+ field_width = field.bits.width()
+
+ if field_width == 1:
+ # single bit
+ gen_const(outstr, dname, 'BIT', 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
+ gen_const(outstr, dname, 'MASK', mask, existing_defines, True)
+ gen_const(outstr, dname, 'OFFSET', field.bits.lsb, existing_defines)
+
+ if field.enum is not None:
+ for enum in field.enum:
+ ename = as_define(enum.name)
+ gen_const(
+ outstr,
+ defname + '_' + as_define(field.name),
+ 'VALUE_' + ename,
+ enum.value,
+ existing_defines,
+ True)
+
+ genout(outstr, '\n')
+ return
+
+
+def gen_const_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)
+ gen_const(outstr, defname, 'REG_OFFSET', offset, existing_defines, True)
+ items = win.items
+ gen_const(outstr, defname, 'SIZE_WORDS', items, existing_defines)
+ items = items * (regwidth // 8)
+ gen_const(outstr, defname, 'SIZE_BYTES', items, existing_defines)
+
+ wid = win.validbits
+ if (wid != regwidth):
+ mask = (1 << wid) - 1
+ gen_const(outstr, defname, 'MASK', mask, existing_defines, True)
+
+
+def gen_rust_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"]
+ 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":
+ gen_const(outstr, define_name, '', int(param.value), existing_defines)
+
+ genout(outstr, '\n')
+
+
+def gen_const_module_params(outstr: TextIO,
+ module_data: IpBlock,
+ module_name: str,
+ register_width: int,
+ existing_defines: Set[str]) -> None:
+ for param in module_data.params.get_localparams():
+ gen_rust_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')
+ gen_const(outstr, define_name, '', register_width, existing_defines)
+ genout(outstr, '\n')
+
+
+def gen_multireg_field_defines(outstr: TextIO,
+ regname: str,
+ field: Field,
+ subreg_num: int,
+ regwidth: int,
+ existing_defines: Set[str]) -> None:
+ field_width = field.bits.width()
+ fields_per_reg = regwidth // field_width
+
+ suffix = as_define(field.name + "_FIELD_WIDTH")
+ gen_const(outstr, regname, suffix, field_width, existing_defines)
+
+ suffix = as_define(field.name + "_FIELDS_PER_REG")
+ gen_const(outstr, regname, suffix, fields_per_reg, existing_defines)
+
+ gen_const(outstr, regname, "MULTIREG_COUNT", subreg_num, existing_defines)
+
+ genout(outstr, '\n')
+
+
+def gen_const_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[0],
+ len(multireg.regs), regwidth, existing_defines)
+ else:
+ log.warn("Non-homogeneous multireg " + multireg.reg.name +
+ " skip multireg specific data generation.")
+
+ for subreg in multireg.regs:
+ gen_const_register(outstr, subreg, component, regwidth, rnames,
+ existing_defines)
+
+
+def gen_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
+ gen_const(outstr, defname, 'BIT', fieldlsb, existing_defines)
+ else:
+ # multiple bits (unless it is the whole register)
+ if interrupt.bits.width() != regwidth:
+ mask = interrupt.bits.msb >> fieldlsb
+ gen_const(outstr, defname, 'MASK', mask, existing_defines, True)
+ gen_const(outstr, defname, 'OFFSET', fieldlsb, existing_defines)
+
+
+def gen_const_interrupts(outstr: TextIO,
+ block: IpBlock,
+ component: str,
+ regwidth: int,
+ existing_defines: Set[str]) -> None:
+ # If no_auto_intr 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_interrupt_field(outstr, intr, component, regwidth, existing_defines)
+ genout(outstr, '\n')
+
+
+def gen_rust(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_const_module_params(outstr, block, block.name, block.regwidth,
+ existing_defines)
+
+ gen_const_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_const_register(outstr, x, block.name, block.regwidth, rnames,
+ existing_defines)
+ continue
+
+ if isinstance(x, MultiRegister):
+ gen_const_multireg(outstr, x, block.name, block.regwidth, rnames,
+ existing_defines)
+ continue
+
+ if isinstance(x, Window):
+ gen_const_window(outstr, x, block.name, block.regwidth,
+ rnames, existing_defines)
+ continue
+
+ generated = outstr.getvalue()
+ outstr.close()
+
+ genout(outfile, '// Generated register constants 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')
+
+ genout(outfile, generated)
+
+ genout(outfile, '// End generated register constants for ' + block.name)
+
+ return 0
+
+
+def test_gen_const() -> None:
+ outstr = io.StringIO()
+
+ basic_oneline = 'pub const MACRO_NAME 10;\n'
+ assert (gen_const(outstr, 'MACRO', 'NAME', 10, set()) == basic_oneline)
+
+ long_macro_name = 'A_VERY_VERY_VERY_VERY_VERY_VERY_VERY_VERY_VERY_VERY_VERY_LONG_MACRO_NAME'
+ multiline = ('pub const ' + long_macro_name + ' \\\n' +
+ ' 1000000000;\n')
+
+ assert (gen_const(outstr, long_macro_name, '', 1000000000, set()) == multiline)
diff --git a/util/regtool.py b/util/regtool.py
index aa264c5..8b7ee7f 100755
--- a/util/regtool.py
+++ b/util/regtool.py
@@ -12,7 +12,7 @@
from pathlib import PurePath
from reggen import (gen_cheader, gen_dv, gen_fpv, gen_html,
- gen_json, gen_rtl, gen_selfdoc, version)
+ gen_json, gen_rtl, gen_rust, gen_selfdoc, version)
from reggen.ip_block import IpBlock
DESC = """regtool, generate register info from Hjson source"""
@@ -46,6 +46,10 @@
'-D',
action='store_true',
help='Output C defines header')
+ parser.add_argument('--rust',
+ '-R',
+ action='store_true',
+ help='Output Rust constants')
parser.add_argument('--doc',
action='store_true',
help='Output source file documentation (gfm)')
@@ -117,7 +121,8 @@
arg_to_format = [('j', ('json', None)), ('c', ('compact', None)),
('d', ('html', None)), ('doc', ('doc', None)),
('r', ('rtl', 'rtl')), ('s', ('dv', 'dv')),
- ('f', ('fpv', 'fpv/vip')), ('cdefines', ('cdh', None))]
+ ('f', ('fpv', 'fpv/vip')), ('cdefines', ('cdh', None)),
+ ('rust', ('rs', None))]
format = None
dirspec = None
for arg_name, spec in arg_to_format:
@@ -225,6 +230,8 @@
return gen_html.gen_html(obj, outfile)
elif format == 'cdh':
return gen_cheader.gen_cdefines(obj, outfile, src_lic, src_copy)
+ elif format == 'rs':
+ return gen_rust.gen_rust(obj, outfile, src_lic, src_copy)
else:
return gen_json.gen_json(obj, outfile, format)