summaryrefslogtreecommitdiff
path: root/src/main/scala/Chisel/Tester.scala
diff options
context:
space:
mode:
authorJim Lawson2015-05-11 10:04:01 -0700
committerJim Lawson2015-07-24 15:50:53 -0700
commitb208bfb5691c7b5921dd47d0b599726872acd1cd (patch)
tree5d8695f13db41a807622dfdc93c1b6841911acc8 /src/main/scala/Chisel/Tester.scala
parentcaa7602b878c03c47fd263550e37715f1a67f854 (diff)
move source files under Chisel folder - eclipse compatibility
Diffstat (limited to 'src/main/scala/Chisel/Tester.scala')
-rw-r--r--src/main/scala/Chisel/Tester.scala417
1 files changed, 417 insertions, 0 deletions
diff --git a/src/main/scala/Chisel/Tester.scala b/src/main/scala/Chisel/Tester.scala
new file mode 100644
index 00000000..bdeb0f68
--- /dev/null
+++ b/src/main/scala/Chisel/Tester.scala
@@ -0,0 +1,417 @@
+/*
+ Copyright (c) 2011, 2012, 2013, 2014 The Regents of the University of
+ California (Regents). All Rights Reserved. Redistribution and use in
+ source and binary forms, with or without modification, are permitted
+ provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above
+ copyright notice, this list of conditions and the following
+ two paragraphs of disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ two paragraphs of disclaimer in the documentation and/or other materials
+ provided with the distribution.
+ * Neither the name of the Regents nor the names of its contributors
+ may be used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
+ SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
+ ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
+ REGENTS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF
+ ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION
+ TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+ MODIFICATIONS.
+*/
+
+package Chisel
+import Chisel._
+import scala.math._
+import scala.collection.mutable.ArrayBuffer
+import scala.collection.mutable.HashMap
+import scala.util.Random
+import java.io.{File, IOException, InputStream, OutputStream, PrintStream}
+import java.lang.Double.longBitsToDouble
+import java.lang.Float.intBitsToFloat
+import java.lang.Double.doubleToLongBits
+import java.lang.Float.floatToIntBits
+import scala.sys.process._
+import scala.io.Source._
+import Literal._
+
+case class Poke(val node: Data, val index: Int, val value: BigInt);
+
+class Snapshot(val t: Int) {
+ val pokes = new ArrayBuffer[Poke]()
+}
+
+class ManualTester[+T <: Module]
+ (val c: T, val isT: Boolean = true) {
+ var testIn: InputStream = null
+ var testOut: OutputStream = null
+ var testErr: InputStream = null
+ val sb = new StringBuilder()
+ var delta = 0
+ var t = 0
+ var isTrace = isT
+
+ /**
+ * Waits until the emulator streams are ready. This is a dirty hack related
+ * to the way Process works. TODO: FIXME.
+ */
+ def waitForStreams() = {
+ var waited = 0
+ while (testOut == null || testIn == null || testErr == null) {
+ Thread.sleep(100)
+ if (waited % 10 == 0 && waited > 30) {
+ println("waiting for emulator process treams to be valid ...")
+ }
+ }
+ }
+
+ // TODO: MOVE TO SOMEWHERE COMMON TO BACKEND
+ def ensureDir(dir: String): String = {
+ val d = dir + (if (dir == "" || dir(dir.length-1) == '/') "" else "/")
+ new File(d).mkdirs()
+ d
+ }
+ def createOutputFile(name: String): java.io.FileWriter = {
+ val baseDir = ensureDir(Driver.targetDir)
+ new java.io.FileWriter(baseDir + name)
+ }
+
+ def puts(str: String) = {
+ while (testOut == null) { Thread.sleep(100) }
+ for (e <- str) testOut.write(e);
+ }
+
+ /**
+ * Sends a command to the emulator and returns the reply.
+ * The standard protocol treats a single line as a command, which always
+ * returns a single line of reply.
+ */
+ def emulatorCmd(str: String): String = {
+ // validate cmd
+ if (str contains "\n") {
+ System.err.print(s"emulatorCmd($str): command should not contain newline")
+ return "error"
+ }
+
+ waitForStreams()
+
+ // send command to emulator
+ for (e <- str) testOut.write(e);
+ testOut.write('\n');
+ testOut.flush()
+
+ // read output from emulator
+ var c = testIn.read
+ sb.clear()
+ while (c != '\n' && c != -1) {
+ if (c == 0) {
+ Thread.sleep(100)
+ }
+ sb += c.toChar
+ // Look for a "PRINT" command.
+ if (sb.length == 6 && sb.startsWith("PRINT ")) {
+ do {
+ c = testIn.read
+ sb += c.toChar
+ } while (c != ' ')
+ // Get the PRINT character count.
+ val printCommand = """^PRINT (\d+) """.r
+ val printCommand(nChars) = sb.toString
+ sb.clear()
+ for (i <- 0 until nChars.toInt) {
+ c = testIn.read
+ sb += c.toChar
+ }
+ System.out.print(sb.toString())
+ sb.clear()
+ }
+ c = testIn.read
+ }
+
+ // drain errors
+ try {
+ while(testErr.available() > 0) {
+ System.err.print(Character.toChars(testErr.read()))
+ }
+ } catch {
+ case e : IOException => testErr = null; println("ERR EXCEPTION")
+ }
+
+ if (sb == "error") {
+ System.err.print(s"FAILED: emulatorCmd($str): returned error")
+ ok = false
+ }
+ return sb.toString
+ }
+
+ /*
+ def setClocks(clocks: HashMap[Clock, Int]) {
+ var cmd = "set_clocks"
+ for (clock <- Driver.clocks) {
+ if (clock.srcClock == null) {
+ val s = BigInt(clocks(clock)).toString(16)
+ cmd = cmd + " " + s
+ }
+ }
+ emulatorCmd(cmd)
+ // TODO: check for errors in return
+ }
+ */
+
+ def doPeekBits(name: String, off: Int = -1): BigInt = {
+ if (name == "") {
+ println("Unable to peek data " + name) // TODO: USE DATA
+ -1
+ } else {
+ var cmd = ""
+ if (off != -1) {
+ cmd = "mem_peek " + name + " " + off;
+ } else {
+ cmd = "wire_peek " + name;
+ }
+ val s = emulatorCmd(cmd)
+ val rv = toLitVal(s)
+ if (isTrace) println(" PEEK " + name + " " + (if (off >= 0) (off + " ") else "") + "-> " + s)
+ rv
+ }
+ }
+
+ def peekBits(data: Data, off: Int = -1): BigInt = {
+ doPeekBits(data.debugName, off)
+ }
+
+ def signed_fix(dtype: Element, rv: BigInt): BigInt = {
+ val w = dtype.width
+ dtype match {
+ /* Any "signed" node */
+ case _: SInt | _ : Flo | _: Dbl => (if(rv >= (BigInt(1) << w - 1)) (rv - (BigInt(1) << w)) else rv)
+ /* anything else (i.e., UInt) */
+ case _ => (rv)
+ }
+ }
+
+ def peekAt[T <: Bits](data: Mem[T], off: Int): BigInt = {
+ // signed_fix(data(1), peekBits(data, off))
+ doPeekBits(data.debugName, off)
+ }
+
+ def peek(data: Bits): BigInt = {
+ signed_fix(data, peekBits(data))
+ }
+
+ def peek(data: Flo): Float = {
+ intBitsToFloat(peekBits(data).toInt)
+ }
+
+ def peek(data: Dbl): Double = {
+ longBitsToDouble(peekBits(data).toLong)
+ }
+
+ def peek(data: Aggregate /*, off: Int = -1 */): IndexedSeq[BigInt] = {
+ data.flatten.map(peek(_))
+ }
+
+ def reset(n: Int = 1) = {
+ emulatorCmd("reset " + n)
+ // TODO: check for errors in return
+ if (isTrace) println("RESET " + n)
+ }
+
+ def doPokeBits(name: String, x: BigInt, off: Int): Unit = {
+ if (name == "") {
+ println("Unable to poke data " + name) // TODO: data.toString
+ } else {
+
+ var cmd = ""
+ if (off != -1) {
+ cmd = "mem_poke " + name + " " + off;
+ } else {
+ cmd = "wire_poke " + name;
+ }
+ // Don't prefix negative numbers with "0x"
+ val radixPrefix = if (x < 0) " -0x" else " 0x"
+ val xval = radixPrefix + x.abs.toString(16)
+ cmd = cmd + xval
+ if (isTrace) {
+ println(" POKE " + name + " " + (if (off >= 0) (off + " ") else "") + "<- " + xval)
+ }
+ val rtn = emulatorCmd(cmd)
+ if (rtn != "ok") {
+ System.err.print(s"FAILED: poke(${name}) returned false")
+ ok = false
+ }
+ }
+ }
+
+ def pokeAt[T <: Bits](data: Mem[T], x: BigInt, off: Int): Unit = {
+ doPokeBits(data.debugName, x, off)
+ }
+
+ def pokeBits(data: Data, x: BigInt, off: Int = -1): Unit = {
+ doPokeBits(data.debugName, x, off)
+ }
+
+ def poke(data: Bits, x: BigInt): Unit = {
+ pokeBits(data, x)
+ }
+
+ def poke(data: Flo, x: Float): Unit = {
+ pokeBits(data, BigInt(floatToIntBits(x)))
+ }
+
+ def poke(data: Dbl, x: Double): Unit = {
+ pokeBits(data, BigInt(doubleToLongBits(x)))
+ }
+
+ def poke(data: Aggregate, x: Array[BigInt]): Unit = {
+ val kv = (data.flatten, x.reverse).zipped;
+ for ((x, y) <- kv)
+ poke(x, y)
+ }
+
+ def step(n: Int) = {
+ val target = t + n
+ val s = emulatorCmd("step " + n)
+ delta += s.toInt
+ if (isTrace) println("STEP " + n + " -> " + target)
+ t += n
+ }
+
+ def int(x: Boolean): BigInt = if (x) 1 else 0
+ def int(x: Int): BigInt = x
+ def int(x: Bits): BigInt = x.litValue()
+
+ var ok = true;
+ var failureTime = -1
+
+ def expect (good: Boolean, msg: String): Boolean = {
+ if (isTrace)
+ println(msg + " " + (if (good) "PASS" else "FAIL"))
+ if (!good) { ok = false; if (failureTime == -1) failureTime = t; }
+ good
+ }
+
+ def expect (data: Bits, expected: BigInt): Boolean = {
+ // val mask = (BigInt(1) << data) - 1
+ val got = peek(data)
+
+ // expect((got & mask) == (expected & mask),
+ expect(got == expected,
+ "EXPECT " + data.debugName + " <- 0x" + got.toString(16) + " == 0x" + expected.toString(16))
+ }
+
+ def expect (data: Aggregate, expected: Array[BigInt]): Boolean = {
+ val kv = (data.flatten, expected.reverse).zipped;
+ var allGood = true
+ for ((d, e) <- kv)
+ allGood = expect(d, e) && allGood
+ allGood
+ }
+
+ /* We need the following so scala doesn't use our "tolerant" Float version of expect.
+ */
+ def expect (data: Bits, expected: Int): Boolean = {
+ expect(data, BigInt(expected))
+ }
+ def expect (data: Bits, expected: Long): Boolean = {
+ expect(data, BigInt(expected))
+ }
+ def expect (data: Flo, expected: Double): Boolean = {
+ val got = peek(data)
+ expect(got == expected, "EXPECT " + data.debugName + " <- " + got + " == " + expected)
+ }
+ def expect (data: Dbl, expected: Double): Boolean = {
+ val got = peek(data)
+ expect(got == expected, "EXPECT " + data.debugName + " <- " + got + " == " + expected)
+ }
+
+ /* Compare the floating point value of a node with an expected floating point value.
+ * We will tolerate differences in the bottom bit.
+ */
+ def expect (data: Bits, expected: Float): Boolean = {
+ val gotBits = peek(data).toInt
+ val expectedBits = java.lang.Float.floatToIntBits(expected)
+ var gotFLoat = java.lang.Float.intBitsToFloat(gotBits)
+ var expectedFloat = expected
+ if (gotFLoat != expectedFloat) {
+ val gotDiff = gotBits - expectedBits
+ // Do we have a single bit difference?
+ if (abs(gotDiff) <= 1) {
+ expectedFloat = gotFLoat
+ }
+ }
+ expect(gotFLoat == expectedFloat,
+ "EXPECT " + data.debugName + " <- " + gotFLoat + " == " + expectedFloat)
+ }
+
+ val rnd = if (Driver.testerSeedValid) new Random(Driver.testerSeed) else new Random()
+ var process: Process = null
+
+ def start(): Process = {
+ val n = Driver.appendString(Some(c.name),Driver.chiselConfigClassName)
+ val target = Driver.targetDir + "/" + n
+ val cmd = target
+ println("OPENING " + cmd)
+ /*
+ (if (Driver.backend.isInstanceOf[FloBackend]) {
+ val dir = Driver.backend.asInstanceOf[FloBackend].floDir
+ val command = ArrayBuffer(dir + "fix-console", ":is-debug", "true", ":filename", target + ".hex", ":flo-filename", target + ".mwe.flo")
+ if (Driver.isVCD) { command ++= ArrayBuffer(":is-vcd-dump", "true") }
+ if (Driver.emitTempNodes) { command ++= ArrayBuffer(":emit-temp-nodes", "true") }
+ command ++= ArrayBuffer(":target-dir", Driver.targetDir)
+ command.mkString(" ")
+ } else {
+ target + (if (Driver.backend.isInstanceOf[VerilogBackend]) " -q +vcs+initreg+0 " else "")
+ })
+ */
+ println("SEED " + Driver.testerSeed)
+ println("STARTING " + cmd)
+ val processBuilder = Process(cmd)
+ val pio = new ProcessIO(in => testOut = in, out => testIn = out, err => testErr = err)
+ process = processBuilder.run(pio)
+ waitForStreams()
+ t = 0
+ reset(5)
+ // Skip vpd message
+ if (Driver.backend.isInstanceOf[VerilogBackend] && Driver.isDebug) {
+ var vpdmsg = testIn.read
+ while (vpdmsg != '\n' && vpdmsg != -1)
+ vpdmsg = testIn.read
+ }
+ process
+ }
+
+ def finish(): Boolean = {
+ if (process != null) {
+ emulatorCmd("quit")
+
+ if (testOut != null) {
+ testOut.flush()
+ testOut.close()
+ }
+ if (testIn != null) {
+ testIn.close()
+ }
+ if (testErr != null) {
+ testErr.close()
+ }
+
+ process.destroy()
+ }
+ println("RAN " + t + " CYCLES " + (if (ok) "PASSED" else { "FAILED FIRST AT CYCLE " + failureTime }))
+ ok
+ }
+}
+
+class Tester[+T <: Module](c: T, isTrace: Boolean = true) extends ManualTester(c, isTrace) {
+ start()
+}
+