Merge "Add floating point regfile."
diff --git a/hdl/chisel/src/common/BUILD b/hdl/chisel/src/common/BUILD
index 2ba064c..d2f256d 100644
--- a/hdl/chisel/src/common/BUILD
+++ b/hdl/chisel/src/common/BUILD
@@ -35,6 +35,7 @@
     srcs = [
         "Fp.scala",
     ],
+    visibility = ["//visibility:public"],
 )
 
 chisel_test(
@@ -56,6 +57,7 @@
         ":common",
         ":fp",
     ],
+    visibility = ["//visibility:public"],
 )
 
 chisel_test(
diff --git a/hdl/chisel/src/kelvin/BUILD b/hdl/chisel/src/kelvin/BUILD
index 76d2cda..a8eb9d6 100644
--- a/hdl/chisel/src/kelvin/BUILD
+++ b/hdl/chisel/src/kelvin/BUILD
@@ -12,11 +12,33 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-load("@kelvin_hw//rules:chisel.bzl", "chisel_cc_library", "chisel_library")
+load("@kelvin_hw//rules:chisel.bzl", "chisel_cc_library", "chisel_library",
+     "chisel_test")
 
 package(default_visibility = ["//visibility:public"])
 
 chisel_library(
+    name = "kelvin_float",
+    srcs = [
+        "scalar/FRegfile.scala",
+    ],
+    deps = [
+        "//hdl/chisel/src/common:fp",
+    ],
+)
+
+chisel_test(
+    name = "kelvin_float_tests",
+    srcs = [
+        "scalar/FRegfileTest.scala",
+    ],
+    deps = [
+        "//hdl/chisel/src/common:fp",
+        ":kelvin_float",
+    ],
+)
+
+chisel_library(
     name = "kelvin",
     srcs = [
         "Axi.scala",
diff --git a/hdl/chisel/src/kelvin/scalar/FRegfile.scala b/hdl/chisel/src/kelvin/scalar/FRegfile.scala
new file mode 100644
index 0000000..5680aa3
--- /dev/null
+++ b/hdl/chisel/src/kelvin/scalar/FRegfile.scala
@@ -0,0 +1,71 @@
+// 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 common.Fp32
+
+class FRegfileRead extends Bundle {
+  val valid = Input(Bool())
+  val addr  = Input(UInt(5.W))
+  val data  = Output(new Fp32)
+}
+
+class FRegfileWrite extends Bundle {
+  val valid = Input(Bool())
+  val addr  = Input(UInt(5.W))
+  val data  = Input(new Fp32)
+}
+
+class FRegfile(n_read: Int, n_write: Int) extends Module {
+  val io = IO(new Bundle {
+    val read_ports = Vec(n_read, new FRegfileRead)
+    val write_ports = Vec(n_write, new FRegfileWrite)
+
+    val scoreboard_set = Input(UInt(32.W))
+    val scoreboard = Output(UInt(32.W))
+    val exception = Output(Bool())
+  })
+
+  val fregfile = Reg(Vec(32, new Fp32))
+  val scoreboard = RegInit(0.U(32.W))
+
+  // Update scoreboard
+  val scoreboard_clr = io.write_ports.map(x =>
+      Mux(x.valid, UIntToOH(x.addr), 0.U)).reduce(_|_)
+  scoreboard := (scoreboard & ~scoreboard_clr) | io.scoreboard_set
+  io.scoreboard := scoreboard
+
+  // Writes
+  val register_write_error = Wire(Vec(32, Bool()))
+  for (i <- 0 until 32) {
+    val valid = io.write_ports.map(x => x.valid & x.addr === i.U)
+    val data = PriorityMux(valid, io.write_ports.map(_.data))
+    register_write_error(i) := PopCount(valid) > 1.U
+    when (valid.reduce(_|_)) {
+      fregfile(i) := data
+    }
+  }
+  io.exception := register_write_error.reduce(_|_)
+
+  // Reads
+  for (i <- 0 until n_read) {
+    val read_port = io.read_ports(i)
+    read_port.data := Mux(read_port.valid,
+                          fregfile(read_port.addr),
+                          Fp32.Zero(false.B))
+  }
+}
diff --git a/hdl/chisel/src/kelvin/scalar/FRegfileTest.scala b/hdl/chisel/src/kelvin/scalar/FRegfileTest.scala
new file mode 100644
index 0000000..49f2668
--- /dev/null
+++ b/hdl/chisel/src/kelvin/scalar/FRegfileTest.scala
@@ -0,0 +1,179 @@
+// 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 FRegfileSpec extends AnyFreeSpec with ChiselScalatestTester {
+  "Initialization" in {
+    test(new FRegfile(1, 1)) { dut =>
+      assertResult(0) { dut.io.scoreboard.peekInt() }
+      for (i <- 0 until 32) {
+        dut.io.read_ports(0).valid.poke(true.B)
+        dut.io.read_ports(0).addr.poke(i)
+        assertResult(0) { dut.io.read_ports(0).data.sign.peekInt() }
+        assertResult(0) { dut.io.read_ports(0).data.exponent.peekInt() }
+        assertResult(0) { dut.io.read_ports(0).data.mantissa.peekInt() }
+      }
+    }
+  }
+
+  "Basic read/write" in {
+    test(new FRegfile(1, 1)) { dut =>
+      for (i <- 0 until 32) {
+        dut.io.write_ports(0).valid.poke(true.B)
+        dut.io.write_ports(0).addr.poke(i)
+        dut.io.write_ports(0).data.sign.poke(0)
+        dut.io.write_ports(0).data.exponent.poke(i+127)
+        dut.io.write_ports(0).data.mantissa.poke(0)
+        dut.clock.step()
+      }
+
+      for (i <- 0 until 32) {
+        dut.io.read_ports(0).valid.poke(true.B)
+        dut.io.read_ports(0).addr.poke(i)
+        assertResult(0) { dut.io.read_ports(0).data.sign.peekInt() }
+        assertResult(i+127) { dut.io.read_ports(0).data.exponent.peekInt() }
+        assertResult(0) { dut.io.read_ports(0).data.mantissa.peekInt() }
+      }
+    }
+  }
+
+  "Multiread" in {
+    test(new FRegfile(2, 1)) { dut =>
+      for (i <- 0 until 32) {
+        dut.io.write_ports(0).valid.poke(true.B)
+        dut.io.write_ports(0).addr.poke(i)
+        dut.io.write_ports(0).data.sign.poke(0)
+        dut.io.write_ports(0).data.exponent.poke(i+127)
+        dut.io.write_ports(0).data.mantissa.poke(0)
+        dut.clock.step()
+      }
+
+      dut.io.write_ports(0).valid.poke(false.B)
+
+      dut.io.read_ports(0).valid.poke(true.B)
+      dut.io.read_ports(0).addr.poke(0)
+      assertResult(0) { dut.io.read_ports(0).data.sign.peekInt() }
+      assertResult(127) { dut.io.read_ports(0).data.exponent.peekInt() }
+      assertResult(0) { dut.io.read_ports(0).data.mantissa.peekInt() }
+
+      dut.io.read_ports(1).valid.poke(true.B)
+      dut.io.read_ports(1).addr.poke(20)
+      assertResult(0) { dut.io.read_ports(1).data.sign.peekInt() }
+      assertResult(147) { dut.io.read_ports(1).data.exponent.peekInt() }
+      assertResult(0) { dut.io.read_ports(1).data.mantissa.peekInt() }
+    }
+  }
+
+  "Multiwrite" in {
+    test(new FRegfile(2, 2)) { dut =>
+      for (i <- 0 until 32) {
+        dut.io.write_ports(0).valid.poke(true.B)
+        dut.io.write_ports(0).addr.poke(i)
+        dut.io.write_ports(0).data.sign.poke(0)
+        dut.io.write_ports(0).data.exponent.poke(0)
+        dut.io.write_ports(0).data.mantissa.poke(0)
+        dut.clock.step()
+      }
+
+      dut.io.write_ports(0).valid.poke(true.B)
+      dut.io.write_ports(0).addr.poke(3)
+      dut.io.write_ports(0).data.sign.poke(0)
+      dut.io.write_ports(0).data.exponent.poke(37)
+      dut.io.write_ports(0).data.mantissa.poke(44)
+
+      dut.io.write_ports(1).valid.poke(true.B)
+      dut.io.write_ports(1).addr.poke(12)
+      dut.io.write_ports(1).data.sign.poke(0)
+      dut.io.write_ports(1).data.exponent.poke(14)
+      dut.io.write_ports(1).data.mantissa.poke(560)
+
+      dut.clock.step()
+
+      dut.io.read_ports(0).valid.poke(true.B)
+      dut.io.read_ports(0).addr.poke(3)
+      assertResult(0) { dut.io.read_ports(0).data.sign.peekInt() }
+      assertResult(37) { dut.io.read_ports(0).data.exponent.peekInt() }
+      assertResult(44) { dut.io.read_ports(0).data.mantissa.peekInt() }
+
+      dut.io.read_ports(1).valid.poke(true.B)
+      dut.io.read_ports(1).addr.poke(12)
+      assertResult(0) { dut.io.read_ports(1).data.sign.peekInt() }
+      assertResult(14) { dut.io.read_ports(1).data.exponent.peekInt() }
+      assertResult(560) { dut.io.read_ports(1).data.mantissa.peekInt() }
+    }
+  }
+
+  "Scoreboard" in {
+    test(new FRegfile(1, 2)) { dut =>
+      assertResult(0) { dut.io.scoreboard.peekInt() }
+      dut.io.scoreboard_set.poke(31)
+      dut.clock.step()
+      assertResult(31) { dut.io.scoreboard.peekInt() }
+
+      // Clear the two LSBs
+      dut.io.scoreboard_set.poke(0)
+      dut.io.write_ports(0).valid.poke(true.B)
+      dut.io.write_ports(0).addr.poke(0)
+      dut.io.write_ports(1).valid.poke(true.B)
+      dut.io.write_ports(1).addr.poke(1)
+      dut.clock.step()
+      assertResult(28) { dut.io.scoreboard.peekInt() }
+
+      // Clear the two entries and set 1 in the same cycle
+      dut.io.scoreboard_set.poke(1)
+      dut.io.write_ports(0).valid.poke(true.B)
+      dut.io.write_ports(0).addr.poke(2)
+      dut.io.write_ports(1).valid.poke(true.B)
+      dut.io.write_ports(1).addr.poke(3)
+      dut.clock.step()
+      assertResult(17) { dut.io.scoreboard.peekInt() }
+    }
+  }
+
+  "Multiwrite Exception" in {
+    test(new FRegfile(2, 2)) { dut =>
+      for (i <- 0 until 32) {
+        dut.io.write_ports(0).valid.poke(true.B)
+        dut.io.write_ports(0).addr.poke(i)
+        dut.io.write_ports(0).data.sign.poke(0)
+        dut.io.write_ports(0).data.exponent.poke(0)
+        dut.io.write_ports(0).data.mantissa.poke(0)
+        dut.clock.step()
+      }
+
+      dut.io.write_ports(0).valid.poke(true.B)
+      dut.io.write_ports(0).addr.poke(3)
+      dut.io.write_ports(0).data.sign.poke(0)
+      dut.io.write_ports(0).data.exponent.poke(37)
+      dut.io.write_ports(0).data.mantissa.poke(44)
+
+      dut.io.write_ports(1).valid.poke(true.B)
+      dut.io.write_ports(1).addr.poke(3)
+      dut.io.write_ports(1).data.sign.poke(0)
+      dut.io.write_ports(1).data.exponent.poke(14)
+      dut.io.write_ports(1).data.mantissa.poke(560)
+
+      assertResult(1) { dut.io.exception.peekInt() }
+    }
+  }
+}