Source code for pynq.mmio

#   Copyright (c) 2016, Xilinx, Inc.
#   SPDX-License-Identifier: BSD-3-Clause

import mmap
import os
import warnings

import numpy as np

import pynq._3rdparty.tinynumpy as tnp



class _AccessHook:
    def __init__(self, baseaddress, device):
        self.baseaddress = baseaddress
        self.device = device

    def read(self, offset, length):
        return self.device.read_registers(self.baseaddress + offset, length)

    def write(self, offset, data):
        self.device.write_registers(self.baseaddress + offset, data)


[docs]class MMIO: """This class exposes API for MMIO read and write. Attributes ---------- base_addr : int The base address, not necessarily page aligned. length : int The length in bytes of the address range. array : numpy.ndarray A numpy view of the mapped range for efficient assignment device : Device A device that can interact with the PL server. """ def __init__(self, base_addr, length=4, device=None, **kwargs): """Return a new MMIO object. Parameters ---------- base_addr : int The base address of the MMIO. length : int The length in bytes; default is 4. device: Device The device that MMIO object is created for. """ if "debug" in kwargs: warnings.warn("Keyword debug has been deprecated.", DeprecationWarning) if device is None: from .pl_server.device import Device device = Device.active_device self.device = device if base_addr < 0 or length < 0: raise ValueError("Base address or length cannot be negative.") self.base_addr = base_addr self.length = length if self.device.has_capability("MEMORY_MAPPED"): self.read = self.read self.write = self.write_mm self.array = self.device.mmap(base_addr, length) elif self.device.has_capability("REGISTER_RW"): self.read = self.read self.write = self.write_reg self._hook = _AccessHook(self.base_addr, self.device) self.array = tnp.ndarray(shape=(length // 4,), dtype="u4", hook=self._hook) else: raise ValueError("Device does not have capabilities for MMIO")
[docs] def read(self, offset=0, length=4, word_order="little"): """The method to read data from MMIO. For the `word_order` parameter, it is only effective when operating 8 bytes. If it is `little`, from MSB to LSB, the bytes will be offset+4, offset+5, offset+6, offset+7, offset+0, offset+1, offset+2, offset+3. If it is `big`, from MSB to LSB, the bytes will be offset+0, offset+1, ..., offset+7. This is different than the byte order (endianness); notice the endianness has not changed. Parameters ---------- offset : int The read offset from the MMIO base address. length : int The length of the data in bytes. word_order : str The word order of the 8-byte reads. Returns ------- list A list of data read out from MMIO """ if length not in [1, 2, 4, 8]: raise ValueError( "MMIO currently only supports " "1, 2, 4 and 8-byte reads." ) if offset < 0: raise ValueError("Offset cannot be negative.") if length == 8 and word_order not in ["big", "little"]: raise ValueError("MMIO only supports big and little endian.") idx = offset >> 2 if offset % 4: raise MemoryError("Unaligned read: offset must be multiple of 4.") # Read data out lsb = int(self.array[idx]) if length == 8: if word_order == "little": return ((int(self.array[idx + 1])) << 32) + lsb else: return (lsb << 32) + int(self.array[idx + 1]) else: return lsb & ((2 ** (8 * length)) - 1)
[docs] def write_mm(self, offset, data): """The method to write data to MMIO. Parameters ---------- offset : int The write offset from the MMIO base address. data : int / bytes The integer(s) to be written into MMIO. Returns ------- None """ if offset < 0: raise ValueError("Offset cannot be negative.") idx = offset >> 2 if offset % 4: raise MemoryError("Unaligned write: offset must be multiple of 4.") if type(data) is int: self.array[idx] = np.uint32(data) elif type(data) is bytes: length = len(data) num_words = length >> 2 if length % 4: raise MemoryError("Unaligned write: data length must be multiple of 4.") buf = np.frombuffer(data, np.uint32, num_words, 0) for i in range(len(buf)): self.array[idx + i] = buf[i] else: raise ValueError("Data type must be int or bytes.")
[docs] def write_reg(self, offset, data): """The method to write data to MMIO. Parameters ---------- offset : int The write offset from the MMIO base address. data : int / bytes The integer(s) to be written into MMIO. Returns ------- None """ if offset < 0: raise ValueError("Offset cannot be negative.") idx = offset >> 2 if offset % 4: raise MemoryError("Unaligned write: offset must be multiple of 4.") if type(data) is int: self.array[idx] = data elif type(data) is bytes: self._hook.write(offset, data) else: raise ValueError("Data type must be int or bytes.")