Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Instrumentation widget #53

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/main/scala/midas/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be added to the default.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, may I inquire as to why this is the case? From what I saw it seems that if there are no InstrumentationIOs, it shouldn't cause any backwards-incompatible changes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mainly, because it's not even clear yet that Endpoint-style matching will be the ultimate way you bind your widget to the target.

case MemModelKey => Some((p: Parameters) => new SimpleLatencyPipe()(p))
case FpgaMMIOSize => BigInt(1) << 12 // 4 KB
case MidasLLCKey => None
Expand Down
12 changes: 8 additions & 4 deletions src/main/scala/midas/core/Endpoints.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -30,15 +34,15 @@ 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)
case None => new NastiWidget()(param)
}
}
}

class SimNastiMemIO extends SimMemIO {
def matchType(data: Data) = data match {
case channel: NastiIO => channel.w.valid.dir == OUTPUT
Expand All @@ -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)
}
36 changes: 23 additions & 13 deletions src/main/scala/midas/core/FPGATop.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate your efforts to better support 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)
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/midas/core/SimWrapper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)}
Expand Down
168 changes: 168 additions & 0 deletions src/main/scala/midas/widgets/InstrumentationIO.scala
Original file line number Diff line number Diff line change
@@ -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()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why this needs to be so customized? Also, do you have to emit macros?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These make it a lot easier to pull data out of the instrumentation widget and was really useful for the testcase (and perhaps also in the general case) since it helped remove a lot of boilerplate code (e.g. to mark all the widgets as ready) which depended on the exact elements of the bundle.

I tried to make these functions but then 1) something in the build system complains about duplicate definition; 2) it would depend on simif_t and including simif.h here doesn't seem like a good idea.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok we should talk about this in person.

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()
}
7 changes: 6 additions & 1 deletion src/main/scala/midas/widgets/Widget.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down