---------------------------------------------------------------------------------
-- Title      : Custom AHB Timer
-------------------------------------------------------------------------------
-- File       : AHB_timer.vhd
-- Author     : Mir Ali
-- Company    : Microsemi Corporation
-- Device     : SmartFusion 
-- Standard   : Verilog
-------------------------------------------------------------------------------
-- Description: This code creates an high speed Timer with AHB slave wrapper interface
-------------------------------------------------------------------------------
-- Copyright (c) 2011   Microsemi Corporation
--                      All rights reserved.
-------------------------------------------------------------------------------
-- Revisions  : V1.0 
-------------------------------------------------------------------------------

--================================= Start ===================================--
library ieee;
use     ieee.std_logic_1164.all;
use     ieee.std_logic_unsigned."+";
use     ieee.std_logic_unsigned."-";

entity AHB_timer is
    port(
        HCLK            : in  std_logic;                        -- AHB clock
        HRESETn         : in  std_logic;                        -- AHB reset
        HSEL            : in  std_logic;                        -- AHB select
        HADDR           : in  std_logic_vector(19 downto 0);     -- AHB address bus
        HWRITE          : in  std_logic;                        -- AHB write
        HTRANS          : in  std_logic_vector(1 downto 0);     -- AHB tranfer size
        HSIZE           : in  std_logic_vector(2 downto 0);     -- AHB data size
        HREADY          : in  std_logic;                        -- AHB ready in
        HREADYOUT       : out  std_logic;                       -- AHB ready out
        HWDATA          : in  std_logic_vector(31 downto 0);    -- AHB write data
        HRDATA          : out std_logic_vector(31 downto 0);    -- AHB read data
        HRESP           : out std_logic_vector(1 downto 0);                        -- AHB error out
        TIMINT          : out std_logic;                        -- Timer interrupt
        TIMINT_pending  : out std_logic;                        -- Timer interrupt
        
        Test_port       : out std_logic_vector(3 downto 0); 
        Pulse_in        : in  std_logic;                        -- Fbaric IO catching pulse
        FABTIMER_CLK    : in  std_logic;                        -- FABRIC clock
        FAB_RESETn      : in  std_logic                         -- FABRIC reset
    );
end AHB_timer;

architecture synth of AHB_timer is

    component Decrementor24 is
    port(DataA : in std_logic_vector(23 downto 0);
       Sum : out std_logic_vector(23 downto 0));
    end component;

    component count_zero_blk is -- use to calcuate terminal count
    port( count2_0_zero     : out   std_logic;
          count23_3_zero    : out   std_logic;
          FABTIMER_CLK      : in    std_logic;
          Count : in    std_logic_vector(23 downto 0));
    end component;

    component count24 is
    port( Aclr   : in    std_logic;
          Sload  : in    std_logic;
          Clock  : in    std_logic;
          Data   : in    std_logic_vector(23 downto 0);
          Q      : out   std_logic_vector(23 downto 0);
          Enable : in    std_logic);
    end component;

    -------------------------------------------------------------------------------
    -- Timer base address memory map
    constant TIMERLOADA     : std_logic_vector(4 downto 0) := "00000";
    constant TIMERVALUEA    : std_logic_vector(4 downto 0) := "00100";
    constant TIMERCONTROLA  : std_logic_vector(4 downto 0) := "01000";
    constant TIMERPRESCALEA : std_logic_vector(4 downto 0) := "01100";
    constant TIMERINTCLRA   : std_logic_vector(4 downto 0) := "10000";
    constant TIMERTIMINT1   : std_logic_vector(4 downto 0) := "10100";
    constant TIMERTIMINT2   : std_logic_vector(4 downto 0) := "11000";

    -------------------------------------------------------------------------------
    -- Signal declarations
    -------------------------------------------------------------------------------

    signal DataOut          : std_logic_vector(31 downto 0);

    -- Address decoded signals
    signal TimerMode        : std_logic;                            -- Timer operation mode
    signal IntEnable        : std_logic;                            -- Interrupt enable
    signal TimerEn          : std_logic;                            -- Timer enable
    signal LoadEnReg        : std_logic;                            -- Registered load enable
    signal IntClrEn         : std_logic;                            -- Interrupt Clear enable
    signal TimerPre         : std_logic_vector(3 downto 0);         -- Prescale register
    signal Load             : std_logic_vector(23 downto 0);        -- Stores the load value
    
    -- Counter signals
    signal Count            : std_logic_vector(23 downto 0);        -- Current count
    signal ZeroCount        : std_logic_vector(23 downto 0);
    signal CountIsZero      : std_logic;
    signal OneShot          : std_logic;                            -- Asserted when TimerMode = 1
    signal Sum_out 		    : std_logic_vector(23 downto 0);        -- decrementor output
    
    signal Count_Pulse      : std_logic;
    signal new_count_pulse  : std_logic;
    signal prescl_cnt       : std_logic_vector(7 downto 0);
    
    signal DataOut_int      : std_logic_vector(31 downto 0);        -- Read PrdataNext
    signal TIMINT_pulse_int : std_logic_vector(3 downto 0);         -- width of TIMINT_pulse_int depends on HCLK and FAB clk ratio
    signal TIMINT_int       : std_logic;
    signal TIMINT_int_hclk  : std_logic;
    signal TIMINT_pending_int: std_logic;

    -- AHB state decode signals
    signal HADDR_int        : std_logic_vector(4 downto 0);         -- Internal HADDR
    signal HWRITE_int       : std_logic;                            -- Internal HWRITE    
    
    type   HCLK_REQ_ST_Type is (ST0, ST1, ST2, ST3);
    signal HCLKSTATE        : HCLK_REQ_ST_Type;
    
    type   FCLK_ACK_ST_Type is (FST0, FST1);
    signal FCLKSTATE  : FCLK_ACK_ST_Type;

    -- Pulse Input sample and decode signals
    signal Sample    		: std_logic_vector(1 downto 0);
    signal TIMINT1 			: std_logic_vector(23 downto 0);
    signal TIMINT2 			: std_logic_vector(23 downto 0);

    --Handshaking signals
    signal reg_load_en0     : std_logic;                            -- reg load enable0
    signal reg_load_en1     : std_logic;                            -- reg load enable1
    signal reg_load_en2     : std_logic;                            -- reg load enable2
    signal reg_load_ack0    : std_logic;                            -- reg load ack0
    signal reg_load_ack1    : std_logic;                            -- reg load ack1
    signal reg_load_ack2    : std_logic;                            -- reg load ack2

    signal count2_0_zero    : std_logic;
    signal count23_3_zero   : std_logic;

begin

My_Decrementor: Decrementor24 port map(DataA => Count, Sum => Sum_out);

HRESP <= "00";
Test_port(3) <= CountIsZero;
Test_port(2) <= count(23);
Test_port(1) <= not TimerEn;
Test_port(0) <= Pulse_in;
--*****************************************************************************************

    -------------------------------------------------------------------------------
    -- Sample Pulse_in
    p_sampleSeq : process (FAB_RESETn, FABTIMER_CLK)
    begin
        if (FAB_RESETn = '0') then
            Sample <= "00";
        elsif (FABTIMER_CLK'event and FABTIMER_CLK = '1') then
            Sample(0) <= Pulse_in;
            Sample(1) <= Sample(0);
        end if;
    end process;

    -- Load Count Values for Pulse start and end cycle : TIMINT1- Start Pulse counter value,  TIMINT2- End Pulse counter value
    p_CountVal : process (FAB_RESETn, FABTIMER_CLK)
    begin
        if (FAB_RESETn = '0') then
            TIMINT1 <= (others => '1');
            TIMINT2 <= (others => '1');
        elsif (FABTIMER_CLK'event and FABTIMER_CLK = '1') then
            if (Sample = "10") then  
                TIMINT1 <= Load; -- Count+ 1;
            elsif (Sample = "01") then 
                TIMINT2 <= Count;
            end if;
        end if;
    end process;

    -------------------------------------------------------------------------------
    -- Handshaking signal HCLK signals to FABTIMER_CLK
    -------------------------------------------------------------------------------
    HCLK_FCLK_ctrl_sync : process (FAB_RESETn, FABTIMER_CLK)
    begin
        if (FAB_RESETn = '0') then
                reg_load_en1  <= '0';
                reg_load_en2  <= '0';
        elsif (FABTIMER_CLK'event and FABTIMER_CLK = '1') then
                reg_load_en1  <= reg_load_en0;
                reg_load_en2  <= reg_load_en1;
          end if;  
    end process HCLK_FCLK_ctrl_sync;
    -------------------------------------------------------------------------------
    -- Handshaking signal FABTIMER_CLK to HCLK
    -------------------------------------------------------------------------------
    FCLK_HCLK_ctrl_sync : process (HRESETn, HCLK)
    begin
        if (HRESETn = '0') then
                reg_load_ack1  <= '0';
                reg_load_ack2  <= '0';
        elsif (HCLK'event and HCLK = '1') then
                reg_load_ack1  <= reg_load_ack0;
                reg_load_ack2  <= reg_load_ack1;
          end if;  
    end process FCLK_HCLK_ctrl_sync;


    -------------------------------------------------------------------------------
    -- Load Control register, Prescale register, TimerLoad register and Interrupt Clear register from AHB bus
    -------------------------------------------------------------------------------
    HCLK_tx_state_seq : process (HRESETn, HCLK)
    begin
        if (HRESETn = '0') then
                HCLKSTATE     <= ST0;
                HADDR_int   <= "00000";
                HWRITE_int  <= '0';
                reg_load_en0  <= '0';
                HREADYOUT  <= '1';
        elsif (HCLK'event and HCLK = '1') then
            case HCLKSTATE is
                when ST0 =>
                    if (HSEL = '1' and HSIZE = "010" and HREADY = '1' and HTRANS(1) = '1') then          
                        HADDR_int  <= HADDR(4 downto 0);
                        HWRITE_int <= HWRITE;
                        HCLKSTATE  <= ST1;
                        HREADYOUT  <= '0';
                        reg_load_en0  <= '1';
                    end if;
                
                when ST1 =>
                    if (reg_load_ack2  = '1') then
                        HCLKSTATE      <= ST2;
                        reg_load_en0  <= '0';
                    end if;  
                
                when ST2 =>
                    if (reg_load_ack2  = '0') then
                        HCLKSTATE     <= ST3;
                        HREADYOUT  <= '1';
                    end if;
                
                when ST3 =>
                        HCLKSTATE     <= ST0;
                
                when others =>
                        null;
            end case;
        end if;
    end process HCLK_tx_state_seq;

    FAB_rcvr_state_seq : process (FAB_RESETn, FABTIMER_CLK)
    begin
        if (FAB_RESETn = '0') then
                TimerMode  <= '0';
                IntEnable  <= '0';
                TimerEn    <= '0';
                TimerPre    <= "0000";
                Load        <= (others => '0');
                LoadEnReg  <= '0';
                IntClrEn   <= '0';
                DataOut <= (others => '0');
                FCLKSTATE     <= FST0;
                reg_load_ack0 <= '0';
        elsif (FABTIMER_CLK'event and FABTIMER_CLK = '1') then
            case FCLKSTATE is
                when FST0 =>
                    if (reg_load_en2 = '1') then          
                        if (HWRITE_int = '1') then
                            case HADDR_int is
                                when TIMERCONTROLA =>
                                    TimerMode  <= HWDATA (2);
                                    IntEnable  <= HWDATA (1);
                                    TimerEn    <= HWDATA (0);
                                    LoadEnReg  <= '0';
                                    IntClrEn   <= '0';
                                when TIMERPRESCALEA =>
                                    TimerPre   <= HWDATA (3 downto 0);
                                    LoadEnReg  <= '0';
                                    IntClrEn   <= '0';
                                when TIMERLOADA =>
                                    Load       <= HWDATA(23 downto 0);
                                    LoadEnReg  <= '1';
                                    IntClrEn   <= '0';
                                when TIMERINTCLRA =>
                                    LoadEnReg  <= '0';
                                    IntClrEn   <= HWDATA(0);                           
                                when others =>
                                    null;
                            end case;
                        else
                            case HADDR_int is
                                when TIMERLOADA =>
                                    DataOut(31 downto 0) <= "00000000" & Load;
                                when TIMERVALUEA =>
                                    DataOut(31 downto 0) <= "00000000" & Count;
                                when TIMERCONTROLA =>
                                    DataOut(31 downto 0) <= "00000000000000000000000000000"&TimerMode & IntEnable & TimerEn;
                                    when TIMERPRESCALEA =>
                                DataOut(31 downto 0) <= "0000000000000000000000000000"& TimerPre;
                                when TIMERINTCLRA =>
                                    DataOut(31 downto 0) <= "0000000000000000000000000000000"& IntClrEn;                            
                                when TIMERTIMINT1 =>
                                    DataOut(31 downto 0) <= "00000000" & TIMINT1;                            
                                when TIMERTIMINT2 =>
                                    DataOut(31 downto 0) <= "00000000" & TIMINT2;                            
                                when others =>
                                    DataOut(31 downto 0) <= (others => '0');
                            end case;
                        end if;

                    FCLKSTATE      <= FST1;
                    reg_load_ack0 <= '1';
                    end if;
                
                when FST1 =>
                    if (reg_load_en2 = '0') then
                        FCLKSTATE      <= FST0;
                        reg_load_ack0 <= '0';
                        LoadEnReg  <= '0';
                        IntClrEn   <= '0';
                    end if;

                
                when others =>
                        null;
            end case;
        end if;
    end process FAB_rcvr_state_seq;        


    OneShot <= '1' when (TimerMode = '1') else '0';


    -------------------------------------------------------------------------------
    -- Clock prescaler
    -- Determines effective clock rate for the counter based on FAB CLK:
    -- 0001 = no division (default)
    -- 0001 = divide by 2
    -- 0010 = divide by 4
    -- 0011 = divide by 8
    -- 0100 = divide by 16
    -- 0101 = divide by 32
    -- 0110 = divide by 64
    -- 0111 = divide by 128
    -- 1000 = divide by 256
    -------------------------------------------------------------------------------
    p_prescl_cnt : process (FAB_RESETn, FABTIMER_CLK)
    begin
        if (FAB_RESETn = '0') then
            prescl_cnt <= (others => '0');
        elsif (FABTIMER_CLK'event and FABTIMER_CLK = '1') then
            if (LoadEnReg = '1' ) then
                prescl_cnt<= (others => '0');
            else
                prescl_cnt <= prescl_cnt + '1';
            end if;
        end if;
    end process p_prescl_cnt;

    p_next_count_pulse : process (prescl_cnt, TimerPre)
    begin
        Count_Pulse <= '0';

        case TimerPre is
            when "0000" =>  
                                Count_Pulse <= '1';
            when "0001" =>  if (prescl_cnt(0) = '1') then
                                Count_Pulse <= '1';
                            end if;
            when "0010" =>  if (prescl_cnt(1 downto 0) = "11") then
                                Count_Pulse <= '1';
                            end if;
            when "0011" =>  if (prescl_cnt(2 downto 0) = "111") then
                                Count_Pulse <= '1';
                            end if;
            when "0100" =>  if (prescl_cnt(3 downto 0) = "1111") then
                                Count_Pulse <= '1';
                            end if;
            when "0101" =>  if (prescl_cnt(4 downto 0) = "11111") then
                                Count_Pulse <= '1';
                            end if;
            when "0110" =>  if (prescl_cnt(5 downto 0) = "111111") then
                                Count_Pulse <= '1';
                            end if;
            when "0111" =>  if (prescl_cnt(6 downto 0) = "1111111") then
                                Count_Pulse <= '1';
                            end if;
            when "1000" =>  if (prescl_cnt(7 downto 0) = "11111111") then
                                Count_Pulse <= '1';
                            end if;
            when others =>  if (prescl_cnt(7 downto 0) = "11111111") then
                                Count_Pulse <= '1';
                            end if;
        end case;
    end process p_next_count_pulse;

    p_new_count_pulse : process (FAB_RESETn, FABTIMER_CLK)
    begin
        if (FAB_RESETn = '0') then
            new_count_pulse <= '0';
        elsif (FABTIMER_CLK'event and FABTIMER_CLK = '1') then
            new_count_pulse <= Count_Pulse;
        end if;
    end process p_new_count_pulse;

    -------------------------------------------------------------------------------
    -- Counter register
    -- This is the main counter, it start couting mainly when TimerEn2 is high
    -- When counter reaches zero, it is loaded with Load value for continuous mode
    -------------------------------------------------------------------------------
    p_CountSeq : process (FAB_RESETn, FABTIMER_CLK)
    begin
        if (FAB_RESETn = '0') then
            Count <= (others => '1');
        elsif (FABTIMER_CLK'event and FABTIMER_CLK = '1') then
            if (LoadEnReg = '1' ) then 
                Count <= Load;
            else
               if ((TimerEn = '1' and new_count_pulse = '1') or (Pulse_in = '0')) then --(TimerEn = '1' and new_count_pulse = '1')
                    if (count2_0_zero = '1' and count23_3_zero ='1' ) and (OneShot = '1') then --(CountIsZero = '1') then
                        Count <= Count;
                    elsif (count2_0_zero = '1' and count23_3_zero ='1' ) and (OneShot = '0') then
                        Count <= Load;
                    else
                        Count <= Sum_out;       -- use decrementor 
                        --Count <= Count - '1'; -- Standard counter
                    end if;
                end if;
            end if;
        end if;
    end process p_CountSeq;

    --ZeroCount <= (others => '0');
    CountIsZero <=  '1' when (count2_0_zero = '1' and count23_3_zero ='1') else '0';

    count_zero_blk_instance: count_zero_blk
    port map ( count2_0_zero    => count2_0_zero,
               count23_3_zero   => count23_3_zero,
               FABTIMER_CLK     => FABTIMER_CLK,
               Count            => Count);

    -------------------------------------------------------------------------------
    -- Generating TIMINT Interrupt
    -- Strect interrupt pulse using xor logic
    -------------------------------------------------------------------------------
    TIMINT_pulse_int(0) <=  '1' when ((CountIsZero = '1') and IntEnable = '1') else '0';

    p_strecth_TIMINT_pulse : process (FAB_RESETn, FABTIMER_CLK)
    begin
        if (FAB_RESETn = '0') then
            TIMINT_pulse_int(1)  <= '0';
            TIMINT_pulse_int(2)  <= '0';
            TIMINT_pulse_int(3)  <= '0';

        elsif (FABTIMER_CLK'event and FABTIMER_CLK = '1') then
            TIMINT_pulse_int(1)  <= TIMINT_pulse_int(0);
            TIMINT_pulse_int(2)  <= TIMINT_pulse_int(1);
            TIMINT_pulse_int(3)  <= TIMINT_pulse_int(2);

        end if;
    end process p_strecth_TIMINT_pulse;

    TIMINT_int  <= TIMINT_pulse_int(0) xor TIMINT_pulse_int(1) xor TIMINT_pulse_int(2) xor TIMINT_pulse_int(3) ;

    p_TIMINT_int_hclk : process (HRESETn, HCLK)
    begin
        if (HRESETn = '0') then
            TIMINT_int_hclk <= '0';
        elsif (HCLK'event and HCLK = '1') then
            TIMINT_int_hclk <= TIMINT_int;
        end if;
    end process p_TIMINT_int_hclk;

    TIMINT <= TIMINT_int_hclk;

    -------------------------------------------------------------------------------
    -- Generating TIMINT_pending Interrupt
    -------------------------------------------------------------------------------
    p_TIMINT_pending_int : process (FAB_RESETn, TIMINT_int_hclk,IntClrEn)
    begin
        if (FAB_RESETn = '0') or (IntClrEn = '1') then
                TIMINT_pending_int <= '0';
        elsif (TIMINT_int_hclk'event and TIMINT_int_hclk = '1') then
                TIMINT_pending_int <= '1';
        end if;
    end process p_TIMINT_pending_int;

    TIMINT_pending <= TIMINT_pending_int ;

        

    -- Generate HRDATA on falling edge
    p_HRDATA : process (HRESETn, HCLK)
    begin
        if (HRESETn = '0') then
            DataOut_int <= (others => '0');
        elsif (HCLK'event and HCLK = '1') then
                DataOut_int <= DataOut;
        end if;
    end process p_HRDATA;

HRDATA <= DataOut_int;

end synth;

--================================= End ===================================--