Create a Simple Timer Peripheral


Overview

In this project, we will add code to a peripheral template generated by the Peripheral Wizard to create a simple timer. The timer peripheral will be used by the PowerPC to make the LEDs flash with a fixed period.

Figure: The Timer peripheral

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.

Create the Basic Project

Follow these steps to create the basic project:

  1. Open XPS and from the dialog box, select “Base System Builder wizard” and OK.
  2. Create a new folder for the project and select it using “Browse”. In “Advanced Options” tick “Use repository paths” and select the “C:\XUPV2P\lib” folder using “Browse”. Click “OK”.  
  3. Tick “I would like to create a new design” and click “Next”.
  4. Select “Xilinx” as the board vendor. Select “XUP Virtex II Pro Development System” as the board name. Select “C” as the board revision. Click “Next”.  
  5. Tick “PowerPC” and click “Next”.  
  6. Select all clock frequencies to be 100MHz. Select “No debug”. Click “Next”.  
  7. In selecting the Additional IO Interfaces, tick RS232_Uart_1 and LEDs_4Bit. Untick everything else.  
  8. When Adding Internal Peripherals, select 64KB for the plb_bram_if_cntlr_1 and click “Next”.  
  9. Select RS232_Uart_1 for both STDIN and STDOUT. Un-tick “Memory Test” and leave “Peripheral Test” ticked. Click “Next”.  
  10. On the “Configure Peripheral Test Application” page, select plb_bram_if_cntlr_1 for the Instruction, Data, Stack and Heap memories. Click “Next”.  
  11. Click “Generate”.
  12. Click “Finish”.
  13. Tick “Start using Platform Studio” and click “OK”.

Modify the Software Application

The software application that the BSB has created for us will test the LEDs by making them flash one at a time. We want to modify this application so that it makes all the LEDs flash at the same time.

  1. From the “Applications” tab, open “Sources” within the “Project: TestApp_Peripheral” tree. Open the “TestApp_Peripheral.c” source file.
  2. Replace all the code in this file with the following source and save the file.
#include "xparameters.h"
#include "xbasic_types.h"
#include "xgpio.h"
#include "xstatus.h"

XGpio GpioOutput;

int main (void) {

  Xuint32 status;
  unsigned int i;

  // Initialize the GPIO driver so that it's ready to use,
  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);

  while(1){
    // Turn OFF the LEDs
    XGpio_DiscreteWrite(&GpioOutput, 1, 0xF);
    // Wait for a period
    for(i = 0; i < 0x0F00000; i++){}
    // Turn ON the LEDs
    XGpio_DiscreteWrite(&GpioOutput, 1, 0x0);
    // Wait for a period
    for(i = 0; i < 0x0F00000; i++){}
  }
}
  1. From the menu select “Device Configuration->Download Bitstream”.

The bitstream will be created and downloaded into the FPGA. When the application runs, you will see the LEDs on the XUPV2P board flashing.

If you read through the code, you will notice that we have used simple “for” loops to implement delays between turning the LEDs on and turning them off. Without the delays, the LEDs would flash more quickly than the human eye can see. The main problems with using these “for” loops are: (1) we can’t be very precise about the delay time because it depends on how quickly the processor completes the “for” loop which depends on how many instructions per second it can perform and whether any interrupt routines are executed during that time, (2) the main program can’t do anything during the time that we wait.

For these reasons, instead of using the PowerPC to calculate the delays, it is more convenient to use an external timer module. An independent timer peripheral can count-down the delay that we specify and will not be influenced by other processes occurring at the same time. To check the timer, the PowerPC has the choice of either (1) polling the timer peripheral to find out when the delay period has expired or (2) receive an interrupt when the delay period has expired. In this tutorial, we will use the polling method for simplicity. The interrupt method is explored in the tutorial Timer with Interrupts.

Create the Timer Peripheral

We now create our Timer peripheral using the Peripheral Wizard.

  1. Select from the menu “Hardware->Create or Import Peripheral”. Click “Next”.
  2. Select “Create templates for a new peripheral” and click “Next”.
  3. We must now decide where to place the files for the peripheral. They can be placed within this project, or they can be made accessible to other projects. Select “To an XPS project”. Click “Next”.
  4. Type my_timer for the peripheral name. Click “Next”.  
  5. Select “On-chip Peripheral Bus” (OPB) and click “Next”.
  6. The Peripheral Wizard can generate our VHDL template to include many different features. Tick “User logic S/W register support”. Un-tick everything else and click “Next”.  
  7. Choose “2” for the number of software accessible registers. Choose 32-bits for the size of the registers, as we will use a 32-bit timer.  
  8. On the “IP Interconnect” page we can customize our connection to the OPB but we will leave everything as is for simplicity. Click “Next”.
  9. On the “Peripheral Simulation Support” page, we can specify if we want the wizard to create a simulation platform for our peripheral. Click “Next” without ticking the option to generate.
  10. After the “Peripheral Implementation Support” page, the wizard will generate all the template files for us. Tick “Generate ISE and XST project files” and “Generate template driver files”. Click “Next”.
  11. Click “Finish”. Now our templates are created and we can modify them to include the code for the timer.

Modify the Peripheral

Now we want to add a simple timer to this peripheral template and connect it up with the registers that the Peripheral Wizard created for us.

  1. Select from the menu “File->Open” and look in the project folder.
  2. Open the folders: 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.
  3. Open the file user_logic.vhd. We will need to modify this source code to include our timer code.
  4. Find the line of code that says --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;
  1. Find the line of code that says --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;
  1. Find the line of code that says “when “01” => slv_ip2bus_data <= slv_reg1” and replace it with the three lines of code below. Now, when the timer control register is read from, bit 0 will represent the timer_expired signal.
when "01" =>
  slv_ip2bus_data(1 to C_DWIDTH-1) <= slv_reg1(1 to C_DWIDTH-1);
  slv_ip2bus_data(0) <= timer_expired;
  1. Save and close the file.

Import the Timer Peripheral

Now we will use the Peripheral Wizard again, but this time using the import function.

  1. Select from the menu “Hardware->Create or Import Peripheral” and click “Next”.
  2. Select “Import existing peripheral” and click “Next”.
  3. Select “To an XPS project”, ensure that the folder chosen is the project folder, and click “Next”.
  4. For the name of the peripheral, type 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”.  
  5. Tick “HDL source files” and click “Next”.  
  6. Select “Use existing Peripheral Analysis Order file (*.pao)” and click “Browse”. From the project folder, go to pcores\my_timer_v1_00_a\data and select the my_timer_v2_1_0.pao file. Click “Next”.  
  7. On the HDL analysis information page, click “Next”. The wizard will mention if any errors are found in the design.
  8. On the Bus Interfaces page, tick “OPB Slave” and click “Next”.  
  9. On the SOPB: Port page, click “Next”.
  10. On the SOPB: Parameter page, click “Next”.
  11. On the “Parameter Attributes” page, click “Next”.
  12. On the “Port Attributes” page, click “Next”.
  13. Click “Finish”.

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

  1. From the “IP Catalog” find the my_timer IP core in the “Project Repository” group. Right click on the core and select “Add IP”.  
  2. From the “System Assembly View” using the “Bus Interface” filter, connect the my_timer_0 to the OPB bus.  
  3. Click on the “Addresses” filter. Change the “Size” for my_timer_0 to 64K. Then click “Generate Addresses”.

Now we have created an instance of the Timer peripheral in our design.

Modify the Software Application

Now the timer peripheral is in our project and ready for us to use. We can now modify our software application to use the timer to produce the delays between turning the LEDs on and turning them off.

In the code below, to interact with the timer peripheral, we are using the my_timer driver that was created by the peripheral wizard and located in the project folder at \drivers\my_timer. The include file my_timer.h provides us with simple functions for reading and writing to the registers that we created in our peripheral. In general, you should always check the peripheral driver source code whenever you create a peripheral because it often contains useful functions for the features you included in the peripheral.

  1. From the “Applications” tab, open “Sources” within the “Project: TestApp_Peripheral” tree. Open the TestApp_Peripheral.c source file.
  2. Replace all the code in this file with the following source and save the file.
#include "xparameters.h"
#include "xbasic_types.h"
#include "xgpio.h"
#include "xstatus.h"
#include "my_timer.h"

#define TIMER_RESET   0x00000000
#define TIMER_RUN     0x40000000
#define TIMER_EXPIRED 0x80000000
#define TIMER_HALFSEC 0x02FAF080

XGpio GpioOutput;
unsigned int *my_timer_p =
              (unsigned int *) XPAR_MY_TIMER_0_BASEADDR;

int main (void) {

  Xuint32 my_timer;
  Xuint32 status;

  // 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);

  // 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);

  while(1){
    // Turn OFF the LEDs
    XGpio_DiscreteWrite(&GpioOutput, 1, 0xF);
    // Start the timer and wait for it to expire
    MY_TIMER_mWriteSlaveReg1(my_timer, TIMER_RUN);
    while(!(MY_TIMER_mReadSlaveReg1(my_timer) & TIMER_EXPIRED)){}
    // Reset the timer
    MY_TIMER_mWriteSlaveReg1(my_timer, TIMER_RESET);
    // Turn ON the LEDs
    XGpio_DiscreteWrite(&GpioOutput, 1, 0x0);
    // Start the timer and wait for it to expire
    MY_TIMER_mWriteSlaveReg1(my_timer, TIMER_RUN);
    while(!(MY_TIMER_mReadSlaveReg1(my_timer) & TIMER_EXPIRED)){}
    // Reset the timer
    MY_TIMER_mWriteSlaveReg1(my_timer, TIMER_RESET);
  }
}

 

Download and Test the Project

  1. Turn on the XUPV2P board.
  2. From the XPS software, select “Device Configuration->Download Bitstream”.

The bitstream will be created and downloaded into the FPGA. When the application runs, you will see the LEDs on the XUPV2P board flashing once every second. The timer peripheral is set for a delay period of half a second. Every time the timer expires, the state of the LEDs is inverted so that the LEDs remain ON for half a second and OFF for half a second.

The project folder for this tutorial can be downloaded in a compressed ZIP file SimpleTimer.zip . Right-click on the link and select “Save Link As”.

In the next tutorial, Integrating a Black Box into a Peripheral, we show how to integrate a netlist (.NGC) file into a peripheral. For an improved version of the timer peripheral using interrupts, see Timer with Interrupts.


See also