Embedded software

Software

The Xilinx software includes an integrated development environment (IDE). Before this can be used the generated hardware needs to be exported to an .xsa file. This file can be used in the Vitis IDE, which can be launched from the start menu or vivado.

Workspace creation

When starting Vitis you’re greeted with the window to create a workspace. It is recommended to use a single workspace per System-On-Chip project and .xsa file.

Make sure that in your path of the Vitis workspace there is no spaces! Otherwise your project will later not compile.

After selecting a suitable workspace location you can create a Application project. Then you press next to create a project.

Now it will ask for your platform. Here you can use a hardware platform that is built-in, but we have create our own hardware platform. So we need to press Create a new platform from hardware (XSA). Here you select the .xsa file you exported from Vivado previously.

When giving a name to your application project, make sure to NOT give it the same name as your IP or Vivado project.

Now you can press next a couple of times and create a demo application Hello World!

A hardware platform

Once a Vitis project is created 2 folders will be created in the project explorer the first one with the green symbol is the hardware platform. This layer contains the low level information on the hardware:

  • the addresses of the IP components that are connected
  • a bitstream for the reconfigurable fabric
  • the address map of the processor

This hardware platform is shared among all the applications that are created in the same Vitis workspace. Once the hardware is fixed (and bugfree) there is no reason why this should change, anyhow. It is possible to update the hardware by right clicking it and selecting update hardware specifications.

The hardware platform is generated during the export of the hardware in Vivado.

The user application

With a hardware platform and a board support package ready, you can create a user application. There are a number of templates provided in SDK. If you want to make sure your serial connection is working, it might be useful to start with the Hello world example. Otherwise an Empty application is the way to go.

Add some vitamin C

In case you hadn’t noticed yet, this processor is not as powerful as your laptop’s. Also the amount of available memory is slightly less. That aside, the biggest thing you’ll be missing is an operating system. Welcome to the exciting world of bare-metal programming. Here are a number of points, that are worth pointing out.

  • don’t use printf(“”) That C-function is too heavy to be using on our SOC. Xilinx has provided an alternative xil_printf(“”) which supports basic functionalities and is less heavy.
  • the standard input and output devices are a single serial port (COMx, /dev/ttyXXX). This can, off course, be changed but the default is a serial port. The baudrate is 115200.
  • a text base menu is a nice way to implement multiple features
  • there is no sleep function, out-of-the-box. The easiest way is shown in the example below ( pause(void) ), the best way is to add a timer IP block.

Memory-Mapped IO

As mentioned before, the IP core that we've created is Memory-Mapped. Upon the creation of the SOC, the available memory space is segmented. This way, our xmas-light IP component has a base address and a memory size. All the IO operations on the AXI bus are 32-bit operations. Hence, writing to the base address results in a write to slave register 0. The memory space, however, is byte-flavoured. Because there are still 4 bytes in a 32-bit word, this means that writing to slave register 1 requires writing to base address + 4. The board support package generates, among a lot of other things, a file xparameters.h. This file contains a lot of defines to make programming the SOC a little bit more generic. For example, there could be the following defines:
#define XPAR_XMAS_LIGHT_0_S00_AXI_BASEADDR       0x40C00000
#define XPAR_XMAS_LIGHT_0_S00_AXI_HIGHADDR       0x40C000FF

xparameters.h location

By including this header file, our software can simply use the human-friendly XPAR_XMAS_LIGHT_0_S00_AXI_BASEADDR identifier to refer to the base address of the component. Next to making our lives a little easier, this also provides a flexible solution. If our component is, for some reason, mapped to another address in another design, our software doesn’t need updating. Simply including xparameters.h will do the trick.

As we are the designers of this IP core, we know the mapping between the memory space and the functionalities. It can be useful, for ourself, to fix this mapping only once.

#define XMAS_LIGHT_COMMAND       XPAR_XMAS_LIGHT_0_S00_AXI_BASEADDR+0*4
#define XMAS_LIGHT_CR            XPAR_XMAS_LIGHT_0_S00_AXI_BASEADDR+1*4

Using this approach results in a (bit) more flexible and portable software.

If we want to write a command to the IP core, we need to give a value to the command and make the command valid bit high. The example below gives this functionality as function:

void xmas_light_write_command(uint32_t data) {
  Xil_Out32(XMAS_LIGHT_COMMAND, data);
  Xil_Out32(XMAS_LIGHT_CR, 0x00000001);
  Xil_Out32(XMAS_LIGHT_CR, 0x00000000);
}

The argument data is written to slave register 0. Remember this is positioned at the base address which we renamed to XMAS_LIGHT_COMMAND. When the data is written a 0x1 is written to slave register 1 after which a 0x0 is written. This has as effect that the command_valid bit goes high for a number of clock cycles.

For the sake of completeness it is mentioned there is also a similar reading function: Xil_In32(). An example is given on lin 98, in the code below.

Example

#include "xparameters.h" /* from bsp; contains a LOT of defines */
#include "xil_io.h"      /* from bsp; IO functions*/
#include "xuartps_hw.h"  /* from bsp; UART driver functions*/

/* Some defines to make our lives easier*/
#define XMAS_LIGHT_COMMAND       XPAR_XMAS_LIGHT_0_S00_AXI_BASEADDR+0*4
#define XMAS_LIGHT_CR            XPAR_XMAS_LIGHT_0_S00_AXI_BASEADDR+1*4

/* Forward declarations */
void pause(void);
void xmas_light_write_command(uint32_t data);

int main(void) {
  uint8_t choice = '0', counter;
  uint32_t value;

  /* make a text-based menu */
  while(choice != 'q') {
    xil_printf("LED commands\r\n");
    xil_printf("    0: blank all LEDs\r\n");
    xil_printf("    1: drive all LEDs\r\n");
    xil_printf("    a: blink all LEDs at 1 Hz\r\n");
    xil_printf("    b: blink all LEDs at 2 Hz\r\n");
    xil_printf("    c: blink all LEDs at 4 Hz\r\n");
    xil_printf("    d: blink all LEDs at 8 Hz\r\n");
    xil_printf("RGBLED commands\r\n");
    xil_printf("    e: drive all RGBLEDs: BLUE\r\n");
    xil_printf("    f: drive all RGBLEDs: GREEN\r\n");
    xil_printf("    g: drive all RGBLEDs: RED\r\n");
    xil_printf("General\r\n");
    xil_printf("    t: the fancy stuff\r\n");
    xil_printf("\r\n    q: quit\r\n  : ");

    /* get the choice */
    choice = XUartPs_RecvByte(XPAR_PS7_UART_0_BASEADDR);
    xil_printf("%c\r\n", choice);

    /* handle according to the choice ... and we work case INsensitive */
    if (choice == '0') { /* turn off ALL LEDs */
      xmas_light_write_command(0xF0);
      xmas_light_write_command(0x3F000);
    } else if (choice == '1') { /* turn on ALL LEDs */
      xmas_light_write_command(0xF1);
      xmas_light_write_command(0x3FF00);
    } else if ((choice == 'a')||(choice == 'A')) {
      xmas_light_write_command(0xFC);
    } else if ((choice == 'b')||(choice == 'B')) {
      xmas_light_write_command(0xFD);
    } else if ((choice == 'c')||(choice == 'C')) {
      xmas_light_write_command(0xFE);
    } else if ((choice == 'd')||(choice == 'D')) {
      xmas_light_write_command(0xFF);
    } else if ((choice == 'e')||(choice == 'E')) {
      xmas_light_write_command(0x24F00);
    } else if ((choice == 'f')||(choice == 'F')) {
      xmas_light_write_command(0x12F00);
    } else if ((choice == 'g')||(choice == 'G')) {
      xmas_light_write_command(0x09F00);
    } else if ((choice == 't')||(choice == 'T')) {
      /* the fancy stuff */
      xmas_light_write_command(0x3F000); // RGB LEDs off

      /* repeat 10 times */
      for(int j = 0;j<10;j++) {
        // fade in RED
        for(counter=0;counter<16;counter++) {
          xmas_light_write_command(0x01000+((counter)<<8)); // scale red
          pause();
        }
        // fade in GREEN
        for(counter=0;counter<16;counter++) {
          xmas_light_write_command(0x02000+((counter)<<8)); // scale blue
          pause();
        }
        // fade out RED
        for(counter=0;counter<16;counter++) {
          xmas_light_write_command(0x01000+((15-counter)<<8)); // scale red down
          pause();
        }
        // fade in BLUE
        for(counter=0;counter<16;counter++) {
          xmas_light_write_command(0x04000+((counter)<<8)); // scale blue
          pause();
        }
        // fade out GREEN
        for(counter=0;counter<16;counter++) {
          xmas_light_write_command(0x02000+((15-counter)<<8)); // scale red down
          pause();
        }
        // fade out BLUE
        for(counter=0;counter<16;counter++) {
          xmas_light_write_command(0x04000+((15-counter)<<8)); // scale red down
          pause();
        }
      }

    } else if ((choice == 'r')||(choice == 'R')) {
      xil_printf("  read: %08x\r\n", Xil_In32(XMAS_LIGHT_COMMAND));
      xmas_light_write_command(0xFF);
    } else if ((choice == 'q')||(choice == 'Q')) {
      xil_printf("Byebye\r\n");
    } else {
      xil_printf("Sorry '%c' is not (yet) supported\r\n", choice);
    }
  };

  return 0;
}

void pause(void) {
  for(int i=0;i<1024;i++) {
    for(int j=0;j<1024;j++) {
      asm("nop");
  }}
}


void xmas_light_write_command(uint32_t data) {
  Xil_Out32(XMAS_LIGHT_COMMAND, data);
  Xil_Out32(XMAS_LIGHT_CR, 0x00000001);
  Xil_Out32(XMAS_LIGHT_CR, 0x00000000);
}

From the SDK software you can program the FPGA. This needs to be done first. After configuring the bitstream, the software can be run on the hardware. This should look something like the example below.

Exercise

  1. Generate the SOC for yourself
  2. Run the code from the example above
  3. Get creative and make some cool light effects