--  The!Cart bank switching and memory access core
--
--  Copyright (C) 2011-2025 Matthias Reichl <hias@horus.com>
--
--  This program is free software; you can redistribute it and/or modify
--  it under the terms of the GNU General Public License as published by
--  the Free Software Foundation; either version 2 of the License, or
--  (at your option) any later version.
--
--  This program 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, write to the Free Software
--  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.NUMERIC_STD.ALL;

library work;
use work.all;
use CartDef.all;

entity CartLogic is
    Port (	clk_register: in std_logic;
		a: in vec12;
		cctl_access: in CartControlState;
		d_in: in std_logic_vector;
		rw: in std_logic;
		reset_n: in std_logic;
		s4: in std_logic;
		s5: in std_logic;
		mem_out: out cart_mem_output;
		data_out: out data_output
	);
		
end CartLogic;

architecture Behavioral of CartLogic is

-- general cart configuration

signal cfg_bank: bankvec := startup_bank;
signal cfg_enable: std_logic := '1';
signal cfg_bank2: bankvec := (others => '0');
signal cfg_enable2: std_logic := '0';

subtype cart_mode_type is std_logic_vector(5 downto 0);
-- 8k modes
constant cart_mode_off:		cart_mode_type := "000000";
constant cart_mode_8k:		cart_mode_type := "000001";
constant cart_mode_atarimax1:	cart_mode_type := "000010";
constant cart_mode_atarimax8:	cart_mode_type := "000011";
constant cart_mode_oss:		cart_mode_type := "000100";

constant cart_mode_sdx64:	cart_mode_type := "001000";
constant cart_mode_diamond64:	cart_mode_type := "001001";
constant cart_mode_express64:	cart_mode_type := "001010";

constant cart_mode_atrax_128:	cart_mode_type := "001100";
constant cart_mode_williams_64:	cart_mode_type := "001101";

-- 16k modes
constant cart_mode_flexi:	cart_mode_type := "100000";
constant cart_mode_16k:		cart_mode_type := "100001";
constant cart_mode_megamax16:	cart_mode_type := "100010";
constant cart_mode_blizzard_16:	cart_mode_type := "100011";
constant cart_mode_sic:		cart_mode_type := "100100";

constant cart_mode_mega_4096:	cart_mode_type := "100101";

constant cart_mode_mega_16:	cart_mode_type := "101000";
constant cart_mode_mega_32:	cart_mode_type := "101001";
constant cart_mode_mega_64:	cart_mode_type := "101010";
constant cart_mode_mega_128:	cart_mode_type := "101011";
constant cart_mode_mega_256:	cart_mode_type := "101100";
constant cart_mode_mega_512:	cart_mode_type := "101101";
constant cart_mode_mega_1024:	cart_mode_type := "101110";
constant cart_mode_mega_2048:	cart_mode_type := "101111";

constant cart_mode_xegs_32:	cart_mode_type := "110000";
constant cart_mode_xegs_64:	cart_mode_type := "110001";
constant cart_mode_xegs_128:	cart_mode_type := "110010";
constant cart_mode_xegs_256:	cart_mode_type := "110011";
constant cart_mode_xegs_512:	cart_mode_type := "110100";
constant cart_mode_xegs_1024:	cart_mode_type := "110101";

constant cart_mode_sxegs_32:	cart_mode_type := "111000";
constant cart_mode_sxegs_64:	cart_mode_type := "111001";
constant cart_mode_sxegs_128:	cart_mode_type := "111010";
constant cart_mode_sxegs_256:	cart_mode_type := "111011";
constant cart_mode_sxegs_512:	cart_mode_type := "111100";
constant cart_mode_sxegs_1024:	cart_mode_type := "111101";

signal cfg_mode: cart_mode_type := cart_mode_8k;

signal cfg_source_ram: std_logic := '0';	-- ROM
signal cfg_write_enable: std_logic := '0';	-- writes disabled

signal cfg_source_ram2: std_logic := '0';	-- secondary bank: ROM
signal cfg_write_enable2: std_logic := '0';	-- secondary bank: writes disabled

-- OSS cart emulation
signal oss_bank: std_logic_vector(1 downto 0) := "00";

-- sic cart 8xxx/axxx enable
signal sic_8xxx_enable: std_logic := '0';
signal sic_axxx_enable: std_logic := '1';

signal access_8xxx: boolean;
signal access_axxx: boolean;

begin

access_8xxx <= ( s4 = '0');
access_axxx <= ( s5 = '0');

-- read back config registers
config_io: process(a, rw, cctl_access,
	cfg_bank, cfg_enable,
	cfg_bank2, cfg_enable2,
	cfg_source_ram, cfg_write_enable,
	cfg_source_ram2, cfg_write_enable2,
	cfg_mode,
	sic_8xxx_enable, sic_axxx_enable)
begin
	data_out.dout_enable <= false;
	data_out.dout <= x"00";
	if (rw = '1') then
		case cctl_access is
		when access_cctl_thecart =>
			case a(3 downto 0) is
			when cfg_bank_adr_lo =>
				data_out.dout_enable <= true;
				data_out.dout <= cfg_bank(20 downto 13);
			when cfg_bank_adr_hi =>
				data_out.dout_enable <= true;
				data_out.dout <= "00" & cfg_bank(26 downto 21);
			when cfg_enable_adr =>
				data_out.dout_enable <= true;
				data_out.dout <= "0000000" & cfg_enable;
			when cfg_bank2_adr_lo =>
				data_out.dout_enable <= true;
				data_out.dout <= cfg_bank2(20 downto 13);
			when cfg_bank2_adr_hi =>
				data_out.dout_enable <= true;
				data_out.dout <= "00" & cfg_bank2(26 downto 21);
			when cfg_enable2_adr =>
				data_out.dout_enable <= true;
				data_out.dout <= "0000000" & cfg_enable2;
			when cfg_mode_adr =>
				data_out.dout_enable <= true;
				data_out.dout <= "00" & cfg_mode;
			when cfg_misc_adr =>
				data_out.dout_enable <= true;
				data_out.dout <= "0000" &
					cfg_source_ram2 & cfg_write_enable2 &
					cfg_source_ram & cfg_write_enable;
			when others => null;
			end case;
		when access_cctl_emulated =>
			case cfg_mode is
			when cart_mode_sic =>
				-- sic register readback at $D500-$D51F
				if (a(7 downto 5) = "000") then
					data_out.dout_enable <= true;
					-- bit 7 = 0 means flash is write protected
					data_out.dout <= "0" & (not sic_axxx_enable) & sic_8xxx_enable & cfg_bank(18 downto 14);
				end if;
			when cart_mode_mega_4096 =>
				if (a(7 downto 5) = "000") then
					data_out.dout_enable <= true;
					data_out.dout <= cfg_bank(21 downto 14);
				end if;
			when others => null;
			end case;
		when others => null;
		end case;
	end if;
end process config_io;

set_config: process(clk_register)
begin
	if falling_edge(clk_register) then
		if (reset_n = '0') then
			cfg_mode <= cart_mode_8k;
			cfg_bank <= startup_bank;
			cfg_enable <= '1';
			cfg_bank2 <= (others => '0');
			cfg_enable2 <= '0';
			cfg_write_enable <= '0';
			cfg_source_ram <= '0';
			cfg_write_enable2 <= '0';
			cfg_source_ram2 <= '0';
			oss_bank <= "00";
			sic_8xxx_enable <= '0';
			sic_axxx_enable <= '1';
		else
			case cctl_access is
			when access_cctl_thecart =>
				if (rw = '0') then
					case a(3 downto 0) is
					when cfg_bank_adr_lo =>
						cfg_bank(20 downto 13) <= d_in(7 downto 0);
						cfg_enable <= '1';	-- automatically enable cart on bank access
					when cfg_bank_adr_hi =>
						cfg_bank(26 downto 21) <= d_in(5 downto 0);
						cfg_enable <= '1';	-- automatically enable cart on bank access
					when cfg_enable_adr =>
						cfg_enable <= d_in(0);
					when cfg_bank2_adr_lo =>
						cfg_bank2(20 downto 13) <= d_in(7 downto 0);
						cfg_enable2 <= '1';	-- automatically enable cart on bank access
					when cfg_bank2_adr_hi =>
						cfg_bank2(26 downto 21) <= d_in(5 downto 0);
						cfg_enable2 <= '1';	-- automatically enable cart on bank access
					when cfg_enable2_adr =>
						cfg_enable2 <= d_in(0);
					when cfg_mode_adr =>
						cfg_mode <= d_in(5 downto 0);
					when cfg_misc_adr =>
						cfg_source_ram2 <= d_in(3);
						cfg_write_enable2 <= d_in(2);
						cfg_source_ram <= d_in(1);
						cfg_write_enable <= d_in(0);
					when others => null;
					end case;
				end if;
			when access_cctl_emulated =>
				-- modes using data lines to switch banks
				if (rw = '0') then
					
					-- sxegs bank enable
					if (cfg_mode(5 downto 3) = cart_mode_sxegs_32(5 downto 3)) then
						cfg_enable <= not d_in(7);
					end if;

					-- megacart bank enable
					if (cfg_mode(5 downto 3) = cart_mode_mega_32(5 downto 3)) then
						-- disable via bit 7
						cfg_enable <= not d_in(7);
					end if;

					case cfg_mode is
					-- (s)xegs
					when cart_mode_xegs_32 | cart_mode_sxegs_32 =>
						cfg_bank(14 downto 13) <= d_in(1 downto 0);
					when cart_mode_xegs_64 | cart_mode_sxegs_64 =>
						cfg_bank(15 downto 13) <= d_in(2 downto 0);
					when cart_mode_xegs_128 | cart_mode_sxegs_128 =>
						cfg_bank(16 downto 13) <= d_in(3 downto 0);
					when cart_mode_xegs_256 | cart_mode_sxegs_256 =>
						cfg_bank(17 downto 13) <= d_in(4 downto 0);
					when cart_mode_xegs_512 | cart_mode_sxegs_512 =>
						cfg_bank(18 downto 13) <= d_in(5 downto 0);
					when cart_mode_xegs_1024 | cart_mode_sxegs_1024 =>
						cfg_bank(19 downto 13) <= d_in(6 downto 0);

					-- megacart
					when cart_mode_mega_32 =>
						cfg_bank(14 downto 14) <= d_in(0 downto 0);
					when cart_mode_mega_64 =>
						cfg_bank(15 downto 14) <= d_in(1 downto 0);
					when cart_mode_mega_128 =>
						cfg_bank(16 downto 14) <= d_in(2 downto 0);
					when cart_mode_mega_256 =>
						cfg_bank(17 downto 14) <= d_in(3 downto 0);
					when cart_mode_mega_512 =>
						cfg_bank(18 downto 14) <= d_in(4 downto 0);
					when cart_mode_mega_1024 =>
						cfg_bank(19 downto 14) <= d_in(5 downto 0);
					when cart_mode_mega_2048 =>
						cfg_bank(20 downto 14) <= d_in(6 downto 0);

					when cart_mode_mega_4096 =>
						if (a(7 downto 5) = "000") then
							cfg_bank(21 downto 14) <= d_in(7 downto 0);
							if (d_in = x"FF") then
								cfg_enable <= '0';
							else
								cfg_enable <= '1';
							end if;
						end if;
					when cart_mode_atrax_128 =>
						cfg_enable <= not d_in(7);
						cfg_bank(16 downto 13)<= d_in(3 downto 0);
					when cart_mode_blizzard_16 =>
						cfg_enable <= '0';
					when cart_mode_sic =>
						if (a(7 downto 5) = "000") then
							sic_8xxx_enable <= d_in(5);
							sic_axxx_enable <= not d_in(6);
							cfg_bank(18 downto 14) <= d_in(4 downto 0);
						end if;
					when others => null;
					end case;
				end if; -- rw = 0
				
				-- cart config using addresses, ignore read/write
				case cfg_mode is
				when cart_mode_oss =>
					oss_bank <= a(0) & NOT a(3);
				when cart_mode_atarimax1 =>
					-- D500-D50F: set bank, enable
					if (a(7 downto 4) = x"0") then
						cfg_bank(16 downto 13) <= a(3 downto 0);
						cfg_enable <= '1';
					end if;
					-- D510-D51F: disable
					if (a(7 downto 4) = x"1") then
						cfg_enable <= '0';
					end if;
				when cart_mode_atarimax8 =>
					-- D500-D57F: set bank, enable
					if (a(7) = '0') then
						cfg_bank(19 downto 13) <= a(6 downto 0);
						cfg_enable <= '1';
					else
					-- D580-D5FF: disable
						cfg_enable <= '0';
					end if;
				when cart_mode_megamax16 =>
					-- D500-D57F: set 16k bank, enable
					if (a(7) = '0') then
						cfg_bank(20 downto 14) <= a(6 downto 0);
						cfg_enable <= '1';
					else
						cfg_enable <= '0';
					end if;
				when cart_mode_williams_64 =>
					if (a(7 downto 4) = x"0") then
						cfg_enable <= not a(3);
						cfg_bank(15 downto 13) <= a(2 downto 0);
					end if;
				when cart_mode_sdx64 =>
					if (a(7 downto 4) = x"E") then
						cfg_enable <= not a(3);
						cfg_bank(15 downto 13) <= not a(2 downto 0);
					end if;
				when cart_mode_diamond64 =>
					if (a(7 downto 4) = x"D") then
						cfg_enable <= not a(3);
						cfg_bank(15 downto 13) <= not a(2 downto 0);
					end if;
				when cart_mode_express64 =>
					if (a(7 downto 4) = x"7") then
						cfg_enable <= not a(3);
						cfg_bank(15 downto 13) <= not a(2 downto 0);
					end if;
				when others => null;
				end case;
			when others => null;
			end case;
		end if;
	end if;
end process set_config;

access_cart_data: process(a, rw, access_8xxx, access_axxx,
	cfg_bank, cfg_enable, cfg_bank2, cfg_enable2,
	cfg_source_ram, cfg_write_enable,
	cfg_source_ram2, cfg_write_enable2,
	cfg_mode,
	oss_bank,
	sic_8xxx_enable, sic_axxx_enable)

variable address: vec27;
variable source_ram: boolean;
variable allow_write: boolean;

variable rd4: boolean;
variable rd5: boolean;
variable do_access: boolean;

begin
	address := cfg_bank & a(12 downto 0);
	source_ram := (cfg_source_ram = '1');
	allow_write := (cfg_write_enable = '1');

	rd4 := false;
	rd5 := (cfg_enable = '1');
	do_access := (cfg_enable = '1') and access_axxx;

	if (cfg_mode(5) = '1') then	-- default for 16k carts
		rd4 := (cfg_enable = '1');
		do_access := (cfg_enable = '1') and (access_8xxx or access_axxx);
	end if;

	case cfg_mode is
	when cart_mode_8k |
	     cart_mode_atarimax1 | cart_mode_atarimax8 | 
	     cart_mode_atrax_128 | cart_mode_williams_64 |
	     cart_mode_sdx64 | cart_mode_diamond64 | cart_mode_express64 =>
		null;
	when cart_mode_16k | cart_mode_megamax16 | cart_mode_blizzard_16 =>
		if (access_8xxx) then
			address(13) := '0';
		else
			address(13) := '1';
		end if;
	when cart_mode_oss =>
		if (oss_bank = "00") then
			rd5 := false;
			do_access := false;
		else
			address(13) := oss_bank(1) and (not a(12));
			address(12) := oss_bank(0) and (not a(12));
		end if;
	when cart_mode_flexi =>
		rd4 := (cfg_enable2 = '1');
		do_access := (cfg_enable = '1') and access_axxx;
		if (access_8xxx) then
			address(26 downto 13) := cfg_bank2;
			do_access := (cfg_enable2 = '1');
			source_ram := (cfg_source_ram2 = '1');
			allow_write := (cfg_write_enable2 = '1');
		end if;
	when cart_mode_xegs_32 |cart_mode_sxegs_32 =>
		if (access_axxx) then
			address(14 downto 13) := (others => '1');
		end if;
	when cart_mode_xegs_64 | cart_mode_sxegs_64 =>
		if (access_axxx) then
			address(15 downto 13) := (others => '1');
		end if;
	when cart_mode_xegs_128 | cart_mode_sxegs_128 =>
		if (access_axxx) then
			address(16 downto 13) := (others => '1');
		end if;
	when cart_mode_xegs_256 | cart_mode_sxegs_256 =>
		if (access_axxx) then
			address(17 downto 13) := (others => '1');
		end if;
	when cart_mode_xegs_512 | cart_mode_sxegs_512 =>
		if (access_axxx) then
			address(18 downto 13) := (others => '1');
		end if;
	when cart_mode_xegs_1024 | cart_mode_sxegs_1024 =>
		if (access_axxx) then
			address(19 downto 13) := (others => '1');
		end if;
	when cart_mode_mega_16 | cart_mode_mega_32 | cart_mode_mega_64 | cart_mode_mega_128 |
	     cart_mode_mega_256 | cart_mode_mega_512 | cart_mode_mega_1024 | cart_mode_mega_2048 |
	     cart_mode_mega_4096 =>
		if (access_8xxx) then
			address(13) := '0';
		else
			address(13) := '1';
		end if;
	when cart_mode_sic =>
		rd4 := (cfg_enable = '1') and (sic_8xxx_enable = '1');
		rd5 := (cfg_enable = '1') and (sic_axxx_enable = '1');
		do_access := (access_8xxx and (cfg_enable = '1') and (sic_8xxx_enable = '1'))
				or
			     (access_axxx and (cfg_enable = '1') and (sic_axxx_enable = '1'));
		if (access_8xxx) then
			address(13) := '0';
		else
			address(13) := '1';
		end if;
	when others =>
		rd4 := false;
		rd5 := false;
		do_access := false;
		allow_write := false;
		source_ram := false;
	end case;

	if  (rw = '0') and (allow_write = false) then
		do_access := false;
	end if;

	mem_out.adr <= address;
	mem_out.rd4 <= rd4;
	mem_out.rd5 <= rd5;
	mem_out.ram_access <= false;
	mem_out.rom_access <= false;

	if (do_access) then
		if (source_ram) then
			mem_out.ram_access <= true;
		else
			mem_out.rom_access <= true;
		end if;
	end if;
end process access_cart_data;

end Behavioral;