-- -----------------------------------------------------------------------
--
-- Turbo Chameleon
--
-- Multi purpose FPGA expansion for the Commodore 64 computer
--
-- -----------------------------------------------------------------------
-- Copyright 2005-2019 by Peter Wendrich (pwsoft@syntiac.com)
-- http://www.syntiac.com
--
-- This source file is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Lesser General Public License as published
-- by the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This source file is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
--
-- -----------------------------------------------------------------------

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

-- -----------------------------------------------------------------------

architecture rtl of chameleon2_io is
-- Clocks
	signal no_clock_loc : std_logic;
	signal phi : std_logic;
	signal end_of_phi_0 : std_logic;
	signal end_of_phi_1 : std_logic;

-- I/O entity internal C64 bus
	signal c64_cs_loc : std_logic;
	signal c64_cs_roms_loc : std_logic;
	signal c64_cs_vicii_loc : std_logic;
	signal c64_cs_clockport_loc : std_logic;
	signal c64_we_loc : std_logic;
	signal c64_a_loc : unsigned(15 downto 0);
	signal c64_d_loc : unsigned(7 downto 0);
	signal c64_q_loc : unsigned(7 downto 0);

-- C64 joystick/keyboard
	signal c64_joystick1 : unsigned(5 downto 0);
	signal c64_joystick2 : unsigned(5 downto 0);
	signal c64_joystick3 : unsigned(5 downto 0);
	signal c64_joystick4 : unsigned(5 downto 0);
	signal c64_keys : unsigned(63 downto 0);

-- CDTV remote
	signal ir_up : std_logic := '0';
	signal ir_down : std_logic := '0';
	signal ir_left : std_logic := '0';
	signal ir_right : std_logic := '0';
	signal ir_f1 : std_logic := '0';
	signal ir_f2 : std_logic := '0';
	signal ir_f3 : std_logic := '0';
	signal ir_f4 : std_logic := '0';
	signal ir_f5 : std_logic := '0';
	signal ir_f6 : std_logic := '0';
	signal ir_f7 : std_logic := '0';
	signal ir_f8 : std_logic := '0';
	signal ir_space : std_logic := '0';
	signal ir_enter : std_logic := '0';
	signal ir_left_button : std_logic := '0';
	signal ir_middle_button : std_logic := '0';
	signal ir_right_button : std_logic := '0';
	signal ir_arrowleft : std_logic := '0';
	signal ir_y : std_logic := '0';
	signal ir_n : std_logic := '0';
	signal ir_runstop : std_logic := '0';
	signal ir_keys : unsigned(63 downto 0);
	signal ir_joystick1 : unsigned(5 downto 0) := (others => '1');
	signal ir_joystick2 : unsigned(5 downto 0) := (others => '1');

-- Docking-station
	signal docking_station_loc : std_logic;
	signal docking_irq : std_logic;
	signal docking_joystick1 : unsigned(5 downto 0);
	signal docking_joystick2 : unsigned(5 downto 0);
	signal docking_joystick3 : unsigned(5 downto 0);
	signal docking_joystick4 : unsigned(5 downto 0);
	signal docking_keys : unsigned(63 downto 0);
	signal docking_amiga_reset_n : std_logic;
	signal docking_amiga_scancode : unsigned(7 downto 0);
begin
	no_clock <= no_clock_loc;
	docking_station <= docking_station_loc;

	phi_out <= phi;
	phi_end_0 <= end_of_phi_0;
	phi_end_1 <= end_of_phi_1;
	--
	c64_q <= c64_q_loc;
	--
	joystick1 <= docking_joystick1 and ir_joystick1 and c64_joystick1;
	joystick2 <= docking_joystick2 and ir_joystick2 and c64_joystick2;
	joystick3 <= docking_joystick3 and c64_joystick3;
	joystick4 <= docking_joystick4 and c64_joystick4;
	keys <= docking_keys and c64_keys and ir_keys;

-- -----------------------------------------------------------------------
-- PHI2 clock sync
-- -----------------------------------------------------------------------
	phiInstance : entity work.chameleon_phi_clock
		generic map (
			phase_shift => 8
		)
		port map (
			clk => clk,
			phi2_n => phi2_n,
			mode => phi_mode,

			no_clock => no_clock_loc,
			docking_station => docking_station_loc,

			phiLocal => phi,
			phiCnt => phi_cnt,
			phiPreHalf => end_of_phi_0,
			phiPreEnd => end_of_phi_1,
			phiPost1 => phi_post_1,
			phiPost2 => phi_post_2,
			phiPost3 => phi_post_3,
			phiPost4 => phi_post_4
		);

-- -----------------------------------------------------------------------
-- Docking-station
-- To enable set enable_docking_station to true.
-- -----------------------------------------------------------------------
	genDockingStation : if enable_docking_station generate
		myDockingStation : entity work.chameleon_docking_station
			port map (
				clk => clk,

				docking_station => docking_station_loc,

				dotclock_n => dotclock_n,
				io_ef_n => ioef,
				rom_lh_n => romlh,
				irq_q => docking_irq,

				joystick1 => docking_joystick1,
				joystick2 => docking_joystick2,
				joystick3 => docking_joystick3,
				joystick4 => docking_joystick4,
				keys => docking_keys,
				restore_key_n => restore_key_n,

				amiga_power_led => '0', -- led_green,
				amiga_drive_led => '0', -- led_red,
				amiga_reset_n => amiga_reset_n,
				amiga_trigger => amiga_trigger,
				amiga_scancode => amiga_scancode
			);
	end generate;

	noDockingStation : if not enable_docking_station generate
		docking_joystick1 <= (others => '1');
		docking_joystick2 <= (others => '1');
		docking_joystick3 <= (others => '1');
		docking_joystick4 <= (others => '1');
		docking_keys <= (others => '1');
	end generate;

-- -----------------------------------------------------------------------
-- CDTV remote support
-- To enable set enable_cdtv_remote to true.
-- -----------------------------------------------------------------------
	genCdtvRemote : if enable_cdtv_remote generate
		myCdtvRemote : entity work.chameleon_cdtv_remote
			port map (
				clk => clk,
				ena_1mhz => ena_1mhz,
				ir => ir_data,

				key_1 => ir_f1,
				key_2 => ir_f2,
				key_3 => ir_f3,
				key_4 => ir_f4,
				key_5 => ir_f5,
				key_6 => ir_f6,
				key_7 => ir_f7,
				key_8 => ir_f8,
				key_9 => ir_runstop,
				key_0 => ir_space,
				key_escape => ir_arrowleft,
				key_enter => ir_enter,
				key_genlock => ir_left_button,
				key_cdtv => ir_middle_button,
				key_power => ir_right_button,
				key_rew => ir_left,
				key_play => ir_up,
				key_ff => ir_right,
				key_stop => ir_down,
				key_vol_up => ir_y,
				key_vol_dn => ir_n,

				joystick_a => ir_joystick1,
				joystick_b => ir_joystick2
			);

			ir_keys <= (not ir_runstop) & "11" & (not ir_n) & "111" & (not (ir_up or ir_down)) &
					"1111111" & (not (ir_f5 or ir_f6)) &
					"1111111" & (not (ir_f3 or ir_f4)) &
					(not ir_space) & (not (ir_left or ir_up or ir_f2 or ir_f4 or ir_f6 or ir_f8)) & "11111" & (not (ir_f1 or ir_f2)) &
					"1111111" & (not (ir_f7 or ir_f8)) &
					"1111111" & (not (ir_left or ir_right)) &
					(not ir_arrowleft) & "111" & (not ir_y) & "11" & (not ir_enter) &
					"11111111";
	end generate;

	noCdtvRemote : if not enable_cdtv_remote generate
		ir_keys <= (others => '1');
		ir_joystick1 <= (others => '1');
		ir_joystick2 <= (others => '1');
	end generate;

-- -----------------------------------------------------------------------
-- C64 keyboard and joystick support
-- To enable set enable_c64_joykeyb to true.
-- -----------------------------------------------------------------------
	c64_joykeyb_gen : if enable_c64_joykeyb generate
		c64_joykeyb_blk : block
			signal c64_kb_cs_reg : std_logic := '0';
			signal c64_kb_req : std_logic := '0';
			signal c64_kb_ack : std_logic := '0';
			signal c64_kb_we : std_logic := '0';
			signal c64_kb_a : unsigned(15 downto 0);
			signal c64_kb_d : unsigned(7 downto 0);
			signal c64_kb_q : unsigned(7 downto 0);
		begin
			c64_joykeyb_inst : entity work.chameleon_c64_joykeyb
				generic map (
					enable_4player => enable_c64_4player
				)
				port map (
					clk => clk,
					ena_1mhz => ena_1mhz,
					no_clock => no_clock_loc,
					reset => reset,

					ba => ba_in,
					req => c64_kb_req,
					ack => c64_kb_ack,
					we => c64_kb_we,
					a => c64_kb_a,
					d => c64_kb_d,
					q => c64_kb_q,

					joystick1 => c64_joystick1,
					joystick2 => c64_joystick2,
					joystick3 => c64_joystick3,
					joystick4 => c64_joystick4,
					keys => c64_keys
				);

			process(clk)
			begin
				if rising_edge(clk) then
					if end_of_phi_1 = '1' then
						c64_kb_cs_reg <= '0';
						if c64_kb_req /= c64_kb_ack then
							if c64_kb_cs_reg = '1' then
								-- C64 bus transaction finished
								c64_kb_ack <= c64_kb_req;
							else
								-- Start new C64 bus transaction
								c64_kb_cs_reg <= '1';
							end if;
						end if;
					end if;
				end if;
			end process;

			c64_cs_loc <= c64_kb_cs_reg;
			c64_cs_roms_loc <= '0';
			c64_cs_vicii_loc <= '0';
			c64_cs_clockport_loc <= '0';
			c64_we_loc <= c64_kb_we;
			c64_a_loc <= c64_kb_a;
			c64_d_loc <= c64_kb_q;
			c64_kb_d <= c64_q_loc;
		end block;
	end generate;

	no_c64_joykeyb_gen : if not enable_c64_joykeyb generate
		c64_joystick1 <= (others => '1');
		c64_joystick2 <= (others => '1');
		c64_joystick3 <= (others => '1');
		c64_joystick4 <= (others => '1');
		c64_keys <= (others => '1');

		c64_cs_loc <= c64_cs;
		c64_cs_roms_loc <= c64_cs_roms;
		c64_cs_vicii_loc <= c64_cs_vicii;
		c64_cs_clockport_loc <= c64_cs_clockport;
		c64_we_loc <= c64_we;
		c64_a_loc <= c64_a;
		c64_d_loc <= c64_d;
	end generate;

-- -----------------------------------------------------------------------
-- Cartridge port and C64 bus control
-- -----------------------------------------------------------------------
	c64_bus_blk : block
		constant dir_fr_c64 : std_logic := '0';
		constant dir_to_c64 : std_logic := '1';

		type state_t is (
			BUS_RESET,
			BUS_WAIT_PHI0,
			BUS0_00, BUS0_01, BUS0_02, BUS0_03,
			BUS0_04, BUS0_05, BUS0_06, BUS0_07, BUS0_08,
			BUS0_09, BUS0_0A, --BUS0_0B, BUS0_0C, BUS0_0D, BUS0_0E, BUS0_0F,
			BUS_WAIT_PHI1,
			BUS1_00, BUS1_01, BUS1_02, BUS1_03, BUS1_04, BUS1_05);

		signal state_reg : state_t := BUS_RESET;
		--signal exrom_out_reg : std_logic := '0';
		signal clock_ior_reg : std_logic := '1';
		signal clock_iow_reg : std_logic := '1';

		signal game_out_reg : std_logic := '1';
		signal rw_out_reg : std_logic := '0';

		signal sa_dir_reg : std_logic := '1';
		signal sa_oe_reg : std_logic := '1';
		signal sa15_out_reg : std_logic := '0';

		signal low_a_oe_reg : std_logic := '0';
		signal low_a_reg : unsigned(15 downto 0) := (others => '0');

		signal sd_dir_reg : std_logic := '1';
		signal sd_oe_reg : std_logic := '1';
		signal low_d_inp_reg : unsigned(7 downto 0) := (others => '0');
		signal low_d_oe_reg : std_logic := '0';
		signal low_d_out_reg : unsigned(7 downto 0) := (others => '0');

		signal c64_q_reg : unsigned(c64_q'range) := (others => '1');

	begin
		process(clk)
		begin
			if rising_edge(clk) then
				low_d_inp_reg <= low_d;
				case state_reg is
				when BUS_RESET =>
					clock_ior_reg <= '1';
					clock_iow_reg <= '1';
					game_out_reg <= '1';
					rw_out_reg <= '0';
					sa_oe_reg <= '1';
					sa15_out_reg <= '0';
					sd_oe_reg <= '1';
					low_a_oe_reg <= '0';
					low_d_oe_reg <= '0';
					if reset = '0' then
						state_reg <= BUS_WAIT_PHI0;
					end if;
				when BUS_WAIT_PHI0 =>
					c64_q_reg <= low_d_inp_reg;
					if phi = '0' then
						state_reg <= BUS0_00;
					else
						if c64_cs_vicii_loc = '1' then
							low_d_out_reg <= c64_d_loc;
						end if;
					end if;
				when BUS0_00 =>
					state_reg <= BUS0_01;
				when BUS0_01 =>
					game_out_reg <= '1';
					low_a_reg(15) <= '0';
					clock_ior_reg <= '1';
					clock_iow_reg <= '1';
					state_reg <= BUS0_02;
				when BUS0_02 =>
					state_reg <= BUS0_03;
				when BUS0_03 =>
					low_a_oe_reg <= '0';
					rw_out_reg <= '0';
					sa_oe_reg <= '1';
					state_reg <= BUS0_04;
				when BUS0_04 =>
					sa15_out_reg <= '1';
					state_reg <= BUS0_05;
				when BUS0_05 =>
					state_reg <= BUS0_06;
				when BUS0_06 =>
					state_reg <= BUS0_07;
				when BUS0_07 =>
					state_reg <= BUS0_08;
				when BUS0_08 =>
					state_reg <= BUS0_09;
				when BUS0_09 =>
					sd_dir_reg <= dir_to_c64;
					state_reg <= BUS0_0A;
				when BUS0_0A =>
					low_d_oe_reg <= '1';
					sd_oe_reg <= '0';
					state_reg <= BUS_WAIT_PHI1;
				--	state_reg <=
				-- when BUS0_04 => state_reg <= BUS0_05;
				-- when BUS0_05 => state_reg <= BUS0_06;
				-- when BUS0_06 => state_reg <= BUS0_07;
				-- when BUS0_07 => state_reg <= BUS0_08;
				-- when BUS0_08 => state_reg <= BUS0_09;
				-- when BUS0_09 => state_reg <= BUS0_0A;
				-- when BUS0_0A => state_reg <= BUS0_0B;
				-- when BUS0_0B => state_reg <= BUS0_0C;
				-- when BUS0_0C => state_reg <= BUS0_0D;
				-- when BUS0_0D => state_reg <= BUS0_0E;
				-- when BUS0_0E => state_reg <= BUS0_0F;
				-- when BUS0_0F =>
					-- state_reg <= BUS_WAIT_PHI1;
				when BUS_WAIT_PHI1 =>
					low_d_out_reg <= c64_vicii_data;
					if phi = '1' then
						state_reg <= BUS1_00;
					end if;
				when BUS1_00 =>
					state_reg <= BUS1_01;
				when BUS1_01 =>
					state_reg <= BUS1_02;
				when BUS1_02 =>
					sa_dir_reg <= dir_to_c64;
					rw_out_reg <= c64_we_loc and c64_cs_loc;
					if (c64_cs_loc = '1') and (c64_cs_vicii_loc = '0') then
						sa15_out_reg <= '0';
						low_a_oe_reg <= '1';
					end if;
					if ((c64_cs_loc and c64_we_loc) = '0') and (c64_cs_vicii_loc = '0') then
						low_d_oe_reg <= '0';
						sd_oe_reg <= '1';
					end if;

					-- For reading ROMs the GAME line need to be high (game_out_reg low as driver inverts).
					game_out_reg <= '1';
					if c64_cs_roms_loc = '1' then
						game_out_reg <= '0';
					end if;

					low_a_reg <= "0" & c64_a_loc(14 downto 0);
					low_d_out_reg <= c64_d_loc;
					state_reg <= BUS1_03;
				when BUS1_03 =>
					if (c64_cs_loc = '1') and (c64_cs_vicii_loc = '0') then
						sa_oe_reg <= '0';
					end if;
					if c64_cs_vicii_loc = '1' then
						sa15_out_reg <= '1';
					end if;
					low_a_reg <= "0" & c64_a_loc(14 downto 0);
					low_d_out_reg <= c64_d_loc;
					state_reg <= BUS1_04;
				when BUS1_04 =>
					sd_dir_reg <= dir_fr_c64;
					if ((c64_cs_loc and c64_we_loc) or c64_cs_vicii_loc) = '1' then
						sd_dir_reg <= dir_to_c64;
						low_d_oe_reg <= '1';
					end if;
					low_a_reg <= "0" & c64_a_loc(14 downto 0);
					low_d_out_reg <= c64_d_loc;
					state_reg <= BUS1_05;
				when BUS1_05 =>
					sd_oe_reg <= '0';
					if c64_cs_clockport_loc = '1' then
						clock_ior_reg <= c64_we_loc;
						clock_iow_reg <= not c64_we_loc;
					end if;
					low_a_reg <= c64_a_loc(15 downto 0);
					low_d_out_reg <= c64_d_loc;
					state_reg <= BUS_WAIT_PHI0;
				end case;
			end if;
		end process;

		clock_ior <= clock_ior_reg;
		clock_iow <= clock_iow_reg;

		dma_out <= '1';
		exrom_out <= '0';
		game_out <= game_out_reg;
		rw_out <= rw_out_reg;

		sa_dir <= sa_dir_reg;
		sa_oe <= sa_oe_reg;
		sa15_out <= sa15_out_reg;
		low_a <= low_a_reg when low_a_oe_reg = '1' else (others => 'Z');

		sd_dir <= sd_dir_reg;
		sd_oe <= sd_oe_reg;
		low_d <= low_d_out_reg when low_d_oe_reg = '1' else (others => 'Z');

		c64_q_loc <= c64_q_reg;
	end block;
end architecture;
