module btb
#(
    parameter BTBNUM = 32,
	parameter RASNUM = 16
)
(
    input             clk           ,
    input             reset         ,
    //from/to if
    input  [31:0]     fetch_pc      ,
    input             fetch_en      ,
    output [31:0]     ret_pc        ,
    output            taken         ,
    output            ret_en        ,
    output [ 4:0]     ret_index     ,
    //from id
    input             operate_en    ,
    input  [31:0]     operate_pc    ,
    input  [ 4:0]     operate_index ,
    input             pop_ras       ,
    input             push_ras      ,
    input             add_entry     ,
    input             delete_entry  ,
    input             pre_error     ,
    input             pre_right     ,
    input             target_error  ,
    input             right_orien   ,
    input  [31:0]     right_target  
);

/*
* btb_pc record all branch inst pc except jirl
* ras_pc only record jirl pc
*/
reg [29:0] btb_pc      [BTBNUM-1:0];
reg [29:0] btb_target  [BTBNUM-1:0];
reg [ 1:0] btb_counter [BTBNUM-1:0];

reg [BTBNUM-1:0] btb_valid;

reg [29:0] ras_pc [RASNUM-1:0];

reg [RASNUM-1:0] ras_valid;

reg [31:0] fetch_pc_r;
reg fetch_en_r;

//reg [BTBNUM-1:0] jirl_flag;

reg [29:0] ras [7:0];
reg [ 3:0] ras_ptr;

wire [29:0] ras_top;

wire ras_full;
wire ras_empty;

wire [31:0] btb_match_rd;
wire [15:0] ras_match_rd;

wire btb_match;
wire ras_match;

wire [29:0] btb_match_target;
wire [ 1:0] btb_match_counter;
wire [ 4:0] btb_match_index;
wire [ 4:0] btb_random_index;

wire [ 3:0] ras_match_index;
wire [ 3:0] ras_random_index;

wire btb_all_entry_valid;
wire [4:0] btb_select_one_invalid_entry;
wire [4:0] btb_add_entry_index;
reg  [4:0] btb_add_entry_index_r;
wire [31:0] btb_add_entry_dec;

wire ras_all_entry_valid;
wire [3:0] ras_select_one_invalid_entry;
wire [3:0] ras_add_entry_index;

wire [31:0] btb_untaken_entry;
reg  [31:0] btb_untaken_entry_r;
wire [31:0] btb_untaken_entry_t;
reg         btb_add_entry_r;
wire [4:0]  btb_sel_one_untaken_entry;
wire        btb_has_one_untaken_entry;

reg [5:0] fcsr;

always @(posedge clk) begin
    if (reset)
        fetch_en_r <= 1'b0;
    else 
	fetch_en_r <= fetch_en;

    if (fetch_en) 
        fetch_pc_r <= fetch_pc;
end

always @(posedge clk) begin
	btb_untaken_entry_r   <= btb_untaken_entry;
	btb_add_entry_r       <= operate_en && !pop_ras && add_entry;
	btb_add_entry_index_r <= btb_add_entry_index;
end

//untaken entry list cal at previous clock
assign btb_untaken_entry_t = btb_untaken_entry_r & ({32{!btb_add_entry_r}} | ~btb_add_entry_dec); 
assign btb_has_one_untaken_entry = |btb_untaken_entry_t;

decoder_5_32 dec_btb_add_entry (.in(btb_add_entry_index_r), .out(btb_add_entry_dec));
one_valid_32 sel_one_untaken_entry (.in(btb_untaken_entry_t), .out_en(btb_sel_one_untaken_entry));

//assign btb_random_index = (btb_match && (fcsr[4:0] == btb_match_index)) ? (fcsr[4:0]+1'b1) : fcsr[4:0];
//untaken entry can exit first, make no difference, except lose history infor. 
assign btb_add_entry_index = !btb_all_entry_valid ?      btb_select_one_invalid_entry :
							 btb_has_one_untaken_entry ? btb_sel_one_untaken_entry : 
							 							 fcsr[4:0] ; 

assign btb_all_entry_valid = &btb_valid;
one_valid_32 sel_one_btb_entry (.in(~btb_valid), .out_en(btb_select_one_invalid_entry));

//assign ras_random_index = (ras_match && (fcsr[3:0] == ras_match_index)) ? (fcsr[3:0]+1'b1) : fcsr[3:0];
assign ras_add_entry_index = ras_all_entry_valid ? fcsr[3:0] : ras_select_one_invalid_entry;
assign ras_all_entry_valid = &ras_valid;
one_valid_16 sel_one_ras_entry (.in(~ras_valid), .out_en(ras_select_one_invalid_entry));

always @(posedge clk) begin
    if (reset) begin
        btb_valid <= 32'b0;
		ras_valid <= 16'b0;
    end
    else if (operate_en && !pop_ras) begin
        if (add_entry) begin
            btb_valid[btb_add_entry_index]   <= 1'b1;
            btb_pc[btb_add_entry_index]      <= operate_pc[31:2];
            btb_target[btb_add_entry_index]  <= right_target[31:2];
            btb_counter[btb_add_entry_index] <= 2'b10;
        end
        else if (target_error) begin
            btb_target[operate_index]    <= right_target[31:2];
            btb_counter[operate_index]   <= 2'b10;
        end
        else if (pre_error || pre_right) begin
            if (right_orien) begin
                if (btb_counter[operate_index] != 2'b11) begin
                    btb_counter[operate_index] <= btb_counter[operate_index] + 2'b1;
                end
            end
            else begin
                if (btb_counter[operate_index] != 2'b00) begin
                    btb_counter[operate_index] <= btb_counter[operate_index] - 2'b1;
                end
            end
        end
    end
	else if (operate_en && pop_ras) begin
		if (add_entry) begin
			ras_valid[ras_add_entry_index] <= 1'b1;
            ras_pc[ras_add_entry_index]    <= operate_pc[31:2];
		end
	end
    
end

genvar i;
generate 
    for (i = 0; i < BTBNUM; i = i + 1)
        begin: btb_match_com
            assign btb_match_rd[i] = fetch_en_r && ((fetch_pc_r[31:2] == btb_pc[i]) && btb_valid[i]); 
        end
endgenerate

generate 
    for (i = 0; i < RASNUM; i = i + 1)
        begin: ras_match_com
            assign ras_match_rd[i] = fetch_en_r && ((fetch_pc_r[31:2] == ras_pc[i]) && ras_valid[i]); 
        end
endgenerate

generate 
	for (i = 0; i < BTBNUM; i = i + 1)
		begin: sel_untaken_entry
			assign btb_untaken_entry[i] = btb_valid[i] && ~|btb_counter[i];
		end
endgenerate

assign btb_match = |btb_match_rd;
assign ras_match = |ras_match_rd && !ras_empty;

assign ras_top = ras[ras_ptr - 4'b1]; //ras modify may before inst fetch

encoder_32_5 encode_btb_match (.in(btb_match_rd), .out(btb_match_index));
encoder_16_4 encode_ras_match (.in(ras_match_rd), .out(ras_match_index));

assign btb_match_target = btb_target[btb_match_index];
assign btb_match_counter = btb_counter[btb_match_index];

assign ret_pc = {32{ras_match}} & {ras_top, 2'b0} |
				{32{btb_match}} & {btb_match_target, 2'b0};
assign ret_en = btb_match || ras_match;
assign taken  = btb_match && btb_match_counter[1] || ras_match;
assign ret_index = {5{btb_match}} & {btb_match_index} | 
				   {5{ras_match}} & {1'b0,ras_match_index};

assign ras_full  = ras_ptr[3];
assign ras_empty = (ras_ptr == 4'd0);

always @(posedge clk) begin
    if (reset) begin
        ras_ptr <= 4'b0;
    end
    else if (operate_en) begin
        if (push_ras && !ras_full) begin
            ras[ras_ptr] <= operate_pc[31:2] + 30'b1;
            ras_ptr <= ras_ptr + 4'b1;
        end
        else if (pop_ras && !ras_empty) begin
            ras_ptr <= ras_ptr - 4'b1;
        end
    end
end

always @(posedge clk) begin
    if (reset) begin
        fcsr <= 6'b100010;
    end
    else begin
        fcsr[0] <= fcsr[5];
        fcsr[1] <= fcsr[0];
        fcsr[2] <= fcsr[1];
        fcsr[3] <= fcsr[2] ^ fcsr[5];
        fcsr[4] <= fcsr[3] ^ fcsr[5];
        fcsr[5] <= fcsr[4];
    end
end

endmodule
