--------------------------------------------------------------------------------
-- CERN BE-CO-HT
--------------------------------------------------------------------------------
--
-- unit name:   vtu_diag
--
-- description: Frequency meter and counter
--
--------------------------------------------------------------------------------
-- Copyright CERN 2012-2019
--------------------------------------------------------------------------------
-- Copyright and related rights are licensed under the Solderpad Hardware
-- License, Version 2.0 (the "License"); you may not use this file except
-- in compliance with the License. You may obtain a copy of the License at
-- http://solderpad.org/licenses/SHL-2.0.
-- Unless required by applicable law or agreed to in writing, software,
-- hardware and materials distributed under this License is distributed on an
-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
-- or implied. See the License for the specific language governing permissions
-- and limitations under the License.
--------------------------------------------------------------------------------

--  Principle of operation:
--
--  This block counts the number of pulses on CLK_IN_I during a period.
--  At the end of the period, the value is saved and the counter reset.
--  The saved value is available on FREQ_O, which is synchronized with
--  CLK_SYS_I if G_SYNC_OUT is True.
--  The width of the counter is defined by G_COUNTER_BITS.
--
--  - If g_WITH_INTERNAL_TIMEBASE is True:
--    The period is defined by an internal counter that generates a pulse
--    every G_CLK_SYS_FREQ CLK_SYS_I ticks.
--
--  - If g_WITH_INTERNAL_TIMEBASE is False:
--    The period is defined by PPS_P1_I

library ieee;

use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

library work;
use work.gencores_pkg.all;

entity vtu_diag is
  generic (
    --  CLK_I frequency
    g_CLK_FREQ : natural := 62_500_000);
  port (
    clk_i        : in  std_logic;
    rst_n_i      : in  std_logic;

    --  Signal to be measured.
    sig_i        : in  std_logic;
    dat_i         : in  std_logic_vector(7 downto 0);

    --  Must be set to enable the unit.
    enable_i     : in std_logic;

    --  Select the window to measure the frequency.
    --  A change to this value is taken into account at the end of the
    --  previous value.
    windows_i    : in std_logic_vector(2 downto 0);

    --  Last frequency measured.
    freq_o       : out std_logic_vector(31 downto 0);
    --  Counter incremented when FREQ_O is updated.
    generation_o : out std_logic_vector(15 downto 0);

    --  Count number of pulses.  Valid only if SIG_I pulses are longer than
    --  CLK_I period.
    counter_o    : out std_logic_vector(31 downto 0) );

  attribute keep_hierarchy : string;
  attribute keep_hierarchy of vtu_diag : entity is "yes";
  
end vtu_diag;

architecture arch of vtu_diag is

  signal gate_pulse : std_logic := '0';
  signal gate_pulse_synced, gate_pulse_synced_d : std_logic := '0';

  signal cntr_last : unsigned(31 downto 0) := (others => '0');
  signal cntr_gate : unsigned(31 downto 0) := (others => '0');
  signal cntr_meas : unsigned(31 downto 0) := (others => '0');

  --  Result of the freq measure.
  signal freq_reg : std_logic_vector(31 downto 0) := (others => '0');

  signal generation : unsigned (15 downto 0);

  signal enable_sig : std_logic;

  signal counter     : unsigned(31 downto 0);
  signal counter_resync : std_logic_vector(31 downto 0);
  signal counter_wr  : std_logic;

  signal freq_wr : std_logic;

  signal dat_d : std_logic_vector(8 downto 0);

  signal rst_sig_n : std_logic;
  
begin
  assert g_CLK_FREQ mod 2000 = 0
    report "inaccurate results due to clk frequency"
    severity failure;

  p_pulse_counter: process (sig_i)
  begin
    if rising_edge(sig_i) then
      dat_d <= dat_d(0) & dat_i;
    end if;
  end process;

  --  Mesure number of SIG_I pulses during the window.
  p_freq_counter : process (sig_i)
    variable cnt : natural range 0 to 4;
  begin
    if rising_edge(sig_i) then
      if enable_sig = '0' then
        gate_pulse_synced_d <= '0';
        counter <= (others => '0');
        freq_reg  <= (others => '0');
        cntr_meas <= (others => '0');
      else
        gate_pulse_synced_d <= gate_pulse_synced;

        --  Count the number of rising edges in dat_d.
        cnt := 0;
        for i in 7 downto 0 loop
          if dat_d(i + 1) = '0' and dat_d (i) = '1' then
            cnt := cnt + 1;
          end if;
        end loop;

        if gate_pulse_synced = '1' then
          --  End of the window:
          --  Save result and restart counter.
          freq_reg  <= std_logic_vector(cntr_meas);
          cntr_meas <= to_unsigned(cnt, 32);
        else
          cntr_meas <= cntr_meas + cnt;
        end if;

        counter <= counter + cnt;
      end if;
    end if;
  end process p_freq_counter;

  --  Generate the end-of-window pulse at the end of the measure window.
  p_gate_counter : process(clk_i)
  begin
    if rising_edge(clk_i) then
      if rst_n_i = '0' or enable_i = '0' then
        --  Once the reset and enable are released, a pulse start is generated.
        cntr_gate <= (others => '0');
        cntr_last <= (others => '0');
      elsif cntr_gate = cntr_last then
        --  End of the window, start a new one.
        cntr_gate  <= (others => '0');
        gate_pulse <= '1';
        case windows_i is
          when "000" =>
            --  5us
            cntr_last <= to_unsigned (g_CLK_FREQ / 200_000, 32) - 1;
          when "010" =>
            --  500us
            cntr_last <= to_unsigned (g_CLK_FREQ / 2_000, 32) - 1;
          when "100" =>
            --  50ms
            cntr_last <= to_unsigned (g_CLK_FREQ / 20, 32) - 1;
          when "101" =>
            --  5sec
            cntr_last <= to_unsigned (g_CLK_FREQ * 5, 32) - 1;
          when "110" =>
            --  20sec
            cntr_last <= to_unsigned (g_CLK_FREQ * 20, 32) - 1;
          when others =>
            null;
        end case;
      else
        cntr_gate  <= cntr_gate + 1;
        gate_pulse <= '0';
      end if;
    end if;
  end process;

  --  Synchronizer for the end-of-window pulse (from clk_i to sig_i).
  U_Sync_Gate : entity work.gc_pulse_synchronizer2
    port map (
      clk_in_i  => clk_i,
      rst_in_n_i => rst_n_i,
      clk_out_i => sig_i,
      rst_out_n_i => '1',
      d_ready_o => open,
      d_ack_p_o => open,
      d_p_i     => gate_pulse,
      q_p_o     => gate_pulse_synced);


  -- resync reset from clk to the sig domain
  inst_sig_rst_sync : entity work.gc_sync
    port map (
      clk_i     => sig_i,
      rst_n_a_i => '1',
      d_i       => rst_n_i,
      q_o       => rst_sig_n );
  
  inst_sig_sync : entity work.gc_sync
    port map (
      clk_i => sig_i,
      rst_n_a_i => rst_sig_n,
      d_i => enable_i,
      q_o => enable_sig);

  --  Transfer last frequency measure from sig_i to clk_i
  cmp_gc_sync_word_wr_freq : entity work.gc_sync_word_wr
    generic map (
      g_AUTO_WR => False,
      g_WIDTH   => 32)
    port map (
      clk_in_i    => sig_i,
      rst_in_n_i  => enable_sig,
      clk_out_i   => clk_i,
      rst_out_n_i => rst_n_i,
      wr_i        => gate_pulse_synced_d,
      data_i      => freq_reg,
      data_o      => freq_o,
      wr_o        => freq_wr);

  cmp_gc_sync_word_wr_counter : entity work.gc_sync_word_wr
    generic map (
      g_AUTO_WR => True,
      g_WIDTH   => 32)
    port map (
      clk_in_i    => sig_i,
      rst_in_n_i  => enable_sig,
      clk_out_i   => clk_i,
      rst_out_n_i => rst_n_i,
      data_i      => std_logic_vector(counter),
      data_o      => counter_resync,
      wr_o        => counter_wr);
  
  process (clk_i)
  begin
    if rising_edge (clk_i) then
      if rst_n_i = '0' then
        generation <= (others => '0');
        counter_o <= (others => '0');
      else
        if freq_wr = '1' then
          generation <= generation + 1;
        end if;
        if counter_wr = '1' then
          counter_o <= counter_resync;
        end if;
      end if;
    end if;
  end process;

  generation_o <= std_logic_vector (generation);
end arch;
