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

library work;
use work.wishbone_types.all;
use work.sim_console.all;

entity litedram_wrapper is
    generic (
	DRAM_ABITS     : positive;
	DRAM_ALINES    : positive
	);
    port(
	-- LiteDRAM generates the system clock and reset
	-- from the input clkin
	clk_in        : in std_ulogic;
	rst           : in std_ulogic;
	system_clk    : out std_ulogic;
	system_reset  : out std_ulogic;
	core_alt_reset : out std_ulogic;
	pll_locked    : out std_ulogic;

	-- Wishbone ports:
	wb_in         : in wishbone_master_out;
	wb_out        : out wishbone_slave_out;
	wb_is_csr     : in std_ulogic;
	wb_is_init    : in std_ulogic;

	-- Init core serial debug
	serial_tx     : out std_ulogic;
	serial_rx     : in std_ulogic;

	-- Misc
	init_done     : out std_ulogic;
	init_error    : out std_ulogic;

	-- DRAM wires
	ddram_a       : out std_ulogic_vector(DRAM_ALINES-1 downto 0);
	ddram_ba      : out std_ulogic_vector(2 downto 0);
	ddram_ras_n   : out std_ulogic;
	ddram_cas_n   : out std_ulogic;
	ddram_we_n    : out std_ulogic;
	ddram_cs_n    : out std_ulogic;
	ddram_dm      : out std_ulogic_vector(1 downto 0);
	ddram_dq      : inout std_ulogic_vector(15 downto 0);
	ddram_dqs_p   : inout std_ulogic_vector(1 downto 0);
	ddram_dqs_n   : inout std_ulogic_vector(1 downto 0);
	ddram_clk_p   : out std_ulogic;
	ddram_clk_n   : out std_ulogic;
	ddram_cke     : out std_ulogic;
	ddram_odt     : out std_ulogic;
	ddram_reset_n : out std_ulogic
	);
end entity litedram_wrapper;

architecture behaviour of litedram_wrapper is

    component litedram_core port (
	clk                    : in std_ulogic;
	rst                    : in std_ulogic;
	pll_locked             : out std_ulogic;
	ddram_a                : out std_ulogic_vector(DRAM_ALINES-1 downto 0);
	ddram_ba               : out std_ulogic_vector(2 downto 0);
	ddram_ras_n            : out std_ulogic;
	ddram_cas_n            : out std_ulogic;
	ddram_we_n             : out std_ulogic;
	ddram_cs_n             : out std_ulogic;
	ddram_dm               : out std_ulogic_vector(1 downto 0);
	ddram_dq               : inout std_ulogic_vector(15 downto 0);
	ddram_dqs_p            : inout std_ulogic_vector(1 downto 0);
	ddram_dqs_n            : inout std_ulogic_vector(1 downto 0);
	ddram_clk_p            : out std_ulogic;
	ddram_clk_n            : out std_ulogic;
	ddram_cke              : out std_ulogic;
	ddram_odt              : out std_ulogic;
	ddram_reset_n          : out std_ulogic;
	init_done              : out std_ulogic;
	init_error             : out std_ulogic;
	user_clk               : out std_ulogic;
	user_rst               : out std_ulogic;
	csr_port0_adr          : in std_ulogic_vector(13 downto 0);
	csr_port0_we           : in std_ulogic;
	csr_port0_dat_w        : in std_ulogic_vector(31 downto 0);
	csr_port0_dat_r        : out std_ulogic_vector(31 downto 0);
	user_port_native_0_cmd_valid   : in std_ulogic;
	user_port_native_0_cmd_ready   : out std_ulogic;
	user_port_native_0_cmd_we      : in std_ulogic;
	user_port_native_0_cmd_addr    : in std_ulogic_vector(DRAM_ABITS-1 downto 0);
	user_port_native_0_wdata_valid : in std_ulogic;
	user_port_native_0_wdata_ready : out std_ulogic;
	user_port_native_0_wdata_we    : in std_ulogic_vector(15 downto 0);
	user_port_native_0_wdata_data  : in std_ulogic_vector(127 downto 0);
	user_port_native_0_rdata_valid : out std_ulogic;
	user_port_native_0_rdata_ready : in std_ulogic;
	user_port_native_0_rdata_data  : out std_ulogic_vector(127 downto 0)
	);
    end component;
    
    signal user_port0_cmd_valid		: std_ulogic;
    signal user_port0_cmd_ready		: std_ulogic;
    signal user_port0_cmd_we		: std_ulogic;
    signal user_port0_cmd_addr		: std_ulogic_vector(DRAM_ABITS-1 downto 0);
    signal user_port0_wdata_valid	: std_ulogic;
    signal user_port0_wdata_ready	: std_ulogic;
    signal user_port0_wdata_we		: std_ulogic_vector(15 downto 0);
    signal user_port0_wdata_data	: std_ulogic_vector(127 downto 0);
    signal user_port0_rdata_valid	: std_ulogic;
    signal user_port0_rdata_ready	: std_ulogic;
    signal user_port0_rdata_data	: std_ulogic_vector(127 downto 0);

    signal ad3                          : std_ulogic;

    signal dram_user_reset              : std_ulogic;

    signal csr_port0_adr                : std_ulogic_vector(13 downto 0);
    signal csr_port0_we                 : std_ulogic;
    signal csr_port0_dat_w              : std_ulogic_vector(31 downto 0);
    signal csr_port0_dat_r              : std_ulogic_vector(31 downto 0);
    signal csr_port_read_comb           : std_ulogic_vector(63 downto 0);
    signal csr_valid	                : std_ulogic;
    signal csr_write_valid	        : std_ulogic;

    signal wb_init_in                   : wishbone_master_out;
    signal wb_init_out                  : wishbone_slave_out;

    type state_t is (CMD, MWRITE, MREAD, CSR);
    signal state : state_t;

    constant INIT_RAM_SIZE : integer := 16384;
    constant INIT_RAM_ABITS :integer := 14;
    constant INIT_RAM_FILE : string := "litedram_core.init";

    type ram_t is array(0 to (INIT_RAM_SIZE / 8) - 1) of std_logic_vector(63 downto 0);

    impure function init_load_ram(name : string) return ram_t is
	file ram_file : text open read_mode is name;
	variable temp_word : std_logic_vector(63 downto 0);
	variable temp_ram : ram_t := (others => (others => '0'));
	variable ram_line : line;
    begin
	for i in 0 to (INIT_RAM_SIZE/8)-1 loop
	    exit when endfile(ram_file);
	    readline(ram_file, ram_line);
	    hread(ram_line, temp_word);
	    temp_ram(i) := temp_word;
	end loop;
	return temp_ram;
    end function;

    signal init_ram : ram_t := init_load_ram(INIT_RAM_FILE);

    attribute ram_style : string;
    attribute ram_style of init_ram: signal is "block";

begin

    -- BRAM Memory slave
    init_ram_0: process(system_clk)
	variable adr : integer;
    begin
	if rising_edge(system_clk) then
	    wb_init_out.ack <= '0';
	    if (wb_init_in.cyc and wb_init_in.stb) = '1' then
		adr := to_integer((unsigned(wb_init_in.adr(INIT_RAM_ABITS-1 downto 3))));
		if wb_init_in.we = '0' then
		    wb_init_out.dat <= init_ram(adr);
		else
		    for i in 0 to 7 loop
			if wb_init_in.sel(i) = '1' then
			    init_ram(adr)(((i + 1) * 8) - 1 downto i * 8) <=
				wb_init_in.dat(((i + 1) * 8) - 1 downto i * 8);
			end if;
		    end loop;
		end if;
		wb_init_out.ack <= not wb_init_out.ack;
	    end if;
	end if;
    end process;

    wb_init_in.adr <= wb_in.adr;
    wb_init_in.dat <= wb_in.dat;
    wb_init_in.sel <= wb_in.sel;
    wb_init_in.we <= wb_in.we;
    wb_init_in.stb <= wb_in.stb;
    wb_init_in.cyc <= wb_in.cyc and wb_is_init;

   -- Address bit 3 selects the top or bottom half of the data
    -- bus (64-bit wishbone vs. 128-bit DRAM interface)
    --
    ad3 <= wb_in.adr(3);

    -- DRAM data interface signals
    user_port0_cmd_valid <= (wb_in.cyc and wb_in.stb and not wb_is_csr and not wb_is_init)
			    when state = CMD else '0';
    user_port0_cmd_we <= wb_in.we when state = CMD else '0';
    user_port0_wdata_valid <= '1' when state = MWRITE else '0';
    user_port0_rdata_ready <= '1' when state = MREAD else '0';
    user_port0_cmd_addr <= wb_in.adr(DRAM_ABITS+3 downto 4);
    user_port0_wdata_data <= wb_in.dat & wb_in.dat;
    user_port0_wdata_we <= wb_in.sel & "00000000" when ad3 = '1' else
			   "00000000" & wb_in.sel;

    -- DRAM CSR interface signals. We only support access to the bottom byte
    csr_valid <= wb_in.cyc and wb_in.stb and wb_is_csr;
    csr_write_valid <= wb_in.we and wb_in.sel(0);
    csr_port0_adr <= wb_in.adr(15 downto 2) when wb_is_csr = '1' else (others => '0');
    csr_port0_dat_w <= wb_in.dat(31 downto 0);
    csr_port0_we <= (csr_valid and csr_write_valid) when state = CMD else '0';

    -- Wishbone out signals
    wb_out.ack <= '1' when state = CSR else
		  wb_init_out.ack when wb_is_init = '1' else
		  user_port0_wdata_ready when state = MWRITE else
		  user_port0_rdata_valid when state = MREAD else '0';

    csr_port_read_comb <= x"00000000" & csr_port0_dat_r;
    wb_out.dat <= csr_port_read_comb when wb_is_csr = '1' else
		  wb_init_out.dat when wb_is_init = '1' else
		  user_port0_rdata_data(127 downto 64) when ad3 = '1' else
		  user_port0_rdata_data(63 downto 0);
    -- We don't do pipelining yet.
    wb_out.stall <= '0' when wb_in.cyc = '0' else not wb_out.ack;

    -- Reset ignored, the reset controller use the pll lock signal,
    -- and alternate core reset address set when DRAM is not initialized.
    --
    system_reset <= '0';
    core_alt_reset <= not init_done;

    -- State machine
    sm: process(system_clk)
    begin
	
	if rising_edge(system_clk) then
	    if dram_user_reset = '1' then
		state <= CMD;
	    else
		case state is
		when CMD =>
		    if csr_valid = '1' then
			state <= CSR;
		    elsif (user_port0_cmd_ready and user_port0_cmd_valid) = '1' then
			state <= MWRITE when wb_in.we = '1' else MREAD;
		    end if;
		when MWRITE =>
		    if user_port0_wdata_ready = '1' then
			state <= CMD;
		    end if;
		when MREAD =>
		    if user_port0_rdata_valid = '1' then
			state <= CMD;
		    end if;
		when CSR =>
		    state <= CMD;
		end case;
	    end if;
	end if;
    end process;

    litedram: litedram_core
	port map(
	    clk => clk_in,
	    rst => rst,
	    pll_locked => pll_locked,
	    ddram_a => ddram_a,
	    ddram_ba => ddram_ba,
	    ddram_ras_n => ddram_ras_n,
	    ddram_cas_n => ddram_cas_n,
	    ddram_we_n => ddram_we_n,
	    ddram_cs_n => ddram_cs_n,
	    ddram_dm => ddram_dm,
	    ddram_dq => ddram_dq,
	    ddram_dqs_p => ddram_dqs_p,
	    ddram_dqs_n => ddram_dqs_n,
	    ddram_clk_p => ddram_clk_p,
	    ddram_clk_n => ddram_clk_n,
	    ddram_cke => ddram_cke,
	    ddram_odt => ddram_odt,
	    ddram_reset_n => ddram_reset_n,
	    init_done => init_done,
	    init_error => init_error,
	    user_clk => system_clk,
	    user_rst => dram_user_reset,
	    csr_port0_adr => csr_port0_adr,
	    csr_port0_we => csr_port0_we,
	    csr_port0_dat_w => csr_port0_dat_w,
	    csr_port0_dat_r => csr_port0_dat_r,
	    user_port_native_0_cmd_valid => user_port0_cmd_valid,
	    user_port_native_0_cmd_ready => user_port0_cmd_ready,
	    user_port_native_0_cmd_we => user_port0_cmd_we,
	    user_port_native_0_cmd_addr => user_port0_cmd_addr,
	    user_port_native_0_wdata_valid => user_port0_wdata_valid,
	    user_port_native_0_wdata_ready => user_port0_wdata_ready,
	    user_port_native_0_wdata_we => user_port0_wdata_we,
	    user_port_native_0_wdata_data => user_port0_wdata_data,
	    user_port_native_0_rdata_valid => user_port0_rdata_valid,
	    user_port_native_0_rdata_ready => user_port0_rdata_ready,
	    user_port_native_0_rdata_data => user_port0_rdata_data
	    );

end architecture behaviour;