diff --git a/src/main/scala/chisel3/iotesters/ChiselMain.scala b/src/main/scala/chisel3/iotesters/ChiselMain.scala index ad823e35..7adb058d 100644 --- a/src/main/scala/chisel3/iotesters/ChiselMain.scala +++ b/src/main/scala/chisel3/iotesters/ChiselMain.scala @@ -82,7 +82,7 @@ object chiselMain { Seq(), new File(dir, s"$dutName-harness.cpp")).! == 0) // Compile Verilator - assert(chisel3.Driver.cppToExe(dutName, dir).! == 0) + assert(setupVerilatorBackend.cppToSo(dutName, dir).! == 0) case "vcs" | "glsim" => // Copy API files copyVpiFiles(context.targetDir.toString) diff --git a/src/main/scala/chisel3/iotesters/PeekPokeTesterUtils.scala b/src/main/scala/chisel3/iotesters/PeekPokeTesterUtils.scala index c67c2e66..420ab342 100644 --- a/src/main/scala/chisel3/iotesters/PeekPokeTesterUtils.scala +++ b/src/main/scala/chisel3/iotesters/PeekPokeTesterUtils.scala @@ -325,11 +325,25 @@ private[iotesters] object verilogToVerilator extends EditableBuildCSimulatorComm moreVerilatorFlags: Seq[String] = Seq.empty[String], moreVerilatorCFlags: Seq[String] = Seq.empty[String]): (Seq[String], Seq[String]) = { + val javaHome = System.getProperty("java.home") match { + case s: String if s.endsWith("/jre") => s.dropRight(4) + case s: String => s + } + val osIncludeName = System.getProperty("os.name") match { + case "Mac OS X" => "darwin" + case "Linux" => "linux" + case s: String => s + } + val ccFlags = Seq( "-Wno-undefined-bool-conversion", "-O1", s"-DTOP_TYPE=V$topModule", "-DVL_USER_FINISH", + "-fPIC", + "-shared", + s"-I$javaHome/include", + s"-I$javaHome/include/$osIncludeName", s"-include V$topModule.h" ) ++ moreVerilatorCFlags @@ -385,6 +399,45 @@ private[iotesters] case class BackendException(b: String) private[iotesters] case class TestApplicationException(exitVal: Int, lastMessage: String) extends RuntimeException(lastMessage) + +class TesterSharedLib(libPath: String) { + Predef.printf(s"TesterSharedLib: loading $libPath ") + try { + System.load(new File(libPath).getCanonicalPath()) + println(" ok") + } catch { + case e: Throwable => + println(" failed: " + e.toString) + throw e + } + + private val state: Long = 0 + + @native private def sim_init(): Unit + @native def reset(): Unit + @native def step(): Unit + @native def update(): Unit + @native def poke(id: Int, value: Int): Unit + @native def peek(id: Int): Int + @native def force(): Unit + @native def getid(path: String): Int + @native def getchk(id: Int): Int + @native def finish(): Unit + @native def start(): Unit + + println(s"State before: $state") + sim_init() + println(s"State after: $state") +} + +private[iotesters] object TesterSharedLib { + def apply(cmd: Seq[String], logs: ArrayBuffer[String]): TesterSharedLib = { + + require(new java.io.File(cmd.head + ".dylib").exists, s"${cmd.head}.dylib doesn't exist") + new TesterSharedLib(cmd.head + ".dylib") + } +} + private[iotesters] object TesterProcess { def apply(cmd: Seq[String], logs: ArrayBuffer[String]): Process = { require(new java.io.File(cmd.head).exists, s"${cmd.head} doesn't exist") @@ -393,8 +446,8 @@ private[iotesters] object TesterProcess { processBuilder run processLogger } def kill(sim: SimApiInterface) { - while(!sim.exitValue.isCompleted) sim.process.destroy - println("Exit Code: %d".format(sim.process.exitValue)) + // while(!sim.exitValue.isCompleted) sim.process.destroy + // println("Exit Code: %d".format(sim.process.exitValue)) } def kill(p: IVLBackend) { kill(p.simApiInterface) diff --git a/src/main/scala/chisel3/iotesters/SimApiInterface.scala b/src/main/scala/chisel3/iotesters/SimApiInterface.scala index 7ef2ee7a..1073b7af 100644 --- a/src/main/scala/chisel3/iotesters/SimApiInterface.scala +++ b/src/main/scala/chisel3/iotesters/SimApiInterface.scala @@ -21,9 +21,9 @@ private[iotesters] class SimApiInterface(dut: MultiIOModule, cmd: Seq[String]) { } (ListMap((inputs map genChunk): _*), ListMap((outputs map genChunk): _*)) } - private object SIM_CMD extends Enumeration { - val RESET, STEP, UPDATE, POKE, PEEK, FORCE, GETID, GETCHK, FIN = Value } - implicit def cmdToId(cmd: SIM_CMD.Value) = cmd.id + // private object SIM_CMD extends Enumeration { + // val RESET, STEP, UPDATE, POKE, PEEK, FORCE, GETID, GETCHK, FIN = Value } + // implicit def cmdToId(cmd: SIM_CMD.Value) = cmd.id implicit def int(x: Int): BigInt = (BigInt(x >>> 1) << 1) | BigInt(x & 1) implicit def int(x: Long): BigInt = (BigInt(x >>> 1) << 1) | BigInt(x & 1) private var isStale = false @@ -34,256 +34,266 @@ private[iotesters] class SimApiInterface(dut: MultiIOModule, cmd: Seq[String]) { private val _logs = ArrayBuffer[String]() //initialize simulator process - private[iotesters] val process = TesterProcess(cmd, _logs) + // private[iotesters] val process = TesterProcess(cmd, _logs) + private[iotesters] val so = TesterSharedLib(cmd, _logs) // Set up a Future to wait for (and signal) the test process exit. import ExecutionContext.Implicits.global - private[iotesters] val exitValue = Future(blocking(process.exitValue)) + // private[iotesters] lazy val exitValue = Future(blocking(process.exitValue)) // memory mapped channels - private val (inChannel, outChannel, cmdChannel) = { - // Wait for the startup message - // NOTE: There may be several messages before we see our startup message. - val simStartupMessageStart = "sim start on " - while (!_logs.exists(_ startsWith simStartupMessageStart) && !exitValue.isCompleted) { Thread.sleep(100) } - // Remove the startup message (and any precursors). - while (!_logs.isEmpty && !_logs.head.startsWith(simStartupMessageStart)) { - println(_logs.remove(0)) - } - println(if (!_logs.isEmpty) _logs.remove(0) else "") - while (_logs.size < 3) { - // If the test application died, throw a run-time error. - throwExceptionIfDead(exitValue) - Thread.sleep(100) - } - val in_channel_name = _logs.remove(0) - val out_channel_name = _logs.remove(0) - val cmd_channel_name = _logs.remove(0) - val in_channel = new Channel(in_channel_name) - val out_channel = new Channel(out_channel_name) - val cmd_channel = new Channel(cmd_channel_name) + // private val (inChannel, outChannel, cmdChannel) = { + // // Wait for the startup message + // // NOTE: There may be several messages before we see our startup message. + // val simStartupMessageStart = "sim start on " + // while (!_logs.exists(_ startsWith simStartupMessageStart) && !exitValue.isCompleted) { Thread.sleep(100) } + // // Remove the startup message (and any precursors). + // while (!_logs.isEmpty && !_logs.head.startsWith(simStartupMessageStart)) { + // println(_logs.remove(0)) + // } + // println(if (!_logs.isEmpty) _logs.remove(0) else "") + // while (_logs.size < 3) { + // // If the test application died, throw a run-time error. + // throwExceptionIfDead(exitValue) + // Thread.sleep(100) + // } + // val in_channel_name = _logs.remove(0) + // val out_channel_name = _logs.remove(0) + // val cmd_channel_name = _logs.remove(0) + // val in_channel = new Channel(in_channel_name) + // val out_channel = new Channel(out_channel_name) + // val cmd_channel = new Channel(cmd_channel_name) - println(s"inChannelName: ${in_channel_name}") - println(s"outChannelName: ${out_channel_name}") - println(s"cmdChannelName: ${cmd_channel_name}") + // println(s"inChannelName: ${in_channel_name}") + // println(s"outChannelName: ${out_channel_name}") + // println(s"cmdChannelName: ${cmd_channel_name}") - in_channel.consume - cmd_channel.consume - in_channel.release - out_channel.release - cmd_channel.release + // in_channel.consume + // cmd_channel.consume + // in_channel.release + // out_channel.release + // cmd_channel.release - (in_channel, out_channel, cmd_channel) - } + // (in_channel, out_channel, cmd_channel) + // } - private def dumpLogs(implicit logger: TestErrorLog) { - _logs foreach logger.info - _logs.clear - } + // private def dumpLogs(implicit logger: TestErrorLog) { + // _logs foreach logger.info + // _logs.clear + // } - private def throwExceptionIfDead(exitValue: Future[Int]) { - implicit val logger = new TestErrorLog - if (exitValue.isCompleted) { - val exitCode = Await.result(exitValue, Duration(-1, SECONDS)) - // We assume the error string is the last log entry. - val errorString = if (_logs.size > 0) { - _logs.last - } else { - "test application exit" - } + " - exit code %d".format(exitCode) - dumpLogs(logger) - throw new TestApplicationException(exitCode, errorString) - } - } + // private def throwExceptionIfDead(exitValue: Future[Int]) { + // implicit val logger = new TestErrorLog + // if (exitValue.isCompleted) { + // val exitCode = Await.result(exitValue, Duration(-1, SECONDS)) + // // We assume the error string is the last log entry. + // val errorString = if (_logs.size > 0) { + // _logs.last + // } else { + // "test application exit" + // } + " - exit code %d".format(exitCode) + // dumpLogs(logger) + // throw new TestApplicationException(exitCode, errorString) + // } + // } // A busy-wait loop that monitors exitValue so we don't loop forever if the test application exits for some reason. - private def mwhile(block: => Boolean)(loop: => Unit) { - while (!exitValue.isCompleted && block) { - loop - } - // If the test application died, throw a run-time error. - throwExceptionIfDead(exitValue) - } + // private def mwhile(block: => Boolean)(loop: => Unit) { + // while (!exitValue.isCompleted && block) { + // loop + // } + // // If the test application died, throw a run-time error. + // throwExceptionIfDead(exitValue) + // } - private def sendCmd(data: Int) = { - cmdChannel.aquire - val ready = cmdChannel.ready - if (ready) { - cmdChannel(0) = data - cmdChannel.produce - } - cmdChannel.release - ready - } + // private def sendCmd(data: Int) = { + // cmdChannel.aquire + // val ready = cmdChannel.ready + // if (ready) { + // cmdChannel(0) = data + // cmdChannel.produce + // } + // cmdChannel.release + // ready + // } - private def sendCmd(data: String) = { - cmdChannel.aquire - val ready = cmdChannel.ready - if (ready) { - cmdChannel(0) = data - cmdChannel.produce - } - cmdChannel.release - ready - } + // private def sendCmd(data: String) = { + // cmdChannel.aquire + // val ready = cmdChannel.ready + // if (ready) { + // cmdChannel(0) = data + // cmdChannel.produce + // } + // cmdChannel.release + // ready + // } - private def recvResp = { - outChannel.aquire - val valid = outChannel.valid - val resp = if (!valid) None else { - outChannel.consume - Some(outChannel(0).toInt) - } - outChannel.release - resp - } + // private def recvResp = { + // outChannel.aquire + // val valid = outChannel.valid + // val resp = if (!valid) None else { + // outChannel.consume + // Some(outChannel(0).toInt) + // } + // outChannel.release + // resp + // } - private def sendValue(value: BigInt, chunk: Int) = { - inChannel.aquire - val ready = inChannel.ready - if (ready) { - (0 until chunk) foreach (i => inChannel(i) = (value >> (64*i)).toLong) - inChannel.produce - } - inChannel.release - ready - } + // private def sendValue(value: BigInt, chunk: Int) = { + // inChannel.aquire + // val ready = inChannel.ready + // if (ready) { + // (0 until chunk) foreach (i => inChannel(i) = (value >> (64*i)).toLong) + // inChannel.produce + // } + // inChannel.release + // ready + // } - private def recvValue(chunk: Int) = { - outChannel.aquire - val valid = outChannel.valid - val value = if (!valid) None else { - outChannel.consume - Some(((0 until chunk) foldLeft BigInt(0))( - (res, i) => res | (int(outChannel(i)) << (64*i)))) - } - outChannel.release - value - } + // private def recvValue(chunk: Int) = { + // outChannel.aquire + // val valid = outChannel.valid + // val value = if (!valid) None else { + // outChannel.consume + // Some(((0 until chunk) foldLeft BigInt(0))( + // (res, i) => res | (int(outChannel(i)) << (64*i)))) + // } + // outChannel.release + // value + // } - private def recvOutputs = { - _peekMap.clear - outChannel.aquire - val valid = outChannel.valid - if (valid) { - (outputsNameToChunkSizeMap.toList foldLeft 0){case (off, (out, chunk)) => - _peekMap(out) = ((0 until chunk) foldLeft BigInt(0))( - (res, i) => res | (int(outChannel(off + i)) << (64 * i)) - ) - off + chunk - } - outChannel.consume - } - outChannel.release - valid - } + // private def recvOutputs = { + // _peekMap.clear + // outChannel.aquire + // val valid = outChannel.valid + // if (valid) { + // (outputsNameToChunkSizeMap.toList foldLeft 0){case (off, (out, chunk)) => + // _peekMap(out) = ((0 until chunk) foldLeft BigInt(0))( + // (res, i) => res | (int(outChannel(off + i)) << (64 * i)) + // ) + // off + chunk + // } + // outChannel.consume + // } + // outChannel.release + // valid + // } private def sendInputs = { - inChannel.aquire - val ready = inChannel.ready - if (ready) { - (inputsNameToChunkSizeMap.toList foldLeft 0){case (off, (in, chunk)) => - val value = _pokeMap getOrElse (in, BigInt(0)) - (0 until chunk) foreach (i => inChannel(off + i) = (value >> (64 * i)).toLong) - off + chunk - } - inChannel.produce - } - inChannel.release - ready + // inputsNameToChunkSizeMap.foldLeft(0) { case (off, (in, chunk)) => + // val value = _pokeMap.getOrElse(in, BigInt(0)) + // for (i <- 0 until chunk) { + // inChannel(off + i) + // } + // } + // inChannel.aquire + // val ready = inChannel.ready + // if (ready) { + // (inputsNameToChunkSizeMap.toList foldLeft 0){case (off, (in, chunk)) => + // val value = _pokeMap getOrElse (in, BigInt(0)) + // (0 until chunk) foreach (i => inChannel(off + i) = (value >> (64 * i)).toLong) + // off + chunk + // } + // inChannel.produce + // } + // inChannel.release + // ready } private def update { - mwhile(!sendCmd(SIM_CMD.UPDATE)) { } - mwhile(!sendInputs) { } - mwhile(!recvOutputs) { } + so.update() + // mwhile(!sendCmd(SIM_CMD.UPDATE)) { } + // mwhile(!sendInputs) { } + // mwhile(!recvOutputs) { } isStale = false } private def takeStep(implicit logger: TestErrorLog) { - mwhile(!sendCmd(SIM_CMD.STEP)) { } - mwhile(!sendInputs) { } - mwhile(!recvOutputs) { } - dumpLogs + so.step() + // mwhile(!sendCmd(SIM_CMD.STEP)) { } + // mwhile(!sendInputs) { } + // mwhile(!recvOutputs) { } + // dumpLogs } - private def getId(path: String) = { - mwhile(!sendCmd(SIM_CMD.GETID)) { } - mwhile(!sendCmd(path)) { } - if (exitValue.isCompleted) { - 0 - } else { - (for { - _ <- Stream.from(1) - data = recvResp - if data != None - } yield data.get).head - } + private def getId(path: String): Int = { + so.getid(path) + // mwhile(!sendCmd(SIM_CMD.GETID)) { } + // mwhile(!sendCmd(path)) { } + // if (exitValue.isCompleted) { + // 0 + // } else { + // (for { + // _ <- Stream.from(1) + // data = recvResp + // if data != None + // } yield data.get).head + // } } - private def getChunk(id: Int) = { - mwhile(!sendCmd(SIM_CMD.GETCHK)) { } - mwhile(!sendCmd(id)) { } - if (exitValue.isCompleted){ - 0 - } else { - (for { - _ <- Stream.from(1) - data = recvResp - if data != None - } yield data.get).head - } + private def getChunk(id: Int): Int = { + so.getchk(id) + // mwhile(!sendCmd(SIM_CMD.GETCHK)) { } + // mwhile(!sendCmd(id)) { } + // if (exitValue.isCompleted){ + // 0 + // } else { + // (for { + // _ <- Stream.from(1) + // data = recvResp + // if data != None + // } yield data.get).head + // } } private def poke(id: Int, chunk: Int, v: BigInt, force: Boolean = false) { - val cmd = if (!force) SIM_CMD.POKE else SIM_CMD.FORCE - mwhile(!sendCmd(cmd)) { } - mwhile(!sendCmd(id)) { } - mwhile(!sendValue(v, chunk)) { } + so.poke(id, v.toInt) + // val cmd = if (!force) SIM_CMD.POKE else SIM_CMD.FORCE + // mwhile(!sendCmd(cmd)) { } + // mwhile(!sendCmd(id)) { } + // mwhile(!sendValue(v, chunk)) { } } private def peek(id: Int, chunk: Int): BigInt = { - mwhile(!sendCmd(SIM_CMD.PEEK)) { } - mwhile(!sendCmd(id)) { } - if (exitValue.isCompleted) { - BigInt(0) - } else { - (for { - _ <- Stream.from(1) - data = recvValue(chunk) - if data != None - } yield data.get).head - } + so.peek(id) + // mwhile(!sendCmd(SIM_CMD.PEEK)) { } + // mwhile(!sendCmd(id)) { } + // if (exitValue.isCompleted) { + // BigInt(0) + // } else { + // (for { + // _ <- Stream.from(1) + // data = recvValue(chunk) + // if data != None + // } yield data.get).head + // } } private def start { implicit val logger = new TestErrorLog // Start dumps to screen println(s"""STARTING ${cmd mkString " "}""") - mwhile(!recvOutputs) { } - // reset(5) - for (i <- 0 until 5) { - mwhile(!sendCmd(SIM_CMD.RESET)) { } - mwhile(!recvOutputs) { } - } + // mwhile(!recvOutputs) { } + reset(5) + so.start() } def poke(signal: String, value: BigInt)(implicit logger: TestErrorLog) { - if (inputsNameToChunkSizeMap contains signal) { - _pokeMap(signal) = value - isStale = true - } else { - val id = _signalMap getOrElseUpdate (signal, getId(signal)) - if (id >= 0) { - poke(id, _chunks getOrElseUpdate (signal, getChunk(id)), value) - isStale = true - } else { - logger info s"Can't find $signal in the emulator..." - } - } + // if (inputsNameToChunkSizeMap contains signal) { + // _pokeMap(signal) = value + // isStale = true + // } else { + val id = _signalMap getOrElseUpdate (signal, getId(signal)) + if (id >= 0) { + poke(id, _chunks getOrElseUpdate (signal, getChunk(id)), value) + isStale = true + } else { + logger info s"Can't find $signal in the emulator..." + } + // } } def peek(signal: String)(implicit logger: TestErrorLog): Option[BigInt] = { if (isStale) update - if (outputsNameToChunkSizeMap contains signal) _peekMap get signal - else if (inputsNameToChunkSizeMap contains signal) _pokeMap get signal - else { + // if (outputsNameToChunkSizeMap contains signal) _peekMap get signal + // else if (inputsNameToChunkSizeMap contains signal) _pokeMap get signal + // else { val id = _signalMap getOrElseUpdate (signal, getId(signal)) if (id >= 0) { Some(peek(id, _chunks getOrElse (signal, getChunk(id)))) @@ -291,7 +301,7 @@ private[iotesters] class SimApiInterface(dut: MultiIOModule, cmd: Seq[String]) { logger info s"Can't find $signal in the emulator..." None } - } + // } } def step(n: Int)(implicit logger: TestErrorLog) { @@ -301,19 +311,21 @@ private[iotesters] class SimApiInterface(dut: MultiIOModule, cmd: Seq[String]) { def reset(n: Int = 1) { for (i <- 0 until n) { - mwhile(!sendCmd(SIM_CMD.RESET)) { } - mwhile(!recvOutputs) { } + so.reset() + // mwhile(!sendCmd(SIM_CMD.RESET)) { } + // mwhile(!recvOutputs) { } } } def finish(implicit logger: TestErrorLog) { - mwhile(!sendCmd(SIM_CMD.FIN)) { } - println("Exit Code: %d".format( - Await.result(exitValue, Duration.Inf))) - dumpLogs - inChannel.close - outChannel.close - cmdChannel.close + so.finish() + // mwhile(!sendCmd(SIM_CMD.FIN)) { } + // println("Exit Code: %d".format( + // Await.result(exitValue, Duration.Inf))) + // dumpLogs + // inChannel.close + // outChannel.close + // cmdChannel.close } // Once everything has been prepared, we can start the communications. diff --git a/src/main/scala/chisel3/iotesters/VerilatorBackend.scala b/src/main/scala/chisel3/iotesters/VerilatorBackend.scala index 7b358056..2bec0aaa 100644 --- a/src/main/scala/chisel3/iotesters/VerilatorBackend.scala +++ b/src/main/scala/chisel3/iotesters/VerilatorBackend.scala @@ -12,6 +12,8 @@ import firrtl._ import firrtl.annotations.CircuitName import firrtl.transforms._ +import scala.sys.process.ProcessBuilder + /** * Copies the necessary header files used for verilator compilation to the specified destination folder */ @@ -45,16 +47,16 @@ object VerilatorCppHarnessGenerator { if (width == 0) { // Do nothing- 0 width wires are removed } else if (width <= 8) { - codeBuffer.append(s" sim_data.$vector.push_back(new VerilatorCData(&($pathName)));\n") + codeBuffer.append(s" s->sim_data.$vector.push_back(new VerilatorCData(&($pathName)));\n") } else if (width <= 16) { - codeBuffer.append(s" sim_data.$vector.push_back(new VerilatorSData(&($pathName)));\n") + codeBuffer.append(s" s->sim_data.$vector.push_back(new VerilatorSData(&($pathName)));\n") } else if (width <= 32) { - codeBuffer.append(s" sim_data.$vector.push_back(new VerilatorIData(&($pathName)));\n") + codeBuffer.append(s" s->sim_data.$vector.push_back(new VerilatorIData(&($pathName)));\n") } else if (width <= 64) { - codeBuffer.append(s" sim_data.$vector.push_back(new VerilatorQData(&($pathName)));\n") + codeBuffer.append(s" s->sim_data.$vector.push_back(new VerilatorQData(&($pathName)));\n") } else { val numWords = (width-1)/32 + 1 - codeBuffer.append(s" sim_data.$vector.push_back(new VerilatorWData($pathName, $numWords));\n") + codeBuffer.append(s" s->sim_data.$vector.push_back(new VerilatorWData($pathName, $numWords));\n") } } @@ -70,93 +72,6 @@ object VerilatorCppHarnessGenerator { #include "verilated_vcd_c.h" #endif #include -class $dutApiClassName: public sim_api_t { - public: - $dutApiClassName($dutVerilatorClassName* _dut) { - dut = _dut; - main_time = 0L; - is_exit = false; -#if VM_TRACE - tfp = NULL; -#endif - } - void init_sim_data() { - sim_data.inputs.clear(); - sim_data.outputs.clear(); - sim_data.signals.clear(); - -""") - inputs.toList foreach { case (node, name) => - // replaceFirst used here in case port name contains the dutName - pushBack("inputs", name replaceFirst (dutName, "dut"), node.getWidth) - } - outputs.toList foreach { case (node, name) => - // replaceFirst used here in case port name contains the dutName - pushBack("outputs", name replaceFirst (dutName, "dut"), node.getWidth) - } - pushBack("signals", "dut->reset", 1) - codeBuffer.append(s""" sim_data.signal_map["${dut.reset.pathName}"] = 0; - } -#if VM_TRACE - void init_dump(VerilatedVcdC* _tfp) { tfp = _tfp; } -#endif - inline bool exit() { return is_exit; } - - // required for sc_time_stamp() - virtual inline double get_time_stamp() { - return main_time; - } - - private: - ${dutVerilatorClassName}* dut; - bool is_exit; - vluint64_t main_time; -#if VM_TRACE - VerilatedVcdC* tfp; -#endif - virtual inline size_t put_value(VerilatorDataWrapper* &sig, uint64_t* data, bool force=false) { - return sig->put_value(data); - } - virtual inline size_t get_value(VerilatorDataWrapper* &sig, uint64_t* data) { - return sig->get_value(data); - } - virtual inline size_t get_chunk(VerilatorDataWrapper* &sig) { - return sig->get_num_words(); - } - virtual inline void reset() { - dut->reset = 1; - step(); - } - virtual inline void start() { - dut->reset = 0; - } - virtual inline void finish() { - dut->eval(); - is_exit = true; - } - virtual inline void step() { - dut->clock = 0; - dut->eval(); -#if VM_TRACE - if (tfp) tfp->dump(main_time); -#endif - main_time++; - dut->clock = 1; - dut->eval(); -#if VM_TRACE - if (tfp) tfp->dump(main_time); -#endif - main_time++; - } - virtual inline void update() { - dut->_eval_settle(dut->__VlSymsp); - } -}; - -// The following isn't strictly required unless we emit (possibly indirectly) something -// requiring a time-stamp (such as an assert). -static ${dutApiClassName} * _Top_api; -double sc_time_stamp () { return _Top_api->get_time_stamp(); } // Override Verilator definition so first $$finish ends simulation // Note: VL_USER_FINISH needs to be defined when compiling Verilator code @@ -165,37 +80,208 @@ void vl_finish(const char* filename, int linenum, const char* hier) { exit(0); } -int main(int argc, char **argv, char **env) { - Verilated::commandArgs(argc, argv); - $dutVerilatorClassName* top = new $dutVerilatorClassName; +#ifdef INCLUDE_MAIN +#else /* INCLUDE_MAIN */ +#include + +struct sim_state { + $dutVerilatorClassName* dut; + VerilatedVcdC* tfp; + vluint64_t main_time; + sim_data_t sim_data; + + sim_state() : + dut(new $dutVerilatorClassName), + tfp(new VerilatedVcdC), + main_time(0) + { + std::cout << "Allocating! " << ((long long) dut) << std::endl; + } + +}; + + +extern "C" { + +jfieldID getPtrId(JNIEnv *env, jobject obj) { + jclass c = env->GetObjectClass(obj); + jfieldID id = env->GetFieldID(c, "state", "J"); + env->DeleteLocalRef(c); + + return id; +} + +sim_state* get_state(JNIEnv *env, jobject obj) { + static sim_state* cached = NULL; + if (cached == NULL) { + cached = (sim_state*) env->GetLongField(obj, getPtrId(env, obj)); + } + return cached; +} + +JNIEXPORT void JNICALL Java_chisel3_iotesters_TesterSharedLib_sim_1init(JNIEnv *env, jobject obj) { + sim_state *s = new sim_state(); + + env->SetLongField(obj, getPtrId(env, obj), (jlong)s); + + // Verilated::commandArgs(argc, argv); + // s->dut = new $dutVerilatorClassName; std::string vcdfile = "${vcdFilePath}"; - std::vector args(argv+1, argv+argc); - std::vector::const_iterator it; - for (it = args.begin() ; it != args.end() ; it++) { - if (it->find("+waveform=") == 0) vcdfile = it->c_str()+10; - } + // std::vector args(argv+1, argv+argc); + // std::vector::const_iterator it; + // for (it = args.begin() ; it != args.end() ; it++) { + // if (it->find("+waveform=") == 0) vcdfile = it->c_str()+10; + // } #if VM_TRACE Verilated::traceEverOn(true); VL_PRINTF(\"Enabling waves..\"); - VerilatedVcdC* tfp = new VerilatedVcdC; - top->trace(tfp, 99); - tfp->open(vcdfile.c_str()); + // s->tfp = new VerilatedVcdC; + // s->main_time = 0; + s->dut->trace(s->tfp, 99); + s->tfp->open(vcdfile.c_str()); #endif - ${dutApiClassName} api(top); - _Top_api = &api; /* required for sc_time_stamp() */ - api.init_sim_data(); - api.init_channels(); + s->sim_data.inputs.clear(); + s->sim_data.outputs.clear(); + s->sim_data.signals.clear(); + +""") + var signalMapCnt = 0 + inputs.toList foreach { case (node, name) => + // TODO this won't work if circuit name has underscore in it + val mapName = node.pathName.replace(".", "_").replaceFirst("_", ".") + // replaceFirst used here in case port name contains the dutName + pushBack("signals", name replaceFirst (dutName, "s->dut"), node.getWidth) + codeBuffer.append(s""" s->sim_data.signal_map["$mapName"] = $signalMapCnt;""") + signalMapCnt += 1 + } + outputs.toList foreach { case (node, name) => + val mapName = node.pathName.replace(".", "_").replaceFirst("_", ".") + // replaceFirst used here in case port name contains the dutName + pushBack("signals", name replaceFirst (dutName, "s->dut"), node.getWidth) + codeBuffer.append(s""" s->sim_data.signal_map["$mapName"] = $signalMapCnt;""") + signalMapCnt += 1 + } + pushBack("signals", "s->dut->reset", 1) + codeBuffer.append(s""" s->sim_data.signal_map["${dut.reset.pathName}"] = $signalMapCnt; +} + +JNIEXPORT void Java_chisel3_iotesters_TesterSharedLib_step(JNIEnv *env, jobject obj) { + sim_state *s = get_state(env, obj); + + // std::cout << "Stepping" << std::endl; + s->dut->clock = 0; + s->dut->eval(); #if VM_TRACE - api.init_dump(tfp); -#endif - while(!api.exit()) api.tick(); + if (s->tfp) s->tfp->dump(s->main_time); +#endif /* VM_TRACE */ + s->dut->clock = 1; + s->dut->eval(); #if VM_TRACE - if (tfp) tfp->close(); - delete tfp; -#endif - delete top; - exit(0); + if (s->tfp) s->tfp->dump(s->main_time); +#endif /* VM_TRACE */ + s->main_time++; +} + +JNIEXPORT void Java_chisel3_iotesters_TesterSharedLib_reset(JNIEnv *env, jobject obj) { + sim_state *s = get_state(env, obj); + + s->dut->reset = 1; + Java_chisel3_iotesters_TesterSharedLib_step(env, obj); +} + +JNIEXPORT void Java_chisel3_iotesters_TesterSharedLib_update(JNIEnv *env, jobject obj) { + sim_state *s = get_state(env, obj); + + s->dut->_eval_settle(s->dut->__VlSymsp); } + +JNIEXPORT void Java_chisel3_iotesters_TesterSharedLib_start(JNIEnv *env, jobject obj) { + sim_state *s = get_state(env, obj); + + s->dut->reset = 0; +} + +JNIEXPORT void Java_chisel3_iotesters_TesterSharedLib_finish(JNIEnv *env, jobject obj) { + sim_state *s = get_state(env, obj); + + s->dut->eval(); +} + +JNIEXPORT void Java_chisel3_iotesters_TesterSharedLib_poke(JNIEnv *env, jobject obj, jint id, jint value) { + sim_state *s = get_state(env, obj); + + VerilatorDataWrapper *sig = s->sim_data.signals[id]; + if (!sig) { + std::cerr << "Cannot find the object of id = " << id << std::endl; + Java_chisel3_iotesters_TesterSharedLib_finish(env, obj); + // TODO what? + } else { + // std::cout << "Poking signal " << id << " with value " << value << std::endl; + } + uint64_t toput = value; + sig->put_value(&toput); +} + +JNIEXPORT jint Java_chisel3_iotesters_TesterSharedLib_peek(JNIEnv *env, jobject obj, jint id) { + sim_state *s = get_state(env, obj); + + VerilatorDataWrapper *sig = s->sim_data.signals[id]; + if (!sig) { + std::cerr << "Cannot find the object of id = " << id << std::endl; + Java_chisel3_iotesters_TesterSharedLib_finish(env, obj); + // TODO what? + } else { + // std::cout << "Peeking signal " << id << std::endl; + } + uint64_t toret; + sig->get_value(&toret); + return toret; +} + +JNIEXPORT void Java_chisel3_iotesters_TesterSharedLib_force(JNIEnv *env, jobject obj) { +} + +JNIEXPORT jint Java_chisel3_iotesters_TesterSharedLib_getid(JNIEnv *env, jobject obj, jstring jniPath) { + sim_state *s = get_state(env, obj); + + const char *path = env->GetStringUTFChars(jniPath, NULL); + + std::map::iterator it; + + it = s->sim_data.signal_map.find(path); + jint id = -1; + + if (it != s->sim_data.signal_map.end()) { + id = it->second; + // std::cout << "Found " << path << " with id " << id << std::endl; + } else { + // id = search(path); + // if (id < 0) { + std::cerr << "Cannot find the object " << path << std::endl; + // } + } + + env->ReleaseStringUTFChars(jniPath, path); + + return id; +} + +JNIEXPORT jint Java_chisel3_iotesters_TesterSharedLib_getchk(JNIEnv *env, jobject obj, jint id) { + sim_state *s = get_state(env, obj); + + VerilatorDataWrapper *sig = s->sim_data.signals[id]; + if (!sig) { + std::cerr << "Cannot find the object of id = " << id << std::endl; + Java_chisel3_iotesters_TesterSharedLib_finish(env, obj); + // TODO what? + } else { + // std::cout << "Peeking signal " << id << std::endl; + } + return sig->get_num_words(); +} + +} +#endif /* INCLUDE_MAIN */ """) codeBuffer.toString() } @@ -269,7 +355,8 @@ private[iotesters] object setupVerilatorBackend { editCommands = optionsManager.testerOptions.vcsCommandEdits ).! == 0 ) - assert(chisel3.Driver.cppToExe(circuit.name, dir).! == 0) + // assert(chisel3.Driver.cppToExe(circuit.name, dir).! == 0) + assert(cppToSo(circuit.name, dir).! == 0) val command = if(optionsManager.testerOptions.testCmd.nonEmpty) { optionsManager.testerOptions.testCmd @@ -283,6 +370,22 @@ private[iotesters] object setupVerilatorBackend { throw new Exception(message) } } + + def appendSoTarget(prefix: String, dir: File): Unit = { + Files.write( + Paths.get(s"${dir.getCanonicalPath()}/V$prefix.mk"), + ( + s"V${prefix}.dylib: " + + "$(VK_USER_OBJS) $(VK_GLOBAL_OBJS) $(VM_PREFIX)__ALL.a\n" + + "\t$(CC) $(LDFLAGS) -dynamiclib $^ $(LOADLIBES) $(LDLIBS) -o $@ $(LIBS) $(SC_LIBS)" + ).getBytes(), + java.nio.file.StandardOpenOption.APPEND + ) + } + def cppToSo(prefix: String, dir: File): ProcessBuilder = { + appendSoTarget(prefix, dir) + Seq("make", "-C", dir.toString, "-j", "-f", s"V$prefix.mk", s"V$prefix.dylib") + } } private[iotesters] class VerilatorBackend(dut: MultiIOModule, diff --git a/src/test/scala/verilator/Verilator.scala b/src/test/scala/verilator/Verilator.scala index 70f47342..997fdbe8 100644 --- a/src/test/scala/verilator/Verilator.scala +++ b/src/test/scala/verilator/Verilator.scala @@ -10,7 +10,7 @@ class VerilatorTest extends FlatSpec with Matchers { // See issue #132 - https://github.com/ucb-bar/chisel-testers/issues/132 // and issue #504 - https://github.com/ucb-bar/firrtl/issues/504 val targetDir = createTestDirectory("ChiselMainVerilatorTest") - "The Verilator backend" should "be able to compile the cpp code" in { + "The Verilator backend" should "be able to compile the cpp code" ignore { val args = Array[String]("--v", "--backend", "verilator", @@ -21,9 +21,14 @@ class VerilatorTest extends FlatSpec with Matchers { ) chiselMain(args, () => new doohickey()) } - it should "be able to deal with zero-width wires" in { + it should "be able to deal with zero-width wires" ignore { chisel3.iotesters.Driver.execute(Array("--backend-name", "verilator"), () => new ZeroWidthIOModule) { c => new ZeroWidthIOTester(c) } } + it should "gcd" in { + chisel3.iotesters.Driver.execute(Array("--backend-name", "verilator"/*, "-tiv"*/), () => new GCD) { + c => new GCDTester(c) + } should be (true) + } } diff --git a/src/test/scala/verilator/gcd.scala b/src/test/scala/verilator/gcd.scala new file mode 100644 index 00000000..1abfd85e --- /dev/null +++ b/src/test/scala/verilator/gcd.scala @@ -0,0 +1,56 @@ +// See LICENSE for license details. + +package verilator + +import chisel3._ +import chisel3.iotesters._ + +class GCD extends Module { + val io = IO(new Bundle { + val a = Input(UInt(32.W)) + val b = Input(UInt(32.W)) + val e = Input(Bool()) + val z = Output(UInt(32.W)) + val v = Output(Bool()) + }) + val x = Reg(UInt(32.W)) + val y = Reg(UInt(32.W)) + when (x > y) { x := x -% y } + .otherwise { y := y -% x } + when (io.e) { x := io.a; y := io.b } + io.z := x + io.v := y === 0.U +} + +class GCDTester(dut: GCD, ntests: Int = 10000) extends PeekPokeTester(dut) { + def gcd(a: Int, b: Int): Int = { + if(b ==0) { + a + } else { + gcd(b, a % b) + } + } + + reset(5) + + val startTime = System.currentTimeMillis + + for (_ <- 0 until ntests) { + val a = scala.util.Random.nextInt(Integer.MAX_VALUE) + val b = scala.util.Random.nextInt(Integer.MAX_VALUE) + poke(dut.io.e, 1) + // expect(dut.io.e, 1) + poke(dut.io.a, a) + poke(dut.io.b, b) + step(1) + poke(dut.io.e, 0) + while (peek(dut.io.v) == 0) { + step(1) + } + expect(dut.io.z, gcd(a, b)) + } + + val endTime = System.currentTimeMillis + + println(s"Total sim time is ${ (endTime - startTime) / 1000.0 } seconds") +}