-------------------------------------------------------------------------
-- Project: JCPU, a portable 8-bit RISC CPU written in VHDL
-- This file: cpu_embedded using cpu_core and rom
--
-- Copyright (C) 2007 J. Ahrensfeld
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
--
-- For questions and ideas, please contact the author at jens@jayfield.org
--
--------------------------------------------------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.numeric_std.ALL;

use work.sdram_config.all;
use work.sdram_types.all;
use work.utils_pkg.all;

entity tb_sdr_ctrl is
end;

architecture struct of tb_sdr_ctrl is

	-- Number of user data words for simulation
	constant CLK_PERIOD		: time := 10 ns;
	constant BURST_LEN		: natural := 8;

	signal rst				: std_logic := '1';  
	signal clk				: std_logic := '0';  
	signal rst_out			: std_logic;  
	signal clk_out			: std_logic;  
	signal clk_fb			: std_logic;  
	signal part_clk_p		: std_logic;  
	signal part_clk_n		: std_logic;  
	signal part_cke			: std_logic;  
	signal part_cs_n		: std_logic;  
	signal part_we_n		: std_logic;  
	signal part_ras_n		: std_logic;  
	signal part_cas_n		: std_logic;  
	signal part_ba			: unsigned(DDR_BANK_WIDTH-1 downto 0) := (others => '0');
	signal part_dm			: unsigned(DDR_DM_WIDTH-1 downto 0) := (others => '0');
	signal part_dqs			: unsigned(DDR_DQS_WIDTH-1 downto 0) := (others => '0');
	signal part_addr		: unsigned(DDR_ADDR_WIDTH-1 downto 0) := (others => '0');
	signal part_data		: unsigned(DDR_DATA_WIDTH-1 downto 0) := (others => '0');

	signal u_addr			: user_addr_t := (others => '0');
	signal u_tag_in			: user_tag_t := "0000";
	signal u_tag_rd			: user_tag_t;
	signal u_tag_wr			: user_tag_t;
	signal u_cmd			: user_cmd_t := UCMD_NOP;
	signal u_cmd_we			: std_logic := '0';
	signal u_busy			: std_logic;
	signal u_data_w			: unsigned(2*DDR_DATA_WIDTH-1 downto 0) := (others => '0');
	signal u_dm				: unsigned(2*DDR_DM_WIDTH-1 downto 0) := "0000";
	signal u_dm_rd_in		: unsigned(2*DDR_DM_WIDTH-1 downto 0) := "0000";
	signal u_dm_rd_out		: unsigned(2*DDR_DM_WIDTH-1 downto 0) := "0000";
	signal u_data_r			: unsigned(2*DDR_DATA_WIDTH-1 downto 0) := (others => '0');
	signal u_data_vld		: std_logic;
	signal u_req_wr			: std_logic;
	signal u_req_rd			: std_logic;
	signal u_read_en		: std_logic := '0';
	signal u_write_en		: std_logic := '0';
	signal u_addr_cnt_en	: std_logic := '0';
	signal u_addr_load		: std_logic := '0';
	signal u_addr_preset	: user_addr_t := (others => '0');
	signal u_addr_end		: user_addr_t := (others => '0');
	signal ref_data			: unsigned(2*DDR_DATA_WIDTH-1 downto 0) := (others => '0');
	
	type test_phase_t is (IDLE, PART_INIT, WRITE_SINGLE, WRITE_MULTI, READ_SINGLE, READ_MULTI, FINISHED);
	signal test_phase : test_phase_t := IDLE;
	
	type u_addr_mode_t is (MONOTONIC, RANDOM);
	signal u_addr_mode		: u_addr_mode_t := MONOTONIC;
	signal cycle_cnt		: natural := 1;
	signal data_cnt			: natural := 0;
	signal efficiency		: real := 0.0;
	
begin 
   
	clk_fb	<= part_clk_p after 1 ns;

	-- DDR SDRAM Controller Core
	inst_sdram_ctrl : entity work.sdram_ctrl_top
	Generic map
	(
		BL 				=> BURST_LEN,
		read_phaseshift => 0,
		f_sysclk 		=> 100E6
	)
	Port map
	(
	
		sys_rst_in			=> rst,
		sys_clk_in			=> clk,

		sys_rst_out			=> rst_out,
		sys_clk_out			=> clk_out,
		sys_clk_fb			=> clk_fb,
		
		-- User interface
		u_data_vld			=> u_data_vld,
		u_data_req_w		=> u_req_wr,
		u_data_req_r		=> u_req_rd,
		u_tag_in			=> u_tag_in,
		u_tag_rd			=> u_tag_rd,
		u_tag_wr			=> u_tag_wr,
		u_busy				=> u_busy,
		u_addr				=> u_addr,
		u_cmd				=> u_cmd,
		u_cmd_we			=> u_cmd_we,
		u_data_wr			=> u_data_w,
		u_data_rd			=> u_data_r,
		u_dm_wr_in			=> u_dm,
		u_dm_rd_in			=> u_dm_rd_in,
		u_dm_rd_out			=> u_dm_rd_out,
		
		-- SDRAM signals
		sd_clk_p			=> part_clk_p,
		sd_clk_n			=> part_clk_n,
		sd_cke				=> part_cke,
		sd_cs_n				=> part_cs_n,
		sd_cas_n			=> part_cas_n,
		sd_ras_n			=> part_ras_n,
		sd_we_n				=> part_we_n,
		sd_addr				=> part_addr,
		sd_ba				=> part_ba,
		sd_dm				=> part_dm,
		sd_dqs				=> part_dqs,
		sd_data				=> part_data
		
	);
   
   -- MICRON DDR SDRAM Simulation Model
   i_mt46v16m16_0 : entity work.mt46v16m16 
      port map (
         dq    => std_logic_vector(part_data),
         dqs   => std_logic_vector(part_dqs),
         addr  => std_logic_vector(part_addr),
         ba    => std_logic_vector(part_ba),
         clk   => part_clk_p,
         clk_n => part_clk_n,
         cke   => part_cke,
         cs_n  => part_cs_n,
         ras_n => part_ras_n,
         cas_n => part_cas_n,
         we_n  => part_we_n,
         dm    => std_logic_vector(part_dm)
	);

CLK_GEN: process
	begin
		wait for CLK_PERIOD/2;
		clk <= not clk;
	end process;

------------------------------------------------------------------------------------------

R_DATA_COMPARE:
	process(clk_out)
	variable last_data : unsigned(7 downto 0) := (others => '0');
	begin
		if rising_edge(clk_out) then
			if u_data_vld='1' then
				if u_read_en = '1' then
					assert (ref_data = u_data_r) report "Error on read!" severity failure;
				end if;	
				for i in 0 to DDR_DATA_WIDTH/4-1 loop
					ref_data((i+1)*DDR_DATA_WIDTH/2-1 downto i*DDR_DATA_WIDTH/2) <= last_data + i + 1;
				end loop;
				last_data := last_data + DDR_DATA_WIDTH/4;
			elsif u_read_en = '0' then
				last_data := (others => '0');
				ref_data <= (others => '0');
			end if;	
		end if;	
	end process;

W_DATA_GEN:
	process(clk_out)
	variable last_data : unsigned(7 downto 0) := (others => '0');
	begin
		if rising_edge(clk_out) then
			if u_req_wr = '1' then
				for i in 0 to DDR_DATA_WIDTH/4-1 loop
					u_data_w((i+1)*DDR_DATA_WIDTH/2-1 downto i*DDR_DATA_WIDTH/2) <= last_data + i + 1;
				end loop;
				last_data := last_data + DDR_DATA_WIDTH/4;
			elsif u_write_en = '0' then
				last_data := (others => '0');
				u_data_w <= (others => '0');
			end if;	
		end if;	
	end process;

ADDR_GEN:
	process(clk_out)
	variable last_data : unsigned(7 downto 0) := (others => '0');
	begin
		if rising_edge(clk_out) then
			if u_addr_load = '1' then
				u_addr <= u_addr_preset;
			elsif u_addr_cnt_en = '1' then
				if u_addr_mode = MONOTONIC then
					u_addr <= u_addr + BURST_LEN;
				else
					if u_addr(u_addr'left) = '1' then
						if BURST_LEN = 2 then
							u_addr(u_addr'left downto NextExpBaseTwo(BURST_LEN)) <= (u_addr(u_addr'left-1 downto NextExpBaseTwo(BURST_LEN)) & u_addr(u_addr'left)) xor "10000000000010001010100";
						elsif BURST_LEN = 4 then
							u_addr(u_addr'left downto NextExpBaseTwo(BURST_LEN)) <= (u_addr(u_addr'left-1 downto NextExpBaseTwo(BURST_LEN)) & u_addr(u_addr'left)) xor "1000000000010011010110";
						elsif BURST_LEN = 8 then
							u_addr(u_addr'left downto NextExpBaseTwo(BURST_LEN)) <= (u_addr(u_addr'left-1 downto NextExpBaseTwo(BURST_LEN)) & u_addr(u_addr'left)) xor "100000000001011110100";
						end if;	
					else
						u_addr(u_addr'left downto NextExpBaseTwo(BURST_LEN)-1) <= u_addr(u_addr'left-1 downto NextExpBaseTwo(BURST_LEN)-1) & u_addr(u_addr'left);
					end if;
				end if;
			end if;	
		end if;	
	end process;

performance_count:
	process(clk_out)
	begin
		if rising_edge(clk_out) then
			if u_write_en = '1' or u_read_en = '1' then
				cycle_cnt <= cycle_cnt + 1;
				if u_req_wr = '1' or u_req_rd = '1' then
					data_cnt <= data_cnt + 1;
				end if;
			else
				cycle_cnt <= 1;
				data_cnt <= 0;
			end if;	
		end if;	
	end process;

	u_tag_in <= ("00" & u_write_en & u_read_en) when ((u_addr = X"333C98" or u_addr = X"000100") and u_cmd_we = '1') else "0000";

	efficiency	<= real(data_cnt)/real(cycle_cnt);
	u_cmd_we    <= (u_write_en or u_read_en) and (not u_busy);
	u_addr_cnt_en <= '1' when (u_cmd_we = '1' and (u_addr /= u_addr_end)) else '0';
	
	process
	begin
         
	-- Write MAX_WORDS to DDR SDRAM Controller
	wait for 4*CLK_PERIOD;
	rst <= '0';

	wait until rising_edge(clk_out) and u_busy='0';
	wait for 137*CLK_PERIOD;


	wait until rising_edge(clk_out) and u_busy='0';
	-- Bank 0
	u_addr_preset <= X"000000";
	u_addr_end <= X"008000"-BURST_LEN;
	u_addr_load <= '1';
	wait until rising_edge(clk_out);
	u_addr_load <= '0';
	wait until rising_edge(clk_out);
	u_cmd <= UCMD_WRITE;
	u_write_en <= '1';
	while u_addr /= u_addr_end loop
		wait until rising_edge(clk_out);
	end loop;
	u_write_en <= '0';
	wait until rising_edge(clk_out) and u_req_wr='0';
	
	wait until rising_edge(clk_out) and u_busy='0';
	-- Bank 1
	u_addr_preset <= X"400000";
	u_addr_end <= X"408000"-BURST_LEN;
	u_addr_load <= '1';
	wait until rising_edge(clk_out);
	u_addr_load <= '0';
	wait until rising_edge(clk_out);
	u_cmd <= UCMD_WRITE;
	u_write_en <= '1';
	while u_addr /= u_addr_end loop
		wait until rising_edge(clk_out);
	end loop;
	u_write_en <= '0';
	wait until rising_edge(clk_out) and u_req_wr='0';
	
	wait until rising_edge(clk_out) and u_busy='0';
	-- Bank 0
	u_addr_preset <= X"000000";
	u_addr_end <= X"008000"-BURST_LEN;
	u_addr_load <= '1';
	wait until rising_edge(clk_out);
	u_addr_load <= '0';
	wait until rising_edge(clk_out);
	u_cmd <= UCMD_READ;
	u_read_en <= '1';
	while u_addr /= u_addr_end loop
		wait until rising_edge(clk_out);
	end loop;
	u_read_en <= '0';
	wait until rising_edge(clk_out) and u_data_vld='0';

	wait until rising_edge(clk_out) and u_busy='0';
	-- Bank 1
	u_addr_preset <= X"400000";
	u_addr_end <= X"408000"-BURST_LEN;
	u_addr_load <= '1';
	wait until rising_edge(clk_out);
	u_addr_load <= '0';
	wait until rising_edge(clk_out);
	u_cmd <= UCMD_READ;
	u_read_en <= '1';
	while u_addr /= u_addr_end loop
		wait until rising_edge(clk_out);
	end loop;
	u_read_en <= '0';
	wait until rising_edge(clk_out) and u_data_vld='0';

	wait;
	
	end process;
	
end architecture struct;
