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.
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:
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.
Based on the provided state diagram, implement a module named uart_tx with the following specifications:
start
, clk
, and rst_l
(all 1-bit)d_out
(8-bit)done
and tx
(both 1-bit)rst_l
initializes the module in an IDLE
or WAIT
statetx
to the Basys3 board’s UART tx
pin.The transmit operation can be reduced to three states in a high-level machine:
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
0;
msg_count <= 0] <= "h";
message[1] <= "e";
message[2] <= "l";
message[3] <= "l";
message[4] <= "o";
message[0;
start <= "h";
d_out <= 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
1;
start <=
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
0;
msg_count <= 0;
start <= end
// Increment the byte index until reaching the end
else if (msg_count < 4) begin
1;
msg_count <= msg_count + 0;
start <= end
end
end
end
endmodule
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
1; // start up in reset condition
rst = 0;
tx_count =
// 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);
1;
tx_count = tx_count + 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:
testbench
. Could be caused by incorrect port connections, incorrect handshaking, a bad clock divider, or a bad state machine. You’ll want to check start
, the UART clock, and the UART state
.tx
assignment statement. Also check the d_out
declarations in top
and UART_tx
, and make sure they have 8 bits. Check the port connections where UART_tx
is instantiated. Also check your clock divider timing; if your baud rate is way too slow then testbench
will think it sees all zeros, and will report a BAD STOP BIT for each byte.STOP
state.This list is not exhaustive by any means, but represents the problems most often encountered in past runs of this class.
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.
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.
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.
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:
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.