-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- 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.
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--                               RISCV core
--                                 V1.1
--
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

-- - - - - - - -
--  libraries
-- - - - - - - -
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use work.rv_pkg.all;


-- - - - - - - - - - - - - - - - - - - - -
--  module:  UART (uartlite compatable)
-- - - - - - - - - - - - - - - - - - - - -
entity vsuart_lite is
  generic ( CLK_FREQ    : natural := 50000000;           -- default 50M
            UART_SPEED  : natural := 1000000 );          -- default 1M
  port ( clk_i      : in  std_logic;
         xrst_i     : in  std_logic;
         --  vsbus
         pp_addr_i  : in  slv_31_00;
         pp_data_i  : in  slv_31_00;                     -- ! NC [31:8]
         pp_rd_i    : in  std_logic;
         pp_wr_i    : in  slv_03_00;                     -- ! NC [ 3:1]
         pp_data_o  : out slv_31_00;
         --
         rx_i       : in  std_logic;
         tx_o       : out std_logic;
         intr_o     : out std_logic
         );
end entity vsuart_lite;


architecture rtl of vsuart_lite is


  -- - - - - - - - - - - -
  --  uart_lite constants
  -- - - - - - - - - - - -
  constant ULITE_RX                   : slv_07_00 := X"00";
  constant ULITE_RX_DATA_R            : slv_07_00 := X"00";

  constant ULITE_TX                   : slv_07_00 := X"04";
  constant ULITE_TX_DATA_R            : slv_07_00 := X"00";

  constant ULITE_ST                   : slv_07_00 := X"08";
  constant ULITE_ST_IE_B              : natural   := 4;     -- IRQ enabled
  constant ULITE_ST_TXFULL_B          : natural   := 3;     -- TX fifo full
  constant ULITE_ST_TXEMPTY_B         : natural   := 2;     -- TX fifo empty
  constant ULITE_ST_RXFULL_B          : natural   := 1;     -- RX fifo full
  constant ULITE_ST_RXVALID_B         : natural   := 0;     -- RX fifo valid

  constant ULITE_CTRL                 : slv_07_00 := X"0C";
  constant ULITE_CTRL_IE_DEFAULT      : std_logic := '0';
  constant ULITE_CTRL_IE_B            : natural   := 4;     -- enable interrupt
  constant ULITE_CTRL_RST_RX_DEFAULT  : std_logic := '0';
  constant ULITE_CTRL_RST_RX_B        : natural   := 1;     -- clear RX fifo

  constant ULITE_DIV                  : slv_07_00 := X"10";

  --
  -- Uart_LITE REGISTER MAP:
  --
  -- ULITE_RX      READ          0x00  :    ULITE_RX_DATA_R        7:0
  -- ULITE_TX      WRITE         0x04  :    ULITE_TX_DATA_R        7:0
  -- ULITE_ST      READ          0x08  :    ULITE_ST_IE_B            4
  --                                        ULITE_ST_TXFULL_B        3
  --                                        ULITE_ST_TXEMPTY_B       2
  --                                        ULITE_ST_RXFULL_B        1
  --                                        ULITE_ST_RXVALID_B       0
  -- ULITE_CTRL    R/W           0x0C  :    ULITE_CTRL_IE_B          4
  --                                        ULITE_CTRL_RST_RX_B      1
  --
  -- NOTES: RXFULL == RXVALID
  --        STATUS_IE not used in logic


  --  configuration
  constant STOP_BITS      : std_logic := '0';   -- 0 == 1, 1 == 2
  constant START_BIT      : slv_03_00 := X"0";  -- d0
  constant STOP_BIT0      : slv_03_00 := X"9";  -- d9
  constant STOP_BIT1      : slv_03_00 := X"A";  -- d10

  constant UART_DIVISOR_W : natural := 12;
  constant UART_DIVIDER   : slv_t(UART_DIVISOR_W-1 downto 0) :=
    slv_t(to_unsigned((CLK_FREQ/UART_SPEED)-1, UART_DIVISOR_W));

  signal ulite_div_q                 : slv_t(UART_DIVISOR_W-1 downto 0);

  --  ulite_control_ie (internal]
  signal ulite_control_ie_q          : std_logic;
  --  ulite_control_rst_rx (auto_clr)
  signal ulite_control_rst_rx_q      : std_logic;
  signal data_w                      : slv_31_00;
  signal ulite_rx_rd_req_w           : std_logic;
  signal ulite_tx_wr_req_w           : std_logic;

  --  TX signals
  signal tx_busy_q                   : std_logic;
  signal tx_bits_q                   : slv_03_00;
  signal tx_count_q                  : slv_t(UART_DIVISOR_W-1 downto 0);
  signal tx_shift_reg_q              : slv_07_00;

  signal txd_q                       : std_logic;

  --  RX signals
  signal rxd_q                       : std_logic;
  signal rx_data_q                   : slv_07_00;
  signal rx_bits_q                   : slv_t(3 downto 0);
  signal rx_count_q                  : slv_t(UART_DIVISOR_W-1 downto 0);
  signal rx_shift_reg_q              : slv_07_00;
  signal rx_ready0_q                 : std_logic;
  signal rx_ready_q                  : std_logic;
  signal rx_busy_q                   : std_logic;

  -- - - - - - - -
  --  re-sync RXD
  -- - - - - - - -
  signal rxd_ms_q                    : std_logic;
  signal rx_sample_w                 : std_logic;
  signal tx_sample_w                 : std_logic;
  signal tx_complete_q               : std_logic;
  signal txd_w                       : std_logic;
  signal intr_q                      : std_logic;

  -- pragma translate_off
  signal tx_sending_dbg              : slv_t(tx_shift_reg_q'range);
  -- pragma translate_on

begin


  -- - - - - - - - -
  --  re-sync RXD
  -- - - - - - - - -
  p_dffs(clk_i, xrst_i, rx_i,     rxd_ms_q);  -- set ff
  p_dffs(clk_i, xrst_i, rxd_ms_q, rxd_q);     -- set ff


  --  ulite_control_ie (internal]
  --  ulite_control_rst_rx (auto_clr)
  p_ctrl_ie : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        ulite_control_ie_q     <= ULITE_CTRL_IE_DEFAULT;
        ulite_control_rst_rx_q <= ULITE_CTRL_RST_RX_DEFAULT;
        ulite_div_q            <= UART_DIVIDER;
      else
        ulite_control_rst_rx_q <= ULITE_CTRL_RST_RX_DEFAULT;
        if (pp_wr_i(0) = '1') then
          case pp_addr_i(7 downto 0) is
            when ULITE_CTRL =>
              ulite_control_ie_q     <= pp_data_i(ULITE_CTRL_IE_B);
              ulite_control_rst_rx_q <= pp_data_i(ULITE_CTRL_RST_RX_B);
            when ULITE_DIV =>
              ulite_div_q            <= pp_data_i(ulite_div_q'range);
            when others =>
              null;
          end case;
        end if;
      end if;
    end if;
  end process p_ctrl_ie;


  ulite_tx_wr_req_w <= pp_wr_i(0)  and eq(pp_addr_i(7 downto 0), ULITE_TX);


  -- - - - - - - - - - - - - - - - - - - - - - - -
  --  read mux
  -- - - - - - - - - - - - - - - - - - - - - - - -
  p_rd_mux : process ( pp_addr_i, rx_data_q, ulite_control_ie_q,
                       tx_busy_q, rx_ready_q, ulite_div_q )
  begin
    --
    data_w <= X"00000000";                                          -- 32b
    --
    case pp_addr_i(7 downto 0) is
      when ULITE_RX =>
        data_w(ULITE_RX_DATA_R'range)  <= rx_data_q;
      when ULITE_ST =>
        data_w(ULITE_ST_IE_B)          <= ulite_control_ie_q;
        data_w(ULITE_ST_TXFULL_B)      <= tx_busy_q;
        data_w(ULITE_ST_TXEMPTY_B)     <= not tx_busy_q;
        data_w(ULITE_ST_RXFULL_B)      <= rx_ready_q;
        data_w(ULITE_ST_RXVALID_B)     <= rx_ready_q;
      when ULITE_CTRL =>
        data_w(ULITE_CTRL_IE_B)        <= ulite_control_ie_q;
      when ULITE_DIV =>
        data_w(ulite_div_q'range)      <= ulite_div_q;
      when others =>
        data_w                         <= X"00000000";              -- 32b
    end case;
  end process p_rd_mux;


  pp_data_o <= data_w when (pp_rd_i = '1') else X"00000000";        -- NO REG


  ulite_rx_rd_req_w <= '1' when ( pp_rd_i = '1' and
                                  (pp_addr_i(7 downto 0) = ULITE_RX) )
                       else '0';


  -- - - - - - - - - - - - - - - - - - - - - - - -
  --  RX clock divider
  -- - - - - - - - - - - - - - - - - - - - - - - -
  p_rx_cnt : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        rx_count_q      <= (others => '0');
      else
        --  inactive
        if (rx_busy_q = '0') then
          -- i.e. /2
          rx_count_q    <= slv_t(shift_right(unsigned(ulite_div_q), 1));
        --  rx bit timer
        elsif (not_zero(rx_count_q)) then
          rx_count_q    <= dec1u(rx_count_q);
        --  active
        elsif (rx_sample_w = '1') then
          --  last bit?
          if ( (rx_bits_q = STOP_BIT0 and STOP_BITS  = '0') or
               (rx_bits_q = STOP_BIT1 and STOP_BITS /= '0') ) then
            rx_count_q  <= (others => '0');
          else
            rx_count_q  <= ulite_div_q;       -- UART_DIVIDER;
          end if;
        end if;
      end if;
    end if;
  end process p_rx_cnt;


  rx_sample_w <= is_zero(rx_count_q);


  -- - - - - - - - - - - - - - - - - - - - - - - -
  --  RX shift register
  -- - - - - - - - - - - - - - - - - - - - - - - -
  p_rx_sft : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        rx_shift_reg_q <= X"00";
        rx_busy_q      <= '0';
      else
        --  rx busy
        if (rx_busy_q = '1' and  rx_sample_w = '1') then
          --  last bit?
          if ( (rx_bits_q = STOP_BIT0 and STOP_BITS  = '0') or
               (rx_bits_q = STOP_BIT1 and STOP_BITS /= '0') ) then
            rx_busy_q <= '0';
          elsif (rx_bits_q = START_BIT) then
            --  start bit should still be low as sampling mid
            --  way through start bit, so if high, error!
            if (rxd_q = '1') then
              rx_busy_q <= '0';
            end if;
          --  rx shift register
          else
            rx_shift_reg_q <= (rxd_q & rx_shift_reg_q(7 downto 1));
          end if;
        --  start bit?
        elsif (rx_busy_q = '0' and  rxd_q = '0') then
          rx_shift_reg_q <= X"00";
          rx_busy_q      <= '1';
        end if;
      end if;
    end if;
  end process p_rx_sft;


  p_rx_bits : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        rx_bits_q     <= START_BIT;
      else
        if (rx_sample_w = '1' and rx_busy_q = '1') then
          if ((rx_bits_q = STOP_BIT1 and STOP_BITS /= '0') or
              (rx_bits_q = STOP_BIT0 and STOP_BITS  = '0')) then
            rx_bits_q <= START_BIT;
          else
            rx_bits_q <= inc1u(rx_bits_q);
          end if;
        elsif (rx_busy_q = '0' and is_zero(ulite_div_q)) then
          rx_bits_q   <= inc1u(START_BIT);
        elsif (rx_busy_q = '0') then
          rx_bits_q   <= START_BIT;
        end if;
      end if;
    end if;
  end process p_rx_bits;


  -- - - - - - - - - - - - - - - - - - - - - - - -
  --  RX Data
  -- - - - - - - - - - - - - - - - - - - - - - - -
  p_rx_dat : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        rx_ready_q       <= '0';
        rx_ready0_q      <= '0';
        rx_data_q        <= X"00";
      else
        --
        rx_ready0_q      <= rx_ready_q;
        --
        --  if reading data, reset data state
        if (ulite_rx_rd_req_w = '1' or ulite_control_rst_rx_q = '1') then
          rx_ready_q     <= '0';
        end if;
        if (rx_busy_q = '1' and rx_sample_w = '1') then
          --  stop bit
          if ((rx_bits_q = STOP_BIT1 and STOP_BITS /= '0') or
              (rx_bits_q = STOP_BIT0 and STOP_BITS  = '0')) then
            --  RXD should be still high
            if (rxd_q = '1') then
              rx_data_q  <= rx_shift_reg_q;
              rx_ready_q <= '1';
              --  bad Stop bit - wait for a full bit period
              --  before allowing start bit detection again
            else
              rx_ready_q <= '0';
              rx_data_q  <= X"00";
            end if;
            --  mid start bit sample - if high then error
          elsif (rx_bits_q = START_BIT and rxd_q = '1') then
          end if;
        end if;
      end if;
    end if;
  end process p_rx_dat;



  -- - - - - - - - - - - - - - - - - - - - - - - -
  --  TX clock divider
  -- - - - - - - - - - - - - - - - - - - - - - - -
  p_tx_ckdiv : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        tx_count_q    <= (others => '0');
      else
        --  Idle
        if (tx_busy_q = '0') then
          tx_count_q  <= ulite_div_q;
          --  Tx bit timer
        elsif (not_zero(tx_count_q)) then
          tx_count_q  <= dec1u(tx_count_q);
        elsif (tx_sample_w = '1') then
          tx_count_q  <= ulite_div_q;
        end if;
      end if;
    end if;
  end process p_tx_ckdiv;


  tx_sample_w <= is_zero(tx_count_q);


  -- - - - - - - - - - -
  --  TX shift register
  -- - - - - - - - - - -
  p_tx_sft : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        tx_shift_reg_q <= X"00";
        tx_busy_q      <= '0';
        tx_complete_q  <= '0';
        -- pragma translate_off
        tx_sending_dbg <= X"00";                                    -- 7:0
        -- pragma translate_on
      else        --  TX busy
        if (tx_busy_q = '1') then
          --  shift tx data
          if (tx_bits_q /= START_BIT and tx_sample_w = '1') then
            tx_shift_reg_q <= ('0' & tx_shift_reg_q(7 downto 1));
          end if;
          --  last bit?
          if ( tx_sample_w = '1' and
               ( (tx_bits_q = STOP_BIT0 and STOP_BITS  = '0') or
                 (tx_bits_q = STOP_BIT1 and STOP_BITS /= '0') ) )then
            tx_busy_q      <= '0';
            tx_complete_q  <= '1';
            -- pragma translate_off
            tx_sending_dbg <= X"00";                                -- 7:0
            -- pragma translate_on
          end if;
          --  buffer data to transmit
        elsif (ulite_tx_wr_req_w = '1') then
          tx_shift_reg_q <= pp_data_i(ULITE_TX_DATA_R'range);       -- 7:0
          -- pragma translate_off
          tx_sending_dbg <= pp_data_i(ULITE_TX_DATA_R'range);       -- 7:0
          -- pragma translate_on
          tx_busy_q      <= '1';
          tx_complete_q  <= '0';
        else
          tx_complete_q  <= '0';
        end if;
      end if;
    end if;
  end process p_tx_sft;



  p_tx_bits : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        tx_bits_q  <= X"0";             -- 3:0
      else
        if (tx_sample_w = '1' and  tx_busy_q = '1') then
          if ( (tx_bits_q = STOP_BIT1 and STOP_BITS /= '0') or
               (tx_bits_q = STOP_BIT0 and STOP_BITS  = '0') ) then
            tx_bits_q <= START_BIT;
          else
            tx_bits_q <= inc1u(tx_bits_q);
          end if;
        end if;
      end if;
    end if;
  end process p_tx_bits;


  -- - - - - - - -
  --  UART TT pin
  -- - - - - - - -
  p_c_tx : process (tx_busy_q, tx_bits_q, tx_shift_reg_q)
  begin
    txd_w <= '1';
    if (tx_busy_q = '1') then
      --  start bit (TXD = L)
      if (tx_bits_q = START_BIT) then
        txd_w <= '0';
        --  stop bits (TXD = H)
      else if (tx_bits_q = STOP_BIT0 or tx_bits_q = STOP_BIT1) then
             txd_w <= '1';
             --  dData bits
           else
             txd_w <= tx_shift_reg_q(0);
           end if;
      end if;
    end if;
  end process p_c_tx;


  p_dffs(clk_i, xrst_i, txd_w, txd_q);     -- set ff

  tx_o <= txd_q;


  -- - - - - - -
  --  interrupt
  -- - - - - - -
  p_intr : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        intr_q <= '0';
      else
        if (ulite_control_ie_q = '1') then
          if (tx_complete_q = '1') then
            intr_q <= '1';
          elsif (rx_ready0_q = '0' and rx_ready_q = '1') then
            -- generate single cycle pulse only from rise edge of rx_ready_q
            intr_q <= '1';
          else
            intr_q <= '0';
          end if;
        else
          intr_q <= '0';
        end if;
      end if;
    end if;
  end process p_intr;


  intr_o <= intr_q;

end rtl;
