Making My Own VGA Driver In SystemVerilog

This is a continuation of my posts about my final project for EE271, continued from the Project selection process here, here, and here.

The first thing I wanted to get working in this design was the VGA output, as the whole design rests on being able to display the results on a monitor. Not to get too into the weeds of the overall architecture, but I knew I would be using cascaded block rams to create a 1 by 524k frame buffer for the output. 1 bit per pixel since it's only a black and white output, 10 bits for the x coordinate and 9 for the y coordinate, concat them together to create a 19 bit addressing system.

The first step was to figure out how the VGA signal is supposed to behave. I went to google and found an excellent, and very informative website called ePanorama that has a detailed page with timing information, as well as a calculator to figure out timings for different resolutions.

VGA was created a long time ago, during the time of CRT monitors, the time it took for the beam to travel back across the screen to display the next row had to be accounted for. As well as the time it takes for the beam to travel from the bottom of the screen back to the top to start the next frame. These times are referred to as the front porch, back porch, and sync time. What they lead to is an effective resolution that is larger than the 640x480 you will be displaying. The effective resolution then becomes 800x525. During the times outside of the display resolution you need to output a solid black signal, as many monitors and TVs use that time to calibrate the output to the signal being input. There is also a pulse presented on the horizontal and vertical sync lines during the blacked out periods to inform the monitor to go to the next line or back to the top of the screen. I also ended up finding another extremely useful document from a gentlemen named Eduardo Sanchez, called A VGA Display Controller, which i used for reference due to its many pictures and excellent explanations of the timing involved. This image in particular was very helpful to me.

Some things to keep in mind for this design are that because I am using the Basys 3 board, I have 3 4-bit dacs, one for each color, and also that the pixel clock for VGA output at 640x480@60Hz needs to be 25MHz. I was using a faster clock for the rest of the game, so I used a counter inside the VGA module to cut that down to the appropriate 25MHz. This will be covered in more detail in the next post about architecture.

The way I went about building up this design was to use a counter to keep track of x coordinates, that would increment with each pixel clock, and then increment the y coordinate counter each time the x coordinate reached its maximum. These coordinates would then be passed out as the address to the frame buffer block ram, which would return either a 0 or a 1, determining whether the output would be black or white for that pixel. There is also a check to determine whether we are inside the viewable region of the screen, and output black if we are not.

My first attempt at this design did not go particularly well, but it still sort of worked. I loaded the frame buffer with random 0s and 1s just to test the display output. I went and plugged it into a VGA monitor only to see the output shaking and shimmering around on the screen, it was there, but not a solid picture. As an aside, over the course of the term I had sort of gotten lazy about my next state logic and then turning the next state into the present state. I took a lot of shortcuts to save space in my code and this final project is where all that laziness came home to roost.

I don't have a video of shame to show you how this was borne out, nor do I have the original SystemVerilog to show you, but I will just say, it was bad. I decided then, to just start over with a blank slate. I went through and much more methodically determined next states, with separate transitions to those states. This diligence paid off, as the design worked the first time, displaying a solid image on the monitor when I plugged it in.

module v_driver(
    // Clock input
    input wire clk,
    // Data input to be displayed, 1-bit becuase its black and white
    input wire data,
    // Outputs to trigger the transfer of data and the calculation of the next frame
    output reg transfer_start,
    output reg logic_start,
    // Outputs for the VGA connector
    output wire [9:0] x_val,
    output wire [8:0] y_val, 
    output wire h_sync, v_sync,
    output wire [3:0] red, green, blue
    );
    // Parameters for the size of the screen
    parameter X_MAX         = 10'd639;
    parameter Y_MAX         = 9'd479;
    parameter X_SIZE        = 10'd799;
    parameter Y_SIZE        = 10'd525;
    parameter X_ZERO        = 10'b0000000000;
    parameter Y_ZERO        = 10'b0000000000;
    parameter COUNT_MULT    = 3'd6;
    
    // Registers to track the current and next position of the output
    reg [9:0] x_reg = X_ZERO, x_next = X_ZERO + 1'b1, y_reg = Y_ZERO, y_next = Y_ZERO;
    // Registers for the values of the color outputs
    reg [3:0] red_reg = 4'h0, red_next = 4'h0, green_reg = 4'h0, green_next = 4'h0, blue_reg = 4'h0, blue_next = 4'h0;
    // Registers to track the sync pusles both horizontally and vertically
    reg h_sync_reg = 1'b0, h_sync_next = 1'b0, v_sync_reg = 1'b0, v_sync_next = 1'b0;
    // Register to track whether the output should be enabled or not
    reg v_en;
    // Counter to divide the clock by 6
    reg [2:0] count = 3'b000, count_next = 3'b000;
    
    // Always block to clock through the next state of all the registers in the design
    always @ (posedge clk) begin
        count       <= count_next;
        x_reg       <= x_next;
        y_reg       <= y_next;
        v_sync_reg  <= v_sync_next;
        h_sync_reg  <= h_sync_next;
        red_reg     <= red_next;
        green_reg   <= green_next;
        blue_reg    <= blue_next;
        v_en        <= ( (x_reg < 10'd640) & (y_reg < 10'd480) );

        // Case block to output the read data from the memory or to output black
        // if we're outside the displayed portion of the screen
        case(v_en)
            1: begin
                    red_next    <= {4{data}};
                    green_next  <= {4{data}};
                    blue_next   <= {4{data}};
                end
            0: begin
                    red_next    <= 4'h0;
                    green_next  <= 4'h0;
                    blue_next   <= 4'h0;
                end
        endcase 
    end

    always_comb begin
        // Start the transfer at the 432nd line, so that the transfer wont over write anything
        // that hasn't been displayed yet but will finish as early as possible
        transfer_start = (x_next == X_ZERO & y_next == 10'd432) ? 1'b1 : 1'b0;
        // Logic will start calculating the next frame once the new frame starts
        logic_start = (x_next == X_ZERO + 1'b1 & y_next == Y_ZERO) ? 1'b1 : 1'b0;
        // sync pulses for ~90 pixels on the horizontal and ~2 pixels on the vertical
        h_sync_next = (x_reg > 10'd659) & (x_reg < 10'd756);
        v_sync_next = (y_reg > 10'd493) & (y_reg < 10'd496);
        // the counter to divide the clock by 6
        count_next = (count < COUNT_MULT - 1'b1) ? count + 1'b1 : 4'b0000;
        // Count x is the counter is at 5, increment y if x is at the end of the row
        // reset both to 0 if they reach the end of the screen
        y_next  = (x_reg == X_SIZE) ? ( (y_reg < Y_SIZE) ? y_reg + 1'b1 : Y_ZERO) : y_reg;
        x_next  = (x_reg == X_SIZE) ? X_ZERO : ( (count == COUNT_MULT - 1'b1) ? ( x_reg + 1'b1 ) : x_reg );
    end

    // Assign output registers
    // X and Y registers form the address for the memory to read
    assign red = red_reg;
    assign green = green_reg;
    assign blue = blue_reg;
    assign h_sync = h_sync_reg;
    assign v_sync = v_sync_reg;
    assign x_val = x_reg;
    assign y_val = y_reg[8:0];  
    
endmodule

Overall I think it was pretty valuable to write my own VGA driver. Sure I could have just used one of the professor's modules to do so, but I don't really like to abstract away things without at least learning how they work first. In this case, the easiest way to learn how it worked was to build it myself. It might not be the cleanest VGA implementation in SystemVerilog out there, but it is mine and it did the job I needed it to. I do wish I had used a version controlling system, so that the horror of my first attempt would be here for you all to see.

Tune in next time, when I will talk about the overall architectural design of the game, and several implementation problems I ran into.

The Project Selection Process Pt.3

Continued from Part 2.

I am sure at this point all of you are glued to your monitors in anticipation of what my project will be. At this point I was only 2 weeks out from the due date, so I decided to look over the options for lab ideas presented in the document for the final project. Most of the ideas were for simple games, all of which would be displayed on an 8x8 LED matrix. Things like frogger, flappy bird, guitar hero, etc. But amongst the crowd was a suggestion of Conway's Game of Life. In one of my programming classes we had made a version of the Game of Life and it was a good time. Also, if you had noticed, both of my previous ideas involved the real time analysis of information and loosely the Game of Life does as well, with the input being the previous output of the system.

Even though I was picking one of the projects listed in the lab document, I was already planning on expanding the scope of the project. Instead of running the game on an 8x8 LED matrix, I wanted to run the game at 640x480@60Hz out the VGA port. One of my TAs for the class told me that one of the other professors had a VGA module we could use to make it easier, but I knew I wanted to implement my own, even if just to use it as an additional learning opportunity. My interest in video also had me intrigued about implementing my own video output.

Now, with my project decided, we can move onto the fun part, how I actually implemented this beast.

 

The Project Selection Process Pt.2

Continued from Part 1.

We left off last time with me realizing I wouldn't be able to implement my video scalar. Someday though it will still happen, but it'll live in the projects list at 0% for quite a while I imagine.

My next idea came to me from another class I am taking this term, Systems and Signals in Continuous Time. I hadn't expected to enjoy this class, since I thought it was going to be another analog class in the same vein as circuits 1 and 2 at PCC, but it really surprised me. It is almost entirely about systems and signals in the abstract, I also had a fantastic professor, Professor Mari Ostendorf.

My idea was to create a real time FFT. I wanted to be able to plug in a music source, like my iPhone, and have it show frequency content, in bars, on a monitor. I knew going into this that there would be several road blocks to my success, but I was confident they would be surmountable.

The first was that I have not yet taken the discrete time version of the systems and signals class. This means I have not learned about the DFT or the FFT at all. I have only learned about the Fourier Transform in continuous time. The second is that I have not learned how to design digital circuits to manipulate floating point numbers, we only know how to do integer math, and even with that we've only explicitly learned how to do addition, though subtraction and multiplication are relatively simple offshoots of that.

This is when my work began in earnest. I only had 3 weeks or so till the final project was due at this point and I needed to start making progress. I began reading about the DFT/FFT and how they worked. I also started googling for means of performing the FFT using only integer math. This originally seemed very fruitful. I found several papers detailing means of doing the FFT using integer math and without the use of multipliers. As academic papers are though, they were incredibly dense and lacking in actual implementation details. They also had decidedly non-integer numbers in them. I would follow them up until they started talking about decimal numbers. I even went to Professor Ostendorf's office hours to see if she would be willing to help me out without using up too much of her time. She was very helpful, and went over several papers with me over the course of half an hour or so, but not enough progress was made, and I did not want to use up anymore of her time.

Long story short, the real time FFT idea had to go out the window as well. It will also be returning though! hopefully some progress will be made on this front during winter break!

check in next time to see what project was actually chosen.

The Project Selection Process Pt.1

Selecting a final project in a class can be tough. I know it very frequently is for me. It was particularly bad in my first digital design class here at the UW.

I attended Portland Community College for a little over a year before transferring, and in that time I had the opportunity to take 2 digital design classes. When I transferred though, they transferred for credit, but did not qualify for the first digital design class here, EE271. This is because at PCC they did not really teach us any Verilog, we did almost all our labs using 7400 series chips on breadboards. While here at the UW, they do almost all of the labs for this class in Verilog and implement them on FPGAs. They also do not have a separate Verilog course like I would have had I transferred to Portland State, so I was advised to retake the course in order to learn Verilog, since it would be expected that I would know it going forward.

What this lead to is me taking a course where I understood nearly all the concepts in advance. It allowed me to help my classmates a great deal, but it also gave me a long time to consider my final project.

A few weeks into the course I had an idea of what I wanted to do. I decided I wanted to make a video scalar. It would be able to accept composite video from something like an SNES and then output 1080p video over HDMI. Manipulating video has been something I have been very interested in since about 2011, when I started playing with video encoding on my own. So this project was something I was very interested in doing, and having personal motivation to work on it would be very valuable.

The problems began about a month before the end of the term when I started looking into how I would implement the design. The first stage of the design would be to digitize the signal. I haven't ever used them, but I know the Basys 3 has several XADC ports, and finding the Xilinx user guide on how to use them would be pretty easy. The difficult part would be interpreting, or demodulating, the composite video signal into something I could work with.

So I spent the better part of a weekend researching specifications of the composite video spec and found....almost nothing. There was so little information out there as far as signal specifications that I wasn't confident I would ever be able to move with the project. I decided maybe I could take in digital information over Display Port, at 240p, scale it up to 1080p, and output it over HDMI, but even that was turning out to be out of reach. I had really hoped to get some of my classmates to work with me on this project, and my attempts to recruit anyone were fruitless.

This project idea had seemed to meet a dead end, read on next time to hear about the next idea I considered.