Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

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

Please read this entire page before using the VGA core in your design (I know it's long, but it'll help, I promise). If you find any issues with the core, ask a demonstrator or email russell.joyce@york.ac.uk.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

  • Download First, copy the 'embs_vga.tar.gz and extract it _v1_00_a' folder from the code repository/archive to the 'pcores' directory within the root of your XPS project:
Code Block
languagetext
themeEclipse
cd {your XPS project}/pcores/
mv ~/Downloads/embs_vga.tar.gz .
tar xzf embs_vga.tar.gz
rm embs_vga.tar.gz 
  • Load your project in XPS
  • Select Project | Rescan User Repositories.
  • Select the .
  • 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  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 '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  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  component and set its 'Pixel_Clk' frequency  frequency to 40.000000 MHz 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 last 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 in to into it:
Code Block
languagetext
themeEclipse
titleVGA Core Constraints
collapsetrue
# 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; 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.

...

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.

...

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:

...


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.


 

Code Block
languagecpp
themeEclipse
titlevgatest.c
linenumberstrue
collapsetrue
#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):

...

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

...

Code Block
languagecpp
themeEclipse
titlevgatest2vga-patterns.c
linenumberstrue
collapsetrue
#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%