----------------------------------------------------------------------------------
-- Company: n/a
-- Engineer: A. Fachat
-- 
-- Create Date:    12:37:11 05/07/2011 
-- Design Name:    SPI65B
-- Module Name:    SPI6502B - Behavioral 
-- Project Name:   CS/A NETUSB 2.0
-- Target Devices: CS/A NETUSB 2.0
-- Tool versions: 
-- Description:      An SPI interface for 6502-based computers (or compatible).
--                   modelled after the SPI65 interface by Daryl Rictor 
--                   (see http://sbc.rictor.org/io/65spi.html )
--                   This implementation here, however, is a complete reimplementation
--                   as the ABEL language of the original implementation is not supported
--                   by ISE anymore. 
--                   Also I added the interrupt input handling, replacing four of the 
--                   original SPI select outputs with four interrupt inputs
--                   Also folded out the single MISO input into one input for each of the
--                   four supported devices, reducing external parts count again by one.
--
-- Dependencies: 
--
-- Revision: 
-- Revision 0.01 - File Created
-- Revision 0.02 - removed spiclk and replaced with clksrc and clkcnt_is_zero combination,
--                 to drive up SPI clock to half of input clock (and not one fourth only as before)
--                 unfortunately that costed one divisor bit to fit into the CPLD
-- Additional Comments: 
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;


entity AppleIISd is
Port (
        data : inout  STD_LOGIC_VECTOR (7 downto 0);
        nrw : in  STD_LOGIC;
        nreset : in  STD_LOGIC;
        addr : in  STD_LOGIC_VECTOR (1 downto 0);
        clk_phi0 : in  STD_LOGIC;
        ndev_sel : in  STD_LOGIC;
        clk_7m : in  STD_LOGIC;
        spi_miso: in std_logic;
        spi_mosi : out  STD_LOGIC;
        spi_sclk : out  STD_LOGIC;
        spi_Nsel : out  STD_LOGIC;
        wp : in  STD_LOGIC;
        card : in  STD_LOGIC;
        led : out  STD_LOGIC;

        a8 : in  std_logic;
        a9 : in  std_logic;
        a10 : in  std_logic;
        nio_sel : in  std_logic;
        nio_stb : in  std_logic;
        b8 : out  std_logic;
        b9 : out  std_logic;
        b10 : out  std_logic;
        noe : out  std_logic;
        ng : out  std_logic
    );

    constant DIV_WIDTH : integer := 3;

end AppleIISd;

architecture Behavioral of AppleIISd is
    
    -- interface signals
    signal selected: std_logic;
    signal reset: std_logic;
    signal is_read: std_logic;
    signal int_din: std_logic_vector (7 downto 0);
    signal int_dout: std_logic_vector (7 downto 0);
    
    signal int_mosi: std_logic;
    signal int_miso: std_logic;
    signal int_sclk: std_logic;
    
    --------------------------
    -- internal state
    signal spidatain: std_logic_vector (7 downto 0);
    signal spidataout: std_logic_vector (7 downto 0);
    signal inited: std_logic;   -- card initialized
    signal inited_set: std_logic;
    
    -- spi register flags
    signal tc: std_logic;       -- transmission complete; cleared on spi data read
    signal bsy: std_logic;      -- SPI busy
    signal frx: std_logic;      -- fast receive mode
    signal tmo: std_logic;      -- tri-state mosi
    signal ece: std_logic;      -- external clock enable; 0=phi2, 1=external clock
    signal cpol: std_logic;     -- shift clock polarity; 0=rising edge, 1=falling edge
    signal cpha: std_logic;     -- shift clock phase; 0=leading edge, 1=rising edge
    
    signal divisor: std_logic_vector(DIV_WIDTH-1 downto 0);
    signal slavesel: std_logic;     -- slave select output (0=selected)
    
    --------------------------
    -- helper signals

    -- shift engine
    signal start_shifting: std_logic;   -- shifting data
    signal shifting2: std_logic;    -- shifting data
    signal shiftdone: std_logic;    -- shifting data done
    signal shiftcnt: std_logic_vector(3 downto 0);          -- shift counter (5 bit)
    
    -- spi clock
    signal clksrc: std_logic;                                   -- clock source (phi2 or clk_7m)
    -- TODO divcnt is not used at all??
    signal divcnt: std_logic_vector(DIV_WIDTH-1 downto 0);          -- divisor counter
    signal shiftclk : std_logic;

    component AddressDecoder
    port ( 
        A8     : in    std_logic; 
        A9     : in    std_logic; 
        A10    : in    std_logic; 
        CLK : in    std_logic; 
        NDEV_SEL : in std_logic;
        NIO_SEL : in    std_logic; 
        NIO_STB : in    std_logic; 
        RNW  : in    std_logic;
        B8   : out   std_logic; 
        B9   : out   std_logic; 
        B10  : out   std_logic; 
        NOE     : out   std_logic
    );
    end component;
    
begin
    add_dec : AddressDecoder
    port map (
        A8 => a8,      
        A9 => a9,
        A10 => a10,
        CLK => clk_7m,
        NDEV_SEL => ndev_sel,
        NIO_SEL => nio_sel,
        NIO_STB => nio_stb,
        RNW => nrw,
        B8 => b8,
        B9 => b9,
        B10 => b10,
        NOE => noe);
    
    led <= not (inited_set);
    --led <= not (bsy or not slavesel);
    ng <=  ndev_sel and nio_sel and nio_stb;
    bsy <= start_shifting or shifting2;
    
    process(clk_7m, reset, card, inited_set)
    begin
        if(reset = '1' or card = '1') then
            inited <= '0';
        elsif rising_edge(inited_set) then
            inited <= '1';
        end if;
    end process;
    
    process(start_shifting, shiftdone, shiftclk)
    begin
        if (rising_edge(shiftclk)) then
            if (shiftdone = '1') then
                shifting2 <= '0';
            else
                shifting2 <= start_shifting;
            end if;
        end if; 
    end process;

    process(shiftcnt, reset, shiftclk) 
    begin
        if (reset = '1') then
            shiftdone <= '0';
        elsif (rising_edge(shiftclk)) then
            if (shiftcnt = "1111") then
                shiftdone <= '1';
            else
                shiftdone <= '0';
            end if;
        end if;
    end process;
    
    process(reset, shifting2, shiftcnt, shiftclk)
    begin
        if (reset='1') then
            shiftcnt <= (others => '0');
        elsif (rising_edge(shiftclk)) then
            if (shifting2 = '1') then
                -- count phase
                shiftcnt <= shiftcnt + 1;               
            else
                shiftcnt <= (others => '0');
            end if;
        end if;
    end process;

    inproc: process(reset, shifting2, shiftcnt, shiftclk, spidatain, int_miso)
    begin
        if (reset='1') then
            spidatain <= (others => '0');
        elsif (rising_edge(shiftclk)) then
            if (shifting2 = '1' and shiftcnt(0) = '1') then
                    -- shift in to input register
                    spidatain (7 downto 1) <= spidatain (6 downto 0);
                    spidatain (0) <= int_miso;
            end if;
        end if;
    end process;

    outproc: process(reset, shifting2, spidataout, cpol, cpha, shiftcnt, shiftclk)
    begin
        if (reset='1') then
            int_mosi <= '1';
            int_sclk <= cpol;
        else
            -- clock is sync'd
            if (rising_edge(shiftclk)) then
                if (shifting2='0' or shiftdone = '1') then
                    int_mosi <= '1';
                    int_sclk <= cpol;
                else
                    -- output data directly from output register
                    case shiftcnt(3 downto 1) is
                        when "000" => int_mosi <= spidataout(7);
                        when "001" => int_mosi <= spidataout(6);
                        when "010" => int_mosi <= spidataout(5);
                        when "011" => int_mosi <= spidataout(4);
                        when "100" => int_mosi <= spidataout(3);
                        when "101" => int_mosi <= spidataout(2);
                        when "110" => int_mosi <= spidataout(1);
                        when "111" => int_mosi <= spidataout(0);
                        when others => int_mosi <= '1';
                    end case;
                    int_sclk <= cpol xor cpha xor shiftcnt(0);
                end if;
            end if;
        end if;
    end process;


    -- shift operation enable
    shiften: process(reset, selected, nrw, addr, frx, shiftdone)
    begin
        -- start shifting
        if (reset='1' or shiftdone='1') then
            start_shifting <= '0';
        elsif (falling_edge(selected) and addr="00" and (frx='1' or nrw='0')) then
            -- access to register 00, either write (nrw=0) or fast receive bit set (frx)
            -- then both types of access (write but also read)
            start_shifting <= '1';
        end if;
    end process;

    --------------------------
    -- spiclk - spi clock generation
    -- spiclk is still 2 times the freq. than sclk
    clksrc <= clk_phi0 when (ece = '0') else clk_7m;
    
    -- is a pulse signal to allow for divisor==0
    --shiftclk <= clksrc when divcnt = "000000" else '0';
    shiftclk <= clksrc when bsy = '1' else '0';
    
    clkgen: process(reset, divisor, clksrc)
    begin
        if (reset='1') then
            divcnt <= divisor;
        elsif (falling_edge(clksrc)) then
            if (shiftclk = '1') then
                divcnt <= divisor;
            else
                divcnt <= divcnt - 1;
            end if;
        end if;
    end process;
    
    --------------------------
    -- interface section
    -- inputs
    reset <= not (nreset);
    selected <= not(ndev_sel);
    int_din <= data;   
    int_miso <= (spi_miso and not slavesel);
    
    process(selected, clk_7m)
    begin
        if(selected = '0') then
            is_read <= '0';
        elsif(rising_edge(clk_7m) and selected = '1' and clk_phi0 = '1' and  nrw = '1') then
            is_read <= '1';
        end if;
    end process;
    
    -- outputs
    data <= int_dout when (is_read='1') else (others => 'Z');       -- data bus tristate
    spi_sclk <= int_sclk;
    spi_mosi <= int_mosi when tmo='0' else 'Z';     -- mosi tri-state
    spi_Nsel <= slavesel;

    
    tc_proc: process (selected, shiftdone) 
    begin
        if (shiftdone = '1') then
            tc <= '1';
        elsif (falling_edge(selected) and addr="00") then
            tc <= '0';
        end if;
    end process;
    
    --------------------------
    -- cpu register section
    -- cpu read
    cpu_read: process (is_read, addr, 
            spidatain, tc, bsy, frx, tmo, ece, cpol, cpha, divisor,
            slavesel, wp, card, inited)
    begin
        if (is_read = '1') then 
            case addr is
                when "00" =>        -- read SPI data in
                    int_dout <= spidatain;
                when "01" =>        -- read status register
                    int_dout(0) <= cpha;
                    int_dout(1) <= cpol;
                    int_dout(2) <= ece;
                    int_dout(3) <= tmo;
                    int_dout(4) <= frx;
                    int_dout(5) <= bsy;
                    int_dout(6) <= '0';
                    int_dout(7) <= tc;
                when "10" =>        -- read sclk divisor
                    int_dout(DIV_WIDTH-1 downto 0) <= divisor;
                    int_dout(7 downto 3) <= (others => '0');
                when "11" =>        -- read slave select / slave interrupt state
                    int_dout(0) <= slavesel;
                    int_dout(4 downto 1) <= (others => '0');
                    int_dout(5) <= wp;
                    int_dout(6) <= card;
                    int_dout(7) <= inited;
                when others => 
                    int_dout <= (others => '0');
            end case;
        else
            int_dout <= (others => '0');
        end if;
    end process;

    -- cpu write 
    cpu_write: process(reset, selected, nrw, addr, int_din, card, inited)
    begin
        if (reset = '1') then
            cpha <= '0';
            cpol <= '0';
            ece <= '0';
            tmo <= '0';
            frx <= '0';
            slavesel <= '1';
            divisor <= (others => '0');
            spidataout <= (others => '1');
            inited_set <= '0';
        elsif (card = '1') then
            inited_set <= '0';
        elsif (falling_edge(selected) and nrw = '0') then
            case addr is
                when "00" =>        -- write SPI data out (see other process above)
                    spidataout <= int_din;
                when "01" =>        -- write status register
                    cpha <= int_din(0);
                    cpol <= int_din(1);
                    ece <= int_din(2);
                    tmo <= int_din(3);
                    frx <= int_din(4);
                    -- no bit 5 - 7
                when "10" =>        -- write divisor
                    divisor <= int_din(DIV_WIDTH-1 downto 0);
                when "11" =>        -- write slave select / slave interrupt enable
                    slavesel <= int_din(0);
                    -- no bit 1 - 6
                    inited_set <= int_din(7);
                when others =>
            end case;
        end if;
    end process;
    
end Behavioral;

