/zedboard_pl_to_ps_interrupt_example

Tutorial on how to use the PL to PS interrupt on the Zedboard

Setting up a PL to PS interrupt on the Zedboard

This tutorial shows you how to setup a PL to PS interrupt on the Zedboard using Vivado and the Xilinx SDK

Requirements

  • Vivado 2016.4
  • Zedboard

Creating a New Vivado Project

This part is pretty straight forward. You can look up how to setup a new Vivado project here:

https://github.com/k0nze/zedboard_axi4_master_burst_example#creating-a-new-vivado-project

Throughout this tutorial the name for the Vivado project is pl_to_ps_interrupt_example.

Creating a Custom AXI4 IP

After you successfully created a new Vivado project carry out the following steps to create a custom AXI IP which will issue the interrupts from the PL to the PS with an AXI4-Lite slave interface.

  1. Open: Menu -> Tools -> Create and Package IP.

    menu - tools - create and package ip

  2. Click Next >

    click next

  3. Choose Create a new AXI4 peripheral.

    choose create a new axi4 peripheral

  4. Choose a name, description and location for the new AXI4 peripheral. The name in this tutorial is axi4_pl_interrupt_generator and the location is [...]/ip_repo.

    choose name and description

  5. Keep the AXI4-Lite slave interface and click Next >

    click next

  6. Choose Edit IP and click Finish

    edit ip

Edit AXI4 IP

After the successful creation of the new AXI4 IP a new Vivado project was opened. In this project you can find the Vivado generated Verilog code for the AXI4-Lite slave and a top module (wrapper) which contains the AXI4-Lite slave.

sources

  • axi4_pl_interrupt_generator_v1_0 contains the top module
  • axi4_pl_interrupt_generator_v1_0_S00_AXI_instcontains the Verilog code for the AXI4-Lite slave.

The AXI4-Lite slave will be used to set and clear the interrupt from the PS.

Double-click on axi4_pl_interrupt_generator_v1_0_S00_AXI_isnt and navigate to the ports definition and add your own ports under // Users to add ports here.

// Users to add ports here
output wire interrupt_0,
output wire interrupt_1,
// User ports ends

Those two wires will later be connected to outputs of the top module which will be then connected to the interrupt ports of the PS.

Navigate to // Add user logic here and add the following:

// Add user logic here
assign interrupt_0 = slv_reg0[0:0];
assign interrupt_1 = slv_reg1[1:1];
// User logic ends

This will connect the wires to the LSB of the slave registers 0 and 1 to which the PS can write directly (slv_reg0[0:0] and slv_reg1[0:0]).

The newly added ports of the AXI4-Lite slave also have to be added to the module instantiation in the top module axi4_pl_interrupt_generator. Double-click on axi4_pl_interrupt_generator in the sources tree and navigate to: // Instantiation of Axi Bus Interface S00_AXI and add the new ports to the port map:

// Instantiation of Axi Bus Interface S00_AXI
axi4_pl_interrupt_generator_v1_0_S00_AXI # ( 
    .C_S_AXI_DATA_WIDTH(C_S00_AXI_DATA_WIDTH),
    .C_S_AXI_ADDR_WIDTH(C_S00_AXI_ADDR_WIDTH)
) axi4_pl_interrupt_generator_v1_0_S00_AXI_inst (
    
    .interrupt_0(interrupt_0),
    .interrupt_1(interrupt_1),

interrupt_0 and interrupt_1 will be connected to interrupt_0 and interrupt_1 of the top module. To add interrupt_0 and interrupt_1 to the top module navigate to // Users to add ports here and add the following:

// Users to add ports here
output wire interrupt_0,
output wire interrupt_1,
// User ports ends

When the PS sets the LSB of slv_reg0 or slv_reg1 in the AXI4-Lite slave from 0 to 1 a rising edge will be seen at the output ports interrrupt_0 and inpterrupt_1 of the axi4_pl_interrupt_generator. This concludes the edits in the Verilog code of the AXI4-Lite slave.

  1. Click on the tab Package IP - axi_pl_interrupt_generator.

    package ip

  2. Click on File Groups in Packaging Steps.

    file groups

  3. Click on Merge changes from File Groups Wizard.

    merge changes

  4. Click on Customization Parameters in Packaging Steps.

    costomziation parameters

  5. Click on Merge changes from customization Parameters Wizard.

    merge changes

  6. Click on Review and Package in Packaging Steps.

    review and package

  7. Click on Re-Package IP.

    re-package ip

  8. Click Yes to close the Project for the axi4_pl_interrupt_generator.

    close project

You can go back to the Verilog code by clicking on Flow Navigator -> Project Manager -> IP Catalog.

edit ip ip catalog

And navigate to User Repository -> AXI Peripheral -> axi4_pl_interrupt_generator_v1.0 and right-click to open the context menu an choose Edit in IP Packager.

edit ip edit in ip packager

Zynq Block Diagram

So that your custom AXI4 IP can be implemented on the Zynq PL and connected to the Zynq PS you have to create a block diagram in Vivado. The following steps will show you how to do that:

  1. Click on Flow Navigator -> IP Integrator -> Create Block Diagram.

    create block diagram

  2. Choose a name, directory, and specify a source set for the block diagram. In this tutorial everything stays at its default.

    block diagram choose name

  3. Right-click on the white background of the Diagram tab and choose Add IP.

    block diagram add ip

  4. From the list of IPs choose ZYNQ7 Processing System (this is the Zynq PS) and double-click on it.

    block diagram zynq ps

  5. You can now see the Zynq PS in the block diagram. Click on Run Block Automation to connect the Zynq PS with the memory.

    block diagram run block automation

  6. Leave everything at the default values and click on OK.

    block diagram run block automation ok

  7. To connect the interrupt ports of your AXI4 IP to the Zynq PS the Zynq PS needs interrupt ports. To enable those interrupt ports double-click on the Zynq PS in the block diagram.

    block diagram zynq ps

  8. In the Re-customize IP window go to Page -> Navigator -> Interrupts.

    block diagram interrupts

  9. Unfold Fabric Interrupts -> PL-PS Interrupt Ports and check IRQ_F2P[15:0] and click OK.

    block diagram irq f2p

  10. Now its time to add your custom AXI4 IP. Right-click on the white background of the Diagram tab and choose Add IP.

    block diagram add ip

  11. From the list of IPs choose axi4_pl_interrupt_generator_v1.0 (this is your custom AXI4 IP) and double click on it to add it to the block diagram.

    block diagram axi4 ip

  12. Click on Run Connection Automation to connect your custom AXI4 IP to the Zynq PS via the AXI4 bus.

    block diagram run connection automation

  13. Check S00_AXI in the tree on the left-hand side. Select /processing_system7_0/FCLK_CLK0 in the list of Clock Connections and click on OK.

    block diagram clock connection

  14. After the connection automation is done click on block diagram regenerate layout to regenerate the layout of the block diagram. Your block diagram should now look like this:

    block diagram axi4 connection done

  15. To connect the interrupt_0 and interrupt_1 outputs of your custom AXI4 IP to the Zynq PS. Add another module by right-clicking on the white background and choose Add IP and select Concat.

    block diagram concat

  16. Connect the In0 and In1 inputs of the xlconcat_0 module to the interrupt_0 and interrupt_1 outputs of your custom AXI4 IP by hovering with the curser over on of interrupt_* outputs and drawing a line a with the pencil to one of the In* inputs. Do this for both interupt_* outputs.

    block diagram draw connection

  17. Afterwards connect the dout output of the xlconcat_0 to the IRQ_F2P input of the Zynq PS.

    block diagram zynq ps interrupt connection

  18. The block diagram is now finished. In the Sources Panel navigate to Design Sources -> design_1.

    block diagram sources panel

  19. Right-click on design_1 and choose Create HDL Wrapper. This generates HDL code for the block diagram which is necessary for the synthesis.

    block diagram create hdl wrapper

  20. Choose Let Vivado manage wrapper and auto-update and click OK. This will always update your HDL wrapper when the block diagram was changed.

    block diagram update hdl wrapper

  21. Afterwards output products for the your block diagramm have to generated. Navigate in the Sources Panel to design_1_i, right-click on it and choose Generate Output Products

    block diagram generate output products

  22. Leave everything at its default and click Generate.

    block diagram generate

Synthesis and implementation

To bring the custom AXI4 IP with the block diagram to the Zynq PL you have to synthesize and implement it.

  1. Start the synthesis by click on Run Synthesis in Flow Navigator -> Synthesis.

    run synthesis

  2. Leave everything at its default and click OK to launch the systhesis.

    launch synthesis

  3. After the synthesis is finished choose Run implementation and click on OK to run the implementation.

    run implementation

  4. When the implementation is finished choose Generate Bitstream and click on OK to generate the bitstream which contains the configuration data for the Zynq PL.

    generate bitstream

  5. Lastly, when the bitstream generation is finished you can look at the reports to see if all contraints are fulfilled. Choose View Reports and click OK. (However, this is not necessary here since the design is very simple).

    view reports

Software for the Zynq PS

To program the Zedboard and talk to it via UART you have to connect it to the power supply and connect two USB cables from your computer to the following USB ports on the Zedboard.

zedboard connection

Make sure your Zedboard is turned on. If the green POWER led is on the Zedboard is turned on.

zedboard led

The C program which will be transferred to the Zynq PS is going to setup the interrupt system of the Zynq PS and enables the interrupts for the IRQ_F2P[1:0] ports for a rising edge. When the interrupt system is enabled the interrupts will be generated by writing a 1 into slv_reg0[0:0] and slv_reg1[0:0]. This will trigger the interrupt service routines isr0 and isr1 which will clear the interrupts by writing a 0 to slv_reg0[0:0] or slv_reg1[0:0]. Then the interrupt for IRQ_F2P[1:1] will be disabled and by writing a 1 into slv_reg0[0:0] and slv_reg1[0:0] new interrupts will be generated. This will only trigger the interrupt service routine isr0 since the interrupt IRQ_F2P[1:1] is disabled.

  1. You have to export the hardware configuration to the Xilinx SDK. Go to Menu -> File -> Export -> Export Hardware ....

    menu export hardware

  2. Check Include bitstream and click OK.

    export hardware

  3. To launch the Xilinx SDK go to Menu -> File -> Launch SDK

    lauch sdk

  4. When the Xilinx SDK is ready create a new project by going to Menu -> File -> New -> Application Project.

    new application project

  5. Choose a Project name and leave all other parameters at their default value and click on Next >. The Project name in this tutorial is axi4_pl_interrupt_generator_test.

    project name

  6. Choose Hello World under Available Templates and click on finish. This creates a simple Hello World program for the Zynq PS.

    hello world

  7. After the project was successfully created open helloworld.c under Project Explorer -> axi4_pl_interrupt_generator_test -> src -> helloworld.c

    helloworld.c

  8. Replace the Hello World C code with:

    #include <stdio.h>
    #include "platform.h"
    #include "xil_printf.h"
    #include "xbasic_types.h"
    #include "xscugic.h"
    #include "xil_exception.h"
    
    #define INTC_INTERRUPT_ID_0 61 // IRQ_F2P[0:0]
    #define INTC_INTERRUPT_ID_1 62 // IRQ_F2P[1:1]
    
    // instance of interrupt controller
    static XScuGic intc;
    
    // address of AXI PL interrupt generator
    Xuint32* baseaddr_p = (Xuint32*) XPAR_AXI4_PL_INTERRUPT_GENERATOR_0_S00_AXI_BASEADDR;
    
    int setup_interrupt_system();
    
    void isr0 (void *intc_inst_ptr);
    void isr1 (void *intc_inst_ptr);
    void nops(unsigned int num);
    
    int main() {
        init_platform();
    
        xil_printf("== START ==\n\r");
        // set interrupt_0/1 of AXI PL interrupt generator to 0
        *(baseaddr_p+0) = 0x00000000;
        *(baseaddr_p+1) = 0x00000000;
    
        xil_printf("Checkpoint 1\n\r");
    
        // set interrupt_0/1 of AXI PL interrupt generator to 1
        *(baseaddr_p+0) = 0x00000001;
        *(baseaddr_p+1) = 0x00000001;
    
        xil_printf("Checkpoint 2\n\r");
        // read interrupt_0/1 of AXI PL interrupt generator
        xil_printf("slv_reg0: 0x%08x\n\r", *(baseaddr_p+0));
        xil_printf("slv_reg1: 0x%08x\n\r", *(baseaddr_p+1));
    
        // set interrupt_0/1 of AXI PL interrupt generator to 0
        *(baseaddr_p+0) = 0x00000000;
        *(baseaddr_p+1) = 0x00000000;
    
        xil_printf("Checkpoint 3\n\r");
        // read interrupt_0/1 of AXI PL interrupt generator
        xil_printf("slv_reg0: 0x%08x\n\r", *(baseaddr_p+0));
        xil_printf("slv_reg1: 0x%08x\n\r", *(baseaddr_p+1));
    
        xil_printf("Checkpoint 4\n\r");
        // setup and enable interrupts for IRQ_F2P[1:0]
        int status = setup_interrupt_system();
        if (status != XST_SUCCESS) {
             return XST_FAILURE;
        }
    
        xil_printf("Checkpoint 5\n\r");
        nops(1000);
        // set interrupt_0 of AXI PL interrupt generator to 1 (isr0 will be called)
        *(baseaddr_p+0) = 0x00000001;
    
        xil_printf("Checkpoint 6\n\r");
        nops(1000);
        // set interrupt_1 of AXI PL interrupt generator to 1 (isr1 will be called)
        *(baseaddr_p+1) = 0x00000001;
    
        // disable interrupts for IRQ_F2P[1:1]
        XScuGic_Disable(&intc, INTC_INTERRUPT_ID_1);
    
        xil_printf("Checkpoint 7\n\r");
        nops(1000);
        // set interrupt_0 of AXI PL interrupt generator to 1 (isr0 will be called)
        *(baseaddr_p+0) = 0x00000001;
    
        xil_printf("Checkpoint 8\n\r");
        nops(1000);
        // set interrupt_1 of AXI PL interrupt generator to 1
        // (isr1 wont be called since interrupts for IRQ_F2P[1:1] are disabled)
        *(baseaddr_p+1) = 0x00000001;
    
        xil_printf("== STOP ==\n\r");
    
        cleanup_platform();
        return 0;
    }
    
    // interrupt service routine for IRQ_F2P[0:0]
    void isr0 (void *intc_inst_ptr) {
        xil_printf("isr0 called\n\r");
        *(baseaddr_p+0) = 0x00000000;
    }
    
    // interrupt service routine for IRQ_F2P[1:1]
    void isr1 (void *intc_inst_ptr) {
        xil_printf("isr1 called\n\r");
        *(baseaddr_p+1) = 0x00000000;
    }
    
    // sets up the interrupt system and enables interrupts for IRQ_F2P[1:0]
    int setup_interrupt_system() {
    
        int result;
        XScuGic *intc_instance_ptr = &intc;
        XScuGic_Config *intc_config;
    
        // get config for interrupt controller
        intc_config = XScuGic_LookupConfig(XPAR_PS7_SCUGIC_0_DEVICE_ID);
        if (NULL == intc_config) {
            return XST_FAILURE;
        }
    
        // initialize the interrupt controller driver
        result = XScuGic_CfgInitialize(intc_instance_ptr, intc_config, intc_config->CpuBaseAddress);
    
        if (result != XST_SUCCESS) {
            return result;
        }
    
        // set the priority of IRQ_F2P[0:0] to 0xA0 (highest 0xF8, lowest 0x00) and a trigger for a rising edge 0x3.
        XScuGic_SetPriorityTriggerType(intc_instance_ptr, INTC_INTERRUPT_ID_0, 0xA0, 0x3);
    
        // connect the interrupt service routine isr0 to the interrupt controller
        result = XScuGic_Connect(intc_instance_ptr, INTC_INTERRUPT_ID_0, (Xil_ExceptionHandler)isr0, (void *)&intc);
    
        if (result != XST_SUCCESS) {
            return result;
        }
    
        // enable interrupts for IRQ_F2P[0:0]
        XScuGic_Enable(intc_instance_ptr, INTC_INTERRUPT_ID_0);
    
        // set the priority of IRQ_F2P[1:1] to 0xA8 (highest 0xF8, lowest 0x00) and a trigger for a rising edge 0x3.
        XScuGic_SetPriorityTriggerType(intc_instance_ptr, INTC_INTERRUPT_ID_1, 0xA8, 0x3);
    
        // connect the interrupt service routine isr1 to the interrupt controller
        result = XScuGic_Connect(intc_instance_ptr, INTC_INTERRUPT_ID_1, (Xil_ExceptionHandler)isr1, (void *)&intc);
    
        if (result != XST_SUCCESS) {
            return result;
        }
    
        // enable interrupts for IRQ_F2P[1:1]
        XScuGic_Enable(intc_instance_ptr, INTC_INTERRUPT_ID_1);
    
        // initialize the exception table and register the interrupt controller handler with the exception table
        Xil_ExceptionInit();
    
        Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, intc_instance_ptr);
    
        // enable non-critical exceptions
        Xil_ExceptionEnable();
    
        return XST_SUCCESS;
    }
    
    void nops(unsigned int num) {
        int i;
        for(i = 0; i < num; i++) {
            asm("nop");
        }
    }
  9. Program the Zynq PL with the previously generated bitstream by going to Menu -> Xilinx Tools -> Program FPGA.

    menu program fpga

  10. Click on Program.

    program fpga

  11. The Zynq PS will write the content of all xil_printf("..."); statments to the UART.

    On Linux you can connect to the UART of the Zedboard with the following picocom command:

picocom /dev/ttyACM0 -b 115200 -d 8 -y n -p 1
  1. After you programed the Zynq PL you can and connected to the UART you can build and run the Program on the Zynq PS by clicking on build and then on run.

  2. Choose Lauch on Hardware (System Debugger) and click on OK.

    run on hardware

  3. You should see the following output in picocom:

== START ==
Checkpoint 1
Checkpoint 2
slv_reg0: 0x00000001
slv_reg1: 0x00000001
Checkpoint 3
slv_reg0: 0x00000000
slv_reg1: 0x00000000
Checkpoint 4
Checkpoint 5
isr0 called
Checkpoint 6
isr1 called
Checkpoint 7
isr0 called
Checkpoint 8
== STOP ==

You can leave picocom with [CTRL]+[A] [CTRL]+[Q].