---------------------------------------------------------------------------
-- NDT Solutions Ltd. Proprietary Information
-- Company Confidential
-- Copyright 2004
--
-- Filename: EncoderMonitor.vhd
-- Language: VHDL '93
-- Project:  Digitiser Card Peripheral FPGA
--
-- Design Description:
-- Module to monitor the shaft encoders
---------------------------------------------------------------------------

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


entity EncoderMonitor is
  Port (-- Local bus signals
        pnReset          : in  std_ulogic;
        pLCLK            : in  std_ulogic;
        pEncoderReset    : in  std_logic_vector(5 downto 0);
        pEncoderSelect   : in  std_logic_vector(2 downto 0);
        pClicksPerBScan  : in  std_logic_vector(15 downto 0);
        pFractClicks     : in  std_logic_vector(7 downto 0);
        pBScanActive     : in  std_ulogic;
        pEncoder0In      : in  std_logic_vector(1 downto 0);
        pEncoder1In      : in  std_logic_vector(1 downto 0);
        pEncoder2In      : in  std_logic_vector(1 downto 0);
        pEncoder3In      : in  std_logic_vector(1 downto 0);
        pEncoder4In      : in  std_logic_vector(1 downto 0);
        pEncoder5In      : in  std_logic_vector(1 downto 0);
        pEncoder0Pos     : out std_logic_vector(31 downto 0);
        pEncoder1Pos     : out std_logic_vector(31 downto 0);
        pEncoder2Pos     : out std_logic_vector(31 downto 0);
        pEncoder3Pos     : out std_logic_vector(31 downto 0);
        pEncoder4Pos     : out std_logic_vector(31 downto 0);
        pEncoder5Pos     : out std_logic_vector(31 downto 0);
        pSerialOut       : out std_ulogic;
        pSerialOutEn     : out std_ulogic;
        pBScanTriggerOut : out std_ulogic;
        pBScanTriggerIn  : in  std_ulogic);
end entity EncoderMonitor;


architecture Rtl of EncoderMonitor is

  signal ClkEnCount:    integer range 0 to 15;
  signal ClkEn1:        std_ulogic;
  signal ClkEn2:        std_ulogic;

  type tEncInputs is array (0 to 5) of std_logic_vector(1 downto 0);
  signal EncInputs:  tEncInputs;
  signal LastInputs: tEncInputs;

  type tEncCounts is array (0 to 5) of std_logic_vector(31 downto 0);
  signal EncCounts: tEncCounts;

  signal EncInc:    std_logic_vector(5 downto 0);
  signal EncDec:    std_logic_vector(5 downto 0);

  signal SelEncInc:    std_ulogic;
  signal SelEncDec:    std_ulogic;
  signal SelEncReset:  std_ulogic;
  signal EncoderReset: std_logic_vector(5 downto 0);
  signal EncoderResetWait: std_ulogic;

  signal TrigCount: std_logic_vector(15 downto 0);
  signal IncHysterisis: std_ulogic;
  signal DecHysterisis: std_ulogic;
  signal FractTickCount: std_logic_vector(7 downto 0);

  signal SerialResults: std_logic_vector(191 downto 0);
  signal BeginShift:    std_ulogic;
  signal ShiftCount:    std_logic_vector(7 downto 0);
  signal ShiftResults:  std_ulogic;

  signal BScanActive:   std_ulogic;

begin


  -- Generate a low frequency clock enable
  GenClockEn: process(pnReset, pLCLK)
    variable vTemp: std_ulogic;
  begin
    if pnReset = '0' then
      ClkEnCount <= 0;
      ClkEn1     <= '0';
      vTemp      := '0';
      ClkEn2     <= '0';
    elsif rising_edge(pLCLK) then
      ClkEnCount <= (ClkEnCount + 1) mod 16;
      if ClkEnCount = 15 then
        ClkEn1 <= '1';
      else
        ClkEn1 <= '0';
      end if;

      ClkEn2 <= vTemp;
      vTemp  := ClkEn1;
    end if;
  end process GenClockEn;


  -- Clock pBScanActive into pLCLK domain
  GenBScanActive: process(pnReset, pLCLK)
    variable vBScanActive: std_ulogic;
  begin
    if pnReset = '0' then
      vBScanActive := '0';
      BScanActive  <= '0';
    elsif rising_edge(pLCLK) then
      BScanActive  <= vBScanActive;
      vBScanActive := pBScanActive;
    end if;
  end process GenBScanActive;


  EncInputs <= (pEncoder0In, pEncoder1In, pEncoder2In,
                pEncoder3In, pEncoder4In, pEncoder5In);


  -- Monitor encoders and check for changes
  CheckChanges: process(pnReset, pLCLK)
    variable vTemp: std_logic_vector(3 downto 0);
  begin
    if pnReset = '0' then

      for Count in 0 to 5 loop
        LastInputs(Count) <= "00";
        EncInc(Count)     <= '0';
        EncDec(Count)     <= '0';
      end loop;
    elsif rising_edge(pLCLK) then
      if ClkEn1 = '1' then
        for Count in 0 to 5 loop
          vTemp := EncInputs(Count) & LastInputs(Count);

          EncInc(Count) <= '0';
          EncDec(Count) <= '0';

          -- Decide whether the encoder count needs incrementing or
          -- decrementing depending on the last and current values
          case vTemp is
            when "0001" => EncInc(Count) <= '1';
            when "0010" => EncDec(Count) <= '1';
            when "0100" => EncDec(Count) <= '1';
            when "0111" => EncInc(Count) <= '1';
            when "1011" => EncDec(Count) <= '1';
            when "1000" => EncInc(Count) <= '1';
            when "1110" => EncInc(Count) <= '1';
            when "1101" => EncDec(Count) <= '1';
            when others => null;
          end case;

          LastInputs(Count) <= EncInputs(Count);
        end loop;
      end if;
    end if;
  end process CheckChanges;


  -- Generate Encoder position counts
  GenPosCounts: process(pnReset, pLCLK)
  begin
    if pnReset = '0' then

      for Count in 0 to 5 loop
        EncoderReset(Count) <= '0';
        EncCounts(Count)    <= X"00000000";
      end loop;
      EncoderResetWait <= '0';

    elsif rising_edge(pLCLK) then

      for Count in 0 to 5 loop
        -- Latch pEncoderReset until ClkEn2 has seen it
        if pEncoderReset(Count) = '1' then
          EncoderReset(Count) <= '1';
          EncoderResetWait <= '1';
        elsif ClkEn1 = '1' then
          EncoderResetWait <= '0';
        elsif ClkEn2 = '1' and EncoderResetWait = '0' then
          EncoderReset(Count) <= '0';
        end if;

        if ClkEn2 = '1' then
          if EncoderReset(Count) = '1' then
            EncCounts(Count)    <= X"00000000";

          elsif EncInc(Count) = '1' then
            if EncCounts(Count) /= X"80000000" then
              --  Increment count
              EncCounts(Count) <= (signed(EncCounts(Count)) + 1) and X"FFFFFFFF";
            end if;

          elsif EncDec(Count) = '1' then
            if EncCounts(Count) /= X"80000000" then
              -- Decrement count
              EncCounts(Count) <= (signed(EncCounts(Count)) - 1) and X"FFFFFFFF";
            end if;
          end if;
        end if;

      end loop;
    end if;
  end process GenPosCounts;


  -- Connect counts to outputs
  pEncoder0Pos <= EncCounts(0);
  pEncoder1Pos <= EncCounts(1);
  pEncoder2Pos <= EncCounts(2);
  pEncoder3Pos <= EncCounts(3);
  pEncoder4Pos <= EncCounts(4);
  pEncoder5Pos <= EncCounts(5);


  process(pnReset, pLCLK)
  begin
    if pnReset = '0' then
      SelEncInc   <= '0';
      SelEncDec   <= '0';
      SelEncReset <= '0';
    elsif rising_edge(pLCLK) then
      if ClkEn2 = '1' then
        -- Cancel SelEncReset
        SelEncReset <= '0';
      end if;

      case pEncoderSelect is
        when "000"|"001" | "010" | "011" | "100" | "101" =>
          SelEncInc   <= EncInc(conv_integer(unsigned(pEncoderSelect)));
          SelEncDec   <= EncDec(conv_integer(unsigned(pEncoderSelect)));
          if pEncoderReset(conv_integer(unsigned(pEncoderSelect))) = '1' then
            SelEncReset <= '1';
          end if;
        when others =>
          SelEncInc   <= '0';
          SelEncDec   <= '0';
      end case;
    end if;
  end process;


  GenBScanTrig: process(pnReset, pLCLK)
    variable vFractTickCount: std_logic_vector(8 downto 0);
    variable vClicksPerBScan: std_logic_vector(15 downto 0);
  begin
    if pnReset = '0' then
      TrigCount      <= X"0000";
      IncHysterisis  <= '0';
      DecHysterisis  <= '1';
      FractTickCount <= x"00";
      pBScanTriggerOut  <= '0';
      for Count in 0 to 5 loop
        SerialResults((32*Count + 31) downto (32*Count)) <= X"00000000";
      end loop;
      BeginShift <= '0';

    elsif rising_edge(pLCLK) then

      if ClkEn2 = '1' then
        pBScanTriggerOut  <= '0';

        if SelEncReset = '1' then
          TrigCount <= X"0000";
          IncHysterisis  <= '0';
          DecHysterisis  <= '1';
          FractTickCount <= x"00";

        elsif SelEncInc = '1' then
          vFractTickCount := unsigned('0' & FractTickCount) + unsigned(pFractClicks);
          if vFractTickCount(8) = '1' then
            vClicksPerBScan := unsigned(pClicksPerBScan) + 1;
          else
            vClicksPerBScan := pClicksPerBScan;
          end if;

          if TrigCount = vClicksPerBScan then
            TrigCount      <= X"0000";
            FractTickCount <= vFractTickCount(7 downto 0);
            IncHysterisis  <= '0';
            DecHysterisis  <= '1';
            if IncHysterisis = '0' and BScanActive = '0' then
              pBScanTriggerOut <= '1';
            end if;
          else
            TrigCount <= (unsigned(TrigCount) + 1);
            IncHysterisis <= '0';
            DecHysterisis <= '0';
          end if;

        elsif SelEncDec = '1' then
          vFractTickCount := unsigned('0' & FractTickCount) - unsigned(pFractClicks);
          if vFractTickCount(8) = '1' then
            vClicksPerBScan := unsigned(pClicksPerBScan) + 1;
          else
            vClicksPerBScan := pClicksPerBScan;
          end if;

          if TrigCount = X"0000" then
            TrigCount      <= vClicksPerBScan;
            FractTickCount <= vFractTickCount(7 downto 0);
            IncHysterisis  <= '1';
            DecHysterisis  <= '0';
            if DecHysterisis = '0' and BScanActive = '0' then
              pBScanTriggerOut <= '1';
            end if;

          else
            TrigCount <= (unsigned(TrigCount) - 1);
            IncHysterisis <= '0';
            DecHysterisis <= '0';
          end if;
        end if;
      end if;

      if pBScanTriggerIn = '1' then
        -- Latch all encoder values to give to DSP FPGA
        for Count in 0 to 5 loop
          SerialResults((32*Count + 31) downto (32*Count)) <= EncCounts(Count);
        end loop;
        BeginShift <= '1';
      elsif ShiftResults = '1' then
        -- Shift the results out
        SerialResults <= SerialResults(190 downto 0) & '0';
      end if;
    end if;
  end process GenBScanTrig;

  GenShiftCount: process(pnReset, pLCLK)
  begin
    if pnReset = '0' then
      ShiftCount   <= X"00";
      ShiftResults <= '0';
    elsif rising_edge(pLCLK) then
      if BeginShift = '1' then
        ShiftCount   <= X"BF";
        ShiftResults <= '1';
      else
        if ShiftCount /= X"00" then
          ShiftCount <= unsigned(ShiftCount) - 1;
        else
          ShiftResults <= '0';
        end if;
      end if;
    end if;
  end process GenShiftCount;

  GenSerialDataOut: process(pnReset, pLCLK)
  begin
    if pnReset = '0' then
      pSerialOut   <= '0';
      pSerialOutEn <= '0';
    elsif rising_edge(pLCLK) then
      pSerialOut   <= SerialResults(191);
      pSerialOutEn <= ShiftResults;
    end if;
  end process GenSerialDataOut;

end Rtl;
