---------------------------------------------------------------------------
-- NDT Solutions Ltd. Proprietary Information
-- Company Confidential
-- Copyright 2004
--
-- Filename: EepromCtrlg.vhd
-- Language: VHDL '93
-- Project:  Digitiser Card System FPGA
--
-- Design Description:
-- Read/Write access to the EEPROM
--
---------------------------------------------------------------------------


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


entity EepromCtrl is
  port (pnReset      : in  std_ulogic;
        pLCLKA       : in  std_ulogic;               -- local bus clock
        pWriteEeprom : in  std_ulogic;
        pReadEeprom  : in  std_ulogic;
        pEepromBusy  : out std_ulogic;
        pDebug       : out std_logic_vector(7 downto 0);
        -- Block RAM control signals
        pBRamWriteEn : out std_ulogic;
        pBRamAddr    : out std_logic_vector(5 downto 0);
        pBRamWrData  : out std_logic_vector(31 downto 0);
        pBRamRdData  : in  std_logic_vector(31 downto 0);
        -- EEPROM control signals
        pEeprom_C    : out std_ulogic;  -- EEPROM Clock
        pEeprom_nS   : out std_ulogic;  -- EEPROM Chip Select
        pEeprom_D    : out std_ulogic;  -- EEPROM Write Data
        pEeprom_Q    : in  std_ulogic);  -- EEPROM Read Data
end EepromCtrl;


architecture Rtl of EepromCtrl is

  type tCtrlState is (Idle, SendInstr_ReadMem, SendInstr_WREN, SendInstr_WriteMem,
                      SendInstr_RDSR, SendAddr_ReadMem, SendAddr_WriteMem,
                      ReceiveData, WriteData, WaitForWriteDone,
                      Delay_WREN, Delay_WriteMem, Delay_RDSR);
  signal CtrlState: tCtrlState;

  -- Signals to control the instruction register
  signal InstrReg:   std_logic_vector(7 downto 0);
  signal InstrSel:   std_logic_vector(1 downto 0);
  signal InstrLoad:  std_ulogic;
  signal InstrShift: std_ulogic;
  signal InstrDone:  std_ulogic;

  -- Signals for the address register
  signal AddrReg:   std_logic_vector(7 downto 0);
  signal AddrLoad:  std_ulogic;
  signal AddrShift: std_ulogic;
  signal AddrDone:  std_ulogic;

  signal RxDataShift: std_ulogic;
  signal RxDataDone:  std_ulogic;
  signal RxDataWord:  std_logic_vector(31 downto 0);

  signal WriteEeprom: std_ulogic;
  signal ReadEeprom:  std_ulogic;
  signal EepromBusy:  std_ulogic;

  signal StallClock: std_ulogic;
  signal ClkEN_Re:   std_ulogic;
  signal ClkEN_Fe:   std_ulogic;

  signal AddrCount: std_logic_vector(5 downto 0);
  signal AddrReset: std_ulogic;
  signal AddrInc:   std_ulogic;
  signal AddrInc2:  std_ulogic;

  signal WrDataEn:    std_ulogic;
  signal WrDataLoad:  std_ulogic;
  signal WrDataDone:  std_ulogic;
  signal WrDataWord:  std_logic_vector(31 downto 0);
  signal WrBitCount:  integer range 0 to 7;
  signal WrByteCount: integer range 0 to 3;
  signal WrWordCOunt: integer range 0 to 3;

  signal WaitWriteDone: std_ulogic;
  signal WriteDone:     std_ulogic;
  signal StatusData:    std_logic_vector(7 downto 0);

  signal DoDelay:    std_ulogic;
  signal DelayDone:  std_ulogic;
  signal DelayCount: integer range 0 to 7;

begin

  pDebug <= StatusData;


  -- Process to produce a slow clock enable
  GenCE: process(pnReset, pLCLKA)
    variable vCount: integer range 0 to 63;
  begin
    if pnReset = '0' then
      ClkEN_Re <= '0';
      ClkEN_Fe <= '0';
      vCount   := 0;
    elsif rising_edge(pLCLKA) then
      ClkEN_Re <= '0';
      ClkEN_Fe <= '0';

      if vCount = 63 then
        ClkEN_Re <= '1';
      end if;
      if vCount = 31 then
        ClkEN_Fe <= '1';
      end if;

      if vCount = 63 then
        vCount   := 0;
      else
        vCount := vCount + 1;
      end if;
    end if;
  end process GenCE;


  -- Generate the EEPROM Clock
  GenEeprom_C: process(pnReset, pLCLKA)
  begin
    if pnReset = '0' then
      pEeprom_C <= '0';
    elsif rising_edge(pLCLKA) then
      if ClkEN_Re = '1' then
        if StallClock = '0' then --and WriteDone = '0' then
          pEeprom_C <= '1';
        end if;
      elsif ClkEN_Fe = '1' then
        pEeprom_C <= '0';
      end if;
    end if;
  end process GenEeprom_C;


  -- Extend the input control signals
  ExtendSignals: process(pnReset, pLCLKA)
  begin
    if pnReset = '0' then
      WriteEeprom <= '0';
      ReadEeprom  <= '0';
    elsif rising_edge(pLCLKA) then
      if pWriteEeprom = '1' then
        WriteEeprom <= '1';
      elsif ClkEN_Fe = '1' then
        WriteEeprom <= '0';
      end if;

      if pReadEeprom = '1' then
        ReadEeprom <= '1';
      elsif ClkEN_Fe = '1' then
        ReadEeprom <= '0';
      end if;
    end if;
  end process ExtendSignals;


  -- Control state machine
  ControlFSM: process(pnReset, pLCLKA)
    variable vBlockCount: integer range 0 to 15;
  begin
    if pnReset = '0' then
      CtrlState   <= Idle;
      InstrSel    <= "00";
      InstrLoad   <= '0';
      InstrShift  <= '0';
      AddrLoad    <= '0';
      AddrShift   <= '0';
      pEeprom_nS  <= '1';
      EepromBusy  <= '0';
      StallClock  <= '0';
      RxDataShift <= '0';
      AddrReset   <= '0';
      WrDataEn    <= '0';
      WrDataLoad  <= '0';
      vBlockCount := 0;
      WaitWriteDone <= '0';
      DoDelay     <= '0';

    elsif rising_edge(pLCLKA) then
      if ClkEN_Fe = '1' then  -- Falling EEPROM clock
        InstrSel    <= "00";
        InstrLoad   <= '0';
        InstrShift  <= '0';
        AddrLoad    <= '0';
        AddrShift   <= '0';
        pEeprom_nS  <= '1';
        EepromBusy  <= '1';
        StallClock  <= '0';
        RxDataShift <= '0';
        AddrReset   <= '0';
        WrDataEn    <= '0';
        WrDataLoad  <= '0';
        WaitWriteDone <= '0';
        DoDelay     <= '0';

        case CtrlState is
          when Idle =>
            EepromBusy <= '0';
            if ReadEeprom = '1' then
              CtrlState  <= SendInstr_ReadMem;
              InstrSel   <= "00";
              InstrLoad  <= '1';
              EepromBusy <= '1';
            elsif WriteEeprom = '1' then
              CtrlState  <= SendInstr_WREN;
              InstrSel   <= "01";
              InstrLoad  <= '1';
              AddrReset  <= '1';
              EepromBusy <= '1';
              vBlockCount := 0;
            end if;

          when SendInstr_ReadMem =>
            -- Clock out the ReadMem instruction
            if InstrDone = '0' then
              InstrShift <= '1';
            else
              AddrLoad   <= '1';
              StallClock <= '1';
              AddrReset  <= '1';
              CtrlState  <= SendAddr_ReadMem;
            end if;
            pEeprom_nS <= '0';

          when SendAddr_ReadMem =>
            if AddrDone = '0' then
              AddrShift <= '1';
            else
              StallClock <= '1';
              CtrlState <= ReceiveData;
            end if;
            pEeprom_nS <= '0';

          when ReceiveData =>
            if RxDataDone = '0' then
              RxDataShift <= '1';
              pEeprom_nS  <= '0';
            else
--              StallClock <= '1';
              CtrlState <= Idle;
            end if;

          when SendInstr_WREN =>
            if InstrDone = '0' then
              InstrShift <= '1';
              pEeprom_nS <= '0';
            else
              CtrlState  <= Delay_WREN;
            end if;

          when Delay_WREN =>
            if DelayDone = '0' then
              DoDelay <= '1';
            else
              CtrlState  <= SendInstr_WriteMem;
              InstrSel  <= "11";
              InstrLoad <= '1';
            end if;

          when SendInstr_WriteMem =>
            if InstrDone = '0' then
              InstrShift <= '1';
            else
              AddrLoad   <= '1';
              StallClock <= '1';
              CtrlState  <= SendAddr_WriteMem;
            end if;
            pEeprom_nS <= '0';

          when SendAddr_WriteMem =>
            if AddrDone = '0' then
              AddrShift <= '1';
            else
              StallClock <= '1';
              WrDataLoad <= '1';
              CtrlState <= WriteData;
            end if;
            pEeprom_nS <= '0';

          when WriteData =>
            if WrDataDone = '0' then
              WrDataEn <= '1';
              pEeprom_nS <= '0';
            else
              CtrlState <= Delay_WriteMem;
            end if;

          when Delay_WriteMem =>
            if DelayDone = '0' then
              DoDelay <= '1';
            else
              CtrlState <= SendInstr_RDSR;
              InstrSel   <= "10";
              InstrLoad  <= '1';
            end if;

          when SendInstr_RDSR =>
            if InstrDone = '0' then
              InstrShift <= '1';
            else
              CtrlState  <= WaitForWriteDone;
              StallClock <= '1';
              InstrSel  <= "11";
              InstrLoad <= '1';
            end if;
            pEeprom_nS <= '0';

          when WaitForWriteDone =>
            if WriteDone = '0' then
              WaitWriteDone <= '1';
              pEeprom_nS <= '0';
            else
              CtrlState <= Delay_RDSR;
            end if;

          when Delay_RDSR =>
            if DelayDone = '0' then
              DoDelay <= '1';
            else
              if StatusData(0) = '1' then
                -- This Write has not completed yet
                CtrlState <= SendInstr_RDSR;
                InstrSel   <= "10";
                InstrLoad  <= '1';
              elsif vBlockCount = 15 then
                -- This Write has completed, and now all writes have completed
                CtrlState <= Idle;
              else
                -- This Write has completed, but more writes are required
                vBlockCount := (vBlockCount + 1) mod 16;
                CtrlState <= SendInstr_WREN;
                InstrSel   <= "01";
                InstrLoad  <= '1';
              end if;
            end if;

        end case;
      end if;
    end if;
  end process ControlFSM;

  pEeprom_D <= InstrReg(7) or AddrReg(7) or WrDataWord(31);
  pEepromBusy <= EepromBusy or WriteEeprom or ReadEeprom;


  InstructionReg:  process(pnReset, pLCLKA)
    variable vShiftCount: integer range 0 to 7;
  begin
    if pnReset = '0' then
      InstrReg    <= X"00";
      InstrDone   <= '0';
      vShiftCount := 0;
    elsif rising_edge(pLCLKA) then
      if ClkEN_Fe = '1' then  -- Falling EEPROM clock
        InstrDone   <= '0';

        if InstrLoad = '1' then
          case InstrSel is
            when "00" =>  -- Select Read Mem instruction
              InstrReg <= X"03";

            when "01" =>  -- Select Write Enable instruction
              InstrReg <= X"06";

            when "10" =>  -- Select Read Status Reg instruction
              InstrReg <= X"05";

            when others => -- "11" Select Write Mem instruction
              InstrReg <= X"02";
          end case;

          vShiftCount := 0;

        elsif InstrShift = '1' then
          InstrReg <= InstrReg(6 downto 0) & '0';

          if vShiftCount = 6 then
            InstrDone   <= '1';
          end if;

          vShiftCount := (vShiftCount + 1) mod 8;
        end if;
      end if;
    end if;
  end process InstructionReg;


  AddressReg: process(pnReset, pLCLKA)
    variable vShiftCount: integer range 0 to 7;
  begin
    if pnReset = '0' then
      AddrReg    <= X"00";
      AddrDone   <= '0';
      vShiftCount := 0;
    elsif rising_edge(pLCLKA) then
      if ClkEN_Fe = '1' then  -- Falling EEPROM clock
        AddrDone   <= '0';

        if AddrLoad = '1' then
          AddrReg <= AddrCount & "00";
        elsif AddrShift = '1' then
          AddrReg <= AddrReg(6 downto 0) & '0';

          if vShiftCount = 6 then
            AddrDone   <= '1';
          end if;

          vShiftCount := (vShiftCount + 1) mod 8;
        end if;
      end if;
    end if;
  end process AddressReg;



  -- Process to receive data from EEPROM
  RxData: process(pnReset, pLCLKA)
    variable vBitCount:  integer range 0 to 7;
    variable vByteCount: integer range 0 to 3;
    variable vWordCount: integer range 0 to 63;
  begin
    if pnReset = '0' then
      RxDataDone <= '0';
      RxDataWord <= X"00000000";
      vBitCount  := 0;
      vByteCount := 0;
      vWordCount := 0;
      AddrInc    <= '0';
      pBRamWriteEn <= '0';

    elsif rising_edge(pLCLKA) then
      pBRamWriteEn <= '0';

      if ClkEN_Re = '1' then  -- Rising EEPROM clock
        RxDataDone <= '0';
        AddrInc    <= '0';

        if RxDataShift = '1' then
          RxDataWord <= RxDataWord(30 downto 0) & pEeprom_Q;

          if vWordCount = 63 and vByteCount = 3 and vBitCount = 7 then
            RxDataDone <= '1';
          end if;

          if vByteCount = 3 and vBitCount = 7 then
            vWordCount := (vWordCount + 1) mod 64;
            AddrInc      <= '1';
            pBRamWriteEn <= '1';
          end if;
          if vBitCount = 7 then
            vByteCount := (vByteCount + 1) mod 4;
          end if;
          vBitCount := (vBitCount + 1) mod 8;
        end if;
      end if;
    end if;
  end process RxData;

  pBRamWrData <= RxDataWord;


  -- Process to generate an address counter
  GenAddrCount: process(pnReset, pLCLKA)
  begin
    if pnReset = '0' then
      AddrCount <= (others => '0');
    elsif rising_edge(pLCLKA) then
      if ClkEN_Fe = '1' then  -- Falling EEPROM clock
        if AddrReset = '1' then
          AddrCount <= (others => '0');
        elsif AddrInc = '1' or AddrInc2 = '1' then
          AddrCount <= (unsigned(AddrCount) + 1) and "111111";
        end if;
      end if;
    end if;
  end process GenAddrCount;

  pBRamAddr <= AddrCount;


  -- Process to write data to EEPROM
  WrDataProc: process(pnReset, pLCLKA)
  begin
    if pnReset = '0' then
      WrDataDone  <= '0';
      WrDataWord  <= X"00000000";
      WrBitCount  <= 0;
      WrByteCount <= 0;
      WrWordCount <= 0;
      AddrInc2    <= '0';

    elsif rising_edge(pLCLKA) then
      if ClkEN_Fe = '1' then  -- Falling EEPROM clock
        WrDataDone <= '0';
        AddrInc2   <= '0';

        if WrDataLoad = '1' then
          WrDataWord <= pBRamRdData;
          WrBitCount  <= 0;
          WrByteCount <= 0;
          WrWordCount <= 0;

        elsif WrDataEn = '1' then
          WrDataWord <= WrDataWord(30 downto 0) & '0';

          if WrWordCount = 3 and WrByteCount = 3 and WrBitCount = 6 then
            WrDataDone  <= '1';
          end if;

          if WrByteCount = 2 and WrBitCount = 7 then
            AddrInc2 <= '1';
          end if;

          if WrByteCount = 3 and WrBitCount = 7 then
            WrWordCount <= (WrWordCount + 1) mod 4;
            if WrDataDone = '0' then
              -- Do not update WrDataWord at the end of a block
              WrDataWord <= pBRamRdData;
            end if;
          end if;
          if WrBitCount = 7 then
            WrByteCount <= (WrByteCount + 1) mod 4;
          end if;
          WrBitCount <= (WrBitCount + 1) mod 8;

        else
          WrDataWord <= WrDataWord(30 downto 0) & '0';
        end if;

      end if;
    end if;
  end process WrDataProc;


  -- Process to receive data from EEPROM
  ProcDoRDSR: process(pnReset, pLCLKA)
    variable vBitCount:  integer range 0 to 7;
  begin
    if pnReset = '0' then
      vBitCount  := 0;
      WriteDone  <= '0';
      StatusData <= X"01";
    elsif rising_edge(pLCLKA) then
      if ClkEN_Re = '1' then  -- Rising EEPROM clock
        WriteDone  <= '0';

        if WaitWriteDone = '1' then
          if vBitCount /= 7 then
            vBitCount := (vBitCount + 1) mod 8;
          else
            WriteDone <= '1';
          end if;

          StatusData <= StatusData(6 downto 0) & pEeprom_Q;
        else
          vBitCount  := 0;
        end if;
      end if;
    end if;
  end process ProcDoRDSR;

  pBRamWrData <= RxDataWord;


  GenDoDelay: process(pnReset, pLCLKA)
  begin
    if pnReset = '0' then
      DelayDone  <= '0';
      DelayCount <= 0;
    elsif rising_edge(pLCLKA) then
      if ClkEN_Fe = '1' then  -- Falling EEPROM clock
        if DoDelay = '0' then
          DelayDone  <= '0';
          DelayCount <= 0;
        else
          if DelayCount /= 7 then
            DelayDone  <= '0';
            DelayCount <= DelayCount + 1;
          else
            DelayDone  <= '1';
          end if;
        end if;
      end if;
    end if;
  end process GenDoDelay;




end Rtl;
