---------------------------------------------------------------------------
-- NDT Solutions Ltd. Proprietary Information
-- Company Confidential
-- Copyright 2004
--
-- Filename: LbusCont.vhd
-- Language: VHDL '93
-- Project:  Digitiser Card System FPGA
--
-- Design Description:
-- Local Bus Control for Digitiser Card System FPGA
--
-- SRAM controller for the PLX IC local bus - provides a prototype
-- for all other local bus peripherals.
--
---------------------------------------------------------------------------


library IEEE;
use IEEE.Std_Logic_1164.all;
use IEEE.Std_Logic_Arith.all;


entity LbusCont is
  port(pnReset     : in  std_ulogic;
       pLCLKA      : in  std_ulogic;                 -- local bus clock
       pLAin       : in  std_logic_vector(31 downto 2);
       pLDin       : in  std_logic_vector(31 downto 0);    -- local data bus input
       pLDout      : out std_logic_vector(31 downto 0);  -- local data bus output
       pLDoutEn    : out std_ulogic;            -- output buffer enable
       pLW_R       : in  std_ulogic;                  -- write NOT read
       pnADS       : in  std_ulogic;                  -- ads#
       pnBLAST     : in  std_ulogic;                -- blast#
       pnREADY     : out std_ulogic;               -- ready#
       pnBTERM     : out std_ulogic;               -- bterm#
       pLHOLDA     : out std_ulogic;               -- lholda#
       pLHOLD      : in  std_ulogic;               -- lhold#
       pBREQi      : out std_ulogic;
       pnSysRamWE  : out std_ulogic;
       pnSysRamCS  : out std_ulogic;
       pnSysRamOE  : out std_ulogic;
       -- Generic IO signals
       pSysIo0In   : in  std_logic_vector(31 downto 0);
       pSysIo0Out  : out std_logic_vector(31 downto 0);
       pSysIo0Dir  : out std_logic_vector(31 downto 0);
       pSysIo1In   : in  std_logic_vector(20 downto 0);
       pSysIo1Out  : out std_logic_vector(20 downto 0);
       pSysIo1Dir  : out std_logic_vector(20 downto 0);
       -- Block RAM Signals
       pBlkRamWriteEn : out std_ulogic;
       pBlkRamAddr    : out std_logic_vector(5 downto 0);
       pBlkRamDataIn  : out std_logic_vector(31 downto 0);
       pBlkRamDataOut : in  std_logic_vector(31 downto 0);
       -- Control signals for EEPROM access block
       pWriteEeprom   : out std_ulogic;
       pReadEeprom    :  out std_ulogic;
       -- Signals for status reg
       pEepromBusy    : in std_ulogic;
       --
       pCCS        : out std_ulogic;      -- PCI 9056 configuration chip select
       pWhatFpga   : out std_logic_vector(3 downto 0);
       pnDspFpgaCs : out std_logic_vector(3 downto 0);   -- DSP FPGA / sample system chip selects
       pnPerFpgaCs : out std_logic_vector(3 downto 0);   -- Peripheral chip selects
       pFpgaLoad   : out std_ulogic;      -- flag start of fpga progeram sequence
       pFpgaProged : in std_ulogic;     -- The FPGAs have been programmed
       pFpgaDone   : in  std_logic);    -- feedback strobe from fpga load module
end LbusCont;

architecture Behavioral of LbusCont is

  constant cSysFpgaVer: std_logic_vector(15 downto 0) := X"0101";

  signal state_var : integer range 0 to 7;

  signal nReady:      std_ulogic;
  signal nExtndReady: std_ulogic;
  signal nDspFpgaCs:  std_ulogic;
  signal nPerFpgaCs:  std_ulogic;

  signal check_register:   std_logic_vector(31 downto 0);
  signal sys_io_bank0:     std_logic_vector(31 downto 0);
  signal sys_io_bank0_ddr: std_logic_vector(31 downto 0);
  signal sys_io_bank1:     std_logic_vector(20 downto 0);
  signal sys_io_bank1_ddr: std_logic_vector(20 downto 0);
  signal sys_command_reg:  std_logic_vector(31 downto 0);
  signal sys_status_reg:   std_logic_vector(31 downto 0);
  signal sys_test_reg:     std_logic_vector(31 downto 0);


  signal sys_enable_any : std_ulogic;
  signal sys_enable     : std_logic_vector(7 downto 0); -- collection of enable flags organised thus...
                                    --  Bit   Function
                                    --   0     Check register enable
                                    --   1     System IO bank 0 enable
                                    --   2     System IO bank 0 DDR enable
                                    --   3     System IO bank 1 enable
                                    --   4     System IO bank 1 DDR enable
                                    --   5     System command reg / status reg enable
                                    --   6     not used
                                    --   7     test register
  signal sys_reg_enable:    std_ulogic;
  signal local_bus_control: std_ulogic;   -- 0 = PCI has bus control, 1 = local bus control
  signal SysRamAccess:      std_ulogic;
  signal BlockRamAccess:    std_ulogic;
  signal BlockRamAccessQ:   std_ulogic;
  signal SampleRamAccess:   std_ulogic;

  signal IntAddr:           std_logic_vector(31 downto 0);
  signal IntAddrValid:      std_ulogic;
  signal IntLW_R:           std_ulogic;
  signal Intlholda:         std_ulogic;

  signal next_ram_nWR:      std_ulogic;
  signal next_ram_nCS:      std_ulogic;
  signal nADS_delayed:      std_ulogic;
  signal SysRamCsnActive:   std_ulogic;
  signal SysRamOenActive:   std_ulogic;
  signal SysRamWrnActive:   std_ulogic;
  signal SysRamWr_a:        std_ulogic;
  signal SysRamWr_b:        std_ulogic;
  signal fpga_load_int:     std_ulogic;
  signal BREQi:             std_ulogic;

  signal WriteEeprom:       std_ulogic;
  signal ReadEeprom:        std_ulogic;

  signal BlkRamData:        std_logic_vector(31 downto 0);
  signal RegReadData:       std_logic_vector(31 downto 0);

  signal FirstAccess:       std_ulogic;

begin

  pnREADY <= nReady;

  --
  -- Load local address into counter when ADS# is asserted
  --
  GenIntAddr: process(pnReset, pLCLKA)
  begin
    if pnReset = '0' then
      IntAddr(31 downto 2) <= (others => '0');
    elsif rising_edge(pLCLKA) then
      if pnADS = '0' then
        IntAddr(31 downto 2) <= pLAin(31 downto 2);
      elsif pnBLAST = '1' and nExtndReady = '0' then
        IntAddr(31 downto 2) <= unsigned(IntAddr(31 downto 2)) + 1;
      end if;
    end if;
  end process GenIntAddr;

  IntAddr(1 downto 0) <= "00";


  GenSysRamAccess: process(pnReset, pLCLKA)
    variable vLast_nADS: std_ulogic;
  begin
    if pnReset = '0' then
      SysRamAccess    <= '0';
      SampleRamAccess <= '0';
      vLast_nADS      := '1';
    elsif rising_edge(pLCLKA) then
      if vLast_nADS = '0' then
        if IntAddr(31 downto 20) = X"004" then
          SysRamAccess <= '1';
        else
          SysRamAccess <= '0';
        end if;

        if IntAddr(31 downto 20) = X"000" or
           IntAddr(31 downto 20) = X"001" or
           IntAddr(31 downto 20) = X"002" or
           IntAddr(31 downto 20) = X"003" then
          SampleRamAccess <= '1';
        else
          SampleRamAccess <= '0';
        end if;
      end if;

      vLast_nADS := pnADS;
    end if;
  end process GenSysRamAccess;


  GenIntAddrValid: process(pLCLKA)
  begin
    if pnReset = '0' then
      IntAddrValid <= '0';

    elsif rising_edge(pLCLKA) then
      if pnADS = '0' then
        IntAddrValid <= '1';
      elsif pnBLAST = '0' and nReady = '0' then
        IntAddrValid <= '0';
      end if;
    end if;
  end process GenIntAddrValid;


  GenIntLW_R: process(pLCLKA)
  begin
    if rising_edge(pLCLKA) then
      IntLW_R <= pLW_R;
    end if;
  end process GenIntLW_R;


  -- Generate delayed version of pnADS input
  process(pLCLKA)
  begin
    if rising_edge(pLCLKA) then
      nADS_delayed <= pnADS;
    end if;
  end process;


  pBlkRamWriteEn <= IntLW_R when BlockRamAccess = '1' else '0';
  pBlkRamAddr    <= IntAddr(7 downto 2);
  pBlkRamDataIn  <= pLDin;


  -- Check for accesses to System RAM
  GenSysRamSignals: process(sys_command_reg, IntAddr, IntLW_R, next_ram_nCS, Intlholda)
  begin
    if sys_command_reg(0) = '1' and Intlholda = '0' then
      -- Set the System SRAM to read mode for FPGA loading
      SysRamCsnActive <= '0';
      SysRamOenActive <= '0';

    elsif next_ram_nCS = '0' then
      if IntAddr(31 downto 20) = X"004" then
        SysRamCsnActive <= '0';
        if IntLW_R = '1' then
          -- SRAM Write
          SysRamOenActive <= '1';
          -- Toggle nWR when active
        else
          -- SRAM Read
          SysRamOenActive <= '0';
        end if;
      else
        -- Not SRAM access
        SysRamCsnActive <= '1';
        SysRamOenActive <= '1';
      end if;
    else
      -- No access
      SysRamCsnActive <= '1';
      SysRamOenActive <= '1';
    end if;
  end process GenSysRamSignals;


  GenSysRamAccess2: process(sys_command_reg, IntAddr, IntLW_R, nReady, next_ram_nWR)
  begin
    if sys_command_reg(0) = '1' then
      -- Set the System SRAM to read mode for FPGA loading
      SysRamWrnActive <= '1';

    else
      if IntAddr(31 downto 20) = X"004" then
        if IntLW_R = '1' then
          -- SRAM Write
          -- Toggle nWR when active
          if next_ram_nWR = '0' then
            SysRamWrnActive <= '0';
          else
            SysRamWrnActive <= '1';
          end if;
        else
          -- SRAM Read
          SysRamWrnActive <= '1';
        end if;
      else
        -- Not SRAM access
        SysRamWrnActive <= '1';
      end if;
--    else
--      -- No access
--      SysRamWrnActive <= '1';
    end if;
  end process GenSysRamAccess2;




  -- Drive Sytem SRAM control signals
  pnSysRamCS <= SysRamCsnActive;
  pnSysRamOE <= SysRamOenActive;
  pnSysRamWE <= SysRamWrnActive;


  --
  --  manage writes from local bus
  --
  process(pnReset, pLCLKA)
  begin
    if pnReset = '0' then
      sys_command_reg  <= (others => '0');
      check_register   <= (others => '0');
      sys_io_bank0     <= (others => '0');
      sys_io_bank0_ddr <= (others => '0');
      sys_io_bank1     <= (others => '0');
      sys_io_bank1_ddr <= (others => '0');
      sys_test_reg     <= (others => '0');
      WriteEeprom      <= '0';
      ReadEeprom       <= '0';

    elsif rising_edge(pLCLKA) then
      WriteEeprom      <= '0';
      ReadEeprom       <= '0';

      if pFpgaDone = '1' then
        sys_command_reg(0) <= '0';

      elsif(IntLW_R = '1')then
        if(sys_enable(0) = '1') then   -- 0x00500000 check register
          check_register <= pLDin;
        end if;

        if(sys_enable(1) = '1') then   -- 0x00500004 system IO bank 0
          sys_io_bank0 <= pLDin;
        end if;

        if(sys_enable(2) = '1') then   -- 0x00500008 system IO bank 0 data direction register
          sys_io_bank0_ddr <= pLDin;
        end if;

        if(sys_enable(3) = '1')then    -- 0x0050000C system IO bank 1
          sys_io_bank1 <= pLDin(20 downto 0);
        end if;

        if(sys_enable(4) = '1') then   -- 0x00500010 system IO bank 1 data direction register
          sys_io_bank1_ddr <= pLDin(20 downto 0);
        end if;

        if(sys_enable(5) = '1') then   -- 0x00500014 system command register
          sys_command_reg <= pLDin and X"000000F9";
          WriteEeprom      <= pLDin(1);
          ReadEeprom       <= pLDin(2);
        end if;

        if(sys_enable(6) = '1') then   -- 0x00500018 spare
          null;
        end if;

        if(sys_enable(7) = '1') then   -- 0x0050001C testy thingy
          sys_test_reg <= pLDin;
        end if;
      end if;
    end if;

  end process;

  -- Drive outputs with internal signals
  pWriteEeprom <= WriteEeprom;
  pReadEeprom  <= ReadEeprom;
  pSysIo0Out   <= sys_io_bank0;
  pSysIo0Dir   <= sys_io_bank0_ddr;
  pSysIo1Out   <= sys_io_bank1;
  pSysIo1Dir   <= sys_io_bank1_ddr;


  process(pnReset, pLCLKA)
  begin
    if pnReset = '0' then
      sys_status_reg(0) <= '0';
    elsif rising_edge(pLCLKA) then
      sys_status_reg(0) <= pFpgaProged;
    end if;
  end process;

  sys_status_reg(1) <= pEepromBusy;
  sys_status_reg(2) <= Intlholda;
  sys_status_reg(3) <= BREQi;
  sys_status_reg(31 downto 4) <= (others => '0');

  --
  --  setup the start coditions for fpga programming and eeprom functions
  --
  process(sys_command_reg, local_bus_control)
  begin
    fpga_load_int <= '0';             -- default
    BREQi     <= '0';               -- default - lets PCI have the local bus

    if(sys_command_reg(0) = '1') then     -- detect command to program fpga
      BREQi <= '1';             -- request local bus control

      if(local_bus_control = '1') then  -- got local bus control
        fpga_load_int <= '1';         -- flag command to program fpga
      end if;
    end if;
  end process;

  pBREQi <= BREQi;


  -- Delay pFpgaLoad by 1 clock cycle
  process(pLCLKA)
  begin
    if rising_edge(pLCLKA) then
      pFpgaLoad <= fpga_load_int;
    end if;
  end process;

  --
  -- Generate register reads
  --
  process(pnReset, pLCLKA)
    variable vLAin7_2: std_logic_vector(5 downto 0);
  begin
    if pnReset = '0' then
      BlkRamData  <= X"00000000";
      RegReadData <= X"00000000";
    elsif rising_edge(pLCLKA) then
      BlkRamData <= pBlkRamDataOut;

      vLAin7_2 := IntAddr(7 downto 2);
      case vLAin7_2 is
        when "000000" =>
          RegReadData <= not check_register;
        when "000001" =>
          RegReadData <= pSysIo0In;
        when "000010" =>
          RegReadData <= sys_io_bank0_ddr;
        when "000011" =>
          RegReadData <= "00000000000" & pSysIo1In;
        when "000100" =>
          RegReadData <= "00000000000" & sys_io_bank1_ddr;
        when "000101" =>
          RegReadData <= sys_status_reg;
--        when "000110" =>
--          pLDout <= sys_command_reg;
        when "000111" =>
          RegReadData <= sys_test_reg;
        when "111111" =>
          RegReadData <= X"0000" & cSysFpgaVer;
        when others =>
          null;
      end case;
    end if;
  end process;


  --
  --  manage reads to local bus
  --
  process(pnReset, pLCLKA)
    variable vLAin7_2: std_logic_vector(5 downto 0);
  begin
    if pnReset = '0' then
      pLDoutEn <= '0';
      BlockRamAccessQ <= '0';
    elsif rising_edge(pLCLKA) then
      if pnBLAST = '0' and nReady = '0' then
        -- Deassert data output enable at the end of a cycle
        pLDoutEn <= '0';
      elsif IntLW_R = '0' and (sys_reg_enable = '1' or BlockRamAccess = '1') then
        -- Read access, assert data output enable
        pLDoutEn <= '1';
      end if;

      BlockRamAccessQ <= BlockRamAccess;
    end if;
  end process;

  process(BlockRamAccessQ, BlkRamData, RegReadData)
  begin
    pLDout <= (others => '0');

    if BlockRamAccessQ = '1' then
      pLDout <= BlkRamData;
    else
      pLDout <= RegReadData;
    end if;
  end process;


  -- ****************************************************************
  -- address decodes...
  -- ****************************************************************
  process(IntAddr, IntAddrValid, sys_command_reg)
  begin
      -- defaults
      sys_reg_enable <= '0';  -- disable system register access
      BlockRamAccess <= '0';
      nDspFpgaCs <= '1';
      nPerFpgaCs <= '1';

      if IntAddrValid = '1' then
        case IntAddr(31 downto 20) is
          when X"000" | X"001" | X"002" | X"003" =>   -- Sample  enable
            nDspFpgaCs <= '0';
          when X"004" =>            -- System RAM enable
          when X"005" =>            -- registers
            case IntAddr(19 downto 8) is
              when X"000" =>
                sys_reg_enable <= '1';            -- Enable system register access
--              when X"001" =>
--                sys_reg_enable <= '1';            -- Enable system register access
              when X"002" =>
                nDspFpgaCs <= '0';  -- DSP FPGA registers
              when X"003" =>
                nDspFpgaCs <= '0';  -- DSP FPGA registers
              when X"004" =>
                nPerFpgaCs <= '0';  -- Peripheral FPGA registers
              when others =>
                null;
            end case;
          when X"006" =>  -- EEPROM Block RAM
            if IntAddr(19 downto 8) = "000000000000" then
              BlockRamAccess <= '1';
            end if;

          when others =>
            null;
      end case;
    end if;
  end process;


  pnDspFpgaCs <= (not sys_command_reg(7 downto 4)) when (nDspFpgaCs = '0' and not(pnBLAST = '0' and nReady = '0')) else (others => '1');
  pnPerFpgaCs <= (not sys_command_reg(7 downto 4)) when (nPerFpgaCs = '0' and not(pnBLAST = '0' and nReady = '0')) else (others => '1');
  pWhatFpga <= not sys_command_reg(7 downto 4);


  -- system register address decodes
  process(IntAddr, sys_reg_enable)
  begin
    sys_enable <= (others => '0');
    sys_enable_any <= '0';

    if (sys_reg_enable = '1') then
      case IntAddr(7 downto 2) is
        when "000000" =>           -- 0x00500000 check register
          sys_enable(0)  <= '1';
          sys_enable_any <= '1';
        when "000001" =>           -- 0x00500004 system IO bank 0
          sys_enable(1) <= '1';
          sys_enable_any <= '1';
        when "000010" =>           -- 0x00500008 system IO bank 0 DDR
          sys_enable(2) <= '1';
          sys_enable_any <= '1';
        when "000011" =>           -- 0x0050000C system IO bank 1
          sys_enable(3) <= '1';
          sys_enable_any <= '1';
        when "000100" =>           -- 0x00500010 system IO bank 1 DDR
          sys_enable(4) <= '1';
          sys_enable_any <= '1';
        when "000101" =>           -- 0x00500014 system command / status register
          sys_enable(5) <= '1';
          sys_enable_any <= '1';
        when "000110" =>           -- 0x00500018 spare
          sys_enable(6) <= '1';
          sys_enable_any <= '1';
        when "000111" =>
          sys_enable(7) <= '1';   -- 0x0050001C test jobbie
          sys_enable_any <= '1';
        when others =>
          null;
      end case;
    end if;
  end process;



  --******************************************************************
  -- local bus state machine
  -- handles handshaking required by PLX chip for assorted PCI modes
  --******************************************************************
  process(pnReset, pLCLKA)
    begin
    if pnReset = '0' then
      state_var   <= 0;
      nReady      <= '1';
      nExtndReady <= '1';
      pnBTERM     <= '1';
      FirstAccess <= '1';

    elsif rising_edge(pLCLKA) then
      -- defaults...
      nReady      <= '1';
      nExtndReady <= '1';
      pnBTERM     <= '1';

        case state_var is
          when 0 =>
            if pnADS = '0' then  --and (pnBLAST = '1') then     -- start of transaction
              state_var <= 1;                   -- next state next time
            end if;
            FirstAccess <= '1';

          when 1 =>
            state_var <= 2;
            nReady <= '1';
            if BlockRamAccess = '1' and IntLW_R = '0' then
              nExtndReady <= '0';
              state_var <= 6;
            end if;

          when 6 =>
            state_var <= 2;
            nReady <= '1';
            nExtndReady <= '0';

          when 2 =>
            nExtndReady <= '0';
            if SampleRamAccess = '1' and FirstAccess = '1' then
              -- Special case for accessing Sample RAM, allows more pipelining
              state_var <= 4;
              nReady <= '1';
            else
              --
              state_var <= 3;
              nReady <= '0';                      -- pull ready low
            end if;

          when 4 =>
            state_var <= 5;
            nReady <= '1';
            nExtndReady <= '0';
            FirstAccess <= '0';

          when 5 =>
            state_var <= 3;
            nReady <= '0';
            nExtndReady <= '0';

          when 3 =>
            --if(pnADS = '1') and (pnBLAST = '0') then        -- transaction termination
            if pnBLAST = '0' then        -- transaction termination
              nReady <= '1';
              nExtndReady <= '1';
              state_var <= 0;                   -- start again next time
              pnBTERM <= '0';                      -- ram writes and ready disabled    -- by defaults

            else
            --elsif(pnADS = '1') and (pnBLAST = '1') then     -- there's more incoming / outgoing
              if (IntLW_R = '1' and SysRamAccess = '1') or SampleRamAccess = '1' then
                -- Special case for burst writes to system RAM and burst reads from sample RAM
                nReady <= '1';                      -- pull ready low
                nExtndReady <= '1';
                state_var <= 2;                   -- same next time
              else
                nReady <= '0';                      -- pull ready low
                nExtndReady <= '0';
                state_var <= 3;                   -- same next time
              end if;
            end if;

          when others =>      -- catch all
            state_var <= 0;   -- start agin
        end case;
    end if;
  end process;

  -- Generate advance version of nReady signal
  process(state_var, pnADS, pnBLAST, SampleRamAccess, FirstAccess)
  begin
    next_ram_nCS <= '1';
    next_ram_nWR <= '1';

    case state_var is
      when 0 =>
        null;

      when 1 =>
        next_ram_nCS <= '0';
        next_ram_nWR <= '0';

      when 2 =>
        if SampleRamAccess = '1' and FirstAccess = '1' then
          next_ram_nCS <= '1';
        else
          next_ram_nCS <= '0';
        end if;

      when 4 =>
        next_ram_nCS <= '1';

      when 5 =>
        next_ram_nCS <= '1';

      when 3 =>
       -- if pnADS = '1' and pnBLAST = '0' then
         if pnBLAST = '0' then
          null;
        elsif pnADS = '1' and pnBLAST = '1' then
          next_ram_nCS <= '0';
          next_ram_nWR <= '0';
        end if;
      when others => null;
    end case;
  end process;


  -- bus arbitration
  -- to get local bus...
  -- assert BREQi wait for lholda to fall
  process(pLCLKA)
  begin
    if rising_edge(pLCLKA) then
      if local_bus_control = '0' then
        Intlholda <= pLHOLD;          -- ack bus owner
      else
        -- Do not hand over control of the bus
        Intlholda <= '0';
      end if;

      if(BREQi = '0') then        -- PCI can have the bus
        local_bus_control <= '0';   -- flag PCI bus control
      elsif(BREQi = '1') then       -- local bus request
        if(pLHOLD = '0') then        -- PCI relinquished bus
          local_bus_control <= '1'; -- flag local bus control
        end if;
      end if;

    end if;
  end process;

  pLHOLDA <= Intlholda;


-- ****************************
-- *** This is a MUST DO!!! ***
-- ****************************
pCCS <= '1';       -- basically enables local bus - actually disables local bus PLX configuration mode


end Behavioral;
