Last week, after we looked at how we could use the AXI IIC IP with PYNQ, I got a question from a reader asking how we work with interrupts in PYNQ. The answer is that much of the complex interrupt handling like VDMA is handled automatically.
However, there are situations that need to be handled by the application running on the PS such as fabric interrupts from custom IP in the PL.
To demonstrate how interrupts from the PL to the PS can be addressed in PYNQ, I put together a simple hardware project which connects the push button switches and the four LEDs to the processor system using AXI GPIO. For this project, I again targeted the PYNQ-Z1 and added in the AXI GPIO modules. I ensured that the interrupt option was enabled on the AXI GPIO which was connected to the push button and switch inputs.
This interrupt was connected to a AXI interrupt controller which has been configured to raise a level interrupt versus an edge when triggered.
The final bit stream image capture can be seen below.
Once the bit stream was generated, I transferred the Hardware Handoff File and bit stream to a new folder on the PYNQ board.
The first thing I did in the PYNQ environment was create a new notebook and download the overlay just created.
Once this was completed using a terminal window, I checked the interrupts present in the command cat proc/interrupts. This showed a level triggered fabric interrupt.
Now we can start creating the application and use the push button switches to generate the interrupt. When the interrupt occurs, we will toggle the LED status.
To make use of the interrupt, we use the asyncio features of Python. Asyncio enables us to work with several peripherals without blocking execution of the overall application. At its heart, asyncio uses four structures:
Coroutines – These are functions that can halt execution at a given point in time until a task or event occurs. Two new keywords were introduced to do this: async is typically used to define the coroutine; and await is the point at which the program pauses execution.
Futures – Futures encapsulate pending operations so they can be put in queues. Their state of completion can be queried and their results can be retrieved when ready.
Task – Tasks contain coroutines which are registered with an asyncio event loop.
Event loop – The event loop executes all of the ready tasks by polling suspended tasks and scheduling outstanding tasks.
To get started creating an interrupt-based system, we need to import the asyncio library, create coroutine for the interrupt handler, define the future for the coroutine, and create the event loop.
If we want to see what interrupts are enabled in a current overlay, we would use the following command:
In this instance, it shows interrupt from the GPIO as connected to the interrupt controller.
If we are working with a custom IP block in the PL which drives a fabric interrupt, we can then access the interrupt pin using the following command:
intr_inst = Interrupt('axi_gpio_0/ip2intc_irpt')
We do not need to do this when we use the GPIO because the AXI GPIO already has a coroutine available for interrupts.
We then need to define our own coroutine which waits for the actions on the buttons and then toggles the LEDS. We do this using the async keyword.
async def handle(): await sw.wait_for_interrupt_async() #await intr_inst.wait() leds[0:3].toggle()
The next step is to create the handler for the task using the following future:
handler_task = asyncio.ensure_future(handle())
Finally, we can run the event loop. In this case, it will run until the handler task completes but we could make it run continually if we wanted to.
As we run this, we should see the LEDS toggle on the PYNQ-Z1 board. We should also be able to see the number of interrupt events occurring if we observe the processor interrupts again.
In the above image, notice that the interrupt has occurred seven times while I tested the code for this blog.
The complete notebook can be seen below.
Now we know how we can work with interrupts in the PYNQ environment.