Now that you’ve completed a UART transmitter, in this lab you will design a UART receiver, implement and test it alongside your transmitter, similar to what was done in the serial_interface
and SPI_WRITE
assignments.
As a reminder, the UART specs for this lab are:
uart_rx
Design a UART receiver module with these I/O ports:
clk // system clock
rst_l // active-low reset
rx // UART serial data input
d_in // received data (8 bits)
ready // "data is ready" handshake signal
ack // "data acknowledged" handshake signal
The UART RX module is very similar to the TX design, except the flow of information is reversed. Use the TX state machine as a starting point, and design a modified state machine to produce the receiver’s behavior. Note that this is very similar to what was done in previous assignments.
For handshaking on the application side, use a procedure similar to the S2P
module from the serial_interface
assignment. Include handshaking signals in your state machine.
Scan an image of your state transition diagram and add/commit it to the repository.
Program your state machine in the uart_rx
module.
The UART RX is very similar to the SPI RX, with one important difference: since the UART channel has no clock, you should initialize the UART clock divider so that it has a rising edge immediately when the transmission begins.
To support this timing synchronization requirement, modify the clock divider. Supposing your divider has a UART clock named div_clk
with a counter named div_count
, you should modify the logic as follows:
WAIT
state and rx
is 1
, hold both div_clk
and div_count
at 0
. (In other words, the 9600Hz UART clock is stalled until the start bit occurs).WAIT
state and rx
is 0
and div_count
is 0
, then toggle div_clk
to 1
and increment div_count
.else
) the divider acts normally: if div_count >= N
then toggle div_clk
and reset div_count
; else increment div_count
.One approach is to use a clock_divider
submodule with a sync
input. Whenever sync
goes high, it forces the clock divider output to zero. When sync
goes low again, the clock divider resets its phase with a rising edge a short delay after sync
event. In the example below, the delay is set to 1000 cycles of the main system clock. The purpose of this delay is to let the rx
signal settle to a stable value before reading its value.
module clock_divider
#(parameter N=5208
)
(input clk,
input rst_l,
input sync,
output reg div_clk
);
integer clk_count;
initial begin
0;
div_clk = 0;
clk_count = end
always @(posedge clk, negedge rst_l) begin
if (!rst_l) begin
0;
div_clk <= 0;
clk_count <= end
else begin
//---- sync behavior -----------------//
if (sync) begin
0; // set div_clk low
div_clk <= -1000; // delay 1000 cycles before rising edge
clk_count <= Nend
//---- normal clock divider below ----//
else if (clk_count == N) begin
div_clk <= ~div_clk;0;
clk_count <= end
else begin
1;
clk_count <= clk_count + end
//-------------------------------------//
end
end
endmodule // clock_divider
uart_rx
Modify the testbench from uart_tx
so that it sends a transmission. Declare the necessary wire
and reg
signals that correspond to the uart_rx
ports. Remove or comment out unused signals from the tx
module.
The section to focus on is this one:
1; // <--- add this line to initialize rx in the idle mode
rx = 0; // <--- add this line to initialize handshake
ack =
#1000 send = 1; // <--- remove this line
while (1) begin
$random(); // <--- add this line to set data to send
d_out =
// Wait for a signal: // <--- remove
while (tx) begin // all
// Delay 10us between checks // of
#10000; // these
end // lines
$write("Sending %x\n",d_out); // <-- add this
$fwrite(fid,"Sending %x\n",d_out); // <-- and this
$write("Start bit. TX="); // <--- change "TX" to "RX"
$fwrite(fid,"Start bit. TX=");
0; // <--- add this line to send START BIT
rx=
// SEND eight bits:
for (i=0; i<8; i=i+1) begin
#104170;
// <-- Change to rx = d_out[i]
d_in[i] = tx; $write("%b",tx); // <-- tx to rx
$fwrite(fid,"%b",tx); // <-- ditto
end
// Wait for stop bit:
#104170;
if (tx) begin // <-- remove the `if` line
$write(". Stop bit. ");
$fwrite(fid,". Stop bit. ");
end // <-- remove
else begin // <-- remove
$write(". BAD STOP BIT. "); // <-- remove
$fwrite(fid,". BAD STOP BIT. "); // <-- remove
end // <-- remove
// Add these lines:
while (!ready) begin
#104170; // wait for 'ready' signal
end
1;
ack =
// Keep these lines:
$write("Got %xh('%c')\n",d_in,d_in);
$fwrite(fid,"Got %xh ('%c')\n",d_in,d_in);
// Add these lines to test handshake:
while (ready) begin
#104170; // wait for 'ready' signal to drop
end
0; // clear ack
ack =
#104170; // wait one more UART clock cycle before sending again
1;
tx_count = tx_count + end
Complete your testbench, run the simulation and verify that your module receives the same data that was transmitted.
top
moduleCreate a top
module based on the SPI_WRITE
assignment. Instead of the SPI
modules, place one instance of uart_tx
and one instance of uart_rx
, and connect the tx
port from one to the rx
port of the other. The uart_tx
module will send data from the switches, and uart_rx
will write data to the LEDs.
Use buttons for the start
and ack
signals. Use modified debouncers to implement the handshaking protocols.
Create a new testbench based on the SPI_WRITE
assignment to test your top
module. Don’t delete the other testbench that you made; instead, name this one top_testbench
and edit the Makefile by adding these lines:
top: compile elaborate_top simulate
elaborate_top: compile
xelab -debug typical -s sim top_testbench
Important note: there should be a TAB prior to the xelab
command, not spaces.
Then run your simulation using the command make top
.
Create build.tcl
and XDC files for your top
module, implement it and program onto the Basys3 board.
After the design is debugged and verified to work properly, record a short video showing your design work and post the video to 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