FPGA VGA Graphics in Verilog Part 1


This tutorial series introduces video graphics programming using FPGAs, starting with creating a VGA driver and moving onto more advanced features including bitmaps, sprites and effects. FPGAs excel at high-speed I/O and custom logic: you'll be surprised how much you can achieve with a few lines of Verilog.

This series is designed around the Digilent Arty, Basys 3 and Nexys Video boards. If you're using the Arty or Nexys Video you also need the VGA Pmod (the Basys 3 has VGA output built-in). Detailed requirements are given below.

I assume you have a basic understanding of Verilog and are comfortable using Xilinx's Vivado software. If you're new to FPGA development try Getting Started with Verilog & Vivado first.

Once you've completed this tutorial, move onto part 2 where we introduce bitmapped displays.

Find the code and resources for this and other FPGA tutorials at github.com/WillGreen/timetoexplore.

Feedback to @WillFlux is most welcome. Updated January 2019.


  1. Digilent Arty A7-35T, Arty S7-50T, Basys 3, or Nexys Video board (see below for other boards)
  2. VGA Pmod if using the Arty or Nexys Video (Basys 3 has VGA built-in)
  3. VGA capable monitor & cable
  4. Micro USB cable to program and power the board
  5. Xilinx Vivado installed (including Digilent board files)

Other FPGA Boards

If you're using a different FPGA board you should still be able to follow this tutorial with a few changes to the top.v module:

  1. Hardware I/O: update hardware ports, such as CLK and VGA_R, to match your board.
  2. Clock: if your board clock isn't 100 MHz, you need to update the pixel clock code.
  3. VGA Outputs: if your VGA output isn't 4-bits per colour, adjust VGA assign statements.

How VGA Works

VGA is an analogue video standard using a 15-pin D-sub connector. It doesn't require high clock speeds or complex encoding, so is an excellent place to begin when learning about FPGA graphics. VGA has five main signal pins: one for each of red, green, and blue and two for sync. Horizontal sync demarcates a line. Vertical sync demarcates a screen, also known as a frame.

VGA signals have two phases: drawing pixels and the blanking interval. The sync signals occur within blanking intervals; separated from pixel drawing by the front porch and back porch. When VGA was developed monitors were based on cathode ray tubes (CRTs): the blanking interval gives time for voltage levels to stabilise and for the electron gun to return to the start of a line or screen.

In this article, we'll be creating a classic 640x480 60 Hz VGA display. The required pixel clock is 25 MHz1, which is a nice round fraction of our board's 100 MHz clock. The key to producing a valid VGA signal is getting the timings right. For simplicity, we're going to do our timings in pixels and lines. Each pixel is a tick of the 25 MHz pixel clock (40 ns). A line is a complete set of horizontal pixels.

Horizontal Pixel Timings

  • Front Porch: 16
  • Sync Pulse: 96
  • Back Porch: 48
  • Active Video: 640
  • Total pixels: 800

Vertical Line Timings (updated Jan 2019)

  • Active Video: 480
  • Front Porch: 10
  • Sync Pulse: 2
  • Back Porch: 33
  • Total lines: 525

To learn more about display timings, including for HD, see Video Timings: VGA, SVGA, 720P, 1080P.

VGA Module

Create a new RTL project in Vivado called vga01 with the Arty, Basys 3, or Nexys Video board as the target. If you need advice on project creation see part 1 of my introductory tutorial series.

Within your new project create a design source called vga640x480.v [view source]:

module vga640x480(
    input wire i_clk,           // base clock
    input wire i_pix_stb,       // pixel clock strobe
    input wire i_rst,           // reset: restarts frame
    output wire o_hs,           // horizontal sync
    output wire o_vs,           // vertical sync
    output wire o_blanking,     // high during blanking interval
    output wire o_active,       // high during active pixel drawing
    output wire o_screenend,    // high for one tick at the end of screen
    output wire o_animate,      // high for one tick at end of active drawing
    output wire [9:0] o_x,      // current pixel x position
    output wire [8:0] o_y       // current pixel y position

    // VGA timings https://timetoexplore.net/blog/video-timings-vga-720p-1080p
    localparam HS_STA = 16;              // horizontal sync start
    localparam HS_END = 16 + 96;         // horizontal sync end
    localparam HA_STA = 16 + 96 + 48;    // horizontal active pixel start
    localparam VS_STA = 480 + 10;        // vertical sync start
    localparam VS_END = 480 + 10 + 2;    // vertical sync end
    localparam VA_END = 480;             // vertical active pixel end
    localparam LINE   = 800;             // complete line (pixels)
    localparam SCREEN = 525;             // complete screen (lines)

    reg [9:0] h_count;  // line position
    reg [9:0] v_count;  // screen position

    // generate sync signals (active low for 640x480)
    assign o_hs = ~((h_count >= HS_STA) & (h_count < HS_END));
    assign o_vs = ~((v_count >= VS_STA) & (v_count < VS_END));

    // keep x and y bound within the active pixels
    assign o_x = (h_count < HA_STA) ? 0 : (h_count - HA_STA);
    assign o_y = (v_count >= VA_END) ? (VA_END - 1) : (v_count);

    // blanking: high within the blanking period
    assign o_blanking = ((h_count < HA_STA) | (v_count > VA_END - 1));

    // active: high during active pixel drawing
    assign o_active = ~((h_count < HA_STA) | (v_count > VA_END - 1)); 

    // screenend: high for one tick at the end of the screen
    assign o_screenend = ((v_count == SCREEN - 1) & (h_count == LINE));

    // animate: high for one tick at the end of the final active pixel line
    assign o_animate = ((v_count == VA_END - 1) & (h_count == LINE));

    always @ (posedge i_clk)
        if (i_rst)  // reset to start of frame
            h_count <= 0;
            v_count <= 0;
        if (i_pix_stb)  // once per pixel
            if (h_count == LINE)  // end of line
                h_count <= 0;
                v_count <= v_count + 1;
                h_count <= h_count + 1;

            if (v_count == SCREEN)  // end of screen
                v_count <= 0;

o_x and o_y represent the horizontal and vertical position within the visible 640x480 display: this is what you use for positioning and drawing graphics. h_count and v_count represent the number of pixels and lines that have occurred since the start of the line or screen (including blanking interval): these are used for sync signals. For 640x480 sync pulses are active low, so we use ~ when assigning them. For now you can ignore the other outputs; they'll be used later.

Before we move onto drawing graphics we need to understand how our module generates a 25 MHz clock and the role of the i_pix_stb input.

Running to Time

Our 640x480 60 Hz VGA signal needs a 25 MHz pixel clock, but our board has a 100 MHz base clock. How do we efficiently divide the base clock by four?

Four is a power of two, so we could use a simple counter, but that isn't useful in the general case. Instead we're going to adopt Dan Gisselquist's fractional clock divider approach. This is a simple and elegant way to divide a clock by almost any amount. This flexibility allows us to deal with different base and pixel clocks.

reg [15:0] cnt;
reg pix_stb; 
always @(posedge CLK)
    {pix_stb, cnt} <= cnt + 16'h4000;  // divide by 4: (2^16)/4 = 0x4000

This works because the value of pix_stb is set whenever the counter cnt rolls over. {x, y} is the Verilog concatenation operator: {4'b1101, 4'b0011} == 8'b11010011.

If your board has a different clock then adjust the value added to the counter as appropriate. For example a 75 MHz board clock requires dividing by 3 to reach 25 MHz, so we add (216)/3 = 0x5555 to the counter.

To make use of the divided clock, we keep the base clock for the sensitivity list but add a test for strobe:

always @ (posedge CLK)
    if (pix_stb)   
    // do stuff once per pixel clock tick

It's tempting to simplify this by using always @ (posedge pix_stb). Don't do this! While this can work for simple designs, it quickly leads to unstable designs and debugging headaches. Hardware design is hard enough without messing with clock distribution and crossing clock domains.

Hip to be Square

With a VGA module defined and a suitable pixel clock created, we are now in a position to draw simple graphics. The VGA Pmod supports 4-bits per colour, giving us 16 levels for each output: VGA_R, VGA_G, and VGA_B. For example, VGA_R = 4b'1000 would set red to half brightness, while VGA_B = 4b'0011 would be a dark blue.

We don't have a bitmap to draw on. Instead, we define the extent of each square mathematically based on a range of pixels: where a square exists we set the square's output wire to 1. This wire is then linked to the VGA outputs to control the colour signals.

NB. The Basys 3 board doesn't have a reset button, so we use BTNC instead. This is active high, so you need to update the wire rst line near the beginning of top.v.

Create a design source called top.v with the following content [view source]:

module top(
    input wire CLK,             // board clock: 100 MHz on Arty/Basys3/Nexys
    input wire RST_BTN,         // reset button
    output wire VGA_HS_O,       // horizontal sync output
    output wire VGA_VS_O,       // vertical sync output
    output wire [3:0] VGA_R,    // 4-bit VGA red output
    output wire [3:0] VGA_G,    // 4-bit VGA green output
    output wire [3:0] VGA_B     // 4-bit VGA blue output

    wire rst = ~RST_BTN;    // reset is active low on Arty & Nexys Video
    // wire rst = RST_BTN;  // reset is active high on Basys3 (BTNC)

    // generate a 25 MHz pixel strobe
    reg [15:0] cnt;
    reg pix_stb;
    always @(posedge CLK)
        {pix_stb, cnt} <= cnt + 16'h4000;  // divide by 4: (2^16)/4 = 0x4000

    wire [9:0] x;  // current pixel x position: 10-bit value: 0-1023
    wire [8:0] y;  // current pixel y position:  9-bit value: 0-511

    vga640x480 display (

    // Four overlapping squares
    wire sq_a, sq_b, sq_c, sq_d;
    assign sq_a = ((x > 120) & (y >  40) & (x < 280) & (y < 200)) ? 1 : 0;
    assign sq_b = ((x > 200) & (y > 120) & (x < 360) & (y < 280)) ? 1 : 0;
    assign sq_c = ((x > 280) & (y > 200) & (x < 440) & (y < 360)) ? 1 : 0;
    assign sq_d = ((x > 360) & (y > 280) & (x < 520) & (y < 440)) ? 1 : 0;

    assign VGA_R[3] = sq_b;         // square b is red
    assign VGA_G[3] = sq_a | sq_d;  // squares a and d are green
    assign VGA_B[3] = sq_c;         // square c is blue

For example, if x is 210 and y is 150 then we're within sq_a and sq_b, so VGA_G[3] and VGA_R[3] are both set to 1, leading to a yellow pixel.

You can only have one assignment for each VGA output because you can only have one input feeding it without using intervening logic. Each colour consists of four separate outputs: you can have separate assignments for VGA_R[0] and VGA_R[3] but not two assignments for VGA_R[3], nor two assignments for VGA_R. For the green output VGA_G[3] we've used 'or' to combine the output of two squares into one VGA output wire.


Obtain the appopriate constraints file from the Time To Explore git repo and add it to your project:

For reference the Arty A7-35T constraints look like this:

## FPGA VGA Graphics Part 1: Arty Board Constraints

## Clock
set_property -dict {PACKAGE_PIN E3  IOSTANDARD LVCMOS33} [get_ports {CLK}];
create_clock -add -name sys_clk_pin -period 10.00 \
    -waveform {0 5} [get_ports {CLK}];

## Reset Button (active low)
set_property -dict {PACKAGE_PIN C2  IOSTANDARD LVCMOS33} [get_ports {RST_BTN}];

## VGA Pmod Header JB
set_property -dict {PACKAGE_PIN E15 IOSTANDARD LVCMOS33} [get_ports {VGA_R[0]}];
set_property -dict {PACKAGE_PIN E16 IOSTANDARD LVCMOS33} [get_ports {VGA_R[1]}];
set_property -dict {PACKAGE_PIN D15 IOSTANDARD LVCMOS33} [get_ports {VGA_R[2]}];
set_property -dict {PACKAGE_PIN C15 IOSTANDARD LVCMOS33} [get_ports {VGA_R[3]}];
set_property -dict {PACKAGE_PIN J17 IOSTANDARD LVCMOS33} [get_ports {VGA_B[0]}];
set_property -dict {PACKAGE_PIN J18 IOSTANDARD LVCMOS33} [get_ports {VGA_B[1]}];
set_property -dict {PACKAGE_PIN K15 IOSTANDARD LVCMOS33} [get_ports {VGA_B[2]}];
set_property -dict {PACKAGE_PIN J15 IOSTANDARD LVCMOS33} [get_ports {VGA_B[3]}];

## VGA Pmod Header JC
set_property -dict {PACKAGE_PIN U12 IOSTANDARD LVCMOS33} [get_ports {VGA_G[0]}];
set_property -dict {PACKAGE_PIN V12 IOSTANDARD LVCMOS33} [get_ports {VGA_G[1]}];
set_property -dict {PACKAGE_PIN V10 IOSTANDARD LVCMOS33} [get_ports {VGA_G[2]}];
set_property -dict {PACKAGE_PIN V11 IOSTANDARD LVCMOS33} [get_ports {VGA_G[3]}];
set_property -dict {PACKAGE_PIN U14 IOSTANDARD LVCMOS33} [get_ports {VGA_HS_O}];
set_property -dict {PACKAGE_PIN V14 IOSTANDARD LVCMOS33} [get_ports {VGA_VS_O}];

NB. These constraints are valid for VGA Pmod Rev C. Digilent tells me that earlier Pmod revisions were never publicly released, but if you have an older revision, you may need to swap the colours in the constraints file.

Build & Program

Run synthesis, implementation, bitstream generation. See the FPGA introductory post if you need a reminder on how to do this.

Next, hook up your VGA Pmod to the middle two connectors (JB and JC) on your Arty or Nexys Video and use your VGA cable to connect your monitor to the VGA Pmod. Basys 3 users can just connect the VGA cable directly to their board. Finally, connect your board to your computer via USB and program it with vga01/vga01.runs/impl_1/top.bit.

You should see four overlapping squares on your screen; from left to right: green, red, blue, green. Check your constraints file if the colours are wrong.

Not bad, but we're a little below spec owing to a pixel clock of 25 MHz, rather than 25.175 MHz: fH should be 31.469 kHz and fV 60.0 Hz.

More Colours

In this example, we only set the value of the most significant colour bit, for red that's VGA_R[3], so our squares will be roughly half maximum brightness. If you want to set them to maximum brightness then set all bits, for example: assign VGA_R = {4{sq_a}};. You can also make a complete range of colours by combining different colour pins, e.g. to make a single orange square, remove all the existing VGA assignments and add the following:

assign VGA_R = {4{sq_a}};  // square a is 100% red
assign VGA_G[3] = sq_a;    // square a is also 50% green


Static squares are all very well, but we do have 60 frames every second. To animate without tearing, we restrict movement to the time after a frame has finished drawing, but before the next frame starts. The vga640x480 module provide an output called o_animate, which is true for one tick after the end of pixel drawing. We have approximately 1 ms for animation before the next frame begins (33 lines of 800 pixels each 40ns long); for our simple squares this is plenty.

Square Module

To represent an animated square, we're going to create a new module. We're going to base our coordinates on the centre of the square. This doesn't make much difference for squares, but makes sense once we start working with more complex shapes and sprites.

A square moves one pixel in both the horizontal and vertical directions once per frame when i_animate is high. When it reaches the edge of the screen it switches direction.

Add a design source called square.v [view source]:

module square #(
    H_SIZE=80,      // half square width (for ease of co-ordinate calculations)
    IX=320,         // initial horizontal position of square centre
    IY=240,         // initial vertical position of square centre
    IX_DIR=1,       // initial horizontal direction: 1 is right, 0 is left
    IY_DIR=1,       // initial vertical direction: 1 is down, 0 is up
    D_WIDTH=640,    // width of display
    D_HEIGHT=480    // height of display
    input wire i_clk,         // base clock
    input wire i_ani_stb,     // animation clock: pixel clock is 1 pix/frame
    input wire i_rst,         // reset: returns animation to starting position
    input wire i_animate,     // animate when input is high
    output wire [11:0] o_x1,  // square left edge: 12-bit value: 0-4095
    output wire [11:0] o_x2,  // square right edge
    output wire [11:0] o_y1,  // square top edge
    output wire [11:0] o_y2   // square bottom edge

    reg [11:0] x = IX;   // horizontal position of square centre
    reg [11:0] y = IY;   // vertical position of square centre
    reg x_dir = IX_DIR;  // horizontal animation direction
    reg y_dir = IY_DIR;  // vertical animation direction

    assign o_x1 = x - H_SIZE;  // left: centre minus half horizontal size
    assign o_x2 = x + H_SIZE;  // right
    assign o_y1 = y - H_SIZE;  // top
    assign o_y2 = y + H_SIZE;  // bottom

    always @ (posedge i_clk)
        if (i_rst)  // on reset return to starting position
            x <= IX;
            y <= IY;
            x_dir <= IX_DIR;
            y_dir <= IY_DIR;
        if (i_animate && i_ani_stb)
            x <= (x_dir) ? x + 1 : x - 1;  // move left if positive x_dir
            y <= (y_dir) ? y + 1 : y - 1;  // move down if positive y_dir

            if (x <= H_SIZE + 1)  // edge of square is at left of screen
                x_dir <= 1;  // change direction to right
            if (x >= (D_WIDTH - H_SIZE - 1))  // edge of square at right
                x_dir <= 0;  // change direction to left          
            if (y <= H_SIZE + 1)  // edge of square at top of screen
                y_dir <= 1;  // change direction to down
            if (y >= (D_HEIGHT - H_SIZE - 1))  // edge of square at bottom
                y_dir <= 0;  // change direction to up              

This module makes use of Verilog parameters, such as H_SIZE, to allow custom square instances. The values in the module definition, e.g. H_SIZE=80, are defaults used if the user doesn't supply a value when creating a module instance. We use 12-bit values to represent our square position so that the module is usable on a wide range of display resolutions, including 4K.

Top Animation

Now we just need to update the top module to use the square module and create some square instances. In this case, we're animating three coloured squares. Replace your top module with the following then regenerate the bitstream and program your board [view source]:

module top(
    input wire CLK,             // board clock: 100 MHz on Arty/Basys3/Nexys
    input wire RST_BTN,         // reset button
    output wire VGA_HS_O,       // horizontal sync output
    output wire VGA_VS_O,       // vertical sync output
    output wire [3:0] VGA_R,    // 4-bit VGA red output
    output wire [3:0] VGA_G,    // 4-bit VGA green output
    output wire [3:0] VGA_B     // 4-bit VGA blue output

    wire rst = ~RST_BTN;    // reset is active low on Arty & Nexys Video
    // wire rst = RST_BTN;  // reset is active high on Basys3 (BTNC)

    wire [9:0] x;  // current pixel x position: 10-bit value: 0-1023
    wire [8:0] y;  // current pixel y position:  9-bit value: 0-511
    wire animate;  // high when we're ready to animate at end of drawing

    // generate a 25 MHz pixel strobe
    reg [15:0] cnt = 0;
    reg pix_stb = 0;
    always @(posedge CLK)
        {pix_stb, cnt} <= cnt + 16'h4000;  // divide by 4: (2^16)/4 = 0x4000

    vga640x480 display (

    wire sq_a, sq_b, sq_c;
    wire [11:0] sq_a_x1, sq_a_x2, sq_a_y1, sq_a_y2;  // 12-bit values: 0-4095 
    wire [11:0] sq_b_x1, sq_b_x2, sq_b_y1, sq_b_y2;
    wire [11:0] sq_c_x1, sq_c_x2, sq_c_y1, sq_c_y2;

    square #(.IX(160), .IY(120), .H_SIZE(60)) sq_a_anim (

    square #(.IX(320), .IY(240), .IY_DIR(0)) sq_b_anim (

    square #(.IX(480), .IY(360), .H_SIZE(100)) sq_c_anim (

    assign sq_a = ((x > sq_a_x1) & (y > sq_a_y1) &
        (x < sq_a_x2) & (y < sq_a_y2)) ? 1 : 0;
    assign sq_b = ((x > sq_b_x1) & (y > sq_b_y1) &
        (x < sq_b_x2) & (y < sq_b_y2)) ? 1 : 0;
    assign sq_c = ((x > sq_c_x1) & (y > sq_c_y1) &
        (x < sq_c_x2) & (y < sq_c_y2)) ? 1 : 0;

    assign VGA_R[3] = sq_a;  // square a is red
    assign VGA_G[3] = sq_b;  // square b is green
    assign VGA_B[3] = sq_c;  // square c is blue

If the squares don't move then your reset button is probably active high (like on the Basys 3); you need to update the wire rst line near the beginning of top.v.

If you press the reset button (BTNC on the Basys 3) on your board, you should see the squares return to their starting positions. NB. On the Arty don't confuse the reset button with the red prog button at the other corner of the board. Pressing prog will wipe the Arty memory. In the event you wipe your program, just reprogram the board to get your squares back.

Try experimenting with additional squares with different parameters, such as H_SIZE, IX_DIR, and IY_DIR.

Bonus: Super VGA!

Now you've mastered 640x480, why not try updating the design to work at 800x600? All the data you need are in Video Timings: VGA, SVGA, 720P, 1080P. The following design pointers should get you started:

  • You need a 40 MHz pixel clock, so the clock strobe needs updating
  • The total pixel size of SVGA, including blanking, is 1056x628, so you'll need more bits to represent x and y co-ordinates
  • The sync signals are active high (positive) for 800x600

If you get stuck you can view working examples: vga800x600.v and top_static_800x600.v.

What's Next?

We've managed to do a great deal with little logic. But while drawing directly from the pixel position is manageable for a few squares, it quickly becomes cumbersome for anything more sophisticated. To allow for more complex graphics we need memory where we can store and combine values. This is the subject of the next part.

Find more on FPGAs and Verilog in the Time to Explore FPGA Index.

1: Strictly speaking, the specification calls for a 25.175MHz pixel clock, but most VGA monitors are tolerant of this.

©2017-2019 Will Green.