----------------------------------------------------------------------------------
-- Company: 
-- Engineer: Darcy Cook 
-- 
-- Create Date:    19:46:19 03/18/2009 
-- Design Name: 
-- Module Name:    memory_controller - Behavioral 
-- Project Name: 
-- Target Devices: 
-- Tool versions: 
-- Description: 
--
-- Dependencies: 
--
-- Revision: 
-- Revision 0.01 - File Created
-- Additional Comments: 
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

---- Uncomment the following library declaration if instantiating
---- any Xilinx primitives in this code.
--library UNISIM;
--use UNISIM.VComponents.all;

entity memory_controller is
	generic
	(    
		MAX_WAIT								 : integer					:= 100;
		NUM_MICROS                     : integer              := 2;		
		D_WIDTH                        : integer              := 16;
		A_WIDTH								 : integer					:= 16	
    
	);
	Port 
	( 
		clk : in std_logic;
		reset : in std_logic;
		micro_addr_buses : in  STD_LOGIC_VECTOR ((NUM_MICROS*A_WIDTH)-1 downto 0);
      micro_data_out_buses : out STD_LOGIC_VECTOR ((NUM_MICROS*D_WIDTH)-1 downto 0);
		micro_data_in_buses : in STD_LOGIC_VECTOR ((NUM_MICROS*D_WIDTH)-1 downto 0);
      micro_mem_requests : in  STD_LOGIC_VECTOR (NUM_MICROS-1 downto 0);
      micro_ack_ins : in  STD_LOGIC_VECTOR (NUM_MICROS-1 downto 0);
      micro_ack_outs : out  STD_LOGIC_VECTOR (NUM_MICROS-1 downto 0);
      micro_read_write : in  STD_LOGIC_VECTOR (NUM_MICROS-1 downto 0);
      mem_addr : out  STD_LOGIC_VECTOR (A_WIDTH-1 downto 0);
      mem_data : inout  STD_LOGIC_VECTOR (D_WIDTH-1 downto 0);
      mem_read_write : out  STD_LOGIC;
      mem_CE : out  STD_LOGIC
	);
end memory_controller;

architecture Behavioral of memory_controller is
	
	signal curr_processor : integer := 0;	
	signal mem_state : std_logic_vector(1 downto 0) := "00";
	
	signal curr_mem_req : std_logic := '0';
	signal curr_ack_in : std_logic := '0';
	signal curr_read_write : std_logic := '0';	
	signal internal_CE : std_logic := '0';
	signal write_data : STD_LOGIC_VECTOR (D_WIDTH-1 downto 0); 
	signal read_data : STD_LOGIC_VECTOR (D_WIDTH-1 downto 0);
	signal internal_RD_WR : std_logic := '0';
	signal internal_addr : STD_LOGIC_VECTOR (A_WIDTH-1 downto 0);

begin	
	
	mem_CE <= internal_CE; -- asynchronous	
	mem_read_write <= internal_RD_WR;
	mem_addr <= internal_addr;
	
	memory_controller_process : process( clk ) is
	variable wait_count : integer := 0;
		
	begin
		if (clk'event and clk = '1') then
			if (reset = '1') then
				mem_state <= "00";
				curr_processor <= 0;				
				internal_RD_WR <= '0';
				internal_CE <= '0';
				internal_addr <= (others => '0');
				micro_ack_outs <= (others => '0');
			else
				case mem_state is
					when "00" =>  -- this is the state where the memory controller is evaluating whether 
										-- the current processor has a memory request.												
												
						if (curr_mem_req = '1') then --check if there is a memory request
							
							--clock in the address, write data, and the RD/WR signal so they do not change during the memory access
							internal_addr <= micro_addr_buses(((curr_processor+1)*A_WIDTH)-1 downto curr_processor*A_WIDTH);
							internal_RD_WR <= curr_read_write;							
							internal_CE <= '1'; --enable memory
							mem_state <= "01";				

							if (curr_read_write = '1') then
								write_data <= micro_data_in_buses(((curr_processor+1)*D_WIDTH)-1 downto curr_processor*D_WIDTH);
							end if;
						else
							if (curr_processor = (NUM_MICROS-1)) then
								curr_processor <= 0;
							else
								curr_processor <= curr_processor + 1; -- check the next processor for memory requests  
							end if;							
						end if;											
					when "01" => --this is the clock cycle after a read or write request has been made, the 
									  -- action should be finished now (assumes memory clocks on negative edge).
						if (internal_RD_WR = '0') then
							-- the ack is not set yet for a read because one clock cycle is needed to ensure the value settles							
							read_data <= mem_data; --set read_data to the data on the bus
							mem_state <= "10";
						else --if this is a write then the mem controller can signal that we are finished
							micro_ack_outs(curr_processor) <= '1'; --ack to signal that the data is available to the micro
							wait_count := 0;
							mem_state <= "11";
						end if;						
					when "10" =>						
						micro_ack_outs(curr_processor) <= '1'; --ack to signal that the data is available to the micro
						wait_count := 0;
						mem_state <= "11";
					when "11" => -- this is the state where we are waiting for the micro to ack
						wait_count := wait_count + 1;
						
						-- if the micro ack'd the action, or if the timeout count was reached then we are finished
						if ((curr_ack_in = '1') or (wait_count = MAX_WAIT)) then
							micro_ack_outs(curr_processor) <= '0';
							internal_CE <= '0';							
							mem_state <= "00"; --wait for the next memory request							
						end if;					
					when others =>
						mem_state <= "00";					
				end case;
			end if;
		end if;
	end process memory_controller_process;
	
	-- this process multiplexes the data from the current processor
	micro_info_mux_process : process( read_data, curr_processor, micro_read_write, micro_mem_requests, micro_ack_ins ) is
	begin		
		--read_value <= mem_data;				
		curr_ack_in <= micro_ack_ins(curr_processor);		
		curr_mem_req <= micro_mem_requests(curr_processor);						
		curr_read_write <= micro_read_write(curr_processor);
		
		for i in 0 to NUM_MICROS-1 loop
			--if ((i /= curr_processor) or (micro_mem_requests(i) = '0')) then 
				--micro_data_out_buses(((i+1)*D_WIDTH)-1 downto i*D_WIDTH) <= (others => 'Z');
			--elsif (micro_read_write(i) = '1') then --if the micro is writing then read the data from the bus to write to memory
				--micro_data_out_buses(((i+1)*D_WIDTH)-1 downto i*D_WIDTH) <= (others => 'Z');				
			--else -- this is the case where the micro is reading data, so the data should be put on the bus
			micro_data_out_buses(((i+1)*D_WIDTH)-1 downto i*D_WIDTH) <= read_data;
			--end if;			
		end loop;
	end process micro_info_mux_process;
	
	tristate_mem_bus_process : process(internal_CE, curr_read_write, write_data ) is 
	begin
		if ((internal_CE = '1') and (curr_read_write = '1')) then --this is when a write occurs
			mem_data <= write_data; -- put the value on the data bus to write to memory
		else -- this is either the memory is inactive or a read is occuring
			mem_data <= (others => 'Z'); -- set the data bus to high impedance
			--read_data <= mem_data; --set read_data to the data on the bus
		end if;
	end process tristate_mem_bus_process;
	
end Behavioral;
