12. Character Display

The purpose of this laboratory is to create a character generator circuit for displaying ASCII text characters on the VGA display. You will also create a simple circuit that writes characters to the screen using the switches and buttons.

Learning Outcomes

  • Understand how to use internal BRAM memories
  • Draw characters from a font ROM onto the screen

Preliminary

Displaying text is an important function of a video controller. Dedicated circuits are often used to facilitate the display of text characters on a screen. To display text on our VGA display, we will organize the 640x480 display area into larger “tiles” where each tile represents a character location. In this lab, the size of each character is 8 pixels wide and 16 pixels high. Mapped onto a 640x480 display, this font will display 80 text characters in each line (i.e., 640 pixels for each row divided by 8 columns per character) and 30 lines (i.e., 480 lines divided by 16 pixels per character, 480 / 16). The layout of the 80 x 30 character screen is shown below.

When drawing text characters on the screen, we will refer to several coordinate systems. The first coordinate system is the “display” coordinate system used for locating individual pixels on the display (i.e., 640x480). The top left pixel of the display is labeled (0,0), and the lower right pixel of the display is (639,479). The pixel_x and pixel_y outputs from the VGA controller reference this display coordinate system.

There is also a coordinate system, called the “character” coordinate system, for specifying a character position on the display (i.e., the 80x30 character layout shown in the previous figure). The top left character position is (0,0), and the lower right character is (79,29). Every pixel in the display is mapped to one of these characters. To convert the x coordinate of a pixel into its corresponding character column is done by dividing the x pixel value by 8 without rounding. To convert the y coordinate of a pixel into the corresponding character row is done by dividing the y pixel value by 16 (again without rounding). For example, the pixel (138, 312) in the display coordinate system will map to the character located at character position 17, 19 (i.e., 138/8, 312/16). The location of this character in the coordinate system is highlighted in the above figure.

This division can easily be done in digital hardware by taking the top seven bits of pixel_x for the x character location taking the top 6 bits of pixel_y for the y character location. The following SystemVerilog code demonstrates how to generate the character positions from the pixel positions:

assign char_x_pos = pixel_x[9:3];
assign char_y_pos = pixel_y[8:4];

There is also a coordinate system for pixels within an individual character. As described above, each character is 8 pixels wide and 16 pixels high. The position of the top left pixel of a character is (0,0), and the lower right pixel of a character is (7,15) as shown in the figure below.

Every pixel in the display coordinate system maps to a specific character in the character coordinate system and to a pixel within this character. It will be important to map pixel locations in the display coordinate system to pixels within an individual character. The x pixel location within a character can be found by computing the pixel_x mod 8. The y pixel location within a character can be found by computing pixel_y mod 16. Performing a modulo operation using powers of 2 is very simple in a digital system. This is done by simply selecting the lower three bits of the pixel_x value and the lower four bits of the pixel_y value as shown in the following SystemVerilog code:

assign char_x_pixel <= pixel_x[2:0];
assign char_y_pixel <= pixel_y[3:0];

In the example described above, the pixel (138,312) in the display coordinates maps to character (17,19). Within this character, this pixel maps to (138 mod 8=2, 312 mod 16=4).

Answer the following questions to demonstrate your understanding of the organization of the characters on the display:

How many characters can be displayed on the 640 x 480 display using this font?

What is the character position associated with the pixel at location (279, 173)

What is the position of this pixel within a character (i.e., what position is this pixel within a 8x16 character)?

Exercises

Exercise #1 - Font ROM

The appearance of the characters on the screen is determined by a “font ROM”. A SystemVerilog file named font_rom.sv has been created for you that defines the font ROM for the characters. The font ROM is a read only memory that contains the pattern of pixels that should be displayed on the screen when a particular character needs to be displayed. As described earlier, the size of each character in this font ROM is 8 pixels wide and 16 pixels high. The font ROM stores one bit for each of the 8x16 pixels associated with each character for a total of 128 bits. A ‘1’ in the font ROM indicates the corresponding position within the 8x16 character should be displayed in the foreground (i.e., white for white text). A ‘0’ in the font ROM indicates that the corresponding pixel should be blanked or put in the background. The text below demonstrates the contents of the font ROM for the upper-case character ‘A’:

   8'b00000000, // 0
   8'b00000000, // 1
   8'b00010000, // 2    *
   8'b00111000, // 3   ***
   8'b01101100, // 4  ** **
   8'b11000110, // 5 **   **
   8'b11000110, // 6 **   **
   8'b11111110, // 7 *******
   8'b11000110, // 8 **   **
   8'b11000110, // 9 **   **
   8'b11000110, // a **   **
   8'b11000110, // b **   **
   8'b00000000, // c
   8'b00000000, // d
   8'b00000000, // e
   8'b00000000, // f

The font ROM for the character ‘A’ shown above will create the character shown below:

The ROM has been created for you that defines the character shape for 128 different characters (all 7-bit ASCII characters). Each word of the ROM defines one line of a character. Since each character requires 16 lines, the total size of the ROM is 128 characters x 16 lines / character x 8 bits per character line. This ROM fits into the size of a single BRAM and will be mapped to one of the BRAMs on your FPGA board. The font ROM is a synchronous memory meaning that it requires one clock cycle to access the memory contents (see the file for details on the memory timing).

Answer the following questions after reviewing the ‘font_rom.sv’ file:

How wide, in bits, is each word of this ROM?

How many bits are required to define a single character in this ROM?

What is the total size, in bits, of this ROM?

What is the value of the word at address 0x32a in this ROM?

What character is associated with address 0x32a in this ROM?

What is the first address in the font ROM that stores the contents for the character ‘A’?

Assume that pixel_x=279 and pixel_y=170 and you are trying to display the character ‘A’. What value will you read from the font rom?

Assuming the same situation described in the previous question, what is the corresponding pixel value for the character ‘A’ at the given locationn?

TODO: Providing a timing diagram with addresses and ask them to determine the results on the timing diagram.

Exercise #2 - Character Generator

In this exercise you will create a character generator module for display text characters on the VGA display. The character generator performs two important functions: first, it stores the characters that should be displayed on the screen, and second, it determines what to draw on the screen at each pixel location to display the characters. The instructions below will guide you through the process of building this character generator circuit.

Begin this exercise by creating a new SystemVerilog file named charGen.sv with the following ports:

Parameter Default Function
FILENAME ”” Specifies the filename containing the initial contents of the memory
Module Name = charGen      
Port Name Direction Width Function
clk Input 1 System Clock
char_we Input 1 Character write enable
char_addr Input 12 The write address of the character memory
char_value Input 7 The 8 bit value to write into the character memory
pixel_x Input 10 The column address of the current pixel
pixel_y Input 9 The row address of the current pixel
pixel_out Output 1 The value of the character output pixel

The character generator has three primary components: a memory to store the characters to display, the font ROM to store the font characters, and an output pixel multiplexer. The relationship between these three components is shown in the figure below. The design and organization of these components will be described in more detail below.

Character Memory

An important function of the character generator module is to store a character value at each of the 80x30 character position on the display. You will create a dedicated memory for this function within the module. Note that you do not need to create a dedicated system verilog file for this module - just implement this character memory within the charGen module.

The character memory will have two ports: one port for writing characters into the memory and another port for reading the character memory for the display. The first write port will be used occasionally to update the character display and the second read port will be used constantly to feed the VGA display with the appropriate display data. Both the read and write ports are synchronous and will operate on the rising edge of the clock signal.

The character to display at each location is specified by a 7-bit ASCII value. The minimum size of this memory is 80x30x7 bits to provide enough room to store characters (7 bits each) for each of the 80 columns and 30 rows. This memory size, however, is not an even power of two. To simplify our circuit, we will use a character memory that is sized with even powers of two. Specifically, the memory size will be 128x32x8 or 2048x8 bits. This memory can provide enough memory for 128 character columns, 32 rows, and 8 bits for each character. This memory will need 12 address bits and provide 8 bits of data. The top-seven address bits (char_mem_addr[11:5]) are needed to determine the character column (i.e., 2^7 = 128) and the lower five bits (char_mem_addr[4:0]) are needed to determine the character row (i.e., 2^5=32). Although this larger sized memory will result in wasted memory, it will significantly simplify the circuit you will create.

1. Begin your memory by defining an array of logic values of size 2048 x 8 bits.

The character memory is organized in row order. This means that sequential characters in memory are located on the same row next to each other. The address 0x000 corresponds to the character at the location 0,0, the address 0x001 corresponds to the character at column 1, row 0 (1,0), and the address 0x04F corresponds to the character at location 79,0 (the last visible character in row 0). Because the memory is organized with 128 character columns per row, there are an additional 48 characters in this row that are not visible (i.e., addresses 0x050 through 0x07F). The first character of the next row (i.e., the character at column zero, row 1 (0,1)) is stored at address 0x080. Answer the following questions to demonstrate your understanding of the charater memory.

How many bits are stored in the character memory?

What address of the character memory holds the value for the character located on the character display at col,row = (65,17)?

What are the character coordinates of the character stored at address 0xB8F in the character memory?

This memory has two memory ports: a read port and a write port. Both ports should be designed to operate synchronously (i.e., they perform their operation at the clock edge). The write port is used to store character values at specific locations in the character coordinate system. You will use this port to update and modify the character display.
When you want to display a different character at a different location on the screen, you will write a character into the character memory at the desired location. Writing characters to the character memory will occur relatively infrequently. For this lab, you will use the switches and the buttons to write characters into the character memory (this will be described in the description of the top-level design).

There are three signals associated with the write port: the character write address (driven by the input port ‘char_addr’), the character value to write (driven by the ‘char_value’ input port), and the write enable (driven by the ‘char_we’ port). To store a new character in the display, a higher level module will need to set the value of the character to be written, determine the character address, and assert the character write enable control signal.

2. Create a synchronous write port for the character memory as described above

The read port is used by the character generator circuit to determine the character to display at a particular pixel_x and pixel_y location. There are two signals associated with the character memory read port. The first is the character read address ‘char_read_addr’. This address is a 12 bit signal used to determine which location in the character memory to read. Create an internal signal that will be used to determine the character read address (the details of this signal will be described below). The second signal is the ‘char_read_value’. The character value at the location specified by the character read address would be provided on this signal. Create an internal signal for the character read value (the details of how this signal is used will be described below).

3. Create a synchronous read port for the character memory as described above

The character that is read from the character memory will depend on the current ‘pixel_x’ and ‘pixel_y’ input ports (these ports will be driven by the VGA timing circuit). Determine how you are going to generate the 12-bit address signal that will drive the char_read_addr input of the character memory. This statement should be created by concatenating various bits from the pixel_x and pixel_y counters.

4. Generate the logic for the ‘char_read_addr’ signal using the pixel_x and pixel_y input ports

The character memory array can be initialized with an initial set of characters through an initialization file. Add an ‘initial’ block to your character generator module to initialize the contents of the memory if the ‘FILENAME’ parameter is set (see Program 19.7.2 in the textbook for details on how to do this). The following example demonstrates how to read a memory initialization file and load the contents into the character memory:

initial begin
    if (FILENAME != "")
        $readmemh(FILENAME, mem_data, 0);
end

5. Add the memory initialization statement as shown above

Font Rom

The font ROM will be used in conjunction with the character memory to determine which pixels to turn on in order to display characters.

6. Instance the font ROM in your character generator module

You will need two pieces of information to create the read address for the font ROM: the current character value and the ‘pixel_y’ value. The 7-bit character value indicates which character you are currently displaying and the bottom 4 bits of the ‘pixel_y’ value indicates which row within the character you are displaying. Note that the character value read out from the character memory is delayed one clock cycle from the ‘pixel_y’ signal. This means that the two signals are not synchronized and cannot be used together. To synchronize the character value with the ‘pixel_y’ signal, you will need to add a pipeline register to the ‘pixel_y’ signal. Create a new signal ‘pixel_y_d’ that is one clock cycle delayed version of the ‘pixel_y’ signal as shown in the figure below. Create a signal named ‘font_rom_addr’ that is based on the current character and the delayed ‘pixel_y’ signal. The character value should form the upper seven bits of your address when reading the font ROM. The lower 4 bits of ‘pixel_y_d’ value is used to determine which of the 16 lines of this character to display.

Pixel Out Multiplexer

The last component of the character generator is the pixel out multiplexer. The purpose of this multiplexer is to select the appropriate pixel value from the font ROM character output. The font ROM will provide 8 pixels of data for the current line of the character to be drawn, but only one pixel is displayed at a time. The lower 3 bits of ‘pixel_x’ signal is used to determine which of the 8 pixels to display.

The data returned by the font ROM is an 8-bit value where the least significant bit of the data (rom_data[0]) corresponds is the right-most pixel of the character (column 7) and the most significant bit (rom_data[7]) is the left-most pixel of the character (column 0). The multiplexer you design needs to select the appropriate pixel value from the font ROM character output based on the lower 3 bits of the ‘pixel_x’ signal.

Note that output of the font ROM is two clock cycles behind the ‘pixel_y’ signal. These two clock cycles are due to the latency of the character memory read and the font ROM read (each read takes one clock cycle). To compensate for this delay, create a signal ‘pixel_x_dd’ that is two cycles behind ‘pixel_x’ as shown in the figure above.

7. Create a 8:1 multiplexer to select the appropriate pixel value from the font ROM character output

Character Generator Timing Diagram

Exercise #3 - Character Generator Simulation

After creating your module, create a Tcl file named chargen_sim.tcl to simulate your module. Your tcl file should include the following:

  1. Run the simulation for 100ns without setting any values
  2. Create a 100 MHz oscillating clock and run for a few clock cycles
  3. Set the reset to ‘1’ and default values for all inputs and run for a few clock cycles
  4. Deassert the reset signal and run for a few clock cycles
  5. Perform a character write to location 0x000 with the value 0x41 (ASCII ‘A’) and run for a few clock cycles
  6. Set the pixel_y value to ‘2’ and set pixel_x to values 0 through 7 to see the character ‘A’ displayed for row 2
  7. Do the same thing as above but for pixel_y=3

After completing the simulation, take a screenshot of the waveform and name the file chargen_sim.png

After simulating your module and verifying it operates with the Tcl script, you will need to test your receiver with a testbench. The testbench file is named tb_charGen.sv and is located as part of the starter code in your lab repository. Begin by testing the decoder interactively in GUI mode with this testbench to resolve syntax and behavioral problems in your design. Once you have resolved these issues, create a Makefile rule named sim_chargen_tb that will run the simulation of your seven-segment controller with the testbench. This rule should generate a log file named sim_chargen_tb.log. Make sure that your testbench simulates without any errors.

HERE Questions

How many characters are sent by the testbench?

What is the sixth character transmitted in the testbench?

What is the time in nano seconds in which the simulation ends?

Exercise #4 - Top-Level Design

The final design exercise is to integrate your VGA character controller and the VGA timing controller to create a rudimentary terminal. Your terminal will accept characters from the buttons and display them on the screen. Create a new file named chargen_top.sv and include the following ports:

Parameter Default Function
FILENAME ”” Specifies the filename containing the initial contents of the memory
Module Name = chargen_top      
Port Name Direction Width Function
clk Input 1 System Clock
btnd Input 1 Reset
btnc Input 1 Write character
btnl Input 1 Set background color
btnr Input 1 Set foreground color
btnu Input 1 Clear screen
rx_in Input 1 UART Receiver Input
sw Input 12 Switches to determine character to write or color to display
Hsync Output 1 Horizontal synchronization signal
Vsync Output 1 Vertical synchronization signal
vgaRed Output 4 Red color signal
vgaGreen Output 4 Green color signal
vgaBlue Output 4 Blue color signal
  • Background/Foreground Colors
  • VGA Timing Controller
  • VGA char gen module
  • x,y cursor position

Follow these guidelines when creating your top-level design:

  • Instance your VGA timing controller circuit from a previous lab. Note that due to the delays associated with the memories in the character generator, the pixel output will be delayed by two clock cycles in comparison with the HS and VS signals. You should add two flip flops between the HS and VS outputs of the VGA timing controller and the HS and VS output signals of your top-level circuit. This will align the output pixel data with the HS and VS signals.
  • Instance your character generator from this lab and hook it up to the VGA timing controller (i.e., attach the pixel_x and pixel_y outputs of the VGA timing controller to the character generator).
  • Attaches the switches to the char_value input of the character generator. The switches will determine the value to write into your character memory.
  • Instance your seven segment display and drive the right two digits of the segment with the value of the switches (blank the left two digits). This will allow you to look at the hex value of the switches before you send your character to the display.
  • You will need to generate the RGB signals from the pixel_out signal. The character ROM only indicates foreground vs. background (i.e., the result is only a single bit and does not store color). You will want to choose a foreground color and background color and convert the value of the pixel into your chosen foreground and background colors (i.e., if you want the foreground to be white, you will assign RGB=”11111111” when the pixel_out = ‘1’ and if you want the background to be black, you will assign RGB=”00000000” when the pixel_out = ‘0’). Make sure to blank the RBG signals when the VGA timing controller asserts the blank output.
  • When button 0 is pressed, write the value of the switches into the character memory (the memory location to write to will be described in the next bullet). You will need to add a delay to this button so that you are not sending many characters (similar to the delay used in the VGA display lab).
  • Create a register that indicates the character column and character row where a new character should be written. These registers should be updated every time a character is written so that characters one after another, from left to right. For example, the first character location is character 0,0. After writing a character to this location, increment the column register to 1 so the next character will be written at 0,1. After writing to location 0,79, move to the start of the next row (i.e., 1, 0).

Simulate the writing of values to the character memory and verify that your column and row counters increment properly. Once your circuit has simulated properly, create a UCF file that incorporates the I/O signals used in your project and synthesize your circuit.

After you have resolved all warnings and verified that your I/O pins were properly mapped, test your design on the board and VGA monitor.

Exercise #5 - Download and Test

Exercise #X - Character Generator

Final Pass-Off:

  • Required Files
    • charGen.sv
    • chargen_sim.tcl
    • chargen_sim.png
    • chargen_top.sv *
    • sim_rx_top.tcl
    • sim_rx_top.png
    • rx_ibuf.png
  • Required Makefile ‘rules’
    • sim_chargen_tb: generates sim_chargen_tb.log
    • synth: requires rx.sv, rx_top.sv; generates synthesis.log, rx_top_synth.dcp
    • implement: requires rx_top_synth.dcp; generates implement.log, rx_top.dcp, rx_top.bit, utilization.rpt, and timing.rpt

Answer the final two questions in your laboratory report:

How many hours did you work on the lab?

Provide any suggestions for improving this lab in the future