blob: c7ece1ef69e08eaa0d74fcce0314d58e994ead09 [file] [log] [blame]
/****************************************************************************
* fwrisc_mul_div_shift.sv
*
* Copyright 2019 Matthew Ballance
*
* Licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in
* writing, software distributed under the License is
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing
* permissions and limitations under the License.
****************************************************************************/
/**
* Module: fwrisc_mul_div_shift
*
* Multi-cycle multiply/divide/shift unit.
*/
module fwrisc_mul_div_shift #(
parameter ENABLE_MUL_DIV=1,
parameter ENABLE_MUL=ENABLE_MUL_DIV,
parameter ENABLE_DIV=ENABLE_MUL_DIV,
parameter SINGLE_CYCLE_SHIFT=0
)(
input clock,
input reset,
input[31:0] in_a,
input[31:0] in_b,
input[3:0] op,
input in_valid,
output reg[31:0] out,
output reg out_valid
);
`include "fwrisc_mul_div_shift_op.svh"
reg[3:0] op_r;
reg[4:0] shift_amt_r;
reg working;
reg[63:0] mul_res;
reg[31:0] mul_tmp1;
reg[31:0] mul_tmp2;
reg[62:0] div_divisor;
reg[31:0] div_dividend;
reg[31:0] div_quotient;
reg[31:0] div_msk;
reg div_sign;
wire mul_tmp2_zero = (|mul_tmp2 == 0);
always @(posedge clock) begin
if (reset) begin
out <= 32'b0;
out_valid <= 0;
working <= 0;
op_r <= 0;
end else begin
if (in_valid) begin
op_r <= op;
working <= 1;
case (op)
OP_SLL, OP_SRL, OP_SRA: begin // sll
if (SINGLE_CYCLE_SHIFT) begin
shift_amt_r <= 0;
case (op_r)
OP_SLL: begin // sll
if (|in_b[4:0]) begin
out <= (out << in_b[4:0]);
end
end
OP_SRL: begin // srl
if (|in_b[4:0]) begin
out <= (out >> in_b[4:0]);
end
end
OP_SRA: begin // sra
if (|in_b[4:0]) begin
out <= (out >>> in_b[4:0]);
end
end
endcase
end else begin
shift_amt_r <= in_b[4:0];
out <= in_a;
end
end
OP_MUL, OP_MULH: begin // mul/mulh
if (ENABLE_MUL) begin
mul_res <= 64'b0;
mul_tmp1 <= in_a;
mul_tmp2 <= in_b;
shift_amt_r <= 5'd31;
end
end
OP_MULS, OP_MULSH: begin // muls/mulsh
if (ENABLE_MUL) begin
mul_res <= 64'b0;
mul_tmp1 <= $signed(in_a);
mul_tmp2 <= $signed(in_b);
shift_amt_r <= 5'd31;
end
end
OP_DIV, OP_REM: begin
if (ENABLE_DIV) begin
if (in_a[31]) begin
div_dividend <= -in_a;
end else begin
div_dividend <= in_a;
end
if (in_b[31]) begin
div_divisor <= (-in_b) << 31;
end else begin
div_divisor <= in_b << 31;
end
shift_amt_r <= 5'd31;
div_msk <= 'h8000_0000;
end
end
endcase
if (op == OP_DIV) begin
div_sign <= (in_a[31] != in_a[31]);
end else begin
// OP_REM and others
div_sign <= in_a[31];
end
end
if (working) begin
case (op_r)
OP_SLL: begin // sll
if (|shift_amt_r) begin
out <= (out << 1);
end
end
OP_SRL: begin // srl
if (|shift_amt_r) begin
out <= (out >> 1);
end
end
OP_SRA: begin // sra
if (|shift_amt_r) begin
out <= ($signed(out) >>> 1);
end
end
OP_MUL, OP_MULS, OP_MULH, OP_MULSH: begin // mul
if (ENABLE_MUL) begin
if (mul_tmp1[0]) begin
mul_res <= (mul_res + mul_tmp2);
end
mul_tmp2 <= (mul_tmp2 << 1);
mul_tmp1 <= (mul_tmp1 >> 1);
if (op_r == OP_MUL || op_r == OP_MULS) begin // mul/muls
out <= mul_res[31:0];
end else begin // mulh/mulsh
out <= mul_res[63:32];
end
end
end
OP_DIV, OP_REM: begin
if (ENABLE_DIV) begin
if (div_divisor <= div_dividend) begin
div_dividend <= div_dividend - div_divisor;
div_quotient <= div_quotient | div_msk;
end
div_divisor <= div_divisor >> 1;
div_msk <= div_msk >> 1;
if (op == OP_DIV) begin
out <= (div_sign)?-div_quotient:div_quotient;
end else begin
out <= (div_sign)?-div_dividend:div_dividend;
end
end
end
endcase
if (|shift_amt_r == 0) begin
// We're done
working <= 1'b0;
out_valid <= 1'b1;
end else begin
shift_amt_r <= shift_amt_r - 1;
end
end else begin
out_valid <= 0;
end
end
end
endmodule