Skip to content

Commit

Permalink
Improve interrupt documentation (#1246)
Browse files Browse the repository at this point in the history
* Improve interrupt documentation, this includes an example

* Remove reference to tcl

* Display warning when interrupt exists but it is not associated to an interrupt controller

* Remove extra line

* Revert file

* Bugfix, create empty dictionaries to support single interrupt connected to PS

* Clarify for single interrupt designs
  • Loading branch information
mariodruiz committed Oct 8, 2021
1 parent 131c03c commit 592c06b
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 14 deletions.
Binary file added docs/source/images/interrupt_bd.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 6 additions & 10 deletions docs/source/overlay_design_methodology/overlay_design.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ overlay will be covered in the next sections.
Overlay HWH file
----------------

The HWH file is automatically generated from the Vivado IP Integrator block
design and it is used by PYNQ to automatically identify the Zynq system
configuration, IP including versions, interrupts, resets, and other control
signals.
The HWH (hardware handoff) file is automatically generated from the Vivado IP
Integrator block design and it is used by PYNQ to automatically identify the
Zynq system configuration, IP including versions, interrupts, resets, and other
control signals.
Based on this information, some parts of the system configuration can be
automatically modified from PYNQ, drivers can be automatically assigned,
features can be enabled or disabled, and signals can be connected to
Expand All @@ -42,15 +42,11 @@ The HWH file is automatically generated by Vivado when generating the bitstream.
The HWH file must be provided with the bitstream file as part of an overlay.
The PYNQ PL class will automatically parse the HWH file.

To generate the HWH for the Block Diagram from the Vivado GUI:

* Click **File > Export > Block Design**

Or, run the following in the Tcl console:
You can find the hwh file in the following directory

.. code-block:: console
write_bd_tcl
<prj>.gen/sources_1/bd/<bd_name>/hw_handoff/<bd_name>.hwh
The HWH filename should match the .bit filename. For example, `my_overlay.bit`
and `my_overlay.hwh`.
Expand Down
8 changes: 5 additions & 3 deletions docs/source/overlay_design_methodology/pspl_interface.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,16 @@ Interrupt

There are dedicated interrupts which are linked with asyncio events in
the python environment. To integrate into the PYNQ framework Dedicated
interrupts must be attached to an AXI Interrupt controller which is in turn
interrupts must be attached to an **AXI Interrupt Controller** which is in turn
attached to the first interrupt line to the processing system. If more than 32
interrupts are required then AXI interrupt controllers can be cascaded. This
arrangement leaves the other interrupts free for IP not controlled by PYNQ
directly such as SDSoC accelerators.
directly such as Vitis accelerators. The AXI Interrupt Controller can be avoided
for overlays with only one interrupt, in such overlays the interrupt pin must be
connected to the first interrupt line of the processing system.

Interrupts are managed by the Interrupt class, and the implementation is built
on top of *asyncio*, part of the Python standard library.
on top of *asyncio*, part of the Python standard library.


More information about using the Interrupt class can be found in the
Expand Down
171 changes: 171 additions & 0 deletions docs/source/pynq_libraries/interrupt.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,174 @@ for a desired value to be present.
self._interrupt.wait()
# Clear interrupt
self._mmio.write(IP_ISR, 0x1)
Interrupt Controller
--------------------

To integrate into the PYNQ framework Dedicated interrupts must be attached to an
`AXI Interrupt Controller <https://www.xilinx.com/products/intellectual-property/axi_intc.html#documentation>`_
which is in turn attached to the first interrupt line to the processing system.

The AXI Interrupt Controller can be avoided for overlays with only one
interrupt, in such overlays the interrupt pin must be connected to the
first interrupt line of the processing system.

PYNQ only support interrupts that are ultimately connected to IRQ_F2P[0].

An Example for the PYNQ Interrupt Subsystem
-------------------------------------------

The PYNQ ``Interrupt`` class is an asyncio-compatible interface to handling
interrupts from the fabric. This example aims to:

* Show how to create a block design compatible with PYNQ
* Introduce the ``asyncio`` interface and how to call it from other contexts
* Provide an example of the of the recommended way to write a driver
for existing IP


Hardware Design
^^^^^^^^^^^^^^^

In this example we are using two independent instances of the
`AXI Timer IP <https://www.xilinx.com/products/intellectual-property/axi_timer.html#documentation>`_
from the Xilinx IP library.

The PYNQ interrupt software layer is dependent on the hardware design
meeting the following restrictions

* All interrupts must ultimately be connected to the first interrupt
line of the ZYNQ block
* Multiple interrupts must be combined using AXI Interrupt controllers

This block design below shows the pattern of using a concat IP block to combine
all of the single interrupts into a single interrupt bus that then passed
into the input of both the interrupt controller and the processing system.

.. image:: images/interrupt_bd.png
:align: center

Exploring Interrupts in Software
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

With the hardware design complete we can start exploring the software
architecture. To do this first we load the new overlay

.. code-block:: Python
import pynq
ol = pynq.Overlay('timer_interrupts.bit')
We can get access to instances of the interrupt class by navigating the
overlay object. Each IP instances has a ``_interrupts`` dictionary which lists
the names of the interrupts

.. code-block:: Python
timer1 = ol.timer_1
timer1._interrupts
.. code-block:: Python
{'interrupt': {'controller': 'axi_intc_0', 'fullpath': 'timer_1/interrupt', 'index': 1}}
And the interrupts object can then be accessed by its name

.. code-block:: Python
interrupt = timer1.interrupt
The ``Interrupt`` class provides a single function wait which is an asyncio
coroutine that returns when the interrupt is signalled. To demonstrate this
we first need to look at the documentation for the timer and see how to get
it to fire after a specific period of time. We can also look at the register
map of the IP in Python to assist

.. code-block:: Python
timer1.register_map
.. code-block:: Python
RegisterMap {
TCSR0 = Register(MDT0=0, UDT0=1, GENT0=0, CAPT0=0, ARHT0=0, LOAD0=0, ENIT0=1, ENT0=1, T0INT=0, PWMA0=0, ENALL=0, CASC=0),
TLR0 = Register(TCLR0=500000000),
TCR0 = Register(TCR0=4294967295),
TCSR1 = Register(MDT1=0, UDT1=0, GENT1=0, CAPT1=0, ARHT1=0, LOAD1=0, ENIT1=0, ENT1=0, T1INT=0, PWMA1=0, ENALL=0),
TLR1 = Register(TCLR1=0),
TCR1 = Register(TCR1=0)
}
The programming steps for the timer are to do the following:

1. Load the value to count from in the TLR0 register
2. Set then clear the LOAD0 bit to trigger the load
3. Set the ENIT0 bit to enable the interrupt output
4. Set the UDT0 bit to get the timer to count down
5. Set the ENT0 bit start the timer

Once the interrupt is signalled we then need to write to the ``T0INT`` bit
to clear the interrupt.

We can package all of this into a coroutine as follows

.. code-block:: Python
async def wait_for_timer1(cycles):
timer1.register_map.TLR0 = cycles
timer1.register_map.TCSR0.LOAD0 = 1
timer1.register_map.TCSR0.LOAD0 = 0
timer1.register_map.TCSR0.ENIT0 = 1
timer1.register_map.TCSR0.ENT0 = 1
timer1.register_map.TCSR0.UDT0 = 1
await timer1.interrupt.wait()
timer1.register_map.TCSR0.T0INT = 1
To test this we need to use the `asyncio <https://docs.python.org/3/library/asyncio.html>`_
library to schedule our new coroutine. ``asyncio`` uses event loops to
execute coroutines. When python starts it will create a default event loop
which is what the PYNQ interrupt subsystem uses to handle interrupts.

.. code-block:: Python
import asyncio
loop = asyncio.get_event_loop()
.. code-block:: Python
task = loop.create_task(wait_for_timer1(500000000))
loop.run_until_complete(task)
The low-level details
^^^^^^^^^^^^^^^^^^^^^

To see what interrupts are in the system we can look at the ``interrupt_pins``
dictionary. Each entry is a mapping from the name of a pin in the block
diagram to the interrupt controller that manages it.

.. code-block:: Python
ol.interrupt_pins
.. code-block:: Python
{'pynq_interrupts/In0': {'controller': 'axi_intc_0', 'fullpath': 'pynq_interrupts/In0', 'index': 0},
'pynq_interrupts/In1': {'controller': 'axi_intc_0', 'fullpath': 'pynq_interrupts/In1', 'index': 1},
'timer_0/interrupt': {'controller': 'axi_intc_0', 'fullpath': 'timer_0/interrupt', 'index': 0},
'timer_1/interrupt': {'controller': 'axi_intc_0', 'fullpath': 'timer_1/interrupt', 'index': 1}}
This is a low level description of what’s going on but can be useful to
make sure that interrupts are being detected as intended.
At a slightly higher level, each entry in the IP dictionary contains
the subset of the complete dictionary applicable only to that IP.

.. code-block:: Python
ol.ip_dict['timer_0']['interrupts']
.. code-block:: Python
{'interrupt': {'controller': 'axi_intc_0', 'fullpath': 'timer_0/interrupt', 'index': 0}}
3 changes: 2 additions & 1 deletion pynq/pl_server/hwh_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ def add_ps_to_ip_dict(self, mod):
"""
full_name, vlnv, pars, _, _ = self.instance2attr[mod.get('INSTANCE')]
self.ip_dict[full_name] = {}
self.ip_dict[full_name]['gpio'] = dict()
self.ip_dict[full_name]['interrupts'] = dict()
self.ip_dict[full_name]['parameters'] = {j.get('NAME'):
j.get('VALUE')
for j in pars}
Expand Down Expand Up @@ -403,7 +405,6 @@ def init_mem_dict(self):
v['used'] = 1
del self.ip_dict[k]


def _add_interrupt_pins(self, net, parent, offset, raw_map=None):
net_pins = self.nets[net] if net else set()
for p in net_pins:
Expand Down

0 comments on commit 592c06b

Please sign in to comment.