446 lines
16 KiB
Python
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)
|