Source code for icdutil.addrmap

#
# MIT License
#
# Copyright (c) 2023 nbiotcloud
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#

"""Address Map."""

import typing

from attrs import field, frozen
from humannum import Bytes, Hex
from tabulate import tabulate

from .addrrange import AddrRange
from .num import calc_lowest_bit_set, calc_unsigned_width
from .slices import Slice


[docs]@frozen(repr=False) class AddrMap: """ Address Map. Keyword Args: addrwidth (int): Address width. Calculated automatically. is_sub (bool): Handle sub addresses only. Just the minimum number of address bits is evaluated. # default (object): Default item, returned on unallocated address ranges. allow_overlapping (bool): Allow overlapping address ranges. >>> a = AddrMap() >>> a AddrMap() >>> a.add('T', size=0x400) AddrRange('T', 0x0, '1 KB') >>> a.add('A', 0x5000, 0x1000) AddrRange('A', 0x5000, '4 KB') >>> a.add('B', 0x2000, 0x1000) AddrRange('B', 0x2000, '4 KB') >>> a.add('C', 0xD000, 0x800) AddrRange('C', 0xD000, '2 KB') >>> a.add('Z', size=0x1000) AddrRange('Z', 0xE000, '4 KB') >>> a.add('X', 0xF0800000, 1024*1024*3) AddrRange('X', 0xF0800000, '3 MB') Addresses can be used only once: >>> a.add('D', 0xE400, 0x0400) Traceback (most recent call last): ... icdutil.addrmap.Conflict: AddrRange('D', 0xE400, '1 KB'): overlaps with AddrRange('Z', 0xE000, '4 KB'). >>> a.add('D', 0x2000) Traceback (most recent call last): ... icdutil.addrmap.Conflict: AddrRange('D', 0x2000, '1 byte'): overlaps with AddrRange('B', 0x2000, '4 KB'). >>> a.add('D', 0x2000, '8 KB') Traceback (most recent call last): ... icdutil.addrmap.Conflict: AddrRange('D', 0x2000, '8 KB'): overlaps with AddrRange('B', 0x2000, '4 KB'). >>> a.add('D', 0x1800, '4 KB') Traceback (most recent call last): ... icdutil.addrmap.Conflict: AddrRange('D', 0x1800, '4 KB'): overlaps with AddrRange('B', 0x2000, '4 KB'). An iteration will serve :class:`AddrRange` objects sorted by baseaddr: >>> for addrrange in a: ... addrrange AddrRange('T', 0x0, '1 KB') AddrRange('B', 0x2000, '4 KB') AddrRange('A', 0x5000, '4 KB') AddrRange('C', 0xD000, '2 KB') AddrRange('Z', 0xE000, '4 KB') AddrRange('X', 0xF0800000, '3 MB') The size is defined by the numer of entries: >>> len(a) 6 By using a `default` the gaps are filled with this item: >>> for addrrange in a.get(default='d'): ... addrrange AddrRange('T', 0x0, '1 KB') AddrRange('d', 0x400, '7 KB') AddrRange('B', 0x2000, '4 KB') AddrRange('d', 0x3000, '8 KB') AddrRange('A', 0x5000, '4 KB') AddrRange('d', 0x6000, '28 KB') AddrRange('C', 0xD000, '2 KB') AddrRange('d', 0xD800, '2 KB') AddrRange('Z', 0xE000, '4 KB') AddrRange('d', 0xF000, '3.76 GB') AddrRange('X', 0xF0800000, '3 MB') AddrRange('d', 0xF0B00000, '245 MB') >>> len(tuple(a.get('d'))) 12 The `is_sub` attribute defines the address range defaults: >>> b = AddrMap(addrwidth=32, is_sub=True) >>> b AddrMap(addrwidth=32, is_sub=True) >>> b.add('T', size=0x400) AddrRange('T', 0x00000000, '1 KB', addrwidth=32, is_sub=True) >>> b.add('A', 0x5000, 0x1000) AddrRange('A', 0x00005000, '4 KB', addrwidth=32, is_sub=True) >>> b.add('B', 0x2000, 0x1000) AddrRange('B', 0x00002000, '4 KB', addrwidth=32, is_sub=True) >>> b.add('C', 0xD000, 0x800) AddrRange('C', 0x0000D000, '2 KB', addrwidth=32, is_sub=True) >>> b.add('Z', size=0x1000) AddrRange('Z', 0x0000E000, '4 KB', addrwidth=32, is_sub=True) >>> b.add('X', 0xF0800000, 1024*1024*3, is_sub=False) AddrRange('X', 0xF0800000, '3 MB', addrwidth=32) >>> for addrrange in b: ... addrrange AddrRange('T', 0x00000000, '1 KB', addrwidth=32, is_sub=True) AddrRange('B', 0x00002000, '4 KB', addrwidth=32, is_sub=True) AddrRange('A', 0x00005000, '4 KB', addrwidth=32, is_sub=True) AddrRange('C', 0x0000D000, '2 KB', addrwidth=32, is_sub=True) AddrRange('Z', 0x0000E000, '4 KB', addrwidth=32, is_sub=True) AddrRange('X', 0xF0800000, '3 MB', addrwidth=32) The `allow_overlapping` attibute allows address ranges to intersect. >>> c = AddrMap(allow_overlapping=True) >>> c AddrMap(allow_overlapping=True) >>> c.add('L', size=0x1000) AddrRange('L', 0x0, '4 KB') >>> c.add('R', 0x400, size=0x1000) AddrRange('R', 0x400, '4 KB') >>> c.add('M', 0x200, '2 KB') AddrRange('M', 0x200, '2 KB') >>> for addrrange in c: ... addrrange AddrRange('L', 0x0, '4 KB') AddrRange('M', 0x200, '2 KB') AddrRange('R', 0x400, '4 KB') """ addrwidth: typing.Optional[int] = field(default=None) is_sub: bool = field(default=False) allow_overlapping: bool = field(default=False) __addrranges: list = field(factory=list) def __repr__(self): opt_kw = [] if self.addrwidth: opt_kw.append(f"addrwidth={self.addrwidth}") if self.is_sub: opt_kw.append("is_sub=True") if self.allow_overlapping: opt_kw.append("allow_overlapping=True") return f"AddrMap({', '.join(opt_kw)})" def __iter__(self): yield from self.__addrranges def __len__(self): return len(tuple(self.__addrranges)) @property def firstaddr(self) -> typing.Optional[int]: """ First used address. >>> a = AddrMap() >>> a.firstaddr >>> a.add('A', 0x5000, '4 KB') AddrRange('A', 0x5000, '4 KB') >>> a.firstaddr Hex('0x5000') >>> a.add('B', 0x2000, '4 KB') AddrRange('B', 0x2000, '4 KB') >>> a.firstaddr Hex('0x2000') """ try: return self.__addrranges[0].baseaddr except IndexError: return None @property def lastaddr(self) -> typing.Optional[int]: """Last used address.""" try: return self.__addrranges[-1].endaddr except IndexError: return None @property def addrspace(self) -> typing.Optional[int]: """ Allocated Address space. >>> a = AddrMap() >>> a.addrspace >>> a.add('A', 0x5000, '4 KB') AddrRange('A', 0x5000, '4 KB') >>> str(a.addrspace) '0x1000' >>> a.add('B', 0x2000, '4 KB') AddrRange('B', 0x2000, '4 KB') >>> str(a.addrspace) '0x4000' >>> a.add('C', 0x7000, 13) AddrRange('C', 0x7000, '13 bytes') >>> str(a.addrspace) '0x500D' """ try: return self.__addrranges[-1].endaddr + 1 - self.__addrranges[0].baseaddr except IndexError: return None @property def eff_addrwidth(self) -> typing.Optional[int]: """ Effective Address Width. Return address width, either explicitly set or calculated from existing entries. >>> a = AddrMap() >>> a.eff_addrwidth >>> a.add('A', 0x5000, '4 KB') AddrRange('A', 0x5000, '4 KB') >>> a.eff_addrwidth 15 >>> a = AddrMap(addrwidth=16) >>> a.eff_addrwidth 16 >>> a.add('A', 2**18, '4 KB') Traceback (most recent call last): ... RuntimeError: AddrRange('A', 0x40000, '4 KB', addrwidth=16): exceeds maximum address range of 0x10000. """ eff_addrwidth = self.addrwidth if eff_addrwidth is None: lastaddr = self.lastaddr if lastaddr is None: eff_addrwidth = None else: eff_addrwidth = calc_unsigned_width(lastaddr) return eff_addrwidth @property def decode_lsb(self) -> typing.Optional[int]: """ Address decoding LSB (counted from 0). >>> a = AddrMap() >>> a.decode_lsb is None True >>> a.add('A', 0x2000, '8 KB') AddrRange('A', 0x2000, '8 KB') >>> a.decode_lsb 13 >>> a.add('B', 0x5000, '1 KB') AddrRange('B', 0x5000, '1 KB') >>> a.decode_lsb 10 >>> a.add('C', 0x6000, '2 KB') AddrRange('C', 0x6000, '2 KB') >>> a.decode_lsb 10 """ addrranges = self.__addrranges if addrranges: minsize = int(min(addrrange.size for addrrange in addrranges)) return calc_lowest_bit_set(minsize) return None @property def decode_msb(self) -> typing.Optional[int]: """ Address decoding MSB (counted from 0). >>> a = AddrMap(addrwidth=32) >>> a.is_sub False >>> a.decode_msb 31 >>> a.add('A', 0x5000, '4 KB') AddrRange('A', 0x00005000, '4 KB', addrwidth=32) >>> a.decode_msb 31 >>> a = AddrMap(is_sub=True) >>> a.is_sub True >>> a.decode_msb is None True >>> a.add('A', 0x1000, '1 KB') AddrRange('A', 0x1000, '1 KB', is_sub=True) >>> a.decode_msb 12 >>> a.add('B', 0x2000, '8 KB') AddrRange('B', 0x2000, '8 KB', is_sub=True) >>> a.decode_msb 13 >>> a.add('C', 0x4000, 1) AddrRange('C', 0x4000, '1 byte', is_sub=True) >>> a.decode_msb 14 """ eff_addrwidth = self.eff_addrwidth if eff_addrwidth: return eff_addrwidth - 1 return None @property def decode_slice(self): """ Address Slice. >>> a = AddrMap() >>> a.decode_slice is None True >>> a.add('A', 0x1000, '4 KB') AddrRange('A', 0x1000, '4 KB') >>> a.decode_slice Slice('12') >>> a.add('B', 0x3000, '8 KB') AddrRange('B', 0x3000, '8 KB') >>> a.decode_slice Slice('14:12') """ if not self.__addrranges: return None return Slice(left=self.decode_msb, right=self.decode_lsb) @property def is_full(self): """ Return if entire address map range is covered. >>> a = AddrMap() >>> a.is_full is None True >>> a.add('A', size='8 KB') AddrRange('A', 0x0, '8 KB') >>> a.is_full True >>> a.add('B', size='4 KB') AddrRange('B', 0x2000, '4 KB') >>> a.is_full False >>> a.add('C', size='4 KB') AddrRange('C', 0x3000, '4 KB') >>> a.is_full True >>> b = AddrMap(addrwidth=13) >>> b.is_full is None True >>> b.add('A', 0x0, size='6 KB') AddrRange('A', 0x0000, '6 KB', addrwidth=13) >>> b.is_full False >>> b.add('B', size='2 KB') AddrRange('B', 0x1800, '2 KB', addrwidth=13) >>> b.is_full True """ if not self.__addrranges: return None used = sum(addrrange.size for addrrange in self.__addrranges) return used == 1 << self.eff_addrwidth def __add(self, addrrange: AddrRange) -> None: """Insert new AddrRange into AddrMap.""" addrranges = self.__addrranges baseaddr = addrrange.baseaddr # address space check addrwidth = self.addrwidth if addrwidth is not None: addrspace = 1 << addrwidth if addrrange.endaddr >= addrspace: raise RuntimeError(f"{addrrange!r}: exceeds maximum address range of 0x{addrspace:X}.") # add at proper position for pos, item in enumerate(addrranges): if item.baseaddr > baseaddr: break else: pos = len(addrranges) # check neighbors for overlap if not self.allow_overlapping: # lower try: lower = addrranges[pos - 1] except IndexError: pass else: if addrrange.is_overlapping(lower): raise Conflict(addrrange, lower) # upper try: upper = addrranges[pos] except IndexError: pass else: if addrrange.is_overlapping(upper): raise Conflict(addrrange, upper) self.__addrranges.insert(pos, addrrange) def __get(self, default): last = 0 if default is None: yield from self.__addrranges else: for addrrange in self.__addrranges: baseaddr = addrrange.baseaddr if last < baseaddr: yield AddrRange(baseaddr=last, size=baseaddr - last, addrwidth=self.addrwidth, item=default) yield addrrange last = addrrange.endaddr + 1 eff_addrwidth = self.eff_addrwidth if eff_addrwidth: end = 1 << eff_addrwidth if last < end: yield AddrRange(baseaddr=last, size=end - last, addrwidth=self.addrwidth, item=default) def __new(self, addrranges: typing.List[AddrRange]) -> "AddrMap": addrmap = AddrMap(addrwidth=self.addrwidth, is_sub=self.is_sub) addrranges = addrranges or [] addrmap.cp_addrranges(addrranges) return addrmap def __cut(self, addrrange) -> typing.List[AddrRange]: addrranges = self.__addrranges idxs = [] cuts = [] for idx, cut in enumerate(addrranges): if cut.is_overlapping(addrrange): idxs.append(idx) cuts.append(cut) if idxs: first = cuts[0] last = cuts[-1] lastaddr = self.lastaddr # remove for idx in reversed(idxs): addrranges.pop(idx) # re-add left overlap if addrrange.baseaddr: left = first.get_difference(addrrange) if left: self.__add(left[0]) # re-add right overlap if lastaddr > addrrange.endaddr: right = last.get_difference(addrrange) if right: self.__add(right[0]) # shrink cuts cuts[0] = first.get_intersect(addrrange) if first is not last: cuts[-1] = last.get_intersect(addrrange) return cuts def __match(self, addrrange: "AddrRange"): return [a for a in self if a.is_overlapping(addrrange)] @staticmethod def _align(addr, align): addr = int(addr) misalign = addr % align if misalign: addr += align - misalign return Hex(addr) def _find_space(self, size, align, start=0): addr = start = self._align(start, align) for item in self.__addrranges: if item.endaddr < start: # skip all before start addr = max(self._align(item.nextaddr, align), addr) continue if item.baseaddr >= (addr + size): break addr = self._align(item.nextaddr, align) return addr def _check_end(self, addr, size): if self.addrwidth is not None: addrspace = 1 << self.addrwidth if addr >= addrspace or addr + size > addrspace: raise ValueError( f"No space left in address map ({Bytes(addrspace)}) for new range at {addr} with size of {size}" )
[docs] def cp_addrranges(self, addranges: typing.List[AddrRange]) -> None: """Copy address ranges from `other` AddrMap.""" for addrrange in addranges: self.__add(addrrange)
# pylint: disable=too-many-arguments
[docs] def add( self, item, baseaddr: typing.Optional[int] = None, size: int = 1, is_sub: typing.Optional[bool] = None, align: typing.Optional[int] = None, startsearch: typing.Optional[int] = None, ) -> AddrRange: """ Add `item` for address decoding. Args: item: any object managed by this address decoder Keyword Args: baseaddr: Base address. Behind last item by default. size: Size in bytes is_sub: Overwrite sub address attribute. align: Alignment startsearch: Search of free address at given number (if `baseaddr` is `None`) >>> a = AddrMap() >>> a.add('A', baseaddr=0x3000, size='4 KB') AddrRange('A', 0x3000, '4 KB') >>> a.add('B', 0x1000, '1 KB', is_sub=True) AddrRange('B', 0x1000, '1 KB', is_sub=True) >>> a.add('C', 0x4500, '4 KB', align=0x1000) AddrRange('C', 0x5000, '4 KB') """ ba_ = baseaddr or self.get_free_baseaddr(size, align=align, start=startsearch) if align is not None: ba_ = self._align(ba_, align) if is_sub is None: is_sub = self.is_sub addrrange = AddrRange(baseaddr=ba_, size=size, addrwidth=self.addrwidth, item=item, is_sub=is_sub) self.__add(addrrange) return addrrange
[docs] def get_free_baseaddr(self, size: int, align=None, start=None) -> int: """ Return baseaddress of free window with `size`. Args: size: Window Size Keyword Args: align: Alignment, default aligned to size start: Start search behind given address >>> a = AddrMap(is_sub=True, addrwidth=16) >>> a.get_free_baseaddr(0x10, start=0x0) Hex('0x0') >>> a.add('A', 0x1000, '1 KB') AddrRange('A', 0x1000, '1 KB', addrwidth=16, is_sub=True) >>> a.get_free_baseaddr(0x400) Hex('0x1400') >>> a.get_free_baseaddr('4 KB') Hex('0x2000') >>> a.get_free_baseaddr('8 KB', align=0x4000) Hex('0x4000') >>> a.get_free_baseaddr('1 KB', start=0x400) Hex('0x400') >>> a.get_free_baseaddr('1 KB', start=0x4000) Hex('0x4000') >>> a.add('B', 0x3000, '4 KB') AddrRange('B', 0x3000, '4 KB', addrwidth=16, is_sub=True) >>> a.get_free_baseaddr('4 KB', start=0x2800) Hex('0x4000') If the AddrMap has `addrwidth` set, a check for end range overflow is executed >>> a.get_free_baseaddr('8 KB', start=0xF0000) Traceback (most recent call last): ... ValueError: No space left in address map (64 KB) for new range at 0xF0000 with size of 8 KB >>> a.get_free_baseaddr('8 KB', align=0x1000, start=0xF000) Traceback (most recent call last): ... ValueError: No space left in address map (64 KB) for new range at 0xF000 with size of 8 KB """ size = Bytes(size) if align is None: align = size if start is None: addr = self.lastaddr or 0 if addr: addr += 1 addr = self._align(addr, align) else: addr = self._find_space(size, align, start=start) self._check_end(addr, size) return addr
[docs] def get(self, default=None): """ Return all :any:`AddrRange` items and fill gaps with `default`. >>> a = AddrMap() >>> a.add('T', size=0x400) AddrRange('T', 0x0, '1 KB') >>> a.add('A', 0x5000, 0x1000) AddrRange('A', 0x5000, '4 KB') >>> a.add('B', 0x2000, 0x1000) AddrRange('B', 0x2000, '4 KB') >>> a.add('C', 0xD000, '2 KB') AddrRange('C', 0xD000, '2 KB') >>> a.add('Z', size=0x2000) AddrRange('Z', 0xE000, '8 KB') >>> for addrrange in a.get(): ... addrrange AddrRange('T', 0x0, '1 KB') AddrRange('B', 0x2000, '4 KB') AddrRange('A', 0x5000, '4 KB') AddrRange('C', 0xD000, '2 KB') AddrRange('Z', 0xE000, '8 KB') >>> for addrrange in a.get(default='reserved'): ... addrrange AddrRange('T', 0x0, '1 KB') AddrRange('reserved', 0x400, '7 KB') AddrRange('B', 0x2000, '4 KB') AddrRange('reserved', 0x3000, '8 KB') AddrRange('A', 0x5000, '4 KB') AddrRange('reserved', 0x6000, '28 KB') AddrRange('C', 0xD000, '2 KB') AddrRange('reserved', 0xD800, '2 KB') AddrRange('Z', 0xE000, '8 KB') Empty: >>> a = AddrMap() >>> list(a.get()) [] >>> list(a.get(default="<default>")) [] >>> a = AddrMap(addrwidth=16) >>> list(a.get(default="<default>")) [AddrRange('<default>', 0x0000, '64 KB', addrwidth=16)] Default items before and after real AddrRange: >>> a.add('A', 0x5000, 0x1000) AddrRange('A', 0x5000, '4 KB', addrwidth=16) >>> for addrrange in a.get(default='reserved'): ... addrrange AddrRange('reserved', 0x0000, '20 KB', addrwidth=16) AddrRange('A', 0x5000, '4 KB', addrwidth=16) AddrRange('reserved', 0x6000, '40 KB', addrwidth=16) >>> a.add('Z', 0xE000, size=0x2000) AddrRange('Z', 0xE000, '8 KB', addrwidth=16) >>> for addrrange in a.get(default='reserved'): ... addrrange AddrRange('reserved', 0x0000, '20 KB', addrwidth=16) AddrRange('A', 0x5000, '4 KB', addrwidth=16) AddrRange('reserved', 0x6000, '32 KB', addrwidth=16) AddrRange('Z', 0xE000, '8 KB', addrwidth=16) """ return self.__get(default)
[docs] def cut(self, baseaddr, size=1) -> "AddrMap": """ Cut out address range from `baseaddr` for `size`. Return address map with cut address ranges. >>> a = AddrMap() >>> a.add('A', 0x0000, 0x1000) AddrRange('A', 0x0, '4 KB') >>> a.add('B', 0x1000, 0x1000) AddrRange('B', 0x1000, '4 KB') >>> a.add('C', 0x2000, 0x1000) AddrRange('C', 0x2000, '4 KB') >>> a.add('D', 0x3000, 0x1000) AddrRange('D', 0x3000, '4 KB') >>> print(a.get_overview()) Baseaddr Size Sub Item ---------- ------------- ----- ------ 0x0 0x1000 (4 KB) No 'A' 0x1000 0x1000 (4 KB) No 'B' 0x2000 0x1000 (4 KB) No 'C' 0x3000 0x1000 (4 KB) No 'D' Cut multiple address ranges >>> c = a.cut(0x1800, 0x1000) >>> print(a.get_overview()) Baseaddr Size Sub Item ---------- ------------- ----- ------ 0x0 0x1000 (4 KB) No 'A' 0x1000 0x800 (2 KB) No 'B' 0x2800 0x800 (2 KB) No 'C' 0x3000 0x1000 (4 KB) No 'D' >>> print(c.get_overview()) Baseaddr Size Sub Item ---------- ------------ ----- ------ 0x1800 0x800 (2 KB) No 'B' 0x2000 0x800 (2 KB) No 'C' Cut at the front >>> c = a.cut(0, 0x1000) >>> print(a.get_overview()) Baseaddr Size Sub Item ---------- ------------- ----- ------ 0x1000 0x800 (2 KB) No 'B' 0x2800 0x800 (2 KB) No 'C' 0x3000 0x1000 (4 KB) No 'D' >>> print(c.get_overview()) Baseaddr Size Sub Item ---------- ------------- ----- ------ 0x0 0x1000 (4 KB) No 'A' Cut at the end >>> c = a.cut(0x3C00, 0x1000) >>> print(a.get_overview()) Baseaddr Size Sub Item ---------- ------------ ----- ------ 0x1000 0x800 (2 KB) No 'B' 0x2800 0x800 (2 KB) No 'C' 0x3000 0xC00 (3 KB) No 'D' >>> print(c.get_overview()) Baseaddr Size Sub Item ---------- ------------ ----- ------ 0x3C00 0x400 (1 KB) No 'D' Cut exactly one >>> c = a.cut(0x2800, '2 KB') >>> print(a.get_overview()) Baseaddr Size Sub Item ---------- ------------ ----- ------ 0x1000 0x800 (2 KB) No 'B' 0x3000 0xC00 (3 KB) No 'D' >>> print(c.get_overview()) Baseaddr Size Sub Item ---------- ------------ ----- ------ 0x2800 0x800 (2 KB) No 'C' Cut nothing >>> c = a.cut(0x2000, 0x100) >>> print(a.get_overview()) Baseaddr Size Sub Item ---------- ------------ ----- ------ 0x1000 0x800 (2 KB) No 'B' 0x3000 0xC00 (3 KB) No 'D' >>> print(c.get_overview()) Baseaddr Size Sub Item ---------- -------- ----- ------ """ addrrange = AddrRange(baseaddr=baseaddr, size=size, addrwidth=self.addrwidth) return self.__new(self.__cut(addrrange))
[docs] def lookup(self, addr: int): """ Lookup address range by `addr`. >>> a = AddrMap() >>> a.add('A', 0x5000, 0x1000) AddrRange('A', 0x5000, '4 KB') >>> a.add('B', 0x2000, 0x1000) AddrRange('B', 0x2000, '4 KB') >>> a.add('C', 0xD000, 0x800) AddrRange('C', 0xD000, '2 KB') >>> a.lookup(0x2000) AddrRange('B', 0x2000, '4 KB') >>> a.lookup(0x2FFF) AddrRange('B', 0x2000, '4 KB') >>> a.lookup(0x3000) """ for addrrange in self: if addr in addrrange: return addrrange return None
[docs] def match(self, baseaddr: int, size: int = 1) -> "AddrMap": """ Return address map with address ranges intersecting with range from `baseaddr` for `size`. >>> a = AddrMap() >>> a.add('A', 0x1000, 0x1000) AddrRange('A', 0x1000, '4 KB') >>> a.add('B', 0x2000, 0x1000) AddrRange('B', 0x2000, '4 KB') >>> a.add('C', 0x3000, 0x800) AddrRange('C', 0x3000, '2 KB') >>> m = a.match(0x1800, 0x1000) >>> m AddrMap() >>> list(m) [AddrRange('A', 0x1000, '4 KB'), AddrRange('B', 0x2000, '4 KB')] """ addrrange = AddrRange(baseaddr, size=size, addrwidth=self.addrwidth) return self.__new(self.__match(addrrange))
[docs] def match_addrrange(self, addrrange: "AddrRange") -> "AddrMap": """ Return address map with address ranges intersecting with range from `baseaddr` for `size`. >>> a = AddrMap() >>> a.add('A', 0x1000, 0x1000) AddrRange('A', 0x1000, '4 KB') >>> a.add('B', 0x2000, 0x1000) AddrRange('B', 0x2000, '4 KB') >>> a.add('C', 0x3000, 0x800) AddrRange('C', 0x3000, '2 KB') >>> m = a.match_addrrange(AddrRange(0x1800, 0x1000)) >>> m AddrMap() >>> list(m) [AddrRange('A', 0x1000, '4 KB'), AddrRange('B', 0x2000, '4 KB')] """ return self.__new(self.__match(addrrange))
[docs] def find(self, item) -> "AddrMap": """ Return address map with address ranges containing `item`. >>> a = AddrMap() >>> a.add('A', 0x5000, 0x1000) AddrRange('A', 0x5000, '4 KB') >>> a.add('B', 0x2000, 0x1000) AddrRange('B', 0x2000, '4 KB') >>> a.add('A', 0xD000, 0x800) AddrRange('A', 0xD000, '2 KB') >>> list(a.find('A')) [AddrRange('A', 0x5000, '4 KB'), AddrRange('A', 0xD000, '2 KB')] """ return self.__new([addrrange for addrrange in self if addrrange.item == item])
[docs] def get_overview(self) -> str: """ Return overview table. >>> a = AddrMap() >>> a.add('A', 0x5000, 0x1000) AddrRange('A', 0x5000, '4 KB') >>> a.add('B', 0x2000, 0x1000) AddrRange('B', 0x2000, '4 KB') >>> a.add('C', 0xD000, 0x800) AddrRange('C', 0xD000, '2 KB') >>> print(a.get_overview()) Baseaddr Size Sub Item ---------- ------------- ----- ------ 0x2000 0x1000 (4 KB) No 'B' 0x5000 0x1000 (4 KB) No 'A' 0xD000 0x800 (2 KB) No 'C' """ headers = ("Baseaddr", "Size ", "Sub", "Item") matrix = [] for addrrange in self: matrix.append( ( str(addrrange.baseaddr), f"0x{addrrange.size:X} ({addrrange.size})", "Yes" if addrrange.is_sub else "No", repr(addrrange.item), ) ) return tabulate(matrix, headers=headers)
[docs]class Conflict(RuntimeError): """AddrMap Conflict Error.""" def __init__(self, one, other) -> None: self.one = one self.other = other super().__init__(f"{one!r}: overlaps with {other!r}.")