library IEEE;
use IEEE.std_logic_1164.all;  
use IEEE.numeric_std.all; 

entity timer is
   port 
   (
      clk1x                : in  std_logic;
      ce                   : in  std_logic;
      reset                : in  std_logic;
      
      error                : out std_logic;
      
      dotclock             : in  std_logic;
      hblank               : in  std_logic;
      vblank               : in  std_logic;
      
      irqRequest0          : out std_logic := '0';  
      irqRequest1          : out std_logic := '0';  
      irqRequest2          : out std_logic := '0';  
      
      bus_addr             : in  unsigned(5 downto 0); 
      bus_dataWrite        : in  std_logic_vector(31 downto 0);
      bus_read             : in  std_logic;
      bus_write            : in  std_logic;
      bus_dataRead         : out std_logic_vector(31 downto 0);
      
-- synthesis translate_off
      export_t_current0    : out unsigned(15 downto 0);
      export_t_current1    : out unsigned(15 downto 0);
      export_t_current2    : out unsigned(15 downto 0);
-- synthesis translate_on
      
      loading_savestate    : in  std_logic;
      SS_reset             : in  std_logic;
      SS_DataWrite         : in  std_logic_vector(31 downto 0);
      SS_Adr               : in  unsigned(3 downto 0);
      SS_wren              : in  std_logic;
      SS_rden              : in  std_logic;
      SS_DataRead          : out std_logic_vector(31 downto 0)
   );
end entity;

architecture arch of timer is

   type timerRecord is record
      T_CURRENT   : unsigned(15 downto 0);
      T_MODE      : unsigned(15 downto 0);
      T_TARGET    : unsigned(15 downto 0);
      irqDone     : std_logic;
      blockNext   : std_logic;
   end record;
  
   type ttimerArray is array (0 to 2) of timerRecord;
   signal timerArray : ttimerArray := (others => ((others => '0'), (others => '0'), (others => '0'), '0', '0'));
  
   signal timer2_subcount : unsigned(2 downto 0);
   signal hblank_1        : std_logic;
   signal vblank_1        : std_logic;
   signal dotclock_1      : std_logic;
   
   -- savestates
   type t_ssarray is array(0 to 15) of std_logic_vector(31 downto 0); 
   signal ss_out : t_ssarray := (others => (others => '0'));  
   
begin 

   irqRequest0 <= not timerArray(0).T_MODE(10);
   irqRequest1 <= not timerArray(1).T_MODE(10);
   irqRequest2 <= not timerArray(2).T_MODE(10);
   
-- synthesis translate_off
   export_t_current0 <= timerArray(0).T_CURRENT;
   export_t_current1 <= timerArray(1).T_CURRENT;
   export_t_current2 <= timerArray(2).T_CURRENT;
-- synthesis translate_on
   
   ss_out(0)(15 downto 0) <= std_logic_vector(timerArray(0).T_CURRENT);
   ss_out(3)(15 downto 0) <= std_logic_vector(timerArray(0).T_MODE);   
   ss_out(6)(15 downto 0) <= std_logic_vector(timerArray(0).T_TARGET); 
   ss_out(9)(11)          <= timerArray(0).irqDone; 
   ss_out(1)(15 downto 0) <= std_logic_vector(timerArray(1).T_CURRENT);
   ss_out(4)(15 downto 0) <= std_logic_vector(timerArray(1).T_MODE);   
   ss_out(7)(15 downto 0) <= std_logic_vector(timerArray(1).T_TARGET); 
   ss_out(9)(12)          <= timerArray(1).irqDone;  
   ss_out(2)(15 downto 0) <= std_logic_vector(timerArray(2).T_CURRENT);
   ss_out(5)(15 downto 0) <= std_logic_vector(timerArray(2).T_MODE);   
   ss_out(8)(15 downto 0) <= std_logic_vector(timerArray(2).T_TARGET); 
   ss_out(9)(13)          <= timerArray(2).irqDone;  
   ss_out(9)(2 downto 0)  <= std_logic_vector(timer2_subcount);        

   process (clk1x)
      variable channel  : integer range 0 to 3;
      variable newTick  : std_logic_vector(2 downto 0);
      variable newIRQ   : std_logic;
   begin
      if rising_edge(clk1x) then
      
         error <= '0';
      
         if (reset = '1') then
         
            if (loading_savestate = '0') then
               timerArray(0).T_CURRENT <= (others => '0');
               timerArray(0).T_MODE    <= x"0400";
               timerArray(0).T_TARGET  <= (others => '0');
               timerArray(0).irqDone   <= '0';
               
               timerArray(1).T_CURRENT <= (others => '0');
               timerArray(1).T_MODE    <= x"0400";
               timerArray(1).T_TARGET  <= (others => '0');
               timerArray(1).irqDone   <= '0';
               
               timerArray(2).T_CURRENT <= (others => '0');
               timerArray(2).T_MODE    <= x"0400";
               timerArray(2).T_TARGET  <= (others => '0');
               timerArray(2).irqDone   <= '0';
               
               timer2_subcount <= (others => '0');
            end if;
         
            for i in 0 to 2 loop
               timerArray(i).blockNext <= '0';
            end loop;
         
            hblank_1        <= hblank;
            vblank_1        <= vblank;

         elsif (SS_wren = '1') then

            if (to_integer(SS_Adr) = 0) then timerArray(0).T_CURRENT <= unsigned(SS_DataWrite(15 downto 0)); end if;
            if (to_integer(SS_Adr) = 3) then timerArray(0).T_MODE    <= unsigned(SS_DataWrite(15 downto 0)); end if;
            if (to_integer(SS_Adr) = 6) then timerArray(0).T_TARGET  <= unsigned(SS_DataWrite(15 downto 0)); end if;
            if (to_integer(SS_Adr) = 9) then timerArray(0).irqDone   <= SS_DataWrite(11);                    end if;
                                                                                                       
            if (to_integer(SS_Adr) = 1) then timerArray(1).T_CURRENT <= unsigned(SS_DataWrite(15 downto 0)); end if;
            if (to_integer(SS_Adr) = 4) then timerArray(1).T_MODE    <= unsigned(SS_DataWrite(15 downto 0)); end if;
            if (to_integer(SS_Adr) = 7) then timerArray(1).T_TARGET  <= unsigned(SS_DataWrite(15 downto 0)); end if;
            if (to_integer(SS_Adr) = 9) then timerArray(1).irqDone   <= SS_DataWrite(12);                    end if;
                                                                                                   
            if (to_integer(SS_Adr) = 2) then timerArray(2).T_CURRENT <= unsigned(SS_DataWrite(15 downto 0)); end if;
            if (to_integer(SS_Adr) = 5) then timerArray(2).T_MODE    <= unsigned(SS_DataWrite(15 downto 0)); end if;
            if (to_integer(SS_Adr) = 8) then timerArray(2).T_TARGET  <= unsigned(SS_DataWrite(15 downto 0)); end if;
            if (to_integer(SS_Adr) = 9) then timerArray(2).irqDone   <= SS_DataWrite(13);                    end if;
                                                                                                    
            if (to_integer(SS_Adr) = 9) then timer2_subcount <= unsigned(SS_DataWrite(2 downto 0));          end if;

         elsif (ce = '1') then
         
            timer2_subcount <= timer2_subcount + 1;
            hblank_1        <= hblank;
            vblank_1        <= vblank;
            dotclock_1      <= dotclock;
            
            for i in 0 to 2 loop
               if (timerArray(i).T_MODE(7) = '0') then -- not toggle mode -> reset irq
                  timerArray(i).T_MODE(10) <= '1';
               end if;
            end loop;
         
            -- check for new ticks
            newTick := "000";
            if (timerArray(0).T_MODE(8) = '1') then
               newTick(0) := dotclock and (not dotclock_1);
            else
               newTick(0) := '1';
            end if;
            
            if (timerArray(1).T_MODE(8) = '1') then
               if (hblank_1 = '0' and hblank = '1') then  -- todo: correct hblank timer tick position to be found
                  newTick(1) := '1';
               end if;
            else
               newTick(1) := '1';
            end if;
            
            if (timerArray(2).T_MODE(9) = '1') then
               if (timer2_subcount = "111") then
                  newTick(2) := '1';
               end if;
            else
               newTick(2) := '1';
            end if;
            
            
            -- filter ticks with sync mode
            if (timerArray(0).T_MODE(0) = '1') then
               case (timerArray(0).T_MODE(2 downto 1)) is
                  when "00" => if (hblank = '1') then newTick(0) := '0'; end if;
                  when "10" => if (hblank = '0') then newTick(0) := '0'; end if;
                  when "11" => newTick(0) := '0';
                  when others => null;
               end case;
            end if;
            
            if (timerArray(1).T_MODE(0) = '1') then
               case (timerArray(1).T_MODE(2 downto 1)) is
                  when "00" => if (vblank = '1') then newTick(1) := '0'; end if;
                  when "10" => if (vblank = '0') then newTick(1) := '0'; end if;
                  when "11" => newTick(1) := '0';
                  when others => null;
               end case;
            end if;
            
            if (timerArray(2).T_MODE(0) = '1') then
               case (timerArray(2).T_MODE(2 downto 1)) is
                  when "00" => newTick(2) := '0';
                  when "11" => newTick(2) := '0';
                  when others => null;
               end case;
            end if;
            
            -- apply ticks
            for i in 0 to 2 loop

               timerArray(i).blockNext <= '0';
               
               if (newTick(i) = '1' and timerArray(i).blockNext = '0') then
               
                  timerArray(i).T_CURRENT <= timerArray(i).T_CURRENT + 1;
                  newIRQ   := '0';
                  
                  if (timerArray(i).T_CURRENT = timerArray(i).T_TARGET) then
                     timerArray(i).T_MODE(11) <= '1';
                     if (timerArray(i).T_MODE(4) = '1') then
                        newIRQ := '1';
                     end if;
                     if (timerArray(i).T_MODE(3) = '1') then
                        timerArray(i).T_CURRENT <= (others => '0');
                        timerArray(i).blockNext <= '1';
                     end if;
                  end if;  

                  if (timerArray(i).T_CURRENT = x"FFFF") then
                     timerArray(i).T_MODE(12) <= '1';
                     if (timerArray(i).T_MODE(5) = '1') then
                        newIRQ := '1';
                     end if;
                  end if;
                  
                  if (timerArray(i).irqDone = '1' and timerArray(i).T_MODE(6) = '0') then
                     newIRQ := '0';
                  end if;
                  
                  if (newIRQ = '1') then
                     timerArray(i).irqDone <= '1';
                     if (timerArray(i).T_MODE(7) = '1') then -- toggle mode
                        timerArray(i).T_MODE(10) <= not timerArray(i).T_MODE(10);
                     else
                        timerArray(i).T_MODE(10) <= '0';
                     end if;
                  end if;

               end if;
            end loop;
            
            -- apply resets
            if (timerArray(0).T_MODE(0) = '1') then
               if (timerArray(0).T_MODE(2 downto 1) = "01" or timerArray(0).T_MODE(2 downto 1) = "10") then
                  if (hblank_1 = '1' and hblank = '0') then 
                     timerArray(0).T_CURRENT <= (others => '0'); 
                  end if;
               end if;
               if (timerArray(0).T_MODE(2 downto 1) = "11") then
                  if (hblank_1 = '1' and hblank = '0') then 
                     timerArray(0).T_MODE(0) <= '0';
                  end if;
               end if;
            end if;
            
            if (timerArray(1).T_MODE(0) = '1') then
               if (timerArray(1).T_MODE(2 downto 1) = "01" or timerArray(1).T_MODE(2 downto 1) = "10") then
                  if (vblank_1 = '0' and vblank = '1') then 
                     timerArray(1).T_CURRENT <= (others => '0'); 
                  end if;
               end if;
               if (timerArray(1).T_MODE(2 downto 1) = "11") then
                  if (vblank_1 = '0' and vblank = '1') then 
                     timerArray(1).T_MODE(0) <= '0';
                  end if;
               end if;
            end if;

            -- bus interface
            bus_dataRead <= (others => '0');
            
            channel := to_integer(unsigned(bus_addr(5 downto 4)));
           
            -- bus read
            if (bus_read = '1') then
               bus_dataRead <= (others => '1');
               if (channel < 3) then
                  case (bus_addr(3 downto 0)) is
                     when x"0" => bus_dataRead <= x"0000" & std_logic_vector(timerArray(channel).T_CURRENT);
                     when x"4" => 
                        bus_dataRead <= x"0000" & std_logic_vector(timerArray(channel).T_MODE);
                        timerArray(channel).T_MODE(12 downto 11) <= "00";
                     when x"8" => bus_dataRead <= x"0000" & std_logic_vector(timerArray(channel).T_TARGET);
                     when others => null;
                  end case;
               end if;
            end if;

            -- bus write
            if (bus_write = '1') then
               if (channel < 3) then
                  case (bus_addr(3 downto 0)) is
                     when x"0" => 
                        timerArray(channel).T_CURRENT <= unsigned(bus_dataWrite(15 downto 0));
                        timerArray(channel).blockNext <= '1';
                     when x"4" => 
                        timerArray(channel).T_MODE( 9 downto  0) <= unsigned(bus_dataWrite( 9 downto  0));
                        timerArray(channel).T_MODE(15 downto 13) <= unsigned(bus_dataWrite(15 downto 13));
                        timerArray(channel).irqDone              <= '0';
                        timerArray(channel).T_CURRENT            <= (others => '0');
                        timerArray(channel).blockNext            <= '1';
                     when x"8" => timerArray(channel).T_TARGET <= unsigned(bus_dataWrite(15 downto 0));
                     when others => null;
                  end case;
               end if;
            end if;
            
         end if;
      end if;
   end process;
   
--##############################################################
--############################### savestates
--##############################################################

   process (clk1x)
   begin
      if (rising_edge(clk1x)) then
         
         if (SS_rden = '1') then
            SS_DataRead <= ss_out(to_integer(SS_Adr));
         end if;
      
      end if;
   end process;

end architecture;





