EE552 APPLICATION NOTE

Hao Luan , Bo Liu and Albert Chan


Abstract

For our EE552 project, we design a video game. For the Application Notes, we describe in detail the different steps required to realise a video display.


Copyright

We ask that any parts of our application notes used in any project be clearly put as reference in the reference section or wherever is appropriate.


Video display

  1. Pixel Synchronization
  2. The first requirement in manipulating a monitor is the synchronization of the pixels on the monitor with the system clock(25.175Mhz). The following explanation goes side by side with the file "syncgen.vhd". Since this is not our file, we do not have the right to use it here. We ask the readers to refer to Pong.

    Gif graphic,

    To display anything on a monitor, two synchronization signals are required. One is used to synchronize the horizontal direction of the monitor, the other the vertical direction. The synchronization signals are used to synchronize the pixels on the monitor with the system clock that is being used for the project. Since all the pixels on a monitor cannot be available to be displayed simultaneously at any time, a clock is therefore used to update the availability of the pixels in such a way that only one pixel can be displayed at anytime. The order in which the pixels are available is from the first line of the pixels at the top left corner of the monitor to the right of the same line in the horizontal direction. The logic high of the signal HSync is used for the horizontal synchronization. After the availability of the first line of pixels is updated, the second line is updated from left to right, and then the third line and so on. When the last line of pixels is updated, the end of the monitor is reached. At this moment the signal VSync produces a logic high to reset the synchronization so that the synchronization starts again at the top left corner of the monitor. The process repeats itself indefinitely.

  3. Pixel Coordinates
  4. We have now synchronized the pixels of a monitor with the system clock. However we still have no access to the pixels. What we need now is to count the pixels as the pixels are synchronized. The following segment of VHDL codes count the pixels as the pixels are synchronized.

    These are two files required to generate the coordinates of a VGA monitor : count_xy.vhd and syncgen.vhd

    Clearly we see that the vector "countX" counts the pixels in the horizontal direction, while "countY" counts the pixels in the vertical direction. Now we see that every pixel has a unique set of values described by countX and countY. We can use these values to pick out the pixel that we want. Effectively we have set up a coordinate system with the origin at the top left corner of the monitor.

  5. Line Drawing
  6. These files are required to run the demonstration: line.vhd, count_xy.vhd and syncgen.vhd

    Using the values of countX and countY we can draw various shapes of various colors at the specified location on the monitor. The following segment of VHDL codes describes how to draw a vertical red line at the specified location "line_position". The signals vga_red, vga_green and vga_blue are the signals to be connected to the pixels on the monitor. They respectively represent the red, green and blue signals for the pixel.

    Gif graphic,

  7. Character Display
  8. To display letters and other user-defined characters two files are required. The first file describes the shapes of the characters. The second describes the arrangement of these characters to form meaningful messages. The following two files of character descriptions are obtained from : MIPS or char_set.mif, and dis_con2.mif.

  9. Displaying Fixed Messages
  10. Now the strategy used to obtain the character as described in the second column is tricky. First the numbers in the second column are in octal system. If we examine one number and match it with the character in the third column and then if we go back to "char_set.mif", we see that the number is actually the first two digits of the addresses for the corresponding character. We will see later how the third digit is obtained. Notice the reason for the use of octal system for the addresses in the second column above; so it is compatible with the system used in "char_set.mif".

    The following figure should be helpful in understanding the relations between the screen, the file dice_con.mif and char_set.mif. What the figure is showing is that using the positions on the screen, we pick out the set of characters in a row defined in dice_con.mif, each character from the set is then obtained from char_set.mif. The following figure and the explanations below the figure will clearly illustrate the methods of message display.

    Gif graphic,

    To understand how to display messages, we first have to divide the screen into columns and rows, each of which consists of a certain number of pixels. The number of pixels in each column and row will determine the size of the characters. Recall that the characters in char_set.mif are defined by eight by eight pixels. Therefore in order that the characters are not distorted when they are displayed on the screen, it is obvious that we want to define the size of the columns and rows as a multiple of eight; that is eight by eight, sixteen by sixteen, etc. To define the size of the columns and rows we use the signals "countX" and "countY" given by "count_xy.vhd". Notice that both "countX" and "countY" are vectors of size 10 (9 downto 0). The trick in obtaining columns and rows of size sixteen, to display the message "PLAYER 2" for example, is to ignore the four least significant bits of "countX" and "countY". That is we declare two local signals each of size 6 and store the six most significant bits of "countX" and "countY" into the local signals. For example in our programs we call the signals representing the columns and rows of size 16 as col_address and row_address. In the following assignment we effectively obtain columns and rows of size 16.

    col_address <= countX(9 downto 4);

    row_address <= countY(9 downto 4);

    So now as "countX" and "countY" increase, col_address and row_address also increase, but only once every 16 counts of "countX" and "countY".

    Now that we have divided the screen into columns and rows, we will attempt to pick out the columns and rows in which we want to display the characters. We need to match the columns and rows that we have picked with the addresses of the characters described in dice_con.mif or dis_con2.mif.

    Before we are able to match the columns and rows with the addresses in dis_con2.mif, we need to divide each of col_address and row_address into two halves. The half corresponding to the least significant bits is called "con_col" and will match with the columns. The other half corresponding to the most significant bits is called "con_row" and will match with the rows. So to pick out the address for "PLAYER 2" to display it between column 3 and column 10 and in row 10, we write,

    if col_address >= "000011" and col_address <= "001010" and row_address = "001100" then

    con_col <= col_address - "000011";

    con_row <= row_address - "001011";

    rom_address(8 downto 3) <= format_data;

    end if;

    The statement "rom_address(8 downto 3) <= format_data;" will be explained later.

    When col_address = "000011", con_col = "000000" and con_row = "000001". So if we declare a signal "format_address" of size 6 and if we do the following assignment,

    -- Address for Constant Character Data ROM

    format_address(2 Downto 0) <= con_col(2 Downto 0);

    format_address(5 Downto 3) <= con_row(2 Downto 0);

    we see that this effectively gives us

    format_address = "001000"

    when col_address = "000011", which picks out the first two digits of the address for the letter P in dis_con.mif. As col_address increases the successive characters are chosen.

    Now if we examine the lpm_rom function for dis_con2.mif we see that format_address is assigned to address so that the chip knows which character we want to obtain. The output data are then given by q and are assigned to format_data, which is another local signal of size six (6) in this case. format_data contains the bits obtained by converting the octal numbers in dis_con2.mif.

    Half a page before this line, we mentioned the following assignment,

    rom_address(8 downto 3) <= format_data;

    What this does is the content of format_data is assigned to the local signal

    rom_address of size 9 (8 downto 0). Since format_address = "001000", we have effectively chosen the letter P from the second row of messages in dis_con2.mif. Therefore format_data = 010000(base 2) = 20(base 8) = rom_address(8 downto 3).

    Now in order to know what happens to the three least significant bits of rom_address, we need to mention another two local signals. One is pixel_col_count and the other pixel_row_count, both of which are std_logic_vector(3 downto 0). Now we do the following assignments.

    pixel_col_count <= countX(3 downto 0);

    pixel_row_count <= countY(3 downto 0);

    Now since the columns and rows are of size sixteen (16), every time we start at new block, defined by col_address and row_address, of dimensions sixteen by sixteen (16 X 16), the four least significant bits of countX and countY are zeros. So at the beginning we assign zeros into pixel_col_count and pixel_row_count. Now we do the following assignment.

    rom_address(2 Downto 0) <= pixel_row_count(3 Downto 1);

    Notice that we are ignoring the least significant bit of pixel_row_count. What this effectively does is that the display in the vertical direction is doubled. Now we see that now we have,

    rom_address(8 Downto 0) = 010000000(base 2) = 200(base 8).

    Now we assign rom_address into the lpm_rom function for the file char_set.mif as. This tells the chip which address to look for the character we want.

    Now we examine pixel_col_count. Recall first of all that countY increases by one only when countX finishes counting the pixels in a row of the screen. So when countY(3 downto 0) = 0000, countX((3 downto 0) keep increasing. Now if we do the following assignment,

    rom_mux_output <= rom_data ( (CONV_INTEGER(NOT pixel_col_count (3 downto 1))));

    we effectively choose all the bits in the row with address 200(base 8) in the file char_set.mif. Since countX counts the pixels in a row, or in the horizontal direction, and since we ignore the least significant bit of pixel_col_count, we have effectively double the display of the character in the horizontal direction also. Since the character is defined to be eight pixels by eight pixels in char_set.mif, it is now sixteen by sixteen.

    When the signal rom_mux_output is connected to a pixel of a certain color, it is then lit if the signal is high.

    When countX finishes counting two rows countY increases by two and is now countY(3 downto 0) = 0010 so that rom_address is now

    rom_address(8 Downto 0) = 010000001(base 2) = 201(base 8).

    Therefore the second row of pixels of the character P in char_set.mif is chosen. The process continues until the character is written onto the block on the screen.

    The other characters are similarly written onto the screen.

    To write larger characters the columns and the rows have to be defined to the appropriate size and the bits in pixel_row_count and pixel_col_count are to be appropriately chosen. For example, to write a character of size thirty two pixels by thirty two pixels, as we do for the title of the game, we define the columns and the rows to be thirty two by thirty two and ignore the two least significant bits of pixel_row_count and pixel_col_count. Here is an example of the message display without extraneous "vertical bars" beside the characters demo_ms.vhd.

  11. Displaying Alternating Messages
  12. To display messages that circulate so that as one disappears a different one appears, we need to decide the length of the display of a message. Suppose we want to display a message for approximately four seconds before it disappears and a different one appears, we need a new clock. From the program syncgen.vhd we know that the signal VSync has a period of 16.6 milliseconds. We assign this signal to one on the port that we call vclock so we can use in the program message.vhd. Using vclock, we make a new clock, called fresh_clock, under the condition of a counter so that every time the counter counts sixty four cycles of vclock, fresh_clock stays high for a period of vclock. Before the counter reaches two hundred and fifty-five , fresh_clock is assigned a zero. As a result the period of fresh_clock is around 4 seconds.

    Using the fresh clock, we circulate our messages once every 4 seconds. In the meantime, the relative_clock will shift the displayed message to the left every half a second. The methods for displaying alternating messages are explained above. please see message.vhd for details.

  13. How to use above components
  14. We give an example here to demostrate how to use and interface the display components we discuss above. The main file is dicedemo.vhd. You need to download syncgen.vhd, count_xy.vhd message.vhd, demo_ms.vhd char_set.mif, dice_con.mif and dis_con2.mif to finish compilation. A compiled ".sof" file is also available here dicedemo.sof,


  15. Further Information
  16. You can find more information you need from following sites:

    Bo liu

    Albert Chan

    Hao Luan


    Reference

    The files on which this project is based are called PONG and MIPS. which are obtained from Georgia Institute of Technology.


    Example: "DICE RACE" Game Display

    Gif graphic,


    Last modified on April 9, 1998.