-- A6532 RAM-I/O-Timer (RIOT)
-- Copyright 2006, 2010 Retromaster
--
--  This file is part of A2601.
--
--  A2601 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 3 of the License,
--  or any later version.
--
--  A2601 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 A2601.  If not, see <http://www.gnu.org/licenses/>.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity ramx8 is
    generic(addr_width : integer := 7);
    port(clk: in std_logic;
         r: in std_logic;
         d_in: in std_logic_vector(7 downto 0);
         d_out: out std_logic_vector(7 downto 0);
         a: in std_logic_vector(addr_width - 1 downto 0));
end ramx8;

architecture arch of ramx8 is
    type ram_type is array (0 to 2**addr_width - 1) of
        std_logic_vector(7 downto 0);
    signal ram: ram_type;
begin

    process (clk, r, a)
    begin
        if (clk'event and clk = '1') then
            if (r = '1') then
                d_out <= ram(to_integer(unsigned(a)));
            else
                ram(to_integer(unsigned(a))) <= d_in;
            end if;
        end if;
    end process;

end arch;

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;

entity A6532 is
    port(clk: in std_logic;
         ph2_en: in std_logic;
         r: in std_logic;
         rs: in std_logic;
         cs: in std_logic;
         irq: out std_logic;
         d: inout std_logic_vector(7 downto 0) := "ZZZZZZZZ";
         pa: inout std_logic_vector(7 downto 0);
         pb: inout std_logic_vector(7 downto 0);
         pa7: in std_logic;
         a: in std_logic_vector(6 downto 0));
end A6532;

architecture arch of A6532 is

    signal pa_reg: std_logic_vector(7 downto 0) := "00000000";
    signal pb_reg: std_logic_vector(7 downto 0) := "00000000";
    signal pa_ddr: std_logic_vector(7 downto 0) := "00000000";
    signal pb_ddr: std_logic_vector(7 downto 0) := "00000000";
    signal pa_in: std_logic_vector(7 downto 0);
    signal pb_in: std_logic_vector(7 downto 0);

    signal timer: std_logic_vector(7 downto 0) := "00000000";
    signal timer_write: std_logic;
    signal timer_read: std_logic;
    signal timer_intr: std_logic := '0';
    signal timer_intvl: std_logic_vector(1 downto 0) := "11";
    signal timer_dvdr: std_logic_vector(10 downto 0) := "00000000001";
    signal timer_inc: std_logic;
    signal timer_irq_en: std_logic := '0';

    signal edge_pol: std_logic := '0';
    signal edge_irq_en: std_logic := '0';
    signal edge_intr_lo: std_logic := '0';
    signal edge_intr_hi: std_logic := '0';
    signal edge_intr: std_logic;

    signal intr_read: std_logic;

    signal ram_d_out: std_logic_vector(7 downto 0);
    signal ram_r: std_logic;

begin

    io: for i in 0 to 7 generate
        -- TEMPORARY FIX
        --pa(i) <= pa_reg(i) when pa_ddr(i) = '1' else 'Z';
        --pb(i) <= pb_reg(i) when pb_ddr(i) = '1' else 'Z';
        pa(i) <= 'Z';
        pb(i) <= 'Z';
        pa_in(i) <= pa(i);
        pb_in(i) <= pb(i) when pb_ddr(i) = '0' else pb_reg(i);
    end generate;

    ram: work.ramx8 port map(clk, ram_r, d, ram_d_out, a);

    ram_r <= (not rs and r) or rs or not cs;

    timer_write <= (not r) and rs and a(2) and a(4) and cs;
    timer_read <= r and rs and a(2) and (not a(0)) and cs;
    intr_read <=  r and rs and a(0) and a(2) and cs;

    irq <= not ((timer_intr and timer_irq_en) or (edge_intr and edge_irq_en));
    edge_intr <= edge_intr_lo when edge_pol = '0' else edge_intr_hi;

    process(clk, ph2_en, cs, r, rs, a, ram_d_out, pa_in, pa_ddr, pb_in, pb_ddr, timer, timer_intr, edge_intr)
    begin
        if r = '1' then
            if (cs = '0') then
                d <= "ZZZZZZZZ";
            elsif rs = '0' then
                d <= ram_d_out;
            elsif a(2) = '0' then
                case a(1 downto 0) is
                    when "00" =>
                        d <= pa_in;
                    when "01" =>
                        d <= pa_ddr;
                    when "10" =>
                        d <= pb_in;
                    when "11" =>
                        d <= pb_ddr;
                    when others =>
                        null;
                end case;
            elsif a(0) = '0' then
                d <= timer;
            elsif a(0) = '1' then
                d <= timer_intr & edge_intr & "000000";
            else
                d <= "--------";
            end if;
        else
            d <= "ZZZZZZZZ";
            if (clk'event and clk = '1' and cs = '1' and ph2_en = '1') then
                if (rs = '1') then
                    if a(2) = '0' then
                        case a(1 downto 0) is
                            when "00" =>
                                --pa_reg <= d;
                            when "01" =>
                                pa_ddr <= d;
                            when "10" =>
                                pb_reg <= d;
                            when "11" =>
                                pb_ddr <= d;
                            when others =>
                                null;
                        end case;
                    elsif a(4) = '0' then
                        edge_pol <= a(0);
                        edge_irq_en <= a(1);
                    end if;
                end if;
            end if;
        end if;
    end process;

    process(pa7, intr_read)
    begin
        if (intr_read = '1') then
            edge_intr_lo <= '0';
        elsif (pa7'event and pa7 = '1') then
            edge_intr_lo <= '1';
        end if;

        if (intr_read = '1') then
            edge_intr_hi <= '0';
        elsif (pa7'event and pa7 = '0') then
            edge_intr_hi <= '1';
        end if;
    end process;

    with timer_intvl select timer_inc <=
        timer_dvdr(0) when "00",
        timer_dvdr(3) when "01",
        timer_dvdr(6) when "10",
        timer_dvdr(10) when "11",
        '-' when others;

    process(clk, ph2_en)
    begin
        if (clk'event and clk = '1' and ph2_en = '1') then
            if (timer_inc = '1') then
                timer_dvdr <= "00000000001";
            else
                timer_dvdr <= timer_dvdr + 1;
            end if;

            if (timer_write = '1') then
                timer <= d - 1;
                timer_intvl <= a(1 downto 0);
                timer_irq_en <= a(3);
                timer_dvdr <= "00000000001";
            elsif (timer_intr = '0') then
                timer <= timer - timer_inc;
            elsif (not (timer = X"00")) then
                timer <= timer - 1;
            end if;

            if (timer = X"00" and timer_inc = '1' and timer_intr = '0' and timer_write = '0') then
                timer_intr <= '1';
            elsif (timer_read = '1' or timer_write = '1') then
                timer_intr <= '0';
            end if;
        end if;
    end process;

end arch;
