In this lab you will implement a UART (Universal Asynchronous Receiver / Transmitter) device, otherwise known as a serial interface. This task will tie together the tools you have learned in the past 4 labs and will provide a design experience similar to the course project. In addition, your working UART from this lab will be used in your project as part of checkpoint 1.
You are permitted to work with a partner for this lab. It is recommended, but not required, that you work with the person that you intended to work with for the project.
For the prelab, do the following:
You are responsible only for implementing the transmit side of the UART for this lab. We have given you a complete receive side. You would be wise to model your solution after the design we have given you that implements the receive side because they will end up being quite similar.
Recall from lecture how serial devices work. On the ML505, the physical
signaling aspects (such as voltage level) of the serial connection are taken
care of by off-FPGA devices. From the FPGA's perspective, there are two signals,
FPGA_SERIAL_RX
& FPGA_SERIAL_TX
, which correspond to the receive-side and
transmit-side pins of the serial port. The FPGA's job is to correctly frame
characters going back and forth across the serial connection. The figure below
shows a single character frame and will be extremely useful in understanding
the protocol.
In the idle state the serial line is held high. When the TX side is ready to send a character, it pulls the line low. This is called the start bit. Because UART is an asynchronous protocol, all timing within the frame is relative to when the start bit is first sent (or detected, on the receive side). The frame is divided up in to 10 uniformly sized bits: the start bit, 8 data bits, and then the stop bit. The width of a bit in cycles of the system clock is then naturally given by the system clock frequency divided by the baudrate. Notice that both sides must agree on a baudrate for this scheme to be feasible.
Let us first think about sending a character using this scheme. Once we have a character that we want to send out, transmitting it is simply a matter of shifting each bit of the character, plus the start and stop bits, out of a shift register on to the serial line. Remember, the serial baudrate is much slower than the system clock, so we must wait SymbolEdgeTime = (ClockFreq / BaudRate) cycles between changing the character we're putting on the serial line. After we have shifted all 10 bits out of the shift register, we are done.
The receive side is a bit more complicated. Fortunately for you, it is already
implemented and is given to you as part of the lab distribution. You may want to
open up lab5/src/UAReceive.v
and follow along as you are reading this section.
Like the transmit side, the receive side of the serial device is essentially
just a shift register, but this time we are shifting bits from the serial line
into the shift register. However, care must be taken into determining when to
shift bits in. If we attempt to sample the serial signal directly on the edge
between two symbols, we are exceedingly likely to sample on the wrong side of
the edge (or worse, when the signal is transitioning) and get the wrong value
for that bit. The correct solution is to wait halfway into a cycle (until
SampleTime
on the diagram) before reading a bit in to the shift register.
One other subtlety of the receive side is correctly implementing the ready/valid interface. Once we have receive a full character over the serial port, we want to hold the valid signal high until the ready signal goes high, after which the valid signal should be low until we receive another character. This requires using an extra flip-flop that is set when the last character is shifted in to the shift register and cleared when the ready signal is asserted. This allows us to correctly implement the ready/valid handshake.
Although the receive side and transmit side of the UART you will be building are
essentially orthogonal, we are packaging them into one UART
module to keep
things tidy. If you look at UART.v
, you will see that this module is mostly
straightforward instantiations of UAReceive
and UATransmit
, but there are
also two IORegisters
that the serial lines are fed through. What are these
for? An IORegister
is simply a register that attempts to pack itself into a
special block called a IOB
, which are used to drive and sense from the IO
pins. Using an IORegister
helps ensure that you will have a nice, clean,
well-behaved off-chip signal to use as an input or output to your serial
modules.
You will use tools that you have learned in labs 1-4 to implement and test this lab. Make sure to refer back to these old labs if you have any questions about how to run any of these tools.
We have provided a simple testbench, called (predictably) Testbench
that will
run some basic tests on two instantiations of the UART
module with their RX
and TX
signals crossed so that they can talk to each other. There is also a
.do file provided that will run the test. You should note that this test bench
reporting success is not by itself a good indication that your UART is
working properly. Do to the way x
's are treated by Modelsim if a large number
of signals in your design are undefined (which they likely will be due to typos)
the testbench may erroneously pass. Make sure to look at the waveform to see
that everything appears to be working properly.
Your UART will eventually be used to interact with your CPU from your
workstation. Without a CPU, you need some other way to test that your UART works
on the board. We have provided this for you. The provided FPGA_TOP_ML505
contains a very simple finite state machine that simply send ever character it
receives back over the serial device. This will be extremely useful for testing.
Before programming your board, check with the provided EchoTestbench
module
that everything works as it should in simulation.
Once you have echo working in simulation, it is time to try it on the board. Synthesize your design and program the board with it just like you have done in previous labs. Now, make sure the serial cable is plugged in between the ML505 and your workstation and then run
% screen $SERIALTTY 115200
This tells screen
, a highly versatile terminal emulator, to open up the serial
device with a baud rate of 115200. Now, if you have a properly working design,
you should be able to tap a few characters into the terminal and have them
displayed on the screen as they are echoed back to you.
DataOutReady
being set in between? You will have to look at the code for the
instructor's receive-side module to answer this question.