-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- Copyright 2025 VLSI Solution Oy
-- SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1
--
-- Licensed under the Solderpad Hardware License v 2.1 (the "License");
-- you may not use this file except in compliance with the License, or,
-- at your option, the Apache License version 2.0.
-- You may obtain a copy of the License at
--
--    https://solderpad.org/licenses/SHL-2.1/
--
-- Unless required by applicable law or agreed to in writing, any work
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
--  uart_receiver.vhd
--  V1.
--
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use work.uart_pkg.all;


entity uart_receiver is
  -- receiver fifo is 11 biys as it has break, parity and framing error bits
  generic ( FIFO_WIDTH   : natural := 11;       -- 8 + break, parity and framing
            FIFO_DEPTH   : natural := 16;
            FIFO_PNTR_W  : natural :=  4 );
  port ( clk_i             : in  std_logic;     -- clock input
         xrst_i            : in  std_logic;     -- reset input
         lcr_i             : in  slv_07_00;
         rxf_pop_i         : in  std_logic;
         srx_pad_i         : in  std_logic;
         enable_i          : in  std_logic;
         rx_reset_i        : in  std_logic;
         lsr_mask_i        : in  std_logic;
         --
         counter_t_o       : out slv_09_00;
         rxf_count_o       : out slv_t(FIFO_PNTR_W+1-1 downto 0);
         rxf_data_out_o    : out slv_t(FIFO_WIDTH-1 downto 0);
         rxf_overrun_o     : out std_logic;
         rxf_error_bit_o   : out std_logic;
         rxstate_o         : out slv_03_00;
         rxf_push_pulse_o  : out std_logic );
end entity uart_receiver;


architecture rtl of uart_receiver is

  --  lcr bits (line control register)
  constant UART_LC_SB    : natural := 2;          -- stop bits
  constant UART_LC_PE    : natural := 3;          -- parity enable
  constant UART_LC_EP    : natural := 4;          -- even parity
  constant UART_LC_SP    : natural := 5;          -- stick parity


  component uart_rfifo
    -- FIFO parameters
    generic ( FIFO_WIDTH  : natural range 1 to 32 := 11;
              FIFO_DEPTH  : natural range 1 to 32 := 16;
              FIFO_PNTR_W : natural range 1 to 32 := 4 );
    port ( clk_i             : in   std_logic;
           xrst_i            : in   std_logic;
           -- control / data signals
           push_i            : in   std_logic;    -- push to fifo, active high
           pop_i             : in   std_logic;    -- pop from_fifo, active high
           data_i            : in   slv_t(FIFO_WIDTH-1 downto 0);
           data_o            : out  slv_t(FIFO_WIDTH-1 downto 0);
           -- status signals
           fifo_reset_i      : in   std_logic;
           reset_status_i    : in   std_logic;
           overrun_o         : out  std_logic;
           count_o           : out  slv_t(FIFO_PNTR_W+1-1 downto 0);
           error_bit_o       : out  std_logic );
  end component;


  constant sr_idle          : slv_03_00 := "0000";
  constant sr_rec_start     : slv_03_00 := "0001";
  constant sr_rec_bit       : slv_03_00 := "0010";
  constant sr_rec_parity    : slv_03_00 := "0011";
  constant sr_rec_stop      : slv_03_00 := "0100";
  constant sr_check_parity  : slv_03_00 := "0101";
  constant sr_rec_prepare   : slv_03_00 := "0110";
  constant sr_end_bit       : slv_03_00 := "0111";
  constant sr_ca_lc_parity  : slv_03_00 := "1000";
  constant sr_wait1         : slv_03_00 := "1001";
  constant sr_push          : slv_03_00 := "1010";

  signal rcounter16         : slv_03_00;
  signal rbit_counter       : slv_02_00;
  --  receiver shift register
  signal rshift             : slv_07_00;
  signal rparity            : std_logic;          -- received parity
  signal rparity_error      : std_logic;
  signal rframing_error     : std_logic;          -- framing error flag
  signal rparity_xor        : std_logic;
  --  counts the 0 (low) signals
  signal counter_b          : slv_07_00;
  signal rxf_push_q         : std_logic;

  signal rxstate_q          : slv_03_00;

  --  RX FIFO signals
  signal rxf_data_in        : slv_t(FIFO_WIDTH-1 downto 0);
  signal rxf_push_pulse_w   : std_logic;
  signal rxf_push           : std_logic;
  signal rxf_count_w        : slv_t(FIFO_PNTR_W+1-1 downto 0);
  --  an error (parity or framing) is inside the fifo
  signal break_error        : std_logic;

  signal rcounter16_eq_7    : std_logic;
  signal rcounter16_eq_0    : std_logic;

  signal rcounter16_minus_1 : slv_03_00;

  --  break condition detection.
  --  works in conjuction with the receiver state machine
  --  value to be set to timeout counter
  signal toc_value          : slv_09_00;
  --  value to be set to break counter
  signal brc_value          : slv_07_00;

  --  timeout condition detection counts the timeout condition clocks
  signal counter_t_q        : slv_09_00;

begin

  rxf_push_pulse_o  <= rxf_push_pulse_w;
  rxf_count_o       <= rxf_count_w;
  break_error       <= '1' when (unsigned(counter_b) = 0) else '0';

  rxstate_o         <= rxstate_q;

  counter_t_o       <= counter_t_q;


  -- RX FIFO instance
  fifo_rx : uart_rfifo
    generic map ( FIFO_WIDTH   => FIFO_WIDTH,
                  FIFO_DEPTH   => FIFO_DEPTH,
                  FIFO_PNTR_W  => FIFO_PNTR_W )
    port map ( clk_i           => clk_i,
               xrst_i          => xrst_i,
               push_i          => rxf_push_pulse_w,
               pop_i           => rxf_pop_i,
               data_i          => rxf_data_in,
               data_o          => rxf_data_out_o,
               fifo_reset_i    => rx_reset_i,
               reset_status_i  => lsr_mask_i,
               overrun_o       => rxf_overrun_o,
               count_o         => rxf_count_w,
               error_bit_o     => rxf_error_bit_o );


  rcounter16_eq_7    <= '1' when (unsigned(rcounter16) = 7) else '0';
  rcounter16_eq_0    <= '1' when (unsigned(rcounter16) = 0) else '0';
  rcounter16_minus_1 <= dec1u(rcounter16);                              -- - 1


  p_fsm : process (clk_i)
    variable uart_lc_ep_sp_v : slv_01_00;
  begin
    if (rising_edge(clk_i)) then
      if (xrst_i = '0') then
        rxstate_q       <= sr_idle;
        rcounter16      <= (others => '0');
        rbit_counter    <= (others => '0');
        rparity_xor     <= '0';
        rframing_error  <= '0';
        rparity_error   <= '0';
        rparity         <= '0';
        rshift          <= (others => '0');
        rxf_push        <= '0';
        rxf_data_in     <= (others => '0');
        uart_lc_ep_sp_v := "00";
      elsif (enable_i = '1') then
        uart_lc_ep_sp_v := "00";
        case rxstate_q is
          when sr_idle =>
            rxf_push         <= '0';
            rxf_data_in      <= (others => '0');
            rcounter16       <= "1110";
            if (srx_pad_i = '0' and break_error = '0') then
              -- detected a pulse (start bit?)
              rxstate_q         <= sr_rec_start;
            end if;
          when sr_rec_start =>
            rxf_push     <= '0';
            if (rcounter16_eq_7 = '1') then            -- check the pulse
              if (srx_pad_i = '1') then                -- no start bit
                rxstate_q <= sr_idle;
              else                                     -- start bit detected
                rxstate_q <= sr_rec_prepare;
              end if;
            end if;
            rcounter16 <= rcounter16_minus_1;
          when sr_rec_prepare =>
            -- number of bits in a word
            case lcr_i(1 downto 0) is                  -- UART_LC_BITS
              when "00"   => rbit_counter <= "100";
              when "01"   => rbit_counter <= "101";
              when "10"   => rbit_counter <= "110";
              when "11"   => rbit_counter <= "111";
              when others => null;
            end case;
            if (rcounter16_eq_0 = '1') then
              rxstate_q        <= sr_rec_bit;
              rcounter16    <= "1110";
              rshift        <= (others => '0');
            else
              rxstate_q <= sr_rec_prepare;
            end if;
            rcounter16 <= rcounter16_minus_1;
          when sr_rec_bit =>
            if (rcounter16_eq_0 = '1') then
              rxstate_q <= sr_end_bit;
            end if;
            if (rcounter16_eq_7 = '1') then            -- read the bit
              -- number of bits in a word
              case lcr_i(1 downto 0) is                -- UART_LC_BITS
                when "00" =>
                  rshift(4 downto 0)  <= srx_pad_i & rshift(4 downto 1);
                when "01" =>
                  rshift(5 downto 0)  <= srx_pad_i & rshift(5 downto 1);
                when "10" =>
                  rshift(6 downto 0)  <= srx_pad_i & rshift(6 downto 1);
                when "11" =>
                  rshift(7 downto 0)  <= srx_pad_i & rshift(7 downto 1);
                when others =>
                  null;
              end case;
            end if;
            rcounter16 <= rcounter16_minus_1;
          when sr_end_bit =>
            if (rbit_counter = "000") then       -- no more bits in word
              if (lcr_i(UART_LC_PE) = '1')  then -- choose state based on parity
                rxstate_q <= sr_rec_parity;
              else
                rxstate_q      <= sr_rec_stop;
                rparity_error <= '0';            -- no parity - no error :)
              end if;
            else                                 -- else more bits to read
              rxstate_q     <= sr_rec_bit;
              rbit_counter <= dec1u(rbit_counter);     -- - 1
            end if;
            rcounter16 <= "1110";
          when sr_rec_parity =>
            if (rcounter16_eq_7 = '1') then            -- read the parity
              rparity    <= srx_pad_i;
              rxstate_q   <= sr_ca_lc_parity;
            end if;
            rcounter16   <= rcounter16_minus_1;
          when sr_ca_lc_parity =>
            -- rcounter equals 6
            rcounter16  <= rcounter16_minus_1;
            -- calculate parity on all incoming data_out
            rparity_xor <= xor_bits(rshift, rparity);
            rxstate_q    <= sr_check_parity;
          when sr_check_parity =>                -- rcounter equals 5
            uart_lc_ep_sp_v := lcr_i(UART_LC_EP) & lcr_i(UART_LC_SP);
            -- case unsigned'(lcr_i(UART_LC_EP) & lcr_i(UART_LC_SP)) is
            case uart_lc_ep_sp_v is
              when "00" =>
                -- no error if parity 1
                if (rparity_xor = '0') then
                  rparity_error <=  '1';
                else
                  rparity_error <=  '0';
                end if;
              when "01" =>
                rparity_error <= not rparity;   -- parity should sticked to 1
              when "10" =>
                -- error if parity is odd
                if (rparity_xor = '1') then
                  rparity_error <=  '1';
                else
                  rparity_error <=  '0';
                end if;
              when "11" =>
                rparity_error <=  rparity;      -- parity should be sticked to 0
              when others =>
                null;
            end case;
            rcounter16 <= rcounter16_minus_1;
            rxstate_q   <= sr_wait1;
          when sr_wait1 =>
            if (rcounter16_eq_0 = '1') then
              rxstate_q <= sr_rec_stop;
              rcounter16 <= "1110";
            else
              rcounter16 <= rcounter16_minus_1;
            end if;
          when sr_rec_stop =>
            if (rcounter16_eq_7 = '1') then     -- read the parity
              -- no framing error if input is 1 (stop bit)
              rframing_error <= not srx_pad_i;
              rxstate_q <= sr_push;
            end if;
            rcounter16 <= rcounter16_minus_1;
          when sr_push =>
            if(srx_pad_i = '1' or break_error = '1') then
              if(break_error = '1') then
                -- break input (empty character) to receiver FIFO
                rxf_data_in <= X"00" & "100";
              else
                rxf_data_in <= (rshift & '0' & rparity_error & rframing_error);
                rxf_push    <= '1';
                rxstate_q    <= sr_idle;
              end if;
            elsif(rframing_error = '0') then
              -- There's always a framing before break_error -> wait for
              -- break or srx_pad_i
              rxf_data_in <= (rshift & '0' & rparity_error & rframing_error);
              rxf_push    <= '1';
              rcounter16  <= "1110";
              rxstate_q    <= sr_rec_start;
            end if;
          when others =>
            rxstate_q <= sr_idle;
        end case;
      end if;
    end if;
  end process p_fsm;



  p_push : process (clk_i)
  begin
    if (rising_edge(clk_i)) then
      if (xrst_i = '0') then
        rxf_push_q <= '0';
      else
        rxf_push_q <= rxf_push;
      end if;
    end if;
  end process p_push;

  rxf_push_pulse_w <= rxf_push and (not rxf_push_q);


  p_toc : process (lcr_i)
  begin
    case lcr_i(3 downto 0) is
      when "0000"                             =>
        toc_value <= "01" & X"BF";                 -- 447   -- 7 bits
      when "0100"                             =>
        toc_value <= "01" & X"DF";                 -- 479   -- 7.5 bits
      when "0001" | "1000"                    =>
        toc_value <= "01" & X"FF";                 -- 511   -- 8 bits
      when "1100"                             =>
        toc_value <= "10" & X"1F";                 -- 543   -- 8.5 bits
      when "0010" | "0101" | "1001"           =>
        toc_value <= "10" & X"3F";                 -- 575   -- 9 bits
      when "0011" | "0110" | "1010" | "1101"  =>
        toc_value <= "10" & X"7F";                 -- 639   -- 10 bits
      when "0111" | "1011" | "1110"           =>
        toc_value <= "10" & X"BF";                 -- 703   -- 11 bits
      when "1111"                             =>
        toc_value <= "10" & X"FF";                 -- 767   -- 12 bits
      when others                             =>
        null;
    end case;
  end process p_toc;


  -- the same as timeout but 1 insead of 4 character times
  brc_value <= toc_value(9 downto 2);


  -- timeout condition detection
  p_cnt_b : process (clk_i)
  begin
    if (rising_edge(clk_i)) then
      if (xrst_i = '0') then
        counter_b <= X"9F";                    -- 8b : 159
      elsif (srx_pad_i = '1') then
          counter_b <= brc_value;              -- character time length - 1
      elsif(enable_i = '1' and unsigned(counter_b) /= 0) then
        -- only work on enable times  break not reached.
        -- decrement break counter
        counter_b <= std_logic_vector(unsigned(counter_b) - 1);
      end if;  -- always of break condition detection
    end if;
  end process p_cnt_b;



  p_cnt_t : process (clk_i)
  begin
    if (rising_edge(clk_i)) then
      if (xrst_i = '0') then
        counter_t_q <= "10" & X"7F";   -- 639  -- 10 bits for the default 8N1
      elsif(rxf_push_pulse_w = '1' or rxf_pop_i = '1' or
            unsigned(rxf_count_w) = 0)  then
        -- counter is reset when RX FIFO is empty, accessed or above
        -- trigger level
        counter_t_q <= toc_value;
      elsif (enable_i = '1' and unsigned(counter_t_q) /= 0) then
        -- we don't want to underflow
        counter_t_q <= dec1u(counter_t_q);     -- - 1
      end if;
    end if;
  end process p_cnt_t;

end rtl;
