In this article we will see how to describe UART receiver in VHDL. First of all let's create a project in ISE:
I use XC6SLX9 FPGA chip from Xilinx. You need to chose your chip. Also you can use a different FPGA family, for example, Altera. In any case VHDL code should work for any chip. After the project has been created we need to add a VHDL source file. At the beginning of the file the used libraries need to be defined:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.std_logic_unsigned.all;
use IEEE.NUMERIC_STD.ALL;
Then an interface should be defined:
entity My_UART is
port (
clk: in std_logic; -- ext. 25 MHz osc.
rx: in std_logic; -- UART receiver input
rx_byte: out std_logic_vector (7 downto 0) -- received byte
);
end My_UART;
The entity physical signals are commented above and do not need any explanation. After the entity follows the architecture:
architecture Behavioral of My_UART is
begin
end Behavioral;
Before a begin keyword all signals and constants are defined:
constant clk_freq: integer := 25_000_000; -- Osc. frequency
constant uart_baud: integer := 115200; -- UART baudrate
constant uart_freq: integer := clk_freq / (32 * uart_baud); -- UART system clock
signal uart_divider: integer range 0 to uart_freq; -- UART divider
signal uart_clk: std_logic; -- UART divider
signal cnt: integer range 0 to 15; -- UART clk counter
signal sample: std_logic; -- Flag of begining
UART receiver should support a 115200 kbps baudrate. But receiver should read a data on the input pin 16 times faster than actual UART transmit signal is changing. System clock 25 MHz is too high for this. That is why a simple divider is used:
-- sys_clk divider for generating uart_clk
process (clk)
begin
if (clk = '1' and clk'event) then
uart_divider <= uart_divider + 1;
if (uart_divider = uart_freq) then
uart_clk <= uart_clk xor '1';
uart_divider <= 0;
end if;
end if;
end process;
The main part of UART receiver is shown below. A finite state machine has been implemented in VHDL. When it receives a byte, it outputs it to the output.
process (uart_clk)
variable bitn: integer range 0 to 9 := 0; -- number of received bit
variable data: std_logic_vector (7 downto 0); -- received byte
variable rx_s: std_logic_vector (2 downto 0); -- bit sample variable
variable rx_b: std_logic; -- received bit true value
begin
if (uart_clk = '1' and uart_clk'event) then
if (rx_b = '1' and rx = '0') then -- start condition checking
sample <= '1';
rx_b := rx;
else
rx_b := rx;
end if;
if (cnt = 8) then -- 1st bit sample
rx_s(0) := rx;
end if;
if (cnt = 9) then -- 2nd bit sample
rx_s(1) := rx;
end if;
if (cnt = 10) then -- 3rd bit sample
rx_s(2) := rx;
end if;
if (cnt = 11) then -- if bit was sampled
-- Defining true bit value
rx_b := ((rx_s(0) and rx_s(1)) or (rx_s(1) and rx_s(2)) or ((rx_s(0) and rx_s(2))));
case (bitn) is
when 0 =>
if (rx = '0') then
bitn := 1;
else
bitn := 0;
sample <= '0';
end if;
when 1 => data(0) := rx_b;
bitn := 2;
when 2 => data(1) := rx_b;
bitn := 3;
when 3 => data(2) := rx_b;
bitn := 4;
when 4 => data(3) := rx_b;
bitn := 5;
when 5 => data(4) := rx_b;
bitn := 6;
when 6 => data(5) := rx_b;
bitn := 7;
when 7 => data(6) := rx_b;
bitn := 8;
when 8 => data(7) := rx_b;
bitn := 9;
when 9 => bitn := 0;
sample <= '0';
if (rx = '1') then
rx_byte <= data; -- output received byte into port
end if;
end case;
end if;
end if;
end process;
To demonstrate that the UART receiver works, it is need to generate a bitstream for the FPGA. To do this a constraints file *.ucf should be added to the ISE project:
# PlanAhead Generated physical constraints
NET "rx_byte[1]" LOC = P2;
NET "rx_byte[0]" LOC = P1;
NET "rx_byte[2]" LOC = P5;
NET "rx_byte[3]" LOC = P6;
NET "rx_byte[4]" LOC = P7;
NET "rx_byte[5]" LOC = P8;
NET "rx_byte[6]" LOC = P9;
NET "rx_byte[7]" LOC= P10;
NET "clk" LOC = P85;
NET "rx" LOC = P11;
# PlanAhead Generated IO constraints
NET "rx_byte[0]" IOSTANDARD = LVCMOS33;
NET "rx_byte[1]" IOSTANDARD = LVCMOS33;
NET "rx_byte[2]" IOSTANDARD = LVCMOS33;
NET "rx_byte[3]" IOSTANDARD = LVCMOS33;
NET "rx_byte[4]" IOSTANDARD = LVCMOS33;
NET "rx_byte[5]" IOSTANDARD = LVCMOS33;
NET "rx_byte[6]" IOSTANDARD = LVCMOS33;
NET "rx_byte[7]" IOSTANDARD = LVCMOS33;
NET "clk" IOSTANDARD = LVCMOS33;
NET "rx" IOSTANDARD = LVCMOS33;
After the bit stream generation you will get a following picture:
As a UART transmitter it is possible to use any USB-UART CP2102 converter like this:
My setup looks like this:
The source code can be taken from here. :)