-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- 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:  general purpose IO peripheral
-- - - - - - - - - - - - - - - - - - - - -
entity vsgpio is
  generic ( NR_GPIOS   : integer range 1 to 32 := 8 );  
  port ( clk_i         : in  std_logic;
         xrst_i        : in  std_logic;
         --  vsbus,  inpts retimed in higher level
         pp_rd_i       : in  std_logic;
         pp_wr_i       : in  slv_03_00;   --! 3:1 NC
         pp_addr_i     : in  slv_31_00;
         pp_data_i     : in  slv_31_00;
         pp_data_o     : out slv_31_00;
         --  interrupt request
         intr_o        : out std_logic;
         --  gpios to pads
         gpio_i        : in  slv_t(NR_GPIOS-1 downto 0);
         gpio_o        : out slv_t(NR_GPIOS-1 downto 0);
         gpio_oena_o   : out slv_t(NR_GPIOS-1 downto 0); -- PAD: 1 in, 0 out
         gpio_pdena_o  : out slv_t(NR_GPIOS-1 downto 0);
         gpio_puena_o  : out slv_t(NR_GPIOS-1 downto 0)
         );
end entity vsgpio;


architecture rtl of vsgpio is

  subtype gp_slv_t is slv_t(NR_GPIOS-1 downto 0);
  constant GPIO_H2L_R : gp_slv_t := (others => '0');

  -- - - - - - - - - - - - - - - - - - - - -
  -- gpio defs    - default, range
  -- - - - - - - - - - - - - - - - - - - - -
  constant GPIO_DIRECTION                      : slv_07_00 := X"00";
  constant GPIO_DIRECTION_OUTPUT_DEFAULT       : std_logic  := '0';

  constant GPIO_INPUT                          : slv_07_00 := X"04";

  constant GPIO_OUTPUT                         : slv_07_00 := X"08";

  constant GPIO_OUTPUT_SET                     : slv_07_00 := X"0C";

  constant GPIO_OUTPUT_CLR                     : slv_07_00 := X"10";

  constant GPIO_INT_MASK                       : slv_07_00 := X"14";
  constant GPIO_INT_MASK_ENABLE_DEFAULT        : std_logic  := '0';

  constant GPIO_INT_SET                        : slv_07_00 := X"18";

  constant GPIO_INT_CLR                        : slv_07_00 := X"1C";

  constant GPIO_INT_STATUS                     : slv_07_00 := X"20";

  constant GPIO_INT_LEVEL                      : slv_07_00 := X"24";
  constant GPIO_INT_LEVEL_ACTIVE_HIGH_DEFAULT  : std_logic  := '0';

  constant GPIO_INT_MODE                       : slv_07_00 := X"28";
  constant GPIO_INT_MODE_EDGE_DEFAULT          : std_logic  := '0';

  -- pull down control
  constant GPIO_PD_ENA                         : slv_07_00 := X"2C";
  constant GPIO_PD_DEFAULT                     : std_logic  := '1'; -- ! enabled

  -- pull up control
  constant GPIO_PU_ENA                         : slv_07_00 := X"30";
  constant GPIO_PU_DEFAULT                     : std_logic  := '0'; -- !disabled


  --  gpio_direction_output (internal)
  signal gpio_direction_output_q           : gp_slv_t;

  signal gpio_output_set_wr_w              : std_logic;
  signal gpio_output_clr_wr_w              : std_logic;
  signal gpio_output_wr_w                  : std_logic;

  signal gpio_int_mask_enable_q            : gp_slv_t;

  signal gpio_int_set_wr_w                 : std_logic;
  signal gpio_int_clr_wr_w                 : std_logic;
  --  gpio_int_level_active_high (internal)
  signal gpio_int_level_active_high_q      : gp_slv_t;
  --  gpio_int_mode_edge (internal)
  signal gpio_int_mode_edge_q              : gp_slv_t;

  signal gpio_pd_ena_q                     : gp_slv_t;
  signal gpio_pu_ena_q                     : gp_slv_t;
    
  signal data_w                            : slv_t(pp_data_o'range);

  signal input_ms                          : gp_slv_t;
  signal input_q                           : gp_slv_t;

  signal gpoutput_q                        : gp_slv_t;
  signal output_next_w                     : gp_slv_t;

  signal intr_q                            : std_logic;

  signal interrupt_raw_q                   : gp_slv_t;
  signal interrupt_raw_w                   : gp_slv_t;

  signal input_last_q                      : gp_slv_t;

  signal active_low_w                      : gp_slv_t;
  signal falling_edge_w                    : gp_slv_t;
  signal level_mask_w                      : gp_slv_t;
  signal edge_mask_w                       : gp_slv_t;

  signal level_active_w                    : gp_slv_t;

  signal edge_detect_w                     : gp_slv_t;
  signal edge_active_w                     : gp_slv_t;

begin

  -- - - - - - - - - - - - - - - - - -
  --  request logic
  -- - - - - - - - - - - - - - - - - -

  --  gpio_direction_output (internal)
  p_gp_wr : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        gpio_direction_output_q <= (others => GPIO_DIRECTION_OUTPUT_DEFAULT);
        gpio_int_mask_enable_q  <= (others => GPIO_INT_MASK_ENABLE_DEFAULT);
         gpio_int_level_active_high_q <=
          (others => GPIO_INT_LEVEL_ACTIVE_HIGH_DEFAULT);
        gpio_int_mode_edge_q    <= (others => GPIO_INT_MODE_EDGE_DEFAULT);
        gpio_pd_ena_q           <= (others => GPIO_PD_DEFAULT);
        gpio_pu_ena_q           <= (others => GPIO_PU_DEFAULT);
     else
        --
         --
        if (pp_wr_i(0) = '1') then
          case pp_addr_i(7 downto 0) is
            when GPIO_DIRECTION =>
              gpio_direction_output_q       <= pp_data_i(GPIO_H2L_R'range);
            when GPIO_INT_MASK =>
              gpio_int_mask_enable_q        <= pp_data_i(GPIO_H2L_R'range);
            when GPIO_INT_LEVEL =>
              gpio_int_level_active_high_q  <= pp_data_i(GPIO_H2L_R'range);
            when GPIO_INT_MODE =>            -- gpio_int_mode_edge (internal)
              gpio_int_mode_edge_q          <= pp_data_i(GPIO_H2L_R'range);
            when GPIO_PD_ENA =>              -- gpio_int_mode_edge (internal)
              gpio_pd_ena_q                 <= pp_data_i(GPIO_H2L_R'range);
            when GPIO_PU_ENA =>              -- gpio_int_mode_edge (internal)
              gpio_pu_ena_q                 <= pp_data_i(GPIO_H2L_R'range);
            when others =>
              null;
          end case;
        end if;
      end if;
    end if;
  end process p_gp_wr;

  gpio_pdena_o <= gpio_pd_ena_q;
  gpio_puena_o <= gpio_pu_ena_q and (not gpio_pd_ena_q);


  -- - - - - - - - - - - - - - - - - -
  --  read mux
  -- - - - - - - - - - - - - - - - - -
  p_c_omux : process ( pp_rd_i, pp_addr_i, gpio_direction_output_q,
                       input_q, gpoutput_q,
                       gpio_int_mask_enable_q, interrupt_raw_q,
                       gpio_int_level_active_high_q, gpio_int_mode_edge_q,
                       gpio_pd_ena_q, gpio_pu_ena_q )
  begin
    --
    data_w <= (others => '0');
    --
    if (pp_rd_i = '1') then
      case pp_addr_i(7 downto 0) is
        when GPIO_DIRECTION =>
          data_w(GPIO_H2L_R'range)  <= gpio_direction_output_q;
        when GPIO_INPUT =>
          data_w(GPIO_H2L_R'range)  <= input_q;
        when GPIO_OUTPUT =>
          data_w(GPIO_H2L_R'range)  <= gpoutput_q;
        when GPIO_INT_MASK =>
          data_w(GPIO_H2L_R'range)  <= gpio_int_mask_enable_q;
        when GPIO_INT_STATUS =>
          data_w(GPIO_H2L_R'range)  <= interrupt_raw_q;
        when GPIO_INT_LEVEL =>
          data_w(GPIO_H2L_R'range)  <= gpio_int_level_active_high_q;
        when GPIO_INT_MODE =>
          data_w(GPIO_H2L_R'range)  <= gpio_int_mode_edge_q;
        when GPIO_PD_ENA =>              -- gpio_int_mode_edge (internal)
          data_w(GPIO_H2L_R'range)  <= gpio_pd_ena_q;
        when GPIO_PU_ENA =>              -- gpio_int_mode_edge (internal)
          data_w(GPIO_H2L_R'range)  <= gpio_pu_ena_q;
        when others  =>
          null;
      end case;
    end if;
  end process p_c_omux;


  pp_data_o <= data_w;



  -- - - - - - - - - - - - - - - - - -
  --  inputs resynced
  -- - - - - - - - - - - - - - - - - -
  --  resync inputs
  p_gpin : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        input_ms <= (NR_GPIOS-1 downto 0 => '0');
        input_q  <= (NR_GPIOS-1 downto 0 => '0');
      else
        input_ms <= gpio_i;
        input_q  <= input_ms;
      end if;
    end if;
  end process p_gpin;


  --  register gpio_output
  gpio_output_wr_w     <= pp_wr_i(0) and eq(pp_addr_i(7 downto 0), GPIO_OUTPUT);
  --  register gpio_output_clr
  gpio_output_clr_wr_w <= pp_wr_i(0) and
                          eq(pp_addr_i(7 downto 0), GPIO_OUTPUT_CLR);

  --  register gpio_output_set
  gpio_output_set_wr_w <= pp_wr_i(0) and
                          eq(pp_addr_i(7 downto 0), GPIO_OUTPUT_SET);

  -- register gpio_int_set
  gpio_int_set_wr_w    <= pp_wr_i(0) and eq(pp_addr_i(7 downto 0),GPIO_INT_SET);

  --  register gpio_int_clr
  gpio_int_clr_wr_w    <= pp_wr_i(0) and
                          eq(pp_addr_i(7 downto 0), GPIO_INT_CLR);
  


  -- - - - - - - - - - - - - - - - - -
  --  outputs
  -- - - - - - - - - - - - - - - - - -
  p_c_gponxt : process ( pp_data_i, gpoutput_q, gpio_output_set_wr_w,
                         gpio_output_clr_wr_w, gpio_output_wr_w)
  begin
    output_next_w      <= gpoutput_q;
    if (gpio_output_set_wr_w = '1') then    -- gpio_output_set_data (external)
        output_next_w  <= gpoutput_q or pp_data_i(GPIO_H2L_R'range);
    elsif (gpio_output_clr_wr_w = '1') then -- gpio_output_clr_data (external)
      output_next_w    <= gpoutput_q and (not pp_data_i(GPIO_H2L_R'range));
    elsif (gpio_output_wr_w = '1') then     -- gpio_output_data     (external)
        output_next_w  <= pp_data_i(GPIO_H2L_R'range);
    end if;
  end process p_c_gponxt;


  p_gpout : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        gpoutput_q  <= (NR_GPIOS-1 downto 0 => '0');
      else
        gpoutput_q  <= output_next_w;
      end if;
    end if;
  end process p_gpout;

  gpio_o <= gpoutput_q;


  -- - - - - - - - - - - - - - - - - -
  --  interrupts
  -- - - - - - - - - - - - - - - - - -


  p_ilast : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        input_last_q  <= (NR_GPIOS-1 downto 0 => '0');
      else
        input_last_q  <= input_q;
      end if;
    end if;
  end process p_ilast;


  active_low_w   <= (not gpio_int_level_active_high_q);
  falling_edge_w <= (not gpio_int_level_active_high_q);
  level_mask_w   <= (not gpio_int_mode_edge_q);
  edge_mask_w    <= gpio_int_mode_edge_q;

  level_active_w <= (input_q xor active_low_w) and level_mask_w;

  edge_detect_w  <= (input_last_q xor input_q);
  edge_active_w  <= (edge_detect_w and(input_q xor falling_edge_w)) and
                    edge_mask_w;


  p_c_intr : process (pp_data_i, interrupt_raw_q, gpio_int_clr_wr_w,
                      level_active_w, edge_active_w, gpio_int_set_wr_w)
    variable interrupt_raw_v : slv_t(interrupt_raw_w'range);
  begin
    interrupt_raw_v    := interrupt_raw_q;
      --  clear (ACK)
    if (gpio_int_clr_wr_w = '1') then
      interrupt_raw_v  := interrupt_raw_v and (not pp_data_i(GPIO_H2L_R'range));
    end if;
    --  new interrupts
    interrupt_raw_v    := interrupt_raw_v or level_active_w or edge_active_w;
    --  set (SW IRQ)
    if (gpio_int_set_wr_w = '1') then -- gpio_int_set_sw_irq (external)
      interrupt_raw_v  := interrupt_raw_v or pp_data_i(GPIO_H2L_R'range);
    end if;
    interrupt_raw_w    <= interrupt_raw_v;
  end process p_c_intr;



  p_intr : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        intr_q          <= '0';
        interrupt_raw_q <= (NR_GPIOS-1 downto 0 => '0');
      else
        if (unsigned(interrupt_raw_q and gpio_int_mask_enable_q) /= 0) then
          intr_q           <= '1';
        else
          intr_q           <= '0';
        end if;
        interrupt_raw_q  <= interrupt_raw_w;
      end if;
    end if;
  end process p_intr;

  intr_o       <= intr_q;
  gpio_oena_o  <= gpio_direction_output_q;

end rtl;
