module icache #
(
	parameter DW = 128,
	parameter BK = 2,
	parameter CB = 2,
	parameter CL = 64
)
(
	//L1 I Cache
	output [31:0] IL1_ARADDR,
	output [7:0] IL1_ARLEN,
	output [1:0] IL1_ARBURST,
	output IL1_ARVALID,
	input IL1_ARREADY,

	input [63:0] IL1_RDATA,
	input [1:0] IL1_RRESP,
	input IL1_RLAST,
	input IL1_RVALID,
	output IL1_RREADY,

	//from pcGen
	input [63:0] pc_ic_addr,
	output pc_ic_ready,

	//to iqueue
	output [63:0] ic_iq_pc,
	output [63:0] ic_iq_instr,
	output ic_iq_valid,
	input ic_iq_ready,


	input il1_fence,

	input flush,
	input CLK,
	input RSTn

);

	localparam IL1_STATE_CFREE = 0;
	localparam IL1_STATE_CKTAG = 1;
	localparam IL1_STATE_CMISS = 2;
	localparam IL1_STATE_FENCE = 3;


	localparam ADDR_LSB = $clog2(DW*BK/8);
	localparam LINE_W = $clog2(CL); 
	localparam TAG_W = 32 - ADDR_LSB - LINE_W;

	wire [31:0] ic_addr_req;
	wire [63:0] ic_data_rsp;

	wire [63:0] ic_iq_pc_dnxt;
	wire [63:0] ic_iq_pc_qout;
	wire [63:0] ic_iq_instr_dnxt;
	wire [63:0] ic_iq_instr_qout;	
	wire ic_iq_valid_dnxt, ic_iq_valid_qout;

	assign ic_addr_req = pc_ic_addr[31:0] & (~32'b111);

	assign ic_iq_pc_dnxt = pc_ic_ready ? pc_ic_addr : ic_iq_pc_qout;
	assign ic_iq_instr_dnxt = pc_ic_ready ? ic_data_rsp : ic_iq_instr_qout;
	assign ic_iq_valid_dnxt = pc_ic_ready & ~flush;

	assign ic_iq_pc = ic_iq_pc_qout;
	assign ic_iq_instr = ic_iq_instr_qout;
	assign ic_iq_valid = ic_iq_valid_qout;


	gen_dffr # (.DW(64)) ic_iq_pc_dffr (.dnxt(ic_iq_pc_dnxt), .qout(ic_iq_pc_qout), .CLK(CLK), .RSTn(RSTn));
	gen_dffr # (.DW(64)) ic_iq_instr_dffr (.dnxt(ic_iq_instr_dnxt), .qout(ic_iq_instr_qout), .CLK(CLK), .RSTn(RSTn));
	gen_dffr # (.DW(1)) ic_iq_valid_dffr (.dnxt(ic_iq_valid_dnxt), .qout(ic_iq_valid_qout), .CLK(CLK), .RSTn(RSTn));












	wire il1_arvalid_set, il1_arvalid_rst, il1_arvalid_qout;
	wire il1_rready_set, il1_rready_rst, il1_rready_qout;
	wire il1_ar_req;
	wire il1_end_r;


	wire [1:0] il1_state_dnxt;
	wire [1:0] il1_state_qout;
	// wire [1:0] il1_state_mode_dir;

	wire [31:0] cache_addr;

	wire [CB-1:0] cache_en_w;
	wire [CB-1:0] cache_en_r;
	wire [7:0] cache_info_wstrb;
	wire [63:0] cache_info_w;
	wire [64*CB-1:0] cache_info_r;

	wire [31:0] tag_addr;
	wire [CB-1:0] tag_en_w;
	wire [CB-1:0] tag_en_r;
	wire [(TAG_W+7)/8-1:0] tag_info_wstrb;
	wire [TAG_W-1:0] tag_info_w;
	wire [TAG_W*CB-1:0] tag_info_r;

	wire [31:0] cache_addr_dnxt;
	wire [31:0] cache_addr_qout;

	wire [CB-1:0] cb_vhit;
	wire [LINE_W-1:0] valid_cl_sel;
	wire [63:0] cache_data_r;
	wire [64*CB-1:0] cache_info_r_T;

	wire [CL*CB-1:0] cache_valid_set;
	wire [CL*CB-1:0] cache_valid_rst;
	wire [CL*CB-1:0] cache_valid_qout;

	wire isCacheBlockRunout;
	wire [$clog2(CB)-1:0] cache_block_sel;
	wire [15:0] random;
	wire [CB-1:0] blockReplace;

	wire [CB-1:0] cache_cl_valid;

	assign IL1_ARBURST = 2'b01;
	assign IL1_ARADDR = addr_req_qout & { {(32-ADDR_LSB){1'b1}}, {ADDR_LSB{1'b0}} };
	assign IL1_ARLEN = 8'd3;
	assign IL1_ARVALID = il1_arvalid_qout;
	assign IL1_RREADY = il1_rready_qout;

	assign il1_end_r = IL1_RVALID & IL1_RREADY & IL1_RLAST;
	
	assign il1_arvalid_set = ~il1_arvalid_qout & il1_ar_req;
	assign il1_arvalid_rst = il1_arvalid_qout & IL1_ARREADY ;
	gen_rsffr # (.DW(1)) il1_arvalid_rsffr (.set_in(il1_arvalid_set), .rst_in(il1_arvalid_rst), .qout(il1_arvalid_qout), .CLK(CLK), .RSTn(RSTn));
	
	assign il1_rready_set = IL1_RVALID & (~IL1_RLAST | ~il1_rready_qout);
	assign il1_rready_rst = IL1_RVALID &   IL1_RLAST &  il1_rready_qout;
	gen_rsffr # (.DW(1)) il1_rready_rsffr (.set_in(il1_rready_set), .rst_in(il1_rready_rst), .qout(il1_rready_qout), .CLK(CLK), .RSTn(RSTn));






// BBBBBBBBBBBBBBBBB   RRRRRRRRRRRRRRRRR                  AAA               MMMMMMMM               MMMMMMMM
// B::::::::::::::::B  R::::::::::::::::R                A:::A              M:::::::M             M:::::::M
// B::::::BBBBBB:::::B R::::::RRRRRR:::::R              A:::::A             M::::::::M           M::::::::M
// BB:::::B     B:::::BRR:::::R     R:::::R            A:::::::A            M:::::::::M         M:::::::::M
//   B::::B     B:::::B  R::::R     R:::::R           A:::::::::A           M::::::::::M       M::::::::::M
//   B::::B     B:::::B  R::::R     R:::::R          A:::::A:::::A          M:::::::::::M     M:::::::::::M
//   B::::BBBBBB:::::B   R::::RRRRRR:::::R          A:::::A A:::::A         M:::::::M::::M   M::::M:::::::M
//   B:::::::::::::BB    R:::::::::::::RR          A:::::A   A:::::A        M::::::M M::::M M::::M M::::::M
//   B::::BBBBBB:::::B   R::::RRRRRR:::::R        A:::::A     A:::::A       M::::::M  M::::M::::M  M::::::M
//   B::::B     B:::::B  R::::R     R:::::R      A:::::AAAAAAAAA:::::A      M::::::M   M:::::::M   M::::::M
//   B::::B     B:::::B  R::::R     R:::::R     A:::::::::::::::::::::A     M::::::M    M:::::M    M::::::M
//   B::::B     B:::::B  R::::R     R:::::R    A:::::AAAAAAAAAAAAA:::::A    M::::::M     MMMMM     M::::::M
// BB:::::BBBBBB::::::BRR:::::R     R:::::R   A:::::A             A:::::A   M::::::M               M::::::M
// B:::::::::::::::::B R::::::R     R:::::R  A:::::A               A:::::A  M::::::M               M::::::M
// B::::::::::::::::B  R::::::R     R:::::R A:::::A                 A:::::A M::::::M               M::::::M
// BBBBBBBBBBBBBBBBB   RRRRRRRR     RRRRRRRAAAAAAA                   AAAAAAAMMMMMMMM               MMMMMMMM

wire trans_kill_set, trans_kill_rst, trans_kill_qout;


assign trans_kill_set = (flush & il1_state_dnxt == IL1_STATE_CMISS);
assign trans_kill_rst = il1_state_dnxt != IL1_STATE_CMISS;

gen_rsffr #(.DW(1)) trans_kill_rsffr (.set_in(trans_kill_set), .rst_in(trans_kill_rst), .qout(trans_kill_qout), .CLK(CLK), .RSTn(RSTn));


// assign cache_fence_set = il1_fence;
// assign cache_fence_rst = (il1_state_qout == IL1_STATE_FENCE);
// gen_rsffr # (.DW(1)) cache_fence_rsffr ( .set_in(cache_fence_set), .rst_in(cache_fence_rst), .qout(cache_fence_qout), .CLK(CLK), .RSTn(RSTn) );

wire il1_fence_set, il1_fence_rst, il1_fence_qout;

assign il1_fence_set =  il1_fence & ~il1_fence_qout;
assign il1_fence_rst = ~il1_fence &  il1_fence_qout;

gen_rsffr # (.DW(1)) il1_fence_rsffr (.set_in(il1_fence_set), .rst_in(il1_fence_rst), .qout(il1_fence_qout), .CLK(CLK), .RSTn(RSTn));

gen_dffr # (.DW(2)) il1_state_dffr (.dnxt(il1_state_dnxt), .qout(il1_state_qout), .CLK(CLK), .RSTn(RSTn));


// assign il1_state_mode_dir = 
// 	ifu_req_valid ? IL1_STATE_CKTAG : IL1_STATE_CFREE;


assign il1_state_dnxt = 
	( {2{il1_state_qout == IL1_STATE_CFREE}} & (il1_fence_qout ? IL1_STATE_FENCE : (ic_iq_ready&(~flush) ? IL1_STATE_CKTAG : IL1_STATE_CFREE)) ) 
	|
	( {2{il1_state_qout == IL1_STATE_CKTAG}} & ((| cb_vhit ) ? IL1_STATE_CFREE : IL1_STATE_CMISS) )
	|
	( {2{il1_state_qout == IL1_STATE_CMISS}} & (il1_end_r ? IL1_STATE_CFREE : IL1_STATE_CMISS) )
	|
	( {2{il1_state_qout == IL1_STATE_FENCE}} & (~il1_fence_qout ? IL1_STATE_CFREE : IL1_STATE_FENCE) )
	;





assign pc_ic_ready = 
	  ( (il1_state_qout == IL1_STATE_CKTAG) & (| cb_vhit ) & ~flush) 
	| ( (il1_state_qout == IL1_STATE_CMISS) & (cache_addr_qout == addr_req_qout) & IL1_RVALID & IL1_RREADY & ~trans_kill_qout );

assign ic_data_rsp = 
	  ( {64{il1_state_qout == IL1_STATE_CKTAG}} & cache_data_r )
	| ( {64{il1_state_qout == IL1_STATE_CMISS}} & IL1_RDATA );


assign il1_ar_req = (il1_state_qout == IL1_STATE_CKTAG) & (il1_state_dnxt == IL1_STATE_CMISS);







	assign cache_en_w = cb_vhit & {CB{il1_state_qout == IL1_STATE_CMISS & IL1_RVALID & IL1_RREADY}};
	assign cache_en_r = {CB{il1_state_dnxt == IL1_STATE_CKTAG}};
	assign cache_info_wstrb = 8'b11111111;
	assign cache_info_w = IL1_RDATA;

	assign tag_addr = (il1_state_qout == IL1_STATE_CMISS) ? addr_req_qout : ic_addr_req;
	assign tag_en_w = blockReplace & {CB{il1_state_qout == IL1_STATE_CKTAG & il1_state_dnxt == IL1_STATE_CMISS}};
	assign tag_en_r = {CB{(il1_state_dnxt == IL1_STATE_CKTAG) | (il1_state_qout == IL1_STATE_CMISS & IL1_ARVALID & IL1_ARREADY)}};
	assign tag_info_wstrb = {((TAG_W+7)/8){1'b1}};
	assign tag_info_w = tag_addr[31 -: TAG_W];


	assign cache_addr_dnxt = 
		  ( {32{il1_state_qout == IL1_STATE_CFREE}} & ic_addr_req & { {(32-ADDR_LSB){1'b1}}, {ADDR_LSB{1'b0}} } )
		| ( {32{il1_state_qout == IL1_STATE_CKTAG}} & ic_addr_req & { {(32-ADDR_LSB){1'b1}}, {ADDR_LSB{1'b0}} } )
		| ( {32{il1_state_qout == IL1_STATE_CMISS}} & ( (IL1_RVALID & IL1_RREADY) ? cache_addr_qout + 32'b1000 : cache_addr_qout) )
		| ( {32{il1_state_qout == IL1_STATE_FENCE}} & ic_addr_req & { {(32-ADDR_LSB){1'b1}}, {ADDR_LSB{1'b0}} } )
		;

		wire [31:0] addr_req_dnxt;
		wire [31:0] addr_req_qout;
		
	assign addr_req_dnxt = (il1_state_qout == IL1_STATE_CFREE) ? ic_addr_req : addr_req_qout;


	gen_dffr #(.DW(32)) addr_req_dffr ( .dnxt(addr_req_dnxt), .qout(addr_req_qout), .CLK(CLK), .RSTn(RSTn));
	gen_dffr #(.DW(32)) cache_addr_dffr ( .dnxt(cache_addr_dnxt), .qout(cache_addr_qout), .CLK(CLK), .RSTn(RSTn));


	assign cache_addr = (il1_state_qout == IL1_STATE_CMISS) ? cache_addr_qout : ic_addr_req;


cache_mem # ( .DW(DW), .BK(BK), .CB(CB), .CL(CL), .TAG_W(TAG_W) ) i_cache_mem
(
	.cache_addr(cache_addr),
	.cache_en_w(cache_en_w),
	.cache_en_r(cache_en_r),
	.cache_info_wstrb(cache_info_wstrb),
	.cache_info_w(cache_info_w),
	.cache_info_r(cache_info_r),

	.tag_addr(tag_addr),
	.tag_en_w(tag_en_w),
	.tag_en_r(tag_en_r),
	.tag_info_wstrb(tag_info_wstrb),
	.tag_info_w(tag_info_w),
	.tag_info_r(tag_info_r),

	.CLK(CLK),
	.RSTn(RSTn)
);












assign valid_cl_sel = addr_req_qout[ADDR_LSB +: LINE_W];
wire [TAG_W-1:0] chk_tag = addr_req_qout[31 -: TAG_W];

generate
	for ( genvar cb = 0; cb < CB; cb = cb + 1 ) begin
		assign cb_vhit[cb] = (tag_info_r[TAG_W*cb +: TAG_W] == chk_tag) & cache_valid_qout[CB*valid_cl_sel+cb];

		for ( genvar i = 0; i < 64; i = i + 1 ) begin
			assign cache_info_r_T[CB*i+cb] = cache_info_r[64*cb+i];
		end
	end

	for ( genvar i = 0; i < 64; i = i + 1 ) begin
		assign cache_data_r[i] = | (cache_info_r_T[CB*i +: CB] & cb_vhit);
	end


endgenerate







generate
	for ( genvar cl = 0; cl < CL; cl = cl + 1) begin
		for ( genvar cb = 0; cb < CB; cb = cb + 1 ) begin

			assign cache_valid_set[CB*cl+cb] = (il1_state_qout == IL1_STATE_CKTAG) & (il1_state_dnxt == IL1_STATE_CMISS) & (cl == valid_cl_sel) & blockReplace[cb];
			assign cache_valid_rst[CB*cl+cb] = (il1_state_qout == IL1_STATE_FENCE) & (il1_state_dnxt == IL1_STATE_CFREE);

			gen_rsffr # (.DW(1)) cache_valid_rsffr (.set_in(cache_valid_set[CB*cl+cb]), .rst_in(cache_valid_rst[CB*cl+cb]), .qout(cache_valid_qout[CB*cl+cb]), .CLK(CLK), .RSTn(RSTn));

		end


	end

endgenerate







assign cache_cl_valid = cache_valid_qout[CB*valid_cl_sel +:CB];

lzp # ( .CW($clog2(CB)) ) l2c_malloc
(
	.in_i(cache_cl_valid),
	.pos_o(cache_block_sel),
	.all1(isCacheBlockRunout),
	.all0()
);

lfsr i_lfsr
(
	.random(random),
	.CLK(CLK)
);

assign blockReplace = 1 << ( isCacheBlockRunout ? random[$clog2(CB):0] : cache_block_sel );



endmodule