Our top-level design task is to create an internal UART link, consisting of a TX module and an RX module, to transmit data from the Basys3 switches and display the data on the LEDs. The user provides handshaking by pressing buttons. BtnU
initiates the start signal, and BtnL gives the acknowledge or ack
to complete the transaction and update the LEDs.
A block diagram is shown below.
Here are some of the main features:
UART_TX
module’s tx
output is connected to the rx
input of the UART_RX
module.start
and ack
buttons.ready
signal is inverted to clear the ack
debouncer, so the handshake sequence is:
rx
ready
goes HIGHack
buttonready
goes LOW and debouncer is cleared (reset).done
and ack
are displayed on the higher LEDs. Output data is displayed on the lower LEDs.ack
is HIGH, d_out
is written onto the lower LEDs.button_pusher
Test ModuleSince this design requires a human user to press the start
and ack
buttons, the testbench should account for the random timing of button-push events, and should also simulate “bounce” events associated with button pushing. To this end, a button_pusher
module is provided in the demos/
directory. The button_pusher
simulates the random pressing of buttons.
The button_pusher
interface is:
module button_pusher #(
parameter Nbtn=5
)
(
input clk,
output reg [Nbtn-1:0] btn
);
The button_pusher
module uses a parameter, Nbtn
, to specify the number of buttons used in the design (default is all five buttons). The module’s output is a vector indicating which buttons are pressed. If a button is pressed, the corresponding bit of btn
is 1
, otherwise the bit is 0
. The simulated user pushes one button at a time. Button presses and releases occur at random. Buttons may bounce (toggle briefly between 1
and 0
) when changing between pressed and released states.
When running the test_button_pusher.v
simulation (also in demos/
) you see a log of button events like this:
btn 2 ON
(bounce)
btn 2 ON
2652: btn = 00100
(bounce)
2654: btn = 00000
btn 2 ON
2655: btn = 00100
btn 2 OFF
3272: btn = 00000
(bounce)
3273: btn = 00100
3274: btn = 00000
(bounce)
3278: btn = 00100
(bounce)
3280: btn = 00000
(bounce)
3281: btn = 00100
(bounce)
(bounce)
3284: btn = 00000
(bounce)
3289: btn = 00100
3290: btn = 00000
(bounce)
3291: btn = 00100
3292: btn = 00000
btn 4 ON
5252: btn = 10000
(bounce)
5254: btn = 00000
The indented lines show the clock count and the value of btn
. There are short intervals between bounce events, and long intervals between ON/OFF events.
Debugging a system design can be difficult, since problems can be hidden down multiple layers of hierarchy. One way to improve design transparency is to insert $display
or $write
statements within your UART_TX
and UART_RX
modules, so that you get some feedback when key events occur. A good place to put these is in state transitions, or when transmitting/receiving a bit. Here are some examples:
// in UART_TX module:
case (state)
WAIT:
begin
if (start) begin
<= 0; // start at LSB
bit_index <= 0; // start bit
tx <= SEND;
state $display(" ++TX: Starting %hh",d_out);
end
else begin
<= 1; // idle signal
tx <= 0; // not done, ready for data
done end
end
The above example prints out a message when the TX module starts a transmission. The message also indicates the data to be transmitted.
// in UART_RX module:
WAIT:
begin
if (!rx) begin
<= 0; // start at LSB
bit_index <= RECV;
state $display(" -- RX: start bit detected");
end
end
The above example prints out a message when the RX module sees the start bit. This indicates that the TX and RX modules are both engaged in communication.
// In the RX module:
RECV:
begin
[bit_index] <= rx;
d_in$write(">%b ",rx);
if (bit_index == 7) begin
<= STOP;
state $display(" -- RX: checking for STOP bit");
end
else begin
<= bit_index + 1;
bit_index end
end
The above example prints out each bit as it is received. Once all the bits are received, it prints a message indicating that it will check for the STOP bit.
// In the TX module:
STOP:
begin
if (start) begin
<= 1; // Signal that the transmission is complete
done <= 1; // Stop bit
tx end
else begin
<= 0;
done <= WAIT;
state $display(" ++TX: Completed.");
end
end
The above example prints out a message when the TX module is finished. Similarly, the RX module can announce when it’s wrapping up:
// In the RX module:
STOP:
begin
if (!ready && rx) begin
$display(" -- RX: data %hh ready, waiting for ack", d_in);
<= 1; // Signal that the transmission is complete
ready end
else if (ack && ready) begin
$display(" --RX: completed with %hh", d_in);
<= 0;
ready <= WAIT;
state end
end
The above example prints out a message when the STOP bit is detected (i.e. when rx
goes HIGH to terminate the transaction). It prints another message when the ack
signal is detected, indicating that the transaction is completed.
With these messages printed by the TX and RX state machines, the console produces information like this:
++TX: Starting 24h
-- RX: start bit detected
>0 >0 >1 >0 >0 >1 >0 >0 -- RX: checking for STOP bit
-- RX: data 24h ready, waiting for ack
++TX: Completed.
This information is detailed enough to verify that the TX and RX state machines are not stuck, that the UART transmission is initiated and detected, the individual bit values are correct, and the transaction is properly concluded.
sync
ProcessIn the UART_RX module, it’s necessary to have a synchronization process so that the receiver state machine is
in-phase with the UART data. This can be done using a synchronizing clock divider:
module clock_divider
(
#parameter N=5208
)
(
input clk,
input rst_n,
input sync,
output reg div_clk
);
integer clk_count;
initial begin
= 0;
div_clk = 0;
clk_count end
always @(posedge clk, negedge rst_n) begin
if (!rst_n) begin
<= 0;
div_clk <= 0;
clk_count end
else begin
if (sync) begin
<= 0;
div_clk <= N;
clk_count end
else if (clk_count == N) begin
<= ~div_clk;
div_clk <= 0;
clk_count end
else begin
<= clk_count + 1;
clk_count end
end
end
endmodule // clock_divider
The above clock divider relies on a sync
signal that must be generate from the uart_rx
module. Here is an example process for generating the sync
signal:
//-----------------------------------------
// sync process
//-----------------------------------------
reg rx_d;
reg rx_negedge;
always @(posedge clk, negedge rst_n) begin
if (!rst_n) begin
<= 0;
sync <= 1;
rx_d <= 0;
rx_negedge end
else begin
<= rx;
rx_d <= rx_d & ~rx;
rx_negedge
if ((state == WAIT) && rx_negedge)
<= 1;
sync else
<= 0;
sync
end
end
//----------------------------------------
The same clock divider can be used in the UART_TX module, without the sync
signal, like this:
(.N(N)) CD (clk, rst_n, 1'b0, uclk); // sync disable for TX module clock_divider #
For simulation purposes, it is not always necessary to simulate 9600 baud. We can speed-up the UART interface using a top-level parameter, like this:
module top #(
parameter N=5208
)
(
input clk,
input rst,
input start_btn,
input ack_btn,
input [7:0] sw,
output reg [7:0] led
);
Within the top
module, the parameter must be passed down to the uart_tx
and uart_rx
modules:
(.N(N)) TX1
uart_tx #(
(clk),
.clk(rst_n),
.rst_n(start),
.start(sw),
.d_out(txrx),
.tx(done)
.done);
(.N(N)) RX1
uart_rx #(
(clk),
.clk(rst_n),
.rst_n(d),
.d_in(txrx),
.rx(ready),
.ready(ack)
.ack);
Within the TX and RX modules, the parameter N
must be similarly declared, and must be passed along to the clock divider(s) used in those modules. For the RX module:
module uart_rx #(
parameter N=5208
)
(
input clk,
input rst_n,
output reg [7:0] d_in,
input rx,
output reg ready,
input ack
);
reg sync; // Sync signal for clock divider
wire uclk; // UART clock (clock divider output)
(.N(N)) CD (clk, rst_n, sync, uclk); clock_divider #
To transmit data at 9600 baud, the default value is N=5208
. But for simulation, we could set it to something smaller like 10. The testbench instantiation can indicate the altered parameter value:
(.N(10)) DUT (
top #(clk),
.clk(rst),
.rst(start_btn),
.start_btn(ack_btn),
.ack_btn(sw),
.sw(led)
.led);
The testbench should:
top
modulebutton_pusher
module with parameter Nbtn=2
.start
and the other button to ack
.Here is an example showing how to use the button_pusher
:
wire start_btn;
wire ack_btn;
wire [1:0] btn;
(.Nbtn(2)) BTNPUSH
button_pusher #(.clk(clk), .btn(btn));
assign start_btn = btn[0];
assign ack_btn = btn[1];
To detect when the LEDs change, we can use a delay register like this:
reg [7:0] led_D;
reg [7:0] sw_D;
always @(posedge clk) begin
<= 0;
rst
// Detect changes in input/output signals
<= led;
led_D <= sw;
sw_D
if (led_D != led) begin
<= $random();
sw $display("========= LED Change ===========\n %b\n====================",led);
end
if (sw_D != sw) begin
$display("========= Switch Change ========\n %b\n===================",sw);
end
end
In this example, a delay register is also used for sw
to report the new switch values. With these elements combined, you should see an output from test_top
like this:
btn 0 ON
(bounce)
btn 0 ON
(bounce)
btn 0 ON
++TX: Starting 24h
-- RX: start bit detected
>0 >0 >1 >0 >0 >1 >0 >0 -- RX: checking for STOP bit
-- RX: data 24h ready, waiting for ack
++TX: Completed.
btn 0 OFF
(bounce)
(bounce)
(bounce)
(bounce)
(bounce)
(bounce)
(bounce)
(bounce)
btn 0 ON
(bounce)
btn 0 ON
(bounce)
btn 0 ON
(bounce)
btn 0 ON
(bounce)
btn 0 ON
btn 0 OFF
btn 0 ON
(bounce)
btn 0 ON
btn 0 OFF
btn 1 ON
(bounce)
btn 1 ON
========= LED Change ===========
00100100
====================
========= Switch Change ========
11110010
===================
--RX: completed with 24h
btn 1 OFF
Set the testbench stopping condition to large enough value to see several start/ack events, so that the function is verified.