-- UART RECEIVER

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

-- DBIT is the number of data bits. STOP_BITS_TICKS is the number of stop bit * 16
-- Oversampling is set to 16 times the baud rate. rx is the incoming signal 
-- wire, baud_in is '1' when..., rx_done_tick indicates that a byte has been
-- received and dout is that byte
entity UART_RX is
   generic (
      DBIT: integer := 8;
      STOP_BITS_TICKS: integer := 16
      );
   port (
      clk, reset: in std_logic;
      rx: in std_logic;
      baud_in: in std_logic;
      rx_done_tick: out std_logic;
      dout: out std_logic_vector(7 downto 0)
   );
end UART_RX;

architecture beh of UART_RX is
   type state_type is (idle, start, data, stop);
   signal state_reg, state_next: state_type;

-- Oversampling is 16 baud ticks so 4 bits will do, number of bits is 8 so 3 bits will do
   signal baud_cnt_reg, baud_cnt_next: unsigned(3 downto 0);
   signal bits_cnt_reg, bits_cnt_next: unsigned(2 downto 0);
   signal byte_reg, byte_next: std_logic_vector(7 downto 0);
   begin

-- FFs -- baud_cnt_reg tracks baud ticks for oversampling, bits_cnt_reg tracks number of bits received,
-- byte_reg holds the bits of the incoming byte
   process(clk, reset) 
      begin
      if (reset = '1') then
         state_reg <= idle;
         baud_cnt_reg <= (others => '0');
         bits_cnt_reg <= (others => '0');
         byte_reg <= (others => '0');
      elsif (clk'event and clk='1') then
         state_reg <= state_next;
         baud_cnt_reg <= baud_cnt_next; 
         bits_cnt_reg <= bits_cnt_next; 
         byte_reg <= byte_next;
      end if;
   end process;

-- next state logic
   process (state_reg, baud_cnt_reg, bits_cnt_reg, byte_reg, baud_in, rx)
      begin

-- default assignments for FFs is to save the current values, for state machine, it's the 
-- current state, rx_done_tick is '0' until an entire byte is received
      state_next <= state_reg;
      baud_cnt_next <= baud_cnt_reg;
      bits_cnt_next <= bits_cnt_reg;
      byte_next <= byte_reg;
      rx_done_tick <= '0';

      case state_reg is

-- stay in idle until the input signal becomes 0 (start bit) -- note that there is NO
-- check on the baud_in signal here. Given that the sender runs asynchronously with
-- the receiver, we could enter the start state at any arbitrary point in the receiver's
-- baud cycle. This is not a big deal because the transmitter will hold its values for
-- 15 baud cycles, and start moves on after 7, so we are guaranteed to be near the middle
         when idle =>
            if (rx = '0') then
               state_next <= start;
               baud_cnt_next <= (others => '0');
            end if;

-- Oversampling is 16 bauds per bit of data -- stay here until we reach the
-- middle of the start bit -- if baud_cnt_reg is 7, next clock it will be 8, and that's
-- the middle
         when start =>

-- baud_in is generated by the baud generator -- it is one CLOCK cycle wide and only 
-- occurs after a (large) number of clock cycles
            if (baud_in = '1') then

-- wait until middle of start bit to move to data state
               if (baud_cnt_reg = 7) then

-- transfer to data state and reset counters, baud_cnt counts clocks (up to 16) while
-- bits_cnt counts the bits received (upto 8) -- init them to 0 before we move to
-- data state
                  state_next <= data;
                  baud_cnt_next <= (others => '0');
                  bits_cnt_next <= (others => '0');
               else
                  baud_cnt_next <= baud_cnt_reg + 1;
               end if;
            end if;

         when data =>

-- Wait for baud tick 
            if (baud_in = '1') then

-- Wait until the middle of the bit
               if (baud_cnt_reg = 15) then
                  baud_cnt_next <= (others => '0');

-- left concatenate the new bit, assuming the bits are sent LSB to MSB
                  byte_next <= rx & byte_reg(7 downto 1);

-- If this is the last bit, then move to 'stop state' and read stop bit
                  if (bits_cnt_reg = (DBIT - 1)) then
                     state_next <= stop;

-- Add 1 each time we read a bit
                  else
                     bits_cnt_next <= bits_cnt_reg + 1;
                  end if;

-- Count 1 for each baud tick 
               else
                  baud_cnt_next <= baud_cnt_reg + 1;
               end if;
            end if;

-- Process the stop bit
      when stop =>
         if (baud_in = '1') then

-- Depends on the number of stop bits set as parameter -- typically it is 1
            if (baud_cnt_reg = (STOP_BITS_TICKS - 1)) then
               state_next <= idle;

-- Generate a 1 clock wide pulse to indicate byte is available after the stop bit is
-- received (which is not part of the data)
               rx_done_tick <= '1';
            else
               baud_cnt_next <= baud_cnt_reg + 1;
            end if;
         end if;
      end case;
   end process;

-- Put the byte read on the output bus
   dout <= byte_reg; 
end beh;
