Add floating point regfile. Change-Id: Id1a262ad13cca7aaa6d9fdf6bc741378ef6a208e
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 b206de7..be34cc6 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", @@ -191,4 +213,4 @@ chisel_lib = ":kelvin", emit_class = "kelvin.EmitVSt", module_name = "VSt", -) \ No newline at end of file +)
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() } + } + } +}