library ieee;
use ieee.std_logic_1164.all;

library work;
use work.common.all;
use work.wishbone_types.all;

entity dcache_tb is
end dcache_tb;

architecture behave of dcache_tb is
    signal clk          : std_ulogic;
    signal rst          : std_ulogic;

    signal d_in         : Loadstore1ToDcacheType;
    signal d_out        : DcacheToLoadstore1Type;

    signal m_in         : MmuToDcacheType;
    signal m_out        : DcacheToMmuType;

    signal wb_bram_in   : wishbone_master_out;
    signal wb_bram_out  : wishbone_slave_out;

    constant clk_period : time := 10 ns;

    signal stall : std_ulogic;
begin
    dcache0: entity work.dcache
        generic map(
            LINE_SIZE => 64,
            NUM_LINES => 4
            )
        port map(
            clk => clk,
            rst => rst,
            d_in => d_in,
            d_out => d_out,
            stall_out => stall,
            m_in => m_in,
            m_out => m_out,
            wishbone_out => wb_bram_in,
            wishbone_in => wb_bram_out
            );

    -- BRAM Memory slave
    bram0: entity work.wishbone_bram_wrapper
        generic map(
            MEMORY_SIZE   => 1024,
            RAM_INIT_FILE => "icache_test.bin"
            )
        port map(
            clk => clk,
            rst => rst,
            wishbone_in => wb_bram_in,
            wishbone_out => wb_bram_out
            );

    clk_process: process
    begin
        clk <= '0';
        wait for clk_period/2;
        clk <= '1';
        wait for clk_period/2;
    end process;

    rst_process: process
    begin
        rst <= '1';
        wait for 2*clk_period;
        rst <= '0';
        wait;
    end process;

    stim: process
    begin
        -- Clear stuff
        d_in.valid <= '0';
        d_in.load <= '0';
        d_in.nc <= '0';
        d_in.hold <= '0';
        d_in.dcbz <= '0';
        d_in.reserve <= '0';
        d_in.virt_mode <= '0';
        d_in.priv_mode <= '1';
        d_in.addr <= (others => '0');
        d_in.data <= (others => '0');
        d_in.byte_sel <= (others => '1');
        m_in.valid <= '0';
        m_in.addr <= (others => '0');
        m_in.pte <= (others => '0');
        m_in.tlbie <= '0';
        m_in.doall <= '0';
        m_in.tlbld <= '0';

        wait for 4*clk_period;
        wait until rising_edge(clk);

        -- Cacheable read of address 4
        report "cache read address 4...";
        d_in.load <= '1';
        d_in.nc <= '0';
        d_in.addr <= x"0000000000000004";
        d_in.valid <= '1';
        wait until rising_edge(clk) and stall = '0';
        d_in.valid <= '0';

        wait until rising_edge(clk) and d_out.valid = '1';
        assert d_out.data = x"0000000100000000"
            report "data @" & to_hstring(d_in.addr) &
            "=" & to_hstring(d_out.data) &
            " expected 0000000100000000"
            severity failure;

        -- Cacheable read of address 30 (hit after hit forward from reload)
        report "cache read address 30...";
        d_in.load <= '1';
        d_in.nc <= '0';
        d_in.addr <= x"0000000000000030";
        d_in.valid <= '1';
        wait until rising_edge(clk) and stall = '0';
        d_in.valid <= '0';

        wait until rising_edge(clk) and d_out.valid = '1';
        assert d_out.data = x"0000000D0000000C"
            report "data @" & to_hstring(d_in.addr) &
            "=" & to_hstring(d_out.data) &
            " expected 0000000D0000000C"
            severity failure;

        -- Ensure reload completes
        wait for 100*clk_period;
        wait until rising_edge(clk);

        -- Cacheable read of address 38 (hit on idle cache)
        report "cache read address 38...";
        d_in.load <= '1';
        d_in.nc <= '0';
        d_in.addr <= x"0000000000000038";
        d_in.valid <= '1';
        wait until rising_edge(clk) and stall = '0';
        d_in.valid <= '0';

        wait until rising_edge(clk) and d_out.valid = '1';
        assert d_out.data = x"0000000F0000000E"
            report "data @" & to_hstring(d_in.addr) &
            "=" & to_hstring(d_out.data) &
            " expected 0000000F0000000E"
            severity failure;

        -- Cacheable read of address 130 (miss after hit, same index)
        -- This will use way 2
        report "cache read address 130...";
        d_in.load <= '1';
        d_in.nc <= '0';
        d_in.addr <= x"0000000000000130";
        d_in.valid <= '1';
        wait until rising_edge(clk) and stall = '0';
        d_in.valid <= '0';

        wait until rising_edge(clk) and d_out.valid = '1';
        assert d_out.data = x"0000004d0000004c"
            report "data @" & to_hstring(d_in.addr) &
            "=" & to_hstring(d_out.data) &
            " expected 0000004d0000004c"
            severity failure;

        -- Ensure reload completes
        wait for 100*clk_period;
        wait until rising_edge(clk);

        -- Cacheable read again of address 130 (hit in idle cache)
        -- This should feed from way 2
        report "cache read address 130...";
        d_in.load <= '1';
        d_in.nc <= '0';
        d_in.addr <= x"0000000000000130";
        d_in.valid <= '1';
        wait until rising_edge(clk) and stall = '0';
        d_in.valid <= '0';

        wait until rising_edge(clk) and d_out.valid = '1';
        assert d_out.data = x"0000004d0000004c"
            report "data @" & to_hstring(d_in.addr) &
            "=" & to_hstring(d_out.data) &
            " expected 0000004d0000004c"
            severity failure;

        -- Cacheable read of address 40
        report "cache read address 40...";
        d_in.load <= '1';
        d_in.nc <= '0';
        d_in.addr <= x"0000000000000040";
        d_in.valid <= '1';
        wait until rising_edge(clk);
        d_in.valid <= '0';

        wait until rising_edge(clk) and d_out.valid = '1';
        assert d_out.data = x"0000001100000010"
            report "data @" & to_hstring(d_in.addr) &
            "=" & to_hstring(d_out.data) &
            " expected 0000001100000010"
            severity failure;

        -- Cacheable read of address 140 (miss after miss, same index)
        -- This should use way 2
        report "cache read address 140...";
        d_in.load <= '1';
        d_in.nc <= '0';
        d_in.addr <= x"0000000000000140";
        d_in.valid <= '1';
        wait until rising_edge(clk) and stall = '0';
        d_in.valid <= '0';

        wait until rising_edge(clk) and d_out.valid = '1';
        assert d_out.data = x"0000005100000050"
            report "data @" & to_hstring(d_in.addr) &
            "=" & to_hstring(d_out.data) &
            " expected 0000005100000050"
            severity failure;

        -- Non-cacheable read of address 200
        report "non-cache read address 200...";
        d_in.load <= '1';
        d_in.nc <= '1';
        d_in.addr <= x"0000000000000200";
        d_in.valid <= '1';
        wait until rising_edge(clk) and stall = '0';
        d_in.valid <= '0';
        wait until rising_edge(clk) and d_out.valid = '1';
        assert d_out.data = x"0000008100000080"
            report "data @" & to_hstring(d_in.addr) &
            "=" & to_hstring(d_out.data) &
            " expected 0000008100000080"
            severity failure;

        wait until rising_edge(clk);
        wait until rising_edge(clk);
        wait until rising_edge(clk);
        wait until rising_edge(clk);

        std.env.finish;
    end process;
end;