---------------------------------------------------------------------------
-- NDT Solutions Ltd. Proprietary Information
-- Company Confidential
-- Copyright 2004
--
-- Filename: LBusInterface.vhd
-- Language: VHDL '93
-- Project:  Digitiser Card Peripheral FPGA
--
-- Design Description:
-- Module to interface the local bus with the rest of the system - Sequence table version
---------------------------------------------------------------------------

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

-- Bring in the constant cFpgaVersion to define the FPGA version
library work;
use work.PeripheralVerPkg.all;

library work;
use work.PeripheralFpgaPkg.all;


entity PerLBusInterface is
  port (-- Local bus signals
        pnReset            : in  std_ulogic;
        pLClk              : in  std_ulogic;
        pnFPGACS           : in  std_ulogic;
        pLAin              : in  std_logic_vector(31 downto 2);
        pLDin              : in  std_logic_vector(31 downto 0);
        pLDout             : out std_logic_vector(31 downto 0);
        pLDoutput_enable   : out std_ulogic;
        pLWnR              : in  std_ulogic;
        -- Generic IO signals
        pPioIn             : in  std_logic_vector(62 downto 2);
        pPioOut            : out std_logic_vector(62 downto 2);
        pPioDdr            : out std_logic_vector(62 downto 2);
        -- Inputs for readable register bits
        pEncoder0Pos       : in  std_logic_vector(31 downto 0);
        pEncoder1Pos       : in  std_logic_vector(31 downto 0);
        pEncoder2Pos       : in  std_logic_vector(31 downto 0);
        pEncoder3Pos       : in  std_logic_vector(31 downto 0);
        pEncoder4Pos       : in  std_logic_vector(31 downto 0);
        pEncoder5Pos       : in  std_logic_vector(31 downto 0);
        -- Peripheral FPGA Gate data outputs
        pGateDataOut       : out std_ulogic;
        pGateDataEn        : out std_ulogic;
        pGateDataSel       : out std_logic_vector(2 downto 0);
        -- Calibration DAC outputs
        pCalDac_SDI        : out std_ulogic;
        pCalDac_CLK        : out std_ulogic;
        pCalDac_nLD        : out std_ulogic;
        pCalDac_nCS        : out std_ulogic;
        -- Control signals from registers
        pEncoderReset      : out std_logic_vector(5 downto 0);
        pForceSequence     : out std_ulogic;
        pSequenceActive    : in  std_ulogic;
        -- Sequence table control signals
        pSequenceStep      : in  std_logic_vector(4 downto 0);
        pGateStep          : out std_ulogic;
        pGateStepEn        : out std_ulogic;
        -- Register Outputs
        pRegData           : out tRegSet);

        attribute register_balancing: string;
        attribute register_balancing of PerLBusInterface: entity is "yes";
end entity PerLBusInterface;


architecture Rtl of PerLBusInterface is

  component StrobeExtender is
    generic(gLfsrBits: integer range 3 to 32 := 3);
    port(pnReset    : in  std_ulogic;
         pClk       : in  std_ulogic;
         pStrobeIn  : in  std_ulogic;
         pStrobeOut : out std_ulogic);
  end component StrobeExtender;

  component DualPortRam is
    generic(gAddressWidth: integer range 1 to 32 := 6;
            gDataWidth:    integer range 1 to 32 := 32);
    port(pClk       : in  std_ulogic;
         pWriteEn   : in  std_ulogic;
         pAddr      : in  std_logic_vector(gAddressWidth-1 downto 0);
         pDataIn    : in  std_logic_vector(gDataWidth-1 downto 0);
         pDataOut   : out std_logic_vector(gDataWidth-1 downto 0);
         pDpClk     : in  std_ulogic;
         pDpAddr    : in  std_logic_vector(gAddressWidth-1 downto 0);
         pDpDataOut : out std_logic_vector(gDataWidth-1 downto 0));
  end component DualPortRam;

  component DistRam32
    generic(gDataWidth:    integer range 1 to 64 := 64);
    port(pClk     : in  std_ulogic;
         pWriteEn : in  std_ulogic;
         pAddr    : in  std_logic_vector(4 downto 0);
         pDataIn  : in  std_logic_vector(gDataWidth-1 downto 0);
         pDataOut : out std_logic_vector(gDataWidth-1 downto 0));
  end component;

  signal IntAddr:       std_logic_vector(23 downto 0);
  signal LWnR:          std_ulogic;
  signal nFPGACSDel:    std_ulogic;

  -- Signals to create internal registers
  signal RegWriteEn:        std_logic_vector(cNoOfRegs-1 downto 0);  -- Write strobes for registers
  signal RegBank:           tRegSet;
  signal RegReadData:       tRegSet;
  signal ReadRegData:       std_logic_vector(31 downto 0);
  signal ReadRegMask:       std_logic_vector(31 downto 0);
  signal ReadRegDataMux:    std_logic_vector(31 downto 0);

  signal CalClkEn1:         std_ulogic;
  signal CalClkEn2:         std_ulogic;
  signal CalDacStart:       std_ulogic;
  signal CalDacActive:      std_ulogic;
  signal CalDacCount:       integer range 0 to 31;
  signal CalDacShiftReg:    std_logic_vector(13 downto 0);

  signal GateSeqStart:      std_ulogic;
  signal GateSeqRunning:    std_ulogic;
  signal GateSeqRunningDel: std_ulogic;
  signal GateSeqCount:      std_logic_vector(4 downto 0);

  signal GateDataStart:     std_ulogic;
  signal GateAttributeLoad: std_ulogic;
  signal GateDataCount:     integer range 0 to 63;
  signal GateDataOut:       std_ulogic;
  signal SequenceMux:       std_logic_vector(4 downto 0);

  signal HwCtrlTableWe:       std_ulogic;
  signal HwCtrlTableWrAddr:   std_logic_vector(5 downto 0);
  signal HwCtrlOutAddr:       std_logic_vector(5 downto 0);
  signal TrigCtrlTableWe:     std_ulogic;
  signal PulseGeneratorWe:    std_ulogic;
  signal AScanCtrlWe:         std_ulogic;
  signal GateWe:              std_ulogic;
  signal GateWrAddr:          std_logic_vector(5 downto 0);
  signal GateRdAddr:          std_logic_vector(5 downto 0);

  signal HardwareCtrlReg:     std_logic_vector(31 downto 0);
  signal HardwareCtrlOut:     std_logic_vector(31 downto 0);
  signal TriggerCtrlReg:      std_logic_vector(31 downto 0);
  signal TriggerCtrlOut:      std_logic_vector(31 downto 0);
  signal PulseGeneratorReg:   std_logic_vector(11 downto 0);
  signal PulseGeneratorOut:   std_logic_vector(11 downto 0);
  signal AScanCtrlReg:        std_logic_vector(26 downto 20);
  signal AScanCtrlOut:        std_logic_vector(26 downto 20);
  signal GateOut:             std_logic_vector(31 downto 0);

  signal ForceSequence:       std_ulogic;

begin

  -- Process to latch value on address bus and increment it as required
  GenIntAddr: process(pnReset, pLCLK)
  begin
    if pnReset = '0' then
      IntAddr   <= (others => '0');
      LWnR      <= '1';

    elsif rising_edge(pLCLK) then
      if pnFPGACS = '1' then
        -- Load address
        IntAddr(23 downto 2) <= pLAin(23 downto 2);
        LWnR <= pLWnR;

      else
        -- Increment address
        IntAddr(23 downto 2) <= unsigned(IntAddr(23 downto 2)) + 1;

      end if;
    end if;
  end process GenIntAddr;

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


  -- Generate signal containing all read register data
  GenReadRegData: process(RegBank, CalDacActive, pEncoder0Pos, pEncoder1Pos, pEncoder2Pos,
                          pEncoder3Pos, pEncoder4Pos, pEncoder5Pos, GateSeqRunning, pSequenceActive,
                          pPioIn, HardwareCtrlReg, TriggerCtrlReg, PulseGeneratorReg, AScanCtrlReg)
  begin
    -- By default each register actually reads its writeable bits
    for N in 0 to cNoOfRegs-1 loop
      RegReadData(N) <= RegBank(N);
    end loop;

    -- These are the exceptions to this and will override the defaults above
    -- Remember to add any new signals to the process list...
    RegReadData(cCalibrationData)(31) <= CalDacActive;
    RegReadData(cEncoder0Output) <= pEncoder0Pos;
    RegReadData(cEncoder1Output) <= pEncoder1Pos;
    RegReadData(cEncoder2Output) <= pEncoder2Pos;
    RegReadData(cEncoder3Output) <= pEncoder3Pos;
    RegReadData(cEncoder4Output) <= pEncoder4Pos;
    RegReadData(cEncoder5Output) <= pEncoder5Pos;
    RegReadData(cGateSetup)(31)  <= GateSeqRunning;
    RegReadData(cSeqControl)(30) <= pSequenceActive;
    RegReadData(cPerIoBank0)(31 downto 2) <= pPioIn(31 downto 2);
    RegReadData(cPerIoDdr0)(31 downto 2) <= HardwareCtrlReg(31 downto 2);
    RegReadData(cPerIoBank1)(30 downto 0) <= pPioIn(62 downto 32);
    RegReadData(cPerIoDdr1)(30 downto 0) <= HardwareCtrlReg(30 downto 0);
    -- Sequence Step register block RAM data
    RegReadData(cHardwareCtrl)(5 downto 2) <= HardwareCtrlReg(5 downto 2);
    RegReadData(cHardwareCtrl)(30 downto 23) <= HardwareCtrlReg(30 downto 23);
    RegReadData(cTriggerCtrl) <= TriggerCtrlReg;
    RegReadData(cPulseGenerator)(11 downto 0) <= PulseGeneratorReg;
    RegReadData(cAScanCtrl)(26 downto 20) <= AScanCtrlReg;
  end process GenReadRegData;


  GenTristateReadDataMux1: for N in 0 to cNoOfRegs-1 generate
    process(IntAddr, RegReadData)
    begin
      for M in 0 to 31 loop
        ReadRegDataMux(M) <= 'Z';
        if conv_integer(unsigned(IntAddr(cDecodeBits-1 downto 2))) = N then
          if cRegSetReadMask(N)(M) = '1' then
            ReadRegDataMux(M) <= RegReadData(N)(M);
          end if;
        end if;
      end loop;
    end process;
  end generate GenTristateReadDataMux1;


  -- Process to generate register write strobes
  GenRegAccess: process(pnReset, pLClk)
  begin
    if pnReset = '0' then
      ReadRegData <= (others => '0');
      ReadRegMask <= (others => '0');
      RegWriteEn  <= (others => '0');

    elsif rising_edge(pLClk) then
      -- Generate read data for register and SRAM reads
      if pnFPGACS = '0' then
        -- Read of DSP FPGA register
        ReadRegData <= ReadRegDataMux;
        ReadRegMask <= cRegSetReadMask(conv_integer(unsigned(IntAddr(cDecodeBits-1 downto 2))));
      end if;

      -- Generate register write strobes
      RegWriteEn <= (others => '0');
      if LWnR = '1' and pnFPGACS = '0' then
        -- Generate the register write strobes
        RegWriteEn(conv_integer(unsigned(IntAddr(7 downto 2)))) <= '1';
      end if;

    end if;
  end process GenRegAccess;


  -- Drive local bus data output
  pLDout <= ReadRegData and ReadRegMask;


  --
  --  Actual register write logic
  --
  process(pnReset, pLClk)
  begin
    if pnReset = '0' then
      -- Initial values for registers
      for RegLoop in 0 to cNoOfRegs-1 loop
        RegBank(RegLoop) <= cRegSetInitVals(RegLoop);
      end loop;
      -- invert to use i/O tristate control FFs
      RegBank(cPerIoDdr0) <= not cRegSetInitVals(cPerIoDdr0);
      RegBank(cPerIoDdr1) <= not cRegSetInitVals(cPerIoDdr1);

      CalDacStart       <= '0';
      pEncoderReset     <= "000000";
      ForceSequence     <= '0';
 
      GateSeqStart      <= '0';
      GateAttributeLoad <= '0';
      GateSeqRunning    <= '0';
      GateSeqRunningDel <= '0';
      GateSeqCount      <= "11111";
      GateRdAddr        <= "000000";
      GateDataStart     <= '0';
      GateDataCount     <= 0;
      GateDataOut       <= '0';
      pGateStep         <= '0';

    elsif rising_edge(pLClk) then
      CalDacStart     <= '0';
      pEncoderReset   <= "000000";
      ForceSequence   <= '0';

      GateSeqStart    <= '0';
      GateDataStart   <= '0';
      pGateStep       <= '0';

      if pnFPGACS = '0' then
        if RegWriteEn(cCalibrationData) = '1' then
          CalDacStart <= '1';
        end if;

        if RegWriteEn(cBScanTriggerCtrl) = '1' and pLDin(31) = '1' then
          ForceSequence <= '1';
        end if;

        if RegWriteEn(cEncoder0Output) = '1' then
          pEncoderReset(0) <= '1';
        end if;
        if RegWriteEn(cEncoder1Output) = '1' then
          pEncoderReset(1) <= '1';
        end if;
        if RegWriteEn(cEncoder2Output) = '1' then
          pEncoderReset(2) <= '1';
        end if;
        if RegWriteEn(cEncoder3Output) = '1' then
          pEncoderReset(3) <= '1';
        end if;
        if RegWriteEn(cEncoder4Output) = '1' then
          pEncoderReset(4) <= '1';
        end if;
        if RegWriteEn(cEncoder5Output) = '1' then
          pEncoderReset(5) <= '1';
        end if;
        
        -- Don't generate these resets for Ver 1 of the FPGA
        if cFpgaVersion = X"1" then
          pEncoderReset(3) <= '0';
          pEncoderReset(4) <= '0';
          pEncoderReset(5) <= '0';
        end if;

        if RegWriteEn(cGateSetup) = '1' then
          GateSeqStart <= '1';
        end if;

        for RegLoop in 0 to cNoOfRegs-1 loop
          if RegWriteEn(RegLoop) = '1' then
            RegBank(RegLoop) <= pLDin and cRegSetWriteMask(RegLoop);
          end if;
        end loop;

        -- invert to use i/O tristate control FFs
        if RegWriteEn(cPerIoDdr0) = '1' then
          RegBank(cPerIoDdr0) <= (not pLDin) and cRegSetWriteMask(cPerIoDdr0);
        end if;
        if RegWriteEn(cPerIoDdr1) = '1' then
          RegBank(cPerIoDdr1) <= (not pLDin) and cRegSetWriteMask(cPerIoDdr1);
        end if;
      end if;

      -- Generate Gate Sequence Count
      if GateSeqStart = '1' then
        GateSeqCount <= "00000";
        GateDataStart <= '1';
        GateSeqRunning <= '1';
        pGateStep <= '1';
      elsif GateSeqRunning = '1' then
        if GateDataStart = '0' and GateDataCount = 0 then
          if GateSeqCount = "11111" then
            GateSeqRunning <= '0';
          else
            GateDataStart <= '1';
            pGateStep <= '1';
            GateSeqCount <= unsigned(GateSeqCount) + 1;
          end if;
        end if;
      end if;

      GateSeqRunningDel <= GateSeqRunning;

      if GateSeqRunning = '1' and GateSeqRunningDel = '0' then
        pGateStep <= '1';
      end if;

      if GateSeqStart = '1' then
        GateRdAddr <= "000000";
      elsif GateDataStart = '1' or GateAttributeLoad = '1' then
        GateRdAddr <= unsigned(GateRdAddr) + 1;
      end if;

      -- Generate the counter for clocking out the gate data in a serial datastream
      if GateDataStart = '1' then
        GateDataCount <= 1;
      elsif GateDataCount /= 0 then
        GateDataCount <= (GateDataCount + 1) mod 64;
      end if;

      if GateDataCount = 31 then
        GateAttributeLoad <= '1';
      else
        GateAttributeLoad <= '0';
      end if;

      -- Generate the gate data out enable signal
      if GateDataStart = '1' then
        GateDataOut <= '1';
      elsif GateDataCount = 0 then
        GateDataOut <= '0';
      end if;

      -- Generate the gate data serial datastream out
      if GateDataStart = '1' or GateAttributeLoad = '1' then
        RegBank(cGateStartStop)  <= GateOut;
      elsif GateDataOut = '1' then
        RegBank(cGateStartStop)  <= RegBank(cGateStartStop)(30 downto 0) & '0';
      end if;

      pGateDataOut <= RegBank(cGateStartStop)(31);
      pGateDataEn  <= GateDataOut;
      pGateDataSel <= RegBank(cGateSetup)(2 downto 0);

      -- override registers for Sequence table register banks
      RegBank(cHardwareCtrl)(5 downto 2) <= HardwareCtrlOut(5 downto 2);
      RegBank(cHardwareCtrl)(30 downto 23) <= HardwareCtrlOut(30 downto 23);
      RegBank(cTriggerCtrl) <= TriggerCtrlOut;
      RegBank(cPulseGenerator)(11 downto 0) <= PulseGeneratorOut;
      RegBank(cAScanCtrl)(26 downto 20) <= AScanCtrlOut;
    end if;
  end process;

  pGateStepEn <= GateSeqRunning;

  HwCtrlTableWe <= (RegWriteEn(cHardwareCtrl) or RegWriteEn(cPerIoDdr0) or RegWriteEn(cPerIoDdr1)) and not pnFPGACS;
  TrigCtrlTableWe <= RegWriteEn(cTriggerCtrl) and not pnFPGACS;
  PulseGeneratorWe <= RegWriteEn(cPulseGenerator) and not pnFPGACS;
  AScanCtrlWe <= RegWriteEn(cAScanCtrl) and not pnFPGACS;
  GateWe <= (RegWriteEn(cGateStartStop) or RegWriteEn(cGateAttributes)) and not pnFPGACS;

  HwCtrlTableWrAddr <= '0' & RegBank(cSeqStepNo)(4 downto 0) when unsigned(pLAin(7 downto 2)) = cHardwareCtrl else
                       '1' & pLAin(6 downto 2);
  HwCtrlOutAddr <= '0' & pSequenceStep;

  -- Block RAMs for Sequence table register banks
  -- Hardware control also used for I/o DDR shadow registers
  HardwareCtrlTable: DualPortRam
    generic map (gAddressWidth => 6,
                 gDataWidth    => 32)
    port map (pClk       => pLClk,
              pWriteEn   => HwCtrlTableWe,
              pAddr      => HwCtrlTableWrAddr,
              pDataIn    => pLDin,
              pDataOut   => HardwareCtrlReg,
              pDpClk     => pLClk,
              pDpAddr    => HwCtrlOutAddr,
              pDpDataOut => HardwareCtrlOut);

  TriggerCtrlTable: DualPortRam
    generic map (gAddressWidth => 5,
                 gDataWidth    => 32)
    port map (pClk       => pLClk,
              pWriteEn   => TrigCtrlTableWe,
              pAddr      => RegBank(cSeqStepNo)(4 downto 0),
              pDataIn    => pLDin,
              pDataOut   => TriggerCtrlReg,
              pDpClk     => pLClk,
              pDpAddr    => pSequenceStep,
              pDpDataOut => TriggerCtrlOut);

  PulseGeneratorTable: DualPortRam
    generic map (gAddressWidth => 5,
                 gDataWidth    => 12)
    port map (pClk       => pLClk,	  
              pWriteEn   => PulseGeneratorWe,
              pAddr      => RegBank(cSeqStepNo)(4 downto 0),
              pDataIn    => pLDin(11 downto 0),
              pDataOut   => PulseGeneratorReg,
              pDpClk     => pLClk,
              pDpAddr    => pSequenceStep,
              pDpDataOut => PulseGeneratorOut);

  AScanCtrlTable: DualPortRam
    generic map (gAddressWidth => 5,
                 gDataWidth    => 7)
    port map (pClk       => pLClk,	  
              pWriteEn   => AScanCtrlWe,
              pAddr      => RegBank(cSeqStepNo)(4 downto 0),
              pDataIn    => pLDin(26 downto 20),
              pDataOut   => AScanCtrlReg,
              pDpClk     => pLClk,
              pDpAddr    => pSequenceStep,
              pDpDataOut => AScanCtrlOut);

  GateWrAddr <= RegBank(cSeqStepNo)(4 downto 0) & RegWriteEn(cGateAttributes);

  GateTable: DualPortRam
    generic map (gAddressWidth => 6,
                 gDataWidth    => 32)
    port map (pClk       => pLClk,
              pWriteEn   => GateWe,
              pAddr      => GateWrAddr,
              pDataIn    => pLDin,
              pDataOut   => open,
              pDpClk     => pLClk,
              pDpAddr    => GateRdAddr,
              pDpDataOut => GateOut);

  -- Lengthen the ForceBScan strobe to produce the pForceBScan (7 clock cycles wide) output.
  StrobeExtender0: StrobeExtender
    generic map(gLfsrBits => 3)
    port map(pnReset    => pnReset,
             pClk       => pLClk,
             pStrobeIn  => ForceSequence,
             pStrobeOut => pForceSequence);

  -- Connect register bank to module outputs
  pRegData <= RegBank;
  pLDoutput_enable <= '1' when (pnFpgaCS = '0' and LWnR = '0') else '0';


  -- Generate a clock enable for the Cal DAC logic
  process(pnReset, pLClk)
    variable vCount: integer range 0 to 7;
  begin
    if pnReset = '0' then
      vCount := 0;
      CalClkEn1 <= '0';
      CalClkEn2 <= '0';
    elsif rising_edge(pLClk) then
      if vCount = 7 then
        CalClkEn1 <= '1';
      else
        CalClkEn1 <= '0';
      end if;

      if vCount = 3 then
        CalClkEn2 <= '1';
      else
        CalClkEn2 <= '0';
      end if;

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


  -- Generate the Calibration DAC write
  process(pnReset, pLClk)
  begin
    if pnReset = '0' then
      CalDacActive <= '0';
      CalDacCount  <= 0;
    elsif rising_edge(pLClk) then
      if CalDacStart = '1' then
        CalDacActive <= '1';
        CalDacCount  <= 0;
      elsif CalClkEn1 = '1' and CalDacCount = 18 then
        CalDacActive <= '0';
      end if;

      if CalClkEn1 = '1' then
        if CalDacActive = '1' then
          CalDacCount <= (CalDacCount + 1) mod 32;
        end if;
      end if;
    end if;
  end process;


  GenCalDacSignals: process(pnReset, pLClk)
  begin
    if pnReset = '0' then
      pCalDac_CLK <= '1';
      pCalDac_nLD <= '1';
      pCalDac_nCS <= '1';
--      CalDacShiftReg <= "00000000000000";

    elsif rising_edge(pLClk) then
      if CalClkEn1 = '1' then
        case CalDacCount is
          when 0 =>
            pCalDac_CLK <= '1';
            pCalDac_nLD <= '1';
            pCalDac_nCS <= '1';
          when 1 =>
            pCalDac_CLK <= '0';
            pCalDac_nLD <= '1';
            pCalDac_nCS <= '0';
            CalDacShiftReg <= RegBank(cCalibrationData)(13 downto 0);
          when 2 to 14 =>
            pCalDac_CLK <= '0';
            pCalDac_nLD <= '1';
            pCalDac_nCS <= '0';
            CalDacShiftReg <= CalDacShiftReg(12 downto 0) & '0';
          when 15 =>
            pCalDac_CLK <= '1';
            pCalDac_nLD <= '1';
            pCalDac_nCS <= '1';
          when 16 =>
            pCalDac_CLK <= '1';
            pCalDac_nLD <= '0';
            pCalDac_nCS <= '1';
          when others =>
            pCalDac_CLK <= '1';
            pCalDac_nLD <= '1';
            pCalDac_nCS <= '1';
        end case;

      elsif CalClkEn2 = '1' then
        pCalDac_CLK <= '1';
      end if;
    end if;
  end process GenCalDacSignals;

  pCalDac_SDI <= CalDacShiftReg(13);


  -- Generic IO signals
  pPioOut(31 downto 2) <= RegBank(cPerIoBank0)(31 downto 2);
  pPioDdr(31 downto 2) <= RegBank(cPerIoDdr0)(31 downto 2);
  pPioOut(62 downto 32) <= RegBank(cPerIoBank1)(30 downto 0);
  pPioDdr(62 downto 32) <= RegBank(cPerIoDdr1)(30 downto 0);

end Rtl;

