UART Transmitter

MIT Fall 2024

The questions below are due on Wednesday September 25, 2024; 11:59:00 PM.
 
You are not logged in.

Please Log In for full access to the web site.
Note that this link will take you to an external site (https://shimmer.mit.edu) to authenticate, and then you will be redirected back to this page.

UART Protocol

Last week, we saw the SPI protocol in use, and now it's time to use another serial data protocol: Universal Asynchronous Receiver/Transmitter, or UART.

A serial UART transmission of the byte 0x35. Here, there are three clock periods per Baud period, but usually that value is much larger.

This page will have you build a transmitter for UART, which only needs one wire to transmit data as opposed to the three wires of SPI (data, clock, and chip select)! By moving to fewer wires (a benefit), however, there are sacrifices that need to be made in order for it to work reliably.

Here's a handful of highlights of the protocol that'll be important for implementing it:

  • Asynchronous

The UART protocol has only two wires, one for transmitting data and one for receiving data. What that means is there's no clock to rely on. Instead, both sides of the bus agree on a BAUD rate, which defines the number of bits that are transmitted on a wire per second. BAUD rates need to be substantially smaller than the operating clock of the devices on either side of the bus, so that each device can reliably keep time as they send and receive data.

  • Start and Stop Bits

In order to distinguish when there's silence as opposed to a byte being sent on a serial wire, UART includes a "start" bit (0) and a "stop" bit (1) before and after each byte. The wire is held high (logical 1) when not in use to send data, and then the "start" bit (0) indicates that data is about to be sent. The "stop" bit (1) should be sent after the byte is transmitted.

  • Little Endian

When the bits within a byte are being sent, UART decided (somewhat arbitrarily, but largely now for backwards compatibility reasons/convention) that the first bit sent should be the least significant bit of the byte (i.e. byte[0], i.e. the "1's" place of the byte), and then the following bits will each be from a more significant place. This is different from what we did in SPI1.

Check Yourself: UART

In terms of the input clock speed clk_i and BAUD rate baud, how many clock cycles should a single UART bit last for? Use clk_i to represent the input clock frequency, and baud to represent the BAUD rate.

When you write modules for UART, it may be helpful to define a local parameter localparam BAUD_BIT_PERIOD to be equal to this expression.

With a clock speed of 100MHz and a BAUD rate of 19200, how many clock cycles should it take to send one full byte of data? Include the time to send start and stop bits.

The letter "f" is encoded in ASCII as the byte 0x66. What sequence of bits, including the start and stop bit, would transmit the letter "f" over UART?

Building Your Own Transmitter

Try your hand at making a UART transmitter module based on what we discussed above, that can take in data bytes and send them serially over a UART wire.

Parameters

  • parameter INPUT_CLOCK_FREQ: frequency, in Hz, of clk_in
  • parameter BAUD_RATE: bitrate for UART transmission, in bits/second

Inputs and Outputs

  • input wire clk_in: operational clock
  • input wire rst_in: signal to reset the module (active high)
  • input wire [7:0] data_byte_in: carries the byte that should be transmitted over UART; it should only be read when a transmission is started!
  • input wire trigger_in: indicates that a byte transmission should be started, and that data_byte_in contains valid data for the next transmission. Should be ignored if your module is still busy sending a message!! Don't let a new trigger interrupt an ongoing message.
  • output logic busy_out: should be held high whenever the module is still transmitting a byte and can't accept new data input. We didn't do this in our SPI module, but this is a means of allowing "back-pressure", which is a way of telling the system driving the module that new transmits will not be accepted right now (and to, if needed, hold up its operation). This should be busy even when sending start and stop bits!
  • output logic tx_wire_out: serial output signal, as described by UART

Testing

We're into our third week together. It is time that you start to generate your own test code from scratch. Feel free to bring over your SPI tester code from last week for at least a boilerplate starter (but of course make sure to update/modify all appropriate fields, variables, and function names. use the cocotb documentation, and the exercise from week 01, as reference when writing your tests! A few things to keep in mind while testing:

  • When a device is not transmitting, the data line must be kept high. This includes when being reset. Be very careful to avoid false 0's. These can trigger the listener to think a message has started transmitting and cause bad packets to be received. This can impact not only the next byte sent, but many bytes into the future depending on the contents of the data.
  • The stop and start bits are extremely important. They must be full-length (as long as any data bit). Do not neglect them.
  • Things are transmitted lsb-first. lsb-first.
  • lsb-first.

module uart_transmit
Submit your tested UART transmitter module here.
Code Skeleton
  No file selected


 
Footnotes

1SPI in general doesn't actually specify whether data needs to be sent MSB-first or LSB-first. That's left up to the individual chips to decide. Most of the time you'll see MSB-first, but it can vary. (click to return to text)