| // Copyright 2024 Google LLC | |
| // | |
| // 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. | |
| package common | |
| import chisel3._ | |
| import chisel3.util._ | |
| class FmaCmd extends Bundle { | |
| val ina = new Fp32 | |
| val inb = new Fp32 | |
| val inc = new Fp32 | |
| } | |
| class FmaState1 extends Bundle { | |
| // Multiply variables | |
| val ab_inf = Bool() | |
| val ab_sign = Bool() | |
| val exponent = SInt(10.W) | |
| val significand = UInt(48.W) | |
| // Addition variables | |
| val c_inf = Bool() | |
| val c_significand = UInt(48.W) | |
| val shift = SInt(11.W) | |
| val sub = Bool() | |
| val nan = Bool() | |
| } | |
| class FmaState2 extends Bundle { | |
| val ab_inf = Bool() | |
| val c_inf = Bool() | |
| val sign = Bool() | |
| val exponent = SInt(10.W) | |
| val significand = UInt(49.W) | |
| val nan = Bool() | |
| } | |
| object Fma { | |
| def apply(cmd: FmaCmd): Fp32 = { | |
| FmaStage3(FmaStage2(FmaStage1(cmd))) | |
| } | |
| def FmaStage1(cmd: FmaCmd): FmaState1 = { | |
| val state = Wire(new FmaState1) | |
| val ab_zero = cmd.ina.isZero() || cmd.inb.isZero() | |
| val ab_inf = cmd.ina.isInf() || cmd.inb.isInf() | |
| state.ab_inf := ab_inf | |
| state.c_inf := cmd.inc.isInf() | |
| // Compute ina * inb % normalization | |
| val ab_sign = cmd.ina.sign ^ cmd.inb.sign | |
| state.ab_sign := ab_sign | |
| state.significand := cmd.ina.significand() * cmd.inb.significand() | |
| val product_exponent = (cmd.ina.exponent +& cmd.inb.exponent).zext - 127.S | |
| // Preshift c for addition. | |
| val sum_shift = cmd.inc.exponent.zext -& product_exponent | |
| // Right pad c significand to match product, no propagation delay. | |
| val padded_c_significand = cmd.inc.significand << 23.U | |
| // Compute shift, saturate and take 6 bits to barrel shift. | |
| // We saturate to 6 bits max as ceil(log2(48)) = 5. | |
| val raw_right_shift = product_exponent -& cmd.inc.exponent.zext | |
| val right_shift = Clamp(raw_right_shift, 0.S, 63.S).asUInt | |
| state.c_significand := padded_c_significand >> right_shift(5, 0) | |
| state.shift := raw_right_shift | |
| // Mark next cycle as a subtraction if the signs of ab and c differ. | |
| state.sub := (ab_sign ^ cmd.inc.sign) | |
| // Take max exponent of (a*b) or c. The smaller of the two will be right | |
| // shifted (a*b in stage 2 or c in stage 2) | |
| state.exponent := Mux( | |
| raw_right_shift > 0.S, product_exponent, cmd.inc.exponent.zext) | |
| state.nan := cmd.ina.isNan() || cmd.inb.isNan() || cmd.inc.isNan() || | |
| (ab_zero && ab_inf) | |
| state | |
| } | |
| def FmaStage2(state1: FmaState1): FmaState2 = { | |
| val state2 = Wire(new FmaState2) | |
| // Variables to forward to next cycle. | |
| state2.ab_inf := state1.ab_inf | |
| state2.c_inf := state1.c_inf | |
| state2.exponent := state1.exponent | |
| // Inf - Inf = NaN | |
| state2.nan := state1.nan || (state1.ab_inf && state1.c_inf && state1.sub) | |
| // Compute shift, saturate and take 6 bits to barrel shift ab_significand. | |
| // Hopefully shift here matches propagation delay of potential C inversion. | |
| val shift = (Clamp(-state1.shift, 0.S, 63.S).asUInt)(5, 0) | |
| val ab_significand = (state1.significand >> shift).zext | |
| assert(ab_significand.getWidth == 49) | |
| // Zext and invert if necessary | |
| val c_significand = Mux( | |
| state1.sub, -(state1.c_significand.zext), state1.c_significand.zext) | |
| assert(c_significand.getWidth == 49) | |
| val significand_sum = ab_significand +& c_significand | |
| assert(significand_sum.getWidth == 50) | |
| val sign = significand_sum(49) | |
| val new_significand = (significand_sum.abs.asUInt)(48, 0) | |
| assert(new_significand.getWidth == 49) | |
| state2.sign := state1.ab_sign ^ sign | |
| state2.significand := new_significand | |
| state2 | |
| } | |
| def FmaStage3(state: FmaState2): Fp32 = { | |
| // Compute mantissa | |
| val left_shamt = | |
| PriorityEncoder(Cat(1.U(1.W), Reverse(state.significand)))(5,0) | |
| val shifted_significand = | |
| (state.significand << left_shamt)(state.significand.getWidth, 0) | |
| // Grab 25 bit significand | |
| val reduced_significand = shifted_significand(shifted_significand.getWidth - 1, | |
| shifted_significand.getWidth - 26) | |
| // Perform rounding step, going to 26 bits | |
| // TODO(derekjchow): Rounding mode | |
| val rounded_significand = reduced_significand +& 1.U(1.W) | |
| // Get new mantissa | |
| val mantissa = Mux(rounded_significand(25), | |
| rounded_significand(24, 2), | |
| rounded_significand(23, 1)) | |
| // Compute new exponent | |
| // The +2.S comes from two widening operations in previous stages | |
| val exponent = state.exponent - left_shamt.zext + 2.S + | |
| rounded_significand(25).asUInt.zext | |
| // Check for overflow. | |
| val inf = state.ab_inf || state.c_inf || (exponent >= (1 << 8).S) | |
| // Check for very small numbers that should round to zero. | |
| val zero = (reduced_significand === 0.U) || (exponent < 0.S) | |
| val nan = state.nan | |
| MuxCase( | |
| Fp32(state.sign, exponent(7, 0), mantissa), | |
| Array( | |
| nan -> Fp32.NaN(), | |
| inf -> Fp32.Inf(state.sign), | |
| zero -> Fp32.Zero(state.sign) | |
| )) | |
| } | |
| } |