diff options
10 files changed, 209 insertions, 44 deletions
diff --git a/core/src/main/scala/chisel3/Data.scala b/core/src/main/scala/chisel3/Data.scala index ef43a3b0..f468335e 100644 --- a/core/src/main/scala/chisel3/Data.scala +++ b/core/src/main/scala/chisel3/Data.scala @@ -667,7 +667,7 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { topBindingOpt match { // DataView case Some(ViewBinding(target)) => reify(target).ref - case Some(AggregateViewBinding(viewMap, _)) => + case Some(AggregateViewBinding(viewMap)) => viewMap.get(this) match { case None => materializeWire() // FIXME FIRRTL doesn't have Aggregate Init expressions // This should not be possible because Element does the lookup in .topBindingOpt diff --git a/core/src/main/scala/chisel3/Element.scala b/core/src/main/scala/chisel3/Element.scala index 401f2bdf..39b7689c 100644 --- a/core/src/main/scala/chisel3/Element.scala +++ b/core/src/main/scala/chisel3/Element.scala @@ -36,10 +36,15 @@ abstract class Element extends Data { case Some(litArg) => Some(ElementLitBinding(litArg)) case _ => Some(DontCareBinding()) } - case Some(b @ AggregateViewBinding(viewMap, _)) => + // TODO Do we even need this? Looking up things in the AggregateViewBinding is fine + case Some(b @ AggregateViewBinding(viewMap)) => viewMap.get(this) match { - case Some(elt) => Some(ViewBinding(elt)) - case _ => throwException(s"Internal Error! $this missing from topBinding $b") + case Some(elt: Element) => Some(ViewBinding(elt)) + // TODO We could generate a reduced AggregateViewBinding, but is there a point? + // Generating the new object would be somewhat slow, it's not clear if we should do this + // matching anyway + case Some(data: Aggregate) => Some(b) + case _ => throwException(s"Internal Error! $this missing from topBinding $b") } case topBindingOpt => topBindingOpt } diff --git a/core/src/main/scala/chisel3/experimental/dataview/package.scala b/core/src/main/scala/chisel3/experimental/dataview/package.scala index 891ecb81..c583c516 100644 --- a/core/src/main/scala/chisel3/experimental/dataview/package.scala +++ b/core/src/main/scala/chisel3/experimental/dataview/package.scala @@ -97,11 +97,16 @@ package object dataview { val targetContains: Data => Boolean = implicitly[DataProduct[T]].dataSet(target) // Resulting bindings for each Element of the View - val childBindings = + // Kept separate from Aggregates for totality checking + val elementBindings = new mutable.HashMap[Data, mutable.ListBuffer[Element]] ++ viewFieldLookup.view.collect { case (elt: Element, _) => elt } .map(_ -> new mutable.ListBuffer[Element]) + // Record any Aggregates that correspond 1:1 for reification + // Using Data instead of Aggregate to avoid unnecessary checks + val aggregateMappings = mutable.ArrayBuffer.empty[(Data, Data)] + def viewFieldName(d: Data): String = viewFieldLookup.get(d).map(_ + " ").getOrElse("") + d.toString @@ -130,7 +135,7 @@ package object dataview { s"View field $fieldName has width ${vwidth} that is incompatible with target value $tex's width ${twidth}" ) } - childBindings(vex) += tex + elementBindings(vex) += tex } mapping.foreach { @@ -145,7 +150,7 @@ package object dataview { } getMatchedFields(aa, ba).foreach { case (aelt: Element, belt: Element) => onElt(aelt, belt) - case _ => // Ignore matching of Aggregates + case (t, v) => aggregateMappings += (v -> t) } } @@ -155,7 +160,7 @@ package object dataview { val targetSeen: Option[mutable.Set[Data]] = if (total) Some(mutable.Set.empty[Data]) else None - val resultBindings = childBindings.map { + val elementResult = elementBindings.map { case (data, targets) => val targetsx = targets match { case collection.Seq(target: Element) => target @@ -183,23 +188,25 @@ package object dataview { } view match { - case elt: Element => view.bind(ViewBinding(resultBindings(elt))) + case elt: Element => view.bind(ViewBinding(elementResult(elt))) case agg: Aggregate => - // We record total Data mappings to provide a better .toTarget - val topt = target match { - case d: Data if total => Some(d) + // Don't forget the potential mapping of the view to the target! + target match { + case d: Data if total => + aggregateMappings += (agg -> d) case _ => - // Record views that don't have the simpler .toTarget for later renaming - Builder.unnamedViews += view - None } - // TODO We must also record children as unnamed, some could be namable but this requires changes to the Binding + + val fullResult = elementResult ++ aggregateMappings + + // We need to record any Aggregates that don't have a 1-1 mapping (including the view + // itself) getRecursiveFields.lazily(view, "_").foreach { - case (agg: Aggregate, _) if agg != view => - Builder.unnamedViews += agg + case (unnamed: Aggregate, _) if !fullResult.contains(unnamed) => + Builder.unnamedViews += unnamed case _ => // Do nothing } - agg.bind(AggregateViewBinding(resultBindings, topt)) + agg.bind(AggregateViewBinding(fullResult)) } } @@ -208,8 +215,8 @@ package object dataview { private def unfoldView(elt: Element): LazyList[Element] = { def rec(e: Element): LazyList[Element] = e.topBindingOpt match { case Some(ViewBinding(target)) => target #:: rec(target) - case Some(AggregateViewBinding(mapping, _)) => - val target = mapping(e) + case Some(avb: AggregateViewBinding) => + val target = avb.lookup(e).get target #:: rec(target) case Some(_) | None => LazyList.empty } @@ -246,15 +253,11 @@ package object dataview { */ private[chisel3] def reifySingleData(data: Data): Option[Data] = { val candidate: Option[Data] = - data.binding.collect { // First check if this is a total mapping of an Aggregate - case AggregateViewBinding(_, Some(t)) => t - }.orElse { // Otherwise look via top binding - data.topBindingOpt match { - case None => None - case Some(ViewBinding(target)) => Some(target) - case Some(AggregateViewBinding(lookup, _)) => lookup.get(data) - case Some(_) => None - } + data.topBindingOpt match { + case None => None + case Some(ViewBinding(target)) => Some(target) + case Some(AggregateViewBinding(lookup)) => lookup.get(data) + case Some(_) => None } candidate.flatMap { d => // Candidate may itself be a view, keep tracing in those cases diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala index 60290f83..46a38e7c 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala @@ -173,12 +173,12 @@ object Lookupable { // We have to lookup the target(s) of the view since they may need to be underlying into the current context val newBinding = data.topBinding match { case ViewBinding(target) => ViewBinding(lookupData(reify(target))) - case avb @ AggregateViewBinding(map, targetOpt) => + case avb @ AggregateViewBinding(map) => data match { - case _: Element => ViewBinding(lookupData(reify(map(data)))) + case e: Element => ViewBinding(lookupData(reify(avb.lookup(e).get))) case _: Aggregate => // Provide a 1:1 mapping if possible - val singleTargetOpt = targetOpt.filter(_ => avb == data.binding.get).flatMap(reifySingleData) + val singleTargetOpt = map.get(data).filter(_ => avb == data.binding.get).flatMap(reifySingleData) singleTargetOpt match { case Some(singleTarget) => // It is 1:1! // This is a little tricky because the values in newMap need to point to Elements of newTarget @@ -187,15 +187,15 @@ object Lookupable { case (res, from) => (res: Data) -> mapRootAndExtractSubField(map(from), _ => newTarget) }.toMap - AggregateViewBinding(newMap, Some(newTarget)) + AggregateViewBinding(newMap + (result -> newTarget)) case None => // No 1:1 mapping so we have to do a flat binding // Just remap each Element of this aggregate val newMap = coiterate(result, data).map { // Upcast res to Data since Maps are invariant in the Key type parameter - case (res, from) => (res: Data) -> lookupData(reify(map(from))) + case (res, from) => (res: Data) -> lookupData(reify(avb.lookup(from).get)) }.toMap - AggregateViewBinding(newMap, None) + AggregateViewBinding(newMap) } } } @@ -204,8 +204,8 @@ object Lookupable { // We must also mark non-1:1 and child Aggregates in the view for renaming newBinding match { case _: ViewBinding => // Do nothing - case AggregateViewBinding(_, target) => - if (target.isEmpty) { + case AggregateViewBinding(childMap) => + if (!childMap.contains(result)) { Builder.unnamedViews += result } // Binding does not capture 1:1 for child aggregates views diff --git a/core/src/main/scala/chisel3/experimental/package.scala b/core/src/main/scala/chisel3/experimental/package.scala index ce258a25..9b9c83f4 100644 --- a/core/src/main/scala/chisel3/experimental/package.scala +++ b/core/src/main/scala/chisel3/experimental/package.scala @@ -57,6 +57,34 @@ package object experimental { type Direction = ActualDirection val Direction = ActualDirection + /** The same as [[IO]] except there is no prefix for the name of the val */ + def FlatIO[T <: Record](gen: => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = noPrefix { + import dataview._ + def coerceDirection(d: Data) = { + import chisel3.{SpecifiedDirection => SD} + DataMirror.specifiedDirectionOf(gen) match { + case SD.Flip => Flipped(d) + case SD.Input => Input(d) + case SD.Output => Output(d) + case _ => d + } + } + val ports: Seq[Data] = + gen.elements.toSeq.reverse.map { + case (name, data) => + val p = IO(coerceDirection(chiselTypeClone(data).asInstanceOf[Data])) + p.suggestName(name) + p + + } + + implicit val dv: DataView[Seq[Data], T] = DataView.mapping( + _ => chiselTypeClone(gen).asInstanceOf[T], + (seq, rec) => seq.zip(rec.elements.toSeq.reverse).map { case (port, (_, field)) => port -> field } + ) + ports.viewAs[T] + } + implicit class ChiselRange(val sc: StringContext) extends AnyVal { import scala.language.experimental.macros diff --git a/core/src/main/scala/chisel3/internal/Binding.scala b/core/src/main/scala/chisel3/internal/Binding.scala index 6431dd23..bab79bc1 100644 --- a/core/src/main/scala/chisel3/internal/Binding.scala +++ b/core/src/main/scala/chisel3/internal/Binding.scala @@ -141,11 +141,16 @@ case class DontCareBinding() extends UnconstrainedBinding private[chisel3] case class ViewBinding(target: Element) extends UnconstrainedBinding /** Binding for Aggregate Views - * @param childMap Mapping from children of this view to each child's target + * @param childMap Mapping from children of this view to their respective targets * @param target Optional Data this Aggregate views if the view is total and the target is a Data + * @note For any Elements in the childMap, both key and value must be Elements + * @note The types of key and value need not match for the top Data in a total view of type + * Aggregate */ -private[chisel3] case class AggregateViewBinding(childMap: Map[Data, Element], target: Option[Data]) - extends UnconstrainedBinding +private[chisel3] case class AggregateViewBinding(childMap: Map[Data, Data]) extends UnconstrainedBinding { + // Helper lookup function since types of Elements always match + def lookup(key: Element): Option[Element] = childMap.get(key).map(_.asInstanceOf[Element]) +} /** Binding for Data's returned from accessing an Instance/Definition members, if not readable/writable port */ private[chisel3] case object CrossModuleBinding extends TopBinding { diff --git a/docs/src/cookbooks/cookbook.md b/docs/src/cookbooks/cookbook.md index ae7c7bf6..b9e5db38 100644 --- a/docs/src/cookbooks/cookbook.md +++ b/docs/src/cookbooks/cookbook.md @@ -26,6 +26,7 @@ Please note that these examples make use of [Chisel's scala-style printing](../e * [How do I unpack a value ("reverse concatenation") like in Verilog?](#how-do-i-unpack-a-value-reverse-concatenation-like-in-verilog) * [How do I do subword assignment (assign to some bits in a UInt)?](#how-do-i-do-subword-assignment-assign-to-some-bits-in-a-uint) * [How do I create an optional I/O?](#how-do-i-create-an-optional-io) +* [How do I create I/O without a prefix?](#how-do-i-create-io-without-a-prefix) * [How do I minimize the number of bits used in an output vector](#how-do-i-minimize-the-number-of-bits-used-in-an-output-vector) * Predictable Naming * [How do I get Chisel to name signals properly in blocks like when/withClockAndReset?](#how-do-i-get-chisel-to-name-signals-properly-in-blocks-like-whenwithclockandreset) @@ -546,6 +547,50 @@ class ModuleWithOptionalIO(flag: Boolean) extends Module { } ``` +### How do I create I/O without a prefix? + +In most cases, you can simply call `IO` multiple times: + +```scala mdoc:silent:reset +import chisel3._ + +class MyModule extends Module { + val in = IO(Input(UInt(8.W))) + val out = IO(Output(UInt(8.W))) + + out := in +% 1.U +} +``` + +```scala mdoc:verilog +getVerilogString(new MyModule) +``` + +If you have a `Bundle` from which you would like to create ports without the +normal `val` prefix, you can use `FlatIO`: + +```scala mdoc:silent:reset +import chisel3._ +import chisel3.experimental.FlatIO + +class MyBundle extends Bundle { + val foo = Input(UInt(8.W)) + val bar = Output(UInt(8.W)) +} + +class MyModule extends Module { + val io = FlatIO(new MyBundle) + + io.bar := io.foo +% 1.U +} +``` + +Note that `io_` is nowhere to be seen! + +```scala mdoc:verilog +getVerilogString(new MyModule) +``` + ### How do I minimize the number of bits used in an output vector? Use inferred width and a `Seq` instead of a `Vec`: diff --git a/src/test/scala/chiselTests/experimental/DataView.scala b/src/test/scala/chiselTests/experimental/DataView.scala index 0285a524..e7caacfd 100644 --- a/src/test/scala/chiselTests/experimental/DataView.scala +++ b/src/test/scala/chiselTests/experimental/DataView.scala @@ -332,6 +332,36 @@ class DataViewSpec extends ChiselFlatSpec { chirrtl should include("dataOut <= vec[addr]") } + it should "support dynamic indexing for Vecs that correspond 1:1 in a view" in { + class MyBundle extends Bundle { + val foo = Vec(4, UInt(8.W)) + val bar = UInt(2.W) + } + implicit val myView = DataView[(Vec[UInt], UInt), MyBundle]( + _ => new MyBundle, + _._1 -> _.foo, + _._2 -> _.bar + ) + class MyModule extends Module { + val dataIn = IO(Input(UInt(8.W))) + val addr = IO(Input(UInt(2.W))) + val dataOut = IO(Output(UInt(8.W))) + + val vec = RegInit(0.U.asTypeOf(Vec(4, UInt(8.W)))) + val addrReg = Reg(UInt(2.W)) + val view = (vec, addrReg).viewAs[MyBundle] + // Dynamic indexing is more of a "generator" in Chisel3 than an individual node + // This style is not recommended, this is just testing the behavior + val selected = view.foo(view.bar) + view.bar := addr + selected := dataIn + dataOut := selected + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include("vec[addrReg] <= dataIn") + chirrtl should include("dataOut <= vec[addrReg]") + } + it should "error if you try to dynamically index a Vec view that does not correspond to a Vec target" in { class MyModule extends Module { val inA, inB = IO(Input(UInt(8.W))) diff --git a/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala b/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala index da27c9c8..ddeeab6e 100644 --- a/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala +++ b/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala @@ -125,9 +125,7 @@ class DataViewTargetSpec extends ChiselFlatSpec { val pairs = annos.collect { case DummyAnno(t, idx) => (idx, t.toString) }.sortBy(_._1) val expected = Seq( 0 -> "~MyParent|MyChild>out.foo", - // The child of the view that was itself an Aggregate got split because 1:1 is lacking here - 1 -> "~MyParent|MyChild>out.foo[0]", - 1 -> "~MyParent|MyChild>out.foo[1]", + 1 -> "~MyParent|MyChild>out.foo", 2 -> "~MyParent|MyParent/inst:MyChild>out.foo", 3 -> "~MyParent|MyParent/inst:MyChild>out" ) diff --git a/src/test/scala/chiselTests/experimental/FlatIOSpec.scala b/src/test/scala/chiselTests/experimental/FlatIOSpec.scala new file mode 100644 index 00000000..dfce447f --- /dev/null +++ b/src/test/scala/chiselTests/experimental/FlatIOSpec.scala @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.experimental + +import chisel3._ +import chisel3.util.Valid +import chisel3.stage.ChiselStage.emitChirrtl +import chisel3.experimental.FlatIO +import chiselTests.ChiselFlatSpec + +class FlatIOSpec extends ChiselFlatSpec { + behavior.of("FlatIO") + + it should "create ports without a prefix" in { + class MyModule extends RawModule { + val io = FlatIO(new Bundle { + val in = Input(UInt(8.W)) + val out = Output(UInt(8.W)) + }) + io.out := io.in + } + val chirrtl = emitChirrtl(new MyModule) + chirrtl should include("input in : UInt<8>") + chirrtl should include("output out : UInt<8>") + chirrtl should include("out <= in") + } + + it should "support bulk connections between FlatIOs and regular IOs" in { + class MyModule extends RawModule { + val in = FlatIO(Input(Valid(UInt(8.W)))) + val out = IO(Output(Valid(UInt(8.W)))) + out := in + } + val chirrtl = emitChirrtl(new MyModule) + chirrtl should include("out.bits <= bits") + chirrtl should include("out.valid <= valid") + } + + it should "dynamically indexing Vecs inside of FlatIOs" in { + class MyModule extends RawModule { + val io = FlatIO(new Bundle { + val addr = Input(UInt(2.W)) + val in = Input(Vec(4, UInt(8.W))) + val out = Output(Vec(4, UInt(8.W))) + }) + io.out(io.addr) := io.in(io.addr) + } + val chirrtl = emitChirrtl(new MyModule) + chirrtl should include("out[addr] <= in[addr]") + } +} |
