blob: 577168b1105686285ed1d4df9afdf272d0f28610 [file] [log] [blame]
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
'''Simple code that understands enough of otbn.hjson to get a memory layout
Each memory will have an associated "window" entry, which has an offset from
the start of the IP block's register space. This offset will be treated as an
LMA for this memory and these memory LMAs will be used uniformly in
OTBN-specific tooling. This avoids the result depending on the address layout
of the wider chip.
In particular, note that OTBN ELF binaries will use these LMAs (the VMAs are in
OTBN's address space, with both IMEM and DMEM starting at 0). A tool that takes
such binaries and incorporates them into a top-level image will need to do
address translation (essentially, just adding the base address of the OTBN IP
block).
'''
import os
import sys
from typing import Dict, List, Mapping, Tuple
import hjson # type: ignore
# We use reggen to read the hjson file. Since that lives somewhere completely
# different from this script (and there aren't __init__.py files scattered all
# over the OpenTitan repository), we have to do sys.path hacks to find it.
_OLD_SYS_PATH = sys.path
try:
_UTIL_PATH = os.path.join(os.path.dirname(__file__),
'..', '..', '..', '..', '..', 'util')
sys.path = [_UTIL_PATH] + _OLD_SYS_PATH
from reggen.validate import checking_dict, validate # type: ignore
finally:
sys.path = _OLD_SYS_PATH
# An hjson dict is actually an OrderedDict, but typing/mypy support for that is
# a little spotty, so we'll use a generic Mapping type.
_HjsonDict = Mapping[str, object]
# A window is represented as (offset, size)
_Window = Tuple[int, int]
def load_registers(path: str) -> Tuple[int, List[_HjsonDict]]:
'''Load hjson file at path with reggen
Return its register width and list of registers.
'''
try:
with open(path, 'r') as handle:
obj = hjson.loads(handle.read(),
use_decimal=True,
object_pairs_hook=checking_dict)
except ValueError as err:
raise RuntimeError('Failed to parse {!r}: {}'.format(path, err))
# Unconditionally run second validation pass
num_errs = validate(obj)
if num_errs:
raise RuntimeError('Reggen second validation pass failed for {!r} '
'({} errors).'
.format(path, num_errs))
reg_bit_width = int(obj.get('regwidth', 32))
assert isinstance(reg_bit_width, int) and reg_bit_width >= 0
reg_byte_width = reg_bit_width // 8
# obj should be an OrderedDict and should contain a registers entry
# (checked by validate). This is a list of registers which we'll return.
# The validation code would also have exploded if it wasn't a list of
# dictionaries, so we can assert the type safely.
registers = obj['registers']
assert isinstance(registers, list)
return (reg_byte_width, registers)
def extract_windows(reg_byte_width: int,
registers: List[_HjsonDict]) -> Dict[str, _Window]:
'''Make sense of the list of register definitions and extract memories'''
# Conveniently, reggen's validate method stores 'genoffset' (the offset to
# the start) for each window, so we can just look that up.
windows = {}
for reg in registers:
assert isinstance(reg, dict)
window = reg.get('window')
if window is None:
continue
assert isinstance(window, dict)
offset = window['genoffset']
assert isinstance(offset, int)
items = int(window['items'])
window_name = window.get('name', 'Window at +{:#x}'.format(offset))
assert isinstance(window_name, str)
if window_name in windows:
raise ValueError('Duplicate window entry with name {!r}.'
.format(window_name))
windows[window_name] = (offset, items * reg_byte_width)
return windows
def get_memory_layout() -> Dict[str, Tuple[int, int]]:
'''Read otbn.hjson to get IMEM / DMEM layout
Returns a dictionary with two entries, keyed 'IMEM' and 'DMEM'. The value
at each entry is a pair (offset, size_in_bytes).
'''
hjson_path = os.path.join(os.path.dirname(__file__),
'..', '..', 'data', 'otbn.hjson')
reg_byte_width, registers = load_registers(hjson_path)
windows = extract_windows(reg_byte_width, registers)
xmem_names = {'IMEM', 'DMEM'}
for name in xmem_names:
if name not in windows:
raise RuntimeError("otbn.hjson doesn't have a window called {}."
.format(name))
if len(windows) != 2:
raise RuntimeError("Unexpected windows in otbn.hjson: {}"
.format(list(set(windows.keys()) - xmem_names)))
return windows
if __name__ == '__main__':
for name, (off, width) in get_memory_layout().items():
print('{}: {} bytes; LMA {:#x}'.format(name, width, off))