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