Go Board – UART (Universal Asynchronous Receiver/Transmitter)
Learn to Communicate with the Computer (Part 2 – Transmitter)
In the previous project, you learned how to receive data from the computer and display the received byte on the two digit 7-Segment display. You saw that the data being displayed was showing the ASCII hex characters of the keyboard presses being transmitted from the Computer to the Go Board. In this project, we are going to implement the UART Transmitter, so that the computer can receive data from the Go Board.
The picture above shows the path that we completed for Part 1, and the path that we are now going to be designing in Part 2. Part 2 should be much easier, because now you know how a UART actually works. This project is going to take parallel data (as a byte) and serialize it. The serialized data gets sent out one bit at a time to the computer. The computer will then display the received byte on the terminal emulator screen.
Should you prefer to follow along with a YouTube video, well you can do that too! Please subscribe to my channel.
Project Description
Create a loopback of data sent by the computer. The Go Board should receive data from the computer, display it on the two digit 7-Segment display, and then transmit the received data back to the computer. The UART receiver should operate at 115200 baud, 8 data bits, no parity, 1 stop bit, no flow control.
As a refresher, let’s look at how this UART data is formatted for both the receiver and the transmitter:
The project description states that we will be using loopback. Loopback is the routing of the data stream back to its original source without modification. In this case, the transmitter will echo the received characters back to the computer. This allows the keys that you press to be displayed on the terminal screen. If you were paying close attention during the previous project, you might have noticed that the keys you were pressing were not being displayed on the screen. That’s because the Go Board was not echoing them back to the Computer. This project is going to fix that.
The transmitter uses a state machine to progress through the data stream. First the start bit, then the data bits, then the stop bit. You need to keep track of where you are both in the individual bit, and where you are in the data stream as a whole. The state machine will help with that.
I recommend that you give this project a try on your own. You have the skills from the UART Receiver, so this one shouldn’t be too bad.
Note that this UART Transmitter is designed to be portable. Feel free to use it in any design you wish! Please give credit to www.nandland.com if you choose to use it! For the EDA Playground environment, I used the UART_RX module from the previous project and created the Transmitter from scratch. The Testbench instantiates both the TX and RX and simply performs a loopback of the received data into the transmit module. Take a look:
UART Transmitter and Testbench in VHDL
UART Transmitter and Testbench in Verilog
This testbench environment performs very similarly to the previous project. Run the simulation and look at the waveforms. Once you’re comfortable with your UART Transmitter design, you can instantiate it into the top level of your project. The code for both VHDL and Verilog is shown below.
VHDL Code – UART_Loopback_Top.vhd:
library ieee; use ieee.std_logic_1164.all; entity UART_Loopback_Top is port ( -- Main Clock (25 MHz) i_Clk : in std_logic; -- UART Data i_UART_RX : in std_logic; o_UART_TX : out std_logic; -- Segment1 is upper digit, Segment2 is lower digit o_Segment1_A : out std_logic; o_Segment1_B : out std_logic; o_Segment1_C : out std_logic; o_Segment1_D : out std_logic; o_Segment1_E : out std_logic; o_Segment1_F : out std_logic; o_Segment1_G : out std_logic; o_Segment2_A : out std_logic; o_Segment2_B : out std_logic; o_Segment2_C : out std_logic; o_Segment2_D : out std_logic; o_Segment2_E : out std_logic; o_Segment2_F : out std_logic; o_Segment2_G : out std_logic ); end entity UART_Loopback_Top; architecture RTL of UART_Loopback_Top is signal w_RX_DV : std_logic; signal w_RX_Byte : std_logic_vector(7 downto 0); signal w_TX_Active : std_logic; signal w_TX_Serial : std_logic; signal w_Segment1_A, w_Segment2_A : std_logic; signal w_Segment1_B, w_Segment2_B : std_logic; signal w_Segment1_C, w_Segment2_C : std_logic; signal w_Segment1_D, w_Segment2_D : std_logic; signal w_Segment1_E, w_Segment2_E : std_logic; signal w_Segment1_F, w_Segment2_F : std_logic; signal w_Segment1_G, w_Segment2_G : std_logic; begin UART_RX_Inst : entity work.UART_RX generic map ( g_CLKS_PER_BIT => 217) -- 25,000,000 / 115,200 port map ( i_Clk => i_Clk, i_RX_Serial => i_UART_RX, o_RX_DV => w_RX_DV, o_RX_Byte => w_RX_Byte); -- Creates a simple loopback to test TX and RX UART_TX_Inst : entity work.UART_TX generic map ( g_CLKS_PER_BIT => 217) -- 25,000,000 / 115,200 = 217 port map ( i_Clk => i_Clk, i_TX_DV => w_RX_DV, i_TX_Byte => w_RX_Byte, o_TX_Active => w_TX_Active, o_TX_Serial => w_TX_Serial, o_TX_Done => open ); -- Drive UART line high when transmitter is not active o_UART_TX <= w_TX_Serial when w_TX_Active = '1' else '1'; -- Binary to 7-Segment Converter for Upper Digit SevenSeg1_Inst : entity work.Binary_To_7Segment port map ( i_Clk => i_Clk, i_Binary_Num => w_RX_Byte(7 downto 4), o_Segment_A => w_Segment1_A, o_Segment_B => w_Segment1_B, o_Segment_C => w_Segment1_C, o_Segment_D => w_Segment1_D, o_Segment_E => w_Segment1_E, o_Segment_F => w_Segment1_F, o_Segment_G => w_Segment1_G ); o_Segment1_A <= not w_Segment1_A; o_Segment1_B <= not w_Segment1_B; o_Segment1_C <= not w_Segment1_C; o_Segment1_D <= not w_Segment1_D; o_Segment1_E <= not w_Segment1_E; o_Segment1_F <= not w_Segment1_F; o_Segment1_G <= not w_Segment1_G; -- Binary to 7-Segment Converter for Lower Digit SevenSeg2_Inst : entity work.Binary_To_7Segment port map ( i_Clk => i_Clk, i_Binary_Num => w_RX_Byte(3 downto 0), o_Segment_A => w_Segment2_A, o_Segment_B => w_Segment2_B, o_Segment_C => w_Segment2_C, o_Segment_D => w_Segment2_D, o_Segment_E => w_Segment2_E, o_Segment_F => w_Segment2_F, o_Segment_G => w_Segment2_G ); o_Segment2_A <= not w_Segment2_A; o_Segment2_B <= not w_Segment2_B; o_Segment2_C <= not w_Segment2_C; o_Segment2_D <= not w_Segment2_D; o_Segment2_E <= not w_Segment2_E; o_Segment2_F <= not w_Segment2_F; o_Segment2_G <= not w_Segment2_G; end architecture RTL;
Verilog Code – UART_Loopback_Top.v:
module UART_Loopback_Top (input i_Clk, // Main Clock input i_UART_RX, // UART RX Data output o_UART_TX, // UART TX Data // Segment1 is upper digit, Segment2 is lower digit output o_Segment1_A, output o_Segment1_B, output o_Segment1_C, output o_Segment1_D, output o_Segment1_E, output o_Segment1_F, output o_Segment1_G, // output o_Segment2_A, output o_Segment2_B, output o_Segment2_C, output o_Segment2_D, output o_Segment2_E, output o_Segment2_F, output o_Segment2_G ); wire w_RX_DV; wire [7:0] w_RX_Byte; wire w_TX_Active, w_TX_Serial; wire w_Segment1_A, w_Segment2_A; wire w_Segment1_B, w_Segment2_B; wire w_Segment1_C, w_Segment2_C; wire w_Segment1_D, w_Segment2_D; wire w_Segment1_E, w_Segment2_E; wire w_Segment1_F, w_Segment2_F; wire w_Segment1_G, w_Segment2_G; // 25,000,000 / 115,200 = 217 UART_RX #(.CLKS_PER_BIT(217)) UART_RX_Inst (.i_Clock(i_Clk), .i_RX_Serial(i_UART_RX), .o_RX_DV(w_RX_DV), .o_RX_Byte(w_RX_Byte)); UART_TX #(.CLKS_PER_BIT(217)) UART_TX_Inst (.i_Clock(i_Clk), .i_TX_DV(w_RX_DV), // Pass RX to TX module for loopback .i_TX_Byte(w_RX_Byte), // Pass RX to TX module for loopback .o_TX_Active(w_TX_Active), .o_TX_Serial(w_TX_Serial), .o_TX_Done()); // Drive UART line high when transmitter is not active assign o_UART_TX = w_TX_Active ? w_TX_Serial : 1'b1; // Binary to 7-Segment Converter for Upper Digit Binary_To_7Segment SevenSeg1_Inst (.i_Clk(i_Clk), .i_Binary_Num(w_RX_Byte[7:4]), .o_Segment_A(w_Segment1_A), .o_Segment_B(w_Segment1_B), .o_Segment_C(w_Segment1_C), .o_Segment_D(w_Segment1_D), .o_Segment_E(w_Segment1_E), .o_Segment_F(w_Segment1_F), .o_Segment_G(w_Segment1_G)); assign o_Segment1_A = ~w_Segment1_A; assign o_Segment1_B = ~w_Segment1_B; assign o_Segment1_C = ~w_Segment1_C; assign o_Segment1_D = ~w_Segment1_D; assign o_Segment1_E = ~w_Segment1_E; assign o_Segment1_F = ~w_Segment1_F; assign o_Segment1_G = ~w_Segment1_G; // Binary to 7-Segment Converter for Lower Digit Binary_To_7Segment SevenSeg2_Inst (.i_Clk(i_Clk), .i_Binary_Num(w_RX_Byte[3:0]), .o_Segment_A(w_Segment2_A), .o_Segment_B(w_Segment2_B), .o_Segment_C(w_Segment2_C), .o_Segment_D(w_Segment2_D), .o_Segment_E(w_Segment2_E), .o_Segment_F(w_Segment2_F), .o_Segment_G(w_Segment2_G)); assign o_Segment2_A = ~w_Segment2_A; assign o_Segment2_B = ~w_Segment2_B; assign o_Segment2_C = ~w_Segment2_C; assign o_Segment2_D = ~w_Segment2_D; assign o_Segment2_E = ~w_Segment2_E; assign o_Segment2_F = ~w_Segment2_F; assign o_Segment2_G = ~w_Segment2_G; endmodule
Now after building the code in iCEcube2, we should take a quick look at the synthesis results. How many LUTs and Registers (Flip-Flops) does your design use? Is there anything you can think of trying to reduce logic utilization? Let’s take a look at the programmed Go Board in action.
Success! Tera Term is relaying data that we send to the Go Board back to the computer. Now that you have the basics down for UARTs, you can probably think of a LOT more projects to do with them. How would you have the Go Board respond to different key presses from the computer? How might you have a menu displayed to the user? There’s lots to play with, so spend some time thinking about how you might be able to do fun projects with the Go Board and UARTs. If you come up with something cool, please post a link to your design in the comments section below!
We are nearing the end of the Go Board tutorials. The last topic to discuss is a culmination of everything we have learned so far. I would like to talk about how VGA works, and show you how to create PONG with the Go Board. So let’s get started!
Hi Russel,
For the UART_TX, I managed to compile the files and programmed the Go board. I was able to display the ASCII code on the 7 segment display. But the Go board did not display any loopback characters. I used both Putty and Tera Term, but the results were the same. No loop back of an characters. Please advise. Thanks
Hi An Le, I think the problem is that when initiating the UART_TX in the UART_Loopback_Top, the code forgot to pass in i_Rst_L to 1, since it’s an active high signal. Once we assign i_Rst_L to 1, the UART_TX should work fine.
Hi An Le, I think the problem is that when initiating the UART_TX in the UART_Loopback_Top, the code forgot to pass in i_Rst_L to 1, since it’s an active high signal. Once we assign i_Rst_L to 1, the UART_TX should work fine.
Hi Elliot, Have you tested the UART_Loopback_Top successfully? If so, Do we need to include the i_Rst_L in UART_TX right after rising_edge(i_Clk)?
Yes the UART_Loopback_Top works for me. The UART_TX module I use is the one shared in the EDAPlayground http://www.edaplayground.com/x/Pgf. We can see the part “if (~i_Rst_L) r_SM_Main <= 3'b000;" which means if i_Rst_L == 0, the state machine will reset. So in the top module we can just wire i_Rst_L to 1'b0 if we don't care about resetting. Here is what it looks like in the UART_Loopback_Top after adding the signal:
UART_TX #(
.CLKS_PER_BIT(217)
) UART_TX_Inst (
.i_Rst_L (1'b1), // No reset
.i_Clock (i_Clk),
.i_TX_DV (w_RX_DV), // Pass RX to TX module for loopback
.i_TX_Byte (w_RX_Byte), // Pass RX to TX module for loopback
.o_TX_Active(w_TX_Active),
.o_TX_Serial(w_TX_Serial),
.o_TX_Done ()
);
Hi Elliot,
Thank you for help. I managed the UART_Loopback test.
Thanks for the Author for the great work.
Hi, Russel,
I saw in the UART_TX.v file that you have at the top:
module UART_TX
( input i_Rst_L,
input i_Clock,
input i_TX_DV,
input [7:0] i_TX_Byte,
output reg o_TX_Active,
output reg o_TX_Serial,
output reg o_TX_Done);
Could you explain the use of the term “output reg” in
output reg o_TX_Active,
output reg o_TX_Serial
I’m confused because you didn’t use “output reg” anywhere in your UART_RX module.
Could someone explain under what conditions the UART_TX function begins and under what conditions the UART_RX function begins? I’m a little confused as to the order in which things take place in this loopback code.
Do UART_TX and UART_RX happen at the same time? If not, which one occurs first?