// Copyright 2017 ETH Zurich and University of Bologna. // Copyright and related rights are licensed under the Solderpad Hardware // License, Version 0.51 (the “License”); you may not use this file except in // compliance with the License. You may obtain a copy of the License at // http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law // or agreed to in writing, software, hardware and materials distributed under // this 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. //////////////////////////////////////////////////////////////////////////////// // Engineer: Davide Schiavone - pschiavo@iis.ee.ethz.ch // // // // // // Design Name: Fast Multiplier and Division // // Project Name: zero-riscy // // Language: SystemVerilog // // // // Description: 16x16 kernel multiplier and Long Division // // // //////////////////////////////////////////////////////////////////////////////// import zeroriscy_defines::*; `define OP_L 15:0 `define OP_H 31:16 module zeroriscy_multdiv_fast ( input logic clk, input logic rst_n, input logic mult_en_i, input logic div_en_i, input logic [1:0] operator_i, input logic [1:0] signed_mode_i, input logic [31:0] op_a_i, input logic [31:0] op_b_i, output logic [31:0] multdiv_result_o, output logic ready_o ); ///////////////// logic [32:0] alu_operand_a_o; logic [32:0] alu_operand_b_o; logic [33:0] alu_adder_ext_i; logic [31:0] alu_adder_i; logic equal_to_zero; assign alu_adder_ext_i = $unsigned(alu_operand_a_o) + $unsigned(alu_operand_b_o); assign alu_adder_i = alu_adder_ext_i[32:1]; assign equal_to_zero = (alu_operand_b_o[32:1]==32'hffffffff); ///////////////// logic [ 4:0] div_counter_q, div_counter_n; enum logic [1:0] {ALBL, ALBH, AHBL, AHBH } mult_state_q, mult_state_n; enum logic [2:0] { MD_IDLE, MD_ABS_A, MD_ABS_B, MD_COMP, MD_LAST, MD_CHANGE_SIGN, MD_FINISH } divcurr_state_q, divcurr_state_n; logic [34:0] mac_res_ext; logic [33:0] mac_res_q, mac_res_n, mac_res, op_reminder_n; logic [15:0] mult_op_a; logic [15:0] mult_op_b; logic [33:0] accum; logic sign_a, sign_b; logic div_sign_a, div_sign_b; logic signed_mult; logic is_greater_equal; logic div_change_sign, rem_change_sign; logic [31:0] one_shift; logic [31:0] op_denominator_q; logic [31:0] op_numerator_q; logic [31:0] op_quotient_q; logic [31:0] op_denominator_n; logic [31:0] op_numerator_n; logic [31:0] op_quotient_n; logic [32:0] next_reminder, next_quotient; logic [32:0] res_adder_h; logic mult_is_ready; always_ff @(posedge clk or negedge rst_n) begin : proc_mult_state_q if(~rst_n) begin mult_state_q <= ALBL; mac_res_q <= '0; div_counter_q <= '0; divcurr_state_q <= MD_IDLE; op_denominator_q <= '0; op_numerator_q <= '0; op_quotient_q <= '0; end else begin if(mult_en_i) begin mult_state_q <= mult_state_n; end if(div_en_i) begin div_counter_q <= div_counter_n; op_denominator_q <= op_denominator_n ; op_numerator_q <= op_numerator_n ; op_quotient_q <= op_quotient_n ; divcurr_state_q <= divcurr_state_n; end unique case(1'b1) mult_en_i: mac_res_q <= mac_res_n; div_en_i: mac_res_q <= op_reminder_n; default: mac_res_q <= mac_res_q; endcase end end assign signed_mult = (signed_mode_i != 2'b00); assign multdiv_result_o = div_en_i ? mac_res_q[31:0] : mac_res_n[31:0]; assign mac_res_ext = $signed({sign_a, mult_op_a})*$signed({sign_b, mult_op_b}) + $signed(accum); assign mac_res = mac_res_ext[33:0]; assign res_adder_h = alu_adder_ext_i[33:1]; assign next_reminder = is_greater_equal ? res_adder_h : mac_res_q[32:0]; assign next_quotient = is_greater_equal ? op_quotient_q | one_shift : op_quotient_q; assign one_shift = {31'b0, 1'b1} << div_counter_q; /* The adder in the ALU computes alu_operand_a_o + alu_operand_b_o which means Reminder - Divisor. If Reminder - Divisor >= 0, is_greater_equal is equal to 1, the next Reminder is Reminder - Divisor contained in res_adder_h and the */ always_comb begin if ((mac_res_q[31] ^ op_denominator_q[31]) == 0) is_greater_equal = (res_adder_h[31] == 0); else is_greater_equal = mac_res_q[31]; end assign div_sign_a = op_a_i[31] & signed_mode_i[0]; assign div_sign_b = op_b_i[31] & signed_mode_i[1]; assign div_change_sign = div_sign_a ^ div_sign_b; assign rem_change_sign = div_sign_a; always_comb begin : div_fsm div_counter_n = div_counter_q - 1; op_reminder_n = mac_res_q; op_quotient_n = op_quotient_q; divcurr_state_n = divcurr_state_q; op_numerator_n = op_numerator_q; op_denominator_n = op_denominator_q; alu_operand_a_o = {32'h0 , 1'b1}; alu_operand_b_o = {~op_b_i, 1'b1}; unique case(divcurr_state_q) MD_IDLE: begin unique case(operator_i) MD_OP_DIV: begin //Check if the Denominator is 0 //quotient for division by 0 op_reminder_n = '1; divcurr_state_n = equal_to_zero ? MD_FINISH : MD_ABS_A; end default: begin //Check if the Denominator is 0 //reminder for division by 0 op_reminder_n = {2'b0, op_a_i}; divcurr_state_n = equal_to_zero ? MD_FINISH : MD_ABS_A; end endcase //0 - B = 0 iff B == 0 alu_operand_a_o = {32'h0 , 1'b1}; alu_operand_b_o = {~op_b_i, 1'b1}; div_counter_n = 5'd31; end MD_ABS_A: begin //quotient op_quotient_n = '0; //A abs value op_numerator_n = div_sign_a ? alu_adder_i : op_a_i; divcurr_state_n = MD_ABS_B; div_counter_n = 5'd31; //ABS(A) = 0 - A alu_operand_a_o = {32'h0 , 1'b1}; alu_operand_b_o = {~op_a_i, 1'b1}; end MD_ABS_B: begin //reminder op_reminder_n = { 33'h0, op_numerator_q[31]}; //B abs value op_denominator_n = div_sign_b ? alu_adder_i : op_b_i; divcurr_state_n = MD_COMP; div_counter_n = 5'd31; //ABS(B) = 0 - B alu_operand_a_o = {32'h0 , 1'b1}; alu_operand_b_o = {~op_b_i, 1'b1}; end MD_COMP: begin op_reminder_n = {1'b0, next_reminder[31:0], op_numerator_q[div_counter_n]}; op_quotient_n = next_quotient; if(div_counter_q == 5'd1) divcurr_state_n = MD_LAST; else divcurr_state_n = MD_COMP; //Division alu_operand_a_o = {mac_res_q[31:0], 1'b1}; //it contains the reminder alu_operand_b_o = {~op_denominator_q[31:0], 1'b1}; //denominator negated + 1 to do -denominator end MD_LAST: begin unique case(operator_i) MD_OP_DIV: //this time we save the quotient in op_reminder_n (i.e. mac_res_q) since we do not need anymore the reminder op_reminder_n = {1'b0, next_quotient}; default: //this time we do not save the quotient anymore since we need only the reminder op_reminder_n = {2'b0, next_reminder[31:0]}; endcase //Division alu_operand_a_o = {mac_res_q[31:0], 1'b1}; //it contains the reminder alu_operand_b_o = {~op_denominator_q[31:0], 1'b1}; //denominator negated + 1 to do -denominator divcurr_state_n = MD_CHANGE_SIGN; end MD_CHANGE_SIGN: begin divcurr_state_n = MD_FINISH; unique case(operator_i) MD_OP_DIV: op_reminder_n = (div_change_sign) ? alu_adder_i : mac_res_q; default: op_reminder_n = (rem_change_sign) ? alu_adder_i : mac_res_q; endcase //ABS(Quotient) = 0 - Quotient (or Reminder) alu_operand_a_o = {32'h0 , 1'b1}; alu_operand_b_o = {~mac_res_q[31:0], 1'b1}; end MD_FINISH: begin divcurr_state_n = MD_IDLE; end default:; endcase // divcurr_state_q end assign ready_o = mult_is_ready | (divcurr_state_q == MD_FINISH); always_comb begin : mult_fsm mult_op_a = op_a_i[`OP_L]; mult_op_b = op_b_i[`OP_L]; sign_a = 1'b0; sign_b = 1'b0; accum = mac_res_q; mac_res_n = mac_res; mult_state_n = mult_state_q; mult_is_ready = 1'b0; unique case (mult_state_q) ALBL: begin //al*bl mult_op_a = op_a_i[`OP_L]; mult_op_b = op_b_i[`OP_L]; sign_a = 1'b0; sign_b = 1'b0; accum = '0; mac_res_n = mac_res; mult_state_n = ALBH; end ALBH: begin //al*bh<<16 mult_op_a = op_a_i[`OP_L]; mult_op_b = op_b_i[`OP_H]; sign_a = 1'b0; sign_b = signed_mode_i[1] & op_b_i[31]; //result of AL*BL (in mac_res_q) always unsigned with no carry, so carries_q always 00 accum = {18'b0,mac_res_q[31:16]}; unique case(operator_i) MD_OP_MULL: begin mac_res_n = {2'b0,mac_res[`OP_L],mac_res_q[`OP_L]}; end default: begin //MD_OP_MULH mac_res_n = mac_res; end endcase mult_state_n = AHBL; end AHBL: begin //ah*bl<<16 mult_op_a = op_a_i[`OP_H]; mult_op_b = op_b_i[`OP_L]; sign_a = signed_mode_i[0] & op_a_i[31]; sign_b = 1'b0; unique case(operator_i) MD_OP_MULL: begin accum = {18'b0,mac_res_q[31:16]}; mac_res_n = {2'b0,mac_res[15:0],mac_res_q[15:0]}; mult_is_ready = 1'b1; mult_state_n = ALBL; end default: begin accum = mac_res_q; mac_res_n = mac_res; mult_state_n = AHBH; end endcase end AHBH: begin //only MD_OP_MULH here //ah*bh mult_op_a = op_a_i[`OP_H]; mult_op_b = op_b_i[`OP_H]; sign_a = signed_mode_i[0] & op_a_i[31]; sign_b = signed_mode_i[1] & op_b_i[31]; accum[17:0 ] = mac_res_q[33:16]; accum[33:18] = {18{signed_mult & mac_res_q[33]}}; //result of AH*BL is not signed only if signed_mode_i == 2'b00 mac_res_n = mac_res; mult_state_n = ALBL; mult_is_ready = 1'b1; end default:; endcase // mult_state_q end endmodule // zeroriscy_mult