Lab 10: 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.

The average time to complete this lab is 8 hours.

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, and taking the top five 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 modulo 8. The y-pixel location within a character can be found by computing pixel_y modulo 16. Performing a modulo operation using powers of 2 is very simple in a digital system. This is done by simply selecting the lower 3 bits of the pixel_x value and the lower 4 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 char_gen/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 / 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 char_gen/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 index will you read from in 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 location?

Refer to the timing diagram below and answer the following questions. For each answer, provide your response as an 8-bit SystemVerilog value (i.e., 8’b00000000).

What is the value of the ‘data’ output between clocks 1 and 2 (i.e., 1.5)?

What is the value of the ‘data’ output between clocks 2 and 3?

What is the value of the ‘data’ output between clocks 3 and 4?

Exercise #2: Character Generator

In this exercise you will create a character generator module to 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 char_gen/char_gen.sv with the following ports:

Parameter Default Function
FILENAME ”” Specifies the filename of the initial contents of the memory
Module Name: char_gen      
Port Name Direction Width Function
clk Input 1 100MHz 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 7-bit value to pad and 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

Please note: As there is not a rst port signal in the char_gen module, your always_ff blocks in this module will not need to include a reset condition.

The character generator has three primary components:

  1. The Character Memory, which stores which character to display at each position on the screen (i.e., position (0,0) should display character ‘E’, position (1,0) should display character ‘C’, etc.).
  2. The Font ROM, described previously, stores the pixel patterns for each character.
  3. An output pixel multiplexer, which selects the appropriate pixel from the font ROM to output based on the current pixel coordinates.
Character Generator Architecture

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.

We will use a read/write memory for this function called the character memory. 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 write port will be used occasionally to update the character display and the 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 diagram above shows this memory as a separate module, with write (wr_*) and read (rd_*) ports, and a clk input. You might think we should instance a memory block and wire up to these ports. However, instancing an FPGA memory block directly from our code is somewhat complex and unnecessary. In the previous labs you have written behavioral always_ blocks and the synthesis tools has automatically mapped these to FPGA LUTs and flip-flops. Similarly, we will describe a memory behaviorally using always_ blocks, and the synthesis tools will automatically map this to one of the FPGA BRAMs. As such, the code we write will look a bit different than the diagram above, but the resulting circuit will match the diagram.

You do not need to create a separate module for the character memory, we will describe the character memory behaviorally within the char_gen module, as described in the following steps.

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

     logic [7:0] mem_data[0:4095];
    

    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 4096x8 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 five bits (char_mem_addr[11:7]) are needed to determine the character row (i.e., 2^5=32) The bottom seven address bits (char_mem_addr[6:0]) are needed to determine the character column (i.e., 2^7 = 128). Although this larger sized memory will result in wasted memory, it will significantly simplify the circuit you will create.

    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 character 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?

  2. Create synchronous logic to write values into the character memory (called the write port).

    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 writing to the memory: the address (char_addr input port), the character value to write (char_value input port), and the write enable (char_we input port). To store a new character in the display, a higher level module will need to provide these inputs, but that is done later in the lab.

    The write port logic is described with an always_ff block that updates the value of a given index in the mem_data array. When you write to mem_data, because the char_value is only 7 bits and we only need 7 bits, but our character memory (mem_data) requires 8 bits, simply pad char_value with a 1'b0 in the most significant bit.

  3. Create a synchronous read port for the character memory.

    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. An internal 12-bit char_read_addr signal will be used to specify the address to read from the character memory, and an internal char_read_value signal will be used to store the value read from the character memory. The char_read_addr is a function of the pixel_x and pixel_y input ports, which can be generated by concatenating the appropriate bits from these signals.

    The read port logic is also described with an always_ff block that reads the value from the mem_data array at the specified char_read_addr address and stores it in the char_read_value signal. There is no read enable signal for the read port; the memory is always being read.

  4. Initialize the memory contents from a file (if specified).

    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).

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

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.

  • Instance the font ROM in your character generator module.
  • Create the necessary logic for the font ROM read address.
    • This depends on the current character to be displayed (read from the character memory, char_read_value), and the current row within that character (determined by bottom 4 bits of pixel_y).
    • Note that due to the 1-cycle latency of the character memory, the char_read_value is delayed by one clock cycle from the pixel_y signal. Feed the pixel_y signal through a one-cycle pipeline register to create a delayed version of pixel_y (call this pixel_y_r).
    • Use char_read_value (you only need the lower 7 bits) and pixel_y_r to create the font ROM read address, font_rom_addr. The upper 7 bits of the address come from char_read_value, and will index to the appropriate character within the font ROM, while the lower 4 bits come from pixel_y_r, and will index to the appropriate row within that character.
  • Create an 8-bit signal, rom_data, connected to the output of the font ROM.

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 yo u 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_rr that is two cycles behind pixel_x as shown in the figure above. Use this delayed signal to select the appropriate pixel from the font ROM output.

Character Generator Timing Diagram

Include the file char_gen/char_gen.sv in your repository.

Exercise #3: Character Generator Simulation

After creating your module, create a Tcl simulation script for 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. Perform a character write to location 0x000 with the value 0x41 (ASCII ‘A’) and run for a few clock cycles
  4. 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
  5. Do the same thing as above but for pixel_y=3

Note that when you simulate your module, the memory will not be initialized with any values. This is ok - you will be loading a character into the memory during the simulation. Take a screenshot of the waveform.

Include the file char_gen/sim.tcl in your repository.

Include the file char_gen/sim.png in your repository.

After simulating your module and verifying it operates with the Tcl script, you will need to test your receiver with the testbench. You may want to begin by testing the decoder interactively in GUI mode with this testbench to resolve syntax and behavioral problems in your design.

Make sure that your testbench simulates without any errors before proceeding

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

What is the hex value of the character which is being displayed at location pixel_x=349, pixel_y=304?

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 switches or the rx UART receiver and display them on the screen. Your terminal will have the ability to change the foreground and background colors of the characters.

Begin the exercise by create a new file named char_gen_top/char_gen_top.sv and include the following ports:

Parameter Default Function
FILENAME ”” Specifies the filename of the initial contents of character memory
CLK_FREQUENCY 100_000_000 Specifies the frequency of the clock in Hz
BAUD_RATE 19_200 Determines the baud rate of the receiver
WAIT_TIME_US 5_000 Determines the wait time, in micro seconds, for the debounce circuit
REFRESH_RATE 200 Specifies the display refresh rate in Hz
Module Name: char_gen_top      
Port Name Direction Width Function
clk Input 1 100MHz System Clock
btnd Input 1 Reset
btnc Input 1 Write character
btnl Input 1 Set background color
btnr Input 1 Set foreground color
rx_in Input 1 UART Receiver Input
sw Input 12 Determines 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
anode Output 4 Anode signals for each of the four display digits
segment Output 8 Cathode signals for seven-segment display

There are several components needed to implement the top-level design. Follow the guidelines listed below to build your top-level design.

Synchronizers

Create two flip-flop synchronizers for the following asynchronous inputs: btnd, btnc, btnl, btnr, and rx_in. The internal reset signal should be driven by the synchronized btnd signal. Create a one flip-flop synchronizer for the switches (sw).

Seven Segment Display

The seven segment display is used to display the value of the switches to give you feedback on the switch values. Instance the seven segment display controller from your previous lab and pass the top-level parameters to your seven segment module. Attach the switches (lower 12) to the input to the display and only display the right three digits. Disable all digit points.

Background/Foreground Colors

Create two 12-bit registers to represent the background and foreground colors of the characters. Set the initial values of these registers to 12'h000 (black) for the background color and 12'hfff (white) for the foreground color. This initial value can be set when you declare the signals as follows:

logic [11:0] foreground_color = 12'hfff;
logic [11:0] background_color = 12'h000;

When btnl is pressed, set the background color to the value of the switches. When btnr is pressed, set the foreground color to the value of the switches.

Please note: Although initializing signals at declaration is generally prohibited (see coding standard rule S12), this lab explicitly allows setting initial color values as shown above.

VGA Timing Controller and Character Generator

Instance the VGA timing controller from the VGA display lab. Attach the clock signal and reset signal to the module. Create internal signals for the h_sync, v_sync, pixel_x, pixel_y, and blank signals (the details on these signals will be discussed below).

Instance the char_gen from the previous exercise. Attach the clock signal to the module and attach the pixel_x and pixel_y signals from the timing controller to the character generator. Attach internal signals to the char_we, char_addr, char_value, and pixel_out ports (the details on these signals will be described later).

UART Receiver

Instance your UART receiver from the UART receiver lab and pass the top-level parameters to your UART receiver module. Attach the clock and reset to your module. Create internal signals for the other ports (the details on these signals will be described later). Like the last lab, create an ack signal that connects Receive to ReceiveAck to immediately acknowledge any received UART data.

Writing Characters to the Display

An important function of the top-level design is to write characters to the display and overwrite the existing characters. There are two ways to write characters to the display: through the UART receiver or through the switches and buttons. Characters received by the rx module can be displayed on the screen and ASCII values specified on the switches can be displayed when btnc is pressed.

To support character writing, create a 12-bit register for the character address char_addr used to determine where in the memory to write the character. This address should be updated every time a new character is written to the memory. As described earlier, bits [6:0] of this address specify the character column and bits [11:7] specify the character row. Design this register to increment as follows every time a character is written:

  • If the character column is less than 79, increment the column by one.
  • If the character column is 79 and the row is 29, set the column and row to 0.
  • If the character column is 79, set the column to 0 and increment the row by one.

The next step needed to write characters is to generate the char_we control signal for controlling when the character data is written to memory. New characters are written when btnc is pressed or when a character is received from the UART receiver. To generate the control signal from btnc, instance a debouncer circuit for btnc (use the synchronized signal) to debounce the button press and add a one-shot circuit to make sure that only one character is written when a button is pressed. The char_we signal should be asserted when either the center button one-shot signal is asserted or the UART receives a new character.

The final step needed for character writing is to determine the char_value signal to write to the memory. This signal can be generated by a simple multiplexer. When the btnc one-shot control signal is asserted, the character data comes from the switches. Otherwise, the data comes from the Dout port on the rx module.

VGA Color Signals

The final step is to generate the VGA output signals (timing and color signals).

The RGB output signals are determined by the blank signal from the timing generator and the pixel_out signal from the character generator.

  • When the blank signal is asserted, the RGB signals should be set to 12'h000 (black).
  • When the blank signal is deasserted, the RGB signals should be set to the foreground color when the pixel_out signal is asserted and the background color if the pixel_out signal is deasserted. Add output registers to your VGA color signals to ensure that there are no glitches on these signals.

VGA Timing Signals

The timing signals, HS and VS, are generated by the VGA timing controller. These signals, however, need to be synchronized with the color pixel signals described above. Due to the latency involved with generating the ‘pixel_out’ signal in the character generator, the ‘HS’ and ‘VS’ signals need to be delayed by two clock cycles. In addition, by adding a register for the outputs of the RGB signals to remove glitches, the ‘HS’ and ‘VS’ signals will be delayed by an additional clock cycle. You will need three pipelining registers to match the timing of the HS/VS signals with the RGB signals.

Include the file char_gen_top/char_gen_top.sv in your repository.

Exercise #5: Top-Level Simulation

Once you have created your top-level design, you will perform a short simulation to demonstrate the writing of character data is working properly. Your top-level module should have a parameter named FILENAME that specifies the filename containing the initial contents of the character memory. By default, this FILENAME is empty indicating that the memory should not be initialized with any values. For this exercise, you will need to create a memory initialization file that initializes the character memory with Abraham Lincoln’s “Gettysburg Address”. The text for this document is saved in the file gettysburg_address.txt.

A python script has been created for to convert this text file into a memory initialization file:

python3 gen_initial_chars.py gettysburg_address.txt gettysburg_address.mem

Simulate your top-level design with the following two parameters (note that you need the ../.. since the gettysburg_address.mem file is two directories up from the work directory where the simulation is run):

SIM_PARAMS = WAIT_TIME_US=50 FILENAME=../../gettysburg_address.mem

Create a TCL simulation file (sim.tcl) that does the following:

  • Simulate loading the character S into the character memory by pressing btnc (make sure to give the button sufficient wait time to get through the debouncer)
  • Simulate loading the character A into the character memory over the UART receiver (this is the same character sent in your previous lab - you can copy the tcl lines for performing this simulation).

Include the following signal in your simulation: clk, btnc, sw, rx_in, hsync, vsync, VGA signals, char_addr, char_we, and char_write_value.

Take a screenshot of the simulation showing the char_gen writes.

Include the file char_gen_top/sim.tcl in your repository.

Include the file char_gen_top/sim.png in your repository.

Exercise #6: Synthesis and Implementation

  • Create a char_gen_top/basys3.xdc file that contains pin location definitions for each of your I/O ports.

    Include the file char_gen_top/basys3.xdc in your repository.

  • For the synthesis and implementation step, you are required to create your own text message to display on the screen. Create a text file name mymessage.txt that includes inspirational, uplifting, or positive text that fills a good portion of the screen (i.e., the message uses the majority of the lines in the display). Create a Makefile rule named mymessage.mem that generates mymessage.mem file from your text message. Further, ignore the .mem file and clean the .mem file as part of the clean process.

  • Before running synthesis, you will need to use SYNTH_PARAMS to set the FILENAME appropriately.
    SYNTH_PARAMS = FILENAME=../../mymessage.mem
    
  • Run synthesis and carefully review the synthesis log to make sure there are no warnings.

    How many FDCE/FDRE/FDSE cells are used by the design?

    How many total LUT resources are used by the design? (add up all the LUT* resources)

    How many total Block RAMs are used by the design? (RAMB18E1 or RAMB36E1)

    How many total IBUF and OBUF resources do you have in the design?

  • Run implementation.

  • Review the utilization.rpt file to answer the following questions.

    How many ‘Slice LUTs’ are used by your design?

    How many ‘Slice Registers’ are used by your design?

  • Review the timing.rpt file to answer the following questions.

    What is the Worst Negative Slack or ‘WNS’ of your design?

    How many TNS total Endpoints are there in your design?

Open your implemented design in Vivado, and on the ‘Netlist’ tab to the left of the implementation view, expand the netlist to find the Font Rom component (something like: char_gen->fontRom->Leaf Cells->addr_reg_reg). The cell type of the Font ROM is RAMB18E1. Select this cell and take a screenshot of the memory resource.

Include the file char_gen_top/fpga.png in your repository.

Exercise #7: Download and Test

Once the bitstream has been generated, download your bitstream to the FPGA. Hook up your board to the VGA monitor and power it on to make sure the display is operating correctly. Test the ability to receive characters and display them on the screen by running ‘PuTTY’ as in the previous lab. Review the instructions for connecting your transmitter with PuTTY in the PuTTY tutorial. Set the baud rate to 19,200, 8 data bits, ODD parity, and 1 stop bit and connect to the appropriate COM port.

Final Pass-Off:

Refer to the passoff tutorial for the passoff process.

  • See required_files in the passoff script.
  • Makefile rules that must work correctly and be free from errors and warnings:
    • Makefile: clean, mymessage.mem, gettysburg_address.mem
    • char_gen/Makefile: sim_tb
    • char_gen_top/Makefile: synth, implement

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