LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL;

ENTITY gpio_wb IS
	Generic
	(
		f_sysclk	: real	:= 100.0
	);
	Port
	(
		CLK_I		: in  STD_LOGIC;
		RST_I		: in  STD_LOGIC;
		CYC_I		: in  STD_LOGIC;
		STB_I		: in  STD_LOGIC;
		SEL_I		: in  unsigned(3 downto 0);
		WE_I		: in  STD_LOGIC;
		ACK_O		: out STD_LOGIC;
		SRDY_O		: out STD_LOGIC;
		MRDY_I		: in  STD_LOGIC;
		ADDR_I		: in  unsigned(31 downto 0);
		DAT_I		: in  unsigned(31 downto 0);
		DAT_O		: out unsigned(31 downto 0);
		INT_TIM_O	: out STD_LOGIC;

		sys_gpi_0	: in unsigned(31 downto 0);
		sys_gpo_0	: out unsigned(31 downto 0)

	);
END gpio_wb;

ARCHITECTURE behavior OF gpio_wb IS

	constant num_timers			: natural := 2;
	constant ncycles_usec		: natural := natural(f_sysclk);
	
	signal gpio_0_dout_reg		: unsigned(31 downto 0);
	signal gpio_0_dir_reg		: unsigned(31 downto 0);
	signal gpio_0_din_reg		: unsigned(31 downto 0);

	-- Signals to form an timer generating an interrupt every microsecond
	subtype tick_usec_t	is natural range 0 to ncycles_usec-1;
	signal tick_usec			: tick_usec_t := 0;
	signal cnt_usec				: unsigned(31 downto 0);
	signal cnt_sec				: unsigned(31 downto 0);
	signal cnt_usec_preset		: unsigned(31 downto 0);
	signal cnt_sec_preset		: unsigned(31 downto 0);
	signal cnt_usec_en			: std_logic;
	signal cnt_usec_we			: std_logic;
	signal cnt_sec_en			: std_logic;
	signal cnt_sec_we			: std_logic;

	type timer_array_t is array (0 to num_timers-1) of unsigned(31 downto 0);
	signal timer_cnt			: timer_array_t;
	signal timer_cmp			: timer_array_t;
	signal timer_en				: unsigned(0 to num_timers-1);
	signal timer_inten			: unsigned(0 to num_timers-1);
	signal timer_irq			: unsigned(0 to num_timers-1);
	signal timer_ovl			: unsigned(0 to num_timers-1);
	signal timer_irq_ack		: unsigned(0 to num_timers-1);
	signal timer_cnt_we			: unsigned(0 to num_timers-1);
	signal timer_cmp_we			: unsigned(0 to num_timers-1);
	
	signal reg_data_wr			: unsigned(31 downto 0);
	
begin

	SRDY_O		<= CYC_I;
	
tim_irq:
	process(timer_irq)
	variable irq : std_logic;
	begin
		irq := '0';
		for i in 0 to num_timers-1 loop
			irq	:= irq or timer_irq(i);
		end loop;
		INT_TIM_O	<= irq;
	end process;	
		
------------------------------------------------------------------
gpio_0_out:
	process(gpio_0_dir_reg, gpio_0_dout_reg)
	begin
		for i in 0 to 31 loop
			sys_gpo_0(i)	<= 'Z';
			if gpio_0_dir_reg(i) = '1' then
				sys_gpo_0(i)	<= gpio_0_dout_reg(i);
			end if;
		end loop;
	end process;	
		
------------------------------------------------------------------
gpio_0_in:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			for i in 0 to 31 loop
				if gpio_0_dir_reg(i) = '1' then
					gpio_0_din_reg(i)	<= gpio_0_dout_reg(i);
				else
					gpio_0_din_reg(i)	<= sys_gpi_0(i);
				end if;
			end loop;
		end if;
	end process;	
		

------------------------------------------------------------------
registers_write: 
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			cnt_usec_we		<= '0';
			cnt_sec_we		<= '0';
			timer_cnt_we	<= (others => '0');
			timer_cmp_we	<= (others => '0');
			timer_irq_ack	<= (others => '0');
			if RST_I = '1' then
				gpio_0_dout_reg	<= (others => '0');
				gpio_0_dir_reg	<= (others => '0');
				timer_en		<= (others => '0');
				timer_inten		<= (others => '0');
			elsif (STB_I and CYC_I and WE_I) = '1' then
				reg_data_wr		<= DAT_I;
				case ADDR_I(6 downto 2) is
				
				when "00000" =>
					gpio_0_dout_reg <= DAT_I;
					
				when "00001" =>
					gpio_0_dir_reg 	<= DAT_I;
					
				when "00010" =>
					cnt_usec_we		<= '1';

				when "00011" =>
					cnt_sec_we		<= '1';
					
				when "00110" =>		-- timer control
					for i in 0 to num_timers-1 loop
						timer_en(i)			<= DAT_I(2*i+0);
						timer_inten(i)		<= DAT_I(2*i+1);
					end loop;
				when "00111" =>		-- timer status
					for i in 0 to num_timers-1 loop
						timer_irq_ack(i)	<= DAT_I(2*i);	-- IRQ acknowledge
					end loop;

				when "01000" =>		-- timer count 0
					timer_cnt_we(0)	<= '1';

				when "01001" =>		-- timer count 1
					timer_cnt_we(1)	<= '1';

				when "01100" =>		-- timer compare 0
					timer_cmp_we(0)	<= '1';

				when "01101" =>		-- timer compare 1
					timer_cmp_we(1)	<= '1';

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

registers_read: 
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			ACK_O			<= '0';
			if (STB_I and CYC_I) = '1' then
				ACK_O		<= not WE_I;
				DAT_O		<= (others => '0');
				case ADDR_I(6 downto 2) is

				when "00000" =>
					DAT_O	<= gpio_0_din_reg;

				when "00001" =>
					DAT_O	<= gpio_0_dir_reg;

				when "00010" =>
					DAT_O	<= cnt_usec;

				when "00011" =>
					DAT_O	<= cnt_sec;

				when "00110" =>		-- timer control
					for i in 0 to num_timers-1 loop
						DAT_O(2*i+0)	<= timer_en(i);
						DAT_O(2*i+1)	<= timer_inten(i);
					end loop;
					
				when "00111" =>		-- timer status
					for i in 0 to num_timers-1 loop
						DAT_O(2*i+0)	<= timer_irq(i);
						DAT_O(2*i+1)	<= timer_ovl(i);
					end loop;

				when "01000" =>		-- timer count 0
					DAT_O			<= timer_cnt(0);

				when "01001" =>		-- timer count 1
					DAT_O			<= timer_cnt(1);

				when "01100" =>		-- timer compare 0
					DAT_O			<= timer_cmp(0);

				when "01101" =>		-- timer compare 1
					DAT_O			<= timer_cmp(1);

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

cnt_usec_tick:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			cnt_usec_en		<= '0';
			if tick_usec >= tick_usec_t'high then
				tick_usec		<= 0;
				cnt_usec_en		<= '1';
			else
				tick_usec		<= tick_usec + 1;
			end if;
		end if;
	end process;

-- H/W Clock
cnt_usec_clock:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			cnt_sec_en		<= '0';
			if cnt_usec_we = '1' then
				cnt_usec		<= reg_data_wr;
			elsif cnt_usec_en = '1' then
				if to_01(cnt_usec) >= to_unsigned(1E6 - 1, 32) then
					cnt_usec		<= (others => '0');
					cnt_sec_en		<= '1';
				else
					cnt_usec		<= cnt_usec + 1;
				end if;
			end if;	
		end if;
	end process;

cnt_sec_clock:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			if cnt_sec_we = '1' then
				cnt_sec		<= reg_data_wr;
			elsif cnt_sec_en = '1' then
				cnt_sec		<= cnt_sec + 1;
			end if;	
		end if;
	end process;

-- Interrupt timer
proc_int_timer:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			for i in 0 to num_timers-1 loop
				if RST_I = '1' then
					timer_ovl(i) 	<= '0';
					timer_irq(i)	<= '0';
				elsif timer_irq_ack(i) = '1' then
					timer_ovl(i)	<= '0';
					timer_irq(i)	<= '0';
				elsif timer_cnt_we(i) = '1' then
					timer_cnt(i)		<= reg_data_wr;
				elsif timer_en(i) = '1' then
					if to_01(timer_cnt(i)) >= timer_cmp(i) then
						timer_cnt(i)		<= (others => '0');
						if timer_inten(i) = '1' then
							if timer_irq(i) = '1' then
								timer_ovl(i) <= '1';
							end if;	
							timer_irq(i)	<= '1';
						end if;	
					else
						timer_cnt(i)		<= timer_cnt(i) + 1;
					end if;
				end if;	
			end loop;	
		end if;
	end process;

proc_int_timer_reload:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			for i in 0 to num_timers-1 loop
				if timer_cmp_we(i) = '1' then
					timer_cmp(i)	<= reg_data_wr;
				end if;	
			end loop;	
		end if;
	end process;

------------------------------------------------------------------
end behavior;
