UART Receiver
MIT Fall 2025
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.
To avoid glitches, after detecting a 0
on the receiving wire, you should sample again halfway through the baud period and make sure that the receiving wire is still a 0
. This is because we want to make sure that we are actually receiving a start bit, and not just a spurious 0 caused by a digital glitch or loose wire. At this point, if this wire still 0
, then we should treat the start bit as good and continue capturing the rest of the data, if not, we should go back to IDLE
.
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!
Fast transmitters and our state machine
In order to make a receiver that behaves reliably, especially once we're synthesizing it on the FPGA, we need to ensure it can handle some amount of error in terms of clock frequency. If a transmitter connected to our receiver is operating slightly too quickly (like 1-2% too fast), we should still be able to properly capture the bytes sent. We could need this for a genuinely incorrect transmitter, or to have a buffer in case our sampling of the din
wire results in slightly short or fast bit periods. The majority of our protection in this case comes from the fact that we sample in the middle of the bit period, so even if we are slightly out of sync with our transmitter we still sample the correct bit value. However, we also need to carefully consider what happens if the next start bit happens slightly before we believe the previous stop bit to end.
In order to properly handle this situation of a fast transmitter, it's important that as soon as we check the validity of our stop bit (i.e. halfway through the transmission of the stop bit) we should leave our STOP
state; either to the TRANSMIT
state if the stop bit was valid, or back to the IDLE
state if the stop bit was invalid. This way, if the next start bit begins slightly early, we've already made it to the IDLE
state and are ready to handle it. Additionally, the condition to move from IDLE
to START
should be the presence of any 0
on the din
wire, not a falling edge, in case the next start bit starts earlier than expected such that there isn't enough time to measure.
Here is a code skeleton for the UART Receiver module you can use to get going.
`timescale 1ns / 1ps
`default_nettype none
module uart_receive
#(
parameter INPUT_CLOCK_FREQ = 100_000_000,
parameter BAUD_RATE = 9600
)
(
input wire clk,
input wire rst,
input wire din,
output logic dout_valid,
output logic [7:0] dout
);
typedef enum {
IDLE = 0
// TODO: define the rest of the states your receiver needs to operate
} uart_state;
// note: for the online checker, don't rename this variable
uart_state state;
// TODO: module to read UART rx wire
endmodule // uart_receive
`default_nettype wire
Parameters
parameter INPUT_CLOCK_FREQ
: frequency, in Hz, ofclk
parameter BAUD_RATE
: bitrate for UART transmission, in bits/second
Inputs and Outputs
input wire clk
: operational clockinput wire rst
: signal to reset the module (active high)input wire din
: serial input signal, as described by UARToutput logic dout_valid
: 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] dout
: the byte received over the RX wire, valid whendout_valid
is high
Internal Operation
You must design this module as a Finite State Machine, using a state variable called state
with values of IDLE,START,DATA,STOP,TRANSMIT
as described in the description above. The checker below will look for that to a certain extent, but it will also be verified in your checkoff on the next page (and you will not get the checkoff unless it is written as an FSM.)
Did you use a shift buffer? A UART receiver that uses variable indexing may be able to pass this checker, but will not get you a checkoff! Make sure you are using a shift buffer!