Lab: Interrupts

Interrupt Controller

In this example we implement \(f(x)=x!\) as an IP for PYNQ with interrupt controller.

1) Vivado HLS: C/C++ to RTL

In this section, you will write your code in C/C++ and convert it to RTL using Vivado HLS.

1.1) Create a project

Open the Vivado HLS tool, create a new project, and name it pynq_fact.

https://bitbucket.org/repo/x8q9Ed8/images/3438488411-fact1.png

Set top function to fact_intrpt.

https://bitbucket.org/repo/x8q9Ed8/images/2082111028-fact2.png

Select xc7z020clg400-1 as your part number.

https://bitbucket.org/repo/x8q9Ed8/images/2893940654-fact3.png

1.2) Write your code

In Explorer section, right click on Source, and select new file. Create a new file and name it fact_intrpt.cpp**and set **in, out, and return ports as s_axilite interfaces. Your code should look like this:

#pragma HLS INTERFACE s_axilite port=in bundle=control
#pragma HLS INTERFACE s_axilite port=out bundle=control
#pragma HLS INTERFACE s_axilite port=return bundle=control

void fact_intrpt(int* out, int in){
    int i, k=1;
    for(i=1; i<=in; i++){
        k = k*i;
    }
    *out = k;
}

If you run in to II violations (like Vitis 2021 always does) set II=2:

#pragma HLS pipeline II=2

1.3) Synthesize your code and export RTL

You can synthesize your code by clicking on C Synthesis. After finishing C synthesis, click on Export RTL and select Vivado IP (.zip). Now that you exported your RTL, you can close Vitis HLS.

2) Vivado: RTL to Bitstream

In this section we generate a bitstream for our IP.

2.1) Create a new project

Open the Vivado tool and create a new project and name it fact_pynq.

Select RTL Project and check Do not specify sources at this time.

Set your part number to xc7z020clg400-1:

https://bitbucket.org/repo/x8q9Ed8/images/3425856850-fact6.png

2.2) Import RTL

In Flow Navigator, under Project Manager, click on IP Catalog.

On the newly opened window, right click and select Add Repository. Select your extracted Vivado IP that you exported from Vitis.

2.3) Add blocks to your design

In Flow Navigator, under IP Integrator, click on Create Block Design. Leave the name as is, design_1.

You can add IPs to your design by clicking on the + sign. Add the following IPs:

Add ZYNQ7 Processing System to your design:

https://bitbucket.org/repo/x8q9Ed8/images/2209037604-fact8.png

Double click on ZYNQ7 IP to customize it. Under page navigator, got to interrupts, and enable IRQ_F2P[15:0]

https://bitbucket.org/repo/x8q9Ed8/images/117709731-fact8.png

Add fact_intrpt and rename it to fact_intrpt:

Add AXI Interrupt Controller and rename it to axi_intc:

Double click on axi_intc to customize it. Set Interrupt Output Connection to Single.

https://bitbucket.org/repo/x8q9Ed8/images/2935182167-fact11.png

Add two Concat IPs to your design, double click on them for customization, and set Number of Ports to 1:

https://bitbucket.org/repo/x8q9Ed8/images/538208086-fact12.png

2.4) Connect your blocks

Connect the following ports:

fact_intrpt::interrupt to xlconcat_0::in0

xlconcat_0::dout to axi_intc::intr[0:0]

axi_intc::irq to xlconcat_1::in0

https://bitbucket.org/repo/x8q9Ed8/images/2495777624-fact13.png

That is all the manual labor you have to do for the connections. Now, you can use the tool for the rest of the blocks and connections.

In the Diagram window, in the highlighted are, click on Run Block Automation. Then click on Run Connection Automation and select all:

https://bitbucket.org/repo/x8q9Ed8/images/667624785-fact14.png

Your design should look like the following:

https://github.com/KastnerRG/pp4fpgas/raw/master/labs/images/fact_intrpt.jpg

2.5) Generating bitstream

Save your block diagram and check your design from Tools > Validate Design. If it passed successfully, under Sources, right click on design_1, and select Create HDL Wrapper with default settings.

Now you can click on Generate Bitstream in Flow Navigator with default settings to generate your bitstream.

2.6) Export .tcl file and note addresses

Exporting the block design is an optional step, Pynq seems to prefer .hwh over .tcl. After finishing your bitstream generation, you can export your block design from File > Export > Export Block Design, and name it fact_intrpt.tcl:

https://bitbucket.org/repo/x8q9Ed8/images/3372073042-fact16.png

Navigate to your Vivado project folder and search for .bit and then for .hwh. Copy design_1_wrapper.bit (rename to design_1.bit) and design_1.hwh. In Sources, open fact_intrpt_cntrl_io_s_axi.v and copy the address info into a text file. We need these addresses to map our variables in the Python code.

https://bitbucket.org/repo/x8q9Ed8/images/2508179436-fact17.png

3) Host program

In this section we use a Jupyter notebook to interact with our IP.

3.1) Move files to PYNQ

Connect to your PYNQ board and create a new folder. Copy fact_intrpt.hwh and fact_intrpt.bit to this folder like we have done in previous labs. Create a new Jupyter notebook and complete it as following to interact with your design:

from pynq import Overlay
import asyncio
from psutil import cpu_percent

ol = Overlay("fact_intrpt.bit")
ol.download()
# IP's addresses
IP_CTRL = 0x00
AP_START = 0x1
GIER = 0x04
IP_IER = 0x08
IP_ISR = 0x0C
INTRPT_AP_DONE = 0x1
INTRPT_AP_READY = 0x2
OUT_REG = 0x10
INP_REG = 0x18

_INTRPT = INTRPT_AP_DONE

fact_ip = ol.fact_intrpt
fact_ip.write(GIER, 0x1)
fact_ip.write(IP_IER, _INTRPT)
# Coroutine that waits for an IP to be done.
async def read_ip(ip):
    while True:
        # Wait for the IP to finish.
        await ip.interrupt.wait()
        # Clear the interrupt and then print output's value.
        if (ip.read(IP_ISR) & _INTRPT):
            ip.write(IP_ISR, _INTRPT)
            print('interrupt received, out = {}'.format(ip.read(OUT_REG)))

# Task for IP using the coroutine
ip_task = asyncio.ensure_future(read_ip(fact_ip))

# Coroutine for writing input and starting the IP with delay
async def write_wait(interval):
    await asyncio.sleep(interval)
    # write to input
    fact_ip.write(INP_REG, 10)
    print("input = ", fact_ip.read(INP_REG))
    fact_ip.write(IP_CTRL, AP_START) # You can comment it out to test the interrupt
    print("IP started")
    await asyncio.sleep(interval)

# Run the event loop until the time interval expires
time_interval = 2  # time in seconds
loop = asyncio.get_event_loop()
write_task = asyncio.ensure_future(write_wait(time_interval))

# Using psutil to record CPU utilization.
cpu_percent(percpu=True)  # Initializing the CPU monitoring.
loop.run_until_complete(write_task)
cpu_used = cpu_percent(percpu=True)

# Printing the CPU utilization
print('CPU Utilization = {cpu_used}'.format(**locals()))

# Removing the IP task from the event loop.
ip_task.cancel()

If you run into overlay issues run the following to display IP hierarchy in the overlay and select appropriately:

for i in ol.ip_dict:
        print(i)

You should see the following output:

https://github.com/KastnerRG/pp4fpgas/raw/master/labs/images/fact_interpt_op.png

That’s it for the lab.