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