Create a Peripheral using the Peripheral Wizard


Tutorial Overview

In this tutorial we will create a simple project that uses our own IP peripheral (instead of using the XPS General Purpose IO peripheral provided by Xilinx) to read from the DIP switches and write to the LEDs. The software application will display the DIP switch values on the LED outputs and also send the DIP switch values to the UART.

Any custom logic (IP) that you design must connect to the PLB to communicate with the Microblaze processor. When connected to the PLB, the IP becomes part of the memory map accessible to the Microblaze. The IP will have a base address and a high address signifying where in the memory map it resides, and how much of the memory map it occupies. The Microblaze interacts with the IP as though it were part of the memory.

In this example, we will use the Peripheral Wizard to create a basic IP peripheral that connects to the PLB and implements one 32 bit register. Writing to the peripheral through the PLB will allow us to change the contents of the register. The outputs of the register will drive the LEDs. We will modify the core so that reading from it through the PLB will return the DIP switch settings. The diagram below illustrates the peripheral and connections.

Figure: Peripheral for controlling the LEDs and reading the DIP switches

Note that another possibility for this design is to use another register for passing the DIP switch values through to the OPB handler. In this design, only one register was used to keep the design simple and to minimize the changes to the template.

This tutorial contains screenshots to guide you through the entire implementation process. Click on the images to view a higher resolution.

Requirements

Before following this tutorial, you will need to do the following:

  • Buy an ML505/ML506/ML507 or XUPV5 board if you don’t already have one. Xilinx supplies the ML50x boards, but the best deal is the XUPV5 from Digilent. Click the Digilent link for more information.

Create the Basic Project

Follow these steps to create the basic project:

  1. Open XPS. From the dialog box, select “Base System Builder wizard” and OK.
  2. You will be asked to specify which folder to place the project. Click “Browse” and create a new folder for the project. Click “OK”.  
  3. We are given the choice to create a new project or to create one using the template of another project. Tick “I would like to create a new design” and click “Next”.
  4. On the “Select Board” page, select “Xilinx” as the board vendor. Then select the board you are using (eg. “Virtex 5 ML505 Evaluation Platform”). Select “1” as the board revision. Click “Next”. (Note: If you are using the XUPV5 (ML509) board, you might find that it is not listed. Select “Virtex 5 ML505 Evaluation Platform” instead.)  
  5. On the “Select Processor” page, we normally have a choice between using the PowerPC “hard” processor, or the Microblaze “soft” processor. Since the Virtex-5 does not contain any PowerPCs, we can only select Microblaze. Click “Next”.  
  6. On the “Configure Microblaze” page, select the clock frequency to be 125MHz. We will use the RS232 port for debugging rather than the JTAG, so select “No debug”. Click “Next”.  
  7. In selecting the Additional IO Interfaces, leave RS232_Uart_1 ticked and un-tick everything else.  
  8. On the “Add Internal Peripherals” page, click “Next”.  
  9. On the “Software Setup” page, select RS232_Uart_1 for both STDIN and STDOUT. The Base System Builder can produce sample software applications to run on the Microblaze and test the memory and peripherals. For this project we will not need them as we will create our own. Un-tick “Memory Test” and “Peripheral Test”. Click “Next”.  
  10. Click “Generate”.
  11. Click “Finish”.
  12. If you are using the XUPV5 (ML509) board and you selected “Virtex 5 ML505 Evaluation Platform” in step 4 above, you must select the right Virtex-5 version in the project settings. Select “Project->Project Options” and set the Virtex-5 version to “XC5VLX110T”, package “FFG1136” and speed grade “-1”.

Create the Peripheral

We now create our custom 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. On the “Name and Version” page, type my_peripheral for the peripheral name. Click “Next”.  
  5. On the “Bus Interface” page, select “Processor Local Bus” (PLB) and click “Next”.  
  6. On the “IPIF Services” page, we can make the Peripheral Wizard generate our VHDL template to include different features. Select “User logic software register” and “Include data phase timer”, un-tick everything else and click “Next”.  
  7. On the “Slave Interface” page, click “Next”.  
  8. On the “User S/W Register” page, choose “1” for the number of software accessible registers. As the Microblaze is a 32-bit processor, we will choose 32-bits for the size of the register, even though we only need 8-bits for the 8 LEDs.  
  9. On the “IP Interconnect” page we can customize our connection to the PLB but we will leave everything as is for simplicity. Click “Next”.
  10. 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.
  11. 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”.
  12. Click “Finish”. Now our templates are created and we can modify them to suit our application.

Modify the Peripheral

The template created by the Peripheral Wizard implements a 32-bit register that we can read and write to via the PLB. When the peripheral is instantiated, we could access the register by reading and writing to the base address of the peripheral. We need to modify this functionality slightly, because of two things:

  1. We want to connect the outputs of the register to the LEDs – to achieve this we need to create an output port on the peripheral and connect it to the output of the register.
  2. We want to make all reads from the peripheral return the values of the DIP switches – to achieve this we need to create an input port on the peripheral and make it return these values on every read request.

Follow these steps to modify the peripheral:

  1. Select from the menu “File->Open” and browse to pcores\my_peripheral_v1_00_a\hdl\vhdl from the project folder. This folder contains two source files that describe our peripheral: my_peripheral.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.
  2. Open the file my_peripheral.vhd. We will need to add two ports to this source code, one for the LEDs and one for the DIP switches.
  3. Find the line of code that says --ADD USER PORTS BELOW THIS LINE and add these two lines of code:
  LED_Data : out std_logic_vector(0 to 7);
  DIP_Data : in std_logic_vector(0 to 7);
  1. Find the line of code that says --MAP USER PORTS BELOW THIS LINE and add these two lines of code:
  LED_Data => LED_Data,
  DIP_Data => DIP_Data,
  1. Save and close the file.
  2. Open the file user_logic.vhd. We will need to modify this source code to include the two new ports and to modify the behaviour.
  3. Find the line of code that says --ADD USER PORTS BELOW THIS LINE and add the following two lines of code.
  LED_Data : out std_logic_vector(0 to 7);
  DIP_Data : in std_logic_vector(0 to 7);
  1. Find the line of code that says --USER logic implementation added here and add the following line of code below. This connects the LED port to the first 8 bits of the register output.

LED_Data <= slv_reg0(24 to 31);

  1. Find the line of code that says IP2Bus_Data <= slv_ip2bus_data when slv_read_ack = '1' else (others => '0'); and replace it with the code below. Now, when the peripheral is read on the OPB, it will always return the DIP switch settings. As the bus is 32 bits long, we just fill the unused bits with zeros using the “others” keyword.
  IP2Bus_Data(0 to 23) <= (others=>'0');
  IP2Bus_Data(24 to 31) <= DIP_Data(0 to 7);
  1. Save and close the file.

Import the 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_peripheral. 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_peripheral_v1_00_a\data and select the my_peripheral_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 “PLB Slave” and click “Next”.  
  9. On the SPLB: Port page, click “Next”.
  10. On the SPLB: Parameter page, click “Next”.
  11. On the “Identify Interrupt Signals” page, un-tick “Select and configure interrupts” and click “Next”. We don’t need interrupts for our design.  
  12. On the “Parameter Attributes” page, click “Next”.
  13. On the “Port Attributes” page, click “Next”.
  14. Click “Finish”.

Now we have a peripheral that is 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 Microblaze.

  1. From the “IP Catalog” find the my_peripheral IP core in the “Project Local pcores” group. Right click on the core and select “Add IP”.  
  2. From the “System Assembly View” using the “Bus Interface” filter, connect the my_peripheral_0 to the mb_plb (PLB) bus.  
  3. Click on the “Ports” filter and open the my_peripheral_0 tree. There will be two ports listed LED_Data and DIP_Data. Type in a net name for each of them. The net name can be anything you like, but for this project let us call them LED_Data and DIP_Data for simplicity. For each net, use the option “Make External”. If you then click open the “External Ports” tree, you should see these nets listed.  
  4. Click on the “Addresses” filter. Change the “Size” for my_peripheral_0 to 64K. Then click “Generate Addresses”.

We have now created an instance of the peripheral in our design.

Link the External Ports to FPGA Pins

Now we have made the LED and DIP ports external however we need to link them to specific pins on the FPGA. We will use the schematic for the ML505 to get the correct pin numbers for each of the LEDs and DIP switches. Then we modify the User Constraints File (UCF) to include these pin assignments.

  1. Click the “Project” tab and double click on the UCF file to open it.
  2. Find the comment “## IO Devices constraints” and add the following lines of code below it:
#### Module LEDs_8Bit constraints

Net LED_Data_pin<0> LOC = AE24;
Net LED_Data_pin<0> IOSTANDARD=LVCMOS18;
Net LED_Data_pin<0> PULLDOWN;
Net LED_Data_pin<0> SLEW=SLOW;
Net LED_Data_pin<0> DRIVE=2;
Net LED_Data_pin<1> LOC = AD24;
Net LED_Data_pin<1> IOSTANDARD=LVCMOS18;
Net LED_Data_pin<1> PULLDOWN;
Net LED_Data_pin<1> SLEW=SLOW;
Net LED_Data_pin<1> DRIVE=2;
Net LED_Data_pin<2> LOC = AD25;
Net LED_Data_pin<2> IOSTANDARD=LVCMOS18;
Net LED_Data_pin<2> PULLDOWN;
Net LED_Data_pin<2> SLEW=SLOW;
Net LED_Data_pin<2> DRIVE=2;
Net LED_Data_pin<3> LOC = G16;
Net LED_Data_pin<3> IOSTANDARD=LVCMOS25;
Net LED_Data_pin<3> PULLDOWN;
Net LED_Data_pin<3> SLEW=SLOW;
Net LED_Data_pin<3> DRIVE=2;
Net LED_Data_pin<4> LOC = AD26;
Net LED_Data_pin<4> IOSTANDARD=LVCMOS18;
Net LED_Data_pin<4> PULLDOWN;
Net LED_Data_pin<4> SLEW=SLOW;
Net LED_Data_pin<4> DRIVE=2;
Net LED_Data_pin<5> LOC = G15;
Net LED_Data_pin<5> IOSTANDARD=LVCMOS25;
Net LED_Data_pin<5> PULLDOWN;
Net LED_Data_pin<5> SLEW=SLOW;
Net LED_Data_pin<5> DRIVE=2;
Net LED_Data_pin<6> LOC = L18;
Net LED_Data_pin<6> IOSTANDARD=LVCMOS25;
Net LED_Data_pin<6> PULLDOWN;
Net LED_Data_pin<6> SLEW=SLOW;
Net LED_Data_pin<6> DRIVE=2;
Net LED_Data_pin<7> LOC = H18;
Net LED_Data_pin<7> IOSTANDARD=LVCMOS25;
Net LED_Data_pin<7> PULLDOWN;
Net LED_Data_pin<7> SLEW=SLOW;
Net LED_Data_pin<7> DRIVE=2;

#### Module DIP_Switches_8Bit constraints

Net DIP_Data_pin<0> LOC=U25;
Net DIP_Data_pin<0> IOSTANDARD=LVCMOS18;
Net DIP_Data_pin<0> PULLDOWN;
Net DIP_Data_pin<0> SLEW=SLOW;
Net DIP_Data_pin<0> DRIVE=2;
Net DIP_Data_pin<1> LOC=AG27;
Net DIP_Data_pin<1> IOSTANDARD=LVCMOS18;
Net DIP_Data_pin<1> PULLDOWN;
Net DIP_Data_pin<1> SLEW=SLOW;
Net DIP_Data_pin<1> DRIVE=2;
Net DIP_Data_pin<2> LOC=AF25;
Net DIP_Data_pin<2> IOSTANDARD=LVCMOS18;
Net DIP_Data_pin<2> PULLDOWN;
Net DIP_Data_pin<2> SLEW=SLOW;
Net DIP_Data_pin<2> DRIVE=2;
Net DIP_Data_pin<3> LOC=AF26;
Net DIP_Data_pin<3> IOSTANDARD=LVCMOS18;
Net DIP_Data_pin<3> PULLDOWN;
Net DIP_Data_pin<3> SLEW=SLOW;
Net DIP_Data_pin<3> DRIVE=2;
Net DIP_Data_pin<4> LOC=AE27;
Net DIP_Data_pin<4> IOSTANDARD=LVCMOS18;
Net DIP_Data_pin<4> PULLDOWN;
Net DIP_Data_pin<4> SLEW=SLOW;
Net DIP_Data_pin<4> DRIVE=2;
Net DIP_Data_pin<5> LOC=AE26;
Net DIP_Data_pin<5> IOSTANDARD=LVCMOS18;
Net DIP_Data_pin<5> PULLDOWN;
Net DIP_Data_pin<5> SLEW=SLOW;
Net DIP_Data_pin<5> DRIVE=2;
Net DIP_Data_pin<6> LOC=AC25;
Net DIP_Data_pin<6> IOSTANDARD=LVCMOS18;
Net DIP_Data_pin<6> PULLDOWN;
Net DIP_Data_pin<6> SLEW=SLOW;
Net DIP_Data_pin<6> DRIVE=2;
Net DIP_Data_pin<7> LOC=AC24;
Net DIP_Data_pin<7> IOSTANDARD=LVCMOS18;
Net DIP_Data_pin<7> PULLDOWN;
Net DIP_Data_pin<7> SLEW=SLOW;
Net DIP_Data_pin<7> DRIVE=2;
  1. Save and close the file.

Create a Software Application

Now we need to create a simple software application to test the peripheral. The program simply needs to read and write to the peripheral through the PLB.

  1. Click on the “Applications” tab and double click “Add Software Application Project”.
  2. For the name, type “TestApp”. Select the microblaze_0 processor. Click “OK”.
  3. Right-click on Default: microblaze_0_bootloop and select “Mark to Initialize BRAMs” so that this application does not get executed on the Microblaze. It should now have a red cross through its icon.
  4. Right-click on “Project: TestApp” and select “Mark to Initialize BRAMs” to select our application for execution on the Microblaze. It should now not have a red cross through its icon.
  5. Select from the menu File->New, and choose “Text file”. Copy the code below into this new file and save the file as TestApp.c in a new folder called “TestApp” within the project folder.
#include "xparameters.h"
#include "my_peripheral.h"

void * my_peripheral_p;
Xuint32 my_peripheral;

int main (void) {

  unsigned int DataRead;
  unsigned int OldData;

  // Clear the screen
  xil_printf("%c[2J",27);

  // Check and get the device address
  my_peripheral_p = XPAR_MY_PERIPHERAL_0_BASEADDR;
  XASSERT_NONVOID(my_peripheral_p != XNULL);
  my_peripheral = (Xuint32) my_peripheral_p;

  OldData = (unsigned int) 0xffffffff;
  while(1){
    // Read the state of the DIP switches
    DataRead = MY_PERIPHERAL_mReadSlaveReg0(my_peripheral, 0);
    // Send the data to the UART if the settings change
    if(DataRead != OldData){
      xil_printf("DIP Switch settings: 0x%2X\r\n", DataRead);
      // Set the LED outputs to the DIP switch values
      MY_PERIPHERAL_mWriteSlaveReg0(my_peripheral, 0, DataRead);
      // Record the DIP switch settings
      OldData = DataRead;
    }
  }
}

 

  1. Right-click on “Sources” under the “Project: TestApp” tree and select “Add Existing Files”. Select the file we just saved.

The software application simply creates a pointer to the peripheral and makes reads and writes to the peripheral through the pointer. We can obtain the address of the peripheral by looking in the “System Assembly View” using the “Addresses” filter, but in this example, we use the XPAR_MY_PERIPHERAL_0_BASEADDR definition given in the xparameters.h file which is created automatically by XPS. By using this definition, instead of a fixed value, we don’t have to modify our code each time we modify the hardware and addresses.

Download and Test the Project

  1. Open a Hyperterminal window with the required settings. For the correct settings, see Hyperterminal Settings.
  2. Turn on the ML505 board.
  3. We can now download our hardware description and software application to the FPGA. From the XPS software, select “Device Configuration->Download Bitstream”.

When the application is running and Hyperterminal is open, the DIP switch settings should be seen on the LEDs and on the Hyperterminal screen. Change the DIP switches to see the message change. The Hyperterminal output should look similar to the screen shot below:

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

In the next tutorial, Timer with Interrupts, we develop our own timer peripheral so that we can make the LEDs flash.


See also