From a76e77774976365e9717ab5272870c99bb63982c Mon Sep 17 00:00:00 2001 From: Henry Cook Date: Thu, 6 Aug 2015 12:35:05 -0700 Subject: subdir for Chisel.testers package --- src/main/scala/Chisel/AdvTester.scala | 223 -------------- src/main/scala/Chisel/Tester.scala | 406 -------------------------- src/main/scala/Chisel/testers/AdvTester.scala | 223 ++++++++++++++ src/main/scala/Chisel/testers/Driver.scala | 61 ++++ src/main/scala/Chisel/testers/Tester.scala | 379 ++++++++++++++++++++++++ 5 files changed, 663 insertions(+), 629 deletions(-) delete mode 100644 src/main/scala/Chisel/AdvTester.scala delete mode 100644 src/main/scala/Chisel/Tester.scala create mode 100644 src/main/scala/Chisel/testers/AdvTester.scala create mode 100644 src/main/scala/Chisel/testers/Driver.scala create mode 100644 src/main/scala/Chisel/testers/Tester.scala (limited to 'src') diff --git a/src/main/scala/Chisel/AdvTester.scala b/src/main/scala/Chisel/AdvTester.scala deleted file mode 100644 index 9520932b..00000000 --- a/src/main/scala/Chisel/AdvTester.scala +++ /dev/null @@ -1,223 +0,0 @@ -/* - 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. -*/ - -/* - Written by Stephen Twigg, Eric Love - Version 0.9 -*/ -package Chisel.testers -import Chisel._ -import scala.collection.mutable.ArrayBuffer - -class AdvTester[+T <: Module](val dut: T, isTrace: Boolean = false) extends Tester[T](dut, isTrace) { - val defaultMaxCycles = 1024 - var cycles = 0 - var pass = true - - // List of scala objects that need to be processed along with the test benches, like sinks and sources - val preprocessors = new ArrayBuffer[Processable]() - val postprocessors = new ArrayBuffer[Processable]() - // pre v post refers to when user-customized update code ('work') is processed - // e.g. sinks are in the preprocessing list and sources in the postprocessing list - // this allows the testbench to respond to a request within one cycle - - // This section of code lets testers easily emulate have registers right before dut inputs - // This testing style conforms with the general ASPIRE testbench style - // Also, to ensure difference enforced, poke 'deprecated' and replaced with wire_poke - def wire_poke(port: Bits, target: Boolean) = { super.poke(port, int(target)) } - def wire_poke(port: Bits, target: Int) = { super.poke(port, int(target)) } - def wire_poke(port: Bits, target: Long) = { super.poke(port, int(target)) } - def wire_poke(port: Bits, target: BigInt) = { super.poke(port, target) } - def wire_poke(port: Aggregate, target: Array[BigInt]) = { super.poke(port, target) } - - override def poke(port: Bits, target: BigInt) = require(false, "poke hidden for AdvTester, use wire_poke or reg_poke") - override def poke(port: Aggregate, target: Array[BigInt]) = require(false, "poke hidden for AdvTester, use wire_poke or reg_poke") - - val registered_bits_updates = new scala.collection.mutable.HashMap[Bits,BigInt]() - val registered_aggr_updates = new scala.collection.mutable.HashMap[Aggregate,Array[BigInt]]() - - def do_registered_updates() = { - registered_bits_updates.foreach( kv => wire_poke(kv._1,kv._2) ) - registered_aggr_updates.foreach( kv => wire_poke(kv._1,kv._2) ) - - registered_bits_updates.clear() - registered_aggr_updates.clear() - } - - def reg_poke(port: Bits, target: BigInt) = { registered_bits_updates(port) = target } - def reg_poke(port: Aggregate, target: Array[BigInt]) = { registered_aggr_updates(port) = target } - - // Convenience functions - def Boolean2Int(i: Boolean): Int = (if(i) 1 else 0) // TODO: Investigate name and inclusion as a Chisel Tester auto-convert - - // This function replaces step in the advanced tester and makes sure all tester features are clocked in the appropriate order - def takestep(work: => Unit = {}): Unit = { - cycles += 1 - step(1) - do_registered_updates() - preprocessors.foreach(_.process()) // e.g. sinks - work - postprocessors.foreach(_.process()) - } - def takesteps(n: Int)(work: =>Unit = {}): Unit = { - require(n>0, "Number of steps taken must be positive integer.") - (0 until n).foreach(_ => takestep(work)) - } - - // Functions to step depending on predicates - def until(pred: =>Boolean, maxCycles: Int = defaultMaxCycles)(work: =>Unit): Boolean = { - var timeout_cycles = 0 - while(!pred && (timeout_cycles < maxCycles)) { - takestep(work) - timeout_cycles += 1 - } - assert(timeout_cycles < maxCycles, - "until timed out after %d cycles".format(timeout_cycles)) - pred - } - def eventually(pred: =>Boolean, maxCycles: Int = defaultMaxCycles) = {until(pred, maxCycles){}} - def do_until(work: =>Unit)(pred: =>Boolean, maxCycles: Int = defaultMaxCycles): Boolean = { - takestep(work) - until(pred, maxCycles){work} - } - - def assert(expr: Boolean, errMsg:String = "") = { - pass &= expr - if(!expr && errMsg != "") { println("ASSERT FAILED: " + errMsg) } - expr - } - - - class DecoupledSink[T <: Data, R]( socket: DecoupledIO[T], cvt: T=>R ) extends Processable - { - var max_count = -1 - val outputs = new scala.collection.mutable.Queue[R]() - private var amReady = false - private def isValid = () => (peek(socket.valid) == 1) - - def process() = { - // Handle this cycle - if(isValid() && amReady) { - outputs.enqueue(cvt(socket.bits)) - } - // Decide what to do next cycle and post onto register - amReady = max_count < 1 || outputs.length < max_count - reg_poke(socket.ready, Boolean2Int(amReady)) - } - - // Initialize - wire_poke(socket.ready, 0) - preprocessors += this - } - object DecoupledSink { - def apply[T<:Bits](socket: DecoupledIO[T]) = new DecoupledSink(socket, (socket_bits: T) => peek(socket_bits)) - } - - class ValidSink[T <: Data, R]( socket: ValidIO[T], cvt: T=>R ) extends Processable - { - val outputs = new scala.collection.mutable.Queue[R]() - private def isValid = peek(socket.valid) == 1 - - def process() = { - if(isValid) { - outputs.enqueue(cvt(socket.bits)) - } - } - - // Initialize - preprocessors += this - } - object ValidSink { - def apply[T<:Bits](socket: ValidIO[T]) = new ValidSink(socket, (socket_bits: T) => peek(socket_bits)) - } - - class DecoupledSource[T <: Data, R]( socket: DecoupledIO[T], post: (T,R)=>Unit ) extends Processable - { - val inputs = new scala.collection.mutable.Queue[R]() - - private var amValid = false - private var justFired = false - private def isReady = (peek(socket.ready) == 1) - def isIdle = !amValid && inputs.isEmpty && !justFired - - def process() = { - justFired = false - if(isReady && amValid) { // Fired last cycle - amValid = false - justFired = true - } - if(!amValid && !inputs.isEmpty) { - amValid = true - post(socket.bits, inputs.dequeue()) - } - reg_poke(socket.valid, Boolean2Int(amValid)) - } - - // Initialize - wire_poke(socket.valid, 0) - postprocessors += this - } - object DecoupledSource { - def apply[T<:Bits](socket: DecoupledIO[T]) = new DecoupledSource(socket, (socket_bits: T, in: BigInt) => reg_poke(socket_bits, in)) - } - - class ValidSource[T <: Data, R]( socket: ValidIO[T], post: (T,R)=>Unit ) extends Processable - { - val inputs = new scala.collection.mutable.Queue[R]() - private var amValid = false - private var justFired = false - - def isIdle = inputs.isEmpty && !amValid - - def process() = { - // Always advance the input - justFired = (amValid==true) - amValid = false - if(!inputs.isEmpty) { - amValid = true - post(socket.bits, inputs.dequeue()) - } - reg_poke(socket.valid, Boolean2Int(amValid)) - } - - // Initialize - wire_poke(socket.valid, 0) - postprocessors += this - } - object ValidSource { - def apply[T<:Bits](socket: ValidIO[T]) = new ValidSource(socket, (socket_bits: T, in: BigInt) => reg_poke(socket_bits, in)) - } - -} - -trait Processable { - def process(): Unit -} - diff --git a/src/main/scala/Chisel/Tester.scala b/src/main/scala/Chisel/Tester.scala deleted file mode 100644 index 61e78b1d..00000000 --- a/src/main/scala/Chisel/Tester.scala +++ /dev/null @@ -1,406 +0,0 @@ -/* - 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.testers -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._ - -object TesterDriver { - // Setting this to TRUE will initialize the tester's RNG with the - // seed below. - // case "--testerSeed" => { - // testerSeedValid = true - // testerSeed = args(i+1).toLong } - var testerSeedValid = false - var testerSeed = System.currentTimeMillis() - - // Setting this to TRUE will case the test harness to print its - // standard input stream to a file. - var dumpTestInput = false - - private def test[T <: Module](mod: T, ftester: T => Tester[T]): Unit = { - var res = false - var tester: Tester[T] = null - try { - tester = ftester(mod) - } finally { - if (tester != null && tester.process != null) - res = tester.finish() - } - println(if (res) "PASSED" else "*** FAILED ***") - if(!res) throwException("Module under test FAILED at least one test vector.") - } - -} - -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 name: String, val isT: Boolean = true, val skipVPDMessage: 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 ...") - } - } - } - - 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 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 = BigInt(s.substring(2), 16) - 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.getWidth - 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 = (BigInt(x >>> 1) << 1) | x & 1 - def int(x: Long): BigInt = (BigInt(x >>> 1) << 1) | x & 1 - 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 (TesterDriver.testerSeedValid) new Random(TesterDriver.testerSeed) else new Random() - var process: Process = null - - def start(): Process = { - val cmd = "./" + name - println("RUNNING " + cmd) - println("SEED " + TesterDriver.testerSeed) - println("STARTING " + name) - val processBuilder = Process(Seq("bash", "-c", cmd)) - val pio = new ProcessIO(in => testOut = in, out => testIn = out, err => testErr = err) - process = processBuilder.run(pio) - waitForStreams() - t = 0 - reset(5) - if (skipVPDMessage) { - 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, skipVPDMessage: Boolean = false) extends ManualTester(c.name, isTrace, skipVPDMessage) { - start() -} - diff --git a/src/main/scala/Chisel/testers/AdvTester.scala b/src/main/scala/Chisel/testers/AdvTester.scala new file mode 100644 index 00000000..9520932b --- /dev/null +++ b/src/main/scala/Chisel/testers/AdvTester.scala @@ -0,0 +1,223 @@ +/* + 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. +*/ + +/* + Written by Stephen Twigg, Eric Love + Version 0.9 +*/ +package Chisel.testers +import Chisel._ +import scala.collection.mutable.ArrayBuffer + +class AdvTester[+T <: Module](val dut: T, isTrace: Boolean = false) extends Tester[T](dut, isTrace) { + val defaultMaxCycles = 1024 + var cycles = 0 + var pass = true + + // List of scala objects that need to be processed along with the test benches, like sinks and sources + val preprocessors = new ArrayBuffer[Processable]() + val postprocessors = new ArrayBuffer[Processable]() + // pre v post refers to when user-customized update code ('work') is processed + // e.g. sinks are in the preprocessing list and sources in the postprocessing list + // this allows the testbench to respond to a request within one cycle + + // This section of code lets testers easily emulate have registers right before dut inputs + // This testing style conforms with the general ASPIRE testbench style + // Also, to ensure difference enforced, poke 'deprecated' and replaced with wire_poke + def wire_poke(port: Bits, target: Boolean) = { super.poke(port, int(target)) } + def wire_poke(port: Bits, target: Int) = { super.poke(port, int(target)) } + def wire_poke(port: Bits, target: Long) = { super.poke(port, int(target)) } + def wire_poke(port: Bits, target: BigInt) = { super.poke(port, target) } + def wire_poke(port: Aggregate, target: Array[BigInt]) = { super.poke(port, target) } + + override def poke(port: Bits, target: BigInt) = require(false, "poke hidden for AdvTester, use wire_poke or reg_poke") + override def poke(port: Aggregate, target: Array[BigInt]) = require(false, "poke hidden for AdvTester, use wire_poke or reg_poke") + + val registered_bits_updates = new scala.collection.mutable.HashMap[Bits,BigInt]() + val registered_aggr_updates = new scala.collection.mutable.HashMap[Aggregate,Array[BigInt]]() + + def do_registered_updates() = { + registered_bits_updates.foreach( kv => wire_poke(kv._1,kv._2) ) + registered_aggr_updates.foreach( kv => wire_poke(kv._1,kv._2) ) + + registered_bits_updates.clear() + registered_aggr_updates.clear() + } + + def reg_poke(port: Bits, target: BigInt) = { registered_bits_updates(port) = target } + def reg_poke(port: Aggregate, target: Array[BigInt]) = { registered_aggr_updates(port) = target } + + // Convenience functions + def Boolean2Int(i: Boolean): Int = (if(i) 1 else 0) // TODO: Investigate name and inclusion as a Chisel Tester auto-convert + + // This function replaces step in the advanced tester and makes sure all tester features are clocked in the appropriate order + def takestep(work: => Unit = {}): Unit = { + cycles += 1 + step(1) + do_registered_updates() + preprocessors.foreach(_.process()) // e.g. sinks + work + postprocessors.foreach(_.process()) + } + def takesteps(n: Int)(work: =>Unit = {}): Unit = { + require(n>0, "Number of steps taken must be positive integer.") + (0 until n).foreach(_ => takestep(work)) + } + + // Functions to step depending on predicates + def until(pred: =>Boolean, maxCycles: Int = defaultMaxCycles)(work: =>Unit): Boolean = { + var timeout_cycles = 0 + while(!pred && (timeout_cycles < maxCycles)) { + takestep(work) + timeout_cycles += 1 + } + assert(timeout_cycles < maxCycles, + "until timed out after %d cycles".format(timeout_cycles)) + pred + } + def eventually(pred: =>Boolean, maxCycles: Int = defaultMaxCycles) = {until(pred, maxCycles){}} + def do_until(work: =>Unit)(pred: =>Boolean, maxCycles: Int = defaultMaxCycles): Boolean = { + takestep(work) + until(pred, maxCycles){work} + } + + def assert(expr: Boolean, errMsg:String = "") = { + pass &= expr + if(!expr && errMsg != "") { println("ASSERT FAILED: " + errMsg) } + expr + } + + + class DecoupledSink[T <: Data, R]( socket: DecoupledIO[T], cvt: T=>R ) extends Processable + { + var max_count = -1 + val outputs = new scala.collection.mutable.Queue[R]() + private var amReady = false + private def isValid = () => (peek(socket.valid) == 1) + + def process() = { + // Handle this cycle + if(isValid() && amReady) { + outputs.enqueue(cvt(socket.bits)) + } + // Decide what to do next cycle and post onto register + amReady = max_count < 1 || outputs.length < max_count + reg_poke(socket.ready, Boolean2Int(amReady)) + } + + // Initialize + wire_poke(socket.ready, 0) + preprocessors += this + } + object DecoupledSink { + def apply[T<:Bits](socket: DecoupledIO[T]) = new DecoupledSink(socket, (socket_bits: T) => peek(socket_bits)) + } + + class ValidSink[T <: Data, R]( socket: ValidIO[T], cvt: T=>R ) extends Processable + { + val outputs = new scala.collection.mutable.Queue[R]() + private def isValid = peek(socket.valid) == 1 + + def process() = { + if(isValid) { + outputs.enqueue(cvt(socket.bits)) + } + } + + // Initialize + preprocessors += this + } + object ValidSink { + def apply[T<:Bits](socket: ValidIO[T]) = new ValidSink(socket, (socket_bits: T) => peek(socket_bits)) + } + + class DecoupledSource[T <: Data, R]( socket: DecoupledIO[T], post: (T,R)=>Unit ) extends Processable + { + val inputs = new scala.collection.mutable.Queue[R]() + + private var amValid = false + private var justFired = false + private def isReady = (peek(socket.ready) == 1) + def isIdle = !amValid && inputs.isEmpty && !justFired + + def process() = { + justFired = false + if(isReady && amValid) { // Fired last cycle + amValid = false + justFired = true + } + if(!amValid && !inputs.isEmpty) { + amValid = true + post(socket.bits, inputs.dequeue()) + } + reg_poke(socket.valid, Boolean2Int(amValid)) + } + + // Initialize + wire_poke(socket.valid, 0) + postprocessors += this + } + object DecoupledSource { + def apply[T<:Bits](socket: DecoupledIO[T]) = new DecoupledSource(socket, (socket_bits: T, in: BigInt) => reg_poke(socket_bits, in)) + } + + class ValidSource[T <: Data, R]( socket: ValidIO[T], post: (T,R)=>Unit ) extends Processable + { + val inputs = new scala.collection.mutable.Queue[R]() + private var amValid = false + private var justFired = false + + def isIdle = inputs.isEmpty && !amValid + + def process() = { + // Always advance the input + justFired = (amValid==true) + amValid = false + if(!inputs.isEmpty) { + amValid = true + post(socket.bits, inputs.dequeue()) + } + reg_poke(socket.valid, Boolean2Int(amValid)) + } + + // Initialize + wire_poke(socket.valid, 0) + postprocessors += this + } + object ValidSource { + def apply[T<:Bits](socket: ValidIO[T]) = new ValidSource(socket, (socket_bits: T, in: BigInt) => reg_poke(socket_bits, in)) + } + +} + +trait Processable { + def process(): Unit +} + diff --git a/src/main/scala/Chisel/testers/Driver.scala b/src/main/scala/Chisel/testers/Driver.scala new file mode 100644 index 00000000..778f2edf --- /dev/null +++ b/src/main/scala/Chisel/testers/Driver.scala @@ -0,0 +1,61 @@ +/* + 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.testers +import Chisel._ + +object TesterDriver { + // Setting this to TRUE will initialize the tester's RNG with the + // seed below. + // case "--testerSeed" => { + // testerSeedValid = true + // testerSeed = args(i+1).toLong } + var testerSeedValid = false + var testerSeed = System.currentTimeMillis() + + // Setting this to TRUE will case the test harness to print its + // standard input stream to a file. + var dumpTestInput = false + + private def test[T <: Module](mod: T, ftester: T => Tester[T]): Unit = { + var res = false + var tester: Tester[T] = null + try { + tester = ftester(mod) + } finally { + if (tester != null && tester.process != null) + res = tester.finish() + } + println(if (res) "PASSED" else "*** FAILED ***") + if(!res) throwException("Module under test FAILED at least one test vector.") + } + + +} diff --git a/src/main/scala/Chisel/testers/Tester.scala b/src/main/scala/Chisel/testers/Tester.scala new file mode 100644 index 00000000..d3ed7170 --- /dev/null +++ b/src/main/scala/Chisel/testers/Tester.scala @@ -0,0 +1,379 @@ +/* + 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.testers +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 name: String, val isT: Boolean = true, val skipVPDMessage: 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 ...") + } + } + } + + 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 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 = BigInt(s.substring(2), 16) + 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.getWidth + 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 = (BigInt(x >>> 1) << 1) | x & 1 + def int(x: Long): BigInt = (BigInt(x >>> 1) << 1) | x & 1 + 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 (TesterDriver.testerSeedValid) new Random(TesterDriver.testerSeed) else new Random() + var process: Process = null + + def start(): Process = { + val cmd = "./" + name + println("RUNNING " + cmd) + println("SEED " + TesterDriver.testerSeed) + println("STARTING " + name) + val processBuilder = Process(Seq("bash", "-c", cmd)) + val pio = new ProcessIO(in => testOut = in, out => testIn = out, err => testErr = err) + process = processBuilder.run(pio) + waitForStreams() + t = 0 + reset(5) + if (skipVPDMessage) { + 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, skipVPDMessage: Boolean = false) extends ManualTester(c.name, isTrace, skipVPDMessage) { + start() +} + -- cgit v1.2.3