S3ESK VGA Core

The S3ESK VGA Core (previously the EMBS VGA Core) provides a 3-bit (eight colour) display output at 640x480, 800x600 or 1024x768 for the Spartan-3E Starter Kit boards, previously used in the EMBS module.

The IP core source files and example code mentioned below can be found on GitHub - you can download them in a zip file from https://github.com/RTSYork/s3esk-vga/archive/master.zip.

Tips on using VGA in the CS hardware labs

  • The '4:3 in Wide' button on the lab monitors can be used to stop the image stretching to an incorrect aspect ratio.
  • If the image flickers or does not line up correctly with the edges of the screen, try using the 'Auto' button while the display is filled with non-black graphics that touch all four edges of the screen (the test images should work for this).
  • If the monitor complains that the resolution is unsupported, double check that your pixel clock value matches the resolution you've selected (see the table below). The lab monitors should correctly identify the three valid resolutions available with the core as: 640x480@61Hz, 800x600@60Hz and 1024x768@60Hz.

Adding the core to your design

  • First, copy the 'embs_vga_v1_00_a' folder from the code repository/archive to the 'pcores' directory within the root of your XPS project.
  • Load your project in XPS
  • Select Project | Rescan User Repositories.
  • Select the 'IP Catalog' tab on the left-hand side, and expand 'Project Local PCores' then 'EMBS'. You should see a component called 'VGA Core'.
  • Double click this component and answer 'Yes' then 'OK' (the sections below on resolution and pixel width explain how the settings here can be changed, but for now the defaults are fine).
  • Select the 'System Assembly View' tab then 'Bus Interfaces'.
  • Find the embs_vga_0 component and expand it by clicking the +
  • It has two bus connections, MPLB (master PLB) and SPLB (slave PLB). Use the drop down boxes to connect these both to mb_plb.
  • Click the 'Addresses' tab and click the 'Generate Addresses' button. (A yellow icon on its own in the top right)
  • Click the 'Ports' tab and expand the embs_vga_0 component by clicking the +
  • Select the Red, Green, Blue, HSync and VSync connections (not Pixel_Clk), right click and select 'Make External' from the menu. The 'Connected Port' column for each signal should automatically fill in with a value starting with External Ports::embs_vga_0_ followed by the signal name and _pin. This connects the five VGA signals to external ports (it routes them to outside the FPGA).
  • Now select Hardware | Launch Clock Wizard
  • A dialogue may appear telling you "The following clock ports are editable...". Click OK.
  • Find the embs_vga_0 component and set its Pixel_Clk frequency to 40.000000 MHz (you may have to type this if it is not in the drop-down list). This value will be different if a different output resolution is chosen (see below).
  • Click Validate Clocks. It should say "Clock Validations were successful!".
  • Click OK. Click OK on the "Please make sure..." box that appears.

We have finished connecting up the VGA component inside the FPGA.
The final thing we need to do it to tell the tools which external pins on the FPGA to use for the Red, Green, Blue, HSync and VSync signals, and to relax the timing constraints between the PLB and pixel clocks (so the static timing analysis phase doesn't moan at us).

  • Open your project's User Constraints File by double-clicking 'UCF File' in the 'Project Files' list of the 'Project' window in XPS (alternatively, open <your XPS project>/data/system.ucf in a text editor).
  • Scroll to the bottom of the file and copy and paste the following into it:
VGA Core Constraints
# EMBS VGA Core
NET "embs_vga_0_Red_pin" LOC = "H14" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
NET "embs_vga_0_Green_pin" LOC = "H15" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
NET "embs_vga_0_Blue_pin" LOC = "G15" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
NET "embs_vga_0_HSync_pin" LOC = "F15" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
NET "embs_vga_0_VSync_pin" LOC = "F14" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
PIN "embs_vga_0.MPLB_Clk" TNM_NET = clk1;
PIN "embs_vga_0.Pixel_Clk" TNM_NET = clk2;
TIMESPEC TS_clk1_clk2 = FROM "clk1" TO "clk2" 10 ns DATAPATHONLY;
TIMESPEC TS_clk2_clk1 = FROM "clk2" TO "clk1" 10 ns DATAPATHONLY;
    • The NET constraints tell the tools where each signal should be routed and the logic level type to use; e.g. connect embs_vga_0_Red_pin to pin H14 on the FPGA chip, which is routed on the board to the red pin of the VGA socket, and use TTL (5V) logic signals.
    • The TIMESPEC constraint tells to tools to allow up to 10ns of difference between the bus and pixel clock domains used by the core, which run at different speeds - this is fine as the data is buffered using asynchronous FIFOs within the core.
  • Save and close the UCF file.
  • Click Hardware | Generate Bitstream.
  • Re-export your design to SDK.
  • In SDK, right click on your BSP and select Board Support Package Settings.
  • Select Drivers. embs_vga_0 should be in the list. If it is not, close and re-open SDK.
  • Set the driver of embs_vga_0 to generic and click OK.

Core options

The following options can be changed from the peripheral settings in XPS (accessed by double clicking the instance of the core in your project).

Output resolution

The EMBS VGA core supports three display resolutions: 640x480, 800x600 and 1024x768, all running at approximately 60Hz (i.e. the image is refreshed 60 times per second). The resolution must be set in XPS before synthesis. In order to change the resolution of the core, the desired value must be selected and the correct clock speed for 'Pixel_Clk' set in the Clock Wizard.

Clock speeds are:

ResolutionPxl_Clk Frequency (MHz)
640x48025.000000
800x60040.000000
1024x76865.000000

If you have the wrong combination of clock speed and resolution, your project will either fail to meet timing constraints during synthesis or the monitor will complain that the VGA signal is unsupported.

Pixel data width in DDR

The core can be set to read either 4 or 8 bits from memory for each pixel (i.e. two or one pixels per byte). This does not affect the image output (as each pixel always has only 3 bits of data), but does affect how data should be written to the frame buffer from the MicroBlaze. Keeping the core set to 8 bits-per-pixel is the easiest option, as each pixel can be individually written in DDR. If changed to 4 bits-per-pixel, two pixels (one byte) must be written at the same time (or a byte read, one pixel changed, then re-written). The advantages of using 4 bits-per-pixel are a smaller frame buffer and lower PLB and DDR utilisation, however the awkwardness of writing two pixels at once means that 8 bits-per-pixel is more practical unless there are speed or bandwidth concerns.

A note on memory bandwidth

While a VGA core set to 1024x768 resolution at 8 bits-per-pixel will function fine if it is the only part of the system accessing DDR, it uses a lot of memory and PLB bandwidth. If your system is experiencing delays accessing the PLB or DDR, or the graphics output has problems, try reducing the resolution or bits-per-pixel values. As a rough guide to relative PLB and memory utilisation, the following table shows the number of bytes read from DDR per second for each configuration.

ResolutionBits per pixelBytes read per second
640x48049,216,000
640x480818,432,000
800x600414,400,000
800x600828,800,000
1024x768423,592,960
1024x768847,185,920

ChipScope debug ports

One of the choices in the peripheral options is to include ChipScope debug ports. This adds several extra output ports to the core for monitoring internal signals using ChipScope Pro software, and should be left set to 'FALSE' unless ChipScope is being used to debug the core's operation.

Using the EMBS VGA core

The VGA core designates a given chunk of DDR memory as a video frame buffer. It reads the frame from this memory and draws it on the screen. The MicroBlaze can write graphics data into this memory for the VGA core to draw. The first location in the frame buffer corresponds to the top-left of the image, with subsequent locations moving right until the end of a line, then moving to the far left of the line below. At the end of each frame, the read location will reset to the start of the frame buffer. The size of the frame buffer varies depending on the core settings used, as shown in the following table.

ResolutionBits per pixelDecimal size (bytes)Hex size (bytes)
640x4804153,6000x25800
640x4808307,2000x4B000
800x6004240,0000x3A980
800x6008480,0000x75300
1024x7684393,2160x60000
1024x7688786,4320xC0000

The VGA core includes two software-accessible registers:

  • XPAR_EMBS_VGA_0_BASEADDR - Frame Buffer Base Address Register

  • XPAR_EMBS_VGA_0_BASEADDR + 1 - Control Register

Important: the frame buffer location must be set to a valid address in DDR before the core is enabled, otherwise graphics may not be aligned correctly on the display.

Setting the frame buffer location

The first register sets the location in DDR that the core reads the frame buffer from.
For example, to set the frame buffer at the start of DDR, you can use the following C code:

*((volatile unsigned int *) XPAR_EMBS_VGA_0_BASEADDR) = XPAR_DDR_SDRAM_MPMC_BASEADDR;

The location of the frame buffer can be changed at any time by writing a different value to the register.

Controlling the core

Writing a 1 to the control register will enable graphics output from the core, and writing a 0 will disable it.

The default state is disabled, so before the core will output graphics it must be enabled by setting the control register to 1:

*((volatile unsigned int *) XPAR_EMBS_VGA_0_BASEADDR + 1) = 1;

When the core is disabled it will not read any data from DDR, and it will hold the three colour output signals low and the two sync signals high.

Pixel format

The core uses a three-bit pixel value to produce a total of eight possible colours. This limitation is due to the way the FPGA board is wired up, with only a digital output connected to each of the red, green and blue VGA pins instead of an analogue one.

The lower three bits of each 4- or 8-bit pixel are the values of red, green and blue, as follows.

8-bit pixel width

'Byte 1 | Byte 2 | Byte 3 | Byte 4
00000RGB|00000RGB|00000RGB|00000RGB
     Px1      Px2      Px3      Px4

4-bit pixel width

'Byte 1 | Byte 2 | Byte 3 | Byte 4 
0RGB0RGB|0RGB0RGB|0RGB0RGB|0RGB0RGB
 Px1 Px2  Px3 Px4  Px5 Px6  Px7 Px8

So, for example the following C code sets four pixels (starting at location 'x') to red, green, blue and white respectively, for either 4 or 8 bits-per-pixel.

// For 8 bits-per-pixel
*((volatile unsigned char *) x)     = 0b00000100; // Red pixel
*((volatile unsigned char *) x + 1) = 0b00000010; // Green pixel
*((volatile unsigned char *) x + 2) = 0b00000001; // Blue pixel
*((volatile unsigned char *) x + 3) = 0b00000111; // White pixel
 
// For 4 bits-per-pixel
*((volatile unsigned char *) x)     = 0b01000010; // Red and green pixels
*((volatile unsigned char *) x + 1) = 0b00010111; // Blue and white pixels


The available eight colours are:
0b0000 or 0x0: Black
0b0001 or 0x1: Blue
0b0010 or 0x2: Green
0b0011 or 0x3: Cyan
0b0100 or 0x4: Red
0b0101 or 0x5: Magenta
0b0110 or 0x6: Yellow
0b0111 or 0x7: White

Example code

The following two examples can be used to test the EMBS VGA core.

Example 1: Display image from UART


The following C program can be used to copy an image received over the UART to the frame buffer. This can be useful to test that the core is set up correctly.


vgatest.c
#include "xparameters.h"
#include "xil_types.h"
#include "xuartlite_l.h"

int main (void) {
	// Put frame buffer at start of DDR
	u32 buffer = XPAR_DDR_SDRAM_MPMC_BASEADDR;

	// Set VGA core base address to buffer
	*((volatile u32 *)XPAR_EMBS_VGA_0_BASEADDR) = buffer;

	// Enable VGA core output
	*((volatile u32 *)XPAR_EMBS_VGA_0_BASEADDR + 1) = 1;

	while (1) {
		// Copy UART received byte to next location in frame buffer
		*((volatile u8 *)buffer++) = XUartLite_RecvByte(XPAR_RS232_DTE_BASEADDR);
	}

	return 0;
}


To transmit a binary image (see below) over the UART, the following command can be used in a Linux terminal (make sure all other screen sessions to the serial port have been closed):

cat image.bin > /dev/ttyS0

Where 'image.bin' is the name of the image file downloaded below. The following images can be used for each resolution and pixel width setting:

ResolutionBits per pixelImage
640x4804vgatest640x4.bin
640x4808vgatest640x8.bin
800x6004vgatest800x4.bin
800x6008vgatest800x8.bin
1024x7684vgatest1024x4.bin
1024x7688vgatest1024x8.bin

Example 2: Generating patterns in C

The following C program generates several test patterns and displays them using the VGA core. It will progress to the next pattern whenever it receives a byte over the UART (the easiest way to do this is to open a screen session and press any key to advance). It also prints out its current status over the UART.

vga-patterns.c
#include "xparameters.h"
#include <stdio.h>
#include "xil_types.h"
#include "xuartlite_l.h"


///////////////////////////////////////////////////
// Set these values to match those chosen in XPS //
///////////////////////////////////////////////////
#define WIDTH          800
#define HEIGHT         600
#define BITS_PER_PIXEL 8
///////////////////////////////////////////////////


// Set frame buffer location to start of DDR
#define FRAME_BUFFER XPAR_DDR_SDRAM_MPMC_BASEADDR


#if (BITS_PER_PIXEL == 4)

// Colour definitions (2 pixels per byte)
#define BLACK   0b00000000
#define WHITE   0b01110111
#define RED     0b01000100
#define GREEN   0b00100010
#define BLUE    0b00010001
#define CYAN    0b00110011
#define YELLOW  0b01100110
#define MAGENTA 0b01010101

// Draws a rectangle of solid colour on the screen
void drawRect(int xLoc, int yLoc, int width, int height, u8 colour) {
	int x, y;

	for (y = yLoc; y < yLoc + height; y++) {
		for (x = xLoc; x < xLoc + width; x+=2) {
			*((volatile u8 *) FRAME_BUFFER + x/2 + (WIDTH/2 * y)) = colour;
		}
	}
}

// Fills the screen with a 1-pixel chequered pattern
void fillChequers(void) {
	int x, y, odd = 0;

	for (y = 0; y < HEIGHT; y++) {
		for (x = 0; x < WIDTH; x+=2) {
			*((volatile u8 *) FRAME_BUFFER + x/2 + (WIDTH/2 * y)) = (odd ? 0b01110000 : 0b00000111);
		}
		odd = !odd;
	}
}

// Fills the screen with 1-pixel horizontal stripes
void fillStripes(void) {
	int x, y, odd = 0;

	for (y = 0; y < HEIGHT; y++) {
		for (x = 0; x < WIDTH; x+=2) {
			*((volatile u8 *) FRAME_BUFFER + x/2 + (WIDTH/2 * y)) = (odd ? BLACK : WHITE);
		}
		odd = !odd;
	}
}

// Fills quarters of the screen with the specified colours
void fillQuarters(u8 colour1, u8 colour2) {
	drawRect(0, 0, WIDTH/2, HEIGHT/2, colour1);
	drawRect(WIDTH/2, 0, WIDTH/2, HEIGHT/2, colour2);
	drawRect(0, HEIGHT/2, WIDTH/2, HEIGHT/2, colour2);
	drawRect(WIDTH/2, HEIGHT/2, WIDTH/2, HEIGHT/2, colour1);
}

// Draws a white 1-pixel dot in each corner of the screen
void drawDots(void) {
	*((volatile u8 *) FRAME_BUFFER) = 0b01110000;
	*((volatile u8 *) FRAME_BUFFER + WIDTH/2 - 1) = 0b00000111;
	*((volatile u8 *) FRAME_BUFFER + ((HEIGHT - 1) * WIDTH/2)) = 0b01110000;
	*((volatile u8 *) FRAME_BUFFER + ((HEIGHT - 1) * WIDTH/2) + (WIDTH/2 - 1)) = 0b00000111;
}

// Draws a white border around the screen
void drawBorder(void) {
	int i;
	for (i = 0; i < HEIGHT; i++) {
		*((volatile u8 *) FRAME_BUFFER + (i * WIDTH/2)) = 0b01110000;
		*((volatile u8 *) FRAME_BUFFER + (i * WIDTH/2) + WIDTH/2 - 1) = 0b00000111;
	}
	for (i = 0; i < WIDTH/2; i++) {
		*((volatile u8 *) FRAME_BUFFER + i) = WHITE;
		*((volatile u8 *) FRAME_BUFFER + i + ((HEIGHT - 1) * WIDTH/2)) = WHITE;
	}
}

#elif (BITS_PER_PIXEL == 8)

// Colour definitions (1 pixel per byte)
#define BLACK   0b00000000
#define WHITE   0b00000111
#define RED     0b00000100
#define GREEN   0b00000010
#define BLUE    0b00000001
#define CYAN    0b00000011
#define YELLOW  0b00000110
#define MAGENTA 0b00000101

// Draws a rectangle of solid colour on the screen
void drawRect(int xLoc, int yLoc, int width, int height, u8 colour) {
	int x, y;

	for (y = yLoc; y < yLoc + height; y++) {
		for (x = xLoc; x < xLoc + width; x++) {
			*((volatile u8 *) FRAME_BUFFER + x + (WIDTH * y)) = colour;
		}
	}
}

// Fills the screen with a 1-pixel chequered pattern
void fillChequers(void) {
	int x, y, odd = 0;

	for (y = 0; y < HEIGHT; y++) {
		for (x = 0; x < WIDTH; x++) {
			*((volatile u8 *) FRAME_BUFFER + x + (WIDTH * y)) = (odd ? BLACK : WHITE);
			odd = !odd;
		}
		odd = !odd;
	}
}

// Fills the screen with 1-pixel horizontal stripes
void fillStripes(void) {
	int x, y, odd = 0;

	for (y = 0; y < HEIGHT; y++) {
		for (x = 0; x < WIDTH; x++) {
			*((volatile u8 *) FRAME_BUFFER + x + (WIDTH * y)) = (odd ? BLACK : WHITE);
		}
		odd = !odd;
	}
}

// Fills quarters of the screen with the specified colours
void fillQuarters(u8 colour1, u8 colour2) {
	drawRect(0, 0, WIDTH/2, HEIGHT/2, colour1);
	drawRect(WIDTH/2, 0, WIDTH/2, HEIGHT/2, colour2);
	drawRect(0, HEIGHT/2, WIDTH/2, HEIGHT/2, colour2);
	drawRect(WIDTH/2, HEIGHT/2, WIDTH/2, HEIGHT/2, colour1);
}

// Draws a white 1-pixel dot in each corner of the screen
void drawDots(void) {
	*((volatile u8 *) FRAME_BUFFER) = WHITE;
	*((volatile u8 *) FRAME_BUFFER + WIDTH - 1) = WHITE;
	*((volatile u8 *) FRAME_BUFFER + ((HEIGHT - 1) * WIDTH)) = WHITE;
	*((volatile u8 *) FRAME_BUFFER + ((HEIGHT - 1) * WIDTH) + (WIDTH - 1)) = WHITE;
}

// Draws a white border around the screen
void drawBorder(void) {
	int i;
	for (i = 0; i < HEIGHT; i++) {
		*((volatile u8 *) FRAME_BUFFER + (i * WIDTH)) = WHITE;
		*((volatile u8 *) FRAME_BUFFER + (i * WIDTH) + WIDTH - 1) = WHITE;
	}
	for (i = 0; i < WIDTH; i++) {
		*((volatile u8 *) FRAME_BUFFER + i) = WHITE;
		*((volatile u8 *) FRAME_BUFFER + i + ((HEIGHT - 1) * WIDTH)) = WHITE;
	}
}

#endif


// Waits for any byte to be received over UART
void waitForKey(void) {
	XUartLite_RecvByte(XPAR_RS232_DTE_BASEADDR);
}



int main(void) {
	print("\r\n----\r\nVGA test running. Press any key to enable output.\r\n");

	// Set VGA core frame buffer location
	*((volatile unsigned int *) XPAR_EMBS_VGA_0_BASEADDR) = FRAME_BUFFER;

	waitForKey();

	while (1) {
		print("Enabling output\r\n");
		*((volatile unsigned int *) XPAR_EMBS_VGA_0_BASEADDR + 1) = 1;
		waitForKey();

		print("Chequers\r\n");
		fillChequers();
		waitForKey();

		print("Stripes\r\n");
		fillStripes();
		waitForKey();

		print("Squares\r\n");
		fillQuarters(BLACK, WHITE);
		waitForKey();

		print("Coloured squares\r\n");
		fillQuarters(BLUE, GREEN);
		waitForKey();

		print("Red screen\r\n");
		drawRect(0, 0, WIDTH, HEIGHT, RED);
		waitForKey();

		print("Green screen\r\n");
		drawRect(0, 0, WIDTH, HEIGHT, GREEN);
		waitForKey();

		print("Blue screen\r\n");
		drawRect(0, 0, WIDTH, HEIGHT, BLUE);
		waitForKey();

		print("White screen\r\n");
		drawRect(0, 0, WIDTH, HEIGHT, WHITE);
		waitForKey();

		print("Black screen\r\n");
		drawRect(0, 0, WIDTH, HEIGHT, BLACK);
		waitForKey();

		print("Corner dots\r\n");
		drawDots();
		waitForKey();

		print("Screen border\r\n");
		drawBorder();
		waitForKey();

		print("Changing frame buffer location\r\n");
		*((volatile unsigned int *) XPAR_EMBS_VGA_0_BASEADDR) = FRAME_BUFFER + 0x00500000;
		waitForKey();

		print("Restoring frame buffer location\r\n");
		*((volatile unsigned int *) XPAR_EMBS_VGA_0_BASEADDR) = FRAME_BUFFER;
		waitForKey();

		print("Disabling output\r\n");
		*((volatile unsigned int *) XPAR_EMBS_VGA_0_BASEADDR + 1) = 0;
		waitForKey();
	}

	return 0;
}

The file contains functions for 4- and 8-bit output, and allows the screen resolution to be set, so make sure the constants at the top of the file are correct before use.

FPGA Resource Usage

On a Spartan-3E xc3s500e (the FPGA that is on the Starter Board), Xilinx ISE estimates the core usage at:

Logic UtilisationUsedAvailableUtilisation
Number of Slices3734,6568%
Number of Slice Flip Flops4379,3124%
Number of 4 input LUTs5289,3125%
Number of bonded IOBs02320%
Number of BRAMs1205%