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

entity uart_rx_tx_regs is
    generic (
        CLOCK_FREQ : integer := 10_000_000;
        BAUD_RATE  : integer := 9600
    );
    port (
        clk       : in  std_logic;
        rst       : in  std_logic;  -- active high
        rx        : in  std_logic;
        tx        : out std_logic;

        reg8_out  : out std_logic_vector(7 downto 0);
        reg16_out : out std_logic_vector(15 downto 0);
        new_data  : out std_logic
    );
end entity;

architecture rtl of uart_rx_tx_regs is

    constant BAUD_TICKS : integer := CLOCK_FREQ / BAUD_RATE;

    -- RX synchronizer
    signal rx_meta, rx_sync : std_logic := '1';

    -- RX state
    signal rx_active   : std_logic := '0';
    signal rx_baud_cnt : integer range 0 to BAUD_TICKS-1 := 0;
    signal rx_bit_cnt  : integer range 0 to 9 := 0;
    signal rx_shift    : std_logic_vector(7 downto 0) := (others => '0');
    signal rx_idx      : integer range 0 to 2 := 0;

    -- Registers
    signal reg8       : std_logic_vector(7 downto 0) := (others => '0');
    signal reg16      : std_logic_vector(15 downto 0) := (others => '0');
    signal data_pulse : std_logic := '0';

    -- TX state
    type tx_state_t is (TX_IDLE, TX_START, TX_DATA, TX_STOP);
    signal tx_state    : tx_state_t := TX_IDLE;
    signal tx_baud_cnt : integer range 0 to BAUD_TICKS-1 := 0;
    signal tx_bit_cnt  : integer range 0 to 7 := 0;
    signal tx_shift    : std_logic_vector(7 downto 0) := (others => '0');
    signal tx_reg      : std_logic := '1';
    signal tx_idx      : integer range 0 to 2 := 0;
    signal tx_start1    : std_logic := '0';

    type byte_array is array (0 to 2) of std_logic_vector(7 downto 0);
    signal tx_bytes : byte_array;

begin
    tx <= tx_reg;

    ------------------------------------------------------------------
    -- RX Synchronizer
    ------------------------------------------------------------------
    process(clk)
    begin
        if rising_edge(clk) then
            rx_meta <= rx;
            rx_sync <= rx_meta;
        end if;
    end process;

    ------------------------------------------------------------------
    -- UART RX
    ------------------------------------------------------------------
    process(clk)
    begin
        if rising_edge(clk) then
            if rst = '1' then
                rx_active  <= '0';
                rx_bit_cnt <= 0;
                rx_idx     <= 0;
                data_pulse <= '0';
                reg8       <= (others => '0');
                reg16      <= (others => '0');
            else
                data_pulse <= '0';

                if rx_active = '0' then
                    if rx_sync = '0' then
                        rx_active   <= '1';
                        rx_baud_cnt <= BAUD_TICKS / 2;
                        rx_bit_cnt  <= 0;
                    end if;
                else
                    if rx_baud_cnt = 0 then
                        rx_baud_cnt <= BAUD_TICKS - 1;
                        rx_bit_cnt  <= rx_bit_cnt + 1;

                        if rx_bit_cnt >= 1 and rx_bit_cnt <= 8 then
                            rx_shift(rx_bit_cnt-1) <= rx_sync;

                        elsif rx_bit_cnt = 9 then
                            rx_active <= '0';

                            case rx_idx is
                                when 0 =>
                                    reg8 <= rx_shift;
                                when 1 =>
                                    reg16(7 downto 0) <= rx_shift;
                                when 2 =>
                                    reg16(15 downto 8) <= rx_shift;
                                    data_pulse <= '1';
                                    tx_start1   <= '1';
                                when others =>
                                    null;
                            end case;

                            if rx_idx = 2 then
                                rx_idx <= 0;
                            else
                                rx_idx <= rx_idx + 1;
                            end if;
                        end if;
                    else
                        rx_baud_cnt <= rx_baud_cnt - 1;
                    end if;
                end if;
            end if;
        end if;
    end process;

    ------------------------------------------------------------------
    -- UART TX (echo 3 bytes)
    ------------------------------------------------------------------
    process(clk)
    begin
        if rising_edge(clk) then
            if rst = '1' then
                tx_state <= TX_IDLE;
                tx_reg   <= '1';
                tx_start1 <= '0';
                tx_idx   <= 0;
            else
                case tx_state is
                    when TX_IDLE =>
                        if tx_start1 = '1' then
                            tx_bytes(0) <= reg8;
                            tx_bytes(1) <= reg16(7 downto 0);
                            tx_bytes(2) <= reg16(15 downto 8);

                            tx_shift <= reg8;
                            tx_idx   <= 0;
                            tx_start1 <= '0';
                            tx_reg   <= '0';
                            tx_state <= TX_START;
                            tx_baud_cnt <= BAUD_TICKS - 1;
                        end if;

                    when TX_START =>
                        if tx_baud_cnt = 0 then
                            tx_state <= TX_DATA;
                            tx_bit_cnt <= 0;
                            tx_baud_cnt <= BAUD_TICKS - 1;
                        else
                            tx_baud_cnt <= tx_baud_cnt - 1;
                        end if;

                    when TX_DATA =>
                        if tx_baud_cnt = 0 then
                            tx_reg <= tx_shift(tx_bit_cnt);
                            tx_baud_cnt <= BAUD_TICKS - 1;
                            if tx_bit_cnt = 7 then
                                tx_state <= TX_STOP;
                            else
                                tx_bit_cnt <= tx_bit_cnt + 1;
                            end if;
                        else
                            tx_baud_cnt <= tx_baud_cnt - 1;
                        end if;

                    when TX_STOP =>
                        if tx_baud_cnt = 0 then
                            tx_reg <= '1';
                            if tx_idx = 2 then
                                tx_state <= TX_IDLE;
                            else
                                tx_idx <= tx_idx + 1;
                                tx_shift <= tx_bytes(tx_idx + 1);
                                tx_state <= TX_START;
                                tx_reg <= '0';
                            end if;
                            tx_baud_cnt <= BAUD_TICKS - 1;
                        else
                            tx_baud_cnt <= tx_baud_cnt - 1;
                        end if;
                end case;
            end if;
        end if;
    end process;

    reg8_out  <= reg8;
    reg16_out <= reg16;
    new_data  <= data_pulse;

end architecture;
