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() }
+ }
+ }
+}