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

entity cic_up is
   generic(
      num_bits   : natural := 16;   -- How many bits in our incoming data?
      num_stages : natural :=  3;   -- How many stages in the CIC? (N)
      resamp     : natural := 32;   -- By what factor are we changing our data rate? (R)
      diff_dly   : natural :=  1;   -- The differential delay (M)
      out_rate   : natural :=  8);  -- Number of clocks per output sample
                                    --  Must be a power of two
   port(
      clk_i   : in  std_logic;
      rst_i   : in  std_logic;
      -- Input ports
      data_i  : in  std_logic_vector(num_bits-1 downto 0);
      valid_i : in  std_logic;
      -- Output samples
      data_o  : out std_logic_vector;
      valid_o : out std_logic);
end entity cic_up;

architecture behavior of cic_up is
   -----------------------------------------------------------------------------
   -- Functions
   -----------------------------------------------------------------------------
   function log(b,n: positive) return natural is
      variable temp   : natural := 1;
      variable answer : natural := 0;
   begin
      while temp < n loop
         answer := answer + 1;
         temp   := temp * b;
      end loop;
      return answer;
   end function log;

   -----------------------------------------------------------------------------
   function signed_add(l,r: std_logic_vector) return std_logic_vector is
      variable resized_r : signed(r'high+1 downto r'low);
   begin
      resized_r := resize(signed(r), r'length+1);
      return std_logic_vector(signed(l) + resized_r);
   end function signed_add;

   -----------------------------------------------------------------------------
   function signed_sub(l,r: std_logic_vector) return std_logic_vector is
      variable resized_r : signed(r'high+1 downto r'low);
   begin
      resized_r := resize(signed(r), r'length+1);
      return std_logic_vector(signed(l) - resized_r);
   end function signed_sub;

   -----------------------------------------------------------------------------
   impure function input_bits return natural is
      variable a, b, c : integer;
   begin
      a := (num_bits   + 1);
      b := (num_stages - 2)*(log(2,resamp));
      c := (num_stages - 1)*(log(2,diff_dly));
      return a + b + c;
   end function input_bits;

   -----------------------------------------------------------------------------
   impure function output_bits return natural is
      variable a, b, c : integer;
   begin
      a := (num_bits   + 0);
      b := (num_stages - 1)*(log(2,resamp));
      c := (num_stages - 0)*(log(2,diff_dly));
      return a + b + c;
   end function output_bits;

   -----------------------------------------------------------------------------
   -- Types, subtypes, and constants
   -----------------------------------------------------------------------------
   -- Bit growth constants
   constant direction     : string    := "up";
   subtype int_i_range    is natural range  input_bits-1     downto 0;
   subtype int_o_range    is natural range output_bits-1     downto 0;
   subtype comb_sum_range is natural range num_bits          downto 0;
   subtype count_range    is natural range log(2,out_rate)-1 downto 0;
   -- Array type for differential delay
   subtype word      is std_logic_vector(num_bits-1 downto 0);
   type comb_reg_type   is array (integer range <>) of word;
   -----------------------------------------------------------------------------
   -- Signals
   -----------------------------------------------------------------------------
   signal comb_reg : comb_reg_type(0 to diff_dly-1)  := (others => (others => '0'));
   signal comb_sum : std_logic_vector(comb_sum_range):= (others => '0');
   signal int_in   : std_logic_vector(int_i_range);
   signal int_sum  : signed          (int_o_range)   := (others => '0');
   signal int_en   : std_logic;
   signal count    : unsigned(count_range)           := (others => '0');
   signal c_dly    : std_logic;
begin
   -----------------------------------------------------------------------------
   -- Map outputs
   -----------------------------------------------------------------------------
   data_o <= std_logic_vector(int_sum);

   -----------------------------------------------------------------------------
   -- Comb (derivative) section
   -----------------------------------------------------------------------------
   comb_proc : process(clk_i)
   begin
      if rising_edge(clk_i) then
         -- Comb registers
         if rst_i = '1' then
            comb_reg <= (others => (others => '0'));
            comb_sum <= (others => '0');
         elsif valid_i = '1' then
            comb_reg <= data_i & comb_reg(0 to comb_reg'high-1);
            comb_sum <= signed_sub(data_i, comb_reg(comb_reg'high));
         end if;
      end if;
   end process comb_proc;

   -----------------------------------------------------------------------------
   -- Integrator section
   -----------------------------------------------------------------------------
   int_proc : process(clk_i)
   begin
      if rising_edge(clk_i) then
         -- Integrator registers
         if rst_i = '1' then
            int_sum  <= (others => '0');
         else
            if num_stages = 1 then
               -- The connection between the ints and the combs is determined
               --  by the generic out_rate.  This is indirectly controlled by
               --  the top bit in count, which counts to out_rate.
               if out_rate = 1 then
                  valid_o <= '1';
               else
                  valid_o <= count(count'left) and not c_dly;
               end if;

               -- The size of the first integrator register could be a different
               --  size from the last stage of the combs.  This should make up
               --  that difference.
               if valid_i = '1' then
                  int_sum <= resize(int_sum + signed(comb_sum), int_sum'length);
               end if;

            -- Every other stage of the integrators is controlled by the enable
            --  signal of the previous stage
            elsif int_en = '1' then
               int_sum <= int_sum + signed(int_in);
               valid_o <= '1';
            else
               valid_o <= '0';
            end if;
         end if;
      end if;
   end process int_proc;

   -----------------------------------------------------------------------------
   -- Recursive instantiation and termination
   -----------------------------------------------------------------------------
   cic_gen : if num_stages > 1 generate
   begin
      cic_inst : entity work.cic_up
         generic map(
            num_bits   => num_bits+1,
            num_stages => num_stages-1,
            diff_dly   => diff_dly,
            resamp     => resamp,
            out_rate   => out_rate)
         port map(
            clk_i   => clk_i,
            rst_i   => rst_i,
            -- Input ports
            data_i  => comb_sum,
            valid_i => valid_i,
            -- Output samples
            data_o  => int_in,
            valid_o => int_en);
   end generate cic_gen;

   valid_gen : if num_stages = 1 generate
   begin
      vary_valid_o : if out_rate /= 1 generate
      begin
         valid_o_proc : process(clk_i)
         begin
            if rising_edge(clk_i) then
               if rst_i = '1' then
                  count <= (others => '0');
                  c_dly <= '0';
               else
                  count <= count + 1;
                  c_dly <= count(count'left);
               end if;
            end if;
         end process valid_o_proc;
      end generate vary_valid_o;
   end generate valid_gen;

end architecture behavior;
