-- 32bit prescaled Timer
--
-- Original Author unknown.
-- Updated for ZPU SoC use: Philip Smart, 2019.
--
-- The FreeBSD license
-- 
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions
-- are met:
-- 
-- 1. Redistributions of source code must retain the above copyright
--    notice, this list of conditions and the following disclaimer.
-- 2. Redistributions in binary form must reproduce the above
--    copyright notice, this list of conditions and the following
--    disclaimer in the documentation and/or other materials
--    provided with the distribution.
-- 
-- THIS SOFTWARE IS PROVIDED BY THE ZPU PROJECT ``AS IS'' AND ANY
-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
-- PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- ZPU PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
-- INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
-- STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-- ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-- 
-- The views and conclusions contained in the software and documentation
-- are those of the authors and should not be interpreted as representing
-- official policies, either expressed or implied, of the ZPU Project.
library ieee;
use ieee.std_logic_1164.all;
use IEEE.numeric_std.ALL;

library work;

-- Timer controller module

entity timer_controller is
  generic(
        prescale    : integer := 1;                     -- Prescale incoming clock
        timers      : integer := 0                      -- This is a power of 2, so zero means 1 counter, 4 means 16 counters...
    );
  port (
        clk         : in  std_logic;
        reset       : in  std_logic;                    -- active low

        reg_addr_in : in  std_logic_vector(7 downto 0); -- from host CPU
        reg_data_in : in  std_logic_vector(31 downto 0);
        reg_rw      : in  std_logic;
        reg_req     : in  std_logic;

        ticks       : out std_logic_vector(2**timers-1 downto 0)
    );
end entity;
    
architecture rtl of timer_controller is
    constant prescale_adj    : integer := prescale-1;
    signal prescale_counter  : unsigned(15 downto 0);
    signal prescaled_tick    : std_logic;
    type timer_counters is array (2**timers-1 downto 0) of unsigned(31 downto 0);
    signal timer_counter     : timer_counters;
    signal timer_limit       : timer_counters;
    signal timer_enabled     : std_logic_vector(2**timers-1 downto 0);
    signal timer_index       : unsigned(7 downto 0);
begin

    -- Prescaled tick
    process(clk, reset)
    begin
        if reset='0' then
            prescale_counter      <=(others => '0');
        elsif rising_edge(clk) then
            prescaled_tick        <='0';
            prescale_counter      <=prescale_counter-1;
            if prescale_counter=X"00" then
                prescaled_tick    <='1';
                prescale_counter  <=to_unsigned(prescale_adj,16);
            end if;
        end if;
    end process;

    -- The timers proper;
    process(clk,reset)
    begin
        if reset='0' then
            for I in 0 to (2**timers-1) loop
                timer_counter(I)            <= (others => '0');
            end loop;
        elsif rising_edge(clk) then
            ticks                           <= (others => '0');
            if prescaled_tick='1' then
                for I in 0 to (2**timers-1) loop
                    if timer_enabled(I)='1' then
                        timer_counter(I)    <=timer_counter(I)-1;
                        if timer_counter(I)=X"00000000" then
                            timer_counter(I)<=timer_limit(I);
                            ticks(I)        <='1';
                        end if;
                    end if;
                end loop;
            end if;
        end if;
    end process;

    -- Handle CPU access to hardware registers
    process(clk,reset)
    begin
        if reset='0' then
            timer_enabled                   <= (others => '0');
        elsif rising_edge(clk) then
            if reg_req='1' and reg_rw='0' then -- Write access
                case reg_addr_in is
                    when X"00" =>
                        timer_enabled       <= reg_data_in(2**timers-1 downto 0);
                    when X"04" =>
                        timer_index         <= unsigned(reg_data_in(7 downto 0));
                    when X"08" =>
                        timer_limit(to_integer(timer_index(timers downto 0)))<= unsigned(reg_data_in(31 downto 0));                    
                    when others =>
                        null;
                end case;
            end if;
        end if;
    end process;
        
end architecture;
