--------------------------------------------------------------------------------
-- CERN BE-CO-HT
-- General Cores Library
-- https://www.ohwr.org/projects/general-cores
--------------------------------------------------------------------------------
--
-- unit name:   inferred_sync_fifo
--
-- description: Parametrizable synchronous FIFO (Generic version).
-- Single-clock FIFO.
-- - configurable data width and size
-- - configurable full/empty/almost full/almost empty/word count signals
--
--------------------------------------------------------------------------------
-- Copyright CERN 2011-2020
--------------------------------------------------------------------------------
-- 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.
--------------------------------------------------------------------------------

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

use work.genram_pkg.all;

entity inferred_sync_fifo is

  generic (
    g_data_width : natural;
    g_size       : natural;
    g_show_ahead : boolean := false;

    -- Previously, the full flag was asserted at g_size-1 when using g_show_ahead.
    -- The new implementation solves this. However, for backward compatibility,
    -- the default is to still use the previous behaviour. Set this to false to
    -- switch to the new one.
    g_show_ahead_legacy_mode : boolean := true;

    -- Read-side flag selection
    g_with_empty        : boolean := true;   -- with empty flag
    g_with_full         : boolean := true;   -- with full flag
    g_with_almost_empty : boolean := false;
    g_with_almost_full  : boolean := false;
    g_with_count        : boolean := false;  -- with words counter

    g_almost_empty_threshold : integer := 0;  -- threshold for almost empty flag
    g_almost_full_threshold  : integer := 0;  -- threshold for almost full flag

    g_register_flag_outputs : boolean := true;

    g_memory_implementation_hint : string := "auto"
    );

  port (
    rst_n_i : in std_logic := '1';

    clk_i : in std_logic;
    d_i   : in std_logic_vector(g_data_width-1 downto 0);
    we_i  : in std_logic;

    q_o  : out std_logic_vector(g_data_width-1 downto 0);
    rd_i : in  std_logic;

    empty_o        : out std_logic;
    full_o         : out std_logic;
    almost_empty_o : out std_logic;
    almost_full_o  : out std_logic;
    count_o        : out std_logic_vector(f_log2_size(g_size)-1 downto 0)
    );

end inferred_sync_fifo;

architecture syn of inferred_sync_fifo is

  constant c_pointer_width              : integer := f_log2_size(g_size);
  signal   rd_ptr, wr_ptr, rd_ptr_muxed : unsigned(c_pointer_width-1 downto 0);
  signal   usedw                        : unsigned(c_pointer_width downto 0);
  signal   full, empty                  : std_logic;

  signal   we_int, rd_int               : std_logic;
  signal   guard_bit                    : std_logic;

  signal q_comb : std_logic_vector(g_data_width-1 downto 0);

begin  -- syn

  legacy_mode_check: assert g_show_ahead = false or g_show_ahead_legacy_mode = false
    report "inferred_sync_fifo: show-ahead enabled for sync FIFO in " &
    "legacy mode. In this mode, the full flag is asserted at g_SIZE-1. if you want the " &
    "full flag to be asserted at g_SIZE, then disable g_SHOW_AHEAD_LEGACY_MODE."
    severity NOTE;

  we_int <= we_i and not full;
  rd_int <= rd_i and not empty;

  U_FIFO_Ram : generic_dpram
    generic map (
      g_data_width               => g_data_width,
      g_size                     => g_size,
      g_with_byte_enable         => false,
      g_addr_conflict_resolution => "dont_care",
      g_dual_clock               => false,
      g_implementation_hint      => g_memory_implementation_hint)
    port map (
      rst_n_i => rst_n_i,
      clka_i  => clk_i,
      wea_i   => we_int,
      aa_i    => std_logic_vector(wr_ptr(c_pointer_width-1 downto 0)),
      da_i    => d_i,
      clkb_i  => '0',
      ab_i    => std_logic_vector(rd_ptr_muxed(c_pointer_width-1 downto 0)),
      qb_o    => q_comb);

  p_rd_ptr_mux: process(rd_int, rd_ptr)
  begin
    if(rd_int = '1' and g_show_ahead) then
      rd_ptr_muxed <= rd_ptr + 1;
    elsif((rd_int = '1' and not g_show_ahead) or (g_show_ahead)) then
      rd_ptr_muxed <= rd_ptr;
    else
      rd_ptr_muxed <= rd_ptr - 1;
    end if;
  end process p_rd_ptr_mux;

  q_o <= q_comb;

  p_pointers : process(clk_i)
  begin
    if rising_edge(clk_i) then
      if rst_n_i = '0' then
        wr_ptr <= (others => '0');
        rd_ptr <= (others => '0');
      else
        if(we_int = '1') then
          wr_ptr <= wr_ptr + 1;
        end if;

        if(rd_int = '1') then
          rd_ptr <= rd_ptr + 1;
        end if;
      end if;
    end if;
  end process;

  gen_comb_flags_showahead_legacy : if g_show_ahead = true and
                                      g_show_ahead_legacy_mode = true generate

    process(clk_i)
    begin
      if rising_edge(clk_i) then
        if ((rd_ptr + 1 = wr_ptr and rd_int = '1') or (rd_ptr = wr_ptr)) then
          empty <= '1';
        else
          empty <= '0';
        end if;
      end if;
    end process;
    full <= '1' when (wr_ptr + 1 = rd_ptr) else '0';

  end generate gen_comb_flags_showahead_legacy;

  gen_comb_flags_showahead : if g_show_ahead = true and
                               g_show_ahead_legacy_mode = false generate

    process(clk_i)
    begin
      if rising_edge(clk_i) then
        if rst_n_i = '0' then
          guard_bit <= '0';
          empty     <= '1';
        else
          if wr_ptr = rd_ptr_muxed and guard_bit = '0' then
            empty <= '1';
          else
            empty <= '0';
          end if;
          if(wr_ptr + 1 = rd_ptr and we_int = '1') then
            guard_bit <= '1';
          elsif(rd_i = '1') then
            guard_bit <= '0';
          end if;
        end if;
      end if;
    end process;

    full <= '1' when (wr_ptr = rd_ptr and guard_bit = '1') else '0';

  end generate gen_comb_flags_showahead;

  gen_comb_flags : if(g_register_flag_outputs = false and g_show_ahead = false) generate
    empty <= '1' when (wr_ptr = rd_ptr and guard_bit = '0') else '0';
    full  <= '1' when (wr_ptr = rd_ptr and guard_bit = '1') else '0';

    p_guard_bit : process(clk_i)
    begin
      if rising_edge(clk_i) then
        if rst_n_i = '0' then
          guard_bit <= '0';
        elsif(wr_ptr + 1 = rd_ptr and we_int = '1') then
          guard_bit <= '1';
        elsif(rd_i = '1') then
          guard_bit <= '0';
        end if;
      end if;
    end process;
  end generate gen_comb_flags;

  gen_registered_flags : if(g_register_flag_outputs = true and g_show_ahead = false) generate
    p_reg_flags : process(clk_i)
    begin
      if rising_edge(clk_i) then

        if(rst_n_i = '0') then
          full  <= '0';
          empty <= '1';
        else
          if(usedw = 1 and rd_int = '1' and we_int = '0') then
            empty <= '1';
          elsif(we_int = '1' and rd_int = '0') then
            empty <= '0';
          end if;

          if(usedw = g_size-2 and we_int = '1' and rd_int = '0') then
            full <= '1';
          elsif(usedw = g_size-1 and rd_int = '1' and we_int = '0') then
            full <= '0';
          end if;
        end if;

      end if;
    end process;
  end generate gen_registered_flags;


  gen_with_word_counter : if(g_with_count or g_with_almost_empty or g_with_almost_full or g_register_flag_outputs) generate
    p_usedw_counter : process(clk_i)
    begin
      if rising_edge(clk_i) then
        if rst_n_i = '0' then
          usedw <= (others => '0');
        else
          if(we_int = '1' and rd_int = '0') then
            usedw <= usedw + 1;
          elsif(we_int = '0' and rd_int = '1') then
            usedw <= usedw - 1;
          end if;
        end if;
      end if;
    end process;

    count_o <= std_logic_vector(usedw(c_pointer_width-1 downto 0));

  end generate gen_with_word_counter;

  gen_with_almost_full : if(g_with_almost_full) generate
    process(clk_i)
    begin
      if rising_edge(clk_i) then
        if rst_n_i = '0' then
          almost_full_o <= '0';
        else
          if(usedw = g_almost_full_threshold-1) and (we_int = '1' and rd_int = '0') then
            almost_full_o <= '1';
          elsif (usedw = g_almost_full_threshold) and (rd_int = '1' and we_int = '0') then
            almost_full_o <= '0';
          end if;
        end if;
      end if;
    end process;
  end generate gen_with_almost_full;

  gen_without_almost_full : if(not g_with_almost_full) generate
    almost_full_o <= '0';
  end generate gen_without_almost_full;

  gen_with_almost_empty : if(g_with_almost_empty) generate
    process(clk_i)
    begin
      if rising_edge(clk_i) then
        if rst_n_i = '0' then
          almost_empty_o <= '1';
        else
          if(usedw = g_almost_empty_threshold+1) and (rd_int = '1' and we_int = '0') then
            almost_empty_o <= '1';
          elsif (usedw = g_almost_empty_threshold) and (we_int = '1' and rd_int = '0') then
            almost_empty_o <= '0';
          end if;
        end if;
      end if;
    end process;
  end generate gen_with_almost_empty;

  gen_without_almost_empty : if(not g_with_almost_empty) generate
    almost_empty_o <= '0';
  end generate gen_without_almost_empty;

  full_o  <= full;
  empty_o <= empty;

end syn;
