Add scalar FPU. Change-Id: Ibafa30b116a14bdebd04a38a350b96724e0ca22b
diff --git a/hdl/chisel/src/common/BUILD b/hdl/chisel/src/common/BUILD index d2f256d..5bab0ba 100644 --- a/hdl/chisel/src/common/BUILD +++ b/hdl/chisel/src/common/BUILD
@@ -49,9 +49,9 @@ ) chisel_library( - name = "fpu", + name = "fma", srcs = [ - "Fpu.scala", + "Fma.scala", ], deps = [ ":common", @@ -61,12 +61,12 @@ ) chisel_test( - name = "fpu_test", + name = "fma_test", srcs = [ - "FpuTest.scala", + "FmaTest.scala", ], deps = [ ":fp", - ":fpu", + ":fma", ], )
diff --git a/hdl/chisel/src/common/Fpu.scala b/hdl/chisel/src/common/Fma.scala similarity index 92% rename from hdl/chisel/src/common/Fpu.scala rename to hdl/chisel/src/common/Fma.scala index fc2f82e..eb07c2d 100644 --- a/hdl/chisel/src/common/Fpu.scala +++ b/hdl/chisel/src/common/Fma.scala
@@ -17,13 +17,13 @@ import chisel3._ import chisel3.util._ -class FpuCmd extends Bundle { +class FmaCmd extends Bundle { val ina = new Fp32 val inb = new Fp32 val inc = new Fp32 } -class FpuState1 extends Bundle { +class FmaState1 extends Bundle { // Multiply variables val ab_inf = Bool() val ab_sign = Bool() @@ -39,7 +39,7 @@ val nan = Bool() } -class FpuState2 extends Bundle { +class FmaState2 extends Bundle { val ab_inf = Bool() val c_inf = Bool() val sign = Bool() @@ -49,13 +49,13 @@ val nan = Bool() } -object Fpu { - def apply(cmd: FpuCmd): Fp32 = { - FpuStage3(FpuStage2(FpuStage1(cmd))) +object Fma { + def apply(cmd: FmaCmd): Fp32 = { + FmaStage3(FmaStage2(FmaStage1(cmd))) } - def FpuStage1(cmd: FpuCmd): FpuState1 = { - val state = Wire(new FpuState1) + 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() @@ -93,8 +93,8 @@ state } - def FpuStage2(state1: FpuState1): FpuState2 = { - val state2 = Wire(new FpuState2) + def FmaStage2(state1: FmaState1): FmaState2 = { + val state2 = Wire(new FmaState2) // Variables to forward to next cycle. state2.ab_inf := state1.ab_inf @@ -124,7 +124,7 @@ state2 } - def FpuStage3(state: FpuState2): Fp32 = { + def FmaStage3(state: FmaState2): Fp32 = { // Compute mantissa val left_shamt = PriorityEncoder(Cat(1.U(1.W), Reverse(state.significand)))(5,0)
diff --git a/hdl/chisel/src/common/FpuTest.scala b/hdl/chisel/src/common/FmaTest.scala similarity index 88% rename from hdl/chisel/src/common/FpuTest.scala rename to hdl/chisel/src/common/FmaTest.scala index 693ddd9..3d55e30 100644 --- a/hdl/chisel/src/common/FpuTest.scala +++ b/hdl/chisel/src/common/FmaTest.scala
@@ -20,13 +20,13 @@ import org.scalatest.freespec.AnyFreeSpec import chisel3.experimental.BundleLiterals._ -class FpuTester extends Module { +class FmaTester extends Module { val io = IO(new Bundle { val ina = Input(UInt(32.W)) val inb = Input(UInt(32.W)) val inc = Input(UInt(32.W)) - val state1 = Output(new FpuState1) - val state2 = Output(new FpuState2) + val state1 = Output(new FmaState1) + val state2 = Output(new FmaState2) val out = Output(new Fp32) }) @@ -34,19 +34,19 @@ val fp_b = Fp32.fromWord(io.inb) val fp_c = Fp32.fromWord(io.inc) - val cmd = Wire(new FpuCmd) + val cmd = Wire(new FmaCmd) cmd.ina := fp_a cmd.inb := fp_b cmd.inc := fp_c - val stage1 = Fpu.FpuStage1(cmd) - val stage2 = Fpu.FpuStage2(stage1) + val stage1 = Fma.FmaStage1(cmd) + val stage2 = Fma.FmaStage2(stage1) io.state1 := stage1 io.state2 := stage2 - io.out := Fpu.FpuStage3(stage2) + io.out := Fma.FmaStage3(stage2) } -class FpuSpec extends AnyFreeSpec with ChiselScalatestTester { +class FmaSpec extends AnyFreeSpec with ChiselScalatestTester { def Float2BigInt(x: Float): BigInt = { val abs = x.abs var int = BigInt(java.lang.Float.floatToIntBits(abs)) @@ -62,7 +62,7 @@ } "Mul Zero" in { - test(new FpuTester()) { dut => + test(new FmaTester()) { dut => dut.io.ina.poke(Float2BigInt(0)) dut.io.inb.poke(Float2BigInt(42)) dut.io.inc.poke(Float2BigInt(0)) @@ -73,7 +73,7 @@ } "Mul Identity" in { - test(new FpuTester()) { dut => + test(new FmaTester()) { dut => dut.io.ina.poke(Float2BigInt(1)) dut.io.inb.poke(Float2BigInt(42)) dut.io.inc.poke(Float2BigInt(0)) @@ -84,7 +84,7 @@ } "Mul Negative" in { - test(new FpuTester()) { dut => + test(new FmaTester()) { dut => dut.io.ina.poke(Float2BigInt(-1.0f)) dut.io.inb.poke(Float2BigInt(42)) dut.io.inc.poke(Float2BigInt(0)) @@ -95,7 +95,7 @@ } "Mul Half" in { - test(new FpuTester()) { dut => + test(new FmaTester()) { dut => dut.io.ina.poke(Float2BigInt(0.5f)) dut.io.inb.poke(Float2BigInt(42)) dut.io.inc.poke(Float2BigInt(0)) @@ -106,7 +106,7 @@ } "Mul Overflow" in { - test(new FpuTester()) { dut => + test(new FmaTester()) { dut => dut.io.ina.poke(Float2BigInt(2e30f)) dut.io.inb.poke(Float2BigInt(2e30f)) dut.io.inc.poke(Float2BigInt(0)) @@ -117,7 +117,7 @@ } "Mul Rounds to Zero" in { - test(new FpuTester()) { dut => + test(new FmaTester()) { dut => dut.io.ina.poke(Float2BigInt(1e-30f)) dut.io.inb.poke(Float2BigInt(1e-30f)) dut.io.inc.poke(Float2BigInt(0)) @@ -128,7 +128,7 @@ } "Mul NaN" in { - test(new FpuTester()) { dut => + test(new FmaTester()) { dut => dut.io.ina.poke(Float2BigInt(Float.NaN)) dut.io.inb.poke(Float2BigInt(4.0f)) dut.io.inc.poke(Float2BigInt(0)) @@ -139,7 +139,7 @@ } "Fma" in { - test(new FpuTester()) { dut => + test(new FmaTester()) { dut => dut.io.ina.poke(Float2BigInt(2.0f)) dut.io.inb.poke(Float2BigInt(1.5f)) dut.io.inc.poke(Float2BigInt(6.0f)) @@ -151,7 +151,7 @@ } "Fms" in { - test(new FpuTester()) { dut => + test(new FmaTester()) { dut => dut.io.ina.poke(Float2BigInt(2.0f)) dut.io.inb.poke(Float2BigInt(1.5f)) dut.io.inc.poke(Float2BigInt(-6.0f)) @@ -163,7 +163,7 @@ } "Fnma" in { - test(new FpuTester()) { dut => + test(new FmaTester()) { dut => dut.io.ina.poke(Float2BigInt(-2.0f)) dut.io.inb.poke(Float2BigInt(1.5f)) dut.io.inc.poke(Float2BigInt(13.5f)) @@ -175,7 +175,7 @@ } "Fnms" in { - test(new FpuTester()) { dut => + test(new FmaTester()) { dut => dut.io.ina.poke(Float2BigInt(-2.0f)) dut.io.inb.poke(Float2BigInt(1.5f)) dut.io.inc.poke(Float2BigInt(-13.5f)) @@ -187,7 +187,7 @@ } "Add" in { - test(new FpuTester()) { dut => + test(new FmaTester()) { dut => dut.io.ina.poke(Float2BigInt(9000.0f)) dut.io.inb.poke(Float2BigInt(1.0f)) dut.io.inc.poke(Float2BigInt(1.0f)) @@ -199,7 +199,7 @@ } "Sub" in { - test(new FpuTester()) { dut => + test(new FmaTester()) { dut => dut.io.ina.poke(Float2BigInt(15.0f)) dut.io.inb.poke(Float2BigInt(1.0f)) dut.io.inc.poke(Float2BigInt(-100.0f))
diff --git a/hdl/chisel/src/common/Fp.scala b/hdl/chisel/src/common/Fp.scala index 93bce28..a75201d 100644 --- a/hdl/chisel/src/common/Fp.scala +++ b/hdl/chisel/src/common/Fp.scala
@@ -43,6 +43,14 @@ def significand(): UInt = { Cat(exponent.orR, mantissa) } + + def negate(): Fp32 = { + val negated = Wire(new Fp32) + negated.sign := ~sign + negated.exponent := exponent + negated.mantissa := mantissa + negated + } } object Fp32 {
diff --git a/hdl/chisel/src/kelvin/BUILD b/hdl/chisel/src/kelvin/BUILD index a8eb9d6..516690a 100644 --- a/hdl/chisel/src/kelvin/BUILD +++ b/hdl/chisel/src/kelvin/BUILD
@@ -20,9 +20,12 @@ chisel_library( name = "kelvin_float", srcs = [ + "scalar/Fpu.scala", "scalar/FRegfile.scala", ], deps = [ + ":kelvin", + "//hdl/chisel/src/common:fma", "//hdl/chisel/src/common:fp", ], ) @@ -30,11 +33,13 @@ chisel_test( name = "kelvin_float_tests", srcs = [ + "scalar/FpuTest.scala", "scalar/FRegfileTest.scala", ], deps = [ - "//hdl/chisel/src/common:fp", + ":kelvin", ":kelvin_float", + "//hdl/chisel/src/common:fp", ], )
diff --git a/hdl/chisel/src/kelvin/Library.scala b/hdl/chisel/src/kelvin/Library.scala index 0919bff..5cafa8b 100644 --- a/hdl/chisel/src/kelvin/Library.scala +++ b/hdl/chisel/src/kelvin/Library.scala
@@ -328,3 +328,36 @@ } } } + +/** A bundle that extends Data with an addr field. The lifted Data type will be + * contained in the "bits" field. + * @param width The bit-width of the addr field. + * @param gen The type of data to wrap. + */ +class WithAddr[+T <: Data](width: Int, gen: T) extends Bundle { + val addr = UInt(width.W) + val bits = gen +} + +object WithAddr { + def apply[T <: Data](width: Int, gen: T): WithAddr[T] = new WithAddr(width, gen) + + def create[T <: Data](addr: UInt, bits: T) = { + val result = Wire(WithAddr(addr.getWidth, chiselTypeOf(bits))) + result.addr := addr + result.bits := bits + result + } +} + +/** A transformation that lifts a function f = (x => y) to a new function + * f = (WithAddr[x] => WithAddr[y]). The addr field of the lifted function + * is preserved between the inputs and outputs. + * @param width The bit-width of the addr field. + * @param f The function to lift. + */ +object LiftAddr { + def apply[X <: Data, Y <: Data](width: Int, f: X => Y) = { + (x: WithAddr[X]) => WithAddr.create(x.addr, f(x.bits)) + } +} \ No newline at end of file
diff --git a/hdl/chisel/src/kelvin/scalar/Fpu.scala b/hdl/chisel/src/kelvin/scalar/Fpu.scala new file mode 100644 index 0000000..7f639e4 --- /dev/null +++ b/hdl/chisel/src/kelvin/scalar/Fpu.scala
@@ -0,0 +1,74 @@ +// 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 kelvin + +import chisel3._ +import chisel3.experimental.BundleLiterals._ +import chisel3.util._ +import common.Fp32 +import common.Fma +import common.FmaCmd + +object FpuOptype extends ChiselEnum { + val FpuAdd = Value + val FpuSub = Value + val FpuMul = Value + val FpuFma = Value + val FpuFms = Value + val FpuFnma = Value + val FpuFnms = Value +} + +class FpuCmd extends Bundle { + val optype = FpuOptype() + val ina = new Fp32 + val inb = new Fp32 + val inc = new Fp32 + val waddr = UInt(5.W) +} + +object FpuCmd { + def ToFmaCmd(fpuCmd: FpuCmd): WithAddr[FmaCmd] = { + val invert_ab = (fpuCmd.optype === FpuOptype.FpuFnma) || + (fpuCmd.optype === FpuOptype.FpuFnms) + val invert_c = (fpuCmd.optype === FpuOptype.FpuSub) || + (fpuCmd.optype === FpuOptype.FpuFms) || + (fpuCmd.optype === FpuOptype.FpuFnms) + + val fmaCmd = Wire(WithAddr(5, new FmaCmd)) + fmaCmd.bits.ina := Mux(invert_ab, fpuCmd.ina.negate(), fpuCmd.ina) + fmaCmd.bits.inb := Mux((fpuCmd.optype === FpuOptype.FpuAdd) || + (fpuCmd.optype === FpuOptype.FpuSub), + Fp32(false.B, 127.U(8.W), 0.U(23.W)), + fpuCmd.inb) + fmaCmd.bits.inc := Mux((fpuCmd.optype === FpuOptype.FpuMul), + Fp32.fromWord(0.U(32.W)), + Mux(invert_c, fpuCmd.inc.negate(), fpuCmd.inc)) + fmaCmd.addr := fpuCmd.waddr + fmaCmd + } +} + +class Fpu extends Module { + val io = IO(new Bundle { + val cmd = Flipped(Decoupled(new FpuCmd)) + val output = Decoupled(WithAddr(5, new Fp32)) + }) + + val fmaCmd = io.cmd.map(FpuCmd.ToFmaCmd) + val state1 = fmaCmd.map(LiftAddr(5, Fma.FmaStage1)) + val state2 = Queue(state1, 1, true).map(LiftAddr(5, Fma.FmaStage2)) + io.output <> Queue(state2, 1, true).map(LiftAddr(5, Fma.FmaStage3)) +} \ No newline at end of file
diff --git a/hdl/chisel/src/kelvin/scalar/FpuTest.scala b/hdl/chisel/src/kelvin/scalar/FpuTest.scala new file mode 100644 index 0000000..6f5419e --- /dev/null +++ b/hdl/chisel/src/kelvin/scalar/FpuTest.scala
@@ -0,0 +1,94 @@ +// 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 kelvin + +import chisel3._ +import chisel3.util._ +import chiseltest._ +import org.scalatest.freespec.AnyFreeSpec +import chisel3.experimental.BundleLiterals._ +import common.Fp32 + +class FpuSpec extends AnyFreeSpec with ChiselScalatestTester { + def Float2Bits(x: Float): (Boolean, Int, Int) = { + val abs = x.abs + var int = java.lang.Float.floatToIntBits(abs) + + val sign: Boolean = (x < 0) + val exponent: Int = int >> 23 + val mantissa: Int = int & ((1 << 23) - 1) + + (sign, exponent, mantissa) + } + + def EnqueueValid(fpu: Fpu, op: FpuOptype.Type, ina: Float, inb: Float, inc: Float, waddr: Int) = { + fpu.io.cmd.valid.poke(1) + fpu.io.cmd.bits.optype.poke(op) + fpu.io.cmd.bits.waddr.poke(waddr) + + val ina_bits = Float2Bits(ina) + val inb_bits = Float2Bits(inb) + val inc_bits = Float2Bits(inc) + fpu.io.cmd.bits.ina.sign.poke(ina_bits._1) + fpu.io.cmd.bits.ina.exponent.poke(ina_bits._2) + fpu.io.cmd.bits.ina.mantissa.poke(ina_bits._3) + fpu.io.cmd.bits.inb.sign.poke(inb_bits._1) + fpu.io.cmd.bits.inb.exponent.poke(inb_bits._2) + fpu.io.cmd.bits.inb.mantissa.poke(inb_bits._3) + fpu.io.cmd.bits.inc.sign.poke(inc_bits._1) + fpu.io.cmd.bits.inc.exponent.poke(inc_bits._2) + fpu.io.cmd.bits.inc.mantissa.poke(inc_bits._3) + } + + def GetFloat(fpu: Fpu): Float = { + val sign = fpu.io.output.bits.bits.sign.peekInt().toInt + val exponent = fpu.io.output.bits.bits.exponent.peekInt().toInt + val mantissa = fpu.io.output.bits.bits.mantissa.peekInt().toInt + val int_val = (exponent << 23) + mantissa + val negate = if (sign == 1) -1.0f else 1.0f + + negate * java.lang.Float.intBitsToFloat(int_val) + } + + "Pipeline" in { + test(new Fpu) { dut => + dut.io.output.ready.poke(1) + EnqueueValid(dut, FpuOptype.FpuAdd, 1.0f, 0.0f, 1.0f, 1) + dut.clock.step() + EnqueueValid(dut, FpuOptype.FpuSub, 1.0f, 0.0f, -1.0f, 2) + dut.clock.step() + + assertResult(1) { dut.io.output.valid.peekInt() } + assertResult(1) { dut.io.output.bits.addr.peekInt() } + assertResult(2.0f) { GetFloat(dut) } + EnqueueValid(dut, FpuOptype.FpuMul, 1.0f, 1.0f, 0.0f, 3) + dut.clock.step() + + assertResult(1) { dut.io.output.valid.peekInt() } + assertResult(2) { dut.io.output.bits.addr.peekInt() } + assertResult(2.0f) { GetFloat(dut) } + dut.io.cmd.valid.poke(0) + dut.clock.step() + + + assertResult(1) { dut.io.output.valid.peekInt() } + assertResult(3) { dut.io.output.bits.addr.peekInt() } + assertResult(1.0f) { GetFloat(dut) } + dut.clock.step() + + assertResult(0) { dut.io.output.valid.peekInt() } + } + } +} \ No newline at end of file