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.
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!
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:
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.
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.
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.
#define XPAR_XMAS_LIGHT_0_S00_AXI_BASEADDR 0x40C00000
#define XPAR_XMAS_LIGHT_0_S00_AXI_HIGHADDR 0x40C000FF
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.
#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.