---------------------------------------------------------------------------
-- NDT Solutions Ltd. Proprietary Information
-- Company Confidential
-- Copyright 2008
--
-- Filename: EncoderCounter.vhd
-- Language: VHDL '93
-- Project:  NDTS2100 DSP FPGA
--
-- Design Description:
-- Encoder Counters, pulse counters
---------------------------------------------------------------------------

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


entity EncoderCounter is
  port (-- Local bus
        nReset          : in  std_ulogic;
        LClk            : in  std_ulogic;
        -- Register interface signals
        EncoderReset    : in  std_logic_vector(8 downto 0);
        EncoderSelect   : in  std_logic_vector(3 downto 0);
        ClicksPerSeq    : in  std_logic_vector(23 downto 0);
        FractClicks     : in  std_logic_vector(15 downto 0);
        EncoderCount0   : out std_logic_vector(31 downto 0);
        EncoderCount1   : out std_logic_vector(31 downto 0);
        EncoderCount2   : out std_logic_vector(31 downto 0);
        EncoderCount3   : out std_logic_vector(31 downto 0);
        DualEncoderCount: out std_logic_vector(31 downto 0);
        PulseCount0     : out std_logic_vector(31 downto 0);
        PulseCount1     : out std_logic_vector(31 downto 0);
        PulseCount2     : out std_logic_vector(31 downto 0);
        PulseCount3     : out std_logic_vector(31 downto 0);
        -- Encoder/Pulse inputs
        EncoderPhA      : in  std_logic_vector(3 downto 0);
        EncoderPhB      : in  std_logic_vector(3 downto 0);
        PulseIn         : in  std_logic_vector(3 downto 0);
        -- BScan control
        SeqActiveIn     : in  std_ulogic;
        SeqTriggerOut   : out std_ulogic;
        SeqTriggerIn    : in  std_ulogic);
end entity EncoderCounter;


architecture Rtl of EncoderCounter is

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

  signal LastPhA:     std_logic_vector(3 downto 0);
  signal LastPhB:     std_logic_vector(3 downto 0);
  type tSyncPulseInputs is array (0 to 3) of std_logic_vector(1 downto 0);
  signal SyncPulseIn: tSyncPulseInputs;

  type tEncCounts is array (0 to 8) of std_logic_vector(31 downto 0);
  signal EncCount:    tEncCounts;

  signal CountInc:    std_logic_vector(8 downto 0);
  signal CountDec:    std_logic_vector(8 downto 0);

  signal DualInit:    std_ulogic;
  signal DualSelect:  std_ulogic;
  signal LastInc:     std_logic_vector(1 downto 0);
  signal LastDec:     std_logic_vector(1 downto 0);

  signal SelInc:          std_ulogic;
  signal SelDec:          std_ulogic;
  signal SelReset:        std_ulogic;
  signal EncoderResetDel: std_logic_vector(8 downto 0);

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

  signal SeqActive0:  std_ulogic;
  signal SeqActive:   std_ulogic;

begin

  -- Generate a low frequency clock enable
  GenClockEn: process(nReset, LClk)
  begin
    if nReset = '0' then
      ClkEnCount <= 0;
      ClkEn1     <= '0';
      ClkEn2     <= '0';
      ClkEn3     <= '0';
    elsif LClk'event and LClk = '1' then
      ClkEnCount <= (ClkEnCount + 1) mod 16;
      if ClkEnCount = 15 then
        ClkEn1 <= '1';
      else
        ClkEn1 <= '0';
      end if;

      ClkEn3 <= ClkEn2;
      ClkEn2 <= ClkEn1;
    end if;
  end process GenClockEn;


  -- Monitor encoders and check for changes
  EncMon: process(nReset, LClk)
    variable vTemp: std_logic_vector(3 downto 0);
  begin
    if nReset = '0' then
      for Count in 0 to 3 loop
        LastPhA(Count) <= '0';
        LastPhB(Count) <= '0';
        CountInc(Count)   <= '0';
        CountDec(Count)   <= '0';
      end loop;
    elsif LClk'event and LClk = '1' then
      if ClkEn1 = '1' then
        for Count in 0 to 3 loop
          vTemp := EncoderPhA(Count) & EncoderPhB(Count) & LastPhA(Count) & LastPhB(Count);

          CountInc(Count) <= '0';
          CountDec(Count) <= '0';

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

          LastPhA(Count) <= EncoderPhA(Count);
          LastPhB(Count) <= EncoderPhB(Count);
        end loop;
      end if;
    end if;
  end process EncMon;

  -- Dual encoder monitor
  GenDual: process(nReset, LClk)
    variable vDualInc: std_ulogic;
    variable vDualDec: std_ulogic;
  begin
    if nReset = '0' then
      DualInit <= '1';
      DualSelect <= '0';
      LastInc <= "00";
      LastDec <= "00";
    elsif LClk'event and LClk = '1' then
      if ClkEn2 = '1' then
        vDualInc := '0';
        vDualDec := '0';
        -- save last Inc/Dec
        if CountInc(0) = '1' or CountInc(1) = '1' then
          LastInc <= CountInc(1 downto 0);
          LastDec <= "00";
        elsif CountDec(0) = '1' or CountDec(1) = '1' then
          LastDec <= CountDec(1 downto 0);
          LastInc <= "00";
        end if;

        if SelReset = '1' then
          DualInit <= '1';
        elsif DualInit = '1' then
          -- select initial priority on which encoder moves first
          if CountInc(0) = '1' or CountDec(0) = '1' then
            DualSelect <= '0';
            DualInit <= '0';
            vDualInc := CountInc(0);
            vDualDec := CountDec(0);
          elsif CountInc(1) = '1' or CountDec(1) = '1' then
            DualSelect <= '1';
            DualInit <= '0';
            vDualInc := CountInc(1);
            vDualDec := CountDec(1);
          end if;
        else
          if DualSelect = '0' then
            if CountInc(0) = '1' then
              vDualInc := CountInc(0);
            elsif CountDec(0) = '1' then
              vDualDec := CountDec(0);
            elsif CountInc(1) = '1' then
              if LastInc(0) /= '1' then
                vDualInc := CountInc(1);
                DualSelect <= '1';
              end if;
            elsif CountDec(1) = '1' then
              if LastDec(0) /= '1' then
                vDualDec := CountDec(1);
                DualSelect <= '1';
              end if;
            end if;
          else
            if CountInc(1) = '1' then
              vDualInc := CountInc(1);
            elsif CountDec(1) = '1' then
              vDualDec := CountDec(1);
            elsif CountInc(0) = '1' then
              if LastInc(1) /= '1' then
                vDualInc := CountInc(0);
                DualSelect <= '0';
              end if;
            elsif CountDec(0) = '1' then
              if LastDec(1) /= '1' then
                vDualDec := CountDec(0);
                DualSelect <= '0';
              end if;
            end if;
          end if;
        end if;
      end if;

      CountInc(4) <= vDualInc;
      CountDec(4) <= vDualDec;
    end if;
  end process GenDual;

  -- Monitor Pulse inputs
  PulseMon: process(nReset, LClk)
  begin
    if nReset = '0' then
      for Count in 0 to 3 loop
        SyncPulseIn(Count) <= "00";
        CountInc(5+Count)  <= '0';
        CountDec(5+Count)  <= '0';
      end loop;
    elsif LClk'event and LClk = '1' then
      if ClkEn1 = '1' then
        for Count in 0 to 3 loop

          if SyncPulseIn(Count) = "01"  then
            CountInc(5+Count) <= '1';
          else
            CountInc(5+Count) <= '0';
          end if;

          CountDec(5+Count) <= '0';

          SyncPulseIn(Count) <= SyncPulseIn(Count)(0) & PulseIn(Count);
        end loop;
      end if;
    end if;
  end process PulseMon;


  -- Position counts
  PosCounts: process(nReset, LClk)
  begin
    if nReset = '0' then

      for Count in 0 to 8 loop
        EncoderResetDel(Count) <= '0';
        EncCount(Count)        <= X"00000000";
      end loop;

    elsif LClk'event and LClk = '1' then

      for Count in 0 to 8 loop
        -- Latch pEncoderReset until ClkEn3 has seen it
        if EncoderReset(Count) = '1' then
          EncoderResetDel(Count) <= '1';
        elsif ClkEn3 = '1' then
          EncoderResetDel(Count) <= '0';
        end if;

        if ClkEn3 = '1' then
          if EncoderResetDel(Count) = '1' then
            EncCount(Count) <= X"00000000";

          elsif CountInc(Count) = '1' then
            if EncCount(Count) /= X"80000000" then
              --  Increment count
              EncCount(Count) <= signed(EncCount(Count)) + 1;
            end if;

          elsif CountDec(Count) = '1' then
            if EncCount(Count) /= X"80000000" then
              -- Decrement count
              EncCount(Count) <= signed(EncCount(Count)) - 1;
            end if;
          end if;
        end if;

      end loop;
    end if;
  end process PosCounts;

  -- Connect counts to outputs
  EncoderCount0    <= EncCount(0);
  EncoderCount1    <= EncCount(1);
  EncoderCount2    <= EncCount(2);
  EncoderCount3    <= EncCount(3);
  DualEncoderCount <= EncCount(4);
  PulseCount0      <= EncCount(5);
  PulseCount1      <= EncCount(6);
  PulseCount2      <= EncCount(7);
  PulseCount3      <= EncCount(8);


  GenSel: process(nReset, LClk)
    variable vEncoderSelect: integer range 0 to 15;
  begin
    if nReset = '0' then
      SelInc   <= '0';
      SelDec   <= '0';
      SelReset <= '0';
    elsif LClk'event and LClk = '1' then
      if ClkEn3 = '1' then
        -- Cancel SelReset
        SelReset <= '0';
      end if;

      vEncoderSelect := conv_integer(unsigned(EncoderSelect));

      if vEncoderSelect < 9 then
          SelInc   <= CountInc(vEncoderSelect);
          SelDec   <= CountDec(vEncoderSelect);

          if EncoderReset(vEncoderSelect) = '1' then
            SelReset <= '1';
          end if;
      else
          SelInc   <= '0';
          SelDec   <= '0';
      end if;
    end if;
  end process GenSel;


  -- Clock SeqActive into LCLK domain
  GenSeqActive: process(nReset, LClk)
  begin
    if nReset = '0' then
      SeqActive0 <= '0';
      SeqActive  <= '0';
    elsif LClk'event and LClk = '1' then
      SeqActive0 <= SeqActiveIn;
      SeqActive  <= SeqActive0;
    end if;
  end process GenSeqActive;

  -- Generate Seq Trigger
  GenSeqTrig: process(nReset, LClk)
    variable vFractTickCount: std_logic_vector(16 downto 0);
    variable vClicksPerSeq: std_logic_vector(23 downto 0);
  begin
    if nReset = '0' then
      TrigCount      <= (others => '0');
      IncHysterisis  <= '0';
      DecHysterisis  <= '1';
      FractTickCount <= (others => '0');
      SeqTriggerOut <= '0';
    elsif LClk'event and LClk = '1' then

      if ClkEn3 = '1' then
        SeqTriggerOut  <= '0';

        if SelReset = '1' then
          TrigCount <= (others => '0');
          IncHysterisis  <= '0';
          DecHysterisis  <= '1';
          FractTickCount <= (others => '0');
        else
          if SelInc = '1' then
            vFractTickCount := unsigned('0' & FractTickCount) + unsigned(FractClicks);
            if vFractTickCount(16) = '1' then
              vClicksPerSeq := unsigned(ClicksPerSeq) + 1;
            else
              vClicksPerSeq := ClicksPerSeq;
            end if;
  
            if TrigCount = vClicksPerSeq then
              TrigCount      <= (others => '0');
              FractTickCount <= vFractTickCount(15 downto 0);
              IncHysterisis  <= '0';
              DecHysterisis  <= '1';
              if IncHysterisis = '0' and SeqActive = '0' then
                SeqTriggerOut <= '1';
              end if;
            else
              TrigCount <= unsigned(TrigCount) + 1;
              IncHysterisis <= '0';
              DecHysterisis <= '0';
            end if;

          elsif SelDec = '1' then
            vFractTickCount := unsigned('0' & FractTickCount) - unsigned(FractClicks);
            if vFractTickCount(16) = '1' then
              vClicksPerSeq := unsigned(ClicksPerSeq) + 1;
            else
              vClicksPerSeq := ClicksPerSeq;
            end if;
  
            if unsigned(TrigCount) = 0 then
              TrigCount      <= vClicksPerSeq;
              FractTickCount <= vFractTickCount(15 downto 0);
              IncHysterisis  <= '1';
              DecHysterisis  <= '0';
              if DecHysterisis = '0' and SeqActive = '0' then
                SeqTriggerOut <= '1';
              end if;

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

        end if;
      end if;

    end if;
  end process GenSeqTrig;

end Rtl;
