ArtyA7: UART

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.