-------------------------------------------------------------------------------
-- Title      : ACAM TDX-GPX timestamp postprocessor
-- Project    : Fine Delay Core (FmcDelay1ns4cha)
-------------------------------------------------------------------------------
-- File       : fd_acam_timestamp_postprocessor.vhd
-- Author     : Tomasz Wlostowski
-- Company    : CERN
-- Created    : 2011-08-29
-- Last update: 2011-10-20
-- Platform   : FPGA-generic
-- Standard   : VHDL'93
-------------------------------------------------------------------------------
-- Description: Merges the coarse timestamp produced with the internal FPGA
-- counter with the fractional part obtained from the ACAM TDC, generating a final
-- UTC timestamp used for further processing. 
-------------------------------------------------------------------------------
--
-- Copyright (c) 2011 CERN / BE-CO-HT
-- 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
--
-------------------------------------------------------------------------------
-- Revisions  :
-- Date        Version  Author          Description
-- 2011-08-29  1.0      twlostow        Created
-------------------------------------------------------------------------------

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

use work.fd_wbgen2_pkg.all;

entity fd_acam_timestamp_postprocessor is
  generic(
    -- number of the bits in the fractional part
    g_frac_bits : integer := 12);
  port(
    clk_ref_i : in std_logic;
    rst_n_i   : in std_logic;

    ---------------------------------------------------------------------------
    -- Timestamp input, from the ACAM FS
    ---------------------------------------------------------------------------

    raw_valid_i : in std_logic;
    raw_utc_i   : in std_logic_vector(31 downto 0);

    -- "start number" (value of coarse counter, counting at every start pulse of the
    -- TDC, i.e. 125 MHz / 16 = 7.8215 MHz)
    raw_coarse_i : in std_logic_vector(23 downto 0);

    -- raw fractional timestamp generated by ACAM
    raw_frac_i : in std_logic_vector(22 downto 0);

    -- coarse offset (in 125 MHz clock cycles) from the last ACAM's start pulse to the
    -- input pulse (0..15)
    raw_start_offset_i : in std_logic_vector(3 downto 0);

    -- Offset between the actual timescale and the ACAM fixed start signal generated
    -- by the AD9516 PLL. Used to align the timestamps to the externally
    -- provided time base (e.g. by White Rabbit).
    acam_subcycle_offset_i : in std_logic_vector(4 downto 0);

    ---------------------------------------------------------------------------
    -- Post-processed timestamp. WARNING! DE-NORMALIZED!
    ---------------------------------------------------------------------------

    tag_valid_o  : out std_logic;
    tag_utc_o    : out std_logic_vector(31 downto 0);
    tag_coarse_o : out std_logic_vector(27 downto 0);
    tag_frac_o   : out std_logic_vector(g_frac_bits-1 downto 0);

    -- Wishbone regs
    regs_i : in t_fd_out_registers
    );

end fd_acam_timestamp_postprocessor;

architecture behavioral of fd_acam_timestamp_postprocessor is

  -- number of the fractional bits to ignore in the rescaled ACAM's fractional
  -- timestamp
  constant c_SCALER_SHIFT : integer := 12;

  signal pp_pipe : std_logic_vector(3 downto 0);

  signal post_tag_coarse         : unsigned(27 downto 0);
  signal post_tag_frac           : unsigned(g_frac_bits-1 downto 0);
  signal post_tag_utc            : unsigned(31 downto 0);
  signal post_frac_multiplied    : signed(c_SCALER_SHIFT + g_frac_bits + 8 downto 0);
  signal post_frac_multiplied_d0 : signed(c_SCALER_SHIFT + g_frac_bits + 8 downto 0);
  signal post_frac_start_adj     : signed(22 downto 0);

  signal adsfr_d0 : signed(17 downto 0);
  
begin  -- behavioral

  p_buffer_adsfr : process(clk_ref_i)
  begin
    if rising_edge(clk_ref_i) then
      if rst_n_i = '0' then
        adsfr_d0 <= (others => '0');
      else
        adsfr_d0 <= signed(regs_i.adsfr_o);
      end if;
    end if;
  end process;


  p_postprocess_tags : process(clk_ref_i)
  begin
    if rising_edge(clk_ref_i) then
      if rst_n_i = '0' then
        tag_valid_o  <= '0';
        tag_coarse_o <= (others => '0');
        tag_utc_o    <= (others => '0');
        tag_frac_o   <= (others => '0');
      else

-- pipeline stage 1:
-- - subtract the start offset from the fractional value got from the ACAM,

        pp_pipe(0) <= raw_valid_i;

        post_frac_start_adj         <= signed(raw_frac_i) - signed(regs_i.asor_offset_o);
        post_tag_coarse(3 downto 0) <= (others => '0');
        post_tag_utc                <= unsigned(raw_utc_i);

-- pipeline stage 2:
-- - check for the "wraparound" condition and adjust the coarse start counter.
-- Wraparound occurs when the ACAM's hasn't yet accounted for the next start pulse
-- (resulting with a value of the fractional timestamp close to the upper
-- bound), but the FPGA counter had already "noticed" the next start. This
-- happens because of different routing delays and jitter. 

        pp_pipe(1) <= pp_pipe(0);

        if (unsigned(raw_start_offset_i) <= unsigned(regs_i.atmcr_c_thr_o)) and (post_frac_start_adj > signed(regs_i.atmcr_f_thr_o)) then
          post_tag_coarse(post_tag_coarse'left downto 4) <= unsigned(raw_coarse_i) - 1;
        else
          post_tag_coarse(post_tag_coarse'left downto 4) <= unsigned(raw_coarse_i);
        end if;

-- pipeline stage 3:
-- rescale the fractional part to our internal time base 

        pp_pipe(2)           <= pp_pipe(1);
        --post_frac_multiplied <= resize(signed(post_frac_start_adj) * signed(regs_i.adsfr_o), post_frac_multiplied'length);
--        post_frac_multiplied_d0 <= post_frac_multiplied;

        post_frac_multiplied <= resize(signed(post_frac_start_adj) * adsfr_d0, post_frac_multiplied'length);

        
-- pipeline stage 4:
-- - split the rescaled fractional part into the (mod 4096) tag_frac_o and add
-- the rest to the coarse part, along with the start-to-timescale offset

        pp_pipe(3) <= pp_pipe(2);

        tag_utc_o <= std_logic_vector(post_tag_utc);
        tag_coarse_o <= std_logic_vector(
          signed(post_tag_coarse)       -- index of start pulse (mod 16 = 0)
          + signed(acam_subcycle_offset_i)  -- start-to-timescale offset
          + signed(post_frac_multiplied(post_frac_multiplied'left downto c_SCALER_SHIFT + g_frac_bits))); 
        -- extra coarse counts from ACAM's frac part after rescaling

        tag_frac_o <= std_logic_vector(post_frac_multiplied(c_SCALER_SHIFT + g_frac_bits-1 downto c_SCALER_SHIFT));

        tag_valid_o <= pp_pipe(3);

      end if;
    end if;
  end process;

end behavioral;
