---------------------------------------------------------------------------
-- 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
---------------------------------------------------------------------------

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
        pProbeSense        : in  std_ulogic;
        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;
        -- Ping-Pong Block RAM signals
        pBlkRamData        : out std_logic_vector(7 downto 0);
        pBlkRamAddr        : out std_logic_vector(6 downto 0);
        pBlkRamWrEn        : out std_ulogic;
        -- Control signals from registers
        pEncoderReset      : out std_logic_vector(5 downto 0);
        pForceBscan        : out std_ulogic;
        -- Focusing Unit Control Strobe
        pFocusCtrl         : out std_ulogic;
        pFocusWrite        : out std_ulogic;
        -- Delay Value RAM signals
        pDelayValAddr      : out std_logic_vector(8 downto 0);
        pDelayValWr        : 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;


  signal IntAddr:       std_logic_vector(23 downto 0);
  signal LWnR:          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 BlkRamWrite:      std_ulogic;

  signal GateDataStart:    std_ulogic;
  signal GateDataCount:    integer range 0 to 63;
  signal GateDataOut:      std_ulogic;

  signal DelayValAddr:     std_logic_vector(8 downto 0);
  signal DelayValAddrRst:  std_ulogic;
  signal FocusCtrl:        std_logic;  -- Made std_logic to remove synthesis warnings
  signal FocusWrite:       std_logic;  -- Made std_logic to remove synthesis warnings
  signal DoFocus:          std_ulogic;
  signal FocusCount:       integer range 0 to 31;

  signal ForceBScan:       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, pProbeSense, pEncoder0Pos, pEncoder1Pos,
                          pEncoder2Pos, pEncoder3Pos, pEncoder4Pos, pEncoder5Pos, GateDataOut,
                          pPioIn)
  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(cSensorSetup)(31) <= not pProbeSense;
    RegReadData(cEncoder0Output) <= pEncoder0Pos;
    RegReadData(cEncoder1Output) <= pEncoder1Pos;
    RegReadData(cEncoder2Output) <= pEncoder2Pos;
    RegReadData(cEncoder3Output) <= pEncoder3Pos;
    RegReadData(cEncoder4Output) <= pEncoder4Pos;
    RegReadData(cEncoder5Output) <= pEncoder5Pos;
    RegReadData(cGateSetup)(31)  <= GateDataOut;
    RegReadData(cPerIoBank0)(31 downto 2) <= pPioIn(31 downto 2);
    RegReadData(cPerIoBank1)(30 downto 0) <= pPioIn(62 downto 32);
  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(conv_integer(unsigned(cFpgaVersion)))(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(cFpgaVersion)))(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;


  -- Generate the logic to write data to the Delay Value RAM
  -- Note that the adresses used depend on the aperture size
  GenDelayValAddr: process(pnReset, pLClk)
    variable vIncAddr: std_ulogic;
  begin
    if pnReset = '0' then
      DelayValAddr <= "000000000";
      pDelayValWr  <= '0';
      vIncAddr     := '0';
    elsif rising_edge(pLClk) then
      if DelayValAddrRst = '1' then
        DelayValAddr <= "000000000";
      elsif vIncAddr = '1' then
        if DelayValAddr(1 downto 0) = RegBank(cSensorSetup)(9 downto 8) then
          DelayValAddr(1 downto 0) <= "00";
          DelayValAddr(8 downto 2) <= (unsigned(DelayValAddr(8 downto 2)) + 1) and "1111111";
        else
          DelayValAddr(1 downto 0) <= (unsigned(DelayValAddr(1 downto 0)) + 1) and "11";
        end if;
      end if;

      pDelayValWr <= '0';
      if RegWriteEn(cDelayValueData) = '1' and pnFPGACS = '0' then
        pDelayValWr <= '1';
      end if;

      vIncAddr := RegWriteEn(cDelayValueData) and (not pnFPGACS);
    end if;
  end process GenDelayValAddr;

  -- Connect up the pDelayValAddr output
  pDelayValAddr <= DelayValAddr;


  --
  --  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(conv_integer(unsigned(cFpgaVersion)))(RegLoop);
      end loop;

      CalDacStart     <= '0';
      BlkRamWrite     <= '0';
      pEncoderReset   <= "000000";
      ForceBscan      <= '0';
      DelayValAddrRst <= '0';

      GateDataStart      <= '0';
      GateDataCount      <= 0;
      GateDataOut        <= '0';
      DoFocus            <= '0';
      FocusCount         <= 0;
      FocusWrite         <= '0';
      if cFpgaVersion = X"1" then
        FocusCtrl          <= '0';
      end if;

    elsif rising_edge(pLClk) then
      DelayValAddrRst <= '0';
      CalDacStart     <= '0';
      BlkRamWrite     <= '0';
      pEncoderReset   <= "000000";
      ForceBscan      <= '0';
      DoFocus         <= '0';

      GateDataStart <= '0';

      if pnFPGACS = '0' then
        if RegWriteEn(cSensorSetup) = '1' then
          DelayValAddrRst <= '1';
        end if;

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

        if RegWriteEn(cPingPongData) = '1' then
          BlkRamWrite <= '1';
        end if;

        if RegWriteEn(cBScanTriggerCtrl) = '1' and pLDin(31) = '1' then
          ForceBscan <= '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(cFocusUnitCtrl) = '1' and cFpgaVersion = X"1" then
          DoFocus <= '1';
        end if;

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

        for RegLoop in 0 to cNoOfRegs-1 loop
          if RegWriteEn(RegLoop) = '1' then
            RegBank(RegLoop) <= pLDin and cRegSetWriteMask(conv_integer(unsigned(cFpgaVersion)))(RegLoop);
          end if;
        end loop;
      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;

      -- 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 GateDataOut = '1' then
        RegBank(cGateStartStop)  <= RegBank(cGateStartStop)(30 downto 0) & RegBank(cGateAttributes)(31);
        RegBank(cGateAttributes) <= RegBank(cGateAttributes)(30 downto 0) & '0';
      end if;

      if cFpgaVersion = X"1" then
        -- Reset the Focusing Unit Direct Control register after the data has been used
        if DoFocus = '1' then
          FocusCount <= 31;
        elsif FocusCount /= 0 then
          FocusCount <= FocusCount - 1;
        end if;
        
        -- Generate a focus unit direct write enable signal
        if DoFocus = '1' then
          FocusWrite <= '1';
        elsif FocusCount = 0 then
          FocusWrite <= '0';
        end if;

        if DoFocus = '1' then
          RegBank(cFocusUnitCtrl)(31) <= '1';
        elsif FocusCount = 1 then
          RegBank(cFocusUnitCtrl)(31) <= '0';
        end if;

        if FocusCount = 21 then
          FocusCtrl <= '1';
        elsif FocusCount = 11 then
          FocusCtrl <= '0';
        end if;
      end if;

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

    end if;
  end process;

  -- Drive the Focusing Control Output
  GenpFocusCtrl0: if cFpgaVersion = X"0" generate
    -- Just tie to zero for Version 0 of Peripheral FPGA
    FocusCtrl   <= '0';
    FocusWrite  <= '0';
  end generate GenpFocusCtrl0;

  -- Connect up the pFocusCtrl output
  pFocusCtrl  <= FocusCtrl;
  pFocusWrite <= FocusWrite;


  -- 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  => ForceBScan,
             pStrobeOut => pForceBScan);


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

  -- Connect up the Blobk RAM signals
  pBlkRamData <= RegBank(cPingPongData)(7 downto 0);
  pBlkRamAddr <= RegBank(cPingPongData)(14 downto 8);
  pBlkRamWrEn <= BlkRamWrite;


  -- 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;

