# Zynq PS7 Interrupts

**URL:** <https://discuss.pynq.io/t/zynq-ps7-interrupts/198>\
**Category:** Support\
**Created:** [June 10, 2019, 2:54pm UTC](https://discuss.pynq.io/t/zynq-ps7-interrupts/198 "2019-06-10T14:54:18Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![gpcus](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.pynq.io/gpcus/32/54_2.png) [@gpcus](https://discuss.pynq.io/u/gpcus)\
**Post date:** [June 10, 2019, 2:54pm UTC](https://discuss.pynq.io/t/zynq-ps7-interrupts/198/1 "2019-06-10T14:54:18Z")

</div>

Hello there,  
PYNQ PL interrupts handling isn’t very clear to me. I went through the documentation, examples and even an external blog, but I do not understand how it works, at all.  
My requirement is very simple: when an AXI peripheral raise an interrupt, I want to print something (possibly an incrementing counter and a timestamp, when it works…).

- I connected my peripheral interrupt line to IRQ\_F2P[15] in the block design.

- I run PL.interrupt\_pins and it seems the interrupt line is where it should be:  
 ![image](https://us1.discourse-cdn.com/flex019/uploads/pynq1/original/1X/57e691a48c88ba3985980b6a4b89f709c3a50e14.png)

- How do I get my notebook to print a timestamp every time the interrupt is raised?  
I copied and pasted this from the documentation:

```auto
async def interrupt_handler_async(self, value):
    if self.iop.interrupt is None:
        raise RuntimeError('Interrupts not available in this Overlay')
    while(1):
        await self.iop.interrupt.wait() # Wait for interrupt
        # Do something when an interrupt is received
        print("something")
        self.iop.interrupt.clear()
```

And then I did:

```auto
interrupt_handler_async("int_handler", 15)
```

Is that right? All I got is:

```auto
<coroutine object interrupt_handler_async at 0xafdea630>
```

But no printouts…

---

<div class="post-metadata">

**Author:** ![PeterOgden](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.pynq.io/peterogden/32/55_2.png) [@PeterOgden](https://discuss.pynq.io/u/PeterOgden)\
**Post date:** [June 11, 2019, 12:43pm UTC](https://discuss.pynq.io/t/zynq-ps7-interrupts/198/2 "2019-06-11T12:43:01Z")

</div>

Interrupts are an area that is causing confusing and I’m working on creating some better documentation for them. I’ve also noticed some bugs in the code that can potentially cause spurious interrupt responses that I’m working to fix. In the meantime, I’ve pasted below an example I’m working on in the hope it can be of use to you - just remember it is a work in progress.

One thing I will highlight relevant to your example is that we only support interrupts that are ultimately connected to IRQ\_F2P[0] - this is due to how the Linux drivers are configured in the stock image.

Peter

* * *

# An Example for the PYNQ Interrupt Subsystem

The PYNQ `Interrupt` class is an asyncio-compatible interface to handling interrupts from the fabric. This notebook 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 example of the recommended way to write a driver for existing IP

## Hardware design

In this example we are going to use the AXI Timer IP from the Xilinx IP library ([product guide](https://www.xilinx.com/support/documentation/ip_documentation/axi_timer/v2_0/pg079-axi-timer.pdf)) putting two independent timers in the fabric.

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

As we have two timers we will also need an interrupt controller to combine them into a single interrupt line into the PS. The result is a block diagram as follows.

 ![interrupt_example](https://us1.discourse-cdn.com/flex019/uploads/pynq1/original/1X/372cc8b0b89e0f9f15eead6406c6a62a3cb208c8.png)

This block design 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. For more details on how to construct a block diagram see (…)

## 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

```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

```python
timer1 = ol.timer_1
timer1._interrupts

```

```
{'interrupt': {'controller': 'axi_intc_0',
  'fullpath': 'timer_1/interrupt',
  'index': 1}}

```

And the interrupts object can then be accessed by its name

```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

```python
timer1.register_map

```

```
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

```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 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.

```python
import asyncio

loop = asyncio.get_event_loop()

```

```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.

```python
ol.interrupt_pins

```

```
{'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.

```python
ol.ip_dict['timer_0']['interrupts']

```

```
{'interrupt': {'controller': 'axi_intc_0',
  'fullpath': 'timer_0/interrupt',
  'index': 0}}

```

---

<div class="post-metadata">

**Author:** ![gpcus](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.pynq.io/gpcus/32/54_2.png) [@gpcus](https://discuss.pynq.io/u/gpcus)\
**Post date:** [June 11, 2019, 1:41pm UTC](https://discuss.pynq.io/t/zynq-ps7-interrupts/198/3 "2019-06-11T13:41:55Z")

</div>

Thanks, I’ll try to understand better the “ **Exploring interrupts in software** ” part that is the one most relevant to my current problem… The hardware part is OK.

---

<div class="post-metadata">

**Author:** ![gpcus](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.pynq.io/gpcus/32/54_2.png) [@gpcus](https://discuss.pynq.io/u/gpcus)\
**Post date:** [June 14, 2019, 2:47pm UTC](https://discuss.pynq.io/t/zynq-ps7-interrupts/198/4 "2019-06-14T14:47:40Z")

</div>

I cannot get it working.  
This below is my hardware design, the highlighted line ends up in PS7 IRQ\_F2P[15:0]

 ![image](https://us1.discourse-cdn.com/flex019/uploads/pynq1/original/1X/d7be7ab51d6b03b18b1fd3923825efb27020378d.png)  
I attach, because the output is too long for a picture, the output of ol.ip\_dict and ol.interrupt\_pins.  
When I do the same as in your example, i.e.:

> [@PeterOgden](#):
>
> ```auto
> timer1 = ol.timer_1
> timer1._interrupts
> 
> ```
> 
> ```auto
> {'interrupt': {'controller': 'axi_intc_0',
> 'fullpath': 'timer_1/interrupt',
> 'index': 1}}
> 
> ```

I get:

 ![image](https://us1.discourse-cdn.com/flex019/uploads/pynq1/original/1X/6f338f024e418abdca63a0dadc80f9a351422dda.png)  
Where is my mistake?  
[ol\_ip\_dict.txt (48.3 KB)](https://discuss.pynq.io/uploads/short-url/7LMvYdF8msQ332ffs6xbsUzKjWd.txt)  
[ol\_interrupt\_pins.txt (2.6 KB)](https://discuss.pynq.io/uploads/short-url/FlF2uJ40kzGevAbiYnv5WUHG0c.txt)  
by the way, I tried even the following but without success:  
 ![image](https://us1.discourse-cdn.com/flex019/uploads/pynq1/original/1X/38a9063b4a82cb17ef3cfe4037ec20f2f93472da.png)

---

<div class="post-metadata">

**Author:** ![PeterOgden](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.pynq.io/peterogden/32/55_2.png) [@PeterOgden](https://discuss.pynq.io/u/PeterOgden)\
**Post date:** [June 14, 2019, 3:11pm UTC](https://discuss.pynq.io/t/zynq-ps7-interrupts/198/5 "2019-06-14T15:11:22Z")

</div>

It looks like our hwh parsing is getting confused by an IP with two AXI slave ports. Would you be able to share your hwh file so we can add it to our test suite?

You can instantiate the `pynq.Interrupt` class directly by passing it the name of the pin - `"BERT_0/event_irq"` in your case. Note that PYNQ can only interrupt with interrupts that ultimately attach to IRQ line 0 so this still may not work in your example. You can either move the interrupts around or change the UIO entry in device-tree to point to correct interrupt line - that should work but we haven’t tested it.

Peter

---

<div class="post-metadata">

**Author:** ![gpcus](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.pynq.io/gpcus/32/54_2.png) [@gpcus](https://discuss.pynq.io/u/gpcus)\
**Post date:** [June 14, 2019, 3:20pm UTC](https://discuss.pynq.io/t/zynq-ps7-interrupts/198/6 "2019-06-14T15:20:57Z")

</div>

Sure. Here it is.  
[ax\_top\_bd\_wrapper\_with\_ILA.zip (91.6 KB)](https://discuss.pynq.io/uploads/short-url/ekybQ3XcEd9zpb1GKe2FVHTwR5U.zip)  
**“Note that PYNQ can only interrupt with interrupts that ultimately attach to IRQ line 0”** …  
Isn’t that a huge limitation? How to deal with a multi-interrupted system?

---

<div class="post-metadata">

**Author:** ![PeterOgden](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.pynq.io/peterogden/32/55_2.png) [@PeterOgden](https://discuss.pynq.io/u/PeterOgden)\
**Post date:** [June 14, 2019, 3:28pm UTC](https://discuss.pynq.io/t/zynq-ps7-interrupts/198/7 "2019-06-14T15:28:06Z")

</div>

That’s why we recommend using AXI interrupt controllers to collapse all of the interrupts in the design down to a single interrupt line into the PS. The base overlay has 12 fabric interrupts accessible this way.

Peter

---

<div class="post-metadata">

**Author:** ![jancumps](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.pynq.io/jancumps/32/2140_2.png) [@jancumps](https://discuss.pynq.io/u/jancumps)\
**Post date:** [September 5, 2021, 1:50pm UTC](https://discuss.pynq.io/t/zynq-ps7-interrupts/198/8 "2021-09-05T13:50:13Z")

</div>

I tried this one and it mostly works.  
I have one unexpected behaviour: the first time this waits for 5 seconds:

```auto
task = loop.create_task(wait_for_timer1(500000000))
loop.run_until_complete(task)

```

Next time it returns immediately.  
3rd time it waits 5 seconds, … etc

I documented my attempt here: [https://www.element14.com/community/groups/fpga-group/blog/2021/09/05/learning-xilinx-zynq-interrupt-arm-from-fpga-fabric](https://www.element14.com/community/groups/fpga-group/blog/2021/09/05/learning-xilinx-zynq-interrupt-arm-from-fpga-fabric)

---

<div class="post-metadata">

**Author:** ![PeterOgden](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.pynq.io/peterogden/32/55_2.png) [@PeterOgden](https://discuss.pynq.io/u/PeterOgden)\
**Post date:** [September 6, 2021, 8:23am UTC](https://discuss.pynq.io/t/zynq-ps7-interrupts/198/9 "2021-09-06T08:23:44Z")

</div>

Looks like you’ve run into a bug/limitation of our API for handling interrupts. Unfortunately there’s no simple solution that comes to mind. I’ll have a think and see if there’s something we can add to the API to solve this issue.

Basically there’s a race condition between `wait()` returning and your code resetting the interrupt line which means that a second interrupt gets enqueued before you have a chance to clear it. There’s nothing you can do in your code to resolve this and it’s inevitable with the current API so we’ll need to add an extra feature in the next version.

Sorry about this

Peter

---

<div class="post-metadata">

**Author:** ![datMaffin](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.pynq.io/datmaffin/32/2462_2.png) [@datMaffin](https://discuss.pynq.io/u/datMaffin)\
**Post date:** [November 12, 2021, 12:51pm UTC](https://discuss.pynq.io/t/zynq-ps7-interrupts/198/10 "2021-11-12T12:51:45Z")

</div>

I was playing around with this example. I had the same issues as @jancumps .

I believe that the PYNQ implementation is correct/good enough. The main problem was that the block design automation decided to change the interrupt inputs to use the “level detection”, which results in an interrupt being sent immediately after clearing it because `timer1.register_map.TCSR0.T0INT = 1` is only set after the waiting is over. Therefore, during the wait operation, the interrupt will be triggered, PYNQ sees the new input and clears it, and lets `wait` continue. Now, because the interrupt line from the timer is still high and “level detection” is used, another interrupt is immediately triggered. And only afterwards are we able to disable the interrupt from the timer.

I then tested if changing the level detection to “edge” will fix the observed behavior, because only one interrupt should be triggered. This did indeed fix the behavior.

In conclusion, I believe that PYNQ does work as intended. When setting the interrupt controller to do “edge detection”, it works as expected.

I am, however, curious how one would try to work with “level detection” in PYNQ. There would probably be need a way to be able to execute some code right before the clear command is sent to the interrupt controller ([https://github.com/Xilinx/PYNQ/blob/master/pynq/interrupt.py#L235](https://github.com/Xilinx/PYNQ/blob/master/pynq/interrupt.py#L235) ).

Please mention/describe this issue regarding the level interrupt detection type in the newly added documentation:

> <https://github.com/Xilinx/PYNQ/blob/1997c6d16588c588220a996e0f6dfb3594ac96e6/docs/source/pynq_libraries/interrupt.rst>

* * *

By default it chooses level detection:

 ![image](https://us1.discourse-cdn.com/flex019/uploads/pynq1/original/2X/a/a14cdb32762aaa513bbcf11623d0acdf4f1cca20.png)

Had to set interrupt detection to use edge detection manually:

 ![image](https://us1.discourse-cdn.com/flex019/uploads/pynq1/original/2X/c/cfb59d34be47e738d075ca0fae7f985fe2bb8080.png)

Block diagram (as described in this example, I did use timers to generate interrupts):

 ![image](https://us1.discourse-cdn.com/flex019/uploads/pynq1/original/2X/4/47b5528ec3191a5216403c5aae0192fc68fc9cf0.png)

---

<div class="post-metadata">

**Author:** ![jancumps](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.pynq.io/jancumps/32/2140_2.png) [@jancumps](https://discuss.pynq.io/u/jancumps)\
**Post date:** [November 19, 2021, 8:00pm UTC](https://discuss.pynq.io/t/zynq-ps7-interrupts/198/11 "2021-11-19T20:00:52Z")

</div>

Makes sense. I am going to test this now …

---

<div class="post-metadata">

**Author:** ![jancumps](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.pynq.io/jancumps/32/2140_2.png) [@jancumps](https://discuss.pynq.io/u/jancumps)\
**Post date:** [November 19, 2021, 8:41pm UTC](https://discuss.pynq.io/t/zynq-ps7-interrupts/198/12 "2021-11-19T20:41:39Z")

</div>

Confirming that this setup works.

 ![image](https://us1.discourse-cdn.com/flex019/uploads/pynq1/original/2X/e/e4f37e0b62ca13009faa69d2d66b3035a4b20e86.png)

The interrupt now waits the expected time, each time.

I submitted a pull request to adapt the documentation: [https://github.com/Xilinx/PYNQ/pull/1318](https://github.com/Xilinx/PYNQ/pull/1318)

---

<div class="post-metadata">

**Author:** ![PeterOgden](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.pynq.io/peterogden/32/55_2.png) [@PeterOgden](https://discuss.pynq.io/u/PeterOgden)\
**Post date:** [November 22, 2021, 10:08am UTC](https://discuss.pynq.io/t/zynq-ps7-interrupts/198/13 "2021-11-22T10:08:45Z")

</div>

Thanks for trying and confirming this. This is not something I’d thought of and seems to fix the issue nicely. I’ve been trying various ways to make level-triggered interrupts not double-trigger and come to the conclusion we need an API change. Exactly what that would look like is still up for discussion but will most likely involve some form of callback mechanism where the interrupt can be cleared in the IP before we reset the interrupt controller. Anything else is prone to race conditions where interrupts don’t trigger at all which is even worse than where we are now.

Peter
