This tutorial shows you how to setup a PL to PS interrupt on the Zedboard using Vivado and the Xilinx SDK
- Vivado 2016.4
- Zedboard
This part is pretty straight forward. You can look up how to setup a new Vivado project here:
Throughout this tutorial the name for the Vivado project is pl_to_ps_interrupt_example
.
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.
-
Open: Menu -> Tools -> Create and Package IP.
-
Click Next >
-
Choose Create a new AXI4 peripheral.
-
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
. -
Keep the AXI4-Lite slave interface and click Next >
-
Choose Edit IP and click Finish
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.
axi4_pl_interrupt_generator_v1_0
contains the top moduleaxi4_pl_interrupt_generator_v1_0_S00_AXI_inst
contains 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.
-
Click on the tab Package IP - axi_pl_interrupt_generator.
-
Click on File Groups in Packaging Steps.
-
Click on Merge changes from File Groups Wizard.
-
Click on Customization Parameters in Packaging Steps.
-
Click on Merge changes from customization Parameters Wizard.
-
Click on Review and Package in Packaging Steps.
-
Click on Re-Package IP.
-
Click Yes to close the Project for the axi4_pl_interrupt_generator.
You can go back to the Verilog code by clicking on Flow Navigator -> Project Manager -> 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.
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:
-
Click on Flow Navigator -> IP Integrator -> Create Block Diagram.
-
Choose a name, directory, and specify a source set for the block diagram. In this tutorial everything stays at its default.
-
Right-click on the white background of the Diagram tab and choose Add IP.
-
From the list of IPs choose ZYNQ7 Processing System (this is the Zynq PS) and double-click on it.
-
You can now see the Zynq PS in the block diagram. Click on Run Block Automation to connect the Zynq PS with the memory.
-
Leave everything at the default values and click on OK.
-
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.
-
In the Re-customize IP window go to Page -> Navigator -> Interrupts.
-
Unfold Fabric Interrupts -> PL-PS Interrupt Ports and check IRQ_F2P[15:0] and click OK.
-
Now its time to add your custom AXI4 IP. Right-click on the white background of the Diagram tab and choose Add IP.
-
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.
-
Click on Run Connection Automation to connect your custom AXI4 IP to the Zynq PS via the AXI4 bus.
-
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.
-
After the connection automation is done click on to regenerate the layout of the block diagram. Your block diagram should now look like this:
-
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.
-
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.
-
Afterwards connect the dout output of the xlconcat_0 to the IRQ_F2P input of the Zynq PS.
-
The block diagram is now finished. In the Sources Panel navigate to Design Sources -> design_1.
-
Right-click on design_1 and choose Create HDL Wrapper. This generates HDL code for the block diagram which is necessary for the synthesis.
-
Choose Let Vivado manage wrapper and auto-update and click OK. This will always update your HDL wrapper when the block diagram was changed.
-
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
-
Leave everything at its default and click Generate.
To bring the custom AXI4 IP with the block diagram to the Zynq PL you have to synthesize and implement it.
-
Start the synthesis by click on Run Synthesis in Flow Navigator -> Synthesis.
-
Leave everything at its default and click OK to launch the systhesis.
-
After the synthesis is finished choose Run implementation and click on OK to run the implementation.
-
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.
-
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).
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.
Make sure your Zedboard is turned on. If the green POWER led is on the Zedboard is turned on.
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.
-
You have to export the hardware configuration to the Xilinx SDK. Go to Menu -> File -> Export -> Export Hardware ....
-
Check Include bitstream and click OK.
-
To launch the Xilinx SDK go to Menu -> File -> Launch SDK
-
When the Xilinx SDK is ready create a new project by going to Menu -> File -> New -> Application Project.
-
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.
-
Choose Hello World under Available Templates and click on finish. This creates a simple Hello World program for the Zynq PS.
-
After the project was successfully created open
helloworld.c
under Project Explorer -> axi4_pl_interrupt_generator_test -> src -> helloworld.c -
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"); } }
-
Program the Zynq PL with the previously generated bitstream by going to Menu -> Xilinx Tools -> Program FPGA.
-
Click on Program.
-
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
-
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 and then on .
-
Choose Lauch on Hardware (System Debugger) and click on OK.
-
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].