Python Overlay API¶
The Python API is the user interface for the overlay, exposing the programmable functionality in the design.
An API for a PYNQ overlay can consist of
- a simple Python wrapper that interfaces directly with the hardware IP blocks in the overlay
- a more substantial Python layer utilising other Python packages
- a Python library that interfaces to a lower level higher performance library (written in C/C++ for example) to control the overlay
The API for an overlay will manage the transfer of data between the Python environment in the PS, and the overlay in the PL. This may be the transfer of data directly from the PS to a peripheral or managing system memory to allow a peripheral in the PL to read or write data from DRAM that can also be accessed from the Python environment.
The Default API¶
When and Overlay is loaded using the pynq.overlay.Overlay
function
all of the IP and hierarchies in the overlay will have drivers assigned to
them and used to construct an object hierarchy. The IP can then be accessed
via attributes on the returned overlay class using the names of the IP and
hierarchies in the block diagram.
If no driver has been specified for a type of IP then a
pynq.overlay.DefaultIP
will be instantiated offering read
and
write
functions to access the IP’s address space and named accessors to
any interrupts or GPIO pins connected to the IP. Hierarchies, likewise will be
instances of pynq.overlay.DefaultHierarchy
offering
access to any sub hierarchies or contained IP. The top-level Overlay
also acts just like any other IP.
Customising Drivers¶
While the default drivers are useful for getting started with new hardware in a
design it is preferable to have a higher level driver for end users to interact
with. Each of pynq.overlay.DefaultIP
,
pynq.overlay.DefaultHierarchy
and pynq.overlay.Overlay
can be
subclassed and automatically bound to elements of the block diagram. New drivers
will only be bound when the overlay is reloaded.
Creating IP Drivers¶
All IP drivers should inherit from pynq.overlay.DefaultIP
and include
a bindto
class attribute consisting of an array of strings. Each string
should be a type of IP that the driver should bind to. It is also strongly
recommend to call super().__init__
in the class’s constructor. The type of
an IP can be found as the VLNV parameter in Vivado or from the ip_dict
of the overlay.
A template for an IP driver is as follows:
from pynq import DefaultIP
class MyIP(DefaultIP):
bindto = ['My IP Type']
def __init__(self, description):
super().__init__(description)
Creating Hierarchy Drivers¶
Hierarchy drivers should inherit from pynq.overlay.DefaultHierarchy
and provide a static method pynq.overlay.DefaultHierarchy.checkhierarchy()
that takes a description and returns True
if the driver can bind to it.
Any class that meets these two requirements will be automatically added to a
list of drivers tested against each hierarchy in a newly loaded overlay.
A template for a hierarchy driver is as follows:
from pynq import DefaultHierarchy
class MyHierarchy(DefaultHierarchy):
def __init__(self, description):
super().__init__(description)
@staticmethod
def checkhierarchy(description):
return False
Creating Custom Overlay Classes¶
Finally the class changed from the pynq.overlay.Overlay
to provide a
more suitable high-level API or provide overlay-specific initialisation.
The overlay loader will look for a python file located alongside the
bitstream and HWH files, import it and then call the Overlay
function.
A template for a custom overlay class is as follows:
from pynq import Overlay
class MyOverlay(Overlay):
def __init__(self, bitfile_name, download):
super().__init__(bitfile_name, download)
# Other initialisation
Overlay = MyOverlay
Working with Physically Contiguous Memory¶
In many applications there is a need for large buffers to be transferred
between the PS and PL either using DMA engines or HLS IP with AXI master
interfaces. In PYNQ the pynq.buffer.allocate()
function provides a
mechanism to acquire numpy arrays allocated as to be physically contiguous.
The allocate function takes shape
and dtype
parameters in a similar
way to other numpy construction functions.
from pynq import allocate
matrix1 = allocate(shape=(32,32), dtype='f4')
These arrays can either be passed directly to the DMA driver’s transfer
function or they contain a physical_address
attribute which can be used by
custom driver code.
When the array is no longer needed the underlying resources should be freed
using the freebuffer
function. Alternatively a context manager can be used
to ensure that the buffer is freed at the end of a scope.
with allocate(shape=(32,32), dtype=np.float32) as matrix2:
dma.sendchannel.transfer(matrix2)
dma.recvchannel.transfer(matrix1)
dma.sendchannel.wait()
dma.recvchannel.wait()
matrix1.freebuffer()