--------------------------------------------------------------------------------
-- ****
-- T80(c) core. Attempt to finish all undocumented features and provide
--              accurate timings.
-- Version 350.
-- Copyright (c) 2018 Sorgelig
--  Test passed: ZEXDOC, ZEXALL, Z80Full(*), Z80memptr
--  (*) Currently only SCF and CCF instructions aren't passed X/Y flags check as
--      correct implementation is still unclear.
--
-- ****
-- T80(b) core. In an effort to merge and maintain bug fixes ....
--
-- Ver 301 parity flag is just parity for 8080, also overflow for Z80, by Sean Riddle
-- Ver 300 started tidyup
-- MikeJ March 2005
-- Latest version from www.fpgaarcade.com (original www.opencores.org)
--
-- ****
-- Z80 compatible microprocessor core
--
-- Version : 0247
-- Copyright (c) 2001-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/t80/
--
-- Limitations :
--
-- File history :
--
--  0214 : Fixed mostly flags, only the block instructions now fail the zex regression test
--  0238 : Fixed zero flag for 16 bit SBC and ADC
--  0240 : Added GB operations
--  0242 : Cleanup
--  0247 : Cleanup
--

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

entity T80_ALU is
    generic(
        Mode        : integer := 0;
        Flag_C      : integer := 0;
        Flag_N      : integer := 1;
        Flag_P      : integer := 2;
        Flag_X      : integer := 3;
        Flag_H      : integer := 4;
        Flag_Y      : integer := 5;
        Flag_Z      : integer := 6;
        Flag_S      : integer := 7
    );
    port(
        Arith16     : in std_logic;
        Z16         : in std_logic;
		WZ              : in  std_logic_vector(15 downto 0);
		XY_State		    : in  std_logic_vector(1 downto 0);
        ALU_Op      : in std_logic_vector(3 downto 0);
        IR          : in std_logic_vector(5 downto 0);
        ISet        : in std_logic_vector(1 downto 0);
        BusA        : in std_logic_vector(7 downto 0);
        BusB        : in std_logic_vector(7 downto 0);
        F_In        : in std_logic_vector(7 downto 0);
        Q           : out std_logic_vector(7 downto 0);
        F_Out       : out std_logic_vector(7 downto 0)
    );
end T80_ALU;

architecture rtl of T80_ALU is

    procedure AddSub(A : std_logic_vector;
                    B : std_logic_vector;
                    Sub : std_logic;
                    Carry_In : std_logic;
                    signal Res : out std_logic_vector;
                    signal Carry : out std_logic) is
        variable B_i        : unsigned(A'length - 1 downto 0);
        variable Res_i      : unsigned(A'length + 1 downto 0);
    begin
        if Sub = '1' then
            B_i := not unsigned(B);
        else
            B_i := unsigned(B);
        end if;
        Res_i := unsigned("0" & A & Carry_In) + unsigned("0" & B_i & "1");
        Carry <= Res_i(A'length + 1);
        Res <= std_logic_vector(Res_i(A'length downto 1));
    end;

    -- AddSub variables (temporary signals)
    signal  UseCarry        : std_logic;
    signal  Carry7_v        : std_logic;
    signal  Overflow_v      : std_logic;
    signal  HalfCarry_v     : std_logic;
    signal  Carry_v         : std_logic;
    signal  Q_v             : std_logic_vector(7 downto 0);

    signal  BitMask         : std_logic_vector(7 downto 0);

begin

    with IR(5 downto 3) select BitMask <= "00000001" when "000",
                                    "00000010" when "001",
                                    "00000100" when "010",
                                    "00001000" when "011",
                                    "00010000" when "100",
                                    "00100000" when "101",
                                    "01000000" when "110",
                                    "10000000" when others;

    UseCarry <= not ALU_Op(2) and ALU_Op(0);
    AddSub(BusA(3 downto 0), BusB(3 downto 0), ALU_Op(1), ALU_Op(1) xor (UseCarry and F_In(Flag_C)), Q_v(3 downto 0), HalfCarry_v);
    AddSub(BusA(6 downto 4), BusB(6 downto 4), ALU_Op(1), HalfCarry_v, Q_v(6 downto 4), Carry7_v);
    AddSub(BusA(7 downto 7), BusB(7 downto 7), ALU_Op(1), Carry7_v, Q_v(7 downto 7), Carry_v);

	-- bug fix - parity flag is just parity for 8080, also overflow for Z80
	process (Carry_v, Carry7_v, Q_v)
	begin
		if(Mode=2) then
			OverFlow_v <= not (Q_v(0) xor Q_v(1) xor Q_v(2) xor Q_v(3) xor
					   Q_v(4) xor Q_v(5) xor Q_v(6) xor Q_v(7));  else
			OverFlow_v <= Carry_v xor Carry7_v;
		end if;
	end process;

	process (Arith16, ALU_OP, F_In, BusA, BusB, IR, Q_v, Carry_v, HalfCarry_v, OverFlow_v, BitMask, ISet, Z16, WZ, XY_State)
        variable Q_t : std_logic_vector(7 downto 0);
        variable DAA_Q : unsigned(8 downto 0);
    begin
        Q_t := "--------";
        F_Out <= F_In;
        DAA_Q := "---------";
        case ALU_Op is
        when "0000" | "0001" |  "0010" | "0011" | "0100" | "0101" | "0110" | "0111" =>
            F_Out(Flag_N) <= '0';
            F_Out(Flag_C) <= '0';
            case ALU_OP(2 downto 0) is
            when "000" | "001" => -- ADD, ADC
                Q_t := Q_v;
                F_Out(Flag_C) <= Carry_v;
                F_Out(Flag_H) <= HalfCarry_v;
                F_Out(Flag_P) <= OverFlow_v;
            when "010" | "011" | "111" => -- SUB, SBC, CP
                Q_t := Q_v;
                F_Out(Flag_N) <= '1';
                F_Out(Flag_C) <= not Carry_v;
                F_Out(Flag_H) <= not HalfCarry_v;
                F_Out(Flag_P) <= OverFlow_v;
            when "100" => -- AND
                Q_t(7 downto 0) := BusA and BusB;
                F_Out(Flag_H) <= '1';
            when "101" => -- XOR
                Q_t(7 downto 0) := BusA xor BusB;
                F_Out(Flag_H) <= '0';
            when others => -- OR "110"
                Q_t(7 downto 0) := BusA or BusB;
                F_Out(Flag_H) <= '0';
            end case;
            if ALU_Op(2 downto 0) = "111" then -- CP
                    F_Out(Flag_X) <= BusB(3);
                    F_Out(Flag_Y) <= BusB(5);
            else
                F_Out(Flag_X) <= Q_t(3);
                F_Out(Flag_Y) <= Q_t(5);
            end if;
            if Q_t(7 downto 0) = "00000000" then
                F_Out(Flag_Z) <= '1';
                if Z16 = '1' then
                    F_Out(Flag_Z) <= F_In(Flag_Z);  -- 16 bit ADC,SBC
                end if;
            else
                F_Out(Flag_Z) <= '0';
            end if;
            F_Out(Flag_S) <= Q_t(7);
            case ALU_Op(2 downto 0) is
            when "000" | "001" | "010" | "011" | "111" => -- ADD, ADC, SUB, SBC, CP
            when others =>
                F_Out(Flag_P) <= not (Q_t(0) xor Q_t(1) xor Q_t(2) xor Q_t(3) xor
                    Q_t(4) xor Q_t(5) xor Q_t(6) xor Q_t(7));
            end case;
            if Arith16 = '1' then
                F_Out(Flag_S) <= F_In(Flag_S);
                F_Out(Flag_Z) <= F_In(Flag_Z);
                F_Out(Flag_P) <= F_In(Flag_P);
            end if;
        when "1100" =>
            -- DAA
            F_Out(Flag_H) <= F_In(Flag_H);
            F_Out(Flag_C) <= F_In(Flag_C);
            DAA_Q(7 downto 0) := unsigned(BusA);
            DAA_Q(8) := '0';
            if F_In(Flag_N) = '0' then
                -- After addition
                -- Alow > 9 or H = 1
                if DAA_Q(3 downto 0) > 9 or F_In(Flag_H) = '1' then
                    if (DAA_Q(3 downto 0) > 9) then
                        F_Out(Flag_H) <= '1';
                    else
                        F_Out(Flag_H) <= '0';
                    end if;
                    DAA_Q := DAA_Q + 6;
                end if;
                -- new Ahigh > 9 or C = 1
                if DAA_Q(8 downto 4) > 9 or F_In(Flag_C) = '1' then
                    DAA_Q := DAA_Q + 96; -- 0x60
                end if;
            else
                -- After subtraction
                if DAA_Q(3 downto 0) > 9 or F_In(Flag_H) = '1' then
                    if DAA_Q(3 downto 0) > 5 then
                        F_Out(Flag_H) <= '0';
                    end if;
                    DAA_Q(7 downto 0) := DAA_Q(7 downto 0) - 6;
                end if;
                if unsigned(BusA) > 153 or F_In(Flag_C) = '1' then
                    DAA_Q := DAA_Q - 352; -- 0x160
                end if;
            end if;
            F_Out(Flag_X) <= DAA_Q(3);
            F_Out(Flag_Y) <= DAA_Q(5);
            F_Out(Flag_C) <= F_In(Flag_C) or DAA_Q(8);
            Q_t := std_logic_vector(DAA_Q(7 downto 0));
            if DAA_Q(7 downto 0) = "00000000" then
                F_Out(Flag_Z) <= '1';
            else
                F_Out(Flag_Z) <= '0';
            end if;
            F_Out(Flag_S) <= DAA_Q(7);
            F_Out(Flag_P) <= not (DAA_Q(0) xor DAA_Q(1) xor DAA_Q(2) xor DAA_Q(3) xor
                DAA_Q(4) xor DAA_Q(5) xor DAA_Q(6) xor DAA_Q(7));
        when "1101" | "1110" =>
            -- RLD, RRD
            Q_t(7 downto 4) := BusA(7 downto 4);
            if ALU_Op(0) = '1' then
                Q_t(3 downto 0) := BusB(7 downto 4);
            else
                Q_t(3 downto 0) := BusB(3 downto 0);
            end if;
            F_Out(Flag_H) <= '0';
            F_Out(Flag_N) <= '0';
            F_Out(Flag_X) <= Q_t(3);
            F_Out(Flag_Y) <= Q_t(5);
            if Q_t(7 downto 0) = "00000000" then
                F_Out(Flag_Z) <= '1';
            else
                F_Out(Flag_Z) <= '0';
            end if;
            F_Out(Flag_S) <= Q_t(7);
            F_Out(Flag_P) <= not (Q_t(0) xor Q_t(1) xor Q_t(2) xor Q_t(3) xor
                Q_t(4) xor Q_t(5) xor Q_t(6) xor Q_t(7));
        when "1001" =>
            -- BIT
            Q_t(7 downto 0) := BusB and BitMask;
            F_Out(Flag_S) <= Q_t(7);
            if Q_t(7 downto 0) = "00000000" then
                F_Out(Flag_Z) <= '1';
                F_Out(Flag_P) <= '1';
            else
                F_Out(Flag_Z) <= '0';
                F_Out(Flag_P) <= '0';
            end if;
            F_Out(Flag_H) <= '1';
            F_Out(Flag_N) <= '0';
			if IR(2 downto 0) = "110" or XY_State /= "00" then
				F_Out(Flag_X) <= WZ(11);
				F_Out(Flag_Y) <= WZ(13);
			else
                F_Out(Flag_X) <= BusB(3);
                F_Out(Flag_Y) <= BusB(5);
            end if;
        when "1010" =>
            -- SET
            Q_t(7 downto 0) := BusB or BitMask;
        when "1011" =>
            -- RES
            Q_t(7 downto 0) := BusB and not BitMask;
        when "1000" =>
            -- ROT
            case IR(5 downto 3) is
            when "000" => -- RLC
                Q_t(7 downto 1) := BusA(6 downto 0);
                Q_t(0) := BusA(7);
                F_Out(Flag_C) <= BusA(7);
            when "010" => -- RL
                Q_t(7 downto 1) := BusA(6 downto 0);
                Q_t(0) := F_In(Flag_C);
                F_Out(Flag_C) <= BusA(7);
            when "001" => -- RRC
                Q_t(6 downto 0) := BusA(7 downto 1);
                Q_t(7) := BusA(0);
                F_Out(Flag_C) <= BusA(0);
            when "011" => -- RR
                Q_t(6 downto 0) := BusA(7 downto 1);
                Q_t(7) := F_In(Flag_C);
                F_Out(Flag_C) <= BusA(0);
            when "100" => -- SLA
                Q_t(7 downto 1) := BusA(6 downto 0);
                Q_t(0) := '0';
                F_Out(Flag_C) <= BusA(7);
            when "110" => -- SLL (Undocumented) / SWAP
                if Mode = 3 then
                    Q_t(7 downto 4) := BusA(3 downto 0);
                    Q_t(3 downto 0) := BusA(7 downto 4);
                    F_Out(Flag_C) <= '0';
                else
                    Q_t(7 downto 1) := BusA(6 downto 0);
                    Q_t(0) := '1';
                    F_Out(Flag_C) <= BusA(7);
                end if;
            when "101" => -- SRA
                Q_t(6 downto 0) := BusA(7 downto 1);
                Q_t(7) := BusA(7);
                F_Out(Flag_C) <= BusA(0);
            when others => -- SRL
                Q_t(6 downto 0) := BusA(7 downto 1);
                Q_t(7) := '0';
                F_Out(Flag_C) <= BusA(0);
            end case;
            F_Out(Flag_H) <= '0';
            F_Out(Flag_N) <= '0';
            F_Out(Flag_X) <= Q_t(3);
            F_Out(Flag_Y) <= Q_t(5);
            F_Out(Flag_S) <= Q_t(7);
            if Q_t(7 downto 0) = "00000000" then
                F_Out(Flag_Z) <= '1';
            else
                F_Out(Flag_Z) <= '0';
            end if;
            F_Out(Flag_P) <= not (Q_t(0) xor Q_t(1) xor Q_t(2) xor Q_t(3) xor
                Q_t(4) xor Q_t(5) xor Q_t(6) xor Q_t(7));
            if ISet = "00" then
                F_Out(Flag_P) <= F_In(Flag_P);
                F_Out(Flag_S) <= F_In(Flag_S);
                F_Out(Flag_Z) <= F_In(Flag_Z);
            end if;
        when others =>
            null;
        end case;
        Q <= Q_t;
    end process;

end;
