blob: 4a937ef34c10a39cad68a7705e9cdc341420664e [file] [log] [blame]
package core
import chisel3._
import chisel3.tester._
import components.OpCode._
import org.scalatest.FreeSpec
import utils.MathUtils.BinStrToInt
import scala.io.Source
import scala.util.Random
import scala.util.control.Breaks._
class CoreTests extends FreeSpec with ChiselScalatestTester {
def asm(opcode: Int, rd: Int = 0, rs1: Int = 0, rs2: Int = 0): Int =
((opcode & b"1111 111") << 9) |
((rd & b"111") << 6) |
((rs1 & b"111") << 3) |
((rs2 & b"111") << 0)
// `program` is an array of 16-bit memory words
def handleIFetch(dut: Core, program: Array[Int], verbose: Boolean = false): Unit = {
val prog = program :+ 0 // Allows fetching last instruction in program if it is a short-style instruction
breakable {
while (true) {
while (!dut.io.ibus_stb.peek().litToBoolean) {
dut.clock.step()
if (dut.io.halted.peek().litToBoolean) break
}
val addr = dut.io.ibus_addr.peek().litValue().toInt
val result = (BigInt(prog(addr >> 1)) << 16) | BigInt(prog((addr >> 1) + 1)) // 32-bit fetch
for (_ <- 0 until (Random.nextInt(3) + 2)) { // 2-5 cycle latency
dut.clock.step()
dut.io.ibus_stb.expect(false.B)
}
dut.io.ibus_data_rd.poke(result.U)
dut.io.ibus_ack.poke(true.B)
dut.clock.step()
dut.io.ibus_ack.poke(false.B)
if (verbose) {
println(f"IF[0x$addr%04X] => 0x$result%08X")
}
}
}
}
// `data` is 64K-element array of 16-bit memory words
// it will be modified upon stores
def handleDMem(dut: Core, data: collection.mutable.Map[Int, Int], verbose: Boolean = false): Unit = {
breakable {
while (true) {
while (!dut.io.dbus_stb.peek().litToBoolean) {
dut.clock.step()
if (dut.io.halted.peek().litToBoolean) break
}
val addr = (dut.io.dbus_addr.peek().litValue().toInt >> 1) << 1
var result = 0
if (dut.io.dbus_we.peek().litToBoolean) {
data.put(addr, dut.io.dbus_data_wr.peek().litValue().toInt)
if (verbose) {
println(f"M[0x$addr%04X] <= 0x${dut.io.dbus_data_wr.peek().litValue().toInt}%08X")
}
} else {
result = data.getOrElse(addr, 0)
if (verbose) {
println(f"M[0x$addr%04X] => 0x$result%08X")
}
}
for (_ <- 0 until (Random.nextInt(3) + 2)) { // 2-5 cycle latency
dut.clock.step()
dut.io.dbus_stb.expect(false.B)
}
dut.io.dbus_data_rd.poke(result.U)
dut.io.dbus_ack.poke(true.B)
dut.clock.step()
dut.io.dbus_ack.poke(false.B)
}
}
}
def waitForHalt(dut: Core): Unit = {
while (!dut.io.halted.peek().litToBoolean) dut.clock.step()
}
"should fetch instructions" in {
test(new Core(0x0000)) { dut =>
val rand = new Random
dut.io.ibus_ack.poke(false.B)
dut.io.dbus_ack.poke(false.B)
dut.io.restart_core.poke(false.B)
dut.io.waiting.expect(false.B)
dut.io.halted.expect(false.B)
// Expect instruction fetch
dut.io.ibus_stb.expect(true.B)
dut.io.ibus_addr.expect(0.U)
for (_ <- 0 until (Random.nextInt(10) + 2)) {
dut.clock.step()
dut.io.ibus_stb.expect(false.B)
}
dut.io.ibus_ack.poke(true.B)
dut.io.ibus_data_rd.poke(0.U) // ADD r0, r0, r0
var i = 0
while (!dut.io.ibus_stb.peek().litToBoolean) {
dut.clock.step()
i += 1
}
println(s"ADD r0, r0, r0 took $i cycles")
}
}
"should issue loads" in {
test(new Core(0x0000)) { dut =>
dut.io.ibus_ack.poke(false.B)
dut.io.dbus_ack.poke(false.B)
dut.io.restart_core.poke(false.B)
dut.io.waiting.expect(false.B)
dut.io.halted.expect(false.B)
val program = Array (
b"0010 100 001 000 000", // LW r1, r0
0x1234,
b"1111 111 000 000 000", // STOP
)
fork { handleIFetch(dut, program, verbose = true) } .fork {
while(!dut.io.dbus_stb.peek().litToBoolean) dut.clock.step(1)
println(f"LOAD, 0x${dut.io.dbus_addr.peek().litValue().toInt}%04X")
dut.clock.step(2)
dut.io.dbus_data_rd.poke(0.U)
dut.io.dbus_ack.poke(true.B)
dut.clock.step(1)
dut.io.dbus_ack.poke(false.B)
}.join()
}
}
"should issue loads and stores" in {
test(new Core(0x0000)) { dut =>
dut.io.ibus_ack.poke(false.B)
dut.io.dbus_ack.poke(false.B)
dut.io.restart_core.poke(false.B)
dut.io.waiting.expect(false.B)
dut.io.halted.expect(false.B)
val program = Array (
asm(LW, rd = 1, rs1 = 0),
0x1000,
asm(SW, rs1 = 0, rs2 = 1),
0x1002,
asm(STOP)
)
val dmem = collection.mutable.Map (
0x1000 -> 0xABCD
)
fork { handleIFetch(dut, program, verbose = true) }
.fork { handleDMem(dut, dmem, verbose = true) } .fork {
waitForHalt(dut)
assert(dmem(0x1002) == dmem(0x1000))
}.join()
}
}
"should pass a test suite" in {
test(new Core(0x0000)) { dut =>
dut.io.ibus_ack.poke(false.B)
dut.io.dbus_ack.poke(false.B)
dut.io.restart_core.poke(false.B)
dut.io.waiting.expect(false.B)
dut.io.halted.expect(false.B)
val memfile = Source.fromFile("testsuite.hex")
val program = memfile.getLines.map(x => Integer.parseInt(x, 16)).toArray
memfile.close()
val dmem = collection.mutable.Map (
0x1000 -> 0xABCD,
0x9000 -> 0x0000
)
fork { handleIFetch(dut, program, verbose = true) }
.fork { handleDMem(dut, dmem, verbose = true) } .fork {
waitForHalt(dut)
if (dmem(0x9000) == 0xABCD) {
println("All tests passed")
} else if (dmem(0x9000) == 0x0001) {
println(s"Failed at test ${dmem(0x9002)}")
} else {
println("Failure: halted without reporting success or failure")
}
}.join()
}
}
/*"Gcd should calculate proper greatest common denominator" in {
test(new DecoupledGcd(16)) { dut =>
dut.input.initSource()
dut.input.setSourceClock(dut.clock)
dut.output.initSink()
dut.output.setSinkClock(dut.clock)
val testValues = for { x <- 0 to 10; y <- 0 to 10} yield (x, y)
val inputSeq = testValues.map { case (x, y) => (new GcdInputBundle(16)).Lit(_.value1 -> x.U, _.value2 -> y.U) }
val resultSeq = testValues.map { case (x, y) =>
(new GcdOutputBundle(16)).Lit(_.value1 -> x.U, _.value2 -> y.U, _.gcd -> BigInt(x).gcd(BigInt(y)).U)
}
fork {
// push inputs into the calculator, stall for 11 cycles one third of the way
val (seq1, seq2) = inputSeq.splitAt(resultSeq.length / 3)
dut.input.enqueueSeq(seq1)
dut.clock.step(11)
dut.input.enqueueSeq(seq2)
}.fork {
// retrieve computations from the calculator, stall for 10 cycles one half of the way
val (seq1, seq2) = resultSeq.splitAt(resultSeq.length / 2)
dut.output.expectDequeueSeq(seq1)
dut.clock.step(10)
dut.output.expectDequeueSeq(seq2)
}.join()
}
}*/
}