text: # 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))
  