-------------------------------------------------------------------------------
-- Title      : Fast page allocator/deallocator
-- Project    : WhiteRabbit switch
-------------------------------------------------------------------------------
-- File       : swc_page_allocator.vhd
-- Author     : Tomasz Wlostowski
-- Company    : CERN BE-Co-HT
-- Created    : 2010-04-08
-- Last update: 2012-03-29
-- Platform   : FPGA-generic
-- Standard   : VHDL'87
-------------------------------------------------------------------------------
-- Description: Module implements a fast (2 cycle) paged memory allocator.
-- The allocator can serve 4 types of requests:
-- - Allocate a page with given use count (alloc_i = 1). The use count tells
--   the allocator how many clients requested that page (and hence, how many free
--   requests are required to return the page to free pages poll)
-- - Free a page (free_i = 1) - check the use count stored for the page. If it's
--   bigger than 1, decrease the use count, if it's 1 mark the page as free.
-- - Force free a page (force_free_i = 1): immediately frees the page regardless
--   of its current use_count.
-- - Set use count (set_usecnt_i = 1): sets the use count value for the given page.
--   Used to define the reference count for pages pre-allocated in advance by
--   the input blocks.
--   
--   It is implemented as a queue:
--   * queue head is the rd_ptr - it indicates the address in the memory of the next empty page
--   * queue tail is the wr_ptr - it indicates the address in the memory where the freed page can be
--                                stored
--   
-------------------------------------------------------------------------------
--
-- Copyright (c) 2010 - 2012 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
-- 2010-04-08  1.0      twlostow Created
-- 2010-10-11  1.1      mlipinsk comments added !!!!!
-- 2012-01-24  2.0      twlostow completely changed (uses FIFO)
-- 2012-03-05  2.1      mlipinsk added debugging stuff + made interchangeable with old (still buggy)
-- 2012-03-15  2.2      twlostow fixed really ugly missing pages bug
-------------------------------------------------------------------------------

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

use work.swc_swcore_pkg.all;
use work.genram_pkg.all;

entity swc_page_allocator_new is
  generic (
    -- number of pages in the allocator pool
    g_num_pages : integer := 2048;

    -- number of bits of the page address
    g_page_addr_width : integer := 11;

    g_num_ports : integer := 10;

    -- number of bits of the user (reference) count value
    g_usecount_width : integer := 4
    );

  port (
    clk_i   : in std_logic;             -- clock & reset
    rst_n_i : in std_logic;

    -- "Allocate" command strobe (active HI), starts allocation process of a page with use
    -- count given on usecnt_i. Address of the allocated page is returned on
    -- pgaddr_o and is valid when done_o is HI.
    alloc_i : in std_logic;

    -- "Free" command strobe (active HI), releases the page at address pgaddr_i if it's current
    -- use count is equal to 1, otherwise decreases the page's use count.
    free_i : in std_logic;

    force_free_i : in std_logic;  -- free strobe (active HI), releases the page
    -- at address pgaddr_i regardless of the user 
    -- count of the page
    -- it is used in case a package is corrupted
    -- and what have already been
    -- saved, needs to be released


    set_usecnt_i : in std_logic;        -- enables to set user count to already
                                        -- alocated page, used in the case of the
                                        -- address of the first page of a package,
                                        -- we need to allocate this page in advance
                                        -- not knowing the user count, so the user count
                                        -- needs to be set to already allocated page

    -- "Use count" value for the page to be allocated. If the page is to be
    -- used by multiple output queues, each of them will attempt to free it.

    usecnt_i : in std_logic_vector(g_usecount_width-1 downto 0);

    pgaddr_i : in std_logic_vector(g_page_addr_width -1 downto 0);

    pgaddr_o : out std_logic_vector(g_page_addr_width -1 downto 0);

    free_last_usecnt_o : out std_logic;

    done_o : out std_logic;             -- "early" done output (active HI).
                                        -- Indicates that
                                        -- the alloc/release cycle is going to
                                        -- end 1 cycle in advance, so the
                                        -- multiport scheduler can optimize
                                        -- it's performance

    nomem_o : out std_logic;

    dbg_double_free_o       : out std_logic;
    dbg_double_force_free_o : out std_logic;
    dbg_q_write_o           : out std_logic;
    dbg_q_read_o            : out std_logic;
    dbg_initializing_o      : out std_logic
    );

end swc_page_allocator_new;

architecture syn of swc_page_allocator_new is

  signal real_nomem, out_nomem : std_logic;

  -- queue head
  signal rd_ptr           : unsigned(g_page_addr_width-1 downto 0);

  -- queue tail
  signal wr_ptr           : unsigned(g_page_addr_width-1 downto 0);
  
  -- diff between head and tail
  signal free_pages       : unsigned(g_page_addr_width downto 0);

  signal q_write , q_read : std_logic;
  signal pending_free     : std_logic;
  signal read_usecnt      : std_logic_vector(g_usecount_width-1 downto 0);

  signal initializing : std_logic;

  signal usecnt_write                 : std_logic;
  signal usecnt_addr                  : std_logic_vector(g_page_addr_width-1 downto 0);
  signal usecnt_rddata, usecnt_wrdata : std_logic_vector(g_usecount_width-1 downto 0);

  signal q_output_addr, q_input_addr : std_logic_vector(g_page_addr_width-1 downto 0);
  signal alloc_d0                    : std_logic;
  signal force_free_d0               : std_logic;
  signal free_d0                     : std_logic;
  signal done_int                    : std_logic;
  signal ram_ones                    : std_logic_vector(g_page_addr_width + g_usecount_width -1 downto 0);


  --debuggin sygnals
  signal tmp_dbg_dealloc : std_logic;  -- used for symulation debugging, don't remove
  signal tmp_page        : std_logic_vector(g_page_addr_width -1 downto 0);
  signal free_blocks     : unsigned(g_page_addr_width downto 0);
  signal usecnt_not_zero : std_logic;
  signal real_nomem_d0   : std_logic;
begin  -- syn
  ram_ones <= (others => '1');

  -- memeory in which list of addresses of available pages is stored
  -- rd_ptr points to the address of next available page_addr
  -- wr_ptr points to the address where a freed page can be written
  U_Queue_RAM : swc_rd_wr_ram
    generic map (
      g_data_width => g_page_addr_width,
      g_size       => 2**g_page_addr_width)
    port map (
      clk_i   => clk_i,
      rst_n_i => rst_n_i,
      we_i    => q_write,
      wa_i    => std_logic_vector(wr_ptr),
      wd_i    => q_input_addr,
      ra_i    => std_logic_vector(rd_ptr),
      rd_o    => q_output_addr);

  q_write <= '1' when
             (free_d0 = '1' and unsigned(usecnt_rddata) = 1)
             or (force_free_d0 = '1') else initializing;

  q_read <= '1' when (alloc_d0 = '1') and (real_nomem_d0 = '0') else '0';


  q_input_addr <= std_logic_vector(wr_ptr) when initializing = '1' else pgaddr_i;

  --U_Queue_RAM : generic_dpram
  --  generic map (
  --    g_data_width               => g_page_addr_width,
  --    g_size                     => 2**g_page_addr_width,
  --    g_with_byte_enable         => false,
  --    g_dual_clock               => false)
  --  port map (
  --    rst_n_i => rst_n_i,
  --    clka_i  => clk_i,
  --    bwea_i  => ram_ones((g_page_addr_width+7)/8 - 1 downto 0),
  --    wea_i   => q_write,
  --    aa_i    => std_logic_vector(wr_ptr),
  --    da_i    => pgaddr_i,

  --    clkb_i => clk_i,
  --    bweb_i => ram_ones((g_page_addr_width+7)/8 - 1 downto 0),
  --    web_i  => initializing,
  --    ab_i   => std_logic_vector(rd_ptr),
  --    db_i   => std_logic_vector(rd_ptr),
  --    qb_o   => q_output_addr);

  -- stores usecnts of pages, the addres is the page_addr (not ptr)
  U_UseCnt_RAM : swc_rd_wr_ram
    generic map (
      g_data_width => g_usecount_width,
      g_size       => 2**g_page_addr_width)
    port map (
      clk_i   => clk_i,
      rst_n_i => rst_n_i,
      we_i    => usecnt_write,
      wa_i    => usecnt_addr,
      wd_i    => usecnt_wrdata,
      ra_i    => usecnt_addr,
      rd_o    => usecnt_rddata);

  usecnt_addr  <= q_output_addr when alloc_d0 = '1' else pgaddr_i;
  usecnt_write <= (alloc_d0 or set_usecnt_i or free_d0 or force_free_d0) and not initializing;

  usecnt_wrdata <= usecnt_i when (set_usecnt_i = '1' or alloc_d0 = '1') else
                   f_gen_dummy_vec('0', g_usecount_width) when force_free_i = '1' else
                   std_logic_vector(unsigned(usecnt_rddata) - 1);

  p_debug : process(clk_i)
  begin
    if rising_edge(clk_i) then

      if(free_d0 = '1' and unsigned(usecnt_rddata) = 0) then
        dbg_double_free_o <= '1';
      else
        dbg_double_free_o <= '0';
      end if;

      if(force_free_d0 = '1' and unsigned(usecnt_rddata) = 0) then
        dbg_double_force_free_o <= '1';
      else
        dbg_double_force_free_o <= '0';
      end if;

    end if;
  end process;

  --U_UseCnt_RAM : generic_dpram
  --  generic map (
  --    g_data_width       => g_usecount_width,
  --    g_size             => 2**g_page_addr_width,
  --    g_with_byte_enable => false,
  --    g_dual_clock       => false)
  --  port map (
  --    rst_n_i => rst_n_i,
  --    clka_i  => clk_i,
  --    wea_i   => usecnt_write,
  --    bwea_i  => ram_ones((g_usecount_width+7)/8 - 1 downto 0),
  --    aa_i    => usecnt_addr,
  --    da_i    => usecnt_wrdata,
  --    qa_o    => usecnt_rddata,

  --    clkb_i => clk_i,
  --    bweb_i => ram_ones((g_usecount_width+7)/8 - 1 downto 0),
  --    web_i  => initializing,
  --    ab_i   => std_logic_vector(rd_ptr),
  --    db_i   => f_gen_dummy_vec('0', g_usecount_width));


  -- keeps track of write and read pointers (wr_ptr and rd_ptr) and the number of available
  -- free pages (free_pages). 
  -- indicates when we run out of pages
  p_pointers : process(clk_i)
  begin
    if rising_edge(clk_i) then
      if rst_n_i = '0' then
        initializing <= '1';
        rd_ptr       <= (others => '0');
        wr_ptr       <= (others => '0');
        real_nomem   <= '0';
        free_pages   <= to_unsigned(g_num_pages-1, free_pages'length);
      else

        real_nomem_d0 <= real_nomem;

        if(initializing = '1') then
          if(wr_ptr = g_num_pages-1) then
            initializing <= '0';
            wr_ptr       <= to_unsigned(g_num_pages-1, wr_ptr'length);
          else
            wr_ptr <= wr_ptr + 1;
          end if;
        else
          if(q_write = '1') then
            wr_ptr <= wr_ptr + 1;
          end if;

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

          if(q_write = '1' and q_read = '0') then
            real_nomem <= '0';
            free_pages <= free_pages + 1;
          elsif (q_write = '0' and q_read = '1') then
            if(free_pages = 1) then
              real_nomem <= '1';
            end if;
            free_pages <= free_pages - 1;
          end if;
        end if;
      end if;
    end if;
  end process;

  -- registers control signals (alloc/free/force_free)-- creates strobes
  p_delay_alloc : process(clk_i)
  begin
    if rising_edge(clk_i) then
      if rst_n_i = '0' or initializing = '1' then
        alloc_d0      <= '0';
        free_d0       <= '0';
        force_free_d0 <= '0';
      else
        alloc_d0      <= alloc_i and not alloc_d0;
        free_d0       <= free_i and not free_d0;
        force_free_d0 <= force_free_i and not force_free_d0;
      end if;
    end if;
  end process;

  pgaddr_o <= q_output_addr;

  -- generates done signal
  p_gen_done : process(clk_i)
  begin
    if rising_edge(clk_i) then
      if (rst_n_i = '0') or (initializing = '1') then
        done_int <= '0';
      else
        if(done_int = '1')then
          done_int <= '0';
        elsif(((alloc_i = '1' and real_nomem = '0') or set_usecnt_i = '1' or free_i = '1' or force_free_i = '1') and initializing = '0') then
          done_int <= '1';
        end if;
      end if;
    end if;
  end process;

  done_o <= done_int;                   -- and not(free_d0 or alloc_d0);
--  q_write <= (not initializing) when (free_d0 = '1' and unsigned(usecnt_rddata) = 1) or (force_free_i = '1' and done_int = '0') else '0';

  -- generate non_memory output
  p_gen_nomem_output : process(clk_i)
  begin
    if rising_edge(clk_i) then
      if rst_n_i = '0' then
        out_nomem <= '0';
      else
        if(out_nomem = '0' and (free_blocks < to_unsigned(10, free_blocks'length))) then
          out_nomem <= '1';
        elsif(out_nomem = '1' and (free_blocks > to_unsigned((3*g_num_ports), free_blocks'length))) then
          out_nomem <= real_nomem;
        end if;
      end if;
    end if;
  end process;


  nomem_o <= out_nomem;

--  idle_o <= not (initializing or free_d0 or alloc_d0);

  free_last_usecnt_o <= (not initializing) when (free_d0 = '1' and unsigned(usecnt_rddata) = 1) else '0';

  free_blocks <= free_pages;

  dbg_q_read_o       <= q_read;
  dbg_q_write_o      <= q_write;
  dbg_initializing_o <= initializing;
end syn;
