LIBRARY IEEE;
USE IEEE.MATH_REAL.ALL;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL;

library work;
use work.mips_types.all;
use work.mips_util_pkg.all;

ENTITY cam IS
	Generic
	(
		NUM_ENTRIES			: natural := 32;
		DATA_WIDTH			: natural := 20;
		CAM_RAM_MAX_WIDTH	: natural := 15
	);
	Port
	(
		rst				: in  STD_LOGIC;
		clk				: in  STD_LOGIC;
		clk_rd			: in  STD_LOGIC;
		
		rdy				: out STD_LOGIC;

		re				: in  STD_LOGIC;
		we				: in  STD_LOGIC;
		vld				: in  STD_LOGIC;
		addr			: in  unsigned (lg2(NUM_ENTRIES)-1 downto 0);
		tag_wr			: in  unsigned (DATA_WIDTH-1 downto 0);

		tag_rd			: in  unsigned (DATA_WIDTH-1 downto 0);
		hit				: out unsigned (NUM_ENTRIES-1 downto 0);
		hit_vld			: out STD_LOGIC

	);
END cam;

ARCHITECTURE behavior OF cam IS

	type debug_t is record
		num_cam_rams			: integer;
		cam_ram_data_width		: integer;
		cam_ram_addr_width		: integer;
	end record;
	
	signal debug : debug_t;
	
	constant NUM_BLOCKS						: integer := integer(real(DATA_WIDTH)/real(CAM_RAM_MAX_WIDTH-lg2(NUM_ENTRIES)));
	constant BLOCK_ADDR_WIDTH				: integer := CAM_RAM_MAX_WIDTH - lg2(NUM_ENTRIES);
	
	type state_t is (init, ready, clear);
	signal state							: state_t;
	signal state_next						: state_t;

	type pipe_stage_t is record
		re			: std_logic;
		we			: std_logic;
		vld			: std_logic;
		addr		: unsigned (lg2(NUM_ENTRIES)-1 downto 0);
		data		: unsigned (DATA_WIDTH-1 downto 0);
		vld_cam		: unsigned (NUM_ENTRIES-1 downto 0);
	end record;
	
	signal pipe0							: pipe_stage_t;
	signal pipe1							: pipe_stage_t;
	signal pipe2							: pipe_stage_t;
	signal pipe3							: pipe_stage_t;
	signal pipe0_rd							: pipe_stage_t;
	signal pipe1_rd							: pipe_stage_t;
	signal pipe2_rd							: pipe_stage_t;
	signal pipe3_rd							: pipe_stage_t;
	signal  busy							: std_logic;
	signal	vld_cam							: unsigned (NUM_ENTRIES-1 downto 0);
	
	signal  cam_ram_re						: std_logic;
	signal  cam_ram_we						: std_logic;
	
	type cam_ram_array_t is array (0 to NUM_BLOCKS-1) of unsigned (NUM_ENTRIES-1 downto 0);
	signal	cam_ram_din						: cam_ram_array_t;
	signal	cam_ram_dout					: cam_ram_array_t;
	signal	cam_ram_result					: unsigned (NUM_ENTRIES-1 downto 0);
	
	signal	cam_ram_addr_wr					: unsigned (DATA_WIDTH-1 downto 0);
	signal	cam_ram_addr_rd					: unsigned (DATA_WIDTH-1 downto 0);

	signal  track_ram_re					: std_logic;
	signal  track_ram_we					: std_logic;
	signal	track_ram_addr_wr				: unsigned (lg2(NUM_ENTRIES)-1 downto 0);
	signal	track_ram_addr_rd				: unsigned (lg2(NUM_ENTRIES)-1 downto 0);
	signal	track_ram_din					: unsigned (DATA_WIDTH downto 0);
	signal	track_ram_dout					: unsigned (DATA_WIDTH downto 0);

	signal clear_data						: unsigned (DATA_WIDTH-1 downto 0);
	signal clear_addr						: unsigned (BLOCK_ADDR_WIDTH-1 downto 0);
	signal clear_count_en					: std_logic;
	signal clear_count_rdy					: std_logic;
	signal over_write						: std_logic;
	
	
begin
	
	over_write					<= track_ram_dout(DATA_WIDTH) and pipe1.we;
	rdy							<= not busy;
	hit_vld						<= pipe1.re;
	
	debug.num_cam_rams			<= NUM_BLOCKS;
	debug.cam_ram_addr_width	<= BLOCK_ADDR_WIDTH;
	debug.cam_ram_data_width	<= NUM_ENTRIES;

	track_ram_re				<= '1';
	track_ram_we				<= clear_count_en or pipe1.we;
	track_ram_addr_rd			<= pipe0.addr;
	track_ram_addr_wr			<= clear_data(lg2(NUM_ENTRIES)-1 downto 0) when clear_count_en = '1' else pipe1.addr;
	track_ram_din				<= (others => '0') when clear_count_en = '1' else (pipe1.vld & pipe1.data);

	cam_ram_re					<= re when pipe1_rd.we = '0' else not busy;
	cam_ram_we					<= clear_count_en or (pipe2.we and pipe2.vld) or over_write;
	cam_ram_addr_rd				<= tag_rd when pipe1_rd.we = '0' else pipe1_rd.data;	-- clk_rd same edge of clk
--	cam_ram_addr_rd				<= tag_rd when pipe2_rd.we = '0' else pipe2_rd.data;	-- clk_rd opposite edge of clk
	cam_ram_addr_wr				<= clear_data when clear_count_en = '1' else track_ram_dout(DATA_WIDTH-1 downto 0) when over_write = '1' else pipe2.data;

combine_cam_ram_din:
	process(clear_count_en, over_write, pipe2, cam_ram_dout)
	begin
		for i in 0 to NUM_BLOCKS-1 loop
			if clear_count_en = '1' or over_write = '1' then
				cam_ram_din(i) <= (others => '0');
			else
				cam_ram_din(i) <= (((NUM_ENTRIES-1 downto 0 => pipe2.vld) and pipe2.vld_cam) xor cam_ram_dout(i));	
			end if;
		end loop;
	end process;

combine_clear_addr:
	process(clear_addr)
	begin
		for i in 0 to NUM_BLOCKS-1 loop
			clear_data((i+1)*BLOCK_ADDR_WIDTH-1 downto i*BLOCK_ADDR_WIDTH) <= clear_addr;
		end loop;
	end process;

combine_cam_ram_result:
	process(cam_ram_dout)
	variable result : unsigned (NUM_ENTRIES-1 downto 0);
	begin
		result := (others => '0');
		
		for i in 0 to NUM_BLOCKS-1 loop
			result := result or cam_ram_dout(i);
		end loop;
		
		cam_ram_result		<= result;
	end process;
	
	pipe0.re		<= re and not busy;
	pipe0.vld		<= vld;
	pipe0.we		<= we and not busy;
	pipe0.addr		<= addr;
	pipe0.data		<= tag_wr;
	pipe0.vld_cam	<= vld_cam;

pipe_line_wr:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				pipe3.we	<= '0';
				pipe2.we	<= '0';
				pipe1.we	<= '0';
			else
				pipe3	<= pipe2;
				pipe2	<= pipe1;
				pipe1	<= pipe0;
			end if;
		end if;
	end process;	

	pipe0_rd	<= pipe0;
pipe_line_rd:
	process(clk_rd)
	begin
		if rising_edge(clk_rd) then
			if rst = '1' then
				pipe3_rd.we	<= '0';
				pipe2_rd.we	<= '0';
				pipe1_rd.we	<= '0';
			else
				pipe3_rd	<= pipe2_rd;
				pipe2_rd	<= pipe1_rd;
				pipe1_rd	<= pipe0_rd;
			end if;
		end if;
	end process;	

combine_cam_hit:
	process(cam_ram_dout, clear_count_en)
	variable result : unsigned (NUM_ENTRIES-1 downto 0);
	begin
		result := (others => not clear_count_en);
		
		for i in 0 to NUM_BLOCKS-1 loop
			result := result and cam_ram_dout(i);
		end loop;
		
		hit			<= result;
	end process;
	
vld_decode:
process(vld, addr)
	begin
		vld_cam							<= (others => '0');
		vld_cam(to_integer(addr))		<= vld;
	end process;	


inst_track_ram: entity work.dpram_1w1r2c_ra
	GENERIC MAP 
	(
		addr_width	=>	lg2(NUM_ENTRIES),
		data_width	=>	DATA_WIDTH+1
	)
	PORT MAP
	(
		clk_a	=>	clk,
		clk_b	=>	clk,
		en_a	=>	'1',
		en_b	=>	'1',
		we_a	=>	track_ram_we,
		addr_a	=>	track_ram_addr_wr,
		addr_b	=>	track_ram_addr_rd,
		din_a	=>	track_ram_din,
		dout_b	=>	track_ram_dout
	);

gen_cam_ram:
for i in 0 to NUM_BLOCKS-1 generate
	
	inst_cam_ram: entity work.dpram_1w1r2c_ra
		GENERIC MAP 
		(
			addr_width	=>	BLOCK_ADDR_WIDTH,
			data_width	=>	NUM_ENTRIES
		)
		PORT MAP
		(
			clk_a	=>	clk,
			clk_b	=>	clk_rd,
			en_a	=>	'1',
			en_b	=>	cam_ram_re,
			we_a	=>	cam_ram_we,
			addr_a	=>	cam_ram_addr_wr((i+1)*BLOCK_ADDR_WIDTH-1 downto i*BLOCK_ADDR_WIDTH),
			addr_b	=>	cam_ram_addr_rd((i+1)*BLOCK_ADDR_WIDTH-1 downto i*BLOCK_ADDR_WIDTH),
			din_a	=>	cam_ram_din(i),
			dout_b	=>	cam_ram_dout(i)
		);

end generate;

cam_state_next:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				state		<= init;
			else
				state		<= state_next;
			end if;
		end if;
	end process;	

cam_state:
	process(state, clear_count_rdy, over_write)
	begin
		busy				<= over_write;
		clear_count_en		<= '0';
		
		state_next		<= state;
		
		case state is

		when init =>
			busy		<= '1';
			state_next <= clear;
			
		when clear =>
			busy		<= '1';
			clear_count_en	<= '1';
			if clear_count_rdy = '1' then
				state_next <= ready;
			end if;

		when ready =>

		when others => 
			state_next <= ready;
				
		end case;
		
	end process;

clear_counter:
	process(clk)
	begin
		if rising_edge(clk) then
			if clear_count_en = '0' then
				clear_count_rdy	<= '0';
				clear_addr		<= (BLOCK_ADDR_WIDTH-1 downto 0 => '1');
			else	
				if clear_addr /= (BLOCK_ADDR_WIDTH-1 downto 0 => '0') then
					clear_addr 	<= clear_addr - 1;
				else
					clear_count_rdy	<= '1';
				end if;
			end if;	
		end if;
	end process;	
	
------------------------------------------------------------------
end behavior;
