From 86747fd79075107bf20557ae25c00ba4aeee923f Mon Sep 17 00:00:00 2001
From: Minyong Li <ml10g20@soton.ac.uk>
Date: Sun, 13 Jun 2021 23:35:36 +0100
Subject: [PATCH] core: impl ProgramMemory{,Test}

---
 .../ac/soton/ecs/can/core/ProgramMemory.scala |  36 +++++
 .../ecs/can/core/ProgramMemoryTest.scala      | 137 ++++++++++++++++++
 2 files changed, 173 insertions(+)
 create mode 100644 src/main/scala/uk/ac/soton/ecs/can/core/ProgramMemory.scala
 create mode 100644 src/test/scala/uk/ac/soton/ecs/can/core/ProgramMemoryTest.scala

diff --git a/src/main/scala/uk/ac/soton/ecs/can/core/ProgramMemory.scala b/src/main/scala/uk/ac/soton/ecs/can/core/ProgramMemory.scala
new file mode 100644
index 0000000..59ace23
--- /dev/null
+++ b/src/main/scala/uk/ac/soton/ecs/can/core/ProgramMemory.scala
@@ -0,0 +1,36 @@
+package uk.ac.soton.ecs.can.core
+
+import chisel3._
+
+class ProgramMemory(addrWidth: Int, cwWidth: Int, size: Int) extends Module {
+  val io = IO(new Bundle {
+    val br = new Bundle {
+      val abs = Input(Bool())
+      val rel = Input(Bool())
+      val addr = Input(UInt(addrWidth.W))
+    }
+    val cw = Output(UInt(cwWidth.W))
+    val write = new Bundle {
+      val en = Input(Bool())
+      val addr = Input(UInt(addrWidth.W))
+      val data = Input(UInt(cwWidth.W))
+    }
+  })
+
+  val mem = SyncReadMem(size, UInt(cwWidth.W))
+  val pc = RegInit(0.U(addrWidth.W))
+
+  when(io.write.en) {
+    mem(io.write.addr) := io.write.data
+  }
+
+  when(io.br.abs) {
+    pc := io.br.addr.asUInt()
+  }.elsewhen(io.br.rel) {
+    pc := (pc.asSInt() + io.br.addr.asSInt()).asUInt()
+  }.otherwise {
+    pc := pc + 1.U
+  }
+
+  io.cw := mem(pc)
+}
diff --git a/src/test/scala/uk/ac/soton/ecs/can/core/ProgramMemoryTest.scala b/src/test/scala/uk/ac/soton/ecs/can/core/ProgramMemoryTest.scala
new file mode 100644
index 0000000..5138606
--- /dev/null
+++ b/src/test/scala/uk/ac/soton/ecs/can/core/ProgramMemoryTest.scala
@@ -0,0 +1,137 @@
+package uk.ac.soton.ecs.can.core
+
+import org.scalatest._
+import chiseltest._
+import chisel3._
+
+class ProgramMemoryTest extends FlatSpec with ChiselScalatestTester {
+  private val addrWidth = 8
+  private val cwWidth = 16
+  private val memMap = Seq(
+    "h00".U(addrWidth.W) -> "h0000".U(cwWidth.W),
+    "h01".U(addrWidth.W) -> "h1234".U(cwWidth.W),
+    "h02".U(addrWidth.W) -> "h2468".U(cwWidth.W),
+    "h03".U(addrWidth.W) -> "h369a".U(cwWidth.W),
+    "h04".U(addrWidth.W) -> "h4c2d".U(cwWidth.W),
+    "h05".U(addrWidth.W) -> "h59f7".U(cwWidth.W),
+    "h06".U(addrWidth.W) -> "h6efc".U(cwWidth.W),
+    "h07".U(addrWidth.W) -> "h7fff".U(cwWidth.W)
+  )
+  private val size = memMap.length
+
+  private def initMemory(pm: ProgramMemory): Unit = {
+    pm.reset.poke(true.B)
+    pm.io.write.en.poke(true.B)
+
+    memMap.foreach { m =>
+      pm.io.write.addr.poke(m._1)
+      pm.io.write.data.poke(m._2)
+      pm.clock.step()
+    }
+
+    pm.io.write.en.poke(false.B)
+    pm.reset.poke(false.B)
+  }
+
+  it should "be writable and readable as PC increments" in {
+    test(new ProgramMemory(addrWidth, cwWidth, size)) { c =>
+      c.io.br.abs.poke(false.B)
+      c.io.br.rel.poke(false.B)
+      c.io.br.addr.poke(0.U(addrWidth.W))
+
+      initMemory(c)
+
+      c.io.cw.expect("h0000".U(cwWidth.W))
+      c.clock.step()
+
+      // NOTE: FPGA block RAM is synchronous-read. At this moment a new value
+      // has been fetched, but this 1-cycle delay exists because it hasn't been
+      // stored into the read register yet.
+      c.io.cw.expect("h0000".U(cwWidth.W))
+      c.clock.step()
+
+      memMap.takeRight(memMap.length - 1).foreach { m =>
+        c.io.cw.expect(m._2)
+        c.clock.step()
+      }
+    }
+  }
+
+  it should "correctly do relative branch" in {
+    test(new ProgramMemory(addrWidth, cwWidth, size)) { c =>
+      c.io.br.abs.poke(false.B)
+      c.io.br.rel.poke(false.B)
+      c.io.br.addr.poke(0.U(addrWidth.W))
+
+      initMemory(c)
+
+      c.io.cw.expect("h0000".U(cwWidth.W))
+      c.clock.step()
+
+      memMap.take(3).foreach { m =>
+        c.io.cw.expect(m._2)
+        c.clock.step()
+      }
+
+      // @ 0x03 -> 0x369a
+      c.io.cw.expect("h369a".U(cwWidth.W))
+
+      // > 0x06 -> 0x6efc
+      // NOTE: Because of the synchronous BRAM, a 1-cycle delay slot is
+      // introduced. At this moment 0x04 has been fetched, so the offset should
+      // be calculated based on 0x04 rather than 0x03. Here 4 + 2 = 6.
+      c.io.br.addr.poke(2.U(addrWidth.W))
+      c.io.br.rel.poke(true.B)
+      c.clock.step()
+      c.io.br.rel.poke(false.B)
+
+      // 0x04 is now present, but 0x06 has been fetched
+      c.io.cw.expect("h4c2d".U(cwWidth.W))
+      c.clock.step()
+
+      // @ 0x06 now
+      memMap.takeRight(2).foreach { m =>
+        c.io.cw.expect(m._2)
+        c.clock.step()
+      }
+    }
+  }
+
+  it should "do absolute branch correctly" in {
+    test(new ProgramMemory(addrWidth, cwWidth, size)) { c =>
+      c.io.br.abs.poke(false.B)
+      c.io.br.rel.poke(false.B)
+      c.io.br.addr.poke(0.U(addrWidth.W))
+
+      initMemory(c)
+
+      c.io.cw.expect("h0000".U(cwWidth.W))
+      c.clock.step()
+
+      memMap.take(5).foreach { m =>
+        c.io.cw.expect(m._2)
+        c.clock.step()
+      }
+
+      // @ 0x05 -> 0x59f7
+      c.io.cw.expect("h59f7".U(cwWidth.W))
+
+      // > 0x01 -> 0x1234
+      c.io.br.addr.poke("h01".U(addrWidth.W))
+      c.io.br.abs.poke(true.B)
+      c.clock.step()
+      c.io.br.abs.poke(false.B)
+
+      // Delay slot: 0x06 will present no matter what
+      c.io.cw.expect("h6efc".U(cwWidth.W))
+      c.clock.step()
+
+      // @ 0x01 now
+      memMap.takeRight(memMap.length - 1).foreach { m =>
+        c.io.cw.expect(m._2)
+        c.clock.step()
+      }
+    }
+  }
+
+}
-- 
GitLab