--==============================================================================
-- CERN (BE-CO-HT)
-- Pulse generator with trigger
--==============================================================================
--
-- author: Theodor Stana (t.stana@cern.ch)
--
-- date of creation: 2013-03-01
--
-- version: 1.0
--
-- description:
--    This module generates a constant-width pulse. The width is set using the
--    g_pwidth generic, given in number of clk_i cycles. With a clk_i period of
--    50 ns, the output pulse width is by default 50*24=1.2 us.
--
--    The module is designed to work with an external glitch filter. Enabling
--    this glitch filter will result in jitter on the leading edge of the
--    output pulse signal. This jitter can be avoided by bypassing the glitch
--    filter; this is done via the gf_en_n_i input.
--
--    Regardless of whether the glitch filter is enabled, the input trigger signal
--    is extended or cut to g_pwidth, if it is shorter or respectively longer than
--    g_pwidth. At the end of the pulse, a rejection phase is implemented in order
--    to avoid too many pulses arriving on the input. This is to safeguard the
--    blocking output stage of the CONV-TTL-BLO boards. The isolation phase limits
--    the input pulse to 1/500 duty cycle.
--
-- dependencies:
--    none
--
--==============================================================================
-- GNU LESSER GENERAL PUBLIC LICENSE
--==============================================================================
-- This source file is free software; you can redistribute it and/or modify it
-- under the terms of the GNU Lesser General Public License as published by the
-- Free Software Foundation; either version 2.1 of the License, or (at your
-- option) any later version. This source is distributed in the hope that it
-- will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
-- of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-- See the GNU Lesser General Public License for more details. You should have
-- received a copy of the GNU Lesser General Public License along with this
-- source; if not, download it from http://www.gnu.org/licenses/lgpl-2.1.html
--==============================================================================
-- last changes:
--    01-03-2013   Theodor Stana     File created.
--    02-08-2013   Theodor Stana     Implemented rejection phase.
--    17-02-2014   Theodor Stana     Moved the glitch filter to outside the
--                                   module.
--    04-03-2014   Theodor Stana     Added first pulse inhibit on glitch-filtered
--                                   side.
--==============================================================================
-- TODO: -
--==============================================================================

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

entity ctblo_pulse_gen is
  generic
  (
    -- Pulse width, in number of clk_i cycles
    -- Default pulse width (20 MHz clock): 1.2 us
    -- Minimum allowable pulse width (20 MHz clock): 1 us
    -- Maximum allowable pulse width (20 MHz clock): 2 us
    g_pwidth         : natural range 20 to 40 := 24;

    -- Duty cycle divider: D = 1/g_duty_cycle_div
    g_duty_cycle_div : natural := 5
  );
  port
  (
    -- Clock and active-low reset inputs
    clk_i         : in  std_logic;
    rst_n_i       : in  std_logic;

    -- Glitch filter enable input
    -- '1' - Glitch filter disabled (glitch-sensitive, no output jitter)
    -- '0' - Glitch filter enabled (glitch-insensitive, with output jitter)
    gf_en_n_i     : in  std_logic;

    -- Enable input, pulse generation is enabled when '1'
    en_i          : in  std_logic;

    -- Trigger input, has to be '1' to assure pulse output with delay no greater
    -- than internal gate delays.
    trig_a_i      : in  std_logic;

    -- Pulse error output, pulses high for one clock cycle when a pulse arrives
    -- within a pulse period
    pulse_err_p_o : out std_logic;

    -- Pulse output, active-high
    -- latency:
    --    glitch filter disabled: none
    --    glitch filter enabled: glitch filter length + 5 clk_i cycles
    pulse_o       : out std_logic
  );
end entity ctblo_pulse_gen;


architecture behav of ctblo_pulse_gen is

  --============================================================================
  -- Type declarations
  --============================================================================
  type t_state is (
    IDLE,       -- idle state, wait for pulse
    GEN_GF_OFF, -- pulse generation, glitch filter off
    REJ_GF_OFF, -- pulse rejection, glitch filter off
    GEN_GF_ON,  -- pulse generation, glitch filter on
    REJ_GF_ON   -- pulse rejection, glitch filter on
  );

  --============================================================================
  -- Constant declarations
  --============================================================================
  -- Max value of pulse counter for pulse width and pulse rejection width.
  -- glitch filter OFF:
  --   generate:
  --     * g_pwidth-1: counter starts from 0
  --     * g_pwidth-4: three-cycle delay through synchronizer
  --     * g_pwidth-5: reset signal applied in REJ_GF_OFF state
  --   reject:
  --     * g_duty_cycle_div*g_pwidth: D duty cycle
  --     * g_duty_cycle_div*g_pwidth-5: 5-cycle delay added from the generate phase
  -- glitch filter ON:
  --   generate:
  --     * g_pwidth-1: counter starts from 0
  --   reject:
  --     * g_duty_cycle_div*g_pwidth: D duty cycle
  --     * g_duty_cycle_div*g_pwidth-2: need one cycle less to allow for true 1/D
  --     duty cycle,
  --     since the FSM needs to go through IDLE to accept a pulse
  constant c_max_gen_gf_off : natural := g_pwidth-5;
  constant c_max_rej_gf_off : natural := g_duty_cycle_div*g_pwidth - 5;
  constant c_max_gen_gf_on  : natural := g_pwidth-1;
  constant c_max_rej_gf_on  : natural := g_duty_cycle_div*g_pwidth - 2;

  --============================================================================
  -- Function and procedure declarations
  --============================================================================
  function f_log2_size (A : natural) return natural is
  begin
    for I in 1 to 64 loop               -- Works for up to 64 bits
      if (2**I >= A) then
        return(I);
      end if;
    end loop;
    return(63);
  end function f_log2_size;

  --============================================================================
  -- Signal declarations
  --============================================================================
  -- Trigger signals
  signal pulse_gf_off_d0       : std_logic;
  signal pulse_gf_off_d1       : std_logic;
  signal pulse_gf_off_d2       : std_logic;

  signal trig_gf_on            : std_logic;
  signal trig_gf_on_d0         : std_logic;
  signal trig_gf_on_r_edge_p   : std_logic;

  -- Pulse output signals
  signal pulse_gf_on           : std_logic;
  signal pulse_gf_off          : std_logic;
  signal pulse_gf_off_rst      : std_logic;
  signal pulse_gf_off_r_edge_p : std_logic;

  -- Inhibit first pulse
  signal inh_fp_gf_on          : std_logic;
  signal inh_fp_gf_on_d0       : std_logic;

  -- Pulse length counter
  signal pulse_cnt             : unsigned(f_log2_size(g_duty_cycle_div*g_pwidth)-1 downto 0);

  -- FSM signal
  signal state                 : t_state;

--==============================================================================
--  architecture begin
--==============================================================================
begin

  --============================================================================
  -- Output logic
  --============================================================================
  pulse_o <= pulse_gf_off when (gf_en_n_i = '1') else
             pulse_gf_on;

  --============================================================================
  -- Pulse generation logic
  --============================================================================
  -- Generate the pulse on rising edge of trig_a_i
  p_pulse_gf_off: process(pulse_gf_off_rst, trig_a_i)
  begin
    if (pulse_gf_off_rst = '1') then
      pulse_gf_off <= '0';
    elsif rising_edge(trig_a_i) then
      if (en_i = '1') and (gf_en_n_i = '1') then
        pulse_gf_off <= '1';
      end if;
    end if;
  end process p_pulse_gf_off;

  -- and synchronize the trigger in clk_i domain
  p_sync_pulse_gf_off: process (clk_i) is
  begin
    if rising_edge(clk_i) then
      if (rst_n_i = '0') then
        pulse_gf_off_d0       <= '0';
        pulse_gf_off_d1       <= '0';
        pulse_gf_off_d2       <= '0';
        pulse_gf_off_r_edge_p <= '0';
      elsif (en_i = '1') and (gf_en_n_i = '1') then
        pulse_gf_off_d0       <= pulse_gf_off;
        pulse_gf_off_d1       <= pulse_gf_off_d0;
        pulse_gf_off_d2       <= pulse_gf_off_d1;
        pulse_gf_off_r_edge_p <= pulse_gf_off_d1 and (not pulse_gf_off_d2);
      end if;
    end if;
  end process p_sync_pulse_gf_off;

  -- Trigger signal with glitch filter ON is input signal
  trig_gf_on <= trig_a_i;

  -- Rising edge detector for the trigger signal when glitch filter is ON
  p_trig_gf_on : process (clk_i) is
  begin
    if rising_edge(clk_i) then
      if (rst_n_i = '0') then
        trig_gf_on_d0       <= '0';
        trig_gf_on_r_edge_p <= '0';
      else
        trig_gf_on_d0       <= trig_gf_on;
        trig_gf_on_r_edge_p <= trig_gf_on and (not trig_gf_on_d0);
      end if;
    end if;
  end process p_trig_gf_on;

  --============================================================================
  -- Pulse width adjustment logic
  --============================================================================
  -- Generate the FSM logic
  p_pulse_width: process(clk_i)
  begin
    if rising_edge(clk_i) then
      if (rst_n_i = '0') then
        state            <= IDLE;
        pulse_gf_off_rst <= '1';
        pulse_gf_on      <= '0';
        pulse_cnt        <= (others => '0');
        inh_fp_gf_on     <= '1';
        inh_fp_gf_on_d0  <= '1';
        pulse_err_p_o    <= '0';
      elsif (en_i = '1') then
        -- On the first two cycle after reset, the pulse channel needs to be
        -- inhibited when the converter board is in TTL-BAR repetition mode,
        -- since in this mode, an unconnected channel is HIGH for the first
        -- 100us until the "no signal detect" block triggers, and the HIGH level
        -- on the line will get interpreted by the trigger delay (due to its reset
        -- state) as a rising edge on the line, thus triggering a pulse.
        --
        -- This is only needed when the glitch filter is ON, since when it is OFF,
        -- as the unconnected channel is HIGH for the first 100us, no rising-edge
        -- on the channel exists and the p_pulse_gf_off process above is not
        -- triggered.
        inh_fp_gf_on_d0 <= inh_fp_gf_on;
        if inh_fp_gf_on = '1' then
          inh_fp_gf_on    <= '0';
        end if;

        -- State machine logic
        case state is
          ---------------------------------------------------------------------
          -- IDLE
          ---------------------------------------------------------------------
          -- Clear all values and go to pulse generation state when the
          -- appropriate input arrives
          ---------------------------------------------------------------------
          when IDLE =>
            pulse_cnt        <= (others => '0');
            pulse_gf_off_rst <= '0';
            pulse_err_p_o    <= '0';
            if (gf_en_n_i = '1') then
              if (pulse_gf_off_r_edge_p = '1') then
                state <= GEN_GF_OFF;
              end if;
            else
              if (trig_gf_on_r_edge_p = '1') and (inh_fp_gf_on_d0 = '0') then
                state <= GEN_GF_ON;
              end if;
            end if;

          ---------------------------------------------------------------------
          -- GEN_GF_OFF
          ---------------------------------------------------------------------
          -- Extend the generated pulse to the required pulse width.
          ---------------------------------------------------------------------
          when GEN_GF_OFF =>
          -- Pulse logic and state change
            pulse_cnt <= pulse_cnt + 1;
            if (pulse_cnt = c_max_gen_gf_off) then
              state <= REJ_GF_OFF;
            end if;

          -- Pulse error assignment
            pulse_err_p_o <= '0';
            if (trig_gf_on_r_edge_p = '1') then
              pulse_err_p_o <= '1';
            end if;

          ---------------------------------------------------------------------
          -- REJ_GF_OFF
          ---------------------------------------------------------------------
          -- Cut and reject input pulses, to safeguard the output transformers.
          ---------------------------------------------------------------------
          when REJ_GF_OFF =>
          -- Pulse logic and state change
            pulse_gf_off_rst <= '1';
            pulse_cnt        <= pulse_cnt + 1;
            if (pulse_cnt = c_max_rej_gf_off) then
              state <= IDLE;
            end if;

          -- Pulse error assignment
            pulse_err_p_o <= '0';
            if (trig_gf_on_r_edge_p = '1') then
              pulse_err_p_o <= '1';
            end if;

          ---------------------------------------------------------------------
          -- GEN_GF_ON
          ---------------------------------------------------------------------
          -- Start generating the output pulse with the required width.
          ---------------------------------------------------------------------
          when GEN_GF_ON =>
          -- Pulse logic and state change
            pulse_cnt   <= pulse_cnt + 1;
            pulse_gf_on <= '1';
            if (pulse_cnt = c_max_gen_gf_on) then
              state <= REJ_GF_ON;
            end if;

          -- Pulse error assignment
            pulse_err_p_o <= '0';
            if (trig_gf_on_r_edge_p = '1') then
              pulse_err_p_o <= '1';
            end if;

          ---------------------------------------------------------------------
          -- REJ_GF_ON
          ---------------------------------------------------------------------
          -- Stop generating the output pulse and reject incoming pulses.
          ---------------------------------------------------------------------
          when REJ_GF_ON =>
          -- Pulse logic and state change
            pulse_gf_on <= '0';
            pulse_cnt   <= pulse_cnt + 1;
            if (pulse_cnt = c_max_rej_gf_on) then
              state <= IDLE;
            end if;

          -- Pulse error assignment
            pulse_err_p_o <= '0';
            if (trig_gf_on_r_edge_p = '1') then
              pulse_err_p_o <= '1';
            end if;

          when others =>
            state <= IDLE;

        end case;
      end if;
    end if;
  end process p_pulse_width;

end architecture behav;
--==============================================================================
--  architecture end
--==============================================================================
