----------------------------------------------------------------------
-- Project: Leaf
-- Developed by: Daniel Santos
-- Module: Simulator memory with wishbone interface.
-- Date: 2026
----------------------------------------------------------------------

library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use work.leaf_tb_pkg.all;

entity wb_ram is
    generic (
        PROGRAM   : string;
        DUMP_FILE : string
    );
    port (
        clk_i : in  std_logic;
        rst_i : in  std_logic;
        dat_i : in  std_logic_vector(31 downto 0);
        cyc_i : in  std_logic;
        stb_i : in  std_logic;
        we_i  : in  std_logic;
        sel_i : in  std_logic_vector(3  downto 0);
        adr_i : in  std_logic_vector(31 downto 0);
        ack_o : out std_logic;
        dat_o : out std_logic_vector(31 downto 0);
        wr_mem_i : in std_logic;
        rd_mem_i : in std_logic;
        halt_o   : out std_logic
    );
end entity wb_ram;

architecture arch of wb_ram is

    constant MEM_BASE_ADDR : unsigned(29 downto 0) := unsigned(RESET_ADDR(31 downto 2));

    signal addr : integer;

    -- idle state --
    signal idle : std_logic;

    -- enable signals --
    signal mem0_en : std_logic;
    signal mem1_en : std_logic;
    signal mem2_en : std_logic;
    signal mem3_en : std_logic;

    -- write enable signals --
    signal mem0_we : std_logic;
    signal mem1_we : std_logic;
    signal mem2_we : std_logic;
    signal mem3_we : std_logic;

    -- read enable signals --
    signal mem0_re : std_logic;
    signal mem1_re : std_logic;
    signal mem2_re : std_logic;
    signal mem3_re : std_logic;

    procedure dump_memory (
        constant file_path : in string;
        variable memory    : in memory_array
    ) is
        variable dump_start : integer;
        variable dump_stop  : integer;
    begin
        dump_start := to_integer(unsigned(memory(DUMP_START_ADDR)(31 downto 2))-MEM_BASE_ADDR);
        dump_stop  := to_integer(unsigned(memory(DUMP_STOP_ADDR)(31 downto 2))-MEM_BASE_ADDR) - 1;
        if dump_stop >= dump_start and dump_stop < MEM_SIZE/4 then
            write_memory(file_path, memory(dump_start to dump_stop));
        end if;
    end procedure;

begin

    addr <= to_integer(unsigned(adr_i(31 downto 2))-MEM_BASE_ADDR);
    
    idle_reg: process(clk_i)
    begin
        if rising_edge(clk_i) then
            if rst_i = '1' then
                idle <= '1';
            elsif idle = '1' then
                idle <= not (cyc_i and stb_i);
            else
                idle <= '1';
            end if;
        end if;
    end process idle_reg;

    mem0_en <= idle and cyc_i and stb_i and sel_i(0);
    mem1_en <= idle and cyc_i and stb_i and sel_i(1);
    mem2_en <= idle and cyc_i and stb_i and sel_i(2);
    mem3_en <= idle and cyc_i and stb_i and sel_i(3);

    mem0_we <= mem0_en and we_i;
    mem1_we <= mem1_en and we_i;
    mem2_we <= mem2_en and we_i;
    mem3_we <= mem3_en and we_i;

    mem0_re <= mem0_en and not we_i;
    mem1_re <= mem1_en and not we_i;
    mem2_re <= mem2_en and not we_i;
    mem3_re <= mem3_en and not we_i;

    write_mem: process(clk_i)

        variable mem : memory_array(0 to MEM_SIZE/4-1);

    begin
        if rising_edge(clk_i) then
            if rst_i = '1' then
                if rd_mem_i = '1' then
                    read_memory(PROGRAM, mem);
                end if;
                dat_o <= (others => '0');
                halt_o <= '0';
            else
                if addr < mem'length then
                    if mem0_we = '1' then
                        mem(addr)(7  downto 0) := dat_i(7  downto 0);
                    end if;
                    if mem1_we = '1' then
                        mem(addr)(15 downto 8) := dat_i(15 downto 8);
                    end if;
                    if mem2_we = '1' then
                        mem(addr)(23 downto 16) := dat_i(23 downto 16);
                    end if;
                    if mem3_we = '1' then
                        mem(addr)(31 downto 24) := dat_i(31 downto 24);
                    end if;
                    if mem0_re = '1' then
                        dat_o(7  downto  0) <= mem(addr)(7  downto  0);
                    end if;
                    if mem1_re = '1' then
                        dat_o(15 downto  8) <= mem(addr)(15 downto  8);
                    end if;
                    if mem2_re = '1' then
                        dat_o(23 downto 16) <= mem(addr)(23 downto 16);
                    end if;
                    if mem3_re = '1' then
                        dat_o(31 downto 24) <= mem(addr)(31 downto 24);
                    end if;
                end if;
            end if;
            if mem(HALT_CMD_ADDR) = HALT_CMD_DATA then
                halt_o <= '1';
            else
                halt_o <= '0';
            end if;
            if wr_mem_i = '1' then
                dump_memory(DUMP_FILE, mem);
            end if;
        end if;
    end process write_mem;

    ack_o  <= not idle;

end architecture arch;
