-- ****
-- T65(b) core. In an effort to merge and maintain bug fixes ....
--
--
-- Ver 301 more merging
-- Ver 300 Bugfixes by ehenciak added, started tidyup *bust*
-- MikeJ March 2005
-- Latest version from www.fpgaarcade.com (original www.opencores.org)
--
-- ****
--
-- 65xx compatible microprocessor core
--
-- Version : 0246
--
-- Copyright (c) 2002 Daniel Wallner (jesus@opencores.org)
--
-- All rights reserved
--
-- Redistribution and use in source and synthezised forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- Redistributions in synthesized form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- Neither the name of the author nor the names of other contributors may
-- be used to endorse or promote products derived from this software without
-- specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE
-- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--
-- Please report bugs to the author, but before you do so, please
-- make sure that this is not a derivative work and that
-- you have the latest version of this file.
--
-- The latest version of this file can be found at:
--      http://www.opencores.org/cvsweb.shtml/t65/
--
-- Limitations :
--
-- 65C02 and 65C816 modes are incomplete
-- Undocumented instructions are not supported
-- Some interface signals behaves incorrect
--
-- File history :
--
--      0246 : First release
--

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

-- ehenciak 2-23-2005 : Added the enable signal so that one doesn't have to use
-- the ready signal to limit the CPU.
entity T65 is
	port(
		Mode    : in  std_logic_vector(1 downto 0);      -- "00" => 6502, "01" => 65C02, "10" => 65C816
		Res_n   : in  std_logic;
		Enable  : in  std_logic;
		Clk     : in  std_logic;
		Rdy     : in  std_logic;
		Abort_n : in  std_logic;
		IRQ_n   : in  std_logic;
		NMI_n   : in  std_logic;
		SO_n    : in  std_logic;
		R_W_n   : out std_logic;
		Sync    : out std_logic;
		EF      : out std_logic;
		MF      : out std_logic;
		XF      : out std_logic;
		ML_n    : out std_logic;
		VP_n    : out std_logic;
		VDA     : out std_logic;
		VPA     : out std_logic;
		A       : out std_logic_vector(23 downto 0);
		DI      : in  std_logic_vector(7 downto 0);
		DO      : out std_logic_vector(7 downto 0)
	);
end T65;

architecture rtl of T65 is

	-- Registers
	signal ABC, X, Y, D       : std_logic_vector(15 downto 0);
	signal P, AD, DL          : std_logic_vector(7 downto 0) :=  x"00";
	signal BAH                : std_logic_vector(7 downto 0);
	signal BAL                : std_logic_vector(8 downto 0);
	signal PBR                : std_logic_vector(7 downto 0);
	signal DBR                : std_logic_vector(7 downto 0);
	signal PC                 : unsigned(15 downto 0);
	signal S                  : unsigned(15 downto 0);
	signal EF_i               : std_logic;
	signal MF_i               : std_logic;
	signal XF_i               : std_logic;

	signal IR                 : std_logic_vector(7 downto 0);
	signal MCycle             : std_logic_vector(2 downto 0);

	signal Mode_r             : std_logic_vector(1 downto 0);
	signal ALU_Op_r           : std_logic_vector(3 downto 0);
	signal Write_Data_r       : std_logic_vector(2 downto 0);
	signal Set_Addr_To_r      : std_logic_vector(1 downto 0);
	signal PCAdder            : unsigned(8 downto 0);

	signal RstCycle           : std_logic;
	signal IRQCycle           : std_logic;
	signal NMICycle           : std_logic;

	signal B_o                : std_logic;
	signal SO_n_o             : std_logic;
	signal IRQ_n_o            : std_logic;
	signal NMI_n_o            : std_logic;
	signal NMIAct             : std_logic;

	signal Break              : std_logic;

	-- ALU signals
	signal BusA               : std_logic_vector(7 downto 0);
	signal BusA_r             : std_logic_vector(7 downto 0);
	signal BusB               : std_logic_vector(7 downto 0);
	signal ALU_Q              : std_logic_vector(7 downto 0);
	signal P_Out              : std_logic_vector(7 downto 0);

	-- Micro code outputs
	signal LCycle             : std_logic_vector(2 downto 0);
	signal ALU_Op             : std_logic_vector(3 downto 0);
	signal Set_BusA_To        : std_logic_vector(2 downto 0);
	signal Set_Addr_To        : std_logic_vector(1 downto 0);
	signal Write_Data         : std_logic_vector(2 downto 0);
	signal Jump               : std_logic_vector(1 downto 0);
	signal BAAdd              : std_logic_vector(1 downto 0);
	signal BreakAtNA          : std_logic;
	signal ADAdd              : std_logic;
	signal AddY               : std_logic;
	signal PCAdd              : std_logic;
	signal Inc_S              : std_logic;
	signal Dec_S              : std_logic;
	signal LDA                : std_logic;
	signal LDP                : std_logic;
	signal LDX                : std_logic;
	signal LDY                : std_logic;
	signal LDS                : std_logic;
	signal LDDI               : std_logic;
	signal LDALU              : std_logic;
	signal LDAD               : std_logic;
	signal LDBAL              : std_logic;
	signal LDBAH              : std_logic;
	signal SaveP              : std_logic;
	signal Write              : std_logic;

	signal really_rdy         : std_logic;
	signal R_W_n_i            : std_logic;

begin
	-- ehenciak : gate Rdy with read/write to make an "OK, it's
	--            really OK to stop the processor now if Rdy is
	--            deasserted" signal
	really_rdy <= Rdy or not(R_W_n_i);

	-- ehenciak : Drive R_W_n_i off chip.
	R_W_n      <= R_W_n_i;

	Sync <= '1' when MCycle = "000" else '0';
	EF <= EF_i;
	MF <= MF_i;
	XF <= XF_i;
	ML_n <= '0' when IR(7 downto 6) /= "10" and IR(2 downto 1) = "11" and MCycle(2 downto 1) /= "00" else '1';
	VP_n <= '0' when IRQCycle = '1' and (MCycle = "101" or MCycle = "110") else '1';
	VDA <= '1' when Set_Addr_To_r /= "00" else '0';             -- Incorrect !!!!!!!!!!!!
	VPA <= '1' when Jump(1) = '0' else '0';                     -- Incorrect !!!!!!!!!!!!

	mcode : T65_MCode
		port map(
			Mode        => Mode_r,
			IR          => IR,
			MCycle      => MCycle,
			P           => P,
			LCycle      => LCycle,
			ALU_Op      => ALU_Op,
			Set_BusA_To => Set_BusA_To,
			Set_Addr_To => Set_Addr_To,
			Write_Data  => Write_Data,
			Jump        => Jump,
			BAAdd       => BAAdd,
			BreakAtNA   => BreakAtNA,
			ADAdd       => ADAdd,
			AddY        => AddY,
			PCAdd       => PCAdd,
			Inc_S       => Inc_S,
			Dec_S       => Dec_S,
			LDA         => LDA,
			LDP         => LDP,
			LDX         => LDX,
			LDY         => LDY,
			LDS         => LDS,
			LDDI        => LDDI,
			LDALU       => LDALU,
			LDAD        => LDAD,
			LDBAL       => LDBAL,
			LDBAH       => LDBAH,
			SaveP       => SaveP,
			Write       => Write
			);

	alu : T65_ALU
		port map(
			Mode => Mode_r,
			Op => ALU_Op_r,
			BusA => BusA_r,
			BusB => BusB,
			P_In => P,
			P_Out => P_Out,
			Q => ALU_Q
			);

	process (Res_n, Clk)
	begin
		if Res_n = '0' then
			PC <= (others => '0');  -- Program Counter
			IR <= "00000000";
			S <= (others => '0');       -- Dummy !!!!!!!!!!!!!!!!!!!!!
			D <= (others => '0');
			PBR <= (others => '0');
			DBR <= (others => '0');

			Mode_r <= (others => '0');
			ALU_Op_r <= "1100";
			Write_Data_r <= "000";
			Set_Addr_To_r <= "00";

			R_W_n_i <= '1';
			EF_i <= '1';
			MF_i <= '1';
			XF_i <= '1';

		elsif Clk'event and Clk = '1' then
		  if (Enable = '1') then
			if (really_rdy = '1') then
				R_W_n_i <= not Write or RstCycle;

				D <= (others => '1');   -- Dummy
				PBR <= (others => '1'); -- Dummy
				DBR <= (others => '1'); -- Dummy
				EF_i <= '0';    -- Dummy
				MF_i <= '0';    -- Dummy
				XF_i <= '0';    -- Dummy

				if MCycle  = "000" then
					Mode_r <= Mode;

					if IRQCycle = '0' and NMICycle = '0' then
						PC <= PC + 1;
					end if;

					if IRQCycle = '1' or NMICycle = '1' then
						IR <= "00000000";
					else
						IR <= DI;
					end if;
				end if;

				ALU_Op_r <= ALU_Op;
				Write_Data_r <= Write_Data;
				if Break = '1' then
					Set_Addr_To_r <= "00";
				else
					Set_Addr_To_r <= Set_Addr_To;
				end if;

				if Inc_S = '1' then
					S <= S + 1;
				end if;
				if Dec_S = '1' and RstCycle = '0' then
					S <= S - 1;
				end if;
				if LDS = '1' then
					S(7 downto 0) <= unsigned(ALU_Q);
				end if;

				if IR = "00000000" and MCycle = "001" and IRQCycle = '0' and NMICycle = '0' then
					PC <= PC + 1;
				end if;
				--
				-- jump control logic
				--
				case Jump is
				  when "01" =>
					  PC <= PC + 1;

				  when "10" =>
					  PC <= unsigned(DI & DL);

				  when "11" =>
					  if PCAdder(8) = '1' then
						  if DL(7) = '0' then
							  PC(15 downto 8) <= PC(15 downto 8) + 1;
						  else
							  PC(15 downto 8) <= PC(15 downto 8) - 1;
						  end if;
					  end if;
					  PC(7 downto 0) <= PCAdder(7 downto 0);

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

	PCAdder <= resize(PC(7 downto 0),9) + resize(unsigned(DL(7) & DL),9) when PCAdd = '1'
			   else "0" & PC(7 downto 0);

	process (Clk)
	begin
		if Clk'event and Clk = '1' then
		  if (Enable = '1') then
			if (really_rdy = '1') then
				if MCycle = "000" then
					if LDA = '1' then
						ABC(7 downto 0) <= ALU_Q;
					end if;
					if LDX = '1' then
						X(7 downto 0) <= ALU_Q;
					end if;
					if LDY = '1' then
						Y(7 downto 0) <= ALU_Q;
					end if;
					if (LDA or LDX or LDY) = '1' then
						P <= P_Out;
					end if;
				end if;
				if SaveP = '1' then
					P <= P_Out;
				end if;
				if LDP = '1' then
					P <= ALU_Q;
				end if;
				if IR(4 downto 0) = "11000" then
					case IR(7 downto 5) is
					when "000" =>
						P(Flag_C) <= '0';
					when "001" =>
						P(Flag_C) <= '1';
					when "010" =>
						P(Flag_I) <= '0';
					when "011" =>
						P(Flag_I) <= '1';
					when "101" =>
						P(Flag_V) <= '0';
					when "110" =>
						P(Flag_D) <= '0';
					when "111" =>
						P(Flag_D) <= '1';
					when others =>
					end case;
				end if;

				--if IR = "00000000" and MCycle = "011" and RstCycle = '0' and NMICycle = '0' and IRQCycle = '0' then
				--	P(Flag_B) <= '1';
				--end if;
				--if IR = "00000000" and MCycle = "100" and RstCycle = '0' and (NMICycle = '1' or IRQCycle = '1')  then
				--	P(Flag_I) <= '1';
				--	P(Flag_B) <= B_o;
				--end if;

        -- B=1 always on the 6502
				P(Flag_B) <= '1';
				if IR = "00000000" and RstCycle = '0' and (NMICycle = '1' or IRQCycle = '1') then
          if MCycle = "011" then
            -- B=0 in *copy* of P pushed onto the stack
            P(Flag_B) <= '0';
          elsif MCycle = "100" then
            P(Flag_I) <= '1';
          end if;
				end if;

				if SO_n_o = '1' and SO_n = '0' then
					P(Flag_V) <= '1';
				end if;
				if RstCycle = '1' and Mode_r /= "00" then
					P(Flag_1) <= '1';
					P(Flag_D) <= '0';
					P(Flag_I) <= '1';
				end if;
				P(Flag_1) <= '1';

				B_o     <= P(Flag_B);
				SO_n_o <= SO_n;
				IRQ_n_o <= IRQ_n;
				NMI_n_o <= NMI_n;
			end if;
		  end if;
		end if;
	end process;

---------------------------------------------------------------------------
--
-- Buses
--
---------------------------------------------------------------------------

	process (Res_n, Clk)
	begin
		if Res_n = '0' then
			BusA_r <= (others => '0');
			BusB <= (others => '0');
			AD <= (others => '0');
			BAL <= (others => '0');
			BAH <= (others => '0');
			DL <= (others => '0');
		elsif Clk'event and Clk = '1' then
		  if (Enable = '1') then
			if (Rdy = '1') then
				BusA_r <= BusA;
				BusB <= DI;

				case BAAdd is
				when "01" =>
					-- BA Inc
					AD <= std_logic_vector(unsigned(AD) + 1);
					BAL <= std_logic_vector(unsigned(BAL) + 1);
				when "10" =>
					-- BA Add
					BAL <= std_logic_vector(resize(unsigned(BAL(7 downto 0)),9) + resize(unsigned(BusA),9));
				when "11" =>
					-- BA Adj
					if BAL(8) = '1' then
						BAH <= std_logic_vector(unsigned(BAH) + 1);
					end if;
				when others =>
				end case;

				-- ehenciak : modified to use Y register as well (bugfix)
				if ADAdd = '1' then
				  if (AddY = '1') then
					AD <= std_logic_vector(unsigned(AD) + unsigned(Y(7 downto 0)));
				  else
					AD <= std_logic_vector(unsigned(AD) + unsigned(X(7 downto 0)));
				  end if;
				end if;

				if IR = "00000000" then
					BAL <= (others => '1');
					BAH <= (others => '1');
					if RstCycle = '1' then
						BAL(2 downto 0) <= "100";
					elsif NMICycle = '1' then
						BAL(2 downto 0) <= "010";
					else
						BAL(2 downto 0) <= "110";
					end if;
					if Set_addr_To_r = "11" then
						BAL(0) <= '1';
					end if;
				end if;


				if LDDI = '1' then
					DL <= DI;
				end if;
				if LDALU = '1' then
					DL <= ALU_Q;
				end if;
				if LDAD = '1' then
					AD <= DI;
				end if;
				if LDBAL = '1' then
					BAL(7 downto 0) <= DI;
				end if;
				if LDBAH = '1' then
					BAH <= DI;
				end if;
			end if;
		end if;
	  end if;
	end process;

	Break <= (BreakAtNA and not BAL(8)) or (PCAdd and not PCAdder(8));


	with Set_BusA_To select
		BusA <= DI when "000",
			ABC(7 downto 0) when "001",
			X(7 downto 0) when "010",
			Y(7 downto 0) when "011",
			std_logic_vector(S(7 downto 0)) when "100",
			P when "101",
			(others => '-') when others;

	with Set_Addr_To_r select
		A <= "0000000000000001" & std_logic_vector(S(7 downto 0)) when "01",
			DBR & "00000000" & AD when "10",
			"00000000" & BAH & BAL(7 downto 0) when "11",
			PBR & std_logic_vector(PC(15 downto 8)) & std_logic_vector(PCAdder(7 downto 0)) when others;

	with Write_Data_r select
		DO <= DL when "000",
			ABC(7 downto 0) when "001",
			X(7 downto 0) when "010",
			Y(7 downto 0) when "011",
			std_logic_vector(S(7 downto 0)) when "100",
			P when "101",
			std_logic_vector(PC(7 downto 0)) when "110",
			std_logic_vector(PC(15 downto 8)) when others;

-------------------------------------------------------------------------
--
-- Main state machine
--
-------------------------------------------------------------------------

	process (Res_n, Clk)
	begin
		if Res_n = '0' then
			MCycle <= "001";
			RstCycle <= '1';
			IRQCycle <= '0';
			NMICycle <= '0';
			NMIAct <= '0';
		elsif Clk'event and Clk = '1' then
		  if (Enable = '1') then
			if (really_rdy = '1') then
				if MCycle = LCycle or Break = '1' then
					MCycle <= "000";
					RstCycle <= '0';
					IRQCycle <= '0';
					NMICycle <= '0';
					if NMIAct = '1' then
						NMICycle <= '1';
					elsif IRQ_n_o = '0' and P(Flag_I) = '0' then
						IRQCycle <= '1';
					end if;
				else
					MCycle <= std_logic_vector(unsigned(MCycle) + 1);
				end if;

				if NMICycle = '1' then
					NMIAct <= '0';
				end if;
				if NMI_n_o = '1' and NMI_n = '0' then
					NMIAct <= '1';
				end if;
			end if;
		  end if;
		end if;
	end process;

end;
