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

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

entity dram_tb is
    generic (
        DRAM_INIT_FILE : string  := "";
        DRAM_INIT_SIZE : natural := 0;
        L2_TRACE : boolean := false;
        LITEDRAM_TRACE : boolean := false
        );
end dram_tb;

architecture behave of dram_tb is
    signal clk, rst: std_logic;
    signal clk_in, soc_rst : std_ulogic;

    -- testbench signals
    constant clk_period : time := 10 ns;

    -- Sim DRAM
    signal wb_in : wishbone_master_out;
    signal wb_out : wishbone_slave_out;
    signal wb_ctrl_in : wb_io_master_out;

    subtype addr_t is std_ulogic_vector(wb_in.adr'left downto 0);
    subtype data_t is std_ulogic_vector(wb_in.dat'left downto 0);
    subtype sel_t  is std_ulogic_vector(wb_in.sel'left downto 0);

    -- Counter for acks
    signal acks : integer := 0;
    signal reset_acks : std_ulogic;

    -- Read data fifo
    signal rd_ready : std_ulogic := '0';
    signal rd_valid : std_ulogic;
    signal rd_data  : data_t;
begin

    dram: entity work.litedram_wrapper
        generic map(
            DRAM_ABITS => 24,
            DRAM_ALINES => 1,
            DRAM_DLINES => 16,
            DRAM_CKLINES => 1,
            DRAM_PORT_WIDTH => 128,
            PAYLOAD_FILE => DRAM_INIT_FILE,
            PAYLOAD_SIZE => DRAM_INIT_SIZE,
            TRACE => L2_TRACE,
            LITEDRAM_TRACE => LITEDRAM_TRACE
            )
        port map(
            clk_in              => clk_in,
            rst                 => rst,
            system_clk          => clk,
            system_reset        => soc_rst,
            pll_locked          => open,

            wb_in               => wb_in,
            wb_out              => wb_out,
            wb_ctrl_in          => wb_ctrl_in,
            wb_ctrl_out         => open,
            wb_ctrl_is_csr      => '0',
            wb_ctrl_is_init     => '0',

            init_done           => open,
            init_error          => open,

            ddram_a             => open,
            ddram_ba    => open,
            ddram_ras_n => open,
            ddram_cas_n => open,
            ddram_we_n  => open,
            ddram_cs_n  => open,
            ddram_dm    => open,
            ddram_dq    => open,
            ddram_dqs_p => open,
            ddram_dqs_n => open,
            ddram_clk_p => open,
            ddram_clk_n => open,
            ddram_cke   => open,
            ddram_odt   => open,
            ddram_reset_n       => open
            );

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

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

    wb_ctrl_in.cyc <= '0';
    wb_ctrl_in.stb <= '0';

    -- Read data receive queue
    data_queue: entity work.sync_fifo
        generic map (
            DEPTH => 16,
            WIDTH => rd_data'length
            )
        port map (
            clk      => clk,
            reset    => soc_rst or reset_acks,
            rd_ready => rd_ready,
            rd_valid => rd_valid,
            rd_data  => rd_data,
            wr_ready => open,
            wr_valid => wb_out.ack,
            wr_data  => wb_out.dat
            );

    recv_acks: process(clk)
    begin
        if rising_edge(clk) then
            if rst = '1' or reset_acks = '1' then
                acks <= 0;
            elsif wb_out.ack = '1' then
                acks <= acks + 1;
--                report "WB ACK ! DATA=" & to_hstring(wb_out.dat);
            end if;
        end if;
    end process;

    sim: process
        procedure wb_write(addr: addr_t; data: data_t; sel: sel_t) is
        begin
            wb_in.adr <= addr;
            wb_in.sel <= sel;
            wb_in.dat <= data;
            wb_in.we  <= '1';
            wb_in.stb <= '1';
            wb_in.cyc <= '1';
            loop
                wait until rising_edge(clk);
                if wb_out.stall = '0' then
                    wb_in.stb <= '0';
                    exit;
                end if;
            end loop;
        end procedure;

        procedure wb_read(addr: addr_t) is
        begin
            wb_in.adr <= addr;
            wb_in.sel <= x"ff";
            wb_in.we  <= '0';
            wb_in.stb <= '1';
            wb_in.cyc <= '1';
            loop
                wait until rising_edge(clk);
                if wb_out.stall = '0' then
                    wb_in.stb <= '0';
                    exit;
                end if;
            end loop;
        end procedure;

        procedure wait_acks(count: integer) is
        begin
            wait until acks = count;
            wait until rising_edge(clk);
        end procedure;

        procedure clr_acks is
        begin
            reset_acks <= '1';
            wait until rising_edge(clk);
            reset_acks <= '0';
        end procedure;

        procedure read_data(data: out data_t) is
        begin
            assert rd_valid = '1' report "No data to read" severity failure;
            rd_ready <= '1';
            wait until rising_edge(clk);
            rd_ready <= '0';
            data := rd_data;
        end procedure;

        function add_off(a: addr_t; off: integer) return addr_t is
        begin
            return addr_t(unsigned(a) + off);
        end function;

        function make_pattern(num : integer) return data_t is
            variable r : data_t;
            variable t,b : integer;
        begin
            for i in 0 to (data_t'length/8)-1 loop
                t := (i+1)*8-1;
                b := i*8;
                r(t downto b) := std_ulogic_vector(to_unsigned(num+1, 8));
            end loop;
            return r;
        end function;

        procedure check_data(p: data_t) is
            variable d : data_t;
        begin
            read_data(d);
            assert d = p report "bad data, want " & to_hstring(p) &
                    " got " & to_hstring(d) severity failure;
        end procedure;

        variable a : addr_t := (others => '0');
        variable d : data_t := (others => '0');
        variable d1 : data_t := (others => '0');
    begin
        reset_acks <= '0';
        rst <= '1';
        wait until rising_edge(clk_in);
        wait until rising_edge(clk_in);
        wait until rising_edge(clk_in);
        wait until rising_edge(clk_in);
        wait until rising_edge(clk_in);
        rst <= '0';
        wait until rising_edge(clk_in);
        wait until soc_rst = '0';
        wait until rising_edge(clk);

        report "Simple write miss...";
        clr_acks;
        wb_write(a, x"0123456789abcdef", x"ff");
        wait_acks(1);

        report "Simple read miss...";
        clr_acks;
        wb_read(a);
        wait_acks(1);
        read_data(d);
        assert d = x"0123456789abcdef" report "bad data, got " & to_hstring(d) severity failure;

        report "Simple read hit...";
        clr_acks;
        wb_read(a);
        wait_acks(1);
        read_data(d);
        assert d = x"0123456789abcdef" report "bad data, got " & to_hstring(d) severity failure;

        report "Back to back 4 stores 4 reads on hit...";
        clr_acks;
        for i in 0 to 3 loop
            wb_write(add_off(a, i), make_pattern(i), x"ff");
        end loop;
        for i in 0 to 3 loop
            wb_read(add_off(a, i));
        end loop;
        wait_acks(8);
        for i in 0 to 7 loop
            if i < 4 then
                read_data(d);
            else
                check_data(make_pattern(i-4));
            end if;
        end loop;

        report "Back to back 4 stores 4 reads on miss...";
        a(10) := '1';
        clr_acks;
        for i in 0 to 3 loop
            wb_write(add_off(a, i), make_pattern(i), x"ff");
        end loop;
        for i in 0 to 3 loop
            wb_read(add_off(a, i));
        end loop;
        wait_acks(8);
        for i in 0 to 7 loop
            if i < 4 then
                read_data(d);
            else
                check_data(make_pattern(i-4));
            end if;
        end loop;

        report "Back to back interleaved 4 stores 4 reads on hit...";
        a(10) := '1';
        clr_acks;
        for i in 0 to 3 loop
            wb_write(add_off(a, i), make_pattern(i), x"ff");
            wb_read(add_off(a, i));
        end loop;
        wait_acks(8);
        for i in 0 to 3 loop
            read_data(d);
            check_data(make_pattern(i));
        end loop;

        report "Pre-fill a line";
        a(11) := '1';
        clr_acks;
        wb_write(add_off(a,  0), x"1111111100000000", x"ff");
        wb_write(add_off(a,  1), x"3333333322222222", x"ff");
        wb_write(add_off(a,  2), x"5555555544444444", x"ff");
        wb_write(add_off(a,  3), x"7777777766666666", x"ff");
        wb_write(add_off(a,  4), x"9999999988888888", x"ff");
        wb_write(add_off(a,  5), x"bbbbbbbbaaaaaaaa", x"ff");
        wb_write(add_off(a,  6), x"ddddddddcccccccc", x"ff");
        wb_write(add_off(a,  7), x"ffffffffeeeeeeee", x"ff");
        wb_write(add_off(a,  8), x"1111111100000000", x"ff");
        wb_write(add_off(a,  9), x"3333333322222222", x"ff");
        wb_write(add_off(a, 10), x"5555555544444444", x"ff");
        wb_write(add_off(a, 11), x"7777777766666666", x"ff");
        wb_write(add_off(a, 12), x"9999999988888888", x"ff");
        wb_write(add_off(a, 13), x"bbbbbbbbaaaaaaaa", x"ff");
        wb_write(add_off(a, 14), x"ddddddddcccccccc", x"ff");
        wb_write(add_off(a, 15), x"ffffffffeeeeeeee", x"ff");
        wait_acks(16);

        report "Scattered from middle of line...";
        clr_acks;
        wb_read(add_off(a, 3));
        wb_read(add_off(a, 4));
        wb_read(add_off(a, 0));
        wb_read(add_off(a, 2));
        wait_acks(4);
        read_data(d);
        assert d = x"7777777766666666" report "bad data (24), got " & to_hstring(d) severity failure;
        read_data(d);
        assert d = x"9999999988888888" report "bad data (32), got " & to_hstring(d) severity failure;
        read_data(d);
        assert d = x"1111111100000000" report "bad data (0), got " & to_hstring(d) severity failure;
        read_data(d);
        assert d = x"5555555544444444" report "bad data (16), got " & to_hstring(d) severity failure;

        std.env.finish;
    end process;
end architecture;
