-- I2S stereo encoder

LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.numeric_std.ALL;

ENTITY i2s IS
  GENERIC (
    CLK_RATE   : natural :=50000000;
    AUDIO_DW   : natural :=16;
    AUDIO_RATE : natural :=96000);
  PORT (
    reset : IN std_logic;
    clk_sys : IN std_logic;
    half_rate : IN std_logic;

    sclk : OUT std_logic;
    lrclk : OUT std_logic;
    sdata : OUT std_logic;

    left_chan : IN std_logic_vector(AUDIO_DW-1 DOWNTO 0);
    right_chan : IN std_logic_vector(AUDIO_DW-1 DOWNTO 0)
    );  
END ENTITY i2s;

ARCHITECTURE rtl OF i2s IS
  
  FUNCTION gcd (
    CONSTANT a  : natural;
    CONSTANT b  : natural) RETURN natural IS
  BEGIN
    IF b/=0 THEN
      RETURN gcd(b,a MOD b);
    ELSE
      RETURN a;
    END IF;
  END FUNCTION gcd;
  
  CONSTANT AFREQ : natural := AUDIO_RATE * AUDIO_DW * 2 * 2;
  CONSTANT MUL   : natural := AFREQ/gcd(CLK_RATE,AFREQ);
  CONSTANT DIV   : natural :=  CLK_RATE/gcd(CLK_RATE,AFREQ);
  SIGNAL acc   : integer RANGE -MUL TO DIV-MUL :=0;
  
  SIGNAL sync,tog : std_logic;
  SIGNAL cpt : unsigned(5 DOWNTO 0);
  SIGNAL shift : std_logic_vector(AUDIO_DW*2-1 DOWNTO 0);
  SIGNAL lpf_ce : std_logic;
  
  SIGNAL sum_l,sum_r : signed(20 DOWNTO 0);
  SIGNAL acc_l,acc_r : signed(511 DOWNTO 0);
  SIGNAL oleft,oright : signed(15 DOWNTO 0);
  
BEGIN

  ----------------------------------------------------------
  ClockGen:PROCESS (clk_sys) IS
  BEGIN
    IF rising_edge(clk_sys) THEN
      IF acc>0 THEN
        acc<=acc-MUL;
        sync<='0';
      ELSE
        acc<=acc+DIV-MUL;
        sync<='1';
      END IF;
    END IF;
    IF reset='1' THEN
      sync<='0';
    END IF;
  END PROCESS ClockGen;
  
  ----------------------------------------------------------
  LPF:PROCESS(clk_sys) IS
    
    FUNCTION sum(acc : signed(511 DOWNTO 0)) RETURN signed IS
      VARIABLE sum : signed(20 DOWNTO 0) :=(OTHERS =>'0');
    BEGIN
      FOR i IN 0 TO 31 LOOP
        sum:=sum + resize(acc(i*16+15 DOWNTO i*16),21);
      END LOOP;
      RETURN sum(20 DOWNTO 5);
    END FUNCTION;
    
  BEGIN
    IF rising_edge(clk_sys) THEN
      IF lpf_ce='1' THEN
        acc_l<=acc_l(495 DOWNTO 0) & resize(signed(left_chan),16);
        acc_r<=acc_r(495 DOWNTO 0) & resize(signed(right_chan),16);
        oleft <=sum(acc_l);
        oright<=sum(acc_r);
      END IF;
    END IF;
  END PROCESS LPF;
  
  ----------------------------------------------------------
  Serial:PROCESS (clk_sys) IS
  BEGIN
    IF rising_edge(clk_sys) THEN
      lpf_ce<=cpt(0) AND NOT cpt(1) AND sync;
      
      IF sync='1' AND (tog='1' OR half_rate='0') THEN
        cpt<=cpt+1;
        sclk<=cpt(0);
        lrclk<=cpt(5);
        
        sdata<=shift(shift'left);
        IF cpt="000001" THEN
          shift<=std_logic_vector(oleft & oright);
        ELSIF cpt(0)='1' THEN
          shift<=shift(shift'high-1 DOWNTO 0) & oright(0);
        END IF;
      END IF;
      
      tog<=tog XOR sync;
      
      IF reset='1' THEN
        tog<='0';
        cpt<=(OTHERS =>'0');
      END IF;
    END IF;

  END PROCESS Serial;
  
END ARCHITECTURE rtl;
