-- UART TRANSMITTER

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. tx_start is command to start transmitting, baud_in 
-- is baud clock input, din is 8-bit input to be transmitted, tx_done_tick indicates completion of 
-- transmission, tx is output wire of UART.
entity UART_TX is
   generic(
      DBIT: integer := 8;
      STOP_BITS_TICKS: integer := 16
   );
   port(
      clk, reset: in std_logic;
      tx_start: in std_logic;
      baud_in: in std_logic;
      din: in std_logic_vector(7 downto 0);
      tx_done_tick: out std_logic;
      tx: out std_logic
   );
end UART_TX;

architecture beh of UART_TX 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);
   signal tx_reg, tx_next: std_logic;
   begin

-- FFs -- baud_cnt_reg tracks baud ticks for oversampling, bits_cnt_reg tracks number of bits transmitted,
-- byte_reg holds the bits of the outgoing 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');
         tx_reg <= '1';
      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;
         tx_reg <= tx_next;
      end if;
   end process;

-- next state logic
   process (state_reg, baud_cnt_reg, bits_cnt_reg, byte_reg, baud_in, tx_reg, tx_start, din)
      begin

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

      case state_reg is
         when idle =>

-- hold tx wire high when 'not' in transmission mode
            tx_next <= '1';

-- if external signal indicates 'start transmitting', then initialize counter
-- baud_cnt_reg and latch 8-bit input into byte_reg -- change state to 'start'
            if (tx_start = '1') then
               state_next <= start;
               baud_cnt_next <= (others => '0');
               byte_next <= din;
            end if;

         when start =>

-- put out 0 on tx for start bit
            tx_next <= '0';

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

-- if the 15th band rate tick is received, end start bit, begin data bits,
-- re-initialize baud counter (baud_cnt_reg), and bit counter (bits_cnt_reg)
               if (baud_cnt_reg = 15) then
                  state_next <= data;
                  baud_cnt_next <= (others => '0');
                  bits_cnt_next <= (others => '0');

-- keep counting baud ticks
               else
                  baud_cnt_next <= baud_cnt_reg + 1;
               end if;
            end if;

         when data =>

-- put low order bit of data on tx
            tx_next <= byte_reg(0);

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

-- if 15, then move to next bit, re-initialize counter and shift right eliminating 
-- low order bit just transmitted. Bits sent LSB to MSB
               if (baud_cnt_reg = 15) then
                  baud_cnt_next <= (others => '0');
                  byte_next <= '0' & byte_reg(7 downto 1);

-- check if there are any more bits -- if so, we're done, goto 'stop' state
                  if (bits_cnt_reg = (DBIT - 1)) then
                     state_next <= stop;

-- else increment bit counter
                  else
                     bits_cnt_next <= bits_cnt_reg + 1;
                  end if;

-- keep counting baud ticks
               else
                  baud_cnt_next <= baud_cnt_reg + 1;
               end if;
            end if;

         when stop =>

-- put out stop bit ('1' on tx_reg)
            tx_next <= '1';

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

-- if we've counted enough baud ticks for the stop bits indicated above, then
-- pulse 'tx_done_tick' and return to idle
               if (baud_cnt_reg = (STOP_BITS_TICKS-1)) then
                  state_next <= idle;
                  tx_done_tick <= '1';

-- else keep counting baud ticks
               else
                  baud_cnt_next <= baud_cnt_reg + 1;
               end if;
            end if;
      end case;
   end process;

-- output of FF tx_reg drives tx wire of UART port 
   tx <= tx_reg; 

end beh;

