-- -----------------------------------------------------------------------
--
-- Syntiac's generic VHDL support files.
--
-- -----------------------------------------------------------------------
-- Copyright 2005-2010 by Peter Wendrich (pwsoft@syntiac.com)
-- http://www.syntiac.com/?.html
-- -----------------------------------------------------------------------
--
-- gen_usart.vhd
--
-- -----------------------------------------------------------------------
--
-- USART - Synchronous serial receiver/transmitter
--
-- -----------------------------------------------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.numeric_std.ALL;

-- -----------------------------------------------------------------------

entity gen_usart is
	generic (
		bits : integer := 8
	);
	port (
		clk : in std_logic;

		d : in unsigned(bits-1 downto 0) := (others => '0');
		d_trigger : in std_logic := '0';
		d_empty : out std_logic;
		q : out unsigned(bits-1 downto 0);
		q_trigger : out std_logic;

		serial_clk : in std_logic;
		serial_rxd : in std_logic := '1';
		serial_txd : out std_logic;
		serial_cts_n : in std_logic := '0'
	);
end entity;

-- -----------------------------------------------------------------------

architecture rtl of gen_usart is
	type state_t is (
		STATE_IDLE,
		STATE_BITS,
		STATE_STOP);
	signal serial_clk_reg : std_logic := '0';
	signal serial_clk_dly : std_logic := '0';
	signal receive_state : state_t := STATE_IDLE;
	signal receive_shift : unsigned(bits-1 downto 0) := (others => '0');
	signal receive_cnt : integer range 0 to bits-1 := 0;

	signal transmit_state : state_t := STATE_IDLE;
	signal transmit_empty : std_logic := '1';
	signal transmit_buffer : unsigned(bits-1 downto 0) := (others => '0');
	signal transmit_shift : unsigned(bits-1 downto 0) := (others => '0');
	signal transmit_cnt : integer range 0 to bits-1 := 0;
begin
	d_empty <= transmit_empty and (not d_trigger);

	process(clk)
	begin
		if rising_edge(clk) then
			serial_clk_reg <= serial_clk;
			serial_clk_dly <= serial_clk_reg;
		end if;
	end process;

	receive_process: process(clk)
	begin
		if rising_edge(clk) then
			q_trigger <= '0';
			-- Detect rising edge
			if (serial_clk_reg = '1') and (serial_clk_dly = '0') then
				case receive_state is
				when STATE_IDLE =>
					receive_cnt <= 0;
					if serial_rxd = '0' then
						receive_state <= STATE_BITS;
					end if;
				when STATE_BITS =>
					receive_shift <= serial_rxd & receive_shift(receive_shift'high downto 1);
					if receive_cnt = bits-1 then
						receive_state <= STATE_STOP;
					else
						receive_cnt <= receive_cnt + 1;
					end if;
				when STATE_STOP =>
					receive_state <= STATE_IDLE;
					if serial_rxd = '1' then
						q <= receive_shift;
						q_trigger <= '1';
					end if;
				end case;
			end if;
		end if;
	end process;
	
	transmit_process: process(clk)
	begin
		if rising_edge(clk) then
			-- Detect falling edge
			if (serial_clk_reg = '0') and (serial_clk_dly = '1') then
				case transmit_state is
				when STATE_IDLE =>
					transmit_cnt <= 0;
					if (transmit_empty = '0') and (serial_cts_n = '0') then
						transmit_shift <= transmit_buffer;
						transmit_empty <= '1';
						transmit_state <= STATE_BITS;
						serial_txd <= '0';
					else
						serial_txd <= '1';
					end if;
				when STATE_BITS =>
					serial_txd <= transmit_shift(transmit_cnt);
					if transmit_cnt = bits-1 then
						transmit_state <= STATE_STOP;
					else
						transmit_cnt <= transmit_cnt + 1;
					end if;
				when STATE_STOP =>
					serial_txd <= '1';
					transmit_state <= STATE_IDLE;
				end case;
			end if;
			if d_trigger = '1' then
				transmit_buffer <= d;
				transmit_empty <= '0';
			end if;
		end if;
	end process;
	
end architecture;





