library ieee;
use ieee.std_logic_1164.all;

entity apu is 
    port (  clk         : in std_logic;
            ce_n        : in std_logic;
            rst_n       : in std_logic;
            r_nw        : inout std_logic;
            cpu_addr    : inout std_logic_vector (15 downto 0);
            cpu_d       : inout std_logic_vector (7 downto 0);
            rdy         : out std_logic
    );
end apu;

architecture rtl of apu is


component d_flip_flop
    generic (
            dsize : integer := 8
            );
    port (
            clk     : in std_logic;
            res_n   : in std_logic;
            set_n   : in std_logic;
            we_n    : in std_logic;
            d       : in std_logic_vector (dsize - 1 downto 0);
            q       : out std_logic_vector (dsize - 1 downto 0)
        );
end component;

component counter_register
    generic (
        dsize       : integer := 8;
        inc         : integer := 1
    );
    port (  clk         : in std_logic;
            rst_n       : in std_logic;
            ce_n        : in std_logic;
            we_n        : in std_logic;
            d           : in std_logic_vector(dsize - 1 downto 0);
            q           : out std_logic_vector(dsize - 1 downto 0)
    );
end component;

constant dsize     : integer := 8;
constant OAM_DMA   : std_logic_vector(4 downto 0) := "10100";
constant OAM_JP1   : std_logic_vector(4 downto 0) := "10110";
constant OAM_JP2   : std_logic_vector(4 downto 0) := "10111";

--oamaddr=0x2003
constant OAMADDR   : std_logic_vector(15 downto 0) := "0010000000000011";
--oamdata=0x2004
constant OAMDATA   : std_logic_vector(15 downto 0) := "0010000000000100";

signal clk_n            : std_logic;

signal oam_data         : std_logic_vector (dsize - 1 downto 0);

signal oam_bus_ce_n     : std_logic;

signal dma_addr         : std_logic_vector (dsize * 2 - 1 downto 0);
signal dma_cnt_ce_n     : std_logic_vector(0 downto 0);
signal dma_cnt_ce       : std_logic;
signal dma_start_n      : std_logic;
signal dma_end_n        : std_logic;
signal dma_process_n    : std_logic;
signal dma_rst_n        : std_logic;
signal dma_status_we_n  : std_logic;
signal dma_status       : std_logic_vector(1 downto 0);
signal dma_next_status  : std_logic_vector(1 downto 0);

constant DMA_ST_IDLE    : std_logic_vector(1 downto 0) := "00";
constant DMA_ST_SETUP   : std_logic_vector(1 downto 0) := "01";
constant DMA_ST_PROCESS : std_logic_vector(1 downto 0) := "10";

begin

    clk_n <= not clk;

    dma_rst_n <= not dma_process_n;

    dma_l_up_inst : counter_register generic map (1, 1)
            port map (clk_n, dma_rst_n, dma_process_n, '1', (others => '0'), dma_cnt_ce_n);

    dma_cnt_ce <= not dma_cnt_ce_n(0);
    dma_l_inst : counter_register generic map (dsize, 1)
            port map (clk, dma_rst_n, dma_cnt_ce, '1', (others => '0'), 
                                                dma_addr(dsize - 1 downto 0));
    dma_h_inst : d_flip_flop generic map(dsize)
            port map (clk_n, '1', '1', dma_start_n, cpu_d, 
                                                dma_addr(dsize * 2 - 1 downto dsize));

    dma_status_inst : d_flip_flop generic map(2)
            port map (clk_n, rst_n, '1', dma_status_we_n, dma_next_status, dma_status);

    dma_val_inst : d_flip_flop generic map(dsize)
            port map (clk_n, rst_n, '1', dma_process_n, cpu_d, oam_data);

    --apu register access process
    reg_set_p : process (rst_n, ce_n, r_nw, cpu_addr, cpu_d)
    begin
        if (rst_n = '1' and ce_n = '0') then
            if (r_nw = '0') then
                --apu write
                cpu_d <= (others => 'Z');
                if (cpu_addr(4 downto 0) = OAM_DMA) then
                    dma_start_n <= '0';
                else
                    dma_start_n <= '1';
                end if;
            elsif (r_nw = '1') then
                --apu read
                if (cpu_addr(4 downto 0) = OAM_JP1) then
                    cpu_d <= (others => '0');
                elsif (cpu_addr(4 downto 0) = OAM_JP2) then
                    cpu_d <= (others => '0');
                else
                    --return dummy zero vale.
                    cpu_d <= (others => '0');
                end if;
            end if;
        else
            cpu_d <= (others => 'Z');
            dma_start_n <= '1';
        end if; --if (rst_n = '1' and ce_n = '0') 
    end process;

    --dma operation process
    dma_p : process (rst_n, clk)
    begin
        if (rst_n = '0') then
            dma_next_status <= DMA_ST_IDLE;
            dma_end_n <= '1';
            rdy <= '1';
            dma_process_n <= '1';
            cpu_addr <= (others => 'Z');
            cpu_d <= (others => 'Z');
            r_nw <= 'Z';
        else
            if (clk'event and clk = '0') then
                if (dma_start_n = '0') then
                    --pull rdy pin down to stop cpu bus accessing.
                    rdy <= '0';
                end if;
                if (dma_end_n = '0') then
                    --pull rdy pin up to re-enable cpu bus accessing.
                    rdy <= '1';
                end if;
            end if;

            if (clk'event and clk = '1') then
                if (dma_status = DMA_ST_IDLE) then
                    if (dma_start_n = '0') then
                        dma_status_we_n <= '0';
                        dma_next_status <= DMA_ST_SETUP;
                    end if;
                    dma_process_n <= '1';
                    dma_end_n <= '1';
                    cpu_addr <= (others => 'Z');
                    cpu_d <= (others => 'Z');
                    r_nw <= 'Z';
                elsif (dma_status = DMA_ST_SETUP) then
                    cpu_addr <= OAMADDR;
                    cpu_d <= (others => '0');
                    r_nw <= '0';
                    dma_next_status <= DMA_ST_PROCESS;
                elsif (dma_status = DMA_ST_PROCESS) then
                    if (dma_addr(dsize - 1 downto 0) = "11111111" and dma_cnt_ce_n(0) = '1') then
                        dma_status_we_n <= '0';
                        dma_next_status <= DMA_ST_IDLE;
                        dma_end_n <= '0';
                    else
                        dma_status_we_n <= '1';
                        dma_process_n <= '0';
                        dma_end_n <= '1';
                    end if;

                    if (dma_cnt_ce_n(0) = '0') then
                        r_nw <= '1';
                        cpu_addr <= dma_addr;
                        cpu_d <= (others => 'Z');
                    elsif (dma_cnt_ce_n(0) = '1') then
                        r_nw <= '0';
                        cpu_addr <= OAMDATA;
                        cpu_d <= oam_data;
                    end if;
                end if;--if (dma_status = DMA_ST_IDLE) then
            end if;--if (clk'event and clk = '1') then
        end if;
    end process;

end rtl;

