-------------------------------------------------------------------------------
-- Title      : Word packer/unpacker
-- Project    : General Cores Collection library
-------------------------------------------------------------------------------
-- File       : gc_word_packer.vhd
-- Author     : Tomasz Wlostowski
-- Company    : CERN
-- Created    : 2012-09-13
-- Last update: 2020-09-18
-- Platform   : FPGA-generic
-- Standard   : VHDL'93
-------------------------------------------------------------------------------
-- Description: Packs/unpacks g_input_width-sized word(s) into g_output_width-
-- sized word(s). Data is packed starting from the least significant word.
-- Packet width must be integer multiple of the unpacked width.
-------------------------------------------------------------------------------
--
-- Copyright (c) 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
--
-------------------------------------------------------------------------------

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

use work.gencores_pkg.all;

entity gc_word_packer is

  generic(
    -- width of the input words
    g_input_width  : integer;
    -- width of the output words
    g_output_width : integer);

  port(
    clk_i   : in std_logic;
    rst_n_i : in std_logic;

    -- input data
    d_i       : in  std_logic_vector(g_input_width-1 downto 0);
    -- input data valid
    d_valid_i : in  std_logic;
    -- synchronous input data request, when active, the source
    -- is allowed to assert d_valid_i in next clock cycle.
    d_req_o   : out std_logic;

    -- flushes packer input, immediately outputting (incomplete) packed word to q_o
    -- even if the number of input words is less than g_output_width.
    -- Unused when g_input_width > g_output_width (unpacking)
    flush_i : in std_logic := '0';

    -- data output
    q_o       : out std_logic_vector(g_output_width-1 downto 0);
    -- data output valid
    q_valid_o : out std_logic;
    -- synchronous data request: if active, packer can output data in following
    -- clock cycle.
    q_req_i   : in  std_logic
    );

end gc_word_packer;

architecture rtl of gc_word_packer is

  function f_max(a : integer; b : integer) return integer is
  begin
    if(a > b) then
      return a;
    else
      return b;
    end if;
  end f_max;

  function f_min(a : integer; b : integer) return integer is
  begin
    if(a < b) then
      return a;
    else
      return b;
    end if;
  end f_min;

  constant c_sreg_size    : integer := f_max(g_input_width, g_output_width);
  constant c_sreg_entries : integer := c_sreg_size / f_min(g_input_width, g_output_width);

  signal sreg  : std_logic_vector(c_sreg_size-1 downto 0);
  signal count : unsigned(f_log2_ceil(c_sreg_entries + 1) - 1 downto 0);
  signal empty : std_logic;

  signal q_valid_comb, q_valid_reg, q_req_d0 : std_logic;
  
begin  -- rtl

-- Fixme: flush functionality.
  
  gen_grow : if(g_output_width > g_input_width) generate
    p_grow : process(clk_i)
    begin
      if rising_edge(clk_i) then
        if rst_n_i = '0' then
          count <= (others => '0');
        else

          if(d_valid_i = '1') then
            if(q_valid_reg = '0') then
              sreg(to_integer(count) * g_input_width + g_input_width-1 downto to_integer(count) * g_input_width) <= d_i;
            else
              sreg(g_input_width-1 downto 0) <= d_i;
            end if;
          end if;

          if(q_valid_comb = '1') then
            count <= (others => '0');
          elsif(d_valid_i = '1') then
            count <= count + 1;
          end if;

          q_valid_reg <= q_valid_comb;
          
        end if;
      end if;
    end process;

    q_valid_o    <= q_valid_reg;
    q_valid_comb <= '1' when (q_req_i = '1' and (count = c_sreg_entries or (count = c_sreg_entries-1 and d_valid_i = '1'))) else '0';

    d_req_o <= '1' when q_req_i = '1' and (count /= c_sreg_entries) else '0';
    q_o     <= sreg;
  end generate gen_grow;

  gen_shrink : if(g_output_width < g_input_width) generate

    p_shrink : process(clk_i)
    begin
      if rising_edge(clk_i) then
        if rst_n_i = '0' then
          count    <= (others => '0');
          empty    <= '1';
          q_req_d0 <= '0';
        else

          q_req_d0 <= q_req_i;

          if(d_valid_i = '1') then
            sreg <= d_i;
          end if;

          if(count = c_sreg_entries-1 and d_valid_i = '0' and q_valid_comb = '1') then
            empty <= '1';
          elsif(d_valid_i = '1') then
            empty <= '0';
          end if;

          if(q_valid_comb = '1') then
            if(count = c_sreg_entries-1) then
              count <= (others => '0');
            else
              count <= count + 1;
            end if;
          end if;

        end if;
      end if;
    end process;

    q_valid_o <= q_valid_comb;

    d_req_o <= '1' when d_valid_i = '0' and (empty = '1' or (q_req_i = '1' and count = c_sreg_entries-1)) else '0';
    p_gen_output : process(count, sreg, empty, d_i, d_valid_i, q_req_d0)
    begin
      if (q_req_d0 = '1' and empty = '0') then
        q_valid_comb <= '1';
        q_o          <= sreg(to_integer(count) * g_output_width + g_output_width-1 downto to_integer(count) * g_output_width);
      elsif (empty = '1' and d_valid_i = '1' and q_req_d0 = '1') then
        q_valid_comb <= '1';
        q_o          <= d_i(g_output_width-1 downto 0);
      else
        q_valid_comb <= '0';
        q_o          <= (others => 'X');
      end if;
    end process;
  end generate gen_shrink;

  gen_equal : if(g_output_width = g_input_width) generate
    q_o       <= d_i;
    q_valid_o <= d_valid_i;
    d_req_o   <= q_req_i;
  end generate gen_equal;

end rtl;
