module uriscv_muldiv
(
    input           clk_i,
    input           rst_i,

    // Operation select
    input           valid_i,
    input           inst_mul_i,
    input           inst_mulh_i,
    input           inst_mulhsu_i,
    input           inst_mulhu_i,
    input           inst_div_i,
    input           inst_divu_i,
    input           inst_rem_i,
    input           inst_remu_i,

    // Operands
    input [31:0]    operand_ra_i,
    input [31:0]    operand_rb_i,

    // Result
    output          stall_o,
    output          ready_o,
    output [31:0]   result_o
);

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

//-------------------------------------------------------------
// Multiplier
//-------------------------------------------------------------
reg [32:0]   mul_operand_a_q;
reg [32:0]   mul_operand_b_q;
reg          mulhi_sel_q;

//-------------------------------------------------------------
// Multiplier
//-------------------------------------------------------------
wire [64:0]  mult_result_w;
reg  [32:0]  operand_b_r;
reg  [32:0]  operand_a_r;
reg  [31:0]  mul_result_r;

wire mult_inst_w    = inst_mul_i     |
                      inst_mulh_i    |
                      inst_mulhsu_i  |
                      inst_mulhu_i;


always @ *
begin
    if (inst_mulhsu_i)
        operand_a_r = {operand_ra_i[31], operand_ra_i[31:0]};
    else if (inst_mulh_i)
        operand_a_r = {operand_ra_i[31], operand_ra_i[31:0]};
    else // MULHU || MUL
        operand_a_r = {1'b0, operand_ra_i[31:0]};
end

always @ *
begin
    if (inst_mulhsu_i)
        operand_b_r = {1'b0, operand_rb_i[31:0]};
    else if (inst_mulh_i)
        operand_b_r = {operand_rb_i[31], operand_rb_i[31:0]};
    else // MULHU || MUL
        operand_b_r = {1'b0, operand_rb_i[31:0]};
end

// Pipeline flops for multiplier
always @(posedge clk_i )
if (rst_i)
begin
    mul_operand_a_q <= 33'b0;
    mul_operand_b_q <= 33'b0;
    mulhi_sel_q     <= 1'b0;
end
else if (valid_i && mult_inst_w)
begin
    mul_operand_a_q <= operand_a_r;
    mul_operand_b_q <= operand_b_r;
    mulhi_sel_q     <= ~inst_mul_i;
end
else
begin
    mul_operand_a_q <= 33'b0;
    mul_operand_b_q <= 33'b0;
    mulhi_sel_q     <= 1'b0;
end

assign mult_result_w = {{ 32 {mul_operand_a_q[32]}}, mul_operand_a_q}*{{ 32 {mul_operand_b_q[32]}}, mul_operand_b_q};

always @ *
begin
    mul_result_r = mulhi_sel_q ? mult_result_w[63:32] : mult_result_w[31:0];
end

reg mul_busy_q;

always @(posedge clk_i )
if (rst_i)
    mul_busy_q <= 1'b0;
else
    mul_busy_q <= valid_i & mult_inst_w;

//-------------------------------------------------------------
// Divider
//-------------------------------------------------------------
wire div_rem_inst_w     = inst_div_i  || 
                          inst_divu_i ||
                          inst_rem_i  ||
                          inst_remu_i;

wire signed_operation_w = inst_div_i || inst_rem_i;
wire div_operation_w    = inst_div_i || inst_divu_i;

reg [31:0] dividend_q;
reg [62:0] divisor_q;
reg [31:0] quotient_q;
reg [31:0] q_mask_q;
reg        div_inst_q;
reg        div_busy_q;
reg        invert_res_q;

wire div_start_w    = valid_i & div_rem_inst_w & !stall_o;
wire div_complete_w = !(|q_mask_q) & div_busy_q;

always @ (posedge clk_i )
if (rst_i)
begin
    div_busy_q     <= 1'b0;
    dividend_q     <= 32'b0;
    divisor_q      <= 63'b0;
    invert_res_q   <= 1'b0;
    quotient_q     <= 32'b0;
    q_mask_q       <= 32'b0;
    div_inst_q     <= 1'b0;
end 
else if (div_start_w)
begin
    div_busy_q     <= 1'b1;
    div_inst_q     <= div_operation_w;

    if (signed_operation_w && operand_ra_i[31])
        dividend_q <= -operand_ra_i;
    else
        dividend_q <= operand_ra_i;

    if (signed_operation_w && operand_rb_i[31])
        divisor_q <= {-operand_rb_i, 31'b0};
    else
        divisor_q <= {operand_rb_i, 31'b0};

    invert_res_q  <= (inst_div_i && (operand_ra_i[31] != operand_rb_i[31]) && |operand_rb_i) || 
                     (inst_rem_i && operand_ra_i[31]);

    quotient_q     <= 32'b0;
    q_mask_q       <= 32'h80000000;
end
else if (div_complete_w)
begin
    div_busy_q <= 1'b0;
end
else if (div_busy_q)
begin
    if (divisor_q <= {31'b0, dividend_q})
    begin
        dividend_q <= dividend_q - divisor_q[31:0];
        quotient_q <= quotient_q | q_mask_q;
    end

    divisor_q <= {1'b0, divisor_q[62:1]};
    q_mask_q  <= {1'b0, q_mask_q[31:1]};
end

reg [31:0] div_result_r;
always @ *
begin
    div_result_r = 32'b0;

    if (div_inst_q)
        div_result_r = invert_res_q ? -quotient_q : quotient_q;
    else
        div_result_r = invert_res_q ? -dividend_q : dividend_q;
end

//-------------------------------------------------------------
// Shared logic
//-------------------------------------------------------------

// Stall if divider logic is busy and new multiplier or divider op
assign stall_o = (div_busy_q  & (mult_inst_w | div_rem_inst_w)) ||
                 (mul_busy_q & div_rem_inst_w);

reg  [31:0]  result_q;
reg          ready_q;

always @ (posedge clk_i )
if (rst_i)
    ready_q <= 1'b0;
else if (mul_busy_q)
    ready_q <= 1'b1;
else if (div_complete_w)
    ready_q <= 1'b1;
else
    ready_q <= 1'b0;

always @ (posedge clk_i )
if (rst_i)
    result_q <= 32'b0;
else if (div_complete_w)
    result_q <= div_result_r;
else if (mul_busy_q)
    result_q <= mul_result_r;

assign result_o  = result_q;
assign ready_o   = ready_q;

endmodule