From 80d99e5aa0d0547972fb33e7f597265eeaf6ec5b Mon Sep 17 00:00:00 2001 From: David Biancolin Date: Mon, 16 Jul 2018 17:33:04 -0700 Subject: [PATCH 1/7] Model integer clock div/mul between RTL and endpoints This is achieved by duplicating or combining tokens in the channels generated in the SimWrapper. Simulation wrapper generation inspects a new clockRatio member introduced to the Endpoint trait --- src/main/scala/midas/core/Channel.scala | 141 ++++++++++++++++++--- src/main/scala/midas/core/Endpoints.scala | 9 +- src/main/scala/midas/core/FPGATop.scala | 12 +- src/main/scala/midas/core/SimWrapper.scala | 42 ++++-- 4 files changed, 173 insertions(+), 31 deletions(-) diff --git a/src/main/scala/midas/core/Channel.scala b/src/main/scala/midas/core/Channel.scala index 2a7c4323..3a3bde8f 100644 --- a/src/main/scala/midas/core/Channel.scala +++ b/src/main/scala/midas/core/Channel.scala @@ -10,6 +10,36 @@ import freechips.rocketchip.util.ParameterizedBundle import chisel3._ import chisel3.util._ +// For now use the convention that clock ratios are set with respect to the transformed RTL +trait IsRationalClockRatio { + def numerator: Int + def denominator: Int + def isUnity() = numerator == denominator + def isReciprocal() = numerator == 1 + def isIntegral() = denominator == 1 + def inverse: IsRationalClockRatio +} + +case class RationalClockRatio(numerator: Int, denominator: Int) extends IsRationalClockRatio { + def inverse() = RationalClockRatio(denominator, numerator) +} + +case object UnityClockRatio extends IsRationalClockRatio { + val numerator = 1 + val denominator = 1 + def inverse() = UnityClockRatio +} + +case class ReciprocalClockRatio(denominator: Int) extends IsRationalClockRatio { + val numerator = 1 + def inverse = IntegralClockRatio(numerator = denominator) +} + +case class IntegralClockRatio(numerator: Int) extends IsRationalClockRatio { + val denominator = 1 + def inverse = ReciprocalClockRatio(denominator = numerator) +} + class WireChannelIO(w: Int)(implicit p: Parameters) extends Bundle { val in = Flipped(Decoupled(UInt(w.W))) val out = Decoupled(UInt(w.W)) @@ -18,11 +48,43 @@ class WireChannelIO(w: Int)(implicit p: Parameters) extends Bundle { override def cloneType = new WireChannelIO(w)(p).asInstanceOf[this.type] } -class WireChannel(val w: Int)(implicit p: Parameters) extends Module { +class WireChannel( + val w: Int, + clockRatio: IsRationalClockRatio = UnityClockRatio + )(implicit p: Parameters) extends Module { + + require(clockRatio.isReciprocal || clockRatio.isIntegral) + val io = IO(new WireChannelIO(w)) val tokens = Module(new Queue(UInt(w.W), p(ChannelLen))) tokens.io.enq <> io.in io.out <> tokens.io.deq + + // Dequeuing domain is faster; duplicate tokens by dequeuing from the token + // queue every N handshakes + if (clockRatio.isIntegral && !clockRatio.isUnity) { + val deqTokenCount = RegInit((clockRatio.numerator - 1).U(log2Ceil(clockRatio.numerator).W)) + deqTokenCount.suggestName("deqTokenCount") + tokens.io.deq.ready := false.B + when (io.out.fire && deqTokenCount =/= 0.U) { + deqTokenCount := deqTokenCount - 1.U + }.elsewhen(io.out.fire && deqTokenCount === 0.U) { + deqTokenCount := (clockRatio.numerator - 1).U + tokens.io.deq.ready := true.B + } + // Dequeuing domain is slower; drop enqueued tokens by ignoring M-1 enqueue handshakes + } else if (clockRatio.isReciprocal && !clockRatio.isUnity) { + val enqTokenCount = RegInit(0.U(log2Ceil(clockRatio.denominator).W)) + enqTokenCount.suggestName("enqTokenCount") + tokens.io.enq.valid := false.B + when (io.in.fire && enqTokenCount =/= 0.U) { + enqTokenCount := enqTokenCount - 1.U + }.elsewhen(io.in.fire && enqTokenCount === 0.U) { + enqTokenCount := (clockRatio.denominator - 1).U + tokens.io.enq.valid := true.B + } + } + if (p(EnableSnapshot)) { io.trace <> TraceQueue(tokens.io.deq, io.traceLen) } else { @@ -31,6 +93,7 @@ class WireChannel(val w: Int)(implicit p: Parameters) extends Module { } } + class SimReadyValidIO[T <: Data](gen: T) extends Bundle { val target = EnqIO(gen) val host = new HostReadyValid @@ -61,7 +124,15 @@ class ReadyValidChannelIO[T <: Data](gen: T)(implicit p: Parameters) extends Bun override def cloneType = new ReadyValidChannelIO(gen)(p).asInstanceOf[this.type] } -class ReadyValidChannel[T <: Data](gen: T, flipped: Boolean, n: Int = 2)(implicit p: Parameters) extends Module { +class ReadyValidChannel[T <: Data]( + gen: T, + flipped: Boolean, + n: Int = 2, // Target queue depth + // Clock ratio (N/M) of deq interface (N) vs enq interface (M) + clockRatio: IsRationalClockRatio = UnityClockRatio + )(implicit p: Parameters) extends Module { + require(clockRatio.isReciprocal || clockRatio.isIntegral) + val io = IO(new ReadyValidChannelIO(gen)) // Stores tokens with valid target-data that have been successfully enqueued val target = Module(new Queue(gen, n)) @@ -79,23 +150,61 @@ class ReadyValidChannel[T <: Data](gen: T, flipped: Boolean, n: Int = 2)(implici tokens.io.enq.bits := target.io.enq.fire() tokens.io.enq.valid := io.enq.host.hValid - // Track the number of valid target-handshakes that should be visible to the dequeuer - val deqCnts = RegInit(0.U(8.W)) - val deqValid = tokens.io.deq.bits || deqCnts.orR + // Track the number of tokens with valid target-data that should be visible + // to the dequeuer. This allows the enq-side model to advance ahead of the deq-side model + val numTValid = RegInit(0.U(8.W)) + val tValid = tokens.io.deq.bits || numTValid =/= 0.U + val newTValid = tokens.io.deq.fire && tokens.io.deq.bits + val tValidConsumed = io.deq.host.fire && io.deq.target.fire + io.deq.target.bits := target.io.deq.bits - io.deq.target.valid := target.io.deq.valid && deqValid - target.io.deq.ready := io.deq.target.ready && deqValid && io.deq.host.fire - io.deq.host.hValid := tokens.io.deq.valid - tokens.io.deq.ready := io.deq.host.hReady - - when(tokens.io.deq.fire()) { - // target value is valid, but not ready - when(tokens.io.deq.bits && !io.deq.target.ready) { - deqCnts := deqCnts + 1.U - }.elsewhen(!tokens.io.deq.bits && io.deq.target.ready && deqCnts.orR) { - deqCnts := deqCnts - 1.U + io.deq.target.valid := target.io.deq.valid && tValid + target.io.deq.ready := io.deq.target.ready && tValid && io.deq.host.fire + + when(newTValid && !tValidConsumed) { + numTValid := numTValid + 1.U + }.elsewhen(!newTValid && tValidConsumed) { + numTValid := numTValid - 1.U + } + + // Enqueuing and dequeuing domains have the same frequency + // The token queue can be directly coupled between domains + if (clockRatio.isUnity) { + io.deq.host.hValid := tokens.io.deq.valid + tokens.io.deq.ready := io.deq.host.hReady + } + // Dequeuing domain is faster + // Each token in the "token" queue represents a token in the slow domain + // Issue N output tokens per entry in the token queue + else if (clockRatio.isIntegral) { + val deqTokenCount = RegInit((clockRatio.numerator - 1).U(log2Ceil(clockRatio.numerator).W)) + deqTokenCount.suggestName("deqTokenCount") + tokens.io.deq.ready := false.B + io.deq.host.hValid := tokens.io.deq.valid + + when (io.deq.host.fire && deqTokenCount =/= 0.U) { + deqTokenCount := deqTokenCount - 1.U + }.elsewhen(io.deq.host.fire && deqTokenCount === 0.U) { + deqTokenCount := (clockRatio.numerator - 1).U + tokens.io.deq.ready := true.B } } + // Dequeuing domain is slower + // Each entry in the "token" queue represents a token in the slow domain + // Every M tokens received in the fast domain, enqueue a single entry into the "tokens" queue + else if (clockRatio.isReciprocal) { + val enqTokensRemaining = RegInit((clockRatio.denominator - 1).U(log2Ceil(clockRatio.denominator).W)) + enqTokensRemaining.suggestName("enqTokensRemaining") + tokens.io.deq.ready := enqTokensRemaining =/= 0.U || io.deq.host.hReady + io.deq.host.hValid := tokens.io.deq.valid && enqTokensRemaining === 0.U + + when (tokens.io.deq.fire) { + enqTokensRemaining := Mux(enqTokensRemaining === 0.U, + (clockRatio.denominator - 1).U, + enqTokensRemaining - 1.U) + } + } + if (p(EnableSnapshot)) { val wires = Wire(ReadyValidTrace(gen)) diff --git a/src/main/scala/midas/core/Endpoints.scala b/src/main/scala/midas/core/Endpoints.scala index ba86215e..7adee192 100644 --- a/src/main/scala/midas/core/Endpoints.scala +++ b/src/main/scala/midas/core/Endpoints.scala @@ -17,6 +17,7 @@ import scala.collection.mutable.{ArrayBuffer, HashSet} trait Endpoint { protected val channels = ArrayBuffer[(String, Record)]() protected val wires = HashSet[Bits]() + def clockRatio: IsRationalClockRatio = UnityClockRatio def matchType(data: Data): Boolean def widget(p: Parameters): EndpointWidget def widgetName: String = getClass.getSimpleName @@ -64,7 +65,9 @@ abstract class SimMemIO extends Endpoint { } } -class SimNastiMemIO extends SimMemIO { +class SimNastiMemIO( + override val clockRatio: IsRationalClockRatio = UnityClockRatio + ) extends SimMemIO { def matchType(data: Data) = data match { case channel: NastiIO => directionOf(channel.w.valid) == ActualDirection.Output @@ -72,7 +75,9 @@ class SimNastiMemIO extends SimMemIO { } } -class SimAXI4MemIO extends SimMemIO { +class SimAXI4MemIO( + override val clockRatio: IsRationalClockRatio = UnityClockRatio + ) extends SimMemIO { def matchType(data: Data) = data match { case channel: AXI4Bundle => directionOf(channel.w.valid) == ActualDirection.Output diff --git a/src/main/scala/midas/core/FPGATop.scala b/src/main/scala/midas/core/FPGATop.scala index 614304a8..a0783ed1 100644 --- a/src/main/scala/midas/core/FPGATop.scala +++ b/src/main/scala/midas/core/FPGATop.scala @@ -157,12 +157,14 @@ class FPGATop(simIoType: SimWrapperIO)(implicit p: Parameters) extends Module wi widget.io.dma.foreach(dma => dmaPorts += dma) // each widget should have its own reset queue - val resetQueue = Module(new Queue(Bool(), 4)) + val resetQueue = Module(new WireChannel(1, endpoint.clockRatio)) + resetQueue.io.traceLen := DontCare + resetQueue.io.trace.ready := DontCare resetQueue.reset := reset.toBool || simReset - widget.io.tReset <> resetQueue.io.deq - resetQueue.io.enq.bits := defaultIOWidget.io.tReset.bits - resetQueue.io.enq.valid := defaultIOWidget.io.tReset.valid - ready && resetQueue.io.enq.ready + widget.io.tReset <> resetQueue.io.out + resetQueue.io.in.bits := defaultIOWidget.io.tReset.bits + resetQueue.io.in.valid := defaultIOWidget.io.tReset.valid + ready && resetQueue.io.in.ready } } diff --git a/src/main/scala/midas/core/SimWrapper.scala b/src/main/scala/midas/core/SimWrapper.scala index bb231a7e..f478a74b 100644 --- a/src/main/scala/midas/core/SimWrapper.scala +++ b/src/main/scala/midas/core/SimWrapper.scala @@ -252,7 +252,18 @@ class SimWrapper(targetIo: Seq[Data]) arg match { case (port, name) => (0 until getChunks(port)) map { off => val width = scala.math.min(channelWidth, port.getWidth - off * channelWidth) - val channel = Module(new WireChannel(width)) + // Figure out the clock ratio by looking up the endpoint to which this wire belongs + val endpointClockRatio = io.endpoints.find(_(port)) match { + case Some(endpoint) => endpoint.clockRatio + case None => UnityClockRatio + } + + // A channel is considered "flipped" if it's sunk by the tranformed RTL (sourced by an endpoint) + val flipped = directionOf(port) == ActualDirection.Input + val channel = Module(new WireChannel( + width, + clockRatio = if (flipped) endpointClockRatio.inverse else endpointClockRatio + )) // FIXME: it's not working /* port match { case _: Reset => @@ -304,14 +315,28 @@ class SimWrapper(targetIo: Seq[Data]) } def genReadyValidChannel[T <: Data](arg: (String, ReadyValidIO[T])) = - arg match { case (name, io) => + arg match { case (name, rvInterface) => + // Determine which endpoint this channel belongs to by looking it up with the valid + val endpointClockRatio = io.endpoints.find(_(rvInterface.valid)) match { + case Some(endpoint) => endpoint.clockRatio + case None => UnityClockRatio + } + // A channel is considered "flipped" if it's sunk by the tranformed RTL (sourced by an endpoint) + val flipped = directionOf(rvInterface.valid) == ActualDirection.Input val channel = Module(new ReadyValidChannel( - io.bits.cloneType, directionOf(io.valid) == ActualDirection.Input)) + rvInterface.bits.cloneType, + flipped, + clockRatio = if (flipped) endpointClockRatio.inverse else endpointClockRatio + )) + channel suggestName s"ReadyValidChannel_$name" - (directionOf(io.valid): @unchecked) match { - case ActualDirection.Input => io <> channel.io.deq.target - case ActualDirection.Output => channel.io.enq.target <> io + + if (flipped) { + rvInterface <> channel.io.deq.target + } else { + channel.io.enq.target <> rvInterface } + if (!enableSnapshot) channel.io.trace := DontCare channel.io.targetReset.bits := targetReset channel.io.targetReset.valid := fire @@ -322,7 +347,7 @@ class SimWrapper(targetIo: Seq[Data]) // Firing condtion: // 1) all input values are valid // 2) all output FIFOs are not full - fire := (wireInChannels foldLeft true.B)(_ && _.io.out.valid) && + fire := (wireInChannels foldLeft true.B)(_ && _.io.out.valid) && (wireOutChannels foldLeft true.B)(_ && _.io.in.ready) && (readyValidInChannels foldLeft true.B)(_ && _.io.deq.host.hValid) && (readyValidOutChannels foldLeft true.B)(_ && _.io.enq.host.hReady) @@ -331,7 +356,8 @@ class SimWrapper(targetIo: Seq[Data]) wireInChannels foreach (_.io.out.ready := fire) readyValidInChannels foreach (_.io.deq.host.hReady := fire) - // Outputs should be ready when firing conditions are met + // Outputs should be ready when firing conditions are met, inject an intial + // token into each output queue after reset is asserted val resetNext = RegNext(reset.toBool) wireOutChannels foreach (_.io.in.valid := fire || resetNext) readyValidOutChannels foreach (_.io.enq.host.hValid := fire || resetNext) From 93ea3888f63dc283208ea272b9227e0637d4cb49 Mon Sep 17 00:00:00 2001 From: David Biancolin Date: Wed, 18 Jul 2018 19:17:24 -0700 Subject: [PATCH 2/7] Fix a bug where vpd names were being clipped --- src/main/verilog/emul_f1.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/verilog/emul_f1.v b/src/main/verilog/emul_f1.v index b6e5f460..328d9ce6 100644 --- a/src/main/verilog/emul_f1.v +++ b/src/main/verilog/emul_f1.v @@ -107,7 +107,7 @@ module emul; always #(`CLOCK_PERIOD / 2.0) clock = ~clock; - reg [1023:0] vcdplusfile = 0; + reg [2055:0] vcdplusfile = 0; initial begin `ifdef DEBUG From 16ff76c200be9365f9156a29c6623eef0df8ca77 Mon Sep 17 00:00:00 2001 From: David Biancolin Date: Mon, 23 Jul 2018 11:56:08 -0700 Subject: [PATCH 3/7] Add RC-style unittests for channels Parent projects can generate MIDAS unittests by using the Makefrag provided resources/midas/unittest. --- src/main/resources/midas/unittest/Makefrag | 45 ++++++++ src/main/scala/midas/Config.scala | 2 + src/main/scala/midas/SynthUnitTests.scala | 82 ++++++++++++++ src/main/scala/midas/core/Channel.scala | 121 ++++++++++++++++++++- 4 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/midas/unittest/Makefrag create mode 100644 src/main/scala/midas/SynthUnitTests.scala diff --git a/src/main/resources/midas/unittest/Makefrag b/src/main/resources/midas/unittest/Makefrag new file mode 100644 index 00000000..6b29e166 --- /dev/null +++ b/src/main/resources/midas/unittest/Makefrag @@ -0,0 +1,45 @@ +# See LICENSE for license details. +# +# Makefrag for generating MIDAS's synthesizable unit tests + +# These need to be set by the parent Makefile +# +# rocketchip_dir: Location of rocket chip source -- to grab verilog sources and simulation makefrags +# base_dir: Main project's top-level, from which we'll launch sbt +# SBT: command to invoke sbt +# generated_dir: Directory into which to emit generate verilog +# sim_dir: Directory in which to build the simulator. + +PROJECT ?= midas.unittest +CONFIG ?= AllUnitTests + +MAKEFRAG_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) + +long_name := TestHarness +vsrc := $(rocketchip_dir)/src/main/resources/vsrc +csrc := $(rocketchip_dir)/src/main/resources/csrc + +# From Rocket Chip's vsim/Makefile +BACKEND ?= v +TB ?= TestDriver + +# Stupidly guess what this test might depend on +src_path = src/main/scala +scala_srcs := $(shell find $(base_dir) -name "*.scala") + +include $(rocketchip_dir)/vsim/Makefrag + +$(generated_dir)/$(long_name).v $(generated_dir)/$(long_name).behav_srams.v: $(scala_srcs) + mkdir -p $(@D) + cd $(base_dir) && $(SBT) "runMain midas.unittest.Generator -td $(generated_dir) " + touch $(generated_dir)/$(long_name).behav_srams.v + +verilog: $(generated_dir)/$(long_name).v + +run-midas-unittests: $(simv) + cd $(sim_dir) && $(exec_simv) + +run-midas-unittests-debug: $(simv_debug) + cd $(sim_dir) && $(exec_simv_debug) + +.PHONY: unittests unittests-debug verilog diff --git a/src/main/scala/midas/Config.scala b/src/main/scala/midas/Config.scala index c637836a..6893c9a7 100644 --- a/src/main/scala/midas/Config.scala +++ b/src/main/scala/midas/Config.scala @@ -8,6 +8,7 @@ import platform._ import strober.core._ import junctions.{NastiKey, NastiParameters} import freechips.rocketchip.config.{Parameters, Config, Field} +import freechips.rocketchip.unittest.UnitTests trait PlatformType case object Zynq extends PlatformType @@ -60,3 +61,4 @@ class F1Config extends Config(new Config((site, here, up) => { class F1ConfigWithSnapshot extends Config(new Config((site, here, up) => { case EnableSnapshot => true }) ++ new F1Config) + diff --git a/src/main/scala/midas/SynthUnitTests.scala b/src/main/scala/midas/SynthUnitTests.scala new file mode 100644 index 00000000..34b30fb5 --- /dev/null +++ b/src/main/scala/midas/SynthUnitTests.scala @@ -0,0 +1,82 @@ +// See LICENSE for license details. + +package midas +package unittest + +import core._ + +import chisel3._ +import firrtl.{ExecutionOptionsManager, HasFirrtlOptions} + +import freechips.rocketchip.config.{Parameters, Config, Field} +import freechips.rocketchip.unittest.{UnitTests, TestHarness} + + +// Unittests +class WithWireChannelTests extends Config((site, here, up) => { + case UnitTests => (q: Parameters) => { + implicit val p = q + val timeout = 200000 + Seq( + Module(new WireChannelUnitTest(timeout = timeout, clockRatio = ReciprocalClockRatio(2))), + Module(new WireChannelUnitTest(timeout = timeout, clockRatio = ReciprocalClockRatio(3))), + Module(new WireChannelUnitTest(timeout = timeout, clockRatio = ReciprocalClockRatio(4))), + Module(new WireChannelUnitTest(timeout = timeout, clockRatio = ReciprocalClockRatio(6))), + Module(new WireChannelUnitTest(timeout = timeout, clockRatio = ReciprocalClockRatio(7))), + Module(new WireChannelUnitTest(timeout = timeout, clockRatio = IntegralClockRatio(2))), + Module(new WireChannelUnitTest(timeout = timeout, clockRatio = IntegralClockRatio(3))), + Module(new WireChannelUnitTest(timeout = timeout, clockRatio = IntegralClockRatio(4))), + Module(new WireChannelUnitTest(timeout = timeout, clockRatio = IntegralClockRatio(6))), + Module(new WireChannelUnitTest(timeout = timeout, clockRatio = IntegralClockRatio(7))), + Module(new WireChannelUnitTest) + ) + } +}) + +class WithReadyValidChannelTests extends Config((site, here, up) => { + case UnitTests => (q: Parameters) => { + implicit val p = q + val timeout = 200000 + Seq( + Module(new ReadyValidChannelUnitTest) + ) + } +}) + +// Complete configs +//class AllUnitTests extends Config(new WithWireChannelTests ++ new SimConfig) +class AllUnitTests extends Config(new WithReadyValidChannelTests ++ new WithWireChannelTests ++ new SimConfig) + +object Generator extends App with freechips.rocketchip.util.HasGeneratorUtilities { + + case class UnitTestOptions( + configProject: String = "midas.unittest", + config: String = "AllUnitTests") { + val fullConfigClasses: Seq[String] = Seq(configProject + "." + config) + } + + trait HasUnitTestOptions { + self: ExecutionOptionsManager => + var utOptions = UnitTestOptions() + parser.note("MIDAS Unit Test Generator Options") + parser.opt[String]("config-project") + .abbr("cp") + .valueName("") + .foreach { d => utOptions = utOptions.copy(configProject = d) } + parser.opt[String]("config") + .abbr("conf") + .valueName("") + .foreach { cfg => utOptions = utOptions.copy(config = cfg) } + } + + val exOptions = new ExecutionOptionsManager("regressions") + with HasChiselExecutionOptions + with HasFirrtlOptions + with HasUnitTestOptions + + exOptions.parse(args) + + val params = getConfig(exOptions.utOptions.fullConfigClasses).toInstance + Driver.execute(exOptions, () => new TestHarness()(params)) +} + diff --git a/src/main/scala/midas/core/Channel.scala b/src/main/scala/midas/core/Channel.scala index 3a3bde8f..a7b5ea8c 100644 --- a/src/main/scala/midas/core/Channel.scala +++ b/src/main/scala/midas/core/Channel.scala @@ -3,13 +3,15 @@ package midas package core -import strober.core.{TraceQueue, TraceMaxLen} import freechips.rocketchip.config.Parameters -import freechips.rocketchip.util.ParameterizedBundle +import freechips.rocketchip.unittest._ +import freechips.rocketchip.tilelink.LFSR64 // Better than chisel's import chisel3._ import chisel3.util._ +import strober.core.{TraceQueue, TraceMaxLen} + // For now use the convention that clock ratios are set with respect to the transformed RTL trait IsRationalClockRatio { def numerator: Int @@ -54,6 +56,7 @@ class WireChannel( )(implicit p: Parameters) extends Module { require(clockRatio.isReciprocal || clockRatio.isIntegral) + require(p(ChannelLen) >= clockRatio.denominator) val io = IO(new WireChannelIO(w)) val tokens = Module(new Queue(UInt(w.W), p(ChannelLen))) @@ -93,6 +96,60 @@ class WireChannel( } } +class WireChannelUnitTest( + clockRatio: IsRationalClockRatio = UnityClockRatio, + numTokens: Int = 4096, + timeout: Int = 50000 + )(implicit p: Parameters) extends UnitTest(timeout) { + + require(clockRatio.isReciprocal || clockRatio.isIntegral) + override val testName = "WireChannel ClockRatio: ${clockRatio.numerator}/${clockRatio.denominator}" + + val payloadWidth = numTokens * clockRatio.numerator + + val dut = Module(new WireChannel(payloadWidth, clockRatio)) + val inputTokenNum = RegInit(0.U(payloadWidth.W)) + val outputTokenNum = RegInit(0.U(payloadWidth.W)) + val expectedOutputToken = RegInit(0.U(payloadWidth.W)) + val outputDuplicatesRemaining = RegInit((clockRatio.numerator - 1).U) + + val outputReadyFuzz = LFSR64()(0) + val inputValidFuzz = LFSR64()(0) + + val finished = RegInit(false.B) + + dut.io.in.bits := inputTokenNum + dut.io.in.valid := inputValidFuzz + dut.io.out.ready := outputReadyFuzz && !finished + + dut.io.traceLen := DontCare + dut.io.trace.ready := DontCare + + + when (dut.io.in.fire) { + inputTokenNum := inputTokenNum + 1.U + } + + when (dut.io.out.fire) { + assert(finished || dut.io.out.bits === expectedOutputToken, "Output token does not match expected value") + + outputTokenNum := outputTokenNum + 1.U + outputDuplicatesRemaining := Mux(outputDuplicatesRemaining === 0.U, + (clockRatio.numerator-1).U, + outputDuplicatesRemaining - 1.U) + if (clockRatio.isIntegral) { + expectedOutputToken := Mux(outputDuplicatesRemaining === 0.U, + expectedOutputToken + 1.U, + expectedOutputToken) + } else { + expectedOutputToken := expectedOutputToken + clockRatio.denominator.U + } + + finished := finished || outputTokenNum === (numTokens-1).U + } + + io.finished := finished +} class SimReadyValidIO[T <: Data](gen: T) extends Bundle { val target = EnqIO(gen) @@ -231,3 +288,63 @@ class ReadyValidChannel[T <: Data]( io.trace.ready.valid := Bool(false) } } + +class ReadyValidChannelUnitTest( + clockRatio: IsRationalClockRatio = UnityClockRatio, + numTokens: Int = 4096, + timeout: Int = 50000 + )(implicit p: Parameters) extends UnitTest(timeout) { + + require(clockRatio.isReciprocal || clockRatio.isIntegral) + override val testName = "WireChannel ClockRatio: ${clockRatio.numerator}/${clockRatio.denominator}" + + val payloadWidth = numTokens * clockRatio.numerator + + val dut = Module(new ReadyValidChannel(UInt(payloadWidth.W), flipped = false, clockRatio = clockRatio)) + val inputTokenNum = RegInit(0.U(payloadWidth.W)) + val outputTokenNum = RegInit(0.U(payloadWidth.W)) + val expectedOutputToken = RegInit(0.U(payloadWidth.W)) + val outputDuplicatesRemaining = RegInit((clockRatio.numerator - 1).U) + + val outputHReadyFuzz = LFSR64()(0) + val outputTReadyFuzz = LFSR64()(0) + val inputHValidFuzz = LFSR64()(0) + val inputTValidFuzz = LFSR64()(0) + + val finished = RegInit(false.B) + + dut.io.enq.host.hValid := inputHValidFuzz + dut.io.enq.target.valid := inputTValidFuzz + dut.io.enq.target.bits := inputTokenNum + + dut.io.deq.host.hReady := outputHReadyFuzz && !finished + dut.io.deq.target.ready := outputTReadyFuzz && !finished + + dut.io.traceLen := DontCare + dut.io.trace.ready := DontCare + + + when (dut.io.enq.host.fire && dut.io.enq.target.fire) { + inputTokenNum := inputTokenNum + 1.U + } + + when (dut.io.deq.host.fire && dut.io.deq.target.fire) { + assert(finished || dut.io.deq.target.bits === expectedOutputToken, "Output token does not match expected value") + outputTokenNum := outputTokenNum + 1.U + expectedOutputToken := expectedOutputToken + 1.U //clockRatio.denominator.U + + finished := finished || outputTokenNum === (numTokens-1).U + } + + io.finished := finished + + dut.io.traceLen := DontCare + dut.io.traceLen := DontCare + // TODO: FIXME + dut.io.targetReset.valid := reset.toBool() + dut.io.targetReset.bits := reset.toBool() + dut.io.trace.ready.ready := DontCare + dut.io.trace.valid.ready := DontCare + dut.io.trace.bits.ready := DontCare + dut.io.traceLen := DontCare +} From 4576e2b996fb29de7fbaa06b782f796fb1e65277 Mon Sep 17 00:00:00 2001 From: David Biancolin Date: Mon, 23 Jul 2018 12:02:53 -0700 Subject: [PATCH 4/7] [Channels] Fix a bug where target-data was falsely enqueued --- src/main/scala/midas/core/Channel.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/midas/core/Channel.scala b/src/main/scala/midas/core/Channel.scala index a7b5ea8c..28346bc8 100644 --- a/src/main/scala/midas/core/Channel.scala +++ b/src/main/scala/midas/core/Channel.scala @@ -201,7 +201,7 @@ class ReadyValidChannel[T <: Data]( io.targetReset.ready := true.B // TODO: is it ok? target.io.enq.bits := io.enq.target.bits - target.io.enq.valid := io.enq.target.valid && io.enq.host.hValid + target.io.enq.valid := io.enq.target.valid && io.enq.host.fire io.enq.target.ready := target.io.enq.ready io.enq.host.hReady := tokens.io.enq.ready tokens.io.enq.bits := target.io.enq.fire() From 9fbc438897fd4fa8ce9634d723994bbb02cf605e Mon Sep 17 00:00:00 2001 From: David Biancolin Date: Mon, 23 Jul 2018 13:31:05 -0700 Subject: [PATCH 5/7] [Channels] Do some simple bounds checking on output tokens --- src/main/scala/midas/SynthUnitTests.scala | 11 +++-- src/main/scala/midas/core/Channel.scala | 53 ++++++++++++++++++----- 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/src/main/scala/midas/SynthUnitTests.scala b/src/main/scala/midas/SynthUnitTests.scala index 34b30fb5..6afb4b05 100644 --- a/src/main/scala/midas/SynthUnitTests.scala +++ b/src/main/scala/midas/SynthUnitTests.scala @@ -21,12 +21,10 @@ class WithWireChannelTests extends Config((site, here, up) => { Module(new WireChannelUnitTest(timeout = timeout, clockRatio = ReciprocalClockRatio(2))), Module(new WireChannelUnitTest(timeout = timeout, clockRatio = ReciprocalClockRatio(3))), Module(new WireChannelUnitTest(timeout = timeout, clockRatio = ReciprocalClockRatio(4))), - Module(new WireChannelUnitTest(timeout = timeout, clockRatio = ReciprocalClockRatio(6))), Module(new WireChannelUnitTest(timeout = timeout, clockRatio = ReciprocalClockRatio(7))), Module(new WireChannelUnitTest(timeout = timeout, clockRatio = IntegralClockRatio(2))), Module(new WireChannelUnitTest(timeout = timeout, clockRatio = IntegralClockRatio(3))), Module(new WireChannelUnitTest(timeout = timeout, clockRatio = IntegralClockRatio(4))), - Module(new WireChannelUnitTest(timeout = timeout, clockRatio = IntegralClockRatio(6))), Module(new WireChannelUnitTest(timeout = timeout, clockRatio = IntegralClockRatio(7))), Module(new WireChannelUnitTest) ) @@ -38,13 +36,20 @@ class WithReadyValidChannelTests extends Config((site, here, up) => { implicit val p = q val timeout = 200000 Seq( + Module(new ReadyValidChannelUnitTest(timeout = timeout, clockRatio = ReciprocalClockRatio(2))), + Module(new ReadyValidChannelUnitTest(timeout = timeout, clockRatio = ReciprocalClockRatio(3))), + Module(new ReadyValidChannelUnitTest(timeout = timeout, clockRatio = ReciprocalClockRatio(4))), + Module(new ReadyValidChannelUnitTest(timeout = timeout, clockRatio = ReciprocalClockRatio(7))), + Module(new ReadyValidChannelUnitTest(timeout = timeout, clockRatio = IntegralClockRatio(2))), + Module(new ReadyValidChannelUnitTest(timeout = timeout, clockRatio = IntegralClockRatio(3))), + Module(new ReadyValidChannelUnitTest(timeout = timeout, clockRatio = IntegralClockRatio(4))), + Module(new ReadyValidChannelUnitTest(timeout = timeout, clockRatio = IntegralClockRatio(7))), Module(new ReadyValidChannelUnitTest) ) } }) // Complete configs -//class AllUnitTests extends Config(new WithWireChannelTests ++ new SimConfig) class AllUnitTests extends Config(new WithReadyValidChannelTests ++ new WithWireChannelTests ++ new SimConfig) object Generator extends App with freechips.rocketchip.util.HasGeneratorUtilities { diff --git a/src/main/scala/midas/core/Channel.scala b/src/main/scala/midas/core/Channel.scala index 28346bc8..0d33b425 100644 --- a/src/main/scala/midas/core/Channel.scala +++ b/src/main/scala/midas/core/Channel.scala @@ -291,20 +291,23 @@ class ReadyValidChannel[T <: Data]( class ReadyValidChannelUnitTest( clockRatio: IsRationalClockRatio = UnityClockRatio, - numTokens: Int = 4096, + numNonEmptyTokens: Int = 2048, timeout: Int = 50000 )(implicit p: Parameters) extends UnitTest(timeout) { require(clockRatio.isReciprocal || clockRatio.isIntegral) override val testName = "WireChannel ClockRatio: ${clockRatio.numerator}/${clockRatio.denominator}" - val payloadWidth = numTokens * clockRatio.numerator + val payloadWidth = log2Ceil(numNonEmptyTokens + 1) val dut = Module(new ReadyValidChannel(UInt(payloadWidth.W), flipped = false, clockRatio = clockRatio)) - val inputTokenNum = RegInit(0.U(payloadWidth.W)) - val outputTokenNum = RegInit(0.U(payloadWidth.W)) - val expectedOutputToken = RegInit(0.U(payloadWidth.W)) - val outputDuplicatesRemaining = RegInit((clockRatio.numerator - 1).U) + // Count host-level handshakes on tokens + val inputTokenNum = RegInit(0.U(log2Ceil(timeout).W)) + val outputTokenNum = RegInit(0.U(log2Ceil(timeout).W)) + + // For driving the values of non-empty tokens + val inputTokenPayload = RegInit(0.U(payloadWidth.W)) + val expectedOutputPayload = RegInit(0.U(payloadWidth.W)) val outputHReadyFuzz = LFSR64()(0) val outputTReadyFuzz = LFSR64()(0) @@ -315,7 +318,7 @@ class ReadyValidChannelUnitTest( dut.io.enq.host.hValid := inputHValidFuzz dut.io.enq.target.valid := inputTValidFuzz - dut.io.enq.target.bits := inputTokenNum + dut.io.enq.target.bits := inputTokenPayload dut.io.deq.host.hReady := outputHReadyFuzz && !finished dut.io.deq.target.ready := outputTReadyFuzz && !finished @@ -324,16 +327,42 @@ class ReadyValidChannelUnitTest( dut.io.trace.ready := DontCare - when (dut.io.enq.host.fire && dut.io.enq.target.fire) { + when (dut.io.enq.host.fire) { inputTokenNum := inputTokenNum + 1.U + + when ( dut.io.enq.target.fire) { + inputTokenPayload := inputTokenPayload + 1.U + } + } + + val (lowerTokenBound, upperTokenBound) = if (clockRatio.isIntegral) { + val lB = Mux( inputTokenNum > p(ChannelLen).U, + (inputTokenNum - p(ChannelLen).U) * clockRatio.numerator.U, + 0.U) + val uB = inputTokenNum * clockRatio.numerator.U + (lB, uB) + } else { + // The channel requires only a single input token after reset to produce its output token + val uB = (inputTokenNum + (clockRatio.denominator - 1).U) / clockRatio.denominator.U + val lB = Mux(uB > p(ChannelLen).U, + uB - p(ChannelLen).U, + 0.U) + (lB, uB) } - when (dut.io.deq.host.fire && dut.io.deq.target.fire) { - assert(finished || dut.io.deq.target.bits === expectedOutputToken, "Output token does not match expected value") + when (dut.io.deq.host.fire) { outputTokenNum := outputTokenNum + 1.U - expectedOutputToken := expectedOutputToken + 1.U //clockRatio.denominator.U - finished := finished || outputTokenNum === (numTokens-1).U + assert(finished || outputTokenNum <= upperTokenBound, "Received too many output tokens.") + assert(finished || outputTokenNum >= lowerTokenBound, "Received too few output tokens.") + + // Check the target-data + when (dut.io.deq.target.fire) { + assert(finished || dut.io.deq.target.bits === expectedOutputPayload, "Output token does not match expected value") + expectedOutputPayload := expectedOutputPayload + 1.U //clockRatio.denominator.U + + finished := finished || expectedOutputPayload === (numNonEmptyTokens - 1).U + } } io.finished := finished From a1abbc10ba2bfbef88bf734c7a52da616df4aa32 Mon Sep 17 00:00:00 2001 From: David Biancolin Date: Tue, 25 Sep 2018 12:28:51 -0700 Subject: [PATCH 6/7] Factor out recipes for building SW RTL simulators into Makefrags --- src/main/cc/Makefile | 65 ++++++---------------------------- src/main/cc/Makefrag-vcs | 40 +++++++++++++++++++++ src/main/cc/Makefrag-verilator | 36 +++++++++++++++++++ 3 files changed, 87 insertions(+), 54 deletions(-) create mode 100644 src/main/cc/Makefrag-vcs create mode 100644 src/main/cc/Makefrag-verilator diff --git a/src/main/cc/Makefile b/src/main/cc/Makefile index cc9fb34b..68adb8b3 100644 --- a/src/main/cc/Makefile +++ b/src/main/cc/Makefile @@ -76,17 +76,15 @@ $(OUT_DIR)/$(DESIGN)-$(PLATFORM): $(GEN_DIR)/$(DESIGN)-const.h $(lib) $(DRIVER) $(PLATFORM): $(OUT_DIR)/$(DESIGN)-$(PLATFORM) $(OUT_DIR)/$(DESIGN).chain +# Sources for building MIDAS-level simulators. Must be defined before sources VCS/Verilator Makefrags emul_files := simif simif_emul emul/mmio_$(PLATFORM) sample/sample -emul_h := $(addprefix $(midas_dir)/, $(addsuffix .h, $(emul_files) emul/mmio)) -emul_cc := $(addprefix $(midas_dir)/, $(addsuffix .cc, $(emul_files) sample/simif_sample)) -emul_v := $(v_dir)/emul_$(PLATFORM).v - -# Compile verilator emulation binary -VERILATOR ?= verilator --cc --exe -override VERILATOR_FLAGS := --assert -Wno-STMTDLY -O3 \ - -CFLAGS "$(CXXFLAGS)" -LDFLAGS "$(LDFLAGS) -lmidas" \ - $(VERILATOR_FLAGS) +emul_h := $(driver_h) $(endpoint_h) $( $(addprefix $(midas_dir)/, $(addsuffix .h, $(emul_files) emul/mmio)) +# This includes c sources and static libraries +emul_cc := $(DRIVER) $(endpoint_cc) $(addprefix $(midas_dir)/, $(addsuffix .cc, $(emul_files) sample/simif_sample)) $(lib) +emul_v := $(design_v) +emul_h_inc := $(GEN_DIR)/$(DESIGN)-const.h +# The lop level module must be called out for verilator ifeq ($(PLATFORM),zynq) top_module = ZynqShim endif @@ -94,55 +92,14 @@ ifeq ($(PLATFORM),f1) top_module = F1Shim endif -$(OUT_DIR)/V$(DESIGN): $(GEN_DIR)/$(DESIGN)-const.h $(design_v) $(lib) $(DRIVER) $(driver_h) $(emul_cc) $(emul_h) $(endpoint_cc) $(endpoint_h) - mkdir -p $(OUT_DIR) - rm -rf $(GEN_DIR)/V$(DESIGN).csrc - $(VERILATOR) $(VERILATOR_FLAGS) --top-module $(top_module) -Mdir $(GEN_DIR)/V$(DESIGN).csrc \ - -CFLAGS "-include $< -include $(GEN_DIR)/V$(DESIGN).csrc/V$(top_module).h" \ - -o $@ $(design_v) $(DRIVER) $(emul_cc) $(endpoint_cc) - $(MAKE) -C $(GEN_DIR)/V$(DESIGN).csrc -f V$(top_module).mk - -$(OUT_DIR)/V$(DESIGN)-debug: $(GEN_DIR)/$(DESIGN)-const.h $(design_v) $(lib) $(DRIVER) $(driver_h) $(emul_cc) $(emul_h) $(endpoint_cc) $(endpoint_h) - mkdir -p $(OUT_DIR) - rm -rf $(GEN_DIR)/V$(DESIGN)-debug.csrc - $(VERILATOR) $(VERILATOR_FLAGS) --trace --top-module $(top_module) -Mdir $(GEN_DIR)/V$(DESIGN)-debug.csrc \ - -CFLAGS "-include $< -include $(GEN_DIR)/V$(DESIGN)-debug.csrc/V$(top_module).h" \ - -o $@ $(design_v) $(DRIVER) $(emul_cc) $(endpoint_cc) - $(MAKE) -C $(GEN_DIR)/V$(DESIGN)-debug.csrc -f V$(top_module).mk +include Makefrag-verilator verilator: $(OUT_DIR)/V$(DESIGN) $(OUT_DIR)/$(DESIGN).chain $(OUT_DIR)/dramsim2_ini verilator-debug: $(OUT_DIR)/V$(DESIGN)-debug $(OUT_DIR)/$(DESIGN).chain $(OUT_DIR)/dramsim2_ini -# Compile VCS emulation binary -VCS ?= vcs -full64 -override VCS_FLAGS := -quiet -timescale=1ns/1ps +v2k +rad +vcs+initreg+random +vcs+lic+wait \ - -notice -line +lint=all,noVCDE,noONGS,noUI -quiet -debug_pp +no_notifier -e vcs_main -cpp $(CXX) \ - -CFLAGS "$(CXXFLAGS) -DVCS -I$(VCS_HOME)/include" \ - -LDFLAGS "$(LDFLAGS) -lmidas" \ - +define+CLOCK_PERIOD=$(CLOCK_PERIOD) \ - +define+RANDOMIZE_MEM_INIT \ - +define+RANDOMIZE_REG_INIT \ - +define+RANDOMIZE_GARBAGE_ASSIGN \ - +define+RANDOMIZE_INVALID_ASSIGN \ - $(VCS_FLAGS) - -$(OUT_DIR)/$(DESIGN): $(GEN_DIR)/$(DESIGN)-const.h $(design_v) $(emul_v) $(lib) $(DRIVER) $(driver_h) $(emul_cc) $(emul_h) $(endpoint_cc) $(endpoint_h) - mkdir -p $(OUT_DIR) - rm -rf $(GEN_DIR)/$(DESIGN).csrc - rm -rf $(OUT_DIR)/$(DESIGN).daidir - $(VCS) $(VCS_FLAGS) -Mdir=$(GEN_DIR)/$(DESIGN).csrc +vc+list \ - +define+STOP_COND=!emul.reset +define+PRINTF_COND=!emul.reset \ - -CFLAGS "-include $<" \ - -o $@ $(GEN_DIR)/$(DESIGN)-const.vh $(design_v) $(emul_v) $(lib) $(DRIVER) $(emul_cc) $(endpoint_cc) - -$(OUT_DIR)/$(DESIGN)-debug: $(GEN_DIR)/$(DESIGN)-const.h $(design_v) $(emul_v) $(lib) $(DRIVER) $(driver_h) $(emul_cc) $(emul_h) $(endpoint_cc) $(endpoint_h) - mkdir -p $(OUT_DIR) - rm -rf $(GEN_DIR)/$(DESIGN)-debug.csrc - rm -rf $(OUT_DIR)/$(DESIGN)-debug.daidir - $(VCS) $(VCS_FLAGS) -Mdir=$(GEN_DIR)/$(DESIGN)-debug.csrc +vc+list \ - +define+STOP_COND=!emul.reset +define+PRINTF_COND=!emul.reset +define+DEBUG \ - -CFLAGS "-include $<" \ - -o $@ $(GEN_DIR)/$(DESIGN)-const.vh $(design_v) $(emul_v) $(lib) $(DRIVER) $(emul_cc) $(endpoint_cc) +# Add an extra wrapper source for VCS simulators +vcs_wrapper_v := $(v_dir)/emul_$(PLATFORM).v +include Makefrag-vcs vcs: $(OUT_DIR)/$(DESIGN) $(OUT_DIR)/$(DESIGN).chain $(OUT_DIR)/dramsim2_ini vcs-debug: $(OUT_DIR)/$(DESIGN)-debug $(OUT_DIR)/$(DESIGN).chain $(OUT_DIR)/dramsim2_ini diff --git a/src/main/cc/Makefrag-vcs b/src/main/cc/Makefrag-vcs new file mode 100644 index 00000000..21bb12d7 --- /dev/null +++ b/src/main/cc/Makefrag-vcs @@ -0,0 +1,40 @@ +# VCS RTL Simulation Makefrag +# +# This makefrag stores common recipes for building RTL simulators with VCS +# +# Compulsory variables: +# All those described Makefrag-verilator +# vcs_wrapper_v: An additional verilog wrapper around the DUT not used in verilator +# CLOCK_PERIOD: Self explanatory + +VCS ?= vcs -full64 +override VCS_FLAGS := -quiet -timescale=1ns/1ps +v2k +rad +vcs+initreg+random +vcs+lic+wait \ + -notice -line +lint=all,noVCDE,noONGS,noUI -quiet -debug_pp +no_notifier -e vcs_main -cpp $(CXX) \ + -CFLAGS "$(CXXFLAGS) -DVCS -I$(VCS_HOME)/include" \ + -LDFLAGS "$(LDFLAGS) -lmidas" \ + +define+CLOCK_PERIOD=$(CLOCK_PERIOD) \ + +define+RANDOMIZE_MEM_INIT \ + +define+RANDOMIZE_REG_INIT \ + +define+RANDOMIZE_GARBAGE_ASSIGN \ + +define+RANDOMIZE_INVALID_ASSIGN \ + $(VCS_FLAGS) + +vcs_v := $(vcs_wrapper_v) $(emul_v) + +$(OUT_DIR)/$(DESIGN): $(emul_h_inc) $(vcs_v) $(emul_cc) $(emul_h) + mkdir -p $(OUT_DIR) + rm -rf $(GEN_DIR)/$(DESIGN).csrc + rm -rf $(OUT_DIR)/$(DESIGN).daidir + $(VCS) $(VCS_FLAGS) -Mdir=$(GEN_DIR)/$(DESIGN).csrc +vc+list \ + +define+STOP_COND=!emul.reset +define+PRINTF_COND=!emul.reset \ + -CFLAGS "-include $<" \ + -o $@ $(GEN_DIR)/$(DESIGN)-const.vh $(vcs_v) $(emul_cc) + +$(OUT_DIR)/$(DESIGN)-debug: $(emul_h_inc) $(vcs_v) $(emul_cc) $(emul_h) + mkdir -p $(OUT_DIR) + rm -rf $(GEN_DIR)/$(DESIGN)-debug.csrc + rm -rf $(OUT_DIR)/$(DESIGN)-debug.daidir + $(VCS) $(VCS_FLAGS) -Mdir=$(GEN_DIR)/$(DESIGN)-debug.csrc +vc+list \ + +define+STOP_COND=!emul.reset +define+PRINTF_COND=!emul.reset +define+DEBUG \ + -CFLAGS "-include $<" \ + -o $@ $(GEN_DIR)/$(DESIGN)-const.vh $(vcs_v) $(emul_cc) diff --git a/src/main/cc/Makefrag-verilator b/src/main/cc/Makefrag-verilator new file mode 100644 index 00000000..794380df --- /dev/null +++ b/src/main/cc/Makefrag-verilator @@ -0,0 +1,36 @@ +# Verilator RTL Simulation Makefrag +# +# This makefrag stores common recipes for building RTL simulators with Verilator +# +# Compulsory variables: +# OUT_DIR: See Makefile +# GEN_DIR: See Makefile +# DESIGN: See Makefile +# emul_h_inc: A c header with macro definitions to be included in all compilation units +# emul_cc: C++ sources +# emul_h: C++ headers +# emul_v: verilog sources +# +# Verilator Only: +# top_module: The top of the DUT + +VERILATOR ?= verilator --cc --exe +override VERILATOR_FLAGS := --assert -Wno-STMTDLY -O3 \ + -CFLAGS "$(CXXFLAGS)" -LDFLAGS "$(LDFLAGS) -lmidas" \ + $(VERILATOR_FLAGS) + +$(OUT_DIR)/V$(DESIGN): $(emul_h_inc) $(emul_v) $(emul_cc) $(emul_h) + mkdir -p $(OUT_DIR) + rm -rf $(GEN_DIR)/V$(DESIGN).csrc + $(VERILATOR) $(VERILATOR_FLAGS) --top-module $(top_module) -Mdir $(GEN_DIR)/V$(DESIGN).csrc \ + -CFLAGS "-include $< -include $(GEN_DIR)/V$(DESIGN).csrc/V$(top_module).h" \ + -o $@ $(emul_v) $(DRIVER) $(emul_cc) $(endpoint_cc) + $(MAKE) -C $(GEN_DIR)/V$(DESIGN).csrc -f V$(top_module).mk + +$(OUT_DIR)/V$(DESIGN)-debug: $(emul_h_inc) $(emul_v) $(emul_cc) $(emul_h) + mkdir -p $(OUT_DIR) + rm -rf $(GEN_DIR)/V$(DESIGN)-debug.csrc + $(VERILATOR) $(VERILATOR_FLAGS) --trace --top-module $(top_module) -Mdir $(GEN_DIR)/V$(DESIGN)-debug.csrc \ + -CFLAGS "-include $< -include $(GEN_DIR)/V$(DESIGN)-debug.csrc/V$(top_module).h" \ + -o $@ $(emul_v) $(emul_cc) + $(MAKE) -C $(GEN_DIR)/V$(DESIGN)-debug.csrc -f V$(top_module).mk From 80137ec0703ad1be61bd72892541353d1f74ce1c Mon Sep 17 00:00:00 2001 From: David Biancolin Date: Fri, 28 Sep 2018 11:55:18 -0700 Subject: [PATCH 7/7] Shuffle around RTL simulation and unittest makefrags --- src/main/cc/Makefile | 29 ++- src/main/cc/Makefrag-vcs | 40 --- src/main/cc/rtlsim/Makefrag-vcs | 43 +++ src/main/cc/{ => rtlsim}/Makefrag-verilator | 16 +- src/main/cc/rtlsim/generic_vharness.cc | 273 ++++++++++++++++++++ src/main/cc/unittest/Makefrag | 83 ++++++ src/main/resources/midas/unittest/Makefrag | 45 ---- src/main/scala/midas/SynthUnitTests.scala | 18 +- 8 files changed, 441 insertions(+), 106 deletions(-) delete mode 100644 src/main/cc/Makefrag-vcs create mode 100644 src/main/cc/rtlsim/Makefrag-vcs rename src/main/cc/{ => rtlsim}/Makefrag-verilator (63%) create mode 100644 src/main/cc/rtlsim/generic_vharness.cc create mode 100644 src/main/cc/unittest/Makefrag delete mode 100644 src/main/resources/midas/unittest/Makefrag diff --git a/src/main/cc/Makefile b/src/main/cc/Makefile index 68adb8b3..221a5d09 100644 --- a/src/main/cc/Makefile +++ b/src/main/cc/Makefile @@ -50,14 +50,17 @@ $(OUT_DIR)/$(DESIGN).chain: $(if $(wildcard $(GEN_DIR)/$(DESIGN).chain),cp $(GEN_DIR)/$(DESIGN).chain $@,) override CXXFLAGS += -I$(midas_dir) -I$(util_dir) -override LDFLAGS := $(LDFLAGS) -L$(GEN_DIR) -lstdc++ -lpthread -lgmp +# The trailing whitespace is important for some reason... +override LDFLAGS := $(LDFLAGS) -L$(GEN_DIR) -lstdc++ -lpthread -lgmp -lmidas -design_v = $(GEN_DIR)/$(shim).v +design_v := $(GEN_DIR)/$(shim).v +design_h := $(GEN_DIR)/$(DESIGN)-const.h +design_vh := $(GEN_DIR)/$(DESIGN)-const.vh driver_h = $(foreach t, $(DRIVER), $(wildcard $(dir $(t))/*.h)) endpoint_h := $(wildcard $(endpoint_dir)/*.h) endpoint_cc := $(wildcard $(endpoint_dir)/*.cc) endpoint_o := $(patsubst $(endpoint_dir)/%.cc, $(GEN_DIR)/%.o, $(endpoint_cc)) -$(endpoint_o): $(GEN_DIR)/%.o: $(endpoint_dir)/%.cc $(GEN_DIR)/$(DESIGN)-const.h $(endpoint_h) +$(endpoint_o): $(GEN_DIR)/%.o: $(endpoint_dir)/%.cc $(design_h) $(endpoint_h) $(CXX) $(CXXFLAGS) -c -o $@ $< -include $(word 2, $^) platform_files := simif simif_$(PLATFORM) sample/sample @@ -65,11 +68,11 @@ platform_h := $(addprefix $(midas_dir)/, $(addsuffix .h, $(platform_files))) platform_cc := $(addprefix $(midas_dir)/, $(addsuffix .cc, $(platform_files) sample/simif_sample)) platform_o := $(addprefix $(GEN_DIR)/, $(addsuffix .o, $(platform_files) sample/simif_sample)) -$(platform_o): $(GEN_DIR)/%.o: $(midas_dir)/%.cc $(GEN_DIR)/$(DESIGN)-const.h $(platform_h) +$(platform_o): $(GEN_DIR)/%.o: $(midas_dir)/%.cc $(design_h) $(platform_h) mkdir -p $(dir $@) $(CXX) $(CXXFLAGS) -c -o $@ $< -include $(word 2, $^) -$(OUT_DIR)/$(DESIGN)-$(PLATFORM): $(GEN_DIR)/$(DESIGN)-const.h $(lib) $(DRIVER) $(driver_h) $(platform_o) $(endpoint_o) +$(OUT_DIR)/$(DESIGN)-$(PLATFORM): $(design_h) $(lib) $(DRIVER) $(driver_h) $(platform_o) $(endpoint_o) mkdir -p $(OUT_DIR) $(CXX) $(CXXFLAGS) -include $< \ -o $@ $(DRIVER) $(dramsim_o) $(lib_o) $(platform_o) $(endpoint_o) $(LDFLAGS) @@ -77,12 +80,13 @@ $(OUT_DIR)/$(DESIGN)-$(PLATFORM): $(GEN_DIR)/$(DESIGN)-const.h $(lib) $(DRIVER) $(PLATFORM): $(OUT_DIR)/$(DESIGN)-$(PLATFORM) $(OUT_DIR)/$(DESIGN).chain # Sources for building MIDAS-level simulators. Must be defined before sources VCS/Verilator Makefrags +override CFLAGS += -include $(design_h) + emul_files := simif simif_emul emul/mmio_$(PLATFORM) sample/sample -emul_h := $(driver_h) $(endpoint_h) $( $(addprefix $(midas_dir)/, $(addsuffix .h, $(emul_files) emul/mmio)) +emul_h := $(driver_h) $(endpoint_h) $( $(addprefix $(midas_dir)/, $(addsuffix .h, $(emul_files) emul/mmio))) # This includes c sources and static libraries -emul_cc := $(DRIVER) $(endpoint_cc) $(addprefix $(midas_dir)/, $(addsuffix .cc, $(emul_files) sample/simif_sample)) $(lib) -emul_v := $(design_v) -emul_h_inc := $(GEN_DIR)/$(DESIGN)-const.h +emul_cc := $(DRIVER) $(endpoint_cc) $(addprefix $(midas_dir)/, $(addsuffix .cc, $(emul_files) sample/simif_sample)) $(lib) +emul_v := $(design_vh) $(design_v) # The lop level module must be called out for verilator ifeq ($(PLATFORM),zynq) @@ -91,15 +95,16 @@ endif ifeq ($(PLATFORM),f1) top_module = F1Shim endif - -include Makefrag-verilator +include rtlsim/Makefrag-verilator verilator: $(OUT_DIR)/V$(DESIGN) $(OUT_DIR)/$(DESIGN).chain $(OUT_DIR)/dramsim2_ini verilator-debug: $(OUT_DIR)/V$(DESIGN)-debug $(OUT_DIR)/$(DESIGN).chain $(OUT_DIR)/dramsim2_ini # Add an extra wrapper source for VCS simulators vcs_wrapper_v := $(v_dir)/emul_$(PLATFORM).v -include Makefrag-vcs +TB := emul +VCS_FLAGS := -e vcs_main +include rtlsim/Makefrag-vcs vcs: $(OUT_DIR)/$(DESIGN) $(OUT_DIR)/$(DESIGN).chain $(OUT_DIR)/dramsim2_ini vcs-debug: $(OUT_DIR)/$(DESIGN)-debug $(OUT_DIR)/$(DESIGN).chain $(OUT_DIR)/dramsim2_ini diff --git a/src/main/cc/Makefrag-vcs b/src/main/cc/Makefrag-vcs deleted file mode 100644 index 21bb12d7..00000000 --- a/src/main/cc/Makefrag-vcs +++ /dev/null @@ -1,40 +0,0 @@ -# VCS RTL Simulation Makefrag -# -# This makefrag stores common recipes for building RTL simulators with VCS -# -# Compulsory variables: -# All those described Makefrag-verilator -# vcs_wrapper_v: An additional verilog wrapper around the DUT not used in verilator -# CLOCK_PERIOD: Self explanatory - -VCS ?= vcs -full64 -override VCS_FLAGS := -quiet -timescale=1ns/1ps +v2k +rad +vcs+initreg+random +vcs+lic+wait \ - -notice -line +lint=all,noVCDE,noONGS,noUI -quiet -debug_pp +no_notifier -e vcs_main -cpp $(CXX) \ - -CFLAGS "$(CXXFLAGS) -DVCS -I$(VCS_HOME)/include" \ - -LDFLAGS "$(LDFLAGS) -lmidas" \ - +define+CLOCK_PERIOD=$(CLOCK_PERIOD) \ - +define+RANDOMIZE_MEM_INIT \ - +define+RANDOMIZE_REG_INIT \ - +define+RANDOMIZE_GARBAGE_ASSIGN \ - +define+RANDOMIZE_INVALID_ASSIGN \ - $(VCS_FLAGS) - -vcs_v := $(vcs_wrapper_v) $(emul_v) - -$(OUT_DIR)/$(DESIGN): $(emul_h_inc) $(vcs_v) $(emul_cc) $(emul_h) - mkdir -p $(OUT_DIR) - rm -rf $(GEN_DIR)/$(DESIGN).csrc - rm -rf $(OUT_DIR)/$(DESIGN).daidir - $(VCS) $(VCS_FLAGS) -Mdir=$(GEN_DIR)/$(DESIGN).csrc +vc+list \ - +define+STOP_COND=!emul.reset +define+PRINTF_COND=!emul.reset \ - -CFLAGS "-include $<" \ - -o $@ $(GEN_DIR)/$(DESIGN)-const.vh $(vcs_v) $(emul_cc) - -$(OUT_DIR)/$(DESIGN)-debug: $(emul_h_inc) $(vcs_v) $(emul_cc) $(emul_h) - mkdir -p $(OUT_DIR) - rm -rf $(GEN_DIR)/$(DESIGN)-debug.csrc - rm -rf $(OUT_DIR)/$(DESIGN)-debug.daidir - $(VCS) $(VCS_FLAGS) -Mdir=$(GEN_DIR)/$(DESIGN)-debug.csrc +vc+list \ - +define+STOP_COND=!emul.reset +define+PRINTF_COND=!emul.reset +define+DEBUG \ - -CFLAGS "-include $<" \ - -o $@ $(GEN_DIR)/$(DESIGN)-const.vh $(vcs_v) $(emul_cc) diff --git a/src/main/cc/rtlsim/Makefrag-vcs b/src/main/cc/rtlsim/Makefrag-vcs new file mode 100644 index 00000000..ed12fcf0 --- /dev/null +++ b/src/main/cc/rtlsim/Makefrag-vcs @@ -0,0 +1,43 @@ +# VCS RTL Simulation Makefrag +# +# This makefrag stores common recipes for building RTL simulators with VCS +# +# Compulsory variables: +# All those described Makefrag-verilator +# vcs_wrapper_v: An additional verilog wrapper around the DUT not used in verilator +# CLOCK_PERIOD: Self explanatory +# TB := The top level module on which the stop and printf conditions are defined +# + +VCS ?= vcs -full64 +override VCS_FLAGS := -quiet -timescale=1ns/1ps +v2k +rad +vcs+initreg+random +vcs+lic+wait \ + -notice -line +lint=all,noVCDE,noONGS,noUI -quiet -debug_pp +no_notifier -cpp $(CXX) \ + -Mdir=$(GEN_DIR)/$(DESIGN)-debug.csrc \ + +vc+list \ + -CFLAGS "$(CXXFLAGS) $(CFLAGS) -DVCS -I$(VCS_HOME)/include" \ + -LDFLAGS "$(LDFLAGS)" \ + -sverilog \ + +define+CLOCK_PERIOD=$(CLOCK_PERIOD) \ + +define+RANDOMIZE_MEM_INIT \ + +define+RANDOMIZE_REG_INIT \ + +define+RANDOMIZE_GARBAGE_ASSIGN \ + +define+RANDOMIZE_INVALID_ASSIGN \ + +define+STOP_COND=!$(TB).reset \ + +define+PRINTF_COND=!$(TB).reset \ + $(VCS_FLAGS) + +vcs_v := $(emul_v) $(vcs_wrapper_v) + +$(OUT_DIR)/$(DESIGN): $(vcs_v) $(emul_cc) $(emul_h) + mkdir -p $(OUT_DIR) + rm -rf $(GEN_DIR)/$(DESIGN).csrc + rm -rf $(OUT_DIR)/$(DESIGN).daidir + $(VCS) $(VCS_FLAGS) \ + -o $@ $(vcs_v) $(emul_cc) + +$(OUT_DIR)/$(DESIGN)-debug: $(vcs_v) $(emul_cc) $(emul_h) + mkdir -p $(OUT_DIR) + rm -rf $(GEN_DIR)/$(DESIGN)-debug.csrc + rm -rf $(OUT_DIR)/$(DESIGN)-debug.daidir + $(VCS) $(VCS_FLAGS) +define+DEBUG \ + -o $@ $(vcs_v) $(emul_cc) diff --git a/src/main/cc/Makefrag-verilator b/src/main/cc/rtlsim/Makefrag-verilator similarity index 63% rename from src/main/cc/Makefrag-verilator rename to src/main/cc/rtlsim/Makefrag-verilator index 794380df..aa0610c8 100644 --- a/src/main/cc/Makefrag-verilator +++ b/src/main/cc/rtlsim/Makefrag-verilator @@ -6,31 +6,31 @@ # OUT_DIR: See Makefile # GEN_DIR: See Makefile # DESIGN: See Makefile -# emul_h_inc: A c header with macro definitions to be included in all compilation units # emul_cc: C++ sources # emul_h: C++ headers -# emul_v: verilog sources +# emul_v: verilog sources and headers # # Verilator Only: # top_module: The top of the DUT VERILATOR ?= verilator --cc --exe override VERILATOR_FLAGS := --assert -Wno-STMTDLY -O3 \ - -CFLAGS "$(CXXFLAGS)" -LDFLAGS "$(LDFLAGS) -lmidas" \ + -CFLAGS "$(CXXFLAGS) $(CFLAGS)" \ + -LDFLAGS "$(LDFLAGS) " \ $(VERILATOR_FLAGS) -$(OUT_DIR)/V$(DESIGN): $(emul_h_inc) $(emul_v) $(emul_cc) $(emul_h) +$(OUT_DIR)/V$(DESIGN): $(emul_v) $(emul_cc) $(emul_h) mkdir -p $(OUT_DIR) rm -rf $(GEN_DIR)/V$(DESIGN).csrc $(VERILATOR) $(VERILATOR_FLAGS) --top-module $(top_module) -Mdir $(GEN_DIR)/V$(DESIGN).csrc \ - -CFLAGS "-include $< -include $(GEN_DIR)/V$(DESIGN).csrc/V$(top_module).h" \ - -o $@ $(emul_v) $(DRIVER) $(emul_cc) $(endpoint_cc) + -CFLAGS "-include $(GEN_DIR)/V$(DESIGN).csrc/V$(top_module).h" \ + -o $@ $(emul_v) $(emul_cc) $(MAKE) -C $(GEN_DIR)/V$(DESIGN).csrc -f V$(top_module).mk -$(OUT_DIR)/V$(DESIGN)-debug: $(emul_h_inc) $(emul_v) $(emul_cc) $(emul_h) +$(OUT_DIR)/V$(DESIGN)-debug: $(emul_v) $(emul_cc) $(emul_h) mkdir -p $(OUT_DIR) rm -rf $(GEN_DIR)/V$(DESIGN)-debug.csrc $(VERILATOR) $(VERILATOR_FLAGS) --trace --top-module $(top_module) -Mdir $(GEN_DIR)/V$(DESIGN)-debug.csrc \ - -CFLAGS "-include $< -include $(GEN_DIR)/V$(DESIGN)-debug.csrc/V$(top_module).h" \ + -CFLAGS "-include $(GEN_DIR)/V$(DESIGN)-debug.csrc/V$(top_module).h" \ -o $@ $(emul_v) $(emul_cc) $(MAKE) -C $(GEN_DIR)/V$(DESIGN)-debug.csrc -f V$(top_module).mk diff --git a/src/main/cc/rtlsim/generic_vharness.cc b/src/main/cc/rtlsim/generic_vharness.cc new file mode 100644 index 00000000..5346ecbc --- /dev/null +++ b/src/main/cc/rtlsim/generic_vharness.cc @@ -0,0 +1,273 @@ +// See LICENSE.SiFive for license details. +// See LICENSE.Berkeley for license details. + +#include "verilated.h" +#if VM_TRACE +#include +#include "verilated_vcd_c.h" +#endif +#include +#include +#include +#include +#include +#include +#include + +// Originally from Rocket-Chip, with RISC-V specific stuff stripped out + +// For option parsing, which is split across this file, Verilog, a few external +// files must be pulled in. The list of files and what they provide is +// enumerated: +// +// Biancolin: This will be useful later. +// $(ROCKETCHIP_DIR)/generated-src(-debug)?/$(CONFIG).plusArgs: +// defines: +// - PLUSARG_USAGE_OPTIONS +// variables: +// - static const char * verilog_plusargs + +static uint64_t trace_count = 0; +bool verbose; +bool done_reset; + +//void handle_sigterm(int sig) +//{ +// Biancolin: //TODO +//} + +double sc_time_stamp() +{ + return trace_count; +} + +extern "C" int vpi_get_vlog_info(void* arg) +{ + return 0; +} + +static void usage(const char * program_name) +{ + printf("Usage: %s [VERILOG PLUSARG]...\n", + program_name); + fputs("\ +Run a BINARY on the Rocket Chip emulator.\n\ +\n\ +Mandatory arguments to long options are mandatory for short options too.\n\ +\n\ +EMULATOR OPTIONS\n\ + -c, --cycle-count Print the cycle count before exiting\n\ + +cycle-count\n\ + -h, --help Display this help and exit\n\ + -m, --max-cycles=CYCLES Kill the emulation after CYCLES\n\ + +max-cycles=CYCLES\n\ + -s, --seed=SEED Use random number seed SEED\n\ + automatically.\n\ + -V, --verbose Enable all Chisel printfs (cycle-by-cycle info)\n\ + +verbose\n\ +", stdout); +#if VM_TRACE == 0 + fputs("\ +\n\ +EMULATOR DEBUG OPTIONS (only supported in debug build -- try `make debug`)\n", + stdout); +#endif + fputs("\ + -v, --vcd=FILE, Write vcd trace to FILE (or '-' for stdout)\n\ + -x, --dump-start=CYCLE Start VCD tracing at CYCLE\n\ + +dump-start\n\ +", stdout); + //fputs("\n" PLUSARG_USAGE_OPTIONS, stdout); +} + +int main(int argc, char** argv) +{ + unsigned random_seed = (unsigned)time(NULL) ^ (unsigned)getpid(); + uint64_t max_cycles = -1; + int ret = 0; + bool print_cycles = false; + // Port numbers are 16 bit unsigned integers. +#if VM_TRACE + FILE * vcdfile = NULL; + uint64_t start = 0; +#endif + int verilog_plusargs_legal = 1; + + while (1) { + static struct option long_options[] = { + {"cycle-count", no_argument, 0, 'c' }, + {"help", no_argument, 0, 'h' }, + {"max-cycles", required_argument, 0, 'm' }, + {"seed", required_argument, 0, 's' }, + {"rbb-port", required_argument, 0, 'r' }, +#if VM_TRACE + {"vcd", required_argument, 0, 'v' }, + {"dump-start", required_argument, 0, 'x' }, +#endif + {"verbose", no_argument, 0, 'V' } + }; + int option_index = 0; +#if VM_TRACE + int c = getopt_long(argc, argv, "-chm:s:r:v:Vx:", long_options, &option_index); +#else + int c = getopt_long(argc, argv, "-chm:s:r:V", long_options, &option_index); +#endif + if (c == -1) break; + retry: + switch (c) { + // Process long and short EMULATOR options + case '?': usage(argv[0]); return 1; + case 'c': print_cycles = true; break; + case 'h': usage(argv[0]); return 0; + case 'm': max_cycles = atoll(optarg); break; + case 's': random_seed = atoi(optarg); break; + case 'V': verbose = true; break; +#if VM_TRACE + case 'v': { + vcdfile = strcmp(optarg, "-") == 0 ? stdout : fopen(optarg, "w"); + if (!vcdfile) { + std::cerr << "Unable to open " << optarg << " for VCD write\n"; + return 1; + } + break; + } + case 'x': start = atoll(optarg); break; +#endif + // Process legacy '+' EMULATOR arguments by replacing them with + // their getopt equivalents + case 1: { + std::string arg = optarg; + if (arg.substr(0, 1) != "+") { + optind--; + goto done_processing; + } + if (arg == "+verbose") + c = 'V'; + else if (arg.substr(0, 12) == "+max-cycles=") { + c = 'm'; + optarg = optarg+12; + } +#if VM_TRACE + else if (arg.substr(0, 12) == "+dump-start=") { + c = 'x'; + optarg = optarg+12; + } +#endif + else if (arg.substr(0, 12) == "+cycle-count") + c = 'c'; + // If we don't find a legacy '+' EMULATOR argument, it still could be + // a VERILOG_PLUSARG and not an error. + //else if (verilog_plusargs_legal) { + // const char ** plusarg = &verilog_plusargs[0]; + // int legal_verilog_plusarg = 0; + // while (*plusarg && (legal_verilog_plusarg == 0)){ + // if (arg.substr(1, strlen(*plusarg)) == *plusarg) { + // legal_verilog_plusarg = 1; + // } + // plusarg ++; + // } + // if (!legal_verilog_plusarg) { + // verilog_plusargs_legal = 0; + // } else { + // c = 'P'; + // } + // goto retry; + //} + // Not a recongized plus-arg + else { + std::cerr << argv[0] << ": invalid plus-arg (Verilog or HTIF) \"" + << arg << "\"\n"; + c = '?'; + } + goto retry; + } + case 'P': break; // Nothing to do here, Verilog PlusArg + default: + c = '?'; + goto retry; + } + } + +done_processing: + if (verbose) + fprintf(stderr, "using random seed %u\n", random_seed); + + srand(random_seed); + srand48(random_seed); + + Verilated::randReset(2); + Verilated::commandArgs(argc, argv); + TEST_HARNESS *tile = new TEST_HARNESS; + +#if VM_TRACE + Verilated::traceEverOn(true); // Verilator must compute traced signals + std::unique_ptr vcdfd(new VerilatedVcdFILE(vcdfile)); + std::unique_ptr tfp(new VerilatedVcdC(vcdfd.get())); + if (vcdfile) { + tile->trace(tfp.get(), 99); // Trace 99 levels of hierarchy + tfp->open(""); + } +#endif + + //signal(SIGTERM, handle_sigterm); + + bool dump; + // reset for several cycles to handle pipelined reset + for (int i = 0; i < 10; i++) { + tile->reset = 1; + tile->clock = 0; + tile->eval(); +#if VM_TRACE + dump = tfp && trace_count >= start; + if (dump) + tfp->dump(static_cast(trace_count * 2)); +#endif + tile->clock = 1; + tile->eval(); +#if VM_TRACE + if (dump) + tfp->dump(static_cast(trace_count * 2 + 1)); +#endif + trace_count ++; + } + tile->reset = 0; + done_reset = true; + + while (!tile->io_success && trace_count < max_cycles) { + tile->clock = 0; + tile->eval(); +#if VM_TRACE + dump = tfp && trace_count >= start; + if (dump) + tfp->dump(static_cast(trace_count * 2)); +#endif + + tile->clock = 1; + tile->eval(); +#if VM_TRACE + if (dump) + tfp->dump(static_cast(trace_count * 2 + 1)); +#endif + trace_count++; + } + +#if VM_TRACE + if (tfp) + tfp->close(); + if (vcdfile) + fclose(vcdfile); +#endif + + if (trace_count == max_cycles) + { + fprintf(stderr, "*** FAILED *** via trace_count (timeout, seed %d) after %ld cycles\n", random_seed, trace_count); + ret = 2; + } + else if (verbose || print_cycles) + { + fprintf(stderr, "Completed after %ld cycles\n", trace_count); + } + + if (tile) delete tile; + return ret; +} diff --git a/src/main/cc/unittest/Makefrag b/src/main/cc/unittest/Makefrag new file mode 100644 index 00000000..ac2ba22b --- /dev/null +++ b/src/main/cc/unittest/Makefrag @@ -0,0 +1,83 @@ +# See LICENSE for license details. +# +# Makefrag for generating MIDAS's synthesizable unit tests + +# Compulsory arguments: +# ROCKETCHIP_DIR: Location of rocket chip source -- to grab verilog sources and simulation makefrags +# TODO: These are provided as resources -- fix. +# SBT: command to invoke sbt +# GEN_DIR: Directory into which to emit generate verilog + +DESIGN := TestHarness +CONFIG ?= AllUnitTests +OUT_DIR ?= $(GEN_DIR) +TB ?= TestDriver +EMUL ?= vcs +CLOCK_PERIOD ?= 1.0 + +MAKEFRAG_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) +sim_makefrag_dir := $(MAKEFRAG_DIR)/../rtlsim + +vsrc := $(ROCKETCHIP_DIR)/src/main/resources/vsrc +csrc := $(ROCKETCHIP_DIR)/src/main/resources/csrc + +# Stupidly guess what this test might depend on +src_path = src/main/scala +scala_srcs := $(shell find $(BASE_DIR) -name "*.scala") + +$(GEN_DIR)/$(DESIGN).v $(GEN_DIR)/$(DESIGN).behav_srams.v: $(scala_srcs) + mkdir -p $(@D) + cd $(BASE_DIR) && $(SBT) "runMain midas.unittest.Generator -td $(GEN_DIR) -conf $(CONFIG)" + touch $(GEN_DIR)/$(DESIGN).behav_srams.v + +verilog: $(GEN_DIR)/$(DESIGN).v + +# Common SW RTL simulation Makefrag arguments +# These aren't required as yet, but will be in the future +#bb_vsrcs = \ +# $(vsrc)/ClockDivider2.v \ +# $(vsrc)/ClockDivider3.v \ +# $(vsrc)/AsyncResetReg.v \ +# +#sim_vsrcs = \ +# $(bb_vsrcs) + +emul_v := $(GEN_DIR)/$(DESIGN).v #$(sim_vsrcs) +emul_h := +emul_cc := + +# VCS Makefrag arguments +ifeq ($(EMUL),vcs) +vcs_wrapper_v := $(vsrc)/TestDriver.v +VCS_FLAGS = +verbose +include $(sim_makefrag_dir)/Makefrag-vcs + +vcs = $(OUT_DIR)/$(DESIGN) +vcs_debug = $(OUT_DIR)/$(DESIGN)-debug + +vcs: $(vcs) +vcs-debug: $(vcs_debug) +else + +# Verilator Makefrag arguments +top_module := TestHarness +override CFLAGS += -I$(csrc) -include $(csrc)/verilator.h -DTEST_HARNESS=V$(top_module) -std=c++11 +override emul_cc += $(sim_makefrag_dir)/generic_vharness.cc + +include $(sim_makefrag_dir)/Makefrag-verilator + +verilator = $(OUT_DIR)/V$(DESIGN) +verilator_debug = $(OUT_DIR)/V$(DESIGN)-debug + +verilator: $(verilator) +verilator-debug: $(verilator_debug) +endif + +# Run recipes +run-midas-unittests: $($(EMUL)) + cd $(GEN_DIR) && $< + +run-midas-unittests-debug: $($(EMUL)_debug) + cd $(GEN_DIR) && $< + +.PHONY: run-midas-unittests run-midas-unittests-debug verilog diff --git a/src/main/resources/midas/unittest/Makefrag b/src/main/resources/midas/unittest/Makefrag deleted file mode 100644 index 6b29e166..00000000 --- a/src/main/resources/midas/unittest/Makefrag +++ /dev/null @@ -1,45 +0,0 @@ -# See LICENSE for license details. -# -# Makefrag for generating MIDAS's synthesizable unit tests - -# These need to be set by the parent Makefile -# -# rocketchip_dir: Location of rocket chip source -- to grab verilog sources and simulation makefrags -# base_dir: Main project's top-level, from which we'll launch sbt -# SBT: command to invoke sbt -# generated_dir: Directory into which to emit generate verilog -# sim_dir: Directory in which to build the simulator. - -PROJECT ?= midas.unittest -CONFIG ?= AllUnitTests - -MAKEFRAG_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) - -long_name := TestHarness -vsrc := $(rocketchip_dir)/src/main/resources/vsrc -csrc := $(rocketchip_dir)/src/main/resources/csrc - -# From Rocket Chip's vsim/Makefile -BACKEND ?= v -TB ?= TestDriver - -# Stupidly guess what this test might depend on -src_path = src/main/scala -scala_srcs := $(shell find $(base_dir) -name "*.scala") - -include $(rocketchip_dir)/vsim/Makefrag - -$(generated_dir)/$(long_name).v $(generated_dir)/$(long_name).behav_srams.v: $(scala_srcs) - mkdir -p $(@D) - cd $(base_dir) && $(SBT) "runMain midas.unittest.Generator -td $(generated_dir) " - touch $(generated_dir)/$(long_name).behav_srams.v - -verilog: $(generated_dir)/$(long_name).v - -run-midas-unittests: $(simv) - cd $(sim_dir) && $(exec_simv) - -run-midas-unittests-debug: $(simv_debug) - cd $(sim_dir) && $(exec_simv_debug) - -.PHONY: unittests unittests-debug verilog diff --git a/src/main/scala/midas/SynthUnitTests.scala b/src/main/scala/midas/SynthUnitTests.scala index 6afb4b05..6cc8e53e 100644 --- a/src/main/scala/midas/SynthUnitTests.scala +++ b/src/main/scala/midas/SynthUnitTests.scala @@ -16,7 +16,7 @@ import freechips.rocketchip.unittest.{UnitTests, TestHarness} class WithWireChannelTests extends Config((site, here, up) => { case UnitTests => (q: Parameters) => { implicit val p = q - val timeout = 200000 + val timeout = 2000000 Seq( Module(new WireChannelUnitTest(timeout = timeout, clockRatio = ReciprocalClockRatio(2))), Module(new WireChannelUnitTest(timeout = timeout, clockRatio = ReciprocalClockRatio(3))), @@ -49,9 +49,25 @@ class WithReadyValidChannelTests extends Config((site, here, up) => { } }) +// Failing tests +class WithTimeOutCheck extends Config((site, here, up) => { + case UnitTests => (q: Parameters) => { + implicit val p = q + Seq( + Module(new WireChannelUnitTest(timeout = 100, clockRatio = ReciprocalClockRatio(2))), + ) + } +}) + // Complete configs class AllUnitTests extends Config(new WithReadyValidChannelTests ++ new WithWireChannelTests ++ new SimConfig) +class TimeOutCheck extends Config(new WithTimeOutCheck ++ new SimConfig) +// Generates synthesizable unit tests for key modules, such as simulation channels +// See: src/main/cc/unittest/Makefile for the downstream RTL-simulation flow +// +// TODO: Make the core of this generator a trait that can be mixed into +// FireSim's ScalaTests for more type safety object Generator extends App with freechips.rocketchip.util.HasGeneratorUtilities { case class UnitTestOptions(