library ieee;
use ieee.std_logic_1164.all;	
use ieee.std_logic_unsigned.all;

entity cpu6805 is
port(clk, rst_b, IRQ, SCint: in std_logic;
dbus : inout std_logic_vector(7 downto 0);
abus : out std_logic_vector(12 downto 0);
wr: out std_logic);
end cpu6805;

architecture cpu1 of cpu6805 is
-- define registers
signal Opcode : bit_vector(7 downto 0) := (others => '0');
signal A, X, Md : std_logic_vector(7 downto 0) := (others => '0');
alias mode : bit_vector(3 downto 0) is Opcode(7 downto 4);
signal CCR: std_logic_vector(3 downto 0);  	-- CCR = I N Z C
alias I: std_logic is CCR(3);  alias N : std_logic is CCR(2);
alias Z : std_logic is CCR(1); alias C : std_logic is CCR(0);
signal PC, MAR, xadd, xadd1, xadd2: std_logic_vector (12 downto 0);
signal SP: std_logic_vector(5 downto 0);
alias PCH : std_logic_vector(4 downto 0) is PC(12 downto 8);
alias PCL : std_logic_vector(7 downto 0) is PC(7 downto 0);
alias MARH : std_logic_vector(4 downto 0) is MAR(12 downto 8);
alias MARL : std_logic_vector(7 downto 0) is MAR(7 downto 0);
signal va : std_logic_vector(2 downto 0);

type state_type is (reset, fetch, addr1, addr2, addX, data, 
	rd_mod_wr, writeback, testBR, push1, push2, push3, push4, push5, 
	cycle8, cycle9, cycle10, pop5, pop4, pop3, pop2, pop1);
signal ST, nST : state_type;

type decode_type is array(0 to 15) of bit_vector(15 downto 0);
signal opd : bit_vector(15 downto 0);
constant decode: decode_type := 
	(X"0001", X"0002", X"0004", X"0008", X"0010", X"0020", X"0040", X"0080",
	 X"0100", X"0200", X"0400", X"0800", X"1000", X"2000", X"4000", X"8000");
alias SUB : bit is opd(0); alias CMP : bit is opd(1); 
alias SUBC : bit is opd(2); alias CPX : bit is opd(3);
alias ANDa : bit is opd(4); alias BITa : bit is opd(5); 
alias LDA : bit is opd(6); alias STA : bit is opd(7);
alias EOR : bit is opd(8); alias ADC : bit is opd(9); 
alias ORA : bit is opd(10); alias ADD : bit is opd(11);
alias JMP : bit is opd(12); alias JSR : bit is opd(13); 
alias LDX : bit is opd(14); alias STX : bit is opd(15);
alias BRA : bit is opd(0); alias BRN : bit is opd(1); 
alias BHI : bit is opd(2); alias BLS : bit is opd(3);
alias BCC : bit is opd(4); alias BCS : bit is opd(5); 
alias BNE : bit is opd(6); alias BEQ : bit is opd(7);
alias BPL : bit is opd(10); alias BMI : bit is opd(11);
alias BMC : bit is opd(12); alias BMS : bit is opd(13); 
alias BIL : bit is opd(14); alias BIH : bit is opd(15);
alias TAX : bit is opd(7); alias CLC : bit is opd(8); 
alias SEC : bit is opd(9); alias CLI : bit is opd(10); 
alias SEI : bit is opd(11); alias RSP : bit is opd(12); 
alias NOP : bit is opd(13); alias TXA : bit is opd(15);
alias NEG : bit is opd(0); alias COM : bit is opd(3); 
alias LSR : bit is opd(4); alias ROR1 : bit is opd(6); 
alias ASR : bit is opd(7); alias LSL : bit is opd(8); 
alias ROL1 : bit is opd(9); alias DEC : bit is opd(10);
alias INC : bit is opd(12); alias TST : bit is opd(13); 
alias CLR : bit is opd(15); alias RTI : bit is opd(0); 
alias RTS : bit is opd(1); alias SWI : bit is opd(3);

-- define adressing mode = upper 4 bits of opcode
subtype ot is bit_vector(3 downto 0);
constant REL: ot:="0010"; constant DIRM: ot:="0011"; 
constant INHA: ot:="0100"; constant INHX: ot:="0101"; 
constant IX1M: ot:="0110"; constant IXM: ot:="0111";
constant INH1: ot:="1000"; constant INH2: ot:="1001"; 
constant IMM: ot:="1010"; constant DIR: ot:="1011"; 
constant EXT: ot:="1100"; constant IX2: ot:="1101";
constant IX1: ot:="1110"; constant IX: ot:="1111";

signal shiftout, op1_com, op2_com, op1, op2 : std_logic_vector(7 downto 0);
signal alu9 : std_logic_vector(8 downto 0);
	alias Cout : std_logic is alu9(8);
signal shiftin, Cin, newC : std_logic;
signal com2, and2, or2, xor2, rsh, lsh, clear : std_logic;
signal incPC, xadd2PC, db2PCH, MARH2PCH, MARL2PCL, clrPCH, 
	db2PCL, X2PCL : std_logic;
signal db2opcode, incSP, decSP, setSP, setSP1, setI, setI1, clrI : std_logic;
signal xadd2MAR, va2MAR, db2MARH, clrMARH, db2MARL, incMAR, X2MARL:std_logic;
signal ALU2A, db2A, ALU2X, db2X, db2Md, db2CCR, updateNZ, updateC :std_logic;
signal ALU2Md, Md2db : std_logic;
signal A2db, X2db, PCH2db, PCL2db, CCR2db, PC2ab, MAR2ab, SP2ab : std_logic;
signal com1, selA, selX, selMd1, selMd2, update : std_logic;
signal setC, clrC : std_logic;

constant hi_Z : std_logic_vector(7 downto 0) := (others => 'Z');
constant hi_Z13 : std_logic_vector(12 downto 0) := (others => 'Z');
constant zero: std_logic_vector(4 downto 0) := "00000";

begin

-- drive the data bus with tri-state buffers
dbus <= A when A2db='1' else hi_Z;
dbus <= X when X2db='1' else hi_Z;
dbus <= Md when Md2db='1' else hi_Z;
dbus <= "000"&PCH when PCH2db='1' else hi_Z;
dbus <= PCL when PCL2db='1' else hi_Z;
dbus <= "1110"&CCR when CCR2db='1' else hi_Z;

-- drive the address bus
abus <= MAR when MAR2ab='1' 
		else "0000011"&SP when SP2ab='1' 
		else PC;

-- define ALU input operand MUXes
op1 <= A when selA = '1'		-- MUX for op1
	else X when selX = '1'
	else Md when selMd1 = '1'
	else "00000000";
op2 <= Md when selMd2 = '1'		-- MUX for op2
	else "00000000";

-- ALU and shifter operations
op1_com <= not op1 when com1='1' else op1;		-- complementers
op2_com <= not op2 when com2='1' else op2;
alu9 <=  '0'&(op1_com and op2_com) when and2='1' -- adder/logic operations
	else  '0'&(op1_com or op2_com) when or2='1'
	else  '0'&(op1_com xor op2_com) when xor2='1'
	else  ('0'&op1_com) + ('0'&op2_com) + Cin;
shiftout <= shiftin&alu9(7 downto 1) when rsh='1'	-- shifter
	else alu9(6 downto 0)&shiftin when lsh='1'
	else "00000000" when clear = '1'
	else alu9(7 downto 0);
newC <= alu9(0) when rsh='1'				-- carry logic
	else alu9(7) when lsh='1'
	else '1' when setC = '1'
	else '0' when clrC = '1'
	else Cout xor (com1 or com2);

xadd <= xadd1 + xadd2; 			--address adder
opd <= decode(conv_integer(TO_stdlogicvector(Opcode(3 downto 0))));
		-- operation decoder
ALU_control: process (opd, mode, C, alu9)  
-- this process generates control signals for ALU operations
	variable reg_mem, rd_md_wr : boolean := FALSE;
begin
Cin <= '0'; shiftin<='0'; and2 <= '0'; or2 <= '0'; xor2 <= '0'; rsh<='0'; lsh<='0'; com1 <= '0'; com2 <= '0'; updateNZ<='1'; updateC<='1'; clrI <= '0'; setC <= '0'; clrC <= '0'; clear <= '0'; setI1<='0'; setSP1 <= '0';
ALU2A <= '1'; ALU2X <= '0'; ALU2Md <= '0';
selA <= '1'; selX <= '0'; selMd1 <= '0'; selMd2 <= '1';

reg_mem:= (mode = imm) or (mode = dir) or (mode = ext) or (mode = ix) or 
   		(mode = ix1) or (mode = ix2);
rd_md_wr := (mode = dirM) or (mode = inha) or (mode = inhx) or (mode = ix1m) 	or (mode = ixm);

if  reg_mem then				-- control signals for reg-mem ALU operations
		if ADD='1' then null; end if;		-- use defaults
		if ADC='1' then Cin<=C; end if;
		if SUB='1' then com2<='1'; Cin<='1'; end if;
		if SUBC='1' then com2<='1'; Cin<= not C; end if;
		if CMP='1' then com2 <= '1'; Cin <= '1'; ALU2A<='0'; end if;
		if CPX = '1' then selX <= '1'; selA <= '0'; com2 <= '1'; 
			Cin <= '1'; ALU2A<='0'; end if;
		if ANDa='1' then and2<= '1'; updateC<='0'; end if;
		if BITa='1' then and2<= '1'; ALU2A <='0'; updateC<='0'; end if;
		if ORA='1' then or2 <= '1'; updateC<='0'; end if;
		if EOR='1' then xor2<= '1';  updateC<='0'; end if;
		if LDA ='1' then updateC<='0'; selA <= '0'; end if; 
		if LDX ='1' then selA <= '0';
			updateC<='0'; ALU2A<='0'; ALU2X<='1'; end if;
	 	if ((STA or STX) = '1') then ALU2A <= '0'; updateC <='0'; selMd2<='0'; end if;  
			-- only update NZ flags
end if;

if rd_md_wr then
		-- control signals for rd_md_wr ALU/shifter operations
	selMd2 <= '0';  -- op2 is always zero for rd_md_wr
	if (mode /= inha) then
		ALU2A <= '0'; selA <= '0';  -- turn off defaults
		if mode = inhx then ALU2X <= '1'; selX <= '1';  -- op1 = X
		else	ALU2Md <= '1'; selMd1 <= '1';	end if;     -- op1 = Md
	end if;

	if NEG='1' then  Cin<='1'; com1 <= '1'; end if;
	if COM='1' then com1 <= '1'; end if;
	if DEC ='1' then updateC<='0'; com2 <= '1'; end if;	-- op2_com = -1
	if LSR ='1' then rsh<= '1'; end if;
	if ROR1 ='1' then rsh<='1'; shiftin<=C; end if;
	if ASR ='1' then rsh<='1'; shiftin<=alu9(7); end if;
	if LSL ='1' then lsh<='1'; end if;
	if ROL1 ='1' then lsh<='1'; shiftin<=C; end if;
	if INC ='1' then Cin<='1'; updateC<='0'; end if;
	if CLR ='1' then and2 <= '1'; updateC<='0'; end if; 
			-- and with 0 to clear
	if TST ='1' then updateC<='0'; end if;
end if; -- rd_md_wr

if (mode = inh2) then
	selMd2 <= '0'; updateC <= '0'; updateNZ <= '0';  -- op2 is zero
		-- A is always reloaded with A since ALU2A = '1'
	if TAX='1' then ALU2X<='1'; end if;
	if TXA='1' then selX <= '1'; selA <= '0'; end if;
	if CLC='1' then clrC <='1'; updateC <='1'; end if;
	if SEC='1' then setC <='1'; updateC <='1'; end if;
	if CLI='1' then clrI<='1'; end if;
	if SEI='1' then setI1<='1'; end if;
	if RSP='1' then setSP1<='1'; end if;
end if;
end process;

CPU_control: process (ST, rst_b, opd, mode, IRQ, SCint, CCR, MAR, X, PC, Md)
-- CPU state machine
   variable reg_mem, hw_interrupt, BR : boolean;
begin
nST <= reset; BR := FALSE; wr <= '0'; update <= '0';
xadd1 <= (others => '0'); xadd2 <= (others => '0'); va <= "000";
db2A <= '0'; db2X <= '0'; db2Md <= '0'; db2CCR <= '0'; db2opcode <= '0'; 
incPC <= '0'; xadd2PC <= '0'; db2PCH <= '0'; MARH2PCH <= '0'; MARL2PCL <= '0';
clrPCH <= '0'; db2PCL <= '0'; X2PCL <= '0'; xadd2MAR <= '0'; va2MAR <= '0';
db2MARH <= '0'; clrMARH <= '0'; db2MARL <= '0'; X2MARL <= '0'; incMAR <= '0';
A2db <= '0'; X2db <= '0'; CCR2db <= '0'; PCH2db <= '0'; PCL2db <= '0';
Md2db <= '0'; MAR2ab <= '0'; PC2ab <= '0'; SP2ab <= '0'; incSP <= '0'; 
decSP <= '0'; setI <= '0';  setSP <= '0'; 

reg_mem:= (mode = imm) or (mode = dir) or (mode = ext) or (mode = ix) or 
   		(mode = ix1) or (mode = ix2);
hw_interrupt := (I = '0') and (IRQ = '1' or SCint = '1');

if (rst_b = '0') then nST <= reset;
else
case ST is
when reset =>
	setSP <= '1'; 
	if (rst_b = '1') then nST <= cycle8; end if;
when fetch => 
	if (reg_mem and JMP = '0' and JSR = '0')
		then update <= '1'; end if; -- update registers if not JMP or JSR
 	if hw_interrupt then nST <= push1;
		else PC2ab<='1'; db2opcode<='1'; incPC<='1';	-- read next opcode
			nST <= addr1; end if;

when addr1 =>
case mode is 
	when inha | inhx => update <= '1'; nST<= fetch;
	when imm => PC2ab<= '1'; db2Md<='1'; incPC<='1';
		nST <= fetch;
	when inh1 =>
		if SWI = '1' then nST <= push1;
		elsif RTS = '1' then nST <= pop2; incSP<='1';
		elsif RTI = '1' then nST <= pop5; incSP<='1';
		end if;
	when inh2 => update <= '1'; nST <= fetch;
	when dir =>  PC2ab<='1';
		if JMP='1' then db2PCL<='1'; clrPCH<='1'; nST<=fetch;
   	else db2MARL<='1'; clrMARH<='1'; incPC <= '1';
			if JSR='1' then nST<=push1; else nST<=data; end if;
		end if;
	when dirM => PC2ab<='1'; db2MARL<='1'; clrMARH<='1';
		incPC<='1'; nST<=data;
	when ix => 
		if JMP='1' then X2PCL<='1'; clrPCH<='1'; nST<=fetch;
		else X2MARL<='1'; clrMARH<='1'; 
			if JSR='1' then nST<=push1; else nST<=data; end if;
		end if;
	when ixm => X2MARL<='1'; clrMARH<='1'; nST<=data;
	when ext | ix2 => PC2ab<='1'; db2MARH<='1';
		incPC<='1'; nST<=addr2;
	when ix1 | ix1m => PC2ab<='1'; db2MARL<='1'; clrMARH<='1';
		incPC<='1'; nST<=addX;
	when rel => PC2ab<='1'; db2Md<='1';
		incPC<='1'; nST<=testBR;
	when others => null;
end case;

when addr2 =>	PC2ab<='1';
	if (mode = ix2) then db2MARL<='1';incPC<='1'; nST <= addX;  --all [ix2]
	elsif (JMP = '1') then db2PCL<='1'; MARH2PCH<='1'; nST <= fetch; 
				-- JMP [ext]
	else db2MARL<='1';incPC<='1'; 				--JSR/others [ext]
		if (JSR = '1') then nST<=push1; else nST <= data; end if;
	end if;
when addX => xadd1<=MAR; xadd2<=zero&X;
	if (JMP='1' and reg_mem) then xadd2PC<='1';  nST <= fetch;
	else xadd2MAR<='1';
		if (JSR='1' and reg_mem) then nST<= push1; else nST<=data; end if;
	end if;

when data => MAR2ab<='1'; --nST <= fetch;
	if STA = '1' then wr<='1';  A2db<='1'; update <= '1';  -- update NZ flags
	elsif STX ='1' then wr<='1'; X2db<='1'; update <= '1';  -- update NZ flags
	else db2Md <= '1'; end if;							-- read from data bus
	if ((mode = dirM) or (mode = ixm) or (mode = ix1m)) then
		nST <= rd_mod_wr; else nST <= fetch; end if;

when rd_mod_wr => update <= '1'; nST <= writeback;	-- update Md

when writeback => wr<='1'; MAR2ab<='1'; Md2db<='1';	-- write Md to memory 
	nST <= fetch;

when testBR =>
	if BRA = '1' then BR := TRUE; end if;
	if BRN = '1' then BR := FALSE; end if;
	if BHI = '1' then BR := (C or Z) = '0';  end if;
	if BLS = '1' then BR := (C or Z) = '1'; end if;
	if BCC = '1' then BR := C = '0'; end if;
	if BCS = '1' then BR := C = '1'; end if;
	if BNE = '1' then BR := Z = '0'; end if;
	if BEQ = '1' then BR := Z = '1'; end if;
	if BPL = '1' then BR := N = '0'; end if;
	if BMI = '1' then BR := N = '1'; end if;
	if BMC = '1' then BR := I = '0'; end if;
	if BMS = '1' then BR := I = '1'; end if;
	-- set inputs to address adder
	xadd1<=PC; xadd2<=Md(7)&Md(7)&Md(7)&Md(7)&Md(7)&Md;  -- sign extend Md
	if BR then xadd2PC<='1'; end if;			 -- PC <= xadd1 + xadd2
	nST <= fetch;

when push1 => wr<='1'; SP2ab<='1'; PCL2db<='1'; 
	decSP <= '1'; nST <= push2;
when push2 => wr<='1'; SP2ab<='1'; PCH2db<='1'; decSP <= '1'; 
	if (hw_interrupt or SWI = '1') then nST <= push3;
	else MARH2PCH <= '1'; MARL2PCL <= '1'; nST <= fetch; end if; 
					-- PC <= MAR (execute JSR)
when push3 => wr<='1'; SP2ab<='1'; X2db<='1'; 
	decSP <= '1'; nST <= push4;
when push4 => wr<='1'; SP2ab<='1'; A2db<='1'; 
	decSP <= '1'; nST <= push5;
when push5 => wr<='1'; SP2ab<='1';  CCR2db<='1'; 
	decSP <= '1'; nST <= cycle8;
when cycle8 => 
	if SWI = '1' then va <= "110"; 	-- 3 bits of interrupt vector addr
	elsif IRQ = '1' then va <= "101";
	elsif SCint = '1' then va <= "011";
	else va <= "111"; 				-- default for reset vector
	end if;
	va2MAR <= '1';  setI <= '1';  nST <= cycle9;
when cycle9 => MAR2ab <= '1'; db2PCH <= '1';	-- get interrupt vector
	incMAR <= '1'; nST <= cycle10;
when cycle10 => MAR2ab <= '1'; db2PCL <= '1';
	nST <= fetch;
when pop5 => SP2ab<='1'; db2CCR<='1'; 		-- restore registers
	incSP<='1'; nST <=pop4;
when pop4 => SP2ab<='1'; db2A<='1'; 
	incSP<='1'; nST <=pop3;
when pop3 => SP2ab<='1'; db2X<='1'; 
	incSP<='1'; nST <=pop2;
when pop2 => SP2ab<='1'; db2PCH<='1'; 
	incSP<='1'; nST <=pop1;
when pop1 => SP2ab<='1'; db2PCL<='1'; 
	nST <=fetch ;
end case;
end if; -- if (rst_b = '0')
end process;

update_reg: process
begin
wait until CLK'event and CLK='1';
ST <= nST;
if incPC = '1' then PC <= PC + 1; end if;
if xadd2PC = '1' then PC <= xadd; end if;
if db2PCH = '1' then PCH <= dbus(4 downto 0); end if;
if MARH2PCH = '1' then PCH <= MARH; end if;
if MARL2PCL = '1' then PCL <= MARL; end if;
if clrPCH = '1' then PCH <= "00000"; end if;
if db2PCL = '1' then PCL <= dbus; end if;
if X2PCL = '1' then PCL <= X; end if;

if db2opcode= '1' then Opcode <= TO_BITVECTOR(dbus); end if;
if incSP = '1' then SP <= SP+1; end if;
if decSP = '1' then SP <= SP - 1; end if;
if (setSP = '1' or setSP1 = '1') then SP <= "111111"; end if;

if xadd2MAR = '1' then MAR <= xadd; end if;
if va2MAR = '1' then MAR <= "111111111"&va&'0'; end if;
if db2MARH = '1' then MARH <= dbus(4 downto 0); end if;
if clrMARH = '1' then MARH <= "00000"; end if;
if db2MARL = '1' then MARL <= dbus;  end if;
if X2MARL = '1' then MARL <= X; end if;
if incMAR = '1' then MAR(0) <= '1'; end if;	
   -- MAR(0) is always '0' at this time so incrementer is not needed

if db2A='1' then A <= dbus; end if;
if db2X='1' then X<=dbus; end if;
if db2Md = '1' then Md <=dbus; end if;
if db2CCR = '1' then CCR<= dbus(3 downto 0); end if;

if (update = '1') then
	if (ALU2A = '1') then A <= Shiftout; end if; 
	if (ALU2X = '1') then X <= Shiftout; end if;
	if (ALU2Md = '1') then Md <= Shiftout; end if;
	if updateNZ='1' then N <= Shiftout(7);
		if Shiftout = "00000000" then Z <= '1'; else Z <= '0'; end if;
	end if;
	if  updateC='1' then C <= newC; end if;
end if;
	if (setI = '1' or setI1 = '1') then I <= '1'; end if;
	if clrI = '1' then I <= '0'; end if;
end process;

end cpu1;


