UART Terminal Communication: Transmit Side

The UART Interface

The Universal Asynchronous Receiver Transmitter (UART) has been one of the most widely used data interfaces for decades. In this lab, we will design a UART and use it to communicate between the Basys3 board and a terminal program on your computer.

UART specs for this lab:

The term “baud” refers to the number of symbols per second transmitted on the UART wire. A symbol is a HIGH or LOW signal value. In order to synchronize communication with a single wire, the protocol uses one start symbol (i.e. start bit) to indicate the beginning of a transmission, and one stop symbol (i.e. stop bit to indicate the conclusion of a transmission. In total, ten “symbols” are sent to communicate an 8-bit word, so the effective bit rate is 0.8 × (9600) = 7680 bits/sec. The pulse width (i.e. duration) of each symbol is 1/9600 seconds, or 104.17us.

Handshaking

We want to design a UART that can be reused in future designs. To make a reusable design, we should plan for how the UART will interact with its application:

UART data frame

The receiving device expects to receive signals formatted like the timing diagram shown below. When the bus wire is idle, it should have a HIGH value. The start condition is when the signal drops from HIGH to LOW.

After the start event, the signal should stay LOW for the duration of one bit. At 9600 baud, the duration is 1/9600 s. Then the data is sent one bit at a time, starting with the LSB. After all eight bits have been sent, a stop bit (logic HIGH) is transmitted, returning the wire to its idle HIGH level. After the stop bit, the UART can immediately follow with another start condition and data frame, or it can hold the line HIGH in an idle condition until it needs to transmit again.

Assigned Tasks

Implement a UART transmitter

Based on the provided state diagram, implement a module named uart_tx with the following specifications:

UART Transmitter State Machine

The transmit operation can be reduced to three states in a high-level machine:

Top Module

Once your UART is verified, create a new Design Source to implement a top module:

module top(
    input clk,
    input rst,
    output tx,
    input send
    );

  reg [7:0]   message[4:0];   // Message to transmit (an array of bytes)
  reg [7:0]   d_out;          // UART transmit data (one byte)
  reg [31:0]  msg_count;      // Array index of the message byte 

  reg         start;          // Handshake start signal to send one byte 
  wire        done;           // Handshake done signal indicating transmit complete

  wire        rst_l;          // Active-low reset signal
  assign      rst_l = ~rst;
  
    uart_tx UART1(
        .clk(clk),
        .d_out(d_out),
        .start(start),
        .rst_l(rst_l),
        .done(done),
        .tx(tx)
        );

Define the top-level behavior. The first if condition initializes the signals. In this example, the message is the string “hello”, which contains five letters. The assignments below use the 8-bit ASCII value for each letter.

always @(posedge clk, negedge rst_l) begin
       if (~rst_l) begin
         msg_count <= 0;
         message[0] <= "h";
         message[1] <= "e";
         message[2] <= "l";
         message[3] <= "l";
         message[4] <= "o";
         start      <= 0;
         d_out      <= "h";
       end

Next define a process to implement the handshaking and send out the message bytes one-by-one:

       else begin
         if (send && (!start) && (!done)) begin
           start <= 1;
           d_out <= message[msg_count];
         end
         else if (start && done) begin
            // De-bounce the send button at end of message:
            if ((msg_count == 4)&&(~send)) begin
               msg_count <= 0;
               start     <= 0;
            end
            // Increment the byte index until reaching the end
            else if (msg_count < 4) begin
               msg_count <= msg_count + 1;
               start <= 0;
            end
         end
       end
    end
    
endmodule

Simulate the UART

A testbench module is provided in src/testbench.v. For this assignment, the testbench is designed a little differently. Since the UART transmission requires precise timing, the testbench uses # delay statements to specify timed events with nano-second precision. These statements are all placed in the initial block, indicating that the simulator should start processing the pattern starting from time 0, with timing controlled by the indicated # delays.

   // -----------------------------------------------------
   // CREATE STIMULI AND REPORT RESULTS
   // -----------------------------------------------------
   integer i;
   integer tx_count;   
   reg [7:0] d_in;
   
   initial begin
      rst      = 1;  // start up in reset condition
      tx_count = 0;
      
      // After 100ns, stop resetting
      #100 rst = 0;

      // Delay 1000ns, then "send"
      #1000 send = 1;

      while (1) begin

     // Wait for a signal:
     while (tx) begin
        // Delay 10us between checks
        #10000;  
     end
     
     $write("Start bit. TX=");
     $fwrite(fid,"Start bit. TX="); 
     
     // Get eight bits:
     for (i=0; i<8; i=i+1) begin
        #104170;
        d_in[i] = tx;
        $write("%b",tx);
        $fwrite(fid,"%b",tx);       
     end

     // Wait for stop bit:
     #104170;
     
     if (tx) begin
        $write(". Stop bit. ");
        $fwrite(fid,". Stop bit. ");
     end
     else begin
        $write(". BAD STOP BIT. ");
        $fwrite(fid,". BAD STOP BIT. ");
     end

     $write("Got %xh('%c')\n",d_in,d_in);
     $fwrite(fid,"Got %xh ('%c')\n",d_in,d_in);
     tx_count = tx_count + 1;    
      end
   end
   // -------------------------------------------------------

Run make to simulate the design. If your UART is correct, you should see this output:

Start bit. TX=00010110. Stop bit.Got 68h('h')
Start bit. TX=10100110. Stop bit.Got 65h('e')
Start bit. TX=00110110. Stop bit.Got 6ch('l')
Start bit. TX=00110110. Stop bit.Got 6ch('l')
Start bit. TX=11110110. Stop bit.Got 6fh('o')

If you don’t see this exact pattern, you will need to debug your design. For debugging, you may want to review the “Fishbone (Ishikawa) Diagram” method described in the SPI_READ_WRITE assignment. You should also run make gui or use gtkwave to analyze the signal timing.

Some things that are likely to go wrong include the following:

This list is not exhaustive by any means, but represents the problems most often encountered in past runs of this class.

Implement and Test

Create an XDC file based on Basys3_Master.xdc. Map one of the buttons to the rst input, and another button to the send input.

Map tx to the USB-RS232 tx pin (search the constraint file for “USB-RS232” to find the correct lines).

Next run make implement to generate a bitstream.

Testing on Linux Systems

On Linux systems, the femtocom program should be installed in /usr/local/bin. If not, download the script from the Canvas page.

Now open a Terminal on your computer and run these commands:

ls /dev/ttyUSB*

You should see one device. Usually it is ttyUSB0 or ttyUSB1 (if the Vivado Hardware Manager is not running, you may see two devices, it’s usually the higher numbered device). If the device is /dev/ttyUSB0, then you would run:

  ./femtocom /dev/ttyUSB0

Then press the send button on your Basys3 board. You should see “hello” appear in your terminal.

Testing on Windows Systems

On Windows computers, the PuTTY terminal program is widely used for serial communication. After installing PuTTY, launch it and you will see a dialog window like the one on the right side of the image below. Find the “serial” radio button and click it (see highlight in the picture). Then you need to specify the COM port. To find the correct port number, open the Device Manager by typing it into the Windows search bar. You should be able to find the COM port list, as shown on the left side of the image below.

PuTTY session configuration.

Verify the session type, COM port, and baud (9600), then click Open to start the session. Press the button on your Basys3 (assuming it’s programmed with the uart_tx bitstream), and you should see your message appear in the terminal:

PuTTY terminal with uart_tx output message.

Test Cases

Record a short video starting with the blank terminal window, then showing you press the “send” button, then showing the “hello” message in the terminal. Upload the video on Canvas.

Turn in your work using git:

git add src/*.v *.v *.rpt *.txt *.tcl *.bit *.xdc
git commit . -m "Complete"
git push origin master

Indicate on Canvas that your assignment is done.