April 2, 200811 minutes
Overview
In this tutorial we will improve on the Timer peripheral developed in Create a Simple Timer Peripheral. The improvement is achieved by enabling the peripheral to generate an interrupt when the timer expires. The PowerPC then processes the interrupt through an interrupt handler function which gets called whenever the interrupt occurs. In this example, the interrupt handler function will switch the state of the LEDs and reset the timer.
The timer will use two registers, one to store the delay period and the other for starting, stopping and checking if the timer has expired. We will call the first register the delay register and the second register the control register. The delay register will be set by the software application to determine how long of a delay it requires. This value will remain in the delay register unchanged and the software application will be able to read this value if for some reason it needs to. The control register will only use the first two bits, being bit 0 and bit 1. Bit 0 will be read-only and will be used by the timer peripheral to signal to the software application that the timer has expired. Bit 1 will be read and writeable, and it will be used by the software application to make the timer “run”. When a “1” is written to this bit, the timer will start counting down from the delay value. When a “0” is written to this bit, the timer will reset.
This tutorial contains screenshots to guide you through the entire implementation process. You can click on the images to view a higher resolution when necessary.
Are you using EDK 10.1?
Try the updated version of this tutorial based on the Virtex-5 FPGA on the ML505 board: Timer with Interrupts
Create the Basic Project
Follow these steps to create the basic project:
RS232_Uart_1 and LEDs_4Bit. Untick everything else.
plb_bram_if_cntlr_1 and click “Next”.
RS232_Uart_1 for both STDIN and STDOUT. Un-tick “Memory Test” and leave “Peripheral Test” ticked. Click “Next”.
plb_bram_if_cntlr_1 for the Instruction, Data, Stack and Heap memories. Click “Next”.
Create the Timer Peripheral
We now create our Timer peripheral using the Peripheral Wizard.
my_timer for the peripheral name. Click “Next”.
Modify the Timer Peripheral
Now we want to add a timer to this peripheral template and connect it up with the registers that the Peripheral Wizard created for us.
pcores\my_timer_v1_00_a\hdl\vhdl. This folder contains two source files that describe our peripheral my_timer.vhd and user_logic.vhd. The first file is the main part of the peripheral and it implements the interface to the OPB. The second file is where we place our custom logic to make the peripheral do what we need it to do. This part is instantiated by the first file.user_logic.vhd. We will need to modify this source code to include our timer code.--USER signal declarations added here and add the following lines of code just below.-- Timer signals and components
signal timer_count : std_logic_vector(0 to C_DWIDTH-1);
signal timer_expired : std_logic;
signal timer_run : std_logic;--USER logic implementation added here and add the following lines of code just below.-- Timer connections
timer_run <= slv_reg1(1);
-- Timer process - times the delay between bursts
process (Bus2IP_Clk, Bus2IP_Reset)
begin
-- if the peripheral is told to reset, then reset the timer
if Bus2IP_Reset = '1' then
timer_count <= (others => '0');
timer_expired <= '1';
-- otherwise, if there is a clock event, run the timer
elsif Bus2IP_Clk'event and Bus2IP_Clk = '1' then
-- if the timer is not running, then reset the timer
if timer_run = '0' then
timer_count <= slv_reg0;
timer_expired <= '0';
-- if the timer count is not zero then decrease the count
elsif timer_count /= 0 then
timer_count <= timer_count - 1;
timer_expired <= '0';
-- otherwise, the timer has expired
else
timer_expired <= '1';
end if;
end if;
end process;
-- Interrupt connections
IP2Bus_IntrEvent(0) <= timer_expired;when "01" =>
slv_ip2bus_data(1 to C_DWIDTH-1) <= slv_reg1(1 to C_DWIDTH-1);
slv_ip2bus_data(0) <= timer_expired;user_logic.vhd file. As we use the timer_expired signal to generate interrupts, we must delete the example code. Comment out or delete all the code between the line -- Example code to generate user logic interrupts and the line IP2Bus_IntrEvent <= interrupt; inclusively.Import the Timer Peripheral
Now we will use the Peripheral Wizard again, but this time using the import function.
my_timer. Tick “Use version” and select the same version number that we originally created. Click “Next”. It will ask if we are willing to overwrite the existing peripheral and we should answer “Yes”.
pcores\my_timer_v1_00_a\data and select the my_timer_v2_1_0.pao file. Click “Next”.
The timer peripheral is now ready to use and it should be accessible through the “IP Catalog->Project Repository” in the XPS interface. Note that although we can access it through the IP Catalog, other projects will not find it there because it is only associated with our project, as we specified in the Peripheral Wizard.
Create an Instance of the Peripheral
Now we are ready to create an instance of the peripheral into our project which can then be downloaded into the FPGA and tested by using simple code running on the PowerPC.
my_timer IP core in the “Project Repository” group. Right click on the core and select “Add IP”.
my_timer_0 to the OPB bus.
my_timer_0 to view its ports. Click on the “Net” field for the IP2INTC_Irpt port. Type my_timer_irq in this field and press “Enter”.
ppc405_0 to view the PowerPC ports. Click on the “Net” field for the “EICC405EXTINPUTIRQ” port. Type my_timer_irq in this field and press “Enter”. This connects the timer interrupt request signal to the interrupt request input of the PowerPC.my_timer_0 to 64K. Then click “Generate Addresses”.

Now we have created an instance of the Timer peripheral in our design and we have connected its interrupt request signal to the external interrupt request input of the PowerPC. This completes our hardware connections for interrupt capability.
Modify the Software Application
The software application will contain the main program and the interrupt handler function to be called when an interrupt occurs. It must create the interrupt vector table which tells the PowerPC what code to run when an interrupt is generated. It also must enable the Timer peripheral to generate interrupts, and the PowerPC to respond to interrupts.
TestApp_Peripheral.c source file.#include "xparameters.h"
#include "xbasic_types.h"
#include "xgpio.h"
#include "xstatus.h"
#include "my_timer.h"
#include "xexception_l.h"
#define TIMER_RESET 0x00000000
#define TIMER_RUN 0x40000000
#define TIMER_EXPIRED 0x80000000
#define TIMER_HALFSEC 0x02FAF080
XGpio GpioOutput;
Xuint32 my_timer;
unsigned int *my_timer_p =
(unsigned int *) XPAR_MY_TIMER_0_BASEADDR;
//----------------------------------------------------
// INTERRUPT HANDLER FUNCTION
//----------------------------------------------------
void MY_TIMER_Intr_Handler(void * baseaddr_p)
{
static Xuint32 led_data;
Xuint32 baseaddr;
Xuint32 IpStatus;
baseaddr = (Xuint32) baseaddr_p;
// Get the timer interrupt status
IpStatus = MY_TIMER_mReadReg(baseaddr,
MY_TIMER_INTR_ISR_OFFSET);
// If timer caused the interrupt then switch LEDs
if (IpStatus){
// If LEDs are ON then turn OFF and vice versa
led_data = led_data ^ 0xF;
XGpio_DiscreteWrite(&GpioOutput, 1, led_data);
// Reset the timer and hence the interrupt
MY_TIMER_mWriteSlaveReg1(baseaddr, TIMER_RESET);
MY_TIMER_mWriteSlaveReg1(baseaddr, TIMER_RUN);
}
}
//----------------------------------------------------
// MAIN FUNCTION
//----------------------------------------------------
int main (void)
{
Xuint32 status;
// Clear screen
xil_printf("%c[2J",27);
xil_printf(" Timer Project from ");
xil_printf("http://www.fpgadeveloper.com\r\n");
xil_printf(" --------------------------------");
xil_printf("-----------------\r\n\r\n");
//----------------------------------------------------
// INITIALIZE INTERRUPT VECTOR TABLE
//----------------------------------------------------
xil_printf(" - Initializing interrupt vector table\r\n");
// Initialize exception handling
XExc_Init();
XExc_RegisterHandler(XEXC_ID_NON_CRITICAL_INT,
(XExceptionHandler)MY_TIMER_Intr_Handler,
(void *)XPAR_MY_TIMER_0_BASEADDR);
//----------------------------------------------------
// INITIALIZE THE TIMER PERIPHERAL
//----------------------------------------------------
xil_printf(" - Initializing the timer peripheral\r\n");
// Check that the my_timer peripheral exists
XASSERT_NONVOID(my_timer_p != XNULL);
my_timer = (Xuint32) my_timer_p;
// Load the delay register with the delay time of 0.5s
MY_TIMER_mWriteSlaveReg0(my_timer, TIMER_HALFSEC);
//----------------------------------------------------
// SETUP THE LEDs
//----------------------------------------------------
xil_printf(" - Setting up the LEDs\r\n");
// Initialize the GPIO driver
status = XGpio_Initialize(&GpioOutput,
XPAR_LEDS_4BIT_DEVICE_ID);
if (status != XST_SUCCESS)
return XST_FAILURE;
// Set the direction for all signals to be outputs
XGpio_SetDataDirection(&GpioOutput, 1, 0x0);
// Turn the LEDs ON
XGpio_DiscreteWrite(&GpioOutput, 1, 0x0);
//----------------------------------------------------
// ENABLE INTERRUPTS
//----------------------------------------------------
xil_printf(" - Enabling interrupts\r\n");
// Enable timer interrupts
MY_TIMER_EnableInterrupt(my_timer_p);
// Enable PowerPC non-critical interrupts
XExc_mEnableExceptions(XEXC_NON_CRITICAL);
// Start the timer
xil_printf(" - Starting the timer\r\n\r\n");
MY_TIMER_mWriteSlaveReg1(my_timer, TIMER_RUN);
xil_printf(" SUCCESS!\r\n");
while(1){
}
}Use the Default Linker Script
As mentioned earlier, the PowerPC requires an interrupt vector table to know what code to run when an interrupt occurs. The vector table resides in memory reserved by the linker script. The custom linker script for the TestApp_Peripheral application does not include a vector table, so we need to switch to the default linker script.

Download and Test the Project
The Hyperterminal output should look as shown in the image below:
The LEDs should be flashing once every second. The timer peripheral is set for a delay period of half a second. Each call to the interrupt handler changes the state of the LEDs and resets the timer for another half a second. The result is that the LEDs remain ON for half a second and OFF for half a second.
The PowerPC has only one input for non-critical interrupt requests and in this project, we used it for our Timer peripheral. In a more complex design with multiple interrupts sources, we need to use an interrupt controller to multiplex all the interrupt request signals into one. This is the topic of the next tutorial.
The project folder for this tutorial can be downloaded in a compressed ZIP file TimerInterrupts.zip . Right-click on the link and select “Save Link As”.