# 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).

'''

from typing import Dict, Optional, Tuple

from reggen.reg_block import RegBlock

from .otbn_reggen import load_registers

# A window is represented as (offset, size)
_Window = Tuple[int, int]

# This needs to be kept in sync with the parameter of the same name in
# otbn_pkg.sv
_DmemScratchSizeBytes = 1024


def extract_windows(reg_byte_width: int, regs: object) -> Dict[str, _Window]:
    '''Make sense of the list of register definitions and extract memories'''

    windows = {}

    assert isinstance(regs, RegBlock)
    for entry in regs.windows:
        name = entry.name or 'Window at +{:#x}'.format(entry.offset)

        # Should be guaranteed by RegBlock constructor
        assert name not in windows

        windows[name] = (entry.offset, entry.size_in_bytes)

    return windows


class OtbnMemoryLayout:
    def __init__(self, windows: Dict[str, _Window]):
        imem_window = windows.get('IMEM')
        if imem_window is None:
            raise RuntimeError('otbn.hjson has no IMEM window')

        dmem_window = windows.get('DMEM')
        if dmem_window is None:
            raise RuntimeError('otbn.hjson has no DMEM window')

        self.imem_address = imem_window[0]
        self.imem_size_bytes = imem_window[1]

        self.dmem_address = dmem_window[0]
        self.dmem_bus_size_bytes = dmem_window[1]
        self.dmem_size_bytes = dmem_window[1] + _DmemScratchSizeBytes


_LAYOUT = None  # type: Optional[OtbnMemoryLayout]


def get_memory_layout() -> OtbnMemoryLayout:
    '''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).

    '''
    global _LAYOUT
    if _LAYOUT is not None:
        return _LAYOUT

    reg_byte_width, registers = load_registers()
    windows = extract_windows(reg_byte_width, registers)

    _LAYOUT = OtbnMemoryLayout(windows)
    return _LAYOUT
