---------------------------------------------------------------------------
-- NDT Solutions Ltd. Proprietary Information
-- Company Confidential
-- Copyright 2004
--
-- Filename: FocusUnitCtrl.vhd
-- Language: VHDL '93
-- Project:  Digitiser Card Peripheral FPGA
--
-- Design Description:
-- Module to control the Focusing Unit
---------------------------------------------------------------------------


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



entity FocusUnitCtrl is
  Port (-- Local bus signals
        pnReset          : in  std_ulogic;
        pSampleClk       : in  std_ulogic;
        -- Register data
        pApertureSize    : in  std_logic_vector(1 downto 0);
        pDisApWrites     : in  std_ulogic;
        pDisDelWrites    : in  std_ulogic;
        -- A-Scan Trigger signals
        pAScanTrigger    : in  std_ulogic;
        pAScanCount      : in  std_logic_vector(6 downto 0);
        pAScanInterleave : in  std_logic_vector(1 downto 0);
        -- Delay Value RAM Interface
        pDelayValAddr    : out std_logic_vector(10 downto 0);
        pDelayValData    : in  std_logic_vector(7 downto 0);
        -- Element Number
        pElementNumber   : in  std_logic_vector(7 downto 0);
        -- Transmit / Receive out
        pTx_nRx          : out std_ulogic;
        -- Generated signals
        pProbeCtrl       : out std_logic_vector(2 downto 0);
        pProbeData       : out std_logic_vector(7 downto 0);
        pProbeDClock     : out std_ulogic;
        pProbeTx         : out std_ulogic);
end entity FocusUnitCtrl;


architecture rtl of FocusUnitCtrl 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;


  type tCtrlState is (sIdle, sSetDelayAddr0, sSetDelayAddr1, sSetDelayAddr2, sSetDelayData0,
                      sSetDelayData1, sSetDelayData2, sSetLineData0, sSetLineData1,
                      sSetLineData2, sFireDelay, sFireTrigger, sRxDelay);
  signal CtrlState: tCtrlState;

  signal DelayMaxCount:  std_logic_vector(3 downto 0);
  signal DelayDataInc:   std_ulogic;
  signal DelayDataDone:  std_ulogic;

  signal DelayValAddr:   std_logic_vector(3 downto 0);
  signal DelayValRst:    std_ulogic;
  signal DelayValInc:    std_ulogic;

  signal Rx_nTx:           std_ulogic;
  signal ProbeTx:          std_ulogic;

  signal StartDelayShort:  std_ulogic;
  signal DelayActiveShort: std_ulogic;
  signal DelayDoneShort:   std_ulogic;

  signal StartDelayLong:   std_ulogic;
  signal DelayActiveLong:  std_ulogic;
  signal DelayDoneLong:    std_ulogic;


begin


  GenCtrlFSM: process(pnReset, pSampleClk)
    variable vLastTrigger: std_ulogic;
    variable vTemp: std_logic_vector(1 downto 0);
  begin
    if pnReset = '0' then
      vLastTrigger := '0';
      CtrlState        <= sIdle;
      DelayDataInc     <= '0';
      pProbeDClock     <= '0';
      pProbeCtrl       <= "000";
      ProbeTx          <= '0';
      DelayValInc      <= '0';
      pProbeData       <= X"00";
      DelayValRst      <= '0';
      Rx_nTx           <= '0';
      StartDelayShort  <= '0';
      StartDelayLong   <= '0';

    elsif rising_edge(pSampleClk) then
      DelayDataInc     <= '0';
      DelayValInc      <= '0';
      pProbeDClock     <= '0';
      ProbeTx          <= '0';
      DelayValRst      <= '0';
      StartDelayShort  <= '0';
      StartDelayLong   <= '0';

      case CtrlState is
        when sIdle =>
          DelayValRst <= '1';  -- Reset Delay address
          Rx_nTx      <= '0';  -- Transmit happens first
          pProbeData  <= X"00";

          if pAScanTrigger = '1' and vLastTrigger = '0' then
            vTemp := pDisApWrites & pDisDelWrites;
            case vTemp is
              when "00" | "10" =>
                -- Delay value writes are required
                CtrlState <= sSetDelayAddr0;
              when "01" =>
                -- Delay value writes are not required, but element number writes are
                CtrlState <= sSetLineData0;
              when "11" =>
                -- Neither Delay value writes or element number writes are required
                CtrlState <= sFireTrigger;
              when others =>
                null;
            end case;
            
            StartDelayShort <= '1';
          end if;

        when sSetDelayAddr0 =>
          -- Set delay value address part 1
          pProbeCtrl  <= "000";
          pProbeData <= "0000" & DelayValAddr;
          if DelayDoneShort = '1' then
            CtrlState       <= sSetDelayAddr1;
            StartDelayShort <= '1';
          end if;

        when sSetDelayAddr1 =>
          -- Set delay value address part 1
          pProbeDClock <= '1';
          pProbeCtrl  <= "000";
          if DelayDoneShort = '1' then
            DelayDataInc <= '1';
            CtrlState       <= sSetDelayAddr2;
            StartDelayShort <= '1';
          end if;

        when sSetDelayAddr2 =>
          -- Set delay value address part 2
          pProbeCtrl   <= "000";
          if DelayDoneShort = '1' then
            CtrlState       <= sSetDelayData0;
            StartDelayShort <= '1';
          end if;

        when sSetDelayData0 =>
          -- Set delay value data part 1
          pProbeCtrl  <= "001";
          pProbeData  <= pDelayValData;
          if DelayDoneShort = '1' then
            CtrlState        <= sSetDelayData1;
            StartDelayShort  <= '1';
          end if;

        when sSetDelayData1 =>
          -- Set delay value data part 1
          pProbeDClock <= '1';
          pProbeCtrl  <= "001";
          if DelayDoneShort = '1' then
            CtrlState   <= sSetDelayData2;
            StartDelayShort  <= '1';
            DelayValInc      <= '1';
          end if;

        when sSetDelayData2 =>
          -- Set delay value data part 2
          pProbeCtrl   <= "001";

          if DelayDoneShort = '1' then
            StartDelayShort <= '1';
            vTemp := pDisApWrites & DelayDataDone;
            case vTemp is
              when "00" | "10" =>
                -- More delay data to write yet
                CtrlState  <= sSetDelayAddr0;
              when "01" =>
                -- Delay data written, element number write required next
                CtrlState <= sSetLineData0;
              when "11" =>
                -- Delay data written, no element number write required
                if Rx_nTx = '0' then
                  -- We are doing a Tx
                  StartDelayShort <= '1';
                  CtrlState       <= sFireDelay;
                else
                  -- We are doing an Rx
                  CtrlState <= sIdle;
                end if;
              when others =>
                null;
            end case;
          end if;

        when sSetLineData0 =>
          -- Set the element number part 1
          pProbeDClock <= '0';
          pProbeCtrl <= "101";
          pProbeData <= pElementNumber;
          if DelayDoneShort = '1' then
            CtrlState       <= sSetLineData1;
            StartDelayShort <= '1';
          end if;

        when sSetLineData1 =>
          -- Set the element number part 1
          pProbeDClock <= '1';
          pProbeCtrl <= "101";
          if DelayDoneShort = '1' then
            CtrlState       <= sSetLineData2;
            StartDelayShort <= '1';
          end if;

        when sSetLineData2 =>
          -- Set the element number part 2
          pProbeCtrl <= "101";
          if DelayDoneShort = '1' then
            if Rx_nTx = '0' then
              -- We are doing a Tx
              CtrlState       <= sFireDelay;
              StartDelayShort <= '1';
            else
              -- We are doing an Rx
              CtrlState <= sIdle;
            end if;
          end if;

        when sFireDelay =>
          -- Insert a delay between data writes and trigger fireing
          pProbeData       <= X"00";
          if DelayDoneShort = '1' then
            CtrlState      <= sFireTrigger;
            StartDelayLong <= '1';
          end if;

        when sFireTrigger =>
          -- Fire the trigger
          pProbeData  <= X"00";
          ProbeTx    <= '1';

          if DelayDoneLong = '1' then
            if pAScanInterleave = "00" then
              -- Interleaving is not enabled, no extra writes required for receive
              CtrlState <= sIdle;
            else
              -- Interleaving is enabled              
              Rx_nTx          <= '1';
              CtrlState       <= sRxDelay;
              StartDelayShort <= '1';
            end if;
          end if;

        when sRxDelay =>
          -- Delay before programming for receive
          pProbeData       <= X"00";
          if ProbeTx = '0' and DelayDoneShort = '1' then
            vTemp := pDisApWrites & pDisDelWrites;
            case vTemp is
              when "00" | "10" =>
                -- Delay value writes are required
                CtrlState <= sSetDelayAddr0;
              when "01" =>
                -- Delay value writes are not required, but element number writes are
                CtrlState <= sSetLineData0;
              when "11" =>
                -- Neither Delay value writes or element number writes are required
                CtrlState <= sIdle;
              when others =>
                null;
            end case;
            
            StartDelayShort <= '1';
          end if;

      end case;

      vLastTrigger := pAScanTrigger;
    end if;
  end process GenCtrlFSM;

  pProbeTx <= ProbeTx;

  -- Generate a short strobe (15 clocks) for generating slow(ish) writes
  StrobeExtenderShort: StrobeExtender
    generic map(gLfsrBits => 4)
    port map(pnReset    => pnReset,
             pClk       => pSampleClk,
             pStrobeIn  => StartDelayShort,
             pStrobeOut => DelayActiveShort);

  DelayDoneShort <= '1' when (StartDelayShort = '0' and DelayActiveShort = '0') else '0';

  -- Generate a long strobe (127 clocks) for lengthing the Tx pulse
  StrobeExtenderLong: StrobeExtender
    generic map(gLfsrBits => 7)
    port map(pnReset    => pnReset,
             pClk       => pSampleClk,
             pStrobeIn  => StartDelayLong,
             pStrobeOut => DelayActiveLong);

  DelayDoneLong <= '1' when (StartDelayLong = '0' and DelayActiveLong = '0') else '0';


  pTx_nRx <= not Rx_nTx;


  GenDelayMaxCount: process(pApertureSize)
  begin
    case pApertureSize is
      when "00"   => DelayMaxCount <= "0011";
      when "01"   => DelayMaxCount <= "0111";
      when "10"   => DelayMaxCount <= "1011";
      when others => DelayMaxCount <= "1111";
    end case;
  end process GenDelayMaxCount;


  GenDelayValAddr: process(pnReset, pSampleClk)
  begin
    if pnReset = '0' then
      DelayDataDone <= '0';
      DelayValAddr  <= X"0";
    elsif rising_edge(pSampleClk) then
--      DelayDataDone <= '0';

      if DelayValRst = '1' then
        DelayValAddr <= X"0";
        DelayDataDone <= '0';

      elsif DelayValInc = '1' then
        if DelayValAddr = DelayMaxCount then
          DelayValAddr  <= X"0";
        else
          DelayValAddr <= (unsigned(DelayValAddr) + 1) and X"F";
        end if;
      end if;

      if DelayValAddr = X"0" then
        DelayDataDone <= '1';
      else
        DelayDataDone <= '0';
      end if;

    end if;
  end process GenDelayValAddr;

  -- Connect up pDelayValAddr output
  pDelayValAddr <= pAScanCount & DelayValAddr;


end architecture rtl;


