I’ve got an Arty A7 board. It is an FPGA development board. The nice thing about it is the tools to develop FPGA code with it are free and the FPGA is decently sized. One of my first projects was getting the UART to receive data and then echo it back over the transmit channel.
I used some example code for the UART TX to model the UART RX code. I’m sure there was an example out there, but I thought it would be good practice to do it myself.
The one thing that caused me a big headache was that the RX data line appears to be inverted. I finally figured it out by setting one of the LEDs to light up if the RX line went low and it did which brought to my attention the fact it might be. It also idled at a high state which was further evidence.
I also had to review stop and start bits. It was interesting because the serial protocol is designed to be operated from a single wire. You can detect and decode each byte using a single wire and transmit using another.
The FPGA is also fast enough you end up having to build your own timing loops to control when each bit is sampled. Through the whole process you basically build a bit queue for each byte where each sample is appended to the remaining bits.
The only real trouble was with the amount of time it takes the go from VHDL to bitstream. The bitstream is the file used to program the FPGA. The total compilation time was quite long which made incremental debugging a challenge. I think I had to wait a few minutes for each build.
My debugging method involved using the LED lights to signal specific changes in the FPGA program. This enabled me to see what areas of the code were active, when, and it allowed me to get solid output back out. I could have used the serial TX since I already had working code but frankly flipping a signal for an LED was a lot easier.
I’m quite satisfied with the Arty A7 board. It is a real nice tool to have on hand. The ability to work with the physical layer will be important for a lot of projects.
The following code I did not write. I did use it as the basis for the UART_RX_CTRL.vhd
.
----------------------------------------------------------------------------
-- UART_TX_CTRL.vhd -- UART Data Transfer Component
----------------------------------------------------------------------------
-- Author: Sam Bobrowicz
-- Copyright 2011 Digilent, Inc.
----------------------------------------------------------------------------
--
----------------------------------------------------------------------------
-- This component may be used to transfer data over a UART device. It will
-- serialize a byte of data and transmit it over a TXD line. The serialized
-- data has the following characteristics:
-- *9600 Baud Rate
-- *8 data bits, LSB first
-- *1 stop bit
-- *no parity
--
-- Port Descriptions:
--
-- SEND - Used to trigger a send operation. The upper layer logic should
-- set this signal high for a single clock cycle to trigger a
-- send. When this signal is set high DATA must be valid . Should
-- not be asserted unless READY is high.
-- DATA - The parallel data to be sent. Must be valid the clock cycle
-- that SEND has gone high.
-- CLK - A 100 MHz clock is expected
-- READY - This signal goes low once a send operation has begun and
-- remains low until it has completed and the module is ready to
-- send another byte.
-- UART_TX - This signal should be routed to the appropriate TX pin of the
-- external UART device.
--
----------------------------------------------------------------------------
--
----------------------------------------------------------------------------
-- Revision History:
-- 08/08/2011(SamB): Created using Xilinx Tools 13.2
----------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.std_logic_unsigned.all;
entity UART_TX_CTRL is
Port ( SEND : in STD_LOGIC;
DATA : in STD_LOGIC_VECTOR (7 downto 0);
CLK : in STD_LOGIC;
READY : out STD_LOGIC;
UART_TX : out STD_LOGIC);
end UART_TX_CTRL;
architecture Behavioral of UART_TX_CTRL is
type TX_STATE_TYPE is (RDY, LOAD_BIT, SEND_BIT);
constant BIT_TMR_MAX : std_logic_vector(13 downto 0) := "10100010110000"; --10416 = (round(100MHz / 9600)) - 1
constant BIT_INDEX_MAX : natural := 10;
--Counter that keeps track of the number of clock cycles the current bit has been held stable over the
--UART TX line. It is used to signal when the ne
signal bitTmr : std_logic_vector(13 downto 0) := (others => '0');
--combinatorial logic that goes high when bitTmr has counted to the proper value to ensure
--a 9600 baud rate
signal bitDone : std_logic;
--Contains the index of the next bit in txData that needs to be transferred
signal bitIndex : natural;
--a register that holds the current data being sent over the UART TX line
signal txBit : std_logic := '1';
--A register that contains the whole data packet to be sent, including start and stop bits.
signal txData : std_logic_vector(9 downto 0);
signal txState : TX_STATE_TYPE := RDY;
begin
--Next state logic
next_txState_process : process (CLK)
begin
if (rising_edge(CLK)) then
case txState is
when RDY =>
if (SEND = '1') then
txState <= LOAD_BIT;
end if;
when LOAD_BIT =>
txState <= SEND_BIT;
when SEND_BIT =>
if (bitDone = '1') then
if (bitIndex = BIT_INDEX_MAX) then
txState <= RDY;
else
txState <= LOAD_BIT;
end if;
end if;
when others=> --should never be reached
txState <= RDY;
end case;
end if;
end process;
bit_timing_process : process (CLK)
begin
if (rising_edge(CLK)) then
if (txState = RDY) then
bitTmr <= (others => '0');
else
if (bitDone = '1') then
bitTmr <= (others => '0');
else
bitTmr <= bitTmr + 1;
end if;
end if;
end if;
end process;
bitDone <= '1' when (bitTmr = BIT_TMR_MAX) else
'0';
bit_counting_process : process (CLK)
begin
if (rising_edge(CLK)) then
if (txState = RDY) then
bitIndex <= 0;
elsif (txState = LOAD_BIT) then
bitIndex <= bitIndex + 1;
end if;
end if;
end process;
tx_data_latch_process : process (CLK)
begin
if (rising_edge(CLK)) then
if (SEND = '1') then
txData <= '1' & DATA & '0';
end if;
end if;
end process;
tx_bit_process : process (CLK)
begin
if (rising_edge(CLK)) then
if (txState = RDY) then
txBit <= '1';
elsif (txState = LOAD_BIT) then
txBit <= txData(bitIndex);
end if;
end if;
end process;
UART_TX <= txBit;
READY <= '1' when (txState = RDY) else
'0';
end Behavioral;
Here is the RX control module.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.std_logic_unsigned.all;
entity UART_RX_CTRL is
Port ( DATA : out STD_LOGIC_VECTOR (7 downto 0);
CLK : in STD_LOGIC;
READY: out STD_LOGIC;
UART_RX: in STD_LOGIC;
DEBUG0: out STD_LOGIC;
DEBUG1: out STD_LOGIC);
end UART_RX_CTRL;
architecture Behavioral of UART_RX_CTRL is
constant TMR_MAX: std_logic_vector(13 downto 0) := "10100010110000";
type RX_STATE_TYPE is (SWARM, SWAIT, SREAD, SNEXT);
signal rxState: RX_STATE_TYPE := SWARM;
signal bitIndex: natural;
signal bufData: STD_LOGIC_VECTOR (7 downto 0) := (others => '0');
signal rxCounter: std_logic_vector(13 downto 0) := (others => '0');
begin
rxState_process: process (CLK)
begin
if (rising_edge(CLK)) then
case rxState is
when SWARM =>
DEBUG0 <= '0';
DEBUG1 <= '0';
if (rxCounter = TMR_MAX) then
rxState <= SWAIT;
end if;
when SWAIT =>
READY <= '0';
if (UART_RX = '0') then
rxState <= SREAD;
bitIndex <= 0;
else
DEBUG0 <= '1';
end if;
when SREAD =>
if (bitIndex = 10) then
rxState <= SWAIT;
READY <= '1';
DATA <= bufData;
end if;
if (rxCounter = TMR_MAX) then
rxState <= SNEXT;
end if;
when SNEXT =>
bufData(bitIndex) <= UART_RX;
rxState <= SREAD;
bitIndex <= bitIndex + 1;
end case;
end if;
end process;
timer_process: process (CLK)
begin
if (rising_edge(CLK)) then
case rxState is
when SWAIT =>
rxCounter <= (others => '0');
when SWARM =>
if (rxCounter = TMR_MAX) then
rxCounter <= (others => '0');
else
rxCounter <= rxCounter + 1;
end if;
when SREAD =>
if (rxCounter = TMR_MAX) then
rxCounter <= (others => '0');
else
rxCounter <= rxCounter + 1;
end if;
when SNEXT =>
rxCounter <= (others => '0');
end case;
end if;
end process;
end Behavioral;
Here is the top-level module. I also did not write the top-level module. I did strip it down to the bare components for a UART echo program and then included my own RX control component.
-- Arty A7 UART Echo
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
--The IEEE.std_logic_unsigned contains definitions that allow
--std_logic_vector types to be used with the + operator to instantiate a
--counter.
use IEEE.std_logic_unsigned.all;
entity GPIO_demo is
Port ( SW : in STD_LOGIC_VECTOR (3 downto 0);
BTN : in STD_LOGIC_VECTOR (3 downto 0);
CLK : in STD_LOGIC;
LED : out STD_LOGIC_VECTOR (3 downto 0);
UART_TXD : out STD_LOGIC;
UART_RXD : in STD_LOGIC;
RGB0_Red : out STD_LOGIC;
RGB0_Green : out STD_LOGIC;
RGB0_Blue : out STD_LOGIC;
RGB1_Red : out STD_LOGIC;
RGB1_Green : out STD_LOGIC;
RGB1_Blue : out STD_LOGIC;
RGB2_Red : out STD_LOGIC;
RGB2_Green : out STD_LOGIC;
RGB2_Blue : out STD_LOGIC;
RGB3_Red : out STD_LOGIC;
RGB3_Green : out STD_LOGIC;
RGB3_Blue : out STD_LOGIC
);
end GPIO_demo;
architecture Behavioral of GPIO_demo is
component UART_RX_CTRL
Port(
DATA: out STD_LOGIC_VECTOR (7 downto 0);
CLK: in STD_LOGIC;
READY: out STD_LOGIC;
UART_RX: in STD_LOGIC;
DEBUG0: out STD_LOGIC;
DEBUG1: out STD_LOGIC
);
end component;
component UART_TX_CTRL
Port(
SEND : in std_logic;
DATA : in std_logic_vector(7 downto 0);
CLK : in std_logic;
READY : out std_logic;
UART_TX : out std_logic
);
end component;
--UART_TX_CTRL control signals
signal uartRdy : std_logic;
signal uartSend : std_logic := '0';
signal uartData : std_logic_vector (7 downto 0):= "00000000";
signal uartTX : std_logic;
signal uartRxData: STD_LOGIC_VECTOR (7 downto 0):= "00000000";
signal uartRxRdy: STD_LOGIC := '0';
signal debug: STD_LOGIC := '0';
begin
Inst_UART_RX_CTRL: UART_RX_CTRL port map(
DATA => uartRxData,
CLK => CLK,
READY => uartRxRdy,
UART_RX => UART_RXD,
DEBUG0 => RGB0_Red,
DEBUG1 => RGB1_Red
);
--Component used to send a byte of data over a UART line.
Inst_UART_TX_CTRL: UART_TX_CTRL port map(
SEND => uartSend,
DATA => uartData,
CLK => CLK,
READY => uartRdy,
UART_TX => uartTX
);
echo_process: process (CLK)
begin
if (rising_edge(CLK)) then
if (uartRdy = '1') then
debug <= '0';
end if;
if (uartRxRdy = '1') then
if (debug = '0') then
uartData <= uartRxData;
uartSend <= '1';
debug <= '1';
else
uartSend <= '0';
end if;
else
uartSend <= '0';
end if;
end if;
end process;
UART_TXD <= uartTX;
end Behavioral;
I can’t remember where I found the example code. I’m sure there are plenty out there on the Internet. I thought it was pretty interesting how it all worked.