----------------------------------------------------------------------------------
-- Author: Stefan Lohse
----------------------------------------------------------------------------------
library IEEE;
use			IEEE.std_logic_1164.all;

package utilities_pkg is
	type t_frequency is range 0 to natural'high units 
		Hz;												-- primary unit
		kHz = 1000 Hz;						-- secondary unit
		MHz = 1000 kHz;						-- secondary unit
		GHz = 1000 MHz;						-- secondary unit
	end units;
	
	function to_period(b: t_frequency) return time;
	function log2_ceil(a : integer) return natural;
	function isSimulation return boolean;
	
	-- ite = if-then-else
	function ite( cond: boolean; A: integer; B : integer) return integer;
	
	constant SIMULATION : boolean; -- deferred constant declaration
end package;

package body utilities_pkg is
	function to_period(b: t_frequency) return time is
		constant result : real := 1.0/real(t_frequency'pos(B));
	begin
		return result * 1 sec;
	end function;
	
	function log2_ceil(a : integer) return natural is
		variable pow2 : natural; -- equivalent zu := natural'left
	begin
		if a = 0 then
			return 1;
		end if;

		for i in 0 to 31 loop
			pow2 := 2**i;
			if pow2 > a then
				return i;
			end if;
		end loop;		
	end function;

	function isSimulation return boolean is
	begin
		return is_x('X');
	end function;
	
	constant SIMULATION : boolean := isSimulation;
	
	function ite( cond: boolean; A: integer; B : integer) return integer is
	begin
		if cond then
			return A;
		else
			return B;
		end if;
	end function;
end package body;


library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.MATH_REAL.ALL;

use work.utilities_pkg.all;

package uart_pkg is
	type t_baudrate is range 0 to natural'high units -- oder (natural'low to natural'high) oder (1 to 1000000)
		Bd;
		kBd = 1000 Bd;
		MBd = 1000 kBd;
	end units;

	function to_period(b: t_baudrate) return time;
	function timing_to_cycles(period: time; frequency: t_frequency) return integer;
	function ite( cond: boolean; A: t_baudrate; B : t_baudrate) return t_baudrate;	
end package;

package body uart_pkg is
	function to_period(b: t_baudrate) return time is
		constant result : real := 1.0/real(t_baudrate'pos(B));
	begin
		return result * 1 sec;
	end function;

	function timing_to_cycles(period: time; frequency: t_frequency) return integer is
		variable res_real : real;
	begin
		res_real := real(time'pos(period)) / 1.0E12;
		res_real := real(t_frequency'pos(frequency)) * res_real;
	
		return integer(ceil(res_real));
	end function;

	function ite( cond: boolean; A: t_baudrate; B : t_baudrate) return t_baudrate is
	begin
		if cond then
			return A;
		else
			return B;
		end if;
	end function;
end package body;


library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

use work.uart_pkg.ALL;
use work.utilities_pkg.all;

entity uart is
	generic ( 
		uart_oversampling  : positive := 16;
		num_of_databits    : positive := 8
	);
	port ( 
		clk                : in  STD_LOGIC;
		rst                : in  STD_LOGIC;
		rx                 : in  STD_LOGIC;
		valid              : out STD_LOGIC;
		error              : out STD_LOGIC;
		data               : out STD_LOGIC_VECTOR(num_of_databits-1 downto 0);
		uart_strobe        : in  STD_LOGIC
	);
end entity;

architecture rtl of uart is
	signal shift_reg      : std_logic_vector(num_of_databits-1 downto 0);
	signal shift_en       : std_logic;
	
	signal cntbit_value   : unsigned(log2ceil(num_of_databits) downto 0);
	signal cntbit_en      : std_logic;
	signal cntbit_rst     : std_logic;
	
	signal cntsamp_value  : unsigned(log2ceil(num_of_databits) downto 0);
	signal cntsamp_en     : std_logic;
	signal cntsamp_rst    : std_logic;
	
	type t_state is (s_idle, s_startbit, s_receive, s_stopbit, s_error);
	signal current_state  : t_state := s_idle;
	signal next_state     : t_state;
	
begin
	shift: process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				shift_reg <= (others => '0');
			elsif shift_en = '1' then 
				shift_reg <= rx & shift_reg(shift_reg'right downto 1);
			end if;
		end if;
	end process;
	data <= shift_reg;
	
	cntbit: process(clk)
	begin
		if rising_edge(clk) then
			if cntbit_rst = '1' then
				cntbit_value <= (others => '0');
			elsif cntbit_en = '1' then 
				cntbit_value <= cntbit_value + 1;
			end if;
		end if;
	end process;
	
	cntsamp: process(clk)
	begin
		if rising_edge(clk) then
			if cntsamp_rst = '1' then
				cntsamp_value <= (others => '0');
			elsif cntsamp_en = '1' then 
				cntsamp_value <= cntsamp_value + 1;
			end if;
		end if;
	end process;
	
	-- FSM
	fsmreg: process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				current_state <= s_idle;
			else 
				current_state <= next_state;
			end if;
		end if;
	end process;
	
	fsmcomb: process(current_state, uart_strobe, rx, cntbit_value, cntsamp_value)
	begin
		-- default values;
		next_state    <= current_state;
		valid         <= '0';
		error         <= '0';
		cntbit_en     <= '0';
		cntbit_rst    <= '0';
		cntsamp_en    <= '0';
		cntsamp_rst   <= '0';
		
		case current_state is
			when s_idle =>
				cntsamp_rst <= '1';
				cntbit_rst  <= '1';
				if rx = '0' then 
					next_state <= s_startbit;
				end if;
			
			when s_startbit =>
				cntsamp_en <= uart_strobe;
				if cntsamp_value = (uart_oversampling/2)-1 then
					cntsamp_rst <= '1';
					next_state  <= s_receive;
				end if;
				
			when s_receive =>
				cntsamp_en <= uart_strobe;
				if cntsamp_value = uart_oversampling-1 then
					cntsamp_rst <= '1';
					cntbit_en <= '1';
					shift_en  <= '1';
				end if;
				if cntbit_value = num_of_databits then
					cntsamp_rst <= '1';
					next_state <= s_stopbit;
				end if;			
				
			when s_stopbit =>
				cntsamp_en <= uart_strobe;
				valid      <= '1';
				if (cntsamp_value = 15) and rx = '1' then
					next_state <= s_idle;
				elsif (cntsamp_value = 15) and rx = '0' then
					next_state <= s_error;
				end if;
			
			when s_error =>
				error <= '1';
		end case;
	end process;
end architecture;