library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity avalon_arbiter_tb is
end avalon_arbiter_tb;

architecture testbench of avalon_arbiter_tb is
    
    -- Paramètres
    constant DATA_WIDTH : integer := 32;
    constant ADDR_WIDTH : integer := 32;
    constant CLK_PERIOD : time := 10 ns;
    
    -- Signaux de test
    signal clk             : std_logic := '0';
    signal reset_n         : std_logic := '0';
    
    -- Master 0
    signal m0_address      : std_logic_vector(ADDR_WIDTH-1 downto 0) := (others => '0');
    signal m0_write        : std_logic := '0';
    signal m0_writedata    : std_logic_vector(DATA_WIDTH-1 downto 0) := (others => '0');
    signal m0_read         : std_logic := '0';
    signal m0_burstcount   : std_logic_vector(7 downto 0) := (others => '0');
    signal m0_readdata     : std_logic_vector(DATA_WIDTH-1 downto 0);
    signal m0_readdatavalid : std_logic;
    signal m0_waitrequest  : std_logic;
    
    -- Master 1
    signal m1_address      : std_logic_vector(ADDR_WIDTH-1 downto 0) := (others => '0');
    signal m1_write        : std_logic := '0';
    signal m1_writedata    : std_logic_vector(DATA_WIDTH-1 downto 0) := (others => '0');
    signal m1_read         : std_logic := '0';
    signal m1_burstcount   : std_logic_vector(7 downto 0) := (others => '0');
    signal m1_readdata     : std_logic_vector(DATA_WIDTH-1 downto 0);
    signal m1_readdatavalid : std_logic;
    signal m1_waitrequest  : std_logic;
    
    -- Slave
    signal s_address       : std_logic_vector(ADDR_WIDTH-1 downto 0);
    signal s_write         : std_logic;
    signal s_writedata     : std_logic_vector(DATA_WIDTH-1 downto 0);
    signal s_read          : std_logic;
    signal s_burstcount    : std_logic_vector(7 downto 0);
    signal s_readdata      : std_logic_vector(DATA_WIDTH-1 downto 0) := (others => '0');
    signal s_readdatavalid : std_logic := '0';
    signal s_waitrequest   : std_logic := '0';
    
    -- Signaux pour simuler l'esclave
    signal slave_latency_counter : integer := 0;
    signal slave_burst_counter : integer := 0;
    signal slave_pending_read : std_logic := '0';
    signal test_done : std_logic := '0';
    
    -- =====================================================================
    -- PROCÉDURES GÉNÉRIQUES POUR GÉNÉRER LES ACCÈS AVALON
    -- =====================================================================
    
    -- Procédure générique : Écriture Avalon
    procedure avalon_write (
        constant addr : in std_logic_vector(ADDR_WIDTH-1 downto 0);
        constant data : in std_logic_vector(DATA_WIDTH-1 downto 0);
        constant master_name : in string;
        signal m_address    : out std_logic_vector(ADDR_WIDTH-1 downto 0);
        signal m_write      : out std_logic;
        signal m_writedata  : out std_logic_vector(DATA_WIDTH-1 downto 0);
        signal m_waitrequest : in std_logic;
        signal clk_sig      : in std_logic
    ) is
    begin
        wait until rising_edge(clk_sig);
        m_address <= addr;
        m_writedata <= data;
        m_write <= '1';
        
        -- Attendre que waitrequest soit à 0
        wait until rising_edge(clk_sig);
        while m_waitrequest = '1' loop
            wait until rising_edge(clk_sig);
        end loop;
        
        m_write <= '0';
        m_address <= (others => '0');
        m_writedata <= (others => '0');
        
        report master_name & " Write @ 0x" & to_hstring(addr) & " = 0x" & to_hstring(data);
    end procedure;
    
    -- Procédure générique : Lecture Avalon
    procedure avalon_read (
        constant addr : in std_logic_vector(ADDR_WIDTH-1 downto 0);
        constant burst : in integer := 1;
        constant master_name : in string;
        signal m_address      : out std_logic_vector(ADDR_WIDTH-1 downto 0);
        signal m_read         : out std_logic;
        signal m_burstcount   : out std_logic_vector(7 downto 0);
        signal m_readdata     : in std_logic_vector(DATA_WIDTH-1 downto 0);
        signal m_readdatavalid : in std_logic;
        signal m_waitrequest  : in std_logic;
        signal clk_sig        : in std_logic
    ) is
        variable data_received : integer := 0;
    begin
        wait until rising_edge(clk_sig);
        m_address <= addr;
        m_burstcount <= std_logic_vector(to_unsigned(burst, 8));
        m_read <= '1';
        
        -- Attendre acceptation
        wait until rising_edge(clk_sig);
        while m_waitrequest = '1' loop
            wait until rising_edge(clk_sig);
        end loop;
        
        m_read <= '0';
        m_address <= (others => '0');
        m_burstcount <= (others => '0');
        
        report master_name & " Read @ 0x" & to_hstring(addr) & " burst=" & integer'image(burst);
        
        -- Attendre toutes les données du burst
        data_received := 0;
        while data_received < burst loop
            wait until rising_edge(clk_sig);
            if m_readdatavalid = '1' then
                report master_name & "   Data[" & integer'image(data_received) & "] = 0x" & 
                       to_hstring(m_readdata);
                data_received := data_received + 1;
            end if;
        end loop;
    end procedure;
    
    -- Procédure : Attendre N cycles d'horloge
    procedure wait_cycles (
        constant n : in integer;
        signal clk_sig : in std_logic
    ) is
    begin
        for i in 1 to n loop
            wait until rising_edge(clk_sig);
        end loop;
    end procedure;
    
begin
    
    -- Instanciation du DUT
    dut : entity work.avalon_arbiter
        generic map (
            DATA_WIDTH => DATA_WIDTH,
            ADDR_WIDTH => ADDR_WIDTH
        )
        port map (
            clk => clk,
            reset_n => reset_n,
            m0_address => m0_address,
            m0_write => m0_write,
            m0_writedata => m0_writedata,
            m0_read => m0_read,
            m0_burstcount => m0_burstcount,
            m0_readdata => m0_readdata,
            m0_readdatavalid => m0_readdatavalid,
            m0_waitrequest => m0_waitrequest,
            m1_address => m1_address,
            m1_write => m1_write,
            m1_writedata => m1_writedata,
            m1_read => m1_read,
            m1_burstcount => m1_burstcount,
            m1_readdata => m1_readdata,
            m1_readdatavalid => m1_readdatavalid,
            m1_waitrequest => m1_waitrequest,
            s_address => s_address,
            s_write => s_write,
            s_writedata => s_writedata,
            s_read => s_read,
            s_burstcount => s_burstcount,
            s_readdata => s_readdata,
            s_readdatavalid => s_readdatavalid,
            s_waitrequest => s_waitrequest
        );
    
    -- Génération horloge
    clk_process : process
    begin
        while test_done = '0' loop
            clk <= '0';
            wait for CLK_PERIOD/2;
            clk <= '1';
            wait for CLK_PERIOD/2;
        end loop;
        wait;
    end process;
    
    -- Modèle d'esclave
    slave_model : process(clk)
    begin
        if rising_edge(clk) then
            if reset_n = '0' then
                s_readdatavalid <= '0';
                s_waitrequest <= '0';
                slave_latency_counter <= 0;
                slave_burst_counter <= 0;
                slave_pending_read <= '0';
            else
                s_readdatavalid <= '0';
                
                if s_read = '1' and s_waitrequest = '0' then
                    slave_pending_read <= '1';
                    slave_latency_counter <= 2;
                    slave_burst_counter <= to_integer(unsigned(s_burstcount));
                    s_waitrequest <= '1';
                end if;
                
                if slave_pending_read = '1' then
                    if slave_latency_counter > 0 then
                        slave_latency_counter <= slave_latency_counter - 1;
                        if slave_latency_counter = 1 then
                            s_waitrequest <= '0';
                        end if;
                    else
                        if slave_burst_counter > 0 then
                            s_readdatavalid <= '1';
                            s_readdata <= std_logic_vector(to_unsigned(
                                slave_burst_counter * 16#100# + to_integer(unsigned(s_address(7 downto 0))), 
                                DATA_WIDTH));
                            slave_burst_counter <= slave_burst_counter - 1;
                            if slave_burst_counter = 1 then
                                slave_pending_read <= '0';
                            end if;
                        end if;
                    end if;
                end if;
            end if;
        end if;
    end process;
    
    -- =====================================================================
    -- PROCESSUS DE TEST UTILISANT LES PROCÉDURES GÉNÉRIQUES
    -- =====================================================================
    test_process : process
    begin
        -- Test 1 : Reset
        report "========================================";
        report "Test 1: Reset du système";
        report "========================================";
        reset_n <= '0';
        wait_cycles(5, clk);
        reset_n <= '1';
        wait_cycles(2, clk);
        
        -- Test 2 : Écritures simples
        report "========================================";
        report "Test 2: Écritures simples";
        report "========================================";
        
        avalon_write(X"00001000", X"12345678", "M0",
                    m0_address, m0_write, m0_writedata, m0_waitrequest, clk);
        
        wait_cycles(2, clk);
        
        avalon_write(X"00002000", X"DEADBEEF", "M1",
                    m1_address, m1_write, m1_writedata, m1_waitrequest, clk);
        
        wait_cycles(3, clk);
        
        -- Test 3 : Lectures simples
        report "========================================";
        report "Test 3: Lectures simples (burst = 1)";
        report "========================================";
        
        avalon_read(X"00003000", 1, "M0",
                   m0_address, m0_read, m0_burstcount, 
                   m0_readdata, m0_readdatavalid, m0_waitrequest, clk);
        
        wait_cycles(2, clk);
        
        avalon_read(X"00004000", 1, "M1",
                   m1_address, m1_read, m1_burstcount, 
                   m1_readdata, m1_readdatavalid, m1_waitrequest, clk);
        
        wait_cycles(3, clk);
        
        -- Test 4 : Lectures en burst
        report "========================================";
        report "Test 4: Lectures en burst";
        report "========================================";
        
        avalon_read(X"00005000", 4, "M0",
                   m0_address, m0_read, m0_burstcount, 
                   m0_readdata, m0_readdatavalid, m0_waitrequest, clk);
        
        wait_cycles(2, clk);
        
        avalon_read(X"00006000", 8, "M1",
                   m1_address, m1_read, m1_burstcount, 
                   m1_readdata, m1_readdatavalid, m1_waitrequest, clk);
        
        wait_cycles(3, clk);
        
        -- Test 5 : Mélange lecture/écriture
        report "========================================";
        report "Test 5: Mélange lecture/écriture";
        report "========================================";
        
        avalon_write(X"00007000", X"CAFEBABE", "M0",
                    m0_address, m0_write, m0_writedata, m0_waitrequest, clk);
        
        avalon_read(X"00007000", 2, "M1",
                   m1_address, m1_read, m1_burstcount, 
                   m1_readdata, m1_readdatavalid, m1_waitrequest, clk);
        
        avalon_write(X"00008000", X"FACEFEED", "M1",
                    m1_address, m1_write, m1_writedata, m1_waitrequest, clk);
        
        avalon_read(X"00008000", 3, "M0",
                   m0_address, m0_read, m0_burstcount, 
                   m0_readdata, m0_readdatavalid, m0_waitrequest, clk);
        
        wait_cycles(5, clk);
        
        -- Test 6 : Accès séquentiels rapides
        report "========================================";
        report "Test 6: Accès séquentiels rapides";
        report "========================================";
        
        for i in 0 to 3 loop
            avalon_write(std_logic_vector(to_unsigned(16#A000# + i*4, ADDR_WIDTH)), 
                        std_logic_vector(to_unsigned(16#1000# + i, DATA_WIDTH)),
                        "M0",
                        m0_address, m0_write, m0_writedata, m0_waitrequest, clk);
        end loop;
        
        wait_cycles(2, clk);
        
        for i in 0 to 3 loop
            avalon_read(std_logic_vector(to_unsigned(16#A000# + i*4, ADDR_WIDTH)), 
                       1, "M1",
                       m1_address, m1_read, m1_burstcount, 
                       m1_readdata, m1_readdatavalid, m1_waitrequest, clk);
        end loop;
        
        wait_cycles(5, clk);
        
        -- Test 7 : Burst long
        report "========================================";
        report "Test 7: Burst long (16 mots)";
        report "========================================";
        
        avalon_read(X"0000B000", 16, "M0",
                   m0_address, m0_read, m0_burstcount, 
                   m0_readdata, m0_readdatavalid, m0_waitrequest, clk);
        
        wait_cycles(5, clk);
        
        -- Fin du test
        report "========================================";
        report "TOUS LES TESTS SONT TERMINÉS AVEC SUCCÈS !";
        report "========================================";
        test_done <= '1';
        wait;
    end process;
    
end testbench;
