module uriscv_branch
(
     input  [31:0]  pc_i
    ,input  [31:0]  opcode_i
    ,input  [31:0]  rs1_val_i
    ,input  [31:0]  rs2_val_i
    ,output         branch_o
    ,output [31:0]  branch_target_o
);

//-----------------------------------------------------------------
// Includes
//-----------------------------------------------------------------
`include "uriscv_defs.v"

//-----------------------------------------------------------------
// less_than_signed: Less than operator (signed)
// Inputs: x = left operand, y = right operand
// Return: (int)x < (int)y
//-----------------------------------------------------------------
function [0:0] less_than_signed;
    input  [31:0] x;
    input  [31:0] y;
    reg [31:0] v;
begin
    v = (x - y);
    if (x[31] != y[31])
        less_than_signed = x[31];
    else
        less_than_signed = v[31];
end
endfunction

//-----------------------------------------------------------------
// greater_than_signed: Greater than operator (signed)
// Inputs: x = left operand, y = right operand
// Return: (int)x > (int)y
//-----------------------------------------------------------------
function [0:0] greater_than_signed;
    input  [31:0] x;
    input  [31:0] y;
    reg [31:0] v;
begin
    v = (y - x);
    if (x[31] != y[31])
        greater_than_signed = y[31];
    else
        greater_than_signed = v[31];
end
endfunction

//-----------------------------------------------------------------
// Branch Decode
//-----------------------------------------------------------------
wire type_branch_w  = (opcode_i[6:2] == 5'b11000);
wire type_jalr_w    = (opcode_i[6:2] == 5'b11001);
wire type_jal_w     = (opcode_i[6:2] == 5'b11011);

wire [2:0] func3_w  = opcode_i[14:12]; // R, I, S
wire [6:0] func7_w  = opcode_i[31:25]; // R

wire branch_beq_w   = (func3_w == 3'b000);
wire branch_bne_w   = (func3_w == 3'b001);
wire branch_blt_w   = (func3_w == 3'b100);
wire branch_bge_w   = (func3_w == 3'b101);
wire branch_bltu_w  = (func3_w == 3'b110);
wire branch_bgeu_w  = (func3_w == 3'b111);

reg         branch_r;
reg [31:0]  branch_target_r;
reg [31:0]  imm12_r;
reg [31:0]  bimm_r;
reg [31:0]  jimm20_r;

always @ *
begin
    branch_r        = 1'b0;
    branch_target_r = 32'b0;

    // Opcode decode
    imm12_r         = {{20{opcode_i[31]}}, opcode_i[31:20]};
    bimm_r          = {{19{opcode_i[31]}}, opcode_i[31],    opcode_i[7],  opcode_i[30:25], opcode_i[11:8],  1'b0};
    jimm20_r        = {{12{opcode_i[31]}}, opcode_i[19:12], opcode_i[20], opcode_i[30:25], opcode_i[24:21], 1'b0};

    // Default branch target is relative to current PC
    branch_target_r = (pc_i + bimm_r);    

    if (type_jal_w)
    begin
        branch_r        = 1'b1;
        branch_target_r = pc_i + jimm20_r;
    end
    else if (type_jalr_w)
    begin
        branch_r            = 1'b1;
        branch_target_r     = rs1_val_i + imm12_r;
        branch_target_r[0]  = 1'b0;
    end
    else if (type_branch_w)
    begin
        case (1'b1)
        branch_beq_w: // beq
            branch_r      = (rs1_val_i == rs2_val_i);

        branch_bne_w: // bne
            branch_r      = (rs1_val_i != rs2_val_i);

        branch_blt_w: // blt
            branch_r      = less_than_signed(rs1_val_i, rs2_val_i);

        branch_bge_w: // bge
            branch_r      = greater_than_signed(rs1_val_i, rs2_val_i) | (rs1_val_i == rs2_val_i);

        branch_bltu_w: // bltu
            branch_r      = (rs1_val_i < rs2_val_i);

        branch_bgeu_w: // bgeu
            branch_r      = (rs1_val_i >= rs2_val_i);

        default:
            ;
        endcase
    end
end

assign branch_o        = branch_r;
assign branch_target_o = branch_target_r;

endmodule