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 embs_vga.tar.gz and extract it 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:
...
language | text |
---|---|
theme | Eclipse |
...
- .
...
- 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
andVSync
connections (notPixel_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 withExternal 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 itsPixel_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.
...
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:
Resolution | Pxl_Clk Frequency (MHz) |
---|---|
640x480 | 25.000000 |
800x600 | 40.000000 |
1024x768 | 65.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.
Resolution | Bits per pixel | Bytes read per second |
---|---|---|
640x480 | 4 | 9,216,000 |
640x480 | 8 | 18,432,000 |
800x600 | 4 | 14,400,000 |
800x600 | 8 | 28,800,000 |
1024x768 | 4 | 23,592,960 |
1024x768 | 8 | 47,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.
Resolution | Bits per pixel | Decimal size (bytes) | Hex size (bytes) |
---|---|---|---|
640x480 | 4 | 153,600 | 0x25800 |
640x480 | 8 | 307,200 | 0x4B000 |
800x600 | 4 | 240,000 | 0x3A980 |
800x600 | 8 | 480,000 | 0x75300 |
1024x768 | 4 | 393,216 | 0x60000 |
1024x768 | 8 | 786,432 | 0xC0000 |
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 | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
| ||||||||||
#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:
Resolution | Bits per pixel | Image |
---|---|---|
640x480 | 4 | vgatest640x4.bin |
640x480 | 8 | vgatest640x8.bin |
800x600 | 4 | vgatest800x4.bin |
800x600 | 8 | vgatest800x8.bin |
1024x768 | 4 | vgatest1024x4.bin |
1024x768 | 8 | vgatest1024x8.bin |
Example 2: Generating patterns in C
...
Code Block | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
| ||||||||||
#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 Utilisation | Used | Available | Utilisation |
---|---|---|---|
Number of Slices | 373 | 4,656 | 8% |
Number of Slice Flip Flops | 437 | 9,312 | 4% |
Number of 4 input LUTs | 528 | 9,312 | 5% |
Number of bonded IOBs | 0 | 232 | 0% |
Number of BRAMs | 1 | 20 | 5% |