---------------------------------------------------------------------------
-- NDT Solutions Ltd. Proprietary Information
-- Company Confidential
-- Copyright 2004
--
-- Filename: TriggerGenerator.vhd
-- Language: VHDL '93
-- Project:  Digitiser Card DSP FPGA
--
-- Design Description:
-- Module to control trigger generation, DataAvailable register setting
-- and bank switching
--
---------------------------------------------------------------------------

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

entity TriggerGenerator is
  port(pnReset            : in  std_ulogic;
       pADC_2xClk         : in  std_ulogic;
       pSampleEn          : in  std_ulogic;
       pTriggerIn         : in  std_ulogic;
       pTriggerOut        : out std_ulogic;
       pAllTriggersOut    : out std_ulogic;
       pSeqStep0          : in  std_ulogic;
       pCaptureDone       : in  std_ulogic;
       pDataReady         : in  std_ulogic;
       pSetDataReady      : out std_ulogic;
       pSetEarlyTrigError : out std_ulogic;
       pSetDataFullError  : out std_ulogic;
       pSomeDataReady     : out std_ulogic;
       pBScansAvailable   : out std_logic_vector(14 downto 0);
       -- Early data delivery request
       pEarlyDataReq      : in  std_ulogic;
       -- Register Data
       pAScansPerBScan    : in  std_logic_vector(6 downto 0);
       pNumOfBScans       : in  std_logic_vector(14 downto 0);
       pStorageMode       : in  std_logic_vector(1 downto 0);
       --
       pAScanNum          : out std_logic_vector(6 downto 0);
       pAScanActive       : out std_ulogic;
       pBScanActive       : out std_ulogic;
       pLastAScan         : out std_ulogic;
       pResetElemNum      : out std_ulogic;
       pResetAScanAddr    : out std_ulogic;
       pResetCScanAddr    : out std_ulogic;
       pSramBankSelect    : out std_ulogic;
       pnSramBankSelect   : out std_ulogic);
end entity TriggerGenerator;



architecture Rtl of TriggerGenerator is

  component RippleCounter is
    generic(gCountWidth: integer range 2 to 32 := 16);
    port(pReset : in  std_ulogic;
         pInc   : in  std_ulogic;
         pCount : out std_logic_vector(gCountWidth-1 downto 0));
  end component RippleCounter;

  attribute register_balancing: string;
  attribute register_duplication: string;

  signal Trigger:        std_ulogic;
  -- The flip-flops created for these signals must be left alone
  attribute register_balancing of Trigger: signal is "no";
  attribute register_duplication of Trigger: signal is "no";

  signal LastTrigger:    std_ulogic;
  signal DataReady:      std_ulogic;
  attribute register_balancing of DataReady: signal is "no";
  attribute register_duplication of DataReady: signal is "no";
  signal DataRead:       std_ulogic;
  signal TriggerEdge:    std_ulogic;
  signal AllTrigEdge:    std_ulogic;
  signal EarlyDataReq:   std_ulogic;

  signal AScanCount:     integer range 0 to 127;
  signal LastAScan:      std_ulogic;
  signal BScanCount:     integer range 0 to (2 ** 15) - 1;  -- 15-bit counter
  signal AScanActive:    std_ulogic;
  signal BScanActive:    std_ulogic;
  signal BankDone:       std_ulogic;
  signal FirstBScan:     std_ulogic;

  attribute uselowskewlines: string;
  attribute uselowskewlines of BankDone: signal is "yes";

  signal EarlyTrigError: std_ulogic;
  signal DataFullError:  std_ulogic;
  signal SetDataReady:   std_ulogic;

  type tCtrlStates is (sEmpty, sFilling1, sFull1, sFilling2, sFull2);
  signal CtrlState: tCtrlStates;

  signal SramBankSelect: std_ulogic;
  signal nSramBankSelect: std_ulogic;

--  signal ACountReset: std_ulogic;
--  signal ACountInc:   std_ulogic;
--  signal ACount:      std_logic_vector(6 downto 0);

  signal BCountReset: std_ulogic;
  signal BCountInc:   std_ulogic;
  signal BCount:      std_logic_vector(14 downto 0);
  signal SeqWait:     std_ulogic;

begin


  -- Instantiate ripple counter for A-Scan counter
  -- Note ripple counters are VERY slow, but are suitable
  -- for use here as the count is only changed at the start of an A-Scan
  -- and only read at the end of an A-Scan
--  RippleCounter0: RippleCounter
--    generic map(gCountWidth => 7)
--    port map(pReset => ACountReset,
--             pInc   => ACountInc,
--             pCount => ACount);
--  AScanCount <= conv_integer(unsigned(ACount));

  LastAScan <= '1' when AScanCount = conv_integer(unsigned(pAScansPerBScan)) else '0';
  pLastAScan <= LastAScan;

  -- Instantiate ripple counter for B-Scan counter
  -- Note ripple counters are VERY slow, but are suitable
  -- for use here as the count is only changed at the start of an A-Scan
  -- and only read at the end of an A-Scan
  RippleCounter1: RippleCounter
    generic map(gCountWidth => 15)
    port map(pReset => BCountReset,
             pInc   => BCountInc,
             pCount => BCount);

  BScanCount <= conv_integer(unsigned(BCount));


  -- Clock the input trigger signal through 2 D-types to remove metastability
  -- Also clock pDataReady into the pADC_2xClk clock domain
  -- Detect rising edge of trigger (TriggerEdge)
  ChangeClockDomain: process(pnReset, pADC_2xClk)
    variable vTrigger:       std_ulogic;
    variable vDataReady:     std_ulogic;
    variable vLastDataReady: std_ulogic;
    attribute register_balancing of vTrigger: variable is "no";
    attribute register_duplication of vTrigger: variable is "no";
    attribute register_balancing of vDataReady: variable is "no";
    attribute register_duplication of vDataReady: variable is "no";
  begin
    if pnReset = '0' then
      LastTrigger    <= '0';
      Trigger        <= '0';
      vTrigger       := '0';
      vLastDataReady := '0';
      DataReady      <= '0';
      vDataReady     := '0';
      DataRead       <= '0';
      TriggerEdge    <= '0';
      AllTrigEdge    <= '0';
      SeqWait        <= '1';

    elsif rising_edge(pADC_2xClk) then
      if Trigger = '1' and LastTrigger = '0' then
        AllTrigEdge <= '1';
        if CtrlState /= sFull2 and AScanActive /= '1' then
          -- Ignore triggers when an A-Scan is active or both banks are full
          if SeqWait = '0' or pSeqStep0 = '1' then
            TriggerEdge <= '1';
            SeqWait <= '0';
          else
            SeqWait <= '1';
            AllTrigEdge <= '0';
          end if;
        else
          SeqWait <= '1';
        end if;
      elsif pSampleEn = '1' then
        TriggerEdge <= '0';
        AllTrigEdge <= '0';
      end if;

      LastTrigger <= Trigger;
      Trigger     <= vTrigger;
      vTrigger    := pTriggerIn;

      -- Signal that data has been read on falling edge of data ready signal from
      -- Local Bus interface register bank
      if vLastDataReady = '1' and DataReady = '0' then
        DataRead <= '1';
      else
        DataRead <= '0';
      end if;

      vLastDataReady := DataReady;
      DataReady      <= vDataReady;
      vDataReady     := pDataReady;
    end if;
  end process ChangeClockDomain;


  GenEarlyDataReq: process(pnReset, pADC_2xClk)
    variable vEarlyData: std_logic_vector(2 downto 0);
    attribute register_balancing of vEarlyData: variable is "no";
    attribute register_duplication of vEarlyData: variable is "no";
  begin
    if pnReset = '0' then
      vEarlyData    := "000";
      EarlyDataReq  <= '0';

    elsif rising_edge(pADC_2xClk) then
      if vEarlyData(2 downto 1) = "01" and CtrlState = sFilling1 then
        EarlyDataReq <= '1';
      elsif BScanActive = '0' then
        EarlyDataReq <= '0';
      end if;

      -- Clock pEarlyDataReq through 3 d-types to remove metastability
      -- and store last value for edge detection
      vEarlyData := vEarlyData(1 downto 0) & pEarlyDataReq;
    end if;
  end process GenEarlyDataReq;


  CountAScans: process(pnReset, pADC_2xClk)
    variable vAScanDone: std_ulogic;
  begin
    if pnReset = '0' then
      AScanCount  <= 0;
--      ACountReset <= '1';
--      ACountInc   <= '0';
      pAScanNum <= "0000000";

      BCountReset <= '1';
      BCountInc   <= '0';
--      BScanCount  <= 0;
      AScanActive <= '0';
      BScanActive <= '0';
      BankDone    <= '0';
      pTriggerOut <= '0';
      FirstBScan  <= '1';
      pBScansAvailable <= "000000000000000";

    elsif rising_edge(pADC_2xClk) then
      BankDone <= '0';
--      ACountReset <= '0';
--      ACountInc   <= '0';
      BCountReset <= '0';
      BCountInc   <= '0';

      if TriggerEdge = '1' and pSampleEn = '1' then
        if BScanActive = '0' then
--          ACountReset <= '1';
          AScanCount  <= 0;

          if FirstBScan = '1' then
            -- First BScan of SRAM bank
            BCountReset <= '1';
--            BScanCount  <= 0;
          else
            BCountInc <= '1';
--            BScanCount  <= (BScanCount + 1) mod (2 ** 15);
          end if;
        else
--          ACountInc <= '1';
            AScanCount <= (AScanCount + 1) mod 128;
        end if;

        if pSampleEn = '1' then
          AScanActive <= '1';
          BScanActive <= '1';
        end if;
      end if;

      if pCaptureDone = '1' then
        AScanActive <= '0';

        -- Ensure resource sharing of 'equals' comparitor
        if AScanCount = conv_integer(unsigned(pAScansPerBScan)) then
          vAScanDone := '1';
        else
          vAScanDone := '0';
        end if;

        if vAScanDone = '1' then
          BScanActive <= '0';
        end if;

        if vAScanDone = '1' then
          pAScanNum <= "0000000";
        else
          pAScanNum <= std_logic_vector(conv_unsigned(AScanCount + 1, 7));
        end if;

        if (BScanCount = conv_integer(unsigned(pNumOfBScans)) and vAScanDone = '1') or
           (EarlyDataReq = '1' and vAScanDone = '1') then
          -- Required no of B-Scans has completed
          -- or Early Data has been requested and a B-Scan has completed
          BankDone <= '1';
        else
          BankDone <= '0';
        end if;

      elsif BScanActive = '0' and EarlyDataReq = '1' and TriggerEdge = '0' then
        -- No B-Scan is active and early data has been requested
        BankDone   <= '1';
      end if;

      -- Check for triggers when an A-Scan is already happening
      if AScanActive = '1' and AllTrigEdge = '1' then
        EarlyTrigError <= '1';
      else
        EarlyTrigError <= '0';
      end if;

      -- Generate the trigger out
      if pSampleEn = '1' then
        if TriggerEdge = '1' and  AScanActive = '0' then
          pTriggerOut <= '1';
        else
          pTriggerOut <= '0';
        end if;
      end if;

      if TriggerEdge = '1' and pSampleEn = '1' then
        FirstBScan <= '0';
      elsif BankDone = '1' then
        FirstBScan <= '1';
      end if;

      if BankDone = '1' then
        pBScansAvailable <= std_logic_vector(conv_unsigned(BScanCount, 15));
      end if;

    end if;
  end process CountAScans;

  pAllTriggersOut <= AllTrigEdge;
  pAScanActive    <= AScanActive;
  pBScanActive    <= BScanActive;

  -- Extend  signals so they are guarenteed to be seen in the 66MHz clock domain
  GenpSetErrors: process(pnReset, pADC_2xClk)
    variable vSetTrigDelayCount: integer range 0 to 3;
    variable vSetEarlyTrigCount: integer range 0 to 3;
    variable vSetDataFullCount:  integer range 0 to 3;
    variable vSetDataRdyCount:   integer range 0 to 3;
  begin
    if pnReset = '0' then
      pSetEarlyTrigError <= '0';
      vSetEarlyTrigCount := 0;

      pSetDataFullError <= '0';
      vSetDataFullCount := 0;

      pSetDataReady    <= '0';
      vSetDataRdyCount := 0;

    elsif rising_edge(pADC_2xClk) then

      if vSetEarlyTrigCount /= 0 then
        -- Count to extend pSetEarlyTrigError strobe
        vSetEarlyTrigCount := (vSetEarlyTrigCount + 1) mod 4;
      else
        -- Cancel the strobe
        pSetEarlyTrigError  <= '0';

        if EarlyTrigError = '1' then
          -- A trigger (delayed) has arrived while the sampling operation is still active
          pSetEarlyTrigError <= '1';
          vSetEarlyTrigCount := 1;
        end if;
      end if;

      if vSetDataFullCount /= 0 then
        -- Count to extend pSetEarlyTrigError strobe
        vSetDataFullCount := (vSetDataFullCount + 1) mod 4;
      else
        -- Cancel the strobe
        pSetDataFullError  <= '0';

        if DataFullError = '1' then
          -- A trigger (delayed) has arrived while both banks of the sampling SRAM have data in them
          pSetDataFullError <= '1';
          vSetDataFullCount := 1;
        end if;
      end if;

      if vSetDataRdyCount /= 0 then
        -- Count to extend pSetDataReady strobe
        vSetDataRdyCount := (vSetDataRdyCount + 1) mod 4;
      else
        -- Cancel the strobe
        pSetDataReady <= '0';

        if SetDataReady = '1' then
          -- Start the pSetDataReady strobe
          pSetDataReady    <= '1';
          vSetDataRdyCount := 1;
        end if;
      end if;

    end if;
  end process GenpSetErrors;



  GenCtrlFSM: process(pnReset, pADC_2xClk)
    variable vTemp: std_logic_vector(1 downto 0);
  begin
    if pnReset = '0' then
      CtrlState      <= sEmpty;
      DataFullError  <= '0';
      SramBankSelect <= '0';
      nSramBankSelect <= '1';
      SetDataReady  <= '0';

    elsif rising_edge(pADC_2xClk) then
      DataFullError  <= '0';
      SetDataReady   <= '0';
      pSomeDataReady <= '1';

      case CtrlState is
        when sEmpty =>
          -- Both banks of SRAM are empty or have been downloaded
          pSomeDataReady <= '0';
          if TriggerEdge = '1' then
            -- First SRAM bank has begun filling
            CtrlState <= sFilling1;
          end if;

        when sFilling1 =>
          -- 1 Bank of SRAM is filling, one is empty
          if BankDone = '1' then
            -- First bank has completed filling
            CtrlState <= sFull1;
            SramBankSelect <= not SramBankSelect;  -- Flip SRAM banks
            nSramBankSelect <= not nSramBankSelect;  -- Flip SRAM banks
            SetDataReady   <= '1';  -- Signal there is data ready to the software
          end if;

        when sFull1 =>
          -- 1 Bank of SRAM is full, one is empty
          vTemp := DataRead & TriggerEdge;
          case vTemp is
            when "11" =>
              -- Data in first bank has been read as the next B-Scan has started
              CtrlState <= sFilling1;
            when "10" =>
              -- Data in the first bank has been read
              CtrlState <= sEmpty;
            when "01" =>
              -- The next B-Scan has started
              CtrlState <= sFilling2;
            when others =>
              null;
          end case;

        when sFilling2 =>
          -- 1 Bank of SRAM is full, one is filling
          vTemp := DataRead & BankDone;
          case vTemp is
            when "11" =>
              -- Data in first bank has been read as the second bank has completed filling
              CtrlState <= sFull1;
              SramBankSelect <= not SramBankSelect;  -- Flip SRAM banks
              nSramBankSelect <= not nSramBankSelect;  -- Flip SRAM banks
              SetDataReady   <= '1';  -- Signal there is data ready to the software
            when "10" =>
              -- Data in the first bank has been read
              CtrlState <= sFilling1;
            when "01" =>
              -- The second bank has completed filling
              CtrlState <= sFull2;
            when others =>
              null;
          end case;

        when sFull2 =>
          -- Both banks of the SRAM are full
          if DataRead = '1' then
            -- Data in the first bank has been read
            CtrlState <= sFull1;
            SramBankSelect <= not SramBankSelect;  -- Flip SRAM banks
            nSramBankSelect <= not nSramBankSelect;  -- Flip SRAM banks
            SetDataReady   <= '1';  -- Signal there is data ready to the software
          end if;

          if AllTrigEdge = '1' then
            -- We have seen a new trigger edge while both banks are full
            -- flag up an error!!!
            DataFullError <= '1';
          end if;
      end case;

    end if;
  end process GenCtrlFSM;


  pSramBankSelect <= SramBankSelect;
  pnSramBankSelect <= nSramBankSelect;


  GenAddrResets: process(pnReset, pADC_2xClk)
  begin
    if pnReset = '0' then
      pResetAScanAddr <= '0';
      pResetCScanAddr <= '1';
      pResetElemNum   <= '0';
    elsif rising_edge(pADC_2xClk) then
      if BankDone = '1' then
        pResetCScanAddr <= '1';
      else
        pResetCScanAddr <= '0';
      end if;

      if pStorageMode = "01" then -- one AScan
        if pCaptureDone = '1' then
          pResetAScanAddr <= '1';
        else
          pResetAScanAddr <= '0';
        end if;
      elsif pStorageMode = "11" then -- one BScan
        if pCaptureDone = '1' and LastAScan = '1' then
          pResetAScanAddr <= '1';
        else
          pResetAScanAddr <= '0';
        end if;
      else
        if BankDone = '1' then
          pResetAScanAddr <= '1';
        else
          pResetAScanAddr <= '0';
        end if;
      end if;

      if pCaptureDone = '1' and LastAScan = '1' then
        pResetElemNum <= '1';
      else
        pResetElemNum <= '0';
      end if;

    end if;
  end process GenAddrResets;



end architecture Rtl;