FPGA Simple UART

Eric Bainville - Apr 2013

Reception

The reception process is a finite state machine (FSM). After several half-working attempts, I finally adopted the design suggested in Pong P. Chu's book FPGA Prototyping by VHDL Examples: Xilinx Spartan-3 Version. Following this design, the finite state machine is implemented using three processes attached to the same clock. The first process (reg_process) updates the current state from the previously computed new state. The second process (rx_process) takes the current state, the inputs, and prepares the new state. The final process (rx_output) updates the outputs from the current state.

To make the code more legible, I put together all state signals into a record type rx_state_t. The state contains the following fields:

Synchronization with the emitter clock is done when we leave the idle state: counter starts at 0, and is ideally back to 0 at the beginning of each bit. We assume this will remain valid for the entire frame, and will sample the rx input signal each time the counter reaches 8, supposedly in the middle of each bit.

entity basic_uart is
  port (
    -- Client interface
    rx_data: out std_logic_vector(7 downto 0); -- received byte
    rx_enable: out std_logic;                  -- validates received byte (1 system clock spike)
    -- Physical interface
    rx: in std_logic;
  );
end basic_uart;

architecture Behavioral of basic_uart is

  type fsm_state_t is (idle, active); -- common to both RX and TX FSM
  type rx_state_t is
  record
    fsm_state: fsm_state_t;                -- FSM state
    counter: std_logic_vector(3 downto 0); -- tick count
    bits: std_logic_vector(7 downto 0);    -- last 8 received bits
    nbits: std_logic_vector(3 downto 0);   -- number of received bits (includes start bit)
    enable: std_logic;                     -- signal we received a new byte
  end record;
  
  signal rx_state,rx_state_next: rx_state_t;
  
begin

  -- RX state registers update at each CLK, and RESET
  reg_process: process (clk,reset) is
  begin
    if reset = '1' then
      rx_state.fsm_state <= idle;
      rx_state.bits <= (others => '0');
      rx_state.nbits <= (others => '0');
      rx_state.enable <= '0';
    elsif rising_edge(clk) then
      rx_state <= rx_state_next;
    end if;
  end process;
  
  -- RX FSM: updates rx_state_next from rx_state and inputs.
  rx_process: process (rx_state,sample,rx) is
  begin
    case rx_state.fsm_state is
    
    when idle =>
      rx_state_next.counter <= (others => '0');
      rx_state_next.bits <= (others => '0');
      rx_state_next.nbits <= (others => '0');
      rx_state_next.enable <= '0';
      if rx = '0' then
        -- start a new byte
        rx_state_next.fsm_state <= active;
      else
        -- keep idle
        rx_state_next.fsm_state <= idle;
      end if;
      
    when active =>
      rx_state_next <= rx_state;
      if sample = '1' then
        if rx_state.counter = 8 then
          -- sample next RX bit (at the middle of the counter cycle)
          if rx_state.nbits = 9 then
            rx_state_next.fsm_state <= idle; -- back to idle state to wait for next start bit
            rx_state_next.enable <= rx; -- OK if stop bit is '1'
          else
            rx_state_next.bits <= rx & rx_state.bits(7 downto 1); -- shift new bit in bits
            rx_state_next.nbits <= rx_state.nbits + 1;
          end if;
        end if;
        rx_state_next.counter <= rx_state.counter + 1;
      end if;
      
    end case;
  end process;
  
  -- RX output
  rx_output: process (rx_state) is
  begin
    rx_enable <= rx_state.enable;
    rx_data <= rx_state.bits;
  end process;
  
end Behavioral;

The Transmission finite state machine is presented in the next page.