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 cam2 IS
	Generic
	(
		NUM_ENTRIES			: natural := 32;
		ENTRY_WIDTH			: natural := 32;
		DATA_WIDTH			: natural := 20;
		REGISTERED_RTAG		: boolean := true;
		REGISTERED_RESULT	: boolean := false;
		CAM_RAM_MAX_WIDTH	: natural := 8
	);
	Port
	(
		rst				: in  STD_LOGIC;
		clk				: 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);
		entry_din		: in  unsigned (ENTRY_WIDTH-1 downto 0);
		entry_dout		: out unsigned (ENTRY_WIDTH-1 downto 0);

		tag_wr			: in  unsigned (DATA_WIDTH-1 downto 0);
		tag_rd			: in  unsigned (DATA_WIDTH-1 downto 0);

		hit_vld			: out STD_LOGIC

	);
END cam2;

ARCHITECTURE behavior OF cam2 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(2*DATA_WIDTH)/real(CAM_RAM_MAX_WIDTH))-1;
	constant BLOCK_ADDR_WIDTH				: integer := CAM_RAM_MAX_WIDTH;
	
	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);
		entry_data	: unsigned (ENTRY_WIDTH downto 0);
	end record;
	
	signal read_tag							: unsigned (DATA_WIDTH-1 downto 0);
	signal read_en							: std_logic;

	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 cam_ram_re						: std_logic;
	signal cam_ram_we						: std_logic;
	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_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;
	
	type cam_stage_addr_t	is array(0 to NUM_BLOCKS-1) of unsigned (CAM_RAM_MAX_WIDTH-1 downto 0);
	signal cam_stage_raddr					: cam_stage_addr_t;
	signal cam_stage_waddr					: cam_stage_addr_t;

	type cam_stage_data_t	is array(0 to NUM_BLOCKS-1) of unsigned (CAM_RAM_MAX_WIDTH/2-1 downto 0);
	signal cam_stage_din					: cam_stage_data_t;
	signal cam_stage_dout					: cam_stage_data_t;

	signal final_stage_we					: std_logic;
	signal final_stage_din					: unsigned (ENTRY_WIDTH downto 0);
	signal final_stage_dout					: unsigned (ENTRY_WIDTH downto 0);
	signal final_stage_wptr					: unsigned (CAM_RAM_MAX_WIDTH-1 downto 0);
	signal final_stage_rptr					: unsigned (CAM_RAM_MAX_WIDTH-1 downto 0);

begin



	rdy							<= not busy;
	over_write					<= track_ram_dout(DATA_WIDTH) and pipe1.we;

	--------------------------------------------------------
	final_stage_we				<= clear_count_en or (pipe2.we and pipe2.vld) or over_write;
	final_stage_wptr			<= clear_addr when clear_count_en = '1' else cam_stage_waddr(NUM_BLOCKS-1);
	final_stage_rptr			<= cam_stage_raddr(NUM_BLOCKS-1);
	final_stage_din				<= (others => '0') when (clear_count_en or over_write) = '1' else pipe2.entry_data;

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

	track_ram_re				<= '1';
	track_ram_we				<= clear_count_en or pipe1.we;
	track_ram_addr_rd			<= pipe0.addr;
	track_ram_addr_wr			<= clear_addr(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);

	pipe0.re			<= read_en and not busy;
	pipe0.vld			<= vld;
	pipe0.we			<= we and not busy;
	pipe0.addr			<= addr;
	pipe0.data			<= tag_wr;
	pipe0.entry_data	<= vld & entry_din;

	
gen_registered_rtag:
if REGISTERED_RTAG = true generate
registered_rtag:
	process(clk)
	begin
		if rising_edge(clk) then
			read_en							<= re;
			if re = '1' then
				read_tag					<= tag_rd;
			end if;
		end if;
	end process;
						
end generate;
gen_non_registered_rtag:
if REGISTERED_RTAG = false generate
	read_en							<= re;
	read_tag						<= read_tag;
end generate;

gen_registered_output:
if REGISTERED_RESULT = true generate
registered_output:
	process(clk)
	begin
		if rising_edge(clk) then
			if read_en = '1' then
				entry_dout					<= final_stage_dout(ENTRY_WIDTH-1 downto 0);
				hit_vld						<= final_stage_dout(ENTRY_WIDTH);
			end if;
		end if;
	end process;
end generate;
gen_non_registered_output:
if REGISTERED_RESULT = false generate
	entry_dout					<= final_stage_dout(ENTRY_WIDTH-1 downto 0);
	hit_vld						<= final_stage_dout(ENTRY_WIDTH);
end generate;

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;	

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
	);

	--------------------------------------------------------
	cam_stage_raddr(0)			<= read_tag(DATA_WIDTH-1 downto DATA_WIDTH-CAM_RAM_MAX_WIDTH);
	cam_stage_waddr(0)			<= cam_ram_addr_wr(DATA_WIDTH-1 downto DATA_WIDTH-CAM_RAM_MAX_WIDTH);
	cam_stage_din(0)			<= cam_ram_addr_wr(DATA_WIDTH-CAM_RAM_MAX_WIDTH/2-1 downto DATA_WIDTH-CAM_RAM_MAX_WIDTH);

gen_cam_stage_addresses:
for i in 1 to NUM_BLOCKS-1 generate
	cam_stage_raddr(i)	<= to_mips_01(cam_stage_dout(i-1)) & read_tag(DATA_WIDTH-(i+1)*CAM_RAM_MAX_WIDTH/2-1 downto DATA_WIDTH-(i+2)*CAM_RAM_MAX_WIDTH/2);
	cam_stage_waddr(i)	<= cam_ram_addr_wr(DATA_WIDTH-i*CAM_RAM_MAX_WIDTH/2-1 downto DATA_WIDTH-(i+2)*CAM_RAM_MAX_WIDTH/2);
	cam_stage_din(i)	<= cam_ram_addr_wr(DATA_WIDTH-(i+1)*CAM_RAM_MAX_WIDTH/2-1 downto DATA_WIDTH-(i+2)*CAM_RAM_MAX_WIDTH/2);
end generate;

gen_cam_stages:
for i in 0 to NUM_BLOCKS-2 generate
	
inst_cam_stage: entity work.dpram_1w1r1wc
	GENERIC MAP
	(
		addr_width	=>	CAM_RAM_MAX_WIDTH,
		data_width	=>	CAM_RAM_MAX_WIDTH/2
	)
	PORT MAP
	(
		clk_w		=>	clk,
		we			=>	cam_ram_we,
		wptr		=>	cam_stage_waddr(i),
		rptr		=>	cam_stage_raddr(i),
		din			=>	cam_stage_din(i),
		dout		=>	cam_stage_dout(i)
	);

end generate;

inst_cam_final_stage: entity work.dpram_1w1r1wc
	GENERIC MAP
	(
		addr_width	=>	CAM_RAM_MAX_WIDTH,
		data_width	=>	1+ENTRY_WIDTH
	)
	PORT MAP
	(
		clk_w		=>	clk,
		we			=>	final_stage_we,
		wptr		=>	final_stage_wptr,
		rptr		=>	final_stage_rptr,
		din			=>	final_stage_din,
		dout		=>	final_stage_dout
	);

------------------------------------------------------------------
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;
