UART Receiver
MIT Fall 2024
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.
Now that you've written a transmitter for UART, it's time to cover the other side and build a receiver. Unlike the SPI receiver from last week, we don't have a clock to determine when to read our data, since, after all, UART is Asynchronous. So we'll have to be more intentional about when we choose to read each bit we receive.
UART Receiver Sampling
Based on the BAUD rate, we know how long we expect each bit to appear for, but we want to make sure that slight inconsistencies in timing between the transmitter device and receiver device make as little difference as possible. If we measured each bit right as we expect it to first appear, we could unintentionally re-read the previous bit and corrupt our read data--so, instead we want to read each bit received in the middle of the period in which it is meant to be transmitted. This is very, very important.
Just like in our transmitter, we can calculate the UART_BIT_PERIOD
we expect for the full period of each bit on the serial wire. Based on that value, we want to read our UART data bits UART_BIT_PERIOD/2
clock cycles into each period.
The way we determine when our count of clock cycles begins is at the beginning of the start (0
) bit. The transmission wire can sit idle (logical 1
) for an unspecified amount of time, but the moment that it gets pulled low is when we need to start keeping time: the first clock cycle that reads as 0
is determined as the beginning of a data frame, and from there we count our bit periods based on that start time.
Finite State Machine: start and stop bit checking
Once we've begun reading a data frame by detecting a 0
on the receiving wire, by default we expect to see 10 bits appear afterwards, each lasting for UART_BIT_PERIOD
cycles: a start bit of 0
, the 8 bits of the transmitted data, and an end bit of 1
. However, there's a chance that we're actually reading meaningless signals: maybe a loose connection or a digital glitch meant that a 0
came across the wire when it shouldn't have. That's where the start and stop bits come in handy: if the bit that's meant to be a start bit is not a 0
, or if the bit that's meant to be a stop bit is not a 1
, we should stop what we're doing and not interpret our signal as a valid byte.
To accomplish this, we can make a simple state machine for our UART receiver. We want our receiver to begin in an IDLE
state, and get pulled out of it when we receive a 0
on our wire. Then we want to progress through states to read our START
bit, our DATA
bits, and our STOP
bit. But with the START
and STOP
bits, our steps should be interrupted if we read incorrect bits from the wire: when an incorrect START
or STOP
bit is detected, we should return to our IDLE
state instead of continuing on. If both the START
and STOP
bits read correctly, we can progress to our TRANSMIT
state and propogate our data appropriately.
When in the START
, DATA
, and STOP
states, your module should be counting clock cycles to measure each bit period, and counting how many bits have been received. In these states, wait for the midpoint of the bit period to sample data, and write it to a register to accumulate all the bits of the frame.
As you write your receiver module, be sure also to look back at the specification of UART from the last exercise to ensure you interpret bits in the correct order!
Parameters
parameter INPUT_CLOCK_FREQ
: frequency, in Hz, ofclk_in
parameter BAUD_RATE
: bitrate for UART transmission, in bits/second
Inputs and Outputs
input wire clk_in
: operational clockinput wire rst_in
: signal to reset the module (active high)input wire rx_wire_in
: serial input signal, as described by UARToutput logic new_data_out
: single-cycle high as soon as we've verified that a byte was successfully received. No need to wait for the end of the full stop bit, so long as the message was valid.output logic [7:0] data_byte_out
: the byte received over the RX wire, valid whennew_data_out
is high