-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- 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:  system tick timer
-- - - - - - - - - - - - - - - - - - - - -
entity vstimer is
  generic ( TIMER0_64B : std_logic := '1' );  -- 32 / 64 bit timer
  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;
         pp_rd_i    : in  std_logic;
         pp_wr_i    : in  slv_03_00;          --! 3:1 NC
         pp_data_o  : out slv_31_00;
         --
         mtime_o    : out slv_63_00;
         intr_o     : out std_logic
         );
end entity vstimer;


architecture rtl of vstimer is

  -- - - - - - - - - - - - - - - - - - - - -
  --  timer defs   - default, bit, range
  -- - - - - - - - - - - - - - - - - - - - -
  constant TIMER0_CTRL            : slv_07_00 := X"08";         -- 0x008
  constant TIMER0_CTRL_IRQ_B      : natural   := 1;
  constant TIMER0_CTRL_ENABLE_B   : natural   := 2;
  constant TIMER0_CTRL_64B_ENA_B  : natural   := 7;

  constant TIMER0_CMP             : slv_07_00 := X"0C";         -- 0x00C
  constant TIMER0_CMPH            : slv_07_00 := X"2C";         -- 0x02C

  constant TIMER0_VAL             : slv_07_00 := X"10";         -- 0x010
  constant TIMER0_VALH            : slv_07_00 := X"20";         -- 0x020

  constant TIMER1_CTRL            : slv_07_00 := X"14";         -- 0x014
  constant TIMER1_CTRL_IRQ_B      : natural   := 1;
  constant TIMER1_CTRL_ENABLE_B   : natural   := 2;

  constant TIMER1_CMP             : slv_07_00 := X"18";         -- 0x018

  constant TIMER1_VAL             : slv_07_00 := X"1C";         -- 0x01C

  signal timer0_ctrl_irqena_q     : std_logic;
  signal timer0_ctrl_ena_q        : std_logic;

  signal timer0_cmp_value_q       : slv_31_00;

  signal timer1_ctrl_irqena_q     : std_logic;
  signal timer1_ctrl_ena_q        : std_logic;

  signal timer1_cmp_value_q       : slv_31_00;

  signal data_w                   : slv_31_00;
  signal timer0_value_q           : slv_31_00;
  signal timer0_irq_w             : std_logic;
  signal timer1_value_q           : slv_31_00;
  signal timer1_irq_w             : std_logic;
  signal intr_q                   : std_logic;

  signal timer0_64b_ena_q         : std_logic;
  signal timer0_high_q            : slv_31_00;                  -- NA if 32bit
  signal timer0_cmp_high_q        : slv_31_00;                  -- NA if 32bit
  signal timer0_low_max_w         : std_logic;                  -- NA if 32bit
  signal timer0_inc_in_w          : slv_31_00;
  signal timer0_inc_out_w         : slv_31_00;

begin

  -- - - - - - - - - - - - - - - - - - - - -
  --  read mux
  -- - - - - - - - - - - - - - - - - - - - -
  p_c_omux : process ( pp_addr_i, timer0_ctrl_irqena_q,
                       timer0_ctrl_ena_q, timer0_cmp_value_q,
                       timer0_value_q, timer1_ctrl_irqena_q,
                       timer1_ctrl_ena_q, timer1_cmp_value_q,
                       timer1_value_q,
                       timer0_high_q, timer0_cmp_high_q, timer0_64b_ena_q)
  begin
    --
    data_w <= X"00000000";
    --
    case pp_addr_i(7 downto 0) is
      when TIMER0_CTRL   =>
        data_w(TIMER0_CTRL_IRQ_B)       <= timer0_ctrl_irqena_q;
        data_w(TIMER0_CTRL_ENABLE_B)    <= timer0_ctrl_ena_q;
        if (TIMER0_64B = '1') then
          data_w(TIMER0_CTRL_64B_ENA_B) <= timer0_64b_ena_q;
        end if;
      when TIMER0_CMP    =>
        data_w                          <= timer0_cmp_value_q;
      when TIMER0_CMPH   =>
        if (TIMER0_64B = '1') then
          data_w                        <= timer0_cmp_high_q;
        end if;
      when TIMER0_VAL    =>
        data_w                          <= timer0_value_q;
      when TIMER0_VALH   =>
        if (TIMER0_64B = '1') then
          data_w                        <= timer0_high_q;
        end if;
      when TIMER1_CTRL   =>
        data_w(TIMER1_CTRL_IRQ_B)       <= timer1_ctrl_irqena_q;
        data_w(TIMER1_CTRL_ENABLE_B)    <= timer1_ctrl_ena_q;
      when TIMER1_CMP    =>
        data_w                          <= timer1_cmp_value_q;
      when TIMER1_VAL    =>
        data_w                          <= timer1_value_q;
      when others =>
        data_w <= X"00000000";
    end case;
  end process p_c_omux;


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



  --  timer_ctrl0_interrupt (internal)
  --  timer_ctrl0_enable (internal)
  --  timer_cmp0_value (internal)
  --  timer_ctrl1_interrupt (internal)
  --  timer_ctrl1_enable (internal)
  --  timer_cmp1_value (internal)
  p_tctrl0_en : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        timer0_ctrl_irqena_q  <= '0';
        timer0_ctrl_ena_q     <= '0';
        timer0_cmp_value_q    <= X"00000000";
        timer1_ctrl_irqena_q  <= '0';
        timer1_ctrl_ena_q     <= '0';
        timer1_cmp_value_q    <= X"00000000";
        timer0_64b_ena_q      <= '0';
      else
        if (pp_wr_i(0) = '1') then
          case pp_addr_i(7 downto 0) is
            when TIMER0_CTRL =>
              timer0_ctrl_ena_q     <= pp_data_i(TIMER0_CTRL_ENABLE_B);
              timer0_ctrl_irqena_q  <= pp_data_i(TIMER0_CTRL_IRQ_B);
              if (TIMER0_64B = '1') then
                 timer0_64b_ena_q   <= pp_data_i(TIMER0_CTRL_64B_ENA_B);
              end if;
            when TIMER0_CMP =>
              timer0_cmp_value_q    <= pp_data_i;
            when TIMER1_CTRL =>
              timer1_ctrl_irqena_q  <= pp_data_i(TIMER1_CTRL_IRQ_B);
              timer1_ctrl_ena_q     <= pp_data_i(TIMER1_CTRL_ENABLE_B);
            when TIMER1_CMP =>
              timer1_cmp_value_q    <= pp_data_i;
            when others     =>
              null;
          end case;
        end if;
      end if;
    end if;
  end process p_tctrl0_en;




  g_tmr0_64 : if ( TIMER0_64B = '1' ) generate
  begin
    p_tmr0h : process (clk_i)
    begin
      if ( rising_edge(clk_i) ) then
        if (xrst_i = '0') then
          timer0_high_q        <= X"00000000";
          timer0_cmp_high_q    <= X"00000000";
        else
          if (timer0_low_max_w = '1' and timer0_64b_ena_q = '1') then
            timer0_high_q      <= timer0_inc_out_w;
          end if;
          if ( pp_wr_i(0) = '1' and pp_addr_i(7 downto 0) = TIMER0_VALH and
               TIMER0_64B = '1') then
            timer0_high_q      <= pp_data_i;
          end if;
          if ( pp_wr_i(0) = '1' and pp_addr_i(7 downto 0) = TIMER0_CMPH and
               TIMER0_64B = '1') then
            timer0_CMP_high_q  <= pp_data_i;
          end if;
        end if;
      end if;
    end process p_tmr0h;
    --
    timer0_low_max_w  <= is_zero(not(timer0_value_q));
    --
  end generate g_tmr0_64;



  g_tmr0_32 : if ( TIMER0_64B /= '1' ) generate
  begin
    timer0_high_q      <= X"00000000";                          -- NA if 32bit
    timer0_cmp_high_q  <= X"00000000";                          -- NA if 32bit
    timer0_low_max_w   <= '0';                                  -- NA if 32bit
  end generate g_tmr0_32;


  mtime_o          <= timer0_high_q & timer0_value_q;


  timer0_inc_in_w  <= timer0_high_q when (timer0_low_max_w = '1') else
                      timer0_value_q;
  timer0_inc_out_w <= inc1u(timer0_inc_in_w);



  -- - - - - - - - - - - - - - - - - - - - -
  --  timer0, timer1
  -- - - - - - - - - - - - - - - - - - - - -
  p_tmr : process (clk_i)
  begin
    if ( rising_edge(clk_i) ) then
      if (xrst_i = '0') then
        timer0_value_q      <= X"00000000";
        timer1_value_q      <= X"00000000";
      else
        if (timer0_ctrl_ena_q = '1') then
          timer0_value_q    <= timer0_inc_out_w;
          if (timer0_low_max_w = '1') then
            timer0_value_q  <= X"00000000";
          end if;
        end if;
        if (timer1_ctrl_ena_q = '1') then
          timer1_value_q    <= inc1u(timer1_value_q);
        end if;
        --
        if (pp_wr_i(0) = '1') then
          case pp_addr_i(7 downto 0) is
            when TIMER0_VAL  =>  timer0_value_q  <= pp_data_i;
            when TIMER1_VAL  =>  timer1_value_q  <= pp_data_i;
            when others      =>  null;
          end case;
        end if;
      end if;
    end if;
  end process p_tmr;


  timer0_irq_w <= eq(timer0_value_q, timer0_cmp_value_q) and
                  timer0_ctrl_irqena_q                   and
                  timer0_ctrl_ena_q                      and
                  ( eq(timer0_high_q, timer0_cmp_high_q) or
                    not(timer0_64b_ena_q) );


  timer1_irq_w <= eq(timer1_value_q, timer1_cmp_value_q) and
                  timer1_ctrl_irqena_q                   and
                  timer1_ctrl_ena_q;


   -- - - - - - - - - - - - - - - - - - - - -
  --  IRQ output
  -- - - - - - - - - - - - - - - - - - - - -
  p_irq : process (clk_i)
  begin
    if ( rising_edge(clk_i) ) then
      if (xrst_i = '0') then
        intr_q <= '0';
      else
        intr_q <= timer0_irq_w or timer1_irq_w;
      end if;
    end if;
  end process p_irq;


  intr_o <= intr_q;


 end rtl;
