tinyriscv/tools/regtool/reggen/gen_cheader.py

446 lines
16 KiB
Python

# 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"]
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":
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,
field: Field,
subreg_num: int,
regwidth: int,
existing_defines: Set[str]) -> None:
field_width = field.bits.width()
fields_per_reg = regwidth // field_width
define_name = regname + '_' + as_define(field.name + "_FIELD_WIDTH")
define = gen_define(define_name, [], str(field_width), existing_defines)
genout(outstr, define)
define_name = regname + '_' + as_define(field.name + "_FIELDS_PER_REG")
define = gen_define(define_name, [], str(fields_per_reg), 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[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_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)