--------------------------------------------------------------------------------
-- Title   : Hermes
-- Project : TimEX3
--------------------------------------------------------------------------------
-- Description : cPCI interface
--
-- PLX eeprom configuration => Bus descriptor = 00806002
--                                   * Space : 0
--                                   * Local bus width : 32 bits
--                                   * Rd/Wr Data-to-Adress : 3 wait states
--                                   * Enable Ready input
--                          => Range = 0FFFFC00
--                                   * PCI memory space
--                                   * Locate in 32-bit PCI space
--                                   * space range 1Kbyte
--                          => Remap = 00000001 (enable)
--
-- CMD register => bit 0 = board reset, upon reset this bit must be set to '0' !!!
--                          0 => no reset
--                          1 => reset
--------------------------------------------------------------------------------
-- File     : cPCI_hermes.vhdl
-- Revision : x.x.x
-- Created  : October 26, 2012
-- Updated  : December 06, 2013
--------------------------------------------------------------------------------
-- Author       : Jean-Paul Ricaud
-- Organization : Synchrotron Soleil
-- Web          : http://www.synchrotron-soleil.fr
-- Email        : jean-paul.ricaud@synchrotron-soleil.fr
--------------------------------------------------------------------------------
-- Copyright (C) 2012 - 2013 Synchrotron Soleil
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Lesser General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program 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 Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>
--------------------------------------------------------------------------------
--
--------------------------------------------------------------------------------
-- Modifications :
--   Version 1.4.1 ; November 08 2013 ; Jean-Paul Ricaud
--     * Added a config read register (duplication, top-up, etc.)
--
--------------------------------------------------------------------------------

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

library work;
use work.cPCI_hermes_registers.all;
use work.registers_init.all;
use work.type_lib.all;


--------------------------------------------------------------------------------
--------------------------------- ENTITY ---------------------------------------
--------------------------------------------------------------------------------
entity cPCI_hermes is
  port (
    -- PLX bus
    p_AD          : in    std_logic_vector (9 downto 2);  -- address bus
    p_DATA        : inout std_logic_vector (31 downto 0); -- data bus
    p_CSn         : in    std_logic_vector (1 downto 0);  -- chip select
    p_BEn         : in    std_logic_vector (3 downto 0);  -- byte enable
    p_BLASTn      : in    std_logic;  -- burst last
    p_LWRn        : in    std_logic;  -- read/write
    p_RDn         : in    std_logic;  -- read strobe
    p_WRn         : in    std_logic;  -- write strobe
    p_WAITOn      : in    std_logic;  -- wait
    p_ADSn        : in    std_logic;  -- address strobe
    p_ALE         : in    std_logic;  -- address latch
    p_CLK60MHz    : in    std_logic;  -- clock 60 MHz
    p_READYn      : out   std_logic;  -- ready
    p_BTERMn      : out   std_logic;  -- burst treminate
    p_LINTI       : out   std_logic_vector (1 downto 0);  -- interrupt
    -- Internal signals
    p_cmdReg      : out std_logic_vector (31 downto 0); -- command register
    p_config_reg  : in  std_logic_vector (31 downto 0); -- configurarion register
    p_status_reg  : in  std_logic_vector (29 downto 0); -- bit 31 & 30 are reserved for access error

    p_wRegister      : out register_array;
    p_wRegister_init : in register_array;
    p_rRegister      : in  register_array;

    p_CSW         : out std_logic_vector (15 downto 0); -- chip select of internal write transfert
    p_CSR         : out std_logic_vector (15 downto 0); -- chip select of internal read transfert
    -- Other signals
    p_reset       : in  std_logic  -- reset in
  );
end entity cPCI_hermes;

--------------------------------------------------------------------------------
------------------------------- ARCHITECTURE -----------------------------------
--------------------------------------------------------------------------------
architecture rtl_cPCI_hermes of cPCI_hermes is

  ------------------------------------------------------------------------------
  -- constant
  ------------------------------------------------------------------------------
  constant c_readSetupt   : unsigned (2 downto 0) := "010"; -- read setup delay
  constant c_readHoldt    : unsigned (2 downto 0) := "100"; -- read hold delay

  ------------------------------------------------------------------------------
  -- signal
  ------------------------------------------------------------------------------
  signal s_csw          : std_logic_vector (15 downto 0); -- chip select of internal write transfert
  signal s_startW       : std_logic;  -- write cycle start
  signal s_setupW       : std_logic;  -- write cycle setup
  signal s_strobeW      : std_logic;  -- write stobe
  signal s_startR       : std_logic;  -- read cycle start
  signal s_gosR         : std_logic;  -- read setup start
  signal s_holdR        : std_logic;  -- read cycle hold
  signal s_readyWn      : std_logic;  -- write ready strobe
  signal s_readyRn      : std_logic;  -- read ready strobe
  signal s_error        : std_logic_vector (1 downto 0); -- address error on a read or a write
  signal s_readyHold    : std_logic;  -- ready hold
  signal s_extend       : std_logic;  -- write p_READYn extension cycle
  signal s_clearStatus  : std_logic;  -- clear the status register
  signal s_write_access : std_logic;  -- read access
  signal s_read_access  : std_logic;  -- read access
  signal s_valid_read   : std_logic;  -- valid data on read bus
  signal s_readSetupCNT : unsigned (2 downto 0); -- read setup delay counter
  signal s_readHoldCNT  : unsigned (2 downto 0); -- read hold delay counter

  ------------------------------------------------------------------------------
  --------------------------------- Main ---------------------------------------
  ------------------------------------------------------------------------------
  begin

  ------------------------------------------------------------------------------
  -- Write access
  ------------------------------------------------------------------------------
 process (p_CLK60MHz, p_reset)
  begin
    if (p_reset = '1') then
      s_startW <= '0';
    elsif (p_CLK60MHz'event and p_CLK60MHz = '1') then
      if (s_write_access = '1') then
        s_startW <= '1';
      else
        s_startW <= '0';
      end if;
    end if;
  end process;

  -- Write setup time
  process (p_CLK60MHz, p_reset)
  begin
    if (p_reset = '1') then
      s_setupW <= '0';
    elsif (p_CLK60MHz'event and p_CLK60MHz = '1') then
      if (s_STARTW = '1') then
        s_setupW <= '1';
      else
        s_setupW <= '0';
      end if;
    end if;
  end process;

  -- Write registers
  process (p_CLK60MHz, p_reset)
  begin
    if (p_reset = '1') then
      s_test_reg    <= c_test_reg_init;
      s_cmd_reg     <= c_cmd_reg_init;
      s_wRegister   <= p_wRegister_init;
      s_csw         <= (OTHERS => '0');
      s_strobeW     <= '0';
      s_clearStatus <= '0';
      s_error(1)    <= '0';
    elsif (p_CLK60MHz'event and p_CLK60MHz = '1') then
      if (s_setupW = '1') then
        s_strobeW <= '1';
        case p_AD is
          when c_add_test_reg =>
            s_test_reg <= p_DATA;
            s_error(1) <= '0';
          when c_add_status_reg =>
            s_clearStatus <= '1'; -- reading the status register clean up access error bits
            s_error(1) <= '0';
          when c_add_cmd_reg =>
            s_cmd_reg <= p_DATA;
            s_csw <= "0000000000000001";
            s_error(1) <= '0';
          when c_add_register(0) =>
            s_wRegister(0) <= p_DATA;
            s_csw <= "0000000000000010";
            s_error(1) <= '0';
          when c_add_register(1) =>
            s_wRegister(1) <= p_DATA;
            s_csw <= "0000000000000100";
            s_error(1) <= '0';
          when c_add_register(2) =>
            s_wRegister(2) <= p_DATA;
            s_csw <= "0000000000001000";
            s_error(1) <= '0';
          when c_add_register(3) =>
            s_wRegister(3) <= p_DATA;
            s_csw <= "0000000000010000";
            s_error(1) <= '0';
          when c_add_register(4) =>
            s_wRegister(4) <= p_DATA;
            s_csw <= "0000000000100000";
            s_error(1) <= '0';
          when c_add_register(5) =>
            s_wRegister(5) <= p_DATA;
            s_csw <= "0000000001000000";
            s_error(1) <= '0';
          when c_add_register(6) =>
            s_wRegister(6) <= p_DATA;
            s_csw <= "0000000010000000";
            s_error(1) <= '0';
          when c_add_register(7) =>
            s_wRegister(7) <= p_DATA;
            s_csw <= "0000000100000000";
            s_error(1) <= '0';
          when c_add_register(8) =>
            s_wRegister(8) <= p_DATA;
            s_csw <= "0000001000000000";
            s_error(1) <= '0';
          when c_add_register(9) =>
            s_wRegister(9) <= p_DATA;
            s_csw <= "0000010000000000";
            s_error(1) <= '0';
          when c_add_register(10) =>
            s_wRegister(10) <= p_DATA;
            s_csw <= "0000100000000000";
            s_error(1) <= '0';
          when c_add_register(11) =>
            s_wRegister(11) <= p_DATA;
            s_csw <= "0001000000000000";
            s_error(1) <= '0';
          when c_add_register(12) =>
            s_wRegister(12) <= p_DATA;
            s_csw <= "0010000000000000";
            s_error(1) <= '0';
          when c_add_register(13) =>
            s_wRegister(13) <= p_DATA;
            s_csw <= "0100000000000000";
            s_error(1) <= '0';
          when others =>
            s_csw      <= (OTHERS => '0');
            s_error(1) <= '1';
        end case;
      else
        -- Internal
        s_csw <= (OTHERS => '0');
        -- PLX
        s_strobeW     <= '0';
        s_error(1)    <= '0';
        s_clearStatus <= '0';
      end if;
    end if;
  end process;

  -- ready output
  process (p_CLK60MHz, p_reset)
  begin
    if (p_reset = '1') then
      s_readyWn <= '1';
      s_extend  <= '0';
      p_CSW <= (OTHERS => '0');
    elsif (p_CLK60MHz'event and p_CLK60MHz = '1') then
      if ((s_strobeW = '1') or (s_extend = '1')) then
        s_readyWn <= '0';
        if (s_strobeW = '0') then
          s_extend <= '0';
        else
          p_CSW <= s_CSW;
          s_extend <= '1';
        end if;
      else
        p_CSW <= s_CSW;
        s_readyWn <= '1';
        s_extend  <= '0';
      end if;
    end if;
  end process;

  ------------------------------------------------------------------------------
  ------------------------------------------------------------------------------

  ------------------------------------------------------------------------------
  --**************************************************************************--
  --**************************************************************************--
  ------------------------------------------------------------------------------

  ------------------------------------------------------------------------------
  -- Read access
  ------------------------------------------------------------------------------
  process (p_CLK60MHz, p_reset)
  begin
    if (p_reset = '1') then
      s_startR <= '0';
    elsif (p_CLK60MHz'event and p_CLK60MHz = '1') then
      if ((p_CSn(0) = '0') and (p_ADSn = '0') and (p_LWRn = '0')) then
        s_startR <= '1';
      else
        s_startR <= '0';
      end if;
    end if;
  end process;

  -- Read CS strobe
  process (p_CLK60MHz, p_reset)
  begin
    if (p_reset = '1') then
      p_CSR <= (OTHERS => '0');
   elsif (p_CLK60MHz'event and p_CLK60MHz = '1') then
      if ((s_read_access = '1') or ((s_valid_read = '1'))) then
        case p_AD is
          when c_add_cmd_reg =>
            p_CSR(0) <= '1';
          when c_add_register(0) =>
            p_CSR(1) <= '1';
          when c_add_register(1) =>
            p_CSR(2) <= '1';
          when c_add_register(2) =>
            p_CSR(3) <= '1';
          when c_add_register(3) =>
            p_CSR(4) <= '1';
          when c_add_register(4) =>
            p_CSR(5) <= '1';
          when c_add_register(5) =>
            p_CSR(6) <= '1';
          when c_add_register(6) =>
            p_CSR(7) <= '1';
          when c_add_register(7) =>
            p_CSR(8) <= '1';
          when c_add_register(8) =>
            p_CSR(9) <= '1';
          when c_add_register(9) =>
            p_CSR(10) <= '1';
          when c_add_register(10) =>
            p_CSR(11) <= '1';
          when c_add_register(11) =>
            p_CSR(12) <= '1';
          when c_add_register(12) =>
            p_CSR(13) <= '1';
          when c_add_register(13) =>
            p_CSR(14) <= '1';
          when others =>
            p_CSR <= (OTHERS => '0');
        end case;
      else
        p_CSR <= (OTHERS => '0');
      end if;
    end if;
  end process;

  -- Read setup time
  process (p_CLK60MHz, p_reset)
  begin
    if (p_reset = '1') then
      s_gosR   <= '0';
      s_readSetupCNT <= "000";
    elsif (p_CLK60MHz'event and p_CLK60MHz = '1') then
      if ((s_startR = '1') or (s_gosR = '1')) then
        if (s_readSetupCNT < c_readSetupt) then
          s_gosR   <= '1';
          s_readSetupCNT <= s_readSetupCNT + 1;
        else
          s_gosR   <= '0';
          s_readSetupCNT <= "000";
        end if;
      end if;
    end if;
  end process;

  -- Read hold time
  process (p_CLK60MHz, p_reset)
  begin
    if (p_reset = '1') then
      s_holdR <= '0';
      s_readHoldCNT <= "000";
    elsif (p_CLK60MHz'event and p_CLK60MHz = '1') then
      if ((s_startR = '1') or (s_holdR = '1')) then
        if (s_readHoldCNT < c_readHoldt) then
          s_holdR <= '1';
          s_readHoldCNT <= s_readHoldCNT + 1;
        else
          s_holdR <= '0';
          s_readHoldCNT <= "000";
        end if;
      end if;
    end if;
  end process;

  -- Read registers
  process (p_CLK60MHz, p_reset)
  begin
    if (p_reset = '1') then
      p_DATA     <= (OTHERS => 'Z');
      s_error(0) <= '0';
    elsif (p_CLK60MHz'event and p_CLK60MHz = '1') then
      if (s_valid_read = '1') then
        case p_AD is
          when c_add_boardid_reg =>
            p_DATA <= c_board_id;
            s_error(0) <= '0';
          when c_add_firmware_reg =>
            p_DATA <= c_firmware_rev;
            s_error(0) <= '0';
          when c_add_test_reg =>
            p_DATA <= s_test_reg;
            s_error(0) <= '0';
          when c_add_config_reg =>
            p_DATA <= p_config_reg;
            s_error(0) <= '0';
          when c_add_status_reg =>
            p_DATA <= s_status_reg;
            s_error(0) <= '0';
          when c_add_cmd_reg =>
            p_DATA <= s_cmd_reg;
            s_error(0) <= '0';
          when c_add_register(0) =>
            p_DATA <= s_rRegister(0);
            s_error(0) <= '0';
          when c_add_register(1) =>
            p_DATA <= s_rRegister(1);
            s_error(0) <= '0';
          when c_add_register(2) =>
            p_DATA <= s_rRegister(2);
            s_error(0) <= '0';
          when c_add_register(3) =>
            p_DATA <= s_rRegister(3);
            s_error(0) <= '0';
          when c_add_register(4) =>
            p_DATA <= s_rRegister(4);
            s_error(0) <= '0';
          when c_add_register(5) =>
            p_DATA <= s_rRegister(5);
            s_error(0) <= '0';
          when c_add_register(6) =>
            p_DATA <= s_rRegister(6);
            s_error(0) <= '0';
          when c_add_register(7) =>
            p_DATA <= s_rRegister(7);
            s_error(0) <= '0';
          when c_add_register(8) =>
            p_DATA <= s_rRegister(8);
            s_error(0) <= '0';
          when c_add_register(9) =>
            p_DATA <= s_rRegister(9);
            s_error(0) <= '0';
          when c_add_register(10) =>
            p_DATA <= s_rRegister(10);
            s_error(0) <= '0';
          when c_add_register(11) =>
            p_DATA <= s_rRegister(11);
            s_error(0) <= '0';
          when c_add_register(12) =>
            p_DATA <= s_rRegister(12);
            s_error(0) <= '0';
          when c_add_register(13) =>
            p_DATA <= s_rRegister(13);
            s_error(0) <= '0';
          when c_add_foo_reg =>
            -- Unused register
            -- Workarond for Xilinx synthese pb
            p_DATA <= (OTHERS => '1');
            s_error(0) <= '0';
          when others =>
            s_error(0) <= '1';
            p_DATA <= (OTHERS => 'Z');
        end case;
      else
        p_DATA <= (OTHERS => 'Z');
        s_error(0) <= '0';
      end if;
    end if;
  end process;

  -- Ready output
  process (p_CLK60MHz, p_reset)
  begin
    if (p_reset = '1') then
      s_readyRn <= '1';
      s_readyHold <= '0';
    elsif (p_CLK60MHz'event and p_CLK60MHz = '1') then
      if ((s_readSetupCNT = c_readSetupt) or (s_readyHold = '1')) then
        s_READYRn <= '0';
        if (s_readyHold = '1') then
          s_readyHold <= '0';
        else
          s_readyHold <= '1';
        end if;
      else
        s_readyRn <= '1';
      end if;
    end if;
  end process;

  -- Access error process
  -- The 2 last bits of the status register are dedicated to undefined address read / write
  -- access
  -- Writing to the status register clean up access error bits
  process (p_CLK60MHz, p_reset)
  begin
    if (p_reset = '1') then
      s_status_reg(31 downto 30) <= "00";
    elsif (p_CLK60MHz'event and p_CLK60MHz = '1') then
      if (s_clearStatus = '0') then
        case s_error is
          when "01" =>
            -- A false address has been accessed during a read operation
            s_status_reg(30) <= '1';
          when "10" =>
            -- A false address has been accessed during a write operation
            s_status_reg(31) <= '1';
          when others =>
            s_status_reg(31 downto 30) <= s_status_reg(31 downto 30);
        end case;
      else
        s_status_reg(31 downto 30) <= "00"; -- writing the status register clean up access error bits
      end if;
    end if;
  end process;

  ------------------------------------------------------------------------------
  ------------------------------------------------------------------------------
  s_write_access <= '1' when ((p_CSn(0) = '0') and (p_ADSn = '0') and (p_LWRn = '1')) else
                    '0';
  s_read_access  <= '1' when ((p_CSn(0) = '0') and (p_ADSn = '0') and (p_LWRn = '0')) else
                    '0';
  s_valid_read   <= '1' when ((s_startR = '1') or (s_holdR = '1')) else
                    '0';

  ------------------------------------------------------------------------------
  ------------------------------------------------------------------------------
  p_READYn <= s_readyWn and s_readyRn;

  -- Write registers
  p_cmdReg    <= s_cmd_reg; -- command register ; s_CSW = "000000000000001"
  p_wRegister <= s_wRegister;

  -- Read registers
  s_rRegister <= p_rRegister;

  s_status_reg(29 downto 0) <= p_status_reg(29 downto 0); -- Status register

  ------------------------------------------------------------------------------
  ------------------------------------------------------------------------------
  -- Foo outputs
  p_BTERMn <= '1';
  p_LINTI  <= "00";

end architecture rtl_cPCI_hermes;
