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

use work.typedef_package.all;

-- Architecture
--   ---      ----------      ------      -------      ---
--->|ADC|--->|Delay Line|--->|Scaler|--->|ADD/SUB|--->|DAC|--->
--   ---      ----------      ------      -------      ---
--                                           ^
--   ---                       -----    ---  |
--->|ADC|-------------------->|Latch|->|   | |
--   ---                       -----   |MUX|--
--                              GND--->|   |
--                                      ---

-- Feedback Loop
-- This entity implements the feedback loop as defined above.
-- In addition to that, this entity latches and provides various max values that can be used for debug
-- purposes.

entity feedback_loop is
    port (
        clk             : in std_logic;
        reset           : in std_logic;
        adc_data_in1    : in std_logic;     -- PMOD-AD1
        adc_data_in2    : in std_logic;     -- PMOD-AD1
        adc_cs_n        : out std_logic;    -- PMOD-AD1
        adc_sclk        : out std_logic;    -- PMOD-AD1
        dac_data_out    : out std_logic;    -- PMOD-DA3
        dac_cs_n        : out std_logic;    -- PMOD-DA3
        dac_ldac        : out std_logic;    -- PMOD-DA3
        dac_sclk        : out std_logic;    -- PMOD-DA3
        -- DYNAMIC CONFIGURATION
        addsub_mode     : in std_logic;                                 -- (1=ADD, 0=SUB)
        add_input_mux   : in std_logic;                                 -- (1=ADC Input 2, 0=GND)
        delay           : in std_logic_vector(DELAY_WIDTH-1 downto 0);  -- unsigned delay clock count
        factor          : in std_logic_vector(FACTOR_WIDTH-1 downto 0); -- 1Q3 Fixed Point
        -- DEBUG
        reset_debug     : in std_logic;
        adc_data1_max   : out std_logic_vector(ADC_DATA_WIDTH-1 downto 0);
        adc_data2_max   : out std_logic_vector(ADC_DATA_WIDTH-1 downto 0);
        scaler_max      : out std_logic_vector(DAC_DATA_WIDTH-1 downto 0);
        dac_max         : out std_logic_vector(DAC_DATA_WIDTH-1 downto 0)
    );
end entity;

architecture arch of feedback_loop is
    
    --*****SIGNAL DECLARATION*****
    signal adc_data1, adc_data2 : std_logic_vector(ADC_DATA_WIDTH-1 downto 0) := (others => '0');
    signal adc_done : std_logic := '0';
    
    --*****COMPONENT DECLARATION*****
    component pmod_ad1_ctrl is
        generic(
            TRANSFER_CLK_COUNT  : integer := 16;
            DELAY_CLK_CNT       : integer := 2;
            DATA_WIDTH          : integer := 12
        );
        port (
            clk     : in std_logic;
            reset   : in std_logic;
            enable  : in std_logic;
            sdata1  : in std_logic; -- PMOD-AD1
            sdata2  : in std_logic; -- PMOD-AD1
            sclk    : out std_logic;-- PMOD-AD1
            cs_n    : out std_logic;-- PMOD-AD1
            data1   : out std_logic_vector(DATA_WIDTH-1 downto 0);
            data2   : out std_logic_vector(DATA_WIDTH-1 downto 0);
            done    : out std_logic
        );
    end component;
    
    component pmod_da3_ctrl is
        generic(
            TRANSFER_CLK_COUNT  : integer := 16;
            DATA_WIDTH          : integer := 16
        );
        port (
            clk     : in std_logic;
            reset   : in std_logic;
            start   : in std_logic;
            data    : in std_logic_vector(DATA_WIDTH-1 downto 0);
            cs_n    : out std_logic;-- PMOD-DA3
            sdata   : out std_logic;-- PMOD-DA3
            ldac    : out std_logic;-- PMOD-DA3
            sclk    : out std_logic;-- PMOD-DA3
            done    : out std_logic
        );
    end component;
    
    component scaler is
        generic (
            DATA_WIDTH      : integer := 12;
            FACTOR_WIDTH    : integer := 4;
            PIPELINE_STAGES : integer := 1
        );
        port (
            clk         : in std_logic;  
            data_in     : in std_logic_vector(DATA_WIDTH-1 downto 0);
            factor      : in std_logic_vector(FACTOR_WIDTH-1 downto 0);
            data_out    : out std_logic_vector(DATA_WIDTH+FACTOR_WIDTH-1 downto 0)
        );
    end component;
    
    component addsub is
        generic (
            PIPELINE_STAGES : integer := 1;
            DATA_WIDTH      : integer := 16
        );
        port (
            clk     : in std_logic;
            reset   : in std_logic;
            mode    : in std_logic;
            cap     : in std_logic;
            A       : in std_logic_vector(DATA_WIDTH-1 downto 0);
            B       : in std_logic_vector(DATA_WIDTH-1 downto 0);
            RES     : out std_logic_vector(DATA_WIDTH-1 downto 0)
        );
    end component;
    
    component delay_line is
            generic (
                DATA_WIDTH  : integer := 12;
                DELAY_WIDTH : integer := 8;
                MAX_DELAY   : integer := 200
            );
            port (
                clk         : in std_logic;
                reset       : in std_logic;
                delay       : in std_logic_vector(DELAY_WIDTH-1 downto 0);
                data_in     : in std_logic_vector(DATA_WIDTH-1 downto 0);
                data_out    : out std_logic_vector(DATA_WIDTH-1 downto 0)
            );
    end component;
    
    --*****SIGNAL DECLARATION*****
    signal delay_out : std_logic_vector(ADC_DATA_WIDTH downto 0) := (others => '0');
    signal latch_out : std_logic_vector(ADC_DATA_WIDTH-1 downto 0) := (others => '0');
    signal scaler_out, addsub_out, inputA : std_logic_vector(DAC_DATA_WIDTH-1 downto 0) := (others => '0');
    signal scaler_done, addsub_done : std_logic := '0';
    
begin

    --*****STAGE I*****    
    adc_inst : pmod_ad1_ctrl
        generic map(
            TRANSFER_CLK_COUNT  => ADC_TRANSFER_CLK_COUNT,
            DELAY_CLK_CNT       => ADC_DELAY_CLK_CNT,
            DATA_WIDTH          => ADC_DATA_WIDTH
        )
        port map(
            clk     => clk,
            reset   => reset,
            enable  => '1',
            sdata1  => adc_data_in1,
            sdata2  => adc_data_in2,
            sclk    => adc_sclk,
            cs_n    => adc_cs_n,
            data1   => adc_data1,
            data2   => adc_data2,
            done    => adc_done
        );
    
    --*****STAGE II*****
    delay_line_inst : delay_line
        generic map(
            DATA_WIDTH  => ADC_DATA_WIDTH+1,
            DELAY_WIDTH => DELAY_WIDTH,
            MAX_DELAY   => MAX_DELAY
        )
        port map(
            clk         => clk,
            reset       => reset,
            delay       => delay,
            data_in     => (adc_done & adc_data1),
            data_out    => delay_out
        );
    
    --*****STAGE III*****
    scaler_inst : scaler
        generic map(
            DATA_WIDTH      => ADC_DATA_WIDTH,
            FACTOR_WIDTH    => FACTOR_WIDTH,
            PIPELINE_STAGES => 1
        )
        port map(
            clk                                 => clk,
            data_in                             => delay_out(ADC_DATA_WIDTH-1 downto 0),
            factor                              => factor,
            data_out(DAC_DATA_WIDTH)            => open,    --Truncate result
            data_out(DAC_DATA_WIDTH-1 downto 0) => scaler_out
        );
    
    process(clk)
    begin
        if (rising_edge(clk)) then
            if (reset = '1') then
                scaler_done <= '0';
            else
                scaler_done <= delay_out(ADC_DATA_WIDTH);
            end if;
        end if;
    end process;
    
    latch : process(clk)
    begin
        if (rising_edge(clk)) then
            if (reset = '1') then
                latch_out   <= (others => '0');
            elsif (adc_done) then
                latch_out   <= adc_data2;
            end if;
        end if;
    end process;
    
    --*****STAGE IV*****
    
    mux: process(all)
    begin
        if (add_input_mux = '1') then
            inputA  <= latch_out & "0000";
        else
            inputA  <= (others => '0');
        end if;
    end process;
    
    addsub_inst : addsub
        generic map(
            PIPELINE_STAGES => 1,
            DATA_WIDTH      => DAC_DATA_WIDTH
        )
        port map(
            clk     => clk,
            reset   => reset,
            mode    => addsub_mode,
            cap     => add_input_mux,
            A       => inputA,
            B       => scaler_out,
            RES     => addsub_out
        );
    
    process(clk)
    begin
        if (rising_edge(clk)) then
            if (reset = '1') then
                addsub_done <= '0';
            else
                addsub_done <= scaler_done;
            end if;
        end if;
    end process;
    
    --*****STAGE V*****
    dac_inst : pmod_da3_ctrl
        generic map(
            TRANSFER_CLK_COUNT  => DAC_TRANSFER_CLK_COUNT,
            DATA_WIDTH          => DAC_DATA_WIDTH
        )
        port map(
            clk     => clk,
            reset   => reset,
            start   => addsub_done,
            data    => addsub_out,
            cs_n    => dac_cs_n,
            sdata   => dac_data_out,
            ldac    => dac_ldac,
            sclk    => dac_sclk,
            done    => open
        );
    
    --*****DEBUG*****
    process(clk)
    begin
        if (rising_edge(clk)) then
            if (reset = '1' or reset_debug = '1') then
                adc_data1_max   <= (others => '0');
                adc_data2_max   <= (others => '0');
                dac_max         <= (others => '0');
                scaler_max      <= (others => '0');
            -- ADC MAX VALUES
            elsif (adc_done = '1') then
                if (to_integer(unsigned(adc_data1)) >= to_integer(unsigned(adc_data1_max))) then
                    adc_data1_max   <= adc_data1;
                elsif (to_integer(unsigned(adc_data2)) >= to_integer(unsigned(adc_data2_max))) then
                    adc_data2_max   <= adc_data2;
                end if;
            -- DAC MAX VALUES
            elsif (addsub_done = '1') then
                if (to_integer(unsigned(addsub_out)) >= to_integer(unsigned(dac_max))) then
                    dac_max <= addsub_out;
                end if;
            -- SCALER MAX VALUES
            elsif (scaler_done = '1') then
                if (to_integer(unsigned(scaler_out)) >= to_integer(unsigned(scaler_max))) then
                    scaler_max  <= scaler_out;
                end if;
            end if;
        end if;
    end process;
    
    
    
end architecture;