-- -----------------------------------------------------------------------
--
-- Turbo Chameleon
--
-- Multi purpose FPGA expansion for the Commodore 64 computer
--
-- -----------------------------------------------------------------------
-- Copyright 2005-2012 by Peter Wendrich (pwsoft@syntiac.com)
-- All Rights Reserved.
--
-- Allowed to be used in your own projects that are targeted for the
-- Turbo Chameleon 64 cartridge.
--
-- http://www.syntiac.com/chameleon.html
-- -----------------------------------------------------------------------
--
-- Chameleon docking station
--
-- -----------------------------------------------------------------------
-- clk             - system clock
-- docking_station - must be high when docking-station is available.
--                   This can be determined by the state of the phi2 pin.
-- dotclock_n      - Connect to the dotclock_n pin.
-- io_ef_n         - Connect to the io_ef_n pin.
-- rom_hl_n        - Connect to the rom_hl_n pin.
-- irq_q           - IRQ pin output (open drain output, 0 is drive low, 1 is input)
-- joystick*       - Joystick outputs (fire2, fire1, right, left, down, up) low active
-- keys            - State of the keyboard (low is pressed)
-- restore_key_n   - State of the restore key (low is pressed)
--
-- amiga_power_led - Control input for the POWER LED on the Amiga keyboard.
-- amiga_drive_led - Control input for the DRIVE LED on the Amiga keyboard.
-- amiga_reset_n   - Low when the Amiga keyboard does a reset.
-- amiga_trigger   - One clock high when the Amiga keyboard has send a new scancode.
-- amiga_scancode  - Value of the last received scancode from the Amiga keyboard.
-- -----------------------------------------------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.numeric_std.all;

-- -----------------------------------------------------------------------

entity chameleon_docking_station is
	port (
		clk : in std_logic;
		docking_station : in std_logic;

		dotclock_n : in std_logic;
		io_ef_n : in std_logic;
		rom_lh_n : in std_logic;
		irq_q : out std_logic;
		
		joystick1 : out unsigned(5 downto 0);
		joystick2 : out unsigned(5 downto 0);
		joystick3 : out unsigned(5 downto 0);
		joystick4 : out unsigned(5 downto 0);
		--  0 = col0, row0
		--  1 = col1, row0
		--  8 = col0, row1
		-- 63 = col7, row7
		keys : out unsigned(63 downto 0);
		restore_key_n : out std_logic;
		
	-- Amiga keyboard
		amiga_power_led : in std_logic;
		amiga_drive_led : in std_logic;
		amiga_reset_n : out std_logic;
		amiga_trigger : out std_logic;
		amiga_scancode : out unsigned(7 downto 0)
	);
end entity;

-- -----------------------------------------------------------------------

architecture rtl of chameleon_docking_station is
	constant shift_reg_bits : integer := 13*8;
	-- We put the out-of-sync detection just before the actual sync-pulse.
	-- Gives it the biggest chance of catching a sync-problem.
	constant out_of_sync_pos : integer := 102; 
	signal shift_reg : unsigned(shift_reg_bits-1 downto 0);
	signal bit_cnt : unsigned(7 downto 0) := (others => '0');
	signal once : std_logic := '0';

	signal key_reg : unsigned(63 downto 0) := (others => '1');
	signal restore_n_reg : std_logic := '1';
	signal joystick1_reg : unsigned(5 downto 0) := (others => '0');
	signal joystick2_reg : unsigned(5 downto 0) := (others => '0');
	signal joystick3_reg : unsigned(5 downto 0) := (others => '0');
	signal joystick4_reg : unsigned(5 downto 0) := (others => '0');
	signal dotclock_n_reg : std_logic := '0';
	signal dotclock_n_dly : std_logic := '0';
	signal io_ef_n_reg : std_logic := '0';
	signal rom_lh_n_reg : std_logic := '1';
	signal irq_q_reg : std_logic := '1';
	
	signal amiga_reset_n_reg : std_logic := '0';
	signal amiga_trigger_reg : std_logic := '0';
	signal amiga_scancode_reg : unsigned(7 downto 0) := (others => '0');

	signal last_keys_reg : unsigned(63 downto 0) := (others => '1');
	signal approved_keys_reg : unsigned(63 downto 0) := (others => '1');
begin
	joystick1 <= joystick1_reg;
	joystick2 <= joystick2_reg;
	joystick3 <= joystick3_reg;
	joystick4 <= joystick4_reg;
	keys <= approved_keys_reg;
	restore_key_n <= restore_n_reg;
	amiga_reset_n <= amiga_reset_n_reg;
	amiga_trigger <= amiga_trigger_reg;
	amiga_scancode <= amiga_scancode_reg;
	irq_q <= irq_q_reg;

	--
	-- Sample DotClock, IO_EF and ROM_LH input.
	process(clk) is
	begin
		if rising_edge(clk) then
			dotclock_n_reg <= dotclock_n;
			dotclock_n_dly <= dotclock_n_reg;
			io_ef_n_reg <= io_ef_n;
			rom_lh_n_reg <= rom_lh_n;
		end if;
	end process;

	--
	-- Receive serial stream
	process(clk) is
	begin
		if rising_edge(clk) then
			if (dotclock_n_reg = '0') and (dotclock_n_dly = '1') then
				shift_reg <= (not rom_lh_n_reg) & shift_reg(shift_reg'high downto 1);
				bit_cnt <= bit_cnt + 1;
			end if;
			if (io_ef_n_reg = '1') and (bit_cnt = out_of_sync_pos) then
				-- Out of sync detection.
				-- Wait for the MCU on the docking-station to release io_ef
				-- Then we can continue and syncronise on the next io_ef pulse that comes.
				bit_cnt <= to_unsigned(out_of_sync_pos, bit_cnt'length);
			end if;
			if (io_ef_n_reg = '1') and (bit_cnt >= shift_reg_bits) then
				-- Word trigger. Signals start of serial bit-stream.
				bit_cnt <= (others => '0');
			end if;
		end if;
	end process;
	
	--
	-- Amiga keyboard LED control
	process(clk) is
	begin
		if rising_edge(clk) then
			irq_q_reg <= '1';
			if (bit_cnt >= 40) and (bit_cnt < 56) then
				irq_q_reg <= amiga_power_led;
			end if;
			if (bit_cnt >= 72) and (bit_cnt < 88) then
				irq_q_reg <= amiga_drive_led;
			end if;
		end if;
	end process;

	--
	-- Decode bytes
	process(clk) is
	begin
		if rising_edge(clk) then
			if bit_cnt = shift_reg_bits then
				-- Map shifted bits to joysticks
				last_keys_reg <= key_reg;

				if ((shift_reg(101 downto 96) and shift_reg(85 downto 80) and joystick1_reg and joystick2_reg) = "11111" and last_keys_reg = key_reg) then
					approved_keys_reg <= key_reg;
				end if;

				joystick1_reg <= shift_reg(101 downto 96);
				joystick2_reg <= shift_reg(85 downto 80);
				joystick3_reg <= shift_reg(102)& shift_reg(103) & shift_reg(92) & shift_reg(93) & shift_reg(94) & shift_reg(95);
				joystick4_reg <= shift_reg(86) & shift_reg(87) & shift_reg(88) & shift_reg(89) & shift_reg(90) & shift_reg(91);
				restore_n_reg <= shift_reg(1);

				-- Map shifted bits to C64 keyboard
				if (shift_reg(87 downto 80) = X"FF") and (shift_reg(103 downto 96) = X"FF") then
					for row in 0 to 7 loop
						for col in 0 to 7 loop
							-- uC scans column wise.
							key_reg(row*8 + col) <= shift_reg(16 + col*8 + row);
						end loop;
					end loop;
				else
					-- Prevent conflict between keyboard and joystick.
					-- Relase all keyboard keys while joystick button(s) are pressed.
					key_reg <= (others => '1');
				end if;
				
				-- Amiga keyboard
				amiga_reset_n_reg <= shift_reg(2);
				if shift_reg(0) = '1' then
					amiga_scancode_reg <= shift_reg(15 downto 8);
					amiga_trigger_reg <= once;
				end if;
				once <= '0';
			end if;
			if (io_ef_n_reg = '1') then
				once <= '1';
			end if;

			-- No docking station connected.
			-- Disable all outputs to prevent conflicts.
			if docking_station = '0' then
				joystick1_reg <= (others => '1');
				joystick2_reg <= (others => '1');
				joystick3_reg <= (others => '1');
				joystick4_reg <= (others => '1');
				key_reg <= (others => '1');
				restore_n_reg <= '1';
				amiga_reset_n_reg <= '1';
			end if;
		end if;
	end process;
end architecture;
