In-Depth: UART RX Demo with Human Interface

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.

Schematic for top module.

Here are some of the main features:

Testbench Design

The button_pusher Test Module

Since 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.

Getting More Information from the Modules

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
           bit_index <= 0;  // start at LSB
           tx        <= 0;  // start bit
           state     <= SEND;
           $display("  ++TX: Starting %hh",d_out);         
        end
        else begin
           tx   <= 1; // idle signal
           done <= 0; // not done, ready for data      
        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
           bit_index <= 0;  // start at LSB
           state     <= RECV;
           $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
        d_in[bit_index] <= rx;
        $write(">%b ",rx);
        
        if (bit_index == 7) begin          
           state <= STOP;
           $display("  -- RX: checking for STOP bit");
           
        end
        else begin
           bit_index <= bit_index + 1;         
        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
           done <= 1; // Signal that the transmission is complete
           tx   <= 1; // Stop bit          
        end
        else begin
           done <= 0;
           state <= WAIT;
           $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);
           ready <= 1; // Signal that the transmission is complete
        end
        else if (ack && ready) begin
           $display("  --RX: completed with %hh", d_in);           
           ready <= 0;
           state <= WAIT;          
        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.

Clock Divider sync Process

In 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
      div_clk = 0;
      clk_count = 0;
   end

   always @(posedge clk, negedge rst_n) begin
      if (!rst_n) begin
     div_clk   <= 0;
     clk_count <= 0;     
      end
      else begin
     if (sync) begin
        div_clk   <= 0;
        clk_count <= N;
     end
     else if (clk_count == N) begin
        div_clk   <= ~div_clk;
        clk_count <= 0;     
     end
     else begin
        clk_count <= clk_count + 1;     
     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
     sync       <= 0;
     rx_d       <= 1;
     rx_negedge <= 0;    
      end
      else begin
     rx_d       <= rx;
     rx_negedge <= rx_d & ~rx;
     
     if ((state == WAIT) && rx_negedge) 
       sync <= 1;       
     else
       sync <= 0;
     
      end
   end
   //----------------------------------------

The same clock divider can be used in the UART_TX module, without the sync signal, like this:

   clock_divider #(.N(N)) CD (clk, rst_n, 1'b0, uclk); // sync disable for TX module

Adjust Clock Divider Period

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:

   uart_tx #(.N(N)) TX1
     (
      .clk(clk),
      .rst_n(rst_n),
      .start(start),
      .d_out(sw),
      .tx(txrx),
      .done(done)
      );

   uart_rx #(.N(N)) RX1
     (
      .clk(clk),
      .rst_n(rst_n),
      .d_in(d),
      .rx(txrx),
      .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)

   clock_divider #(.N(N)) CD (clk, rst_n, sync, uclk);

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:

   top #(.N(10)) DUT (
        .clk(clk),
        .rst(rst),
        .start_btn(start_btn),
        .ack_btn(ack_btn),
        .sw(sw),
        .led(led)
     );

Testbench Organization

The testbench should:

Here is an example showing how to use the button_pusher:

   wire       start_btn;
   wire       ack_btn;

   wire [1:0] btn;
   
   button_pusher #(.Nbtn(2)) BTNPUSH
     (.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
      rst <= 0;

      // Detect changes in input/output signals
      led_D <= led;
      sw_D  <= sw;
      
      if (led_D != led) begin
     sw <= $random(); 
     $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.