From c2518719a1886166c0b157316425465c7e09ef34 Mon Sep 17 00:00:00 2001 From: Edward Wang Date: Wed, 15 Nov 2017 15:12:48 -0800 Subject: [PATCH 1/4] Don't crash for endpoints with empty valid or ready --- src/main/scala/midas/core/FPGATop.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/midas/core/FPGATop.scala b/src/main/scala/midas/core/FPGATop.scala index ec01e0d7..5172a959 100644 --- a/src/main/scala/midas/core/FPGATop.scala +++ b/src/main/scala/midas/core/FPGATop.scala @@ -111,8 +111,8 @@ class FPGATop(simIoType: SimWrapperIO)(implicit p: Parameters) extends Module wi } loop(port.hBits -> wires) - port.toHost.hValid := valid reduce (_ && _) - port.fromHost.hReady := ready reduce (_ && _) + port.toHost.hValid := valid.foldLeft(true.B)(_ && _) + port.fromHost.hReady := ready.foldLeft(true.B)(_ && _) } // Host Memory Channels From 9ad4d0f96a64aef53c3ee3d7485d1c0b7b438e28 Mon Sep 17 00:00:00 2001 From: Edward Wang Date: Mon, 20 Nov 2017 15:23:38 -0800 Subject: [PATCH 2/4] Instrumentation widget --- src/main/scala/midas/Config.scala | 4 +- src/main/scala/midas/core/Endpoints.scala | 12 +- src/main/scala/midas/core/FPGATop.scala | 36 ++-- src/main/scala/midas/core/SimWrapper.scala | 2 +- .../midas/widgets/InstrumentationIO.scala | 168 ++++++++++++++++++ src/main/scala/midas/widgets/Widget.scala | 7 +- 6 files changed, 209 insertions(+), 20 deletions(-) create mode 100644 src/main/scala/midas/widgets/InstrumentationIO.scala diff --git a/src/main/scala/midas/Config.scala b/src/main/scala/midas/Config.scala index 409b0479..f8feca29 100644 --- a/src/main/scala/midas/Config.scala +++ b/src/main/scala/midas/Config.scala @@ -9,6 +9,8 @@ import strober.core._ import config.{Parameters, Config, Field} import junctions.{NastiKey, NastiParameters} +import midas.endpoints.SimInstrumentationIO + trait PlatformType case object Zynq extends PlatformType case object F1 extends PlatformType @@ -28,7 +30,7 @@ class SimConfig extends Config((site, here, up) => { case KeepSamplesInMem => true case CtrlNastiKey => NastiParameters(32, 32, 12) case MemNastiKey => NastiParameters(64, 32, 6) - case EndpointKey => EndpointMap(Seq(new SimNastiMemIO, new SimAXI4MemIO)) + case EndpointKey => EndpointMap(Seq(new SimNastiMemIO, new SimAXI4MemIO, new SimInstrumentationIO)) case MemModelKey => Some((p: Parameters) => new SimpleLatencyPipe()(p)) case FpgaMMIOSize => BigInt(1) << 12 // 4 KB case MidasLLCKey => None diff --git a/src/main/scala/midas/core/Endpoints.scala b/src/main/scala/midas/core/Endpoints.scala index 872a9408..0a24a5a0 100644 --- a/src/main/scala/midas/core/Endpoints.scala +++ b/src/main/scala/midas/core/Endpoints.scala @@ -17,7 +17,11 @@ trait Endpoint { protected val channels = ArrayBuffer[(String, Record)]() protected val wires = HashSet[Bits]() def matchType(data: Data): Boolean - def widget(p: Parameters): EndpointWidget + + // When creating the widget, optionally pass it the channel type as well. + // Some widgets are parametrized per every channel, and so require this info. + def widget(channel: Option[Data] = None)(p: Parameters): EndpointWidget + def widgetName: String = getClass.getSimpleName final def size = channels.size final def apply(wire: Bits) = wires(wire) @@ -30,7 +34,7 @@ trait Endpoint { } abstract class SimMemIO extends Endpoint { - def widget(p: Parameters) = { + def widget(channel: Option[Data] = None)(p: Parameters) = { val param = p alterPartial ({ case NastiKey => p(MemNastiKey) }) (p(MemModelKey): @unchecked) match { case Some(modelGen) => modelGen(param) @@ -38,7 +42,7 @@ abstract class SimMemIO extends Endpoint { } } } - + class SimNastiMemIO extends SimMemIO { def matchType(data: Data) = data match { case channel: NastiIO => channel.w.valid.dir == OUTPUT @@ -55,5 +59,5 @@ class SimAXI4MemIO extends SimMemIO { case class EndpointMap(endpoints: Seq[Endpoint]) { def get(data: Data) = endpoints find (_ matchType data) - def ++(x: EndpointMap) = EndpointMap(endpoints ++ x.endpoints) + def ++(x: EndpointMap) = EndpointMap(endpoints ++ x.endpoints) } diff --git a/src/main/scala/midas/core/FPGATop.scala b/src/main/scala/midas/core/FPGATop.scala index 5172a959..313555af 100644 --- a/src/main/scala/midas/core/FPGATop.scala +++ b/src/main/scala/midas/core/FPGATop.scala @@ -93,21 +93,30 @@ class FPGATop(simIoType: SimWrapperIO)(implicit p: Parameters) extends Module wi b.elements.toList foreach { case (name, wire) => loop(target.elements(name), wire) } + case (target: Record, r: Record) => + r.elements.toList foreach { case (name, wire) => + loop(target.elements(name), wire) + } case (target: Vec[_], v: Vec[_]) => assert(target.size == v.size) (target.toSeq zip v.toSeq) foreach loop - case (target: Bits, wire: Bits) if wire.dir == INPUT => - val channels = simIo.getIns(wire) - channels.zipWithIndex foreach { case (in, i) => - in.bits := target >> UInt(i * simIo.channelWidth) - in.valid := port.fromHost.hValid || simResetNext + case (target: Bits, wire: Bits) => wire.dir match { + case INPUT => { + val channels = simIo.getIns(wire) + channels.zipWithIndex foreach { case (in, i) => + in.bits := target >> UInt(i * simIo.channelWidth) + in.valid := port.fromHost.hValid || simResetNext + } + ready ++= channels map (_.ready) + } + case OUTPUT => { + val channels = simIo.getOuts(wire) + target := Cat(channels.reverse map (_.bits)) + channels foreach (_.ready := port.toHost.hReady) + valid ++= channels map (_.valid) + } + case _ => throw new IllegalArgumentException(s"Wire '${wire.name}' has unknown direction '${wire.dir}'") } - ready ++= channels map (_.ready) - case (target: Bits, wire: Bits) if wire.dir == OUTPUT => - val channels = simIo.getOuts(wire) - target := Cat(channels.reverse map (_.bits)) - channels foreach (_.ready := port.toHost.hReady) - valid ++= channels map (_.valid) } loop(port.hBits -> wires) @@ -133,13 +142,14 @@ class FPGATop(simIoType: SimWrapperIO)(implicit p: Parameters) extends Module wi case (_: SimMemIO, None) => s"NastiWidget_$i" case _ => s"${endpoint.widgetName}_$i" } - val widget = addWidget(endpoint.widget(p), widgetName) + val endpointChannel = endpoint(i)._2 + val widget = addWidget(endpoint.widget(Some(endpointChannel))(p), widgetName) widget.reset := reset || simReset widget match { case model: MemModel => arb.io.master(i) <> model.io.host_mem case _ => } - channels2Port(widget.io.hPort, endpoint(i)._2) + channels2Port(widget.io.hPort, endpointChannel) // each widget should have its own reset queue val resetQueue = Module(new Queue(Bool(), 4)) resetQueue.reset := reset || simReset diff --git a/src/main/scala/midas/core/SimWrapper.scala b/src/main/scala/midas/core/SimWrapper.scala index 5604420e..4fda6ca7 100644 --- a/src/main/scala/midas/core/SimWrapper.scala +++ b/src/main/scala/midas/core/SimWrapper.scala @@ -19,7 +19,7 @@ object SimUtils { val inputs = ArrayBuffer[(Bits, String)]() val outputs = ArrayBuffer[(Bits, String)]() def loop(name: String, data: Data): Unit = data match { - case b: Bundle => + case b: Record => // Support Records more broadly (including Bundles) b.elements foreach {case (n, e) => loop(s"${name}_${n}", e)} case v: Vec[_] => v.zipWithIndex foreach {case (e, i) => loop(s"${name}_${i}", e)} diff --git a/src/main/scala/midas/widgets/InstrumentationIO.scala b/src/main/scala/midas/widgets/InstrumentationIO.scala new file mode 100644 index 00000000..d54cdfe2 --- /dev/null +++ b/src/main/scala/midas/widgets/InstrumentationIO.scala @@ -0,0 +1,168 @@ +// See LICENSE for license details. + +package midas +package endpoints + +import core._ +import widgets._ + +import config.Parameters + +import chisel3._ +import chisel3.util._ + +import scala.collection.immutable.ListMap + +import scala.language.dynamics // for transparent wrapping of Bundle + +/** + * InstrumentationIO is a bundle-parametrized widget which collects output + * information from a target design and enqueues into a queue for instrumentation + * on the other side. + * + * It only supports Outputs() for now; future work will add support for + * ValidIO or inputs. + */ + +class InstrumentationIO(bundle: Bundle) extends Record with Dynamic { + if (bundle.elements.isEmpty) { + throw new IllegalArgumentException(s"Cannot use a bundle (${bundle}) with no elements. Note: if you are using an anonymous Bundle, create a named bundle and used that instead. (See chisel3 #721)") + } + + override def toString: String = "InstrumentationIO(" + s"${bundle}" + ")" + + // For transparent access to bundle members. + // e.g. if bundle had a member called foo, we can access it with instr_io.foo + // instead of instr_io("foo"). + def selectDynamic(elt: String): Data = if (elt == "bundle") bundle else elements(elt) // we have to special case "bundle" otherwise it will try elements("bundle") + + // We don't support non-Outputs yet. + // TODO: won't work until chisel3 #617. + //~ bundle.elements.toSeq.map { case (field, elt) => require(elt.direction == Output, "All memebers of InstrumentationIO bundle must be Outputs") } + + // Basically clone the given bundle. + val elements = ListMap(bundle.elements.toSeq.map { case (field, elt) => (field -> elt.chiselCloneType) }: _*) + def apply(elt: String): Data = elements(elt) + + override def cloneType = new InstrumentationIO(bundle).asInstanceOf[this.type] +} + +class SimInstrumentationIO extends Endpoint { + def matchType(data: Data) = data match { + case channel: InstrumentationIO => true + case _ => false + } + def widget(channel: Option[Data] = None)(p: Parameters) = channel match { + case Some(io: InstrumentationIO) => new InstrumentationWidget(io.bundle.asInstanceOf[Bundle])(p) + case None => throw new IllegalArgumentException("InstrumentationIO widget requires the channel type!") + case x => throw new IllegalArgumentException(s"Unknown channel type ${x}") + } + override def widgetName = "InstrumentationWidget" +} + +class InstrumentationWidgetIO(bundle: Bundle)(implicit p: Parameters) extends EndpointWidgetIO()(p) { + val hPort = Flipped(HostPort(new InstrumentationIO(bundle))) +} + +class InstrumentationWidget(bundle: Bundle, queueDepth: Int = 8)(implicit p: Parameters) extends EndpointWidget()(p) { + val io = IO(new InstrumentationWidgetIO(bundle)) + + // Create queues to collect the data from the target. + val outBufs: Map[String, (Queue[Data], Bool)] = Map(bundle.elements.toSeq.map { case (fieldName, element) => { + val module = Module(new Queue(element, queueDepth)) + val blockingQueue = Wire(Bool()) + (fieldName, (module, blockingQueue)) + }} : _*) + + // Signal which tells us if at least one queue is full and blocking, in which case we need + // to stop consuming tokens. + val queueFull = outBufs.values.foldLeft(false.B)((signal, x) => { + val (queue, blocking) = x + signal || (!queue.io.enq.ready && blocking) + }) + + val target = io.hPort.hBits + + // Enqueue firing condition. + // 1. Tokens from the target are presented (io.hPort.toHost.hValid) + // 2. Queues aren't full (!queueFull) + // 3. target reset tokens are presented (io.tReset.valid) + val enqueueFire = io.hPort.toHost.hValid && !queueFull && io.tReset.valid + + // Dequeue firing condition. + // 1. the host is ready to accept tokens (io.hPort.fromHost.hReady) + val dequeueFire = io.hPort.fromHost.hReady + + val targetReset = enqueueFire & io.tReset.bits + // Reset buffers with target reset + for ((_, x) <- outBufs) { + val queue = x._1 + queue.reset := reset || targetReset + } + + // We consume instrumentation data from the target and don't send data to target + // yet, so just mark fromHost as always ready. + io.hPort.fromHost.hValid := true.B + + // Tokens from the target are consumed with enqueue firing condition + io.hPort.toHost.hReady := enqueueFire + // Target reset tokens are also consumed with enqueue firing condition + io.tReset.ready := enqueueFire + + // Connect queues. + for ((fieldName, (queue, blocking)) <- outBufs) { + // Enqueue side. + queue.io.enq.bits := target(fieldName) + // Drain-only mode: don't accept new enqueues + val drain_only = Wire(Bool()) + // Data should be enqueued with firing condition and if not in drain-only mode + queue.io.enq.valid := enqueueFire && !io.tReset.bits && !drain_only + + // Dequeue side. + val data_ready = Wire(Bool()) + // Dequeue when host is ready and sends data_ready. + queue.io.deq.ready := data_ready && dequeueFire + + // Generate memory mapped registers for data buffer. + // Extra registers: + // - blocking: Set to true if this queue being full shouldn't block the target. + // - drain_only: Set to true if this queue should only accept dequeues but not enqueues. + genROReg(queue.io.deq.bits, s"${fieldName}_data") + genROReg(!queue.io.enq.ready, s"${fieldName}_full") + genROReg(queue.io.deq.valid, s"${fieldName}_valid") + genWORegInit(blocking, s"${fieldName}_blocking", true.B) + genWORegInit(drain_only, s"${fieldName}_drain_only", false.B) + Pulsify(genWORegInit(data_ready, s"${fieldName}_ready", false.B), pulseLength = 1) + } + + override def genHeader(base: BigInt, sb: StringBuilder) { + super.genHeader(base, sb) + import CppGenerationUtils._ + + // Generate all_ready() + sb.append(s"#define ${widgetNamePrefix}_all_ready() \\\n") + for (fieldName <- outBufs.keys) { + val data_ready = s"${widgetNamePrefix}_${fieldName}_ready" + sb.append(s" write(${data_ready}, 1);\\\n") + } + sb.append(";\n") + + // Generate drain_only() + sb.append(s"#define ${widgetNamePrefix}_drain_only(value) \\\n") + for (fieldName <- outBufs.keys) { + val regname = s"${widgetNamePrefix}_${fieldName}_drain_only" + sb.append(s" write(${regname}, value);\\\n") + } + sb.append(";\n") + + // Generate blocking() + sb.append(s"#define ${widgetNamePrefix}_blocking(value) \\\n") + for (fieldName <- outBufs.keys) { + val regname = s"${widgetNamePrefix}_${fieldName}_blocking" + sb.append(s" write(${regname}, value);\\\n") + } + sb.append(";\n") + } + + genCRFile() +} diff --git a/src/main/scala/midas/widgets/Widget.scala b/src/main/scala/midas/widgets/Widget.scala index 0f923740..5a718cb5 100644 --- a/src/main/scala/midas/widgets/Widget.scala +++ b/src/main/scala/midas/widgets/Widget.scala @@ -133,10 +133,15 @@ abstract class Widget(implicit p: Parameters) extends Module { sb.append(CppGenerationUtils.genMacro(s"${name}(x)", s"${name}_ ## x")) } + // Widget name which will show up in all caps in the header file. + def widgetNamePrefix(): String = { + wName.getOrElse(name).toUpperCase + } + def genHeader(base: BigInt, sb: StringBuilder){ require(_finalized, "Must build Widgets with their companion object") headerComment(sb) - crRegistry.genHeader(wName.getOrElse(name).toUpperCase, base, sb) + crRegistry.genHeader(widgetNamePrefix(), base, sb) } def printCRs = crRegistry.printCRs From 10d3c2e97985a6da509f3dc46d9f8214691f71dd Mon Sep 17 00:00:00 2001 From: Edward Wang Date: Mon, 20 Nov 2017 22:52:53 -0800 Subject: [PATCH 3/4] Fix typo --- src/main/scala/midas/widgets/InstrumentationIO.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/midas/widgets/InstrumentationIO.scala b/src/main/scala/midas/widgets/InstrumentationIO.scala index d54cdfe2..e12fdc76 100644 --- a/src/main/scala/midas/widgets/InstrumentationIO.scala +++ b/src/main/scala/midas/widgets/InstrumentationIO.scala @@ -26,7 +26,7 @@ import scala.language.dynamics // for transparent wrapping of Bundle class InstrumentationIO(bundle: Bundle) extends Record with Dynamic { if (bundle.elements.isEmpty) { - throw new IllegalArgumentException(s"Cannot use a bundle (${bundle}) with no elements. Note: if you are using an anonymous Bundle, create a named bundle and used that instead. (See chisel3 #721)") + throw new IllegalArgumentException(s"Cannot use a bundle (${bundle}) with no elements. Note: if you are using an anonymous Bundle, create a named bundle and use that instead. (See chisel3 #721)") } override def toString: String = "InstrumentationIO(" + s"${bundle}" + ")" From 67acd4a7629140733ba57614871c8aeee0bfb2d5 Mon Sep 17 00:00:00 2001 From: Edward Wang Date: Mon, 20 Nov 2017 23:27:23 -0800 Subject: [PATCH 4/4] Update rocket widgets --- .../rocketchip/src/main/scala/endpoints/SerialWidget.scala | 2 +- .../rocketchip/src/main/scala/endpoints/UARTWidget.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/target-specific/rocketchip/src/main/scala/endpoints/SerialWidget.scala b/target-specific/rocketchip/src/main/scala/endpoints/SerialWidget.scala index 103a2bd3..4bd20b14 100644 --- a/target-specific/rocketchip/src/main/scala/endpoints/SerialWidget.scala +++ b/target-specific/rocketchip/src/main/scala/endpoints/SerialWidget.scala @@ -20,7 +20,7 @@ class SimSerialIO extends Endpoint { case channel: SerialIO => channel.out.valid.dir == OUTPUT case _ => false } - def widget(p: Parameters) = new SerialWidget()(p) + def widget(channel: Option[chisel3.Data] = None)(p: Parameters) = new SerialWidget()(p) override def widgetName = "SerialWidget" } diff --git a/target-specific/rocketchip/src/main/scala/endpoints/UARTWidget.scala b/target-specific/rocketchip/src/main/scala/endpoints/UARTWidget.scala index 8e0f1178..854be0da 100644 --- a/target-specific/rocketchip/src/main/scala/endpoints/UARTWidget.scala +++ b/target-specific/rocketchip/src/main/scala/endpoints/UARTWidget.scala @@ -19,7 +19,7 @@ class SimUART extends Endpoint { case channel: UARTPortIO => channel.txd.dir == OUTPUT case _ => false } - def widget(p: Parameters) = new UARTWidget()(p) + def widget(channel: Option[chisel3.Data] = None)(p: Parameters) = new UARTWidget()(p) override def widgetName = "UARTWidget" }