From 556ce39b2b33787407a3634f775b6a2a9da086c8 Mon Sep 17 00:00:00 2001 From: mergify[bot] Date: Tue, 8 Feb 2022 17:11:01 +0000 Subject: Overload getVerilogString to accept arguments (#2401) (#2402) (cherry picked from commit b55dc36d4edd1d22db37616235c003c89d68d29b) Co-authored-by: Carlos Eduardo --- src/main/scala/chisel3/verilog.scala | 22 ++++++++++++++++++++++ src/test/scala/chiselTests/Module.scala | 9 +++++++++ 2 files changed, 31 insertions(+) diff --git a/src/main/scala/chisel3/verilog.scala b/src/main/scala/chisel3/verilog.scala index b926a15c..c301ff98 100644 --- a/src/main/scala/chisel3/verilog.scala +++ b/src/main/scala/chisel3/verilog.scala @@ -4,7 +4,29 @@ import chisel3.stage.ChiselStage import firrtl.AnnotationSeq object getVerilogString { + + /** + * Returns a string containing the Verilog for the module specified by + * the target. + * + * @param gen the module to be converted to Verilog + * @return a string containing the Verilog for the module specified by + * the target + */ def apply(gen: => RawModule): String = ChiselStage.emitVerilog(gen) + + /** + * Returns a string containing the Verilog for the module specified by + * the target accepting arguments and annotations + * + * @param gen the module to be converted to Verilog + * @param args arguments to be passed to the compiler + * @param annotations annotations to be passed to the compiler + * @return a string containing the Verilog for the module specified by + * the target + */ + def apply(gen: => RawModule, args: Array[String] = Array.empty, annotations: AnnotationSeq = Seq.empty): String = + (new ChiselStage).emitVerilog(gen, args, annotations) } object emitVerilog { diff --git a/src/test/scala/chiselTests/Module.scala b/src/test/scala/chiselTests/Module.scala index 13dbe1e9..b0fece3b 100644 --- a/src/test/scala/chiselTests/Module.scala +++ b/src/test/scala/chiselTests/Module.scala @@ -18,6 +18,8 @@ class SimpleIO extends Bundle { class PlusOne extends Module { val io = IO(new SimpleIO) + val myReg = RegInit(0.U(8.W)) + dontTouch(myReg) io.out := io.in + 1.asUInt } @@ -267,6 +269,13 @@ class ModuleSpec extends ChiselPropSpec with Utils { property("getVerilogString(new PlusOne() should produce a valid Verilog string") { val s = getVerilogString(new PlusOne()) assert(s.contains("assign io_out = io_in + 32'h1")) + assert(s.contains("RANDOMIZE_REG_INIT")) + } + + property("getVerilogString(new PlusOne() should produce a valid Verilog string with arguments") { + val s = getVerilogString(new PlusOne(), Array("--emission-options=disableRegisterRandomization")) + assert(s.contains("assign io_out = io_in + 32'h1")) + assert(!s.contains("RANDOMIZE_REG_INIT")) } property("emitVerilog((new PlusOne()..) shall produce a valid Verilog file in a subfolder") { -- cgit v1.2.3 From 556ce6398e2f23d1f796d4626b4010f00726f4cd Mon Sep 17 00:00:00 2001 From: mergify[bot] Date: Thu, 10 Feb 2022 01:50:28 +0000 Subject: Make Tuple2 Lookupable (#2372) (#2406) (cherry picked from commit 024847d75079a125e5946e9dcf2ed9c14d2db730) Co-authored-by: Megan Wachs --- .../experimental/hierarchy/Lookupable.scala | 24 +++++++++++++++++++++- .../experimental/hierarchy/DefinitionSpec.scala | 10 +++++++++ .../experimental/hierarchy/Examples.scala | 6 ++++++ .../experimental/hierarchy/InstanceSpec.scala | 13 +++++++++++- 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala index 8552267a..a0c2209b 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala @@ -19,7 +19,7 @@ import chisel3.internal.{throwException, AggregateViewBinding, Builder, ChildBin */ @implicitNotFound( "@public is only legal within a class or trait marked @instantiable, and only on vals of type" + - " Data, BaseModule, IsInstantiable, IsLookupable, or Instance[_], or in an Iterable, Option, or Either" + " Data, BaseModule, IsInstantiable, IsLookupable, or Instance[_], or in an Iterable, Option, Either, or Tuple2" ) trait Lookupable[-B] { type C // Return type of the lookup @@ -402,6 +402,28 @@ object Lookupable { } } } + + implicit def lookupTuple2[X, Y]( + implicit sourceInfo: SourceInfo, + compileOptions: CompileOptions, + lookupableX: Lookupable[X], + lookupableY: Lookupable[Y] + ) = new Lookupable[(X, Y)] { + type C = (lookupableX.C, lookupableY.C) + def definitionLookup[A](that: A => (X, Y), definition: Definition[A]): C = { + val ret = that(definition.proto) + ( + lookupableX.definitionLookup[A](_ => ret._1, definition), + lookupableY.definitionLookup[A](_ => ret._2, definition) + ) + } + def instanceLookup[A](that: A => (X, Y), instance: Instance[A]): C = { + import instance._ + val ret = that(proto) + (lookupableX.instanceLookup[A](_ => ret._1, instance), lookupableY.instanceLookup[A](_ => ret._2, instance)) + } + } + implicit def lookupIsInstantiable[B <: IsInstantiable]( implicit sourceInfo: SourceInfo, compileOptions: CompileOptions diff --git a/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala b/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala index 63beb394..05344360 100644 --- a/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala +++ b/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala @@ -329,6 +329,16 @@ class DefinitionSpec extends ChiselFunSpec with Utils { annos should contain(MarkAnnotation("~Top|HasEither>x".rt, "xright")) annos should contain(MarkAnnotation("~Top|HasEither>y".rt, "yleft")) } + it("3.12: should work on tuple2") { + class Top() extends Module { + val i = Definition(new HasTuple2()) + mark(i.xy._1, "x") + mark(i.xy._2, "y") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasTuple2>x".rt, "x")) + annos should contain(MarkAnnotation("~Top|HasTuple2>y".rt, "y")) + } } describe("4: toDefinition") { it("4.0: should work on modules") { diff --git a/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala b/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala index 5b78b7cc..10c0464d 100644 --- a/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala +++ b/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala @@ -200,6 +200,12 @@ object Examples { @public val y: Either[Bool, UInt] = Left(Wire(Bool()).suggestName("y")) } @instantiable + class HasTuple2() extends Module { + val x = Wire(UInt(3.W)) + val y = Wire(Bool()) + @public val xy = (x, y) + } + @instantiable class HasVec() extends Module { @public val x = VecInit(1.U, 2.U, 3.U) } diff --git a/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala b/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala index 45d1f85f..9683d648 100644 --- a/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala +++ b/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala @@ -298,7 +298,18 @@ class InstanceSpec extends ChiselFunSpec with Utils { annos should contain(MarkAnnotation("~Top|Top/i:HasEither>x".rt, "xright")) annos should contain(MarkAnnotation("~Top|Top/i:HasEither>y".rt, "yleft")) } - it("3.12: should properly support val modifiers") { + it("3.12: should work on tuple2") { + class Top() extends Module { + val i = Instance(Definition(new HasTuple2())) + mark(i.xy._1, "x") + mark(i.xy._2, "y") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasTuple2>x".rt, "x")) + annos should contain(MarkAnnotation("~Top|Top/i:HasTuple2>y".rt, "y")) + } + + it("3.13: should properly support val modifiers") { class SupClass extends Module { val value = 10 val overriddenVal = 10 -- cgit v1.2.3 From be4feccad0d4fe487a0bea57cb44702c08831429 Mon Sep 17 00:00:00 2001 From: mergify[bot] Date: Fri, 11 Feb 2022 21:46:50 +0000 Subject: Hierarchy API: make Mems lookupable (#2404) (#2410) (cherry picked from commit 2a985ac376698a2e6300fbee13001d82d3e13989) Co-authored-by: Deborah Soung --- core/src/main/scala/chisel3/Mem.scala | 9 +++-- core/src/main/scala/chisel3/Module.scala | 6 +++- .../experimental/hierarchy/Lookupable.scala | 41 +++++++++++++++++++++- core/src/main/scala/chisel3/internal/Builder.scala | 1 + .../experimental/hierarchy/Annotations.scala | 10 ++++-- .../experimental/hierarchy/DefinitionSpec.scala | 23 ++++++++++++ .../experimental/hierarchy/Examples.scala | 6 ++++ .../experimental/hierarchy/InstanceSpec.scala | 11 ++++++ 8 files changed, 101 insertions(+), 6 deletions(-) diff --git a/core/src/main/scala/chisel3/Mem.scala b/core/src/main/scala/chisel3/Mem.scala index 3f37308c..36984a3a 100644 --- a/core/src/main/scala/chisel3/Mem.scala +++ b/core/src/main/scala/chisel3/Mem.scala @@ -246,6 +246,11 @@ sealed abstract class MemBase[T <: Data](val t: T, val length: BigInt) )( implicit compileOptions: CompileOptions ): T = { + if (Builder.currentModule != _parent) { + throwException( + s"Cannot create a memory port in a different module (${Builder.currentModule.get.name}) than where the memory is (${_parent.get.name})." + ) + } requireIsHardware(idx, "memory port index") val i = Vec.truncateIndex(idx, length)(sourceInfo, compileOptions) @@ -267,7 +272,7 @@ sealed abstract class MemBase[T <: Data](val t: T, val length: BigInt) * @note when multiple conflicting writes are performed on a Mem element, the * result is undefined (unlike Vec, where the last assignment wins) */ -sealed class Mem[T <: Data] private (t: T, length: BigInt) extends MemBase(t, length) +sealed class Mem[T <: Data] private[chisel3] (t: T, length: BigInt) extends MemBase(t, length) object SyncReadMem { @@ -345,7 +350,7 @@ object SyncReadMem { * @note when multiple conflicting writes are performed on a Mem element, the * result is undefined (unlike Vec, where the last assignment wins) */ -sealed class SyncReadMem[T <: Data] private (t: T, n: BigInt, val readUnderWrite: SyncReadMem.ReadUnderWrite) +sealed class SyncReadMem[T <: Data] private[chisel3] (t: T, n: BigInt, val readUnderWrite: SyncReadMem.ReadUnderWrite) extends MemBase[T](t, n) { override def read(x: UInt): T = macro SourceInfoTransform.xArg diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala index 3611f5dd..ed323504 100644 --- a/core/src/main/scala/chisel3/Module.scala +++ b/core/src/main/scala/chisel3/Module.scala @@ -101,8 +101,8 @@ object Module extends SourceInfoDoc { compileOptions: CompileOptions ): T = { val parent = Builder.currentModule - val module: T = bc // bc is actually evaluated here + if (!parent.isEmpty) { Builder.currentModule = parent } module } @@ -229,6 +229,8 @@ package internal { // Private internal class to serve as a _parent for Data in cloned ports private[chisel3] class ModuleClone[T <: BaseModule](val getProto: T) extends PseudoModule with IsClone[T] { override def toString = s"ModuleClone(${getProto})" + // Do not call default addId function, which may modify a module that is already "closed" + override def addId(d: HasId): Unit = () def getPorts = _portsRecord // ClonePorts that hold the bound ports for this module // Used for setting the refs of both this module and the Record @@ -307,6 +309,8 @@ package internal { override def toString = s"DefinitionClone(${getProto})" // No addition components are generated private[chisel3] def generateComponent(): Option[Component] = None + // Do not call default addId function, which may modify a module that is already "closed" + override def addId(d: HasId): Unit = () // Necessary for toTarget to work private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = () // Module name is the same as proto's module name diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala index a0c2209b..bc94f95d 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala @@ -19,7 +19,7 @@ import chisel3.internal.{throwException, AggregateViewBinding, Builder, ChildBin */ @implicitNotFound( "@public is only legal within a class or trait marked @instantiable, and only on vals of type" + - " Data, BaseModule, IsInstantiable, IsLookupable, or Instance[_], or in an Iterable, Option, Either, or Tuple2" + " Data, BaseModule, MemBase, IsInstantiable, IsLookupable, or Instance[_], or in an Iterable, Option, Either, or Tuple2" ) trait Lookupable[-B] { type C // Return type of the lookup @@ -348,6 +348,45 @@ object Lookupable { } } + private[chisel3] def cloneMemToContext[T <: MemBase[_]]( + mem: T, + context: BaseModule + )( + implicit sourceInfo: SourceInfo, + compileOptions: CompileOptions + ): T = { + mem._parent match { + case None => mem + case Some(parent) => + val newParent = cloneModuleToContext(Proto(parent), context) + newParent match { + case Proto(p) if p == parent => mem + case Clone(mod: BaseModule) => + val existingMod = Builder.currentModule + Builder.currentModule = Some(mod) + val newChild: T = mem match { + case m: Mem[_] => new Mem(m.t.asInstanceOf[Data].cloneTypeFull, m.length).asInstanceOf[T] + case m: SyncReadMem[_] => + new SyncReadMem(m.t.asInstanceOf[Data].cloneTypeFull, m.length, m.readUnderWrite).asInstanceOf[T] + } + Builder.currentModule = existingMod + newChild.setRef(mem.getRef, true) + newChild + } + } + } + + implicit def lookupMem[B <: MemBase[_]](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = + new Lookupable[B] { + type C = B + def definitionLookup[A](that: A => B, definition: Definition[A]): C = { + cloneMemToContext(that(definition.proto), definition.getInnerDataContext.get) + } + def instanceLookup[A](that: A => B, instance: Instance[A]): C = { + cloneMemToContext(that(instance.proto), instance.getInnerDataContext.get) + } + } + import scala.language.higherKinds // Required to avoid warning for lookupIterable type parameter implicit def lookupIterable[B, F[_] <: Iterable[_]]( implicit sourceInfo: SourceInfo, diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index 247be57a..1ffe54ab 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -258,6 +258,7 @@ private[chisel3] trait HasId extends InstanceId { (p._component, this) match { case (Some(c), _) => refName(c) case (None, d: Data) if d.topBindingOpt == Some(CrossModuleBinding) => _ref.get.localName + case (None, _: MemBase[Data]) => _ref.get.localName case (None, _) => throwException(s"signalName/pathName should be called after circuit elaboration: $this, ${_parent}") } diff --git a/src/test/scala/chiselTests/experimental/hierarchy/Annotations.scala b/src/test/scala/chiselTests/experimental/hierarchy/Annotations.scala index 2c1d2e9e..ec71fe09 100644 --- a/src/test/scala/chiselTests/experimental/hierarchy/Annotations.scala +++ b/src/test/scala/chiselTests/experimental/hierarchy/Annotations.scala @@ -4,10 +4,11 @@ package chiselTests.experimental.hierarchy import _root_.firrtl.annotations._ import chisel3.experimental.{annotate, BaseModule} -import chisel3.Data +import chisel3.{Data, MemBase} import chisel3.experimental.hierarchy.{Definition, Hierarchy, Instance} -object Annotations { +// These annotations exist purely for testing purposes +private[hierarchy] object Annotations { case class MarkAnnotation(target: IsMember, tag: String) extends SingleTargetAnnotation[IsMember] { def duplicate(n: IsMember): Annotation = this.copy(target = n) } @@ -19,7 +20,12 @@ object Annotations { extends chisel3.experimental.ChiselAnnotation { def toFirrtl = if (isAbsolute) MarkAnnotation(d.toAbsoluteTarget, tag) else MarkAnnotation(d.toTarget, tag) } + case class MarkChiselMemAnnotation[T <: Data](m: MemBase[T], tag: String, isAbsolute: Boolean) + extends chisel3.experimental.ChiselAnnotation { + def toFirrtl = if (isAbsolute) MarkAnnotation(m.toAbsoluteTarget, tag) else MarkAnnotation(m.toTarget, tag) + } def mark(d: Data, tag: String): Unit = annotate(MarkChiselAnnotation(d, tag, false)) + def mark[T <: Data](d: MemBase[T], tag: String): Unit = annotate(MarkChiselMemAnnotation(d, tag, false)) def mark[B <: BaseModule](d: Hierarchy[B], tag: String): Unit = annotate(MarkChiselHierarchyAnnotation(d, tag, true)) def amark(d: Data, tag: String): Unit = annotate(MarkChiselAnnotation(d, tag, true)) def amark[B <: BaseModule](d: Hierarchy[B], tag: String): Unit = annotate(MarkChiselHierarchyAnnotation(d, tag, true)) diff --git a/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala b/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala index 05344360..6ff4a3eb 100644 --- a/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala +++ b/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala @@ -339,6 +339,29 @@ class DefinitionSpec extends ChiselFunSpec with Utils { annos should contain(MarkAnnotation("~Top|HasTuple2>x".rt, "x")) annos should contain(MarkAnnotation("~Top|HasTuple2>y".rt, "y")) } + it("3.13: should work on Mems/SyncReadMems") { + class Top() extends Module { + val i = Definition(new HasMems()) + mark(i.mem, "Mem") + mark(i.syncReadMem, "SyncReadMem") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasMems>mem".rt, "Mem")) + annos should contain(MarkAnnotation("~Top|HasMems>syncReadMem".rt, "SyncReadMem")) + } + it("3.14: should not create memory ports") { + class Top() extends Module { + val i = Definition(new HasMems()) + i.mem(0) := 100.U // should be illegal! + } + val failure = intercept[ChiselException] { + getFirrtlAndAnnos(new Top) + } + assert( + failure.getMessage == + "Cannot create a memory port in a different module (Top) than where the memory is (HasMems)." + ) + } } describe("4: toDefinition") { it("4.0: should work on modules") { diff --git a/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala b/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala index 10c0464d..aff0a771 100644 --- a/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala +++ b/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala @@ -258,4 +258,10 @@ object Examples { val i10 = Instance(tpDef1) val i11 = Instance(tpDef1) } + + @instantiable + class HasMems() extends Module { + @public val mem = Mem(8, UInt(32.W)) + @public val syncReadMem = SyncReadMem(8, UInt(32.W)) + } } diff --git a/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala b/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala index 9683d648..407a7237 100644 --- a/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala +++ b/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala @@ -331,6 +331,17 @@ class InstanceSpec extends ChiselFunSpec with Utils { @public override final lazy val y: Int = 4 } } + it("3.13: should work with Mems/SyncReadMems") { + class Top() extends Module { + val i = Instance(Definition(new HasMems())) + mark(i.mem, "Mem") + mark(i.syncReadMem, "SyncReadMem") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos.foreach { x => println(x.serialize) } + annos should contain(MarkAnnotation("~Top|Top/i:HasMems>mem".rt, "Mem")) + annos should contain(MarkAnnotation("~Top|Top/i:HasMems>syncReadMem".rt, "SyncReadMem")) + } } describe("4: toInstance") { it("4.0: should work on modules") { -- cgit v1.2.3 From 4b8584b1d2c46c76b1540e265a84eeb247d684e4 Mon Sep 17 00:00:00 2001 From: mergify[bot] Date: Tue, 15 Feb 2022 22:35:23 +0000 Subject: Make TruthTable accept unknown input width (#2387) (#2417) Widths are now padded to the maximum width of the inputs. Co-authored-by: Jack Koenig (cherry picked from commit 546b4e13fe90ff09d24b63664c072d46c13c0c38) Co-authored-by: Jiuyang Liu --- .../util/experimental/decode/TruthTable.scala | 9 ++++++-- .../util/experimental/TruthTableSpec.scala | 24 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala b/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala index e742fd66..00fa0f9c 100644 --- a/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala +++ b/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala @@ -31,10 +31,15 @@ object TruthTable { /** Convert a table and default output into a [[TruthTable]]. */ def apply(table: Iterable[(BitPat, BitPat)], default: BitPat, sort: Boolean = true): TruthTable = { - require(table.map(_._1.getWidth).toSet.size == 1, "input width not equal.") + val inputWidth = table.map(_._1.getWidth).max require(table.map(_._2.getWidth).toSet.size == 1, "output width not equal.") val outputWidth = table.map(_._2.getWidth).head - val mergedTable = table + val mergedTable = table.map { + // pad input signals if necessary + case (in, out) if inputWidth > in.width => + (BitPat.N(inputWidth - in.width) ## in, out) + case (in, out) => (in, out) + } .groupBy(_._1.toString) .map { case (key, values) => diff --git a/src/test/scala/chiselTests/util/experimental/TruthTableSpec.scala b/src/test/scala/chiselTests/util/experimental/TruthTableSpec.scala index 2ef316bb..fa2c6f08 100644 --- a/src/test/scala/chiselTests/util/experimental/TruthTableSpec.scala +++ b/src/test/scala/chiselTests/util/experimental/TruthTableSpec.scala @@ -80,4 +80,28 @@ class TruthTableSpec extends AnyFlatSpec { } assert(chisel3.stage.ChiselStage.emitChirrtl(new Foo) == chisel3.stage.ChiselStage.emitChirrtl(new Foo)) } + "TruthTable" should "accept unknown input width" in { + val t = TruthTable( + Seq( + BitPat(0.U) -> BitPat.dontCare(1), + BitPat(1.U) -> BitPat.dontCare(1), + BitPat(2.U) -> BitPat.dontCare(1), + BitPat(3.U) -> BitPat.dontCare(1), + BitPat(4.U) -> BitPat.dontCare(1), + BitPat(5.U) -> BitPat.dontCare(1), + BitPat(6.U) -> BitPat.dontCare(1), + BitPat(7.U) -> BitPat.dontCare(1) + ), + BitPat.N(1) + ) + assert(t.toString contains "000->?") + assert(t.toString contains "001->?") + assert(t.toString contains "010->?") + assert(t.toString contains "011->?") + assert(t.toString contains "100->?") + assert(t.toString contains "101->?") + assert(t.toString contains "110->?") + assert(t.toString contains "111->?") + assert(t.toString contains " 0") + } } -- cgit v1.2.3 From 271095622835df7b0025eb9fd55a99de9082dea2 Mon Sep 17 00:00:00 2001 From: mergify[bot] Date: Thu, 3 Mar 2022 18:07:58 +0000 Subject: Add Verilog-chisel side by side Reference Page to Docs (#2323) (#2426) Co-authored-by: Shola Ogunkelu @Shorla Co-authored-by: Megan Wachs Co-authored-by: Deborah Soung Completed as part of Outreachy Internship Dec 2021-March 2022. (cherry picked from commit 362702f3fd79bf1071db4acecc679f25a0b94a8a) Co-authored-by: Olushola Ogunkelu <77856859+Shorla@users.noreply.github.com>--- docs/src/cookbooks/cookbook.md | 3 +- docs/src/cookbooks/cookbooks.md | 1 + docs/src/cookbooks/verilog-vs-chisel.md | 832 ++++++++++++++++++++++++++++++++ 3 files changed, 835 insertions(+), 1 deletion(-) create mode 100644 docs/src/cookbooks/verilog-vs-chisel.md diff --git a/docs/src/cookbooks/cookbook.md b/docs/src/cookbooks/cookbook.md index ec7e9ed2..ea5892c3 100644 --- a/docs/src/cookbooks/cookbook.md +++ b/docs/src/cookbooks/cookbook.md @@ -31,7 +31,8 @@ Please note that these examples make use of [Chisel's scala-style printing](../e * [How can I dynamically set/parametrize the name of a module?](#how-can-i-dynamically-setparametrize-the-name-of-a-module) * Directionality * [How do I strip directions from a bidirectional Bundle (or other Data)?](#how-do-i-strip-directions-from-a-bidirectional-bundle-or-other-data) - + * [Side-by-Side Comparison of Verilog to Chisel](verilog-vs-chisel.md) + ## Type Conversions ### How do I create a UInt from an instance of a Bundle? diff --git a/docs/src/cookbooks/cookbooks.md b/docs/src/cookbooks/cookbooks.md index 7c3eb8b9..9681a1e8 100644 --- a/docs/src/cookbooks/cookbooks.md +++ b/docs/src/cookbooks/cookbooks.md @@ -11,6 +11,7 @@ If you have any requests or examples to share, please [file an issue](https://github.com/chipsalliance/chisel3/issues/new) and let us know! * [General Cookbooks](cookbook) +* [Verilog vs. Chisel Side-by-Side](verilog-vs-chisel) * [Naming Cookbook](naming) * [Troubleshooting Guide](troubleshooting) * [Hierarchy Cookbook](hierarchy) diff --git a/docs/src/cookbooks/verilog-vs-chisel.md b/docs/src/cookbooks/verilog-vs-chisel.md new file mode 100644 index 00000000..93fd5316 --- /dev/null +++ b/docs/src/cookbooks/verilog-vs-chisel.md @@ -0,0 +1,832 @@ + + + +# Verilog vs Chisel Side-By-Side +This page serves as a quick introduction to Chisel for those familiar with Verilog. It is by no means a comprehensive guide of everything Chisel can do. Feel free to file an issue with suggestions of things you'd like to see added to this page. + +```scala mdoc:invisible +import chisel3._ +import chisel3.util.{switch, is} +import chisel3.stage.ChiselStage +import chisel3.experimental.ChiselEnum +import chisel3.util.{Cat, Fill, DecoupledIO} +``` + + + + + +

Creating a Module

+ + + + + + + + +
VerilogChisel
+ +```verilog +module Foo ( + input a, + output b +); + assign b = a; +endmodule +``` + + + +```scala mdoc +class Foo extends Module { + val a = IO(Input(Bool())) + val b = IO(Output(Bool())) + b := a +} +``` +
+ + + +# Parameterizing a Module + + + + + + + + + + + + + + + + + + + + +
VerilogChisel
+ +```verilog +module PassthroughGenerator( + input [width-1:0] in, + output [width-1:0] out +); + + parameter width = 8; + + assign out = in; +endmodule +``` + + +```scala mdoc:silent +class PassthroughGenerator(width: Int = 8) extends Module { + val in = IO(Input(UInt(width.W))) + val out = IO(Output(UInt(width.W))) + + out := in +} +``` +```scala mdoc:invisible +ChiselStage.emitVerilog(new PassthroughGenerator(10)) +``` +
+ +```verilog +module ParameterizedWidthAdder( + input [in0Width-1:0] in0, + input [in1Width-1:0] in1, + output [sumWidth-1:0] sum +); + parameter in0Width = 8; + parameter in1Width = 1; + parameter sumWidth = 9; + + assign sum = in0 + in1; + +endmodule +``` + + + +```scala mdoc:silent +class ParameterizedWidthAdder( + in0Width: Int, + in1Width: Int, + sumWidth: Int) extends Module { + + val in0 = IO(Input(UInt(in0Width.W))) + val in1 = IO(Input(UInt(in1Width.W))) + val sum = IO(Output(UInt(sumWidth.W))) + + // a +& b includes the carry, a + b does not + sum := in0 +& in1 +} +``` +
+ +
+ + + +# Wire Assignment and Literal Values + + + + + + + + + + + + +
VerilogChisel
+ +```verilog +module MyWireAssignmentModule (); + + wire [31:0] aa = 'd42; + // Logical reg for use in always block, not real register + reg [31:0] a; + + // + always @(*) begin + a = aa; + end + + // Hex value initialization + wire [31:0] b = 32'hbabecafe; + + // Declaration separate from Assignment + wire [15:0] c; + wire d; + + assign c = 16'b1; + assign d = 1'b1; + + // Signed values + wire signed [63:0] g; + assign g = -’d5; + + wire signed [31:0] h = 'd5; + + reg signed[31:0] f; + always@(*) begin + f = ‘d5; + end +endmodule +``` + + + + +```scala mdoc:silent + + +class MyWireAssignmentModule extends Module { + + val aa = 42.U(32.W) + val a = Wire(UInt(32.W)) + a := aa + val b = "hbabecafe".U(32.W) + val c = Wire(UInt(16.W)) + val d = Wire(Bool()) + c := "b1".U(16.W) + d := true.B + val g = Wire(SInt(64.W)) + g := -5.S + val h = 5.asSInt(32.W) + val f = Wire(SInt(32.W)) + f := 5.S +} +``` +```scala mdoc:invisible +ChiselStage.emitVerilog(new MyWireAssignmentModule) +``` + +
+ + + +# Register Declaration and Assignment + + + + + + + + + + + + +
VerilogChisel
+ +```verilog +module RegisterModule( + input clock, + input reset, + input [7:0] in, + output [7:0] out, + input differentClock, + input differentSyncReset, + input differentAsyncReset +); + + reg [7:0] registerWithoutInit; + reg [7:0] registerWithInit; + reg [7:0] registerOnDifferentClockAndSyncReset; + reg [7:0] registerOnDifferentClockAndAsyncReset; + + + always @(posedge clock) begin + registerWithoutInit <= in + 8'h1; + end + + always @(posedge clock) begin + if (reset) begin + registerWithInit <= 8'd42; + end else begin + registerWithInit <= registerWithInit - 8'h1; + end + end + + always @(posedge differentClock) begin + if (differentSyncReset) begin + registerOnDifferentClockAndSyncReset <= 8'h42; + end else begin + registerOnDifferentClockAndSyncReset <= in - 8'h1; + end + end + + always @(posedge differentClock or posedge differentAsyncReset) begin + if (differentAsyncReset) begin + registerOnDifferentClockAndAsyncReset <= 8'h24; + end else begin + registerOnDifferentClockAndAsyncReset <= in + 8'h2; + end + end + + assign out = in + + registerWithoutInit + + registerWithInit + + registerOnDifferentClockAndSyncReset + + registerOnDifferentClockAndAsyncReset; +endmodule + +``` + + +```scala mdoc:silent +class RegisterModule extends Module { + val in = IO(Input(UInt(8.W))) + val out = IO(Output(UInt(8.W))) + + val differentClock = IO(Input(Clock())) + val differentSyncReset = IO(Input(Bool())) + + val differentAsyncReset = IO(Input(AsyncReset())) + + val registerWithoutInit = Reg(UInt(8.W)) + + val registerWithInit = RegInit(42.U(8.W)) + + registerWithoutInit := in + 1.U + + registerWithInit := registerWithInit - 1.U + + val registerOnDifferentClockAndSyncReset = withClockAndReset(differentClock, differentSyncReset) { + val reg = RegInit("h42".U(8.W)) + reg + } + registerOnDifferentClockAndSyncReset := in - 1.U + + val registerOnDifferentClockAndAsyncReset = withClockAndReset(differentClock, differentAsyncReset) { + val reg = RegInit("h24".U(8.W)) + reg + } + registerOnDifferentClockAndAsyncReset := in + 2.U + + out := in + + registerWithoutInit + + registerWithInit + + registerOnDifferentClockAndSyncReset + + registerOnDifferentClockAndAsyncReset +} +``` +```scala mdoc:invisible +ChiselStage.emitVerilog(new RegisterModule) +``` +
+ + + +# Case Statements + + + + + + + + + + + + +
VerilogChisel
+ +```verilog +module CaseStatementModule( + input [2:0] a, + input [2:0] b, + input [2:0] c, + input [1:0] sel, + output reg [2:0] out +); + + always @(*) + case (sel) + 2'b00: out <= a; + 2'b01: out <= b; + 2'b10: out <= c; + default: out <= 3'b0; + end + end +endmodule +``` + + +```scala mdoc:silent +class CaseStatementModule extends Module { + val a, b, c= IO(Input(UInt(3.W))) + val sel = IO(Input(UInt(2.W))) + val out = IO(Output(UInt(3.W))) + + // default goes first + out := 0.U + + switch (sel) { + is ("b00".U) { out := a } + is ("b01".U) { out := b } + is ("b10".U) { out := c } + } +} +``` +```scala mdoc:invisible +ChiselStage.emitVerilog(new CaseStatementModule) +``` +
+ + + +# Case Statements Using Enums + + + + + + + + + + + + + + + + +
VerilogChisel
+ +```verilog +module CaseStatementEnumModule1 ( + input [2:0] rs1, + input [2:0] pc, + input AluMux1Sel sel, + output reg [2:0] out); + + typedef enum {SELECT_RS1, SELECT_PC} AluMux1Sel; + + case(sel) + SELECT_RS1: out <= rs1; + SELECT_PC: out <= pc; + default: out <= 3'b0; + end +endmodule +``` + + +```scala mdoc:silent +class CaseStatementEnumModule1 extends Module { + + object AluMux1Sel extends ChiselEnum { + val selectRS1, selectPC = Value + } + + import AluMux1Sel._ + val rs1, pc = IO(Input(UInt(3.W))) + val sel = IO(Input(AluMux1Sel())) + val out = IO(Output(UInt(3.W))) + + // default goes first + out := 0.U + + switch (sel) { + is (selectRS1) { out := rs1 } + is (selectPC) { out := pc } + } +} +``` +```scala mdoc:invisible +ChiselStage.emitVerilog(new CaseStatementEnumModule1) +``` +
+ +```verilog +module CaseStatementEnumModule2 (input clk); + + typedef enum { + INIT = 3, + IDLE = 'h13, + START = 'h17, + READY = 'h23 } StateValue; + + reg StateValue state; + + + always @(posedge clk) begin + case (state) + IDLE : state = START; + START : state = READY; + READY : state = IDLE ; + default : state = IDLE ; + endcase + end +endmodule +``` + + +```scala mdoc:silent +class CaseStatementEnumModule2 extends Module { + + object StateValue extends ChiselEnum { + val INIT = Value(0x03.U) + val IDLE = Value(0x13.U) + val START = Value(0x17.U) + val READY = Value(0x23.U) + } + import StateValue._ + val state = Reg(StateValue()) + + + switch (state) { + is (INIT) {state := IDLE} + is (IDLE) {state := START} + is (START) {state := READY} + is (READY) {state := IDLE} + } +} +``` +```scala mdoc:invisible +ChiselStage.emitVerilog(new CaseStatementEnumModule2) +``` +
+ + + + + + +# Multi-Dimensional Memories + + + + + + + + + + + + + + + + + + + +
VerilogChisel
+ +```verilog +module ReadWriteSmem( + input clock, + input reset, + input io_enable, + input io_write, + input [9:0] io_addr, + input [31:0] io_dataIn, + output [31:0] io_dataOut +); + +reg [31:0] mem [0:1023]; +reg [9:0] addr_delay; + +assign io_dataOut = mem[addr_delay] + + always @(posedge clock) begin + if (io_enable & io_write) begin + mem[io_addr] <= io_data; + end + if (io_enable) begin + addr_delay <= io_addr; + end + end +endmodule +``` + + +```scala mdoc:silent +class ReadWriteSmem extends Module { + val io = IO(new Bundle { + val enable = Input(Bool()) + val write = Input(Bool()) + val addr = Input(UInt(10.W)) + val dataIn = Input(UInt(32.W)) + val dataOut = Output(UInt(32.W)) + }) + + val mem = SyncReadMem(1024, UInt(32.W)) + + // Create one write port and one read port + mem.write(io.addr, io.dataIn) + io.dataOut := mem.read(io.addr, io.enable) +} +``` +```scala mdoc:invisible +ChiselStage.emitVerilog(new ReadWriteSmem) +``` +
+ +```verilog +module ReadWriteMem( + input clock, + input io_enable, + input io_write, + input [9:0] io_addr, + input [31:0] io_dataIn, + output [31:0] io_dataOut + ); + + reg [31:0] mem [0:1023]; + + assign io_dataOut = mem[io_addr]; + + always @(posedge clock) begin + if (io_enable && io_write) begin + mem[io_addr] <= io_dataIn; + end + end + +endmodule +``` + + + +```scala mdoc:silent +class ReadWriteMem extends Module { + val io = IO(new Bundle { + val enable = Input(Bool()) + val write = Input(Bool()) + val addr = Input(UInt(10.W)) + val dataIn = Input(UInt(32.W)) + val dataOut = Output(UInt(32.W)) +}) + val mem = Mem(1024, UInt(32.W)) + // Create one write port and one read port + mem.write(io.addr, io.dataIn) + io.dataOut := mem.read(io.addr) +} +``` +```scala mdoc:invisible +ChiselStage.emitVerilog(new ReadWriteMem) +``` +
+ + + +# Operators + + + + + + + + + + + + +
VerilogChiselGenerated Verilog
+ +```verilog + module OperatorExampleModule( + input clock, + input reset, + input [31:0] x, + input [31:0] y, + input [31:0] c, + output [31:0] add_res, + output [31:0] sub_res, + output [31:0] mod_res, + output [31:0] div_res, + output [31:0] and_res, + output [31:0] or_res, + output [31:0] xor_res, + output [31:0] not_res, + output [31:0] logical_not_res, + output [31:0] mux_res, + output [31:0] rshift_res, + output [31:0] lshift_res, + output andR_res, + output logical_and_res, + output logical_or_res, + output equ_res, + output not_equ_res, + output orR_res, + output xorR_res, + output gt_res, + output lt_res, + output geq_res, + output leq_res, + output single_bitselect_res, + output [63:0] mul_res, + output [63:0] cat_res, + output [1:0] multiple_bitselect_res, + output [95:0] fill_res +); + assign add_res = x + y; + assign sub_res = x - y; + assign mod_res = x % y; + assign mul_res = x * y; + assign div_res = x / y; + assign equ_res = x == y; + assign not_equ_res = x != y; + assign and_res = x & y; + assign or_res = x | y; + assign xor_res = x ^ y; + assign not_res = ~x; + assign logical_not_res = !(x == 32'h0); + assign logical_and_res = x[0] && y[0]; + assign logical_or_res = x[0] || y[0]; + assign cat_res = {x,y}; + assign mux_res = c[0] ? x : y; + assign rshift_res = x >> y[2:0]; + assign lshift_res = x << y[2:0]; + assign gt_res = x > y; + assign lt_res = x < y; + assign geq_res = x >= y; + assign leq_res = x <= y; + assign single_bitselect_res = x[1]; + assign multiple_bitselect_res = x[1:0]; + assign fill_res = {3{x}}; + assign andR_res = &x; + assign orR_res = |x; + assign xorR_res = ^x; + + + + + +endmodule + +``` + + + +```scala mdoc:silent +class OperatorExampleModule extends Module { + + val x, y, c = IO(Input(UInt(32.W))) + + val add_res, sub_res, + mod_res, div_res, and_res, + or_res, xor_res, not_res, + logical_not_res, mux_res, + rshift_res , lshift_res = IO(Output(UInt(32.W))) + + val logical_and_res, logical_or_res, + equ_res, not_equ_res, andR_res, + orR_res, xorR_res, gt_res,lt_res, + geq_res, leq_res,single_bitselect_res = IO(Output(Bool())) + + val mul_res, cat_res= IO(Output(UInt(64.W))) + + val multiple_bitselect_res = IO(Output(UInt(2.W))) + + val fill_res = IO(Output(UInt((3*32).W))) + + add_res := x + y + sub_res := x - y + mod_res := x % y + mul_res := x * y + div_res := x / y + equ_res := x === y + not_equ_res := x =/= y + and_res := x & y + or_res := x | y + xor_res := x ^ y + not_res := ~x + logical_not_res := !x + logical_and_res := x(0) && y(0) + logical_or_res := x(0) || y(0) + cat_res := Cat(x, y) + mux_res := Mux(c(0), x, y) + rshift_res := x >> y(2, 0) + lshift_res := x << y(2, 0) + gt_res := x > y + lt_res := x < y + geq_res := x >= y + leq_res := x <= y + single_bitselect_res := x(1) + multiple_bitselect_res := x(1, 0) + fill_res:= Fill(3,x) + andR_res := x.andR + orR_res := x.orR + xorR_res := x.xorR +} +``` + +```scala mdoc:invisible +ChiselStage.emitVerilog(new OperatorExampleModule) +``` +
+ + -- cgit v1.2.3 From 3de61ead55662c919a0b9a47be105f883812b96c Mon Sep 17 00:00:00 2001 From: mergify[bot] Date: Fri, 4 Mar 2022 02:23:34 +0000 Subject: Issue errors on out-of-range extracts when width is known (#2428) (#2429) * Issue errors on out-of-range extracts when width is known Firrtl will catch this later on, but better to error early if possible. * Test that errors are generated on OOB extracts when width is known (cherry picked from commit 462def429aa87becb544533880a3075a806c53e4) Co-authored-by: Andrew Waterman --- core/src/main/scala/chisel3/Bits.scala | 13 +++++++++++++ src/test/scala/chiselTests/UIntOps.scala | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/core/src/main/scala/chisel3/Bits.scala b/core/src/main/scala/chisel3/Bits.scala index 8a616d02..4133592f 100644 --- a/core/src/main/scala/chisel3/Bits.scala +++ b/core/src/main/scala/chisel3/Bits.scala @@ -107,6 +107,12 @@ sealed abstract class Bits(private[chisel3] val width: Width) extends Element wi (((value >> castToInt(x, "Index")) & 1) == 1).asBool }.getOrElse { requireIsHardware(this, "bits to be indexed") + + widthOption match { + case Some(w) if x >= w => Builder.error(s"High index $x is out of range [0, ${w - 1}]") + case _ => + } + pushOp(DefPrim(sourceInfo, Bool(), BitsExtractOp, this.ref, ILit(x), ILit(x))) } } @@ -160,6 +166,13 @@ sealed abstract class Bits(private[chisel3] val width: Width) extends Element wi ((value >> y) & ((BigInt(1) << w) - 1)).asUInt(w.W) }.getOrElse { requireIsHardware(this, "bits to be sliced") + + widthOption match { + case Some(w) if y >= w => Builder.error(s"High and low indices $x and $y are both out of range [0, ${w - 1}]") + case Some(w) if x >= w => Builder.error(s"High index $x is out of range [0, ${w - 1}]") + case _ => + } + pushOp(DefPrim(sourceInfo, UInt(Width(w)), BitsExtractOp, this.ref, ILit(x), ILit(y))) } } diff --git a/src/test/scala/chiselTests/UIntOps.scala b/src/test/scala/chiselTests/UIntOps.scala index 5fb86001..0010e9ac 100644 --- a/src/test/scala/chiselTests/UIntOps.scala +++ b/src/test/scala/chiselTests/UIntOps.scala @@ -199,6 +199,24 @@ class UIntOpsSpec extends ChiselPropSpec with Matchers with Utils { a[Exception] should be thrownBy extractCause[Exception] { ChiselStage.elaborate(new BadBoolConversion) } } + property("Out-of-bounds extraction from known-width UInts") { + a[ChiselException] should be thrownBy extractCause[ChiselException] { + ChiselStage.elaborate(new RawModule { + val u = IO(Input(UInt(2.W))) + u(2, 1) + }) + } + } + + property("Out-of-bounds single-bit extraction from known-width UInts") { + a[ChiselException] should be thrownBy extractCause[ChiselException] { + ChiselStage.elaborate(new RawModule { + val u = IO(Input(UInt(2.W))) + u(2) + }) + } + } + property("UIntOps should elaborate") { ChiselStage.elaborate { new UIntOps } } -- cgit v1.2.3 From 10e9697e132a1e0dd1f30ff40ae9119509efaeaf Mon Sep 17 00:00:00 2001 From: mergify[bot] Date: Fri, 4 Mar 2022 17:24:37 +0000 Subject: Add SVG Version of Bundle Example Diagram (#2425) (#2431) (cherry picked from commit 98fdfc5e609099cb4fb1749132cc62236148c6da) Co-authored-by: Megan Wachs --- docs/src/explanations/connection-operators.md | 2 +- docs/src/images/connection-operators-experiment.svg | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 docs/src/images/connection-operators-experiment.svg diff --git a/docs/src/explanations/connection-operators.md b/docs/src/explanations/connection-operators.md index 8a2117e1..86dca664 100644 --- a/docs/src/explanations/connection-operators.md +++ b/docs/src/explanations/connection-operators.md @@ -24,7 +24,7 @@ import chisel3.util.DecoupledIO ``` The diagram for the experiment can be viewed [here](https://docs.google.com/document/d/14C918Hdahk2xOGSJJBT-ZVqAx99_hg3JQIq-vaaifQU/edit?usp=sharing). -![Experiment Image](https://raw.githubusercontent.com/chipsalliance/chisel3/master/docs/src/images/chisel_01.png?sanitize=true) +![Experiment Image](https://raw.githubusercontent.com/chipsalliance/chisel3/master/docs/src/images/connection-operators-experiment.svg?sanitize=true) ```scala mdoc:silent diff --git a/docs/src/images/connection-operators-experiment.svg b/docs/src/images/connection-operators-experiment.svg new file mode 100644 index 00000000..c163cddc --- /dev/null +++ b/docs/src/images/connection-operators-experiment.svg @@ -0,0 +1 @@ + -- cgit v1.2.3 From 6c6409328034e06c4b722e87022029b287e9c90c Mon Sep 17 00:00:00 2001 From: mergify[bot] Date: Mon, 7 Mar 2022 08:00:47 +0000 Subject: Tweaks to the Verilog-vs-Chisel Page (#2432) (#2433) * Tweaks to the Verilog-vs-Chisel Page * Update cookbook.md * Update verilog-vs-chisel.md * Update verilog-vs-chisel.md (cherry picked from commit 7432bdff8369ba55db73c7934e41bb6f4060bb6d) Co-authored-by: Megan Wachs --- docs/src/cookbooks/cookbook.md | 1 - docs/src/cookbooks/verilog-vs-chisel.md | 15 ++++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/src/cookbooks/cookbook.md b/docs/src/cookbooks/cookbook.md index ea5892c3..118db228 100644 --- a/docs/src/cookbooks/cookbook.md +++ b/docs/src/cookbooks/cookbook.md @@ -31,7 +31,6 @@ Please note that these examples make use of [Chisel's scala-style printing](../e * [How can I dynamically set/parametrize the name of a module?](#how-can-i-dynamically-setparametrize-the-name-of-a-module) * Directionality * [How do I strip directions from a bidirectional Bundle (or other Data)?](#how-do-i-strip-directions-from-a-bidirectional-bundle-or-other-data) - * [Side-by-Side Comparison of Verilog to Chisel](verilog-vs-chisel.md) ## Type Conversions diff --git a/docs/src/cookbooks/verilog-vs-chisel.md b/docs/src/cookbooks/verilog-vs-chisel.md index 93fd5316..1adf609e 100644 --- a/docs/src/cookbooks/verilog-vs-chisel.md +++ b/docs/src/cookbooks/verilog-vs-chisel.md @@ -1,7 +1,14 @@ +--- +layout: docs +title: "Verilog-vs-Chisel" +section: "chisel3" +--- + # Verilog vs Chisel Side-By-Side + This page serves as a quick introduction to Chisel for those familiar with Verilog. It is by no means a comprehensive guide of everything Chisel can do. Feel free to file an issue with suggestions of things you'd like to see added to this page. ```scala mdoc:invisible @@ -126,11 +133,6 @@ class ParameterizedWidthAdder( ``` - - - - - @@ -688,8 +690,7 @@ ChiselStage.emitVerilog(new ReadWriteMem) Verilog Chisel - Generated Verilog - + -- cgit v1.2.3 From 941dd7ee76cadacbdd10b3d10d267f5d58a7ed4e Mon Sep 17 00:00:00 2001 From: mergify[bot] Date: Tue, 8 Mar 2022 00:30:37 +0000 Subject: Add scanLeftOr and scanRightOr utilies (#2407) (#2437) Co-authored-by: Jiuyang Liu Co-authored-by: Megan Wachs Co-authored-by: Jack Koenig (cherry picked from commit 73d3c26029c07c17ce179dfead092eab4fb8ae2c) Co-authored-by: Liu Xiaoyi --- .../util/experimental/algorithm/Bitwise.scala | 47 +++++++++++++++++++++ .../experimental/util/algorithm/Bitwise.scala | 48 ++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 integration-tests/src/test/scala/chiselTests/util/experimental/algorithm/Bitwise.scala create mode 100644 src/main/scala/chisel3/experimental/util/algorithm/Bitwise.scala diff --git a/integration-tests/src/test/scala/chiselTests/util/experimental/algorithm/Bitwise.scala b/integration-tests/src/test/scala/chiselTests/util/experimental/algorithm/Bitwise.scala new file mode 100644 index 00000000..6c8eb4b4 --- /dev/null +++ b/integration-tests/src/test/scala/chiselTests/util/experimental/algorithm/Bitwise.scala @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 + +import chisel3._ +import chisel3.util._ +import chiseltest._ +import chiseltest.formal._ +import org.scalatest.flatspec.AnyFlatSpec +import scala.math.min + +class ScanLeftOrTestModule(width: Int) extends Module { + val input = IO(Input(UInt(width.W))) + + var lsb = false.B + val vec = for(b <- input.asBools) yield { + val cur = b || lsb + lsb = cur + cur + } + val ref = VecInit(vec).asUInt + + val testee = scanLeftOr(input) + + assert(testee === ref) +} + +class ScanRightOrTestModule(width: Int) extends Module { + val input = IO(Input(UInt(width.W))) + + val ref = Reverse(scanLeftOr(Reverse(input))) + val testee = scanRightOr(input) + + assert(testee === ref) +} + +class scanOrTest extends AnyFlatSpec with ChiselScalatestTester with Formal { + "scanLeftOr" should "compute correctly" in { + for(i <- 1 to 16) { + verify(new ScanLeftOrTestModule(i), Seq(BoundedCheck(1))) + } + } + + "scanRightOr" should "compute correctly" in { + for(i <- 1 to 16) { + verify(new ScanRightOrTestModule(i), Seq(BoundedCheck(1))) + } + } +} diff --git a/src/main/scala/chisel3/experimental/util/algorithm/Bitwise.scala b/src/main/scala/chisel3/experimental/util/algorithm/Bitwise.scala new file mode 100644 index 00000000..6b4bb8f0 --- /dev/null +++ b/src/main/scala/chisel3/experimental/util/algorithm/Bitwise.scala @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.util + +import chisel3._ + +/** Map each bit to the logical OR of itself and all bits with lower index + * + * Here `scanLeft` means "start from the left and iterate to the right, where left is the lowest index", a common operation on arrays and lists. + * @example {{{ + * scanLeftOr("b00001000".U(8.W)) // Returns "b11111000".U + * scanLeftOr("b00010100".U(8.W)) // Returns "b11111100".U + * scanLeftOr("b00000000".U(8.W)) // Returns "b00000000".U + * }}} + */ +object scanLeftOr { + def apply(data: UInt): UInt = { + val width = data.widthOption match { + case Some(w) => w + case None => throw new IllegalArgumentException("Cannot call scanLeftOr on data with unknown width.") + } + + def helper(s: Int, x: UInt): UInt = + if (s >= width) x else helper(s + s, x | (x << s)(width - 1, 0)) + helper(1, data)(width - 1, 0) + } +} + +/** Map each bit to the logical OR of itself and all bits with higher index + * + * Here `scanRight` means "start from the right and iterate to the left, where right is the highest index", a common operation on arrays and lists. + * @example {{{ + * scanRightOr("b00001000".U) // Returns "b00001111".U + * scanRightOr("b00010100".U) // Returns "b00011111".U + * scanRightOr("b00000000".U) // Returns "b00000000".U + * }}} + */ +object scanRightOr { + def apply(data: UInt): UInt = { + val width = data.widthOption match { + case Some(w) => w + case None => throw new IllegalArgumentException("Cannot call scanRightOr on data with unknown width.") + } + def helper(s: Int, x: UInt): UInt = + if (s >= width) x else helper(s + s, x | (x >> s)) + helper(1, data)(width - 1, 0) + } +} -- cgit v1.2.3 From 4ee545d7706a2d2ba59902fb86a4393287327a9a Mon Sep 17 00:00:00 2001 From: mergify[bot] Date: Wed, 9 Mar 2022 20:40:25 +0000 Subject: Support BlackBoxes in D/I (#2438) (#2442) Also delete an errant println in InstanceSpec (cherry picked from commit 3462c54c018a52a377f1c89121b6ed99c5b0ae1d) Co-authored-by: Jack Koenig --- core/src/main/scala/chisel3/Module.scala | 34 +++++++++-- .../experimental/hierarchy/Lookupable.scala | 7 ++- .../experimental/hierarchy/Examples.scala | 7 +++ .../experimental/hierarchy/InstanceSpec.scala | 69 +++++++++++++++++++++- 4 files changed, 107 insertions(+), 10 deletions(-) diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala index ed323504..84139630 100644 --- a/core/src/main/scala/chisel3/Module.scala +++ b/core/src/main/scala/chisel3/Module.scala @@ -248,8 +248,14 @@ package internal { } // Maps proto ports to module clone's ports private[chisel3] lazy val ioMap: Map[Data, Data] = { - val name2Port = getPorts.elements - getProto.getChiselPorts.map { case (name, data) => data -> name2Port(name) }.toMap + getProto match { + // BlackBox needs special handling for its pseduo-io Bundle + case protoBB: BlackBox => + Map(protoBB._io.get -> getPorts.elements("io")) + case _ => + val name2Port = getPorts.elements + getProto.getChiselPorts.map { case (name, data) => data -> name2Port(name) }.toMap + } } // This module doesn't actually exist in the FIRRTL so no initialization to do private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = () @@ -267,7 +273,17 @@ package internal { case bad => throwException(s"Internal Error! Cloned-module Record $record has unexpected ref $bad") } // Set both the record and the module to have the same instance name - record.setRef(ModuleCloneIO(getProto, instName), force = true) // force because we did .forceName first + val ref = ModuleCloneIO(getProto, instName) + record.setRef(ref, force = true) // force because we did .forceName first + getProto match { + // BlackBox needs special handling for its pseduo-io Bundle + case _: BlackBox => + // Override the io Bundle's ref so that it thinks it is the top for purposes of + // generating FIRRTL + record.elements("io").setRef(ref, force = true) + case _ => // Do nothing + } + this.setRef(Ref(instName)) } } @@ -329,8 +345,8 @@ package internal { * @note These are not true Data (the Record doesn't correspond to anything in the emitted * FIRRTL yet its elements *do*) so have some very specialized behavior. */ - private[chisel3] class ClonePorts(elts: Data*)(implicit compileOptions: CompileOptions) extends Record { - val elements = ListMap(elts.map(d => d.instanceName -> d.cloneTypeFull): _*) + private[chisel3] class ClonePorts(elts: (String, Data)*)(implicit compileOptions: CompileOptions) extends Record { + val elements = ListMap(elts.map { case (name, d) => name -> d.cloneTypeFull }: _*) def apply(field: String) = elements(field) override def cloneType = (new ClonePorts(elts: _*)).asInstanceOf[this.type] } @@ -351,12 +367,18 @@ package internal { // Fake Module to serve as the _parent of the cloned ports // We don't create this inside the ModuleClone because we need the ref to be set by the // currentModule (and not clonePorts) - val clonePorts = new ClonePorts(proto.getModulePorts: _*) + val clonePorts = proto match { + // BlackBox needs special handling for its pseduo-io Bundle + case b: BlackBox => + new ClonePorts(proto.getChiselPorts :+ ("io" -> b._io.get): _*) + case _ => new ClonePorts(proto.getChiselPorts: _*) + } clonePorts.bind(PortBinding(cloneParent)) clonePorts.setAllParents(Some(cloneParent)) cloneParent._portsRecord = clonePorts // Normally handled during Module construction but ClonePorts really lives in its parent's parent if (!compileOptions.explicitInvalidate) { + // FIXME This almost certainly doesn't work since clonePorts is not a real thing... pushCommand(DefInvalid(sourceInfo, clonePorts.ref)) } if (proto.isInstanceOf[Module]) { diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala index bc94f95d..60290f83 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala @@ -10,7 +10,7 @@ import scala.annotation.implicitNotFound import scala.collection.mutable.HashMap import chisel3._ import chisel3.experimental.dataview.{isView, reify, reifySingleData} -import chisel3.internal.firrtl.{Arg, ILit, Index, Slot, ULit} +import chisel3.internal.firrtl.{Arg, ILit, Index, ModuleIO, Slot, ULit} import chisel3.internal.{throwException, AggregateViewBinding, Builder, ChildBinding, ViewBinding, ViewParent} /** Represents lookup typeclass to determine how a value accessed from an original IsInstantiable @@ -123,8 +123,8 @@ object Lookupable { def unrollCoordinates(res: List[Arg], d: Data): (List[Arg], Data) = d.binding.get match { case ChildBinding(parent) => d.getRef match { - case arg @ (_: Slot | _: Index) => unrollCoordinates(arg :: res, parent) - case other => err(s"Unroll coordinates failed for '$arg'! Unexpected arg '$other'") + case arg @ (_: Slot | _: Index | _: ModuleIO) => unrollCoordinates(arg :: res, parent) + case other => err(s"unrollCoordinates failed for '$arg'! Unexpected arg '$other'") } case _ => (res, d) } @@ -135,6 +135,7 @@ object Lookupable { val next = (coor.head, d) match { case (Slot(_, name), rec: Record) => rec.elements(name) case (Index(_, ILit(n)), vec: Vec[_]) => vec.apply(n.toInt) + case (ModuleIO(_, name), rec: Record) => rec.elements(name) case (arg, _) => err(s"Unexpected Arg '$arg' applied to '$d'! Root was '$start'.") } applyCoordinates(coor.tail, next) diff --git a/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala b/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala index aff0a771..fa26cbde 100644 --- a/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala +++ b/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala @@ -47,6 +47,13 @@ object Examples { val addOneDef = Seq.fill(3)(Definition(new AddOne)) out := in + 1.U } + @instantiable + class AddOneBlackBox extends BlackBox { + @public val io = IO(new Bundle { + val in = Input(UInt(32.W)) + val out = Output(UInt(32.W)) + }) + } @instantiable class AddTwo extends Module { diff --git a/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala b/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala index 407a7237..8d8f7ea5 100644 --- a/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala +++ b/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala @@ -43,6 +43,29 @@ class InstanceSpec extends ChiselFunSpec with Utils { val (chirrtl, _) = getFirrtlAndAnnos(new Top) chirrtl.serialize should include("inst i0 of AddOne") } + it("0.3: BlackBoxes should be supported") { + class Top extends Module { + val in = IO(Input(UInt(32.W))) + val out = IO(Output(UInt(32.W))) + val io = IO(new Bundle { + val in = Input(UInt(32.W)) + val out = Output(UInt(32.W)) + }) + val definition = Definition(new AddOneBlackBox) + val i0 = Instance(definition) + val i1 = Instance(definition) + i0.io.in := in + out := i0.io.out + io <> i1.io + } + val chirrtl = getFirrtlAndAnnos(new Top)._1.serialize + chirrtl should include("inst i0 of AddOneBlackBox") + chirrtl should include("inst i1 of AddOneBlackBox") + chirrtl should include("i0.in <= in") + chirrtl should include("out <= i0.out") + chirrtl should include("i1.in <= io.in") + chirrtl should include("io.out <= i1.out") + } } describe("1: Annotations on instances in same chisel compilation") { it("1.0: should work on a single instance, annotating the instance") { @@ -338,7 +361,6 @@ class InstanceSpec extends ChiselFunSpec with Utils { mark(i.syncReadMem, "SyncReadMem") } val (_, annos) = getFirrtlAndAnnos(new Top) - annos.foreach { x => println(x.serialize) } annos should contain(MarkAnnotation("~Top|Top/i:HasMems>mem".rt, "Mem")) annos should contain(MarkAnnotation("~Top|Top/i:HasMems>syncReadMem".rt, "SyncReadMem")) } @@ -717,6 +739,51 @@ class InstanceSpec extends ChiselFunSpec with Utils { annos should contain(e) } } + + it("7.4: should work on Views of BlackBoxes") { + @instantiable + class MyBlackBox extends BlackBox { + @public val io = IO(new Bundle { + val in = Input(UInt(8.W)) + val out = Output(UInt(8.W)) + }) + @public val innerView = io.viewAs + @public val foo = io.in.viewAs[UInt] + @public val bar = io.out.viewAs[UInt] + } + class Top extends RawModule { + val foo = IO(Input(UInt(8.W))) + val bar = IO(Output(UInt(8.W))) + val i = Instance(Definition(new MyBlackBox)) + val outerView = i.io.viewAs + i.foo := foo + bar := i.bar + mark(i.foo, "i.foo") + mark(i.bar, "i.bar") + mark(i.innerView.in, "i.innerView.in") + mark(outerView.out, "outerView.out") + } + val inst = "~Top|Top/i:MyBlackBox" + val expectedAnnos = List( + s"$inst>in".rt -> "i.foo", + s"$inst>out".rt -> "i.bar", + s"$inst>in".rt -> "i.innerView.in", + s"$inst>out".rt -> "outerView.out" + ) + val expectedLines = List( + "i.in <= foo", + "bar <= i.out" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + val text = chirrtl.serialize + for (line <- expectedLines) { + text should include(line) + } + for (e <- expectedAnnos.map(MarkAnnotation.tupled)) { + annos should contain(e) + } + } + } describe("8: @instantiable and @public should compose with CloneModuleAsRecord") { -- cgit v1.2.3 From 741761cfbac8d8b7e297666c66d91cb773a6f109 Mon Sep 17 00:00:00 2001 From: mergify[bot] Date: Thu, 10 Mar 2022 01:10:30 +0000 Subject: Emit FIRRTL bulkconnects whenever possible (#2381) (#2440) Chisel <> semantics differ somewhat from FIRRTL <= semantics, so we only emit <= when it would be legal. Otherwise we continue the old behavior of emitting a connection for every leaf-level Element. Co-authored-by: Deborah Soung Co-authored-by: Jack Koenig (cherry picked from commit 3553a1583403824718923a6cc530cec3b38f5704) Co-authored-by: Jared Barocsi <82000041+jared-barocsi@users.noreply.github.com> Co-authored-by: Jack Koenig --- .../chisel3/experimental/dataview/package.scala | 10 + .../main/scala/chisel3/internal/BiConnect.scala | 130 ++++++++++--- .../main/scala/chisel3/internal/MonoConnect.scala | 206 +++++++++++++++++++-- src/test/scala/chiselTests/BulkConnectSpec.scala | 106 +++++++++++ src/test/scala/chiselTests/BundleSpec.scala | 1 + .../CompatibilityInteroperabilitySpec.scala | 2 +- src/test/scala/chiselTests/MixedVecSpec.scala | 16 ++ src/test/scala/chiselTests/RecordSpec.scala | 19 ++ src/test/scala/chiselTests/VecLiteralSpec.scala | 5 +- .../scala/chiselTests/experimental/DataView.scala | 8 +- 10 files changed, 447 insertions(+), 56 deletions(-) create mode 100644 src/test/scala/chiselTests/BulkConnectSpec.scala diff --git a/core/src/main/scala/chisel3/experimental/dataview/package.scala b/core/src/main/scala/chisel3/experimental/dataview/package.scala index 3278d82c..891ecb81 100644 --- a/core/src/main/scala/chisel3/experimental/dataview/package.scala +++ b/core/src/main/scala/chisel3/experimental/dataview/package.scala @@ -262,4 +262,14 @@ package object dataview { } } + /** Determine the target of a View if it is a single Target + * + * @note An Aggregate may be a view of unrelated [[Data]] (eg. like a Seq or tuple) and thus this + * there is no single Data representing the Target and this function will return None + * @return The single Data target of this view or None if a single Data doesn't exist + */ + private[chisel3] def reifyToAggregate(data: Data): Option[Aggregate] = reifySingleData(data) match { + case Some(a: Aggregate) => Some(a) + case other => None + } } diff --git a/core/src/main/scala/chisel3/internal/BiConnect.scala b/core/src/main/scala/chisel3/internal/BiConnect.scala index 5b4ad1b9..a8b425f5 100644 --- a/core/src/main/scala/chisel3/internal/BiConnect.scala +++ b/core/src/main/scala/chisel3/internal/BiConnect.scala @@ -3,15 +3,14 @@ package chisel3.internal import chisel3._ -import chisel3.experimental.dataview.reify +import chisel3.experimental.dataview.{isView, reify, reifyToAggregate} import chisel3.experimental.{attach, Analog, BaseModule} import chisel3.internal.Builder.pushCommand -import chisel3.internal.firrtl.{Connect, DefInvalid} +import chisel3.internal.firrtl.{Connect, Converter, DefInvalid} import scala.language.experimental.macros import chisel3.internal.sourceinfo._ - -import scala.annotation.tailrec +import _root_.firrtl.passes.CheckTypes /** * BiConnect.connect executes a bidirectional connection element-wise. @@ -91,12 +90,28 @@ private[chisel3] object BiConnect { if (left_v.length != right_v.length) { throw MismatchedVecException } - for (idx <- 0 until left_v.length) { - try { - implicit val compileOptions = connectCompileOptions - connect(sourceInfo, connectCompileOptions, left_v(idx), right_v(idx), context_mod) - } catch { - case BiConnectException(message) => throw BiConnectException(s"($idx)$message") + + val leftReified: Option[Aggregate] = if (isView(left_v)) reifyToAggregate(left_v) else Some(left_v) + val rightReified: Option[Aggregate] = if (isView(right_v)) reifyToAggregate(right_v) else Some(right_v) + + if ( + leftReified.nonEmpty && rightReified.nonEmpty && canBulkConnectAggregates( + leftReified.get, + rightReified.get, + sourceInfo, + connectCompileOptions, + context_mod + ) + ) { + pushCommand(Connect(sourceInfo, leftReified.get.lref, rightReified.get.lref)) + } else { + for (idx <- 0 until left_v.length) { + try { + implicit val compileOptions = connectCompileOptions + connect(sourceInfo, connectCompileOptions, left_v(idx), right_v(idx), context_mod) + } catch { + case BiConnectException(message) => throw BiConnectException(s"($idx)$message") + } } } } @@ -122,29 +137,31 @@ private[chisel3] object BiConnect { } } } - // Handle Records defined in Chisel._ code by emitting a FIRRTL partial connect + // Handle Records defined in Chisel._ code by emitting a FIRRTL bulk + // connect when possible and a partial connect otherwise case pair @ (left_r: Record, right_r: Record) => val notStrict = Seq(left_r.compileOptions, right_r.compileOptions).contains(ExplicitCompileOptions.NotStrict) - if (notStrict) { - // Traces flow from a child Data to its parent - @tailrec def traceFlow(currentlyFlipped: Boolean, data: Data): Boolean = { - import SpecifiedDirection.{Input => SInput, Flip => SFlip} - val sdir = data.specifiedDirection - val flipped = sdir == SInput || sdir == SFlip - data.binding.get match { - case ChildBinding(parent) => traceFlow(flipped ^ currentlyFlipped, parent) - case PortBinding(enclosure) => - val childPort = enclosure != context_mod - childPort ^ flipped ^ currentlyFlipped - case _ => true - } - } - def canBeSink(data: Data): Boolean = traceFlow(true, data) - def canBeSource(data: Data): Boolean = traceFlow(false, data) - // chisel3 <> is commutative but FIRRTL <- is not - val flipConnection = !canBeSink(left_r) || !canBeSource(right_r) - val (newLeft, newRight) = if (flipConnection) pair.swap else pair + + // chisel3 <> is commutative but FIRRTL <- is not + val flipConnection = + !MonoConnect.canBeSink(left_r, context_mod) || !MonoConnect.canBeSource(right_r, context_mod) + val (newLeft, newRight) = if (flipConnection) (right_r, left_r) else (left_r, right_r) + + val leftReified: Option[Aggregate] = if (isView(newLeft)) reifyToAggregate(newLeft) else Some(newLeft) + val rightReified: Option[Aggregate] = if (isView(newRight)) reifyToAggregate(newRight) else Some(newRight) + + if ( + leftReified.nonEmpty && rightReified.nonEmpty && canBulkConnectAggregates( + leftReified.get, + rightReified.get, + sourceInfo, + connectCompileOptions, + context_mod + ) + ) { + pushCommand(Connect(sourceInfo, leftReified.get.lref, rightReified.get.lref)) + } else if (notStrict) { newLeft.bulkConnect(newRight)(sourceInfo, ExplicitCompileOptions.NotStrict) } else { recordConnect(sourceInfo, connectCompileOptions, left_r, right_r, context_mod) @@ -218,6 +235,59 @@ private[chisel3] object BiConnect { } } + /** Check whether two aggregates can be bulk connected (<=) in FIRRTL. From the + * FIRRTL specification, the following must hold for bulk connection: + * + * 1. The types of the left-hand and right-hand side expressions must be + * equivalent. + * 2. The bit widths of the two expressions must allow for data to always + * flow from a smaller bit width to an equal size or larger bit width. + * 3. The flow of the left-hand side expression must be sink or duplex + * 4. Either the flow of the right-hand side expression is source or duplex, + * or the right-hand side expression has a passive type. + */ + private[chisel3] def canBulkConnectAggregates( + sink: Aggregate, + source: Aggregate, + sourceInfo: SourceInfo, + connectCompileOptions: CompileOptions, + context_mod: RawModule + ): Boolean = { + + // check that the aggregates have the same types + def typeCheck = CheckTypes.validConnect( + Converter.extractType(sink, sourceInfo), + Converter.extractType(source, sourceInfo) + ) + + // check records live in appropriate contexts + def contextCheck = + MonoConnect.aggregateConnectContextCheck( + sourceInfo, + connectCompileOptions, + sink, + source, + context_mod + ) + + // sink must be writable and must also not be a literal + def bindingCheck = sink.topBinding match { + case _: ReadOnlyBinding => false + case _ => true + } + + // check data can flow between provided aggregates + def flow_check = MonoConnect.canBeSink(sink, context_mod) && MonoConnect.canBeSource(source, context_mod) + + // do not bulk connect source literals (results in infinite recursion from calling .ref) + def sourceNotLiteralCheck = source.topBinding match { + case _: LitBinding => false + case _ => true + } + + typeCheck && contextCheck && bindingCheck && flow_check && sourceNotLiteralCheck + } + // These functions (finally) issue the connection operation // Issue with right as sink, left as source private def issueConnectL2R(left: Element, right: Element)(implicit sourceInfo: SourceInfo): Unit = { diff --git a/core/src/main/scala/chisel3/internal/MonoConnect.scala b/core/src/main/scala/chisel3/internal/MonoConnect.scala index b4d9aeff..40056c89 100644 --- a/core/src/main/scala/chisel3/internal/MonoConnect.scala +++ b/core/src/main/scala/chisel3/internal/MonoConnect.scala @@ -5,11 +5,13 @@ package chisel3.internal import chisel3._ import chisel3.experimental.{Analog, BaseModule, EnumType, FixedPoint, Interval, UnsafeEnum} import chisel3.internal.Builder.pushCommand -import chisel3.experimental.dataview.reify -import chisel3.internal.firrtl.{Connect, DefInvalid} +import chisel3.internal.firrtl.{Connect, Converter, DefInvalid} +import chisel3.experimental.dataview.{isView, reify, reifyToAggregate} import scala.language.experimental.macros import chisel3.internal.sourceinfo.SourceInfo +import _root_.firrtl.passes.CheckTypes +import scala.annotation.tailrec /** * MonoConnect.connect executes a mono-directional connection element-wise. @@ -129,12 +131,28 @@ private[chisel3] object MonoConnect { // Handle Vec case case (sink_v: Vec[Data @unchecked], source_v: Vec[Data @unchecked]) => if (sink_v.length != source_v.length) { throw MismatchedVecException } - for (idx <- 0 until sink_v.length) { - try { - implicit val compileOptions = connectCompileOptions - connect(sourceInfo, connectCompileOptions, sink_v(idx), source_v(idx), context_mod) - } catch { - case MonoConnectException(message) => throw MonoConnectException(s"($idx)$message") + + val sinkReified: Option[Aggregate] = if (isView(sink_v)) reifyToAggregate(sink_v) else Some(sink_v) + val sourceReified: Option[Aggregate] = if (isView(source_v)) reifyToAggregate(source_v) else Some(source_v) + + if ( + sinkReified.nonEmpty && sourceReified.nonEmpty && canBulkConnectAggregates( + sinkReified.get, + sourceReified.get, + sourceInfo, + connectCompileOptions, + context_mod + ) + ) { + pushCommand(Connect(sourceInfo, sinkReified.get.lref, sourceReified.get.ref)) + } else { + for (idx <- 0 until sink_v.length) { + try { + implicit val compileOptions = connectCompileOptions + connect(sourceInfo, connectCompileOptions, sink_v(idx), source_v(idx), context_mod) + } catch { + case MonoConnectException(message) => throw MonoConnectException(s"($idx)$message") + } } } // Handle Vec connected to DontCare. Apply the DontCare to individual elements. @@ -150,19 +168,34 @@ private[chisel3] object MonoConnect { // Handle Record case case (sink_r: Record, source_r: Record) => - // For each field, descend with right - for ((field, sink_sub) <- sink_r.elements) { - try { - source_r.elements.get(field) match { - case Some(source_sub) => connect(sourceInfo, connectCompileOptions, sink_sub, source_sub, context_mod) - case None => { - if (connectCompileOptions.connectFieldsMustMatch) { - throw MissingFieldException(field) + val sinkReified: Option[Aggregate] = if (isView(sink_r)) reifyToAggregate(sink_r) else Some(sink_r) + val sourceReified: Option[Aggregate] = if (isView(source_r)) reifyToAggregate(source_r) else Some(source_r) + + if ( + sinkReified.nonEmpty && sourceReified.nonEmpty && canBulkConnectAggregates( + sinkReified.get, + sourceReified.get, + sourceInfo, + connectCompileOptions, + context_mod + ) + ) { + pushCommand(Connect(sourceInfo, sinkReified.get.lref, sourceReified.get.ref)) + } else { + // For each field, descend with right + for ((field, sink_sub) <- sink_r.elements) { + try { + source_r.elements.get(field) match { + case Some(source_sub) => connect(sourceInfo, connectCompileOptions, sink_sub, source_sub, context_mod) + case None => { + if (connectCompileOptions.connectFieldsMustMatch) { + throw MissingFieldException(field) + } } } + } catch { + case MonoConnectException(message) => throw MonoConnectException(s".$field$message") } - } catch { - case MonoConnectException(message) => throw MonoConnectException(s".$field$message") } } // Handle Record connected to DontCare. Apply the DontCare to individual elements. @@ -190,6 +223,143 @@ private[chisel3] object MonoConnect { case (sink, source) => throw MismatchedException(sink, source) } + /** Determine if a valid connection can be made between a source [[Aggregate]] and sink + * [[Aggregate]] given their parent module and directionality context + * + * @return whether the source and sink exist in an appropriate context to be connected + */ + private[chisel3] def aggregateConnectContextCheck( + implicit sourceInfo: SourceInfo, + connectCompileOptions: CompileOptions, + sink: Aggregate, + source: Aggregate, + context_mod: RawModule + ): Boolean = { + import ActualDirection.{Bidirectional, Input, Output} + // If source has no location, assume in context module + // This can occur if is a literal, unbound will error previously + val sink_mod: BaseModule = sink.topBinding.location.getOrElse(throw UnwritableSinkException(sink, source)) + val source_mod: BaseModule = source.topBinding.location.getOrElse(context_mod) + + val sink_parent = Builder.retrieveParent(sink_mod, context_mod).getOrElse(None) + val source_parent = Builder.retrieveParent(source_mod, context_mod).getOrElse(None) + + val sink_is_port = sink.topBinding match { + case PortBinding(_) => true + case _ => false + } + val source_is_port = source.topBinding match { + case PortBinding(_) => true + case _ => false + } + + if (!checkWhenVisibility(sink)) { + throw SinkEscapedWhenScopeException(sink) + } + + if (!checkWhenVisibility(source)) { + throw SourceEscapedWhenScopeException(source) + } + + // CASE: Context is same module that both sink node and source node are in + if ((context_mod == sink_mod) && (context_mod == source_mod)) { + sink.direction != Input + } + + // CASE: Context is same module as sink node and source node is in a child module + else if ((sink_mod == context_mod) && (source_parent == context_mod)) { + // NOTE: Workaround for bulk connecting non-agnostified FIRRTL ports + // See: https://github.com/freechipsproject/firrtl/issues/1703 + // Original behavior should just check if the sink direction is an Input + val sinkCanBeInput = sink.direction match { + case Input => true + case Bidirectional(_) => true + case _ => false + } + // Thus, right node better be a port node and thus have a direction + if (!source_is_port) { !connectCompileOptions.dontAssumeDirectionality } + else if (sinkCanBeInput) { + if (source.direction == Output) { + !connectCompileOptions.dontTryConnectionsSwapped + } else { false } + } else { true } + } + + // CASE: Context is same module as source node and sink node is in child module + else if ((source_mod == context_mod) && (sink_parent == context_mod)) { + // NOTE: Workaround for bulk connecting non-agnostified FIRRTL ports + // See: https://github.com/freechipsproject/firrtl/issues/1703 + // Original behavior should just check if the sink direction is an Input + sink.direction match { + case Input => true + case Bidirectional(_) => true + case _ => false + } + } + + // CASE: Context is the parent module of both the module containing sink node + // and the module containing source node + // Note: This includes case when sink and source in same module but in parent + else if ((sink_parent == context_mod) && (source_parent == context_mod)) { + // Thus both nodes must be ports and have a direction + if (!source_is_port) { !connectCompileOptions.dontAssumeDirectionality } + else if (sink_is_port) { + // NOTE: Workaround for bulk connecting non-agnostified FIRRTL ports + // See: https://github.com/freechipsproject/firrtl/issues/1703 + // Original behavior should just check if the sink direction is an Input + sink.direction match { + case Input => true + case Bidirectional(_) => true // NOTE: Workaround for non-agnostified ports + case _ => false + } + } else { false } + } + + // Not quite sure where left and right are compared to current module + // so just error out + else false + } + + /** Trace flow from child Data to its parent. */ + @tailrec private[chisel3] def traceFlow(currentlyFlipped: Boolean, data: Data, context_mod: RawModule): Boolean = { + import SpecifiedDirection.{Input => SInput, Flip => SFlip} + val sdir = data.specifiedDirection + val flipped = sdir == SInput || sdir == SFlip + data.binding.get match { + case ChildBinding(parent) => traceFlow(flipped ^ currentlyFlipped, parent, context_mod) + case PortBinding(enclosure) => + val childPort = enclosure != context_mod + childPort ^ flipped ^ currentlyFlipped + case _ => true + } + } + def canBeSink(data: Data, context_mod: RawModule): Boolean = traceFlow(true, data, context_mod) + def canBeSource(data: Data, context_mod: RawModule): Boolean = traceFlow(false, data, context_mod) + + /** Check whether two aggregates can be bulk connected (<=) in FIRRTL. (MonoConnect case) + * + * Mono-directional bulk connects only work if all signals of the sink are unidirectional + * In the case of a sink aggregate with bidirectional signals, e.g. `Decoupled`, + * a `BiConnect` is necessary. + */ + private[chisel3] def canBulkConnectAggregates( + sink: Aggregate, + source: Aggregate, + sourceInfo: SourceInfo, + connectCompileOptions: CompileOptions, + context_mod: RawModule + ): Boolean = { + // Assuming we're using a <>, check if a bulk connect is valid in that case + def biConnectCheck = + BiConnect.canBulkConnectAggregates(sink, source, sourceInfo, connectCompileOptions, context_mod) + + // Check that the Aggregate can be driven (not bidirectional or an input) to match Chisel semantics + def sinkCanBeDrivenCheck: Boolean = + sink.direction == ActualDirection.Output || sink.direction == ActualDirection.Unspecified + + biConnectCheck && sinkCanBeDrivenCheck + } + // This function (finally) issues the connection operation private def issueConnect(sink: Element, source: Element)(implicit sourceInfo: SourceInfo): Unit = { // If the source is a DontCare, generate a DefInvalid for the sink, diff --git a/src/test/scala/chiselTests/BulkConnectSpec.scala b/src/test/scala/chiselTests/BulkConnectSpec.scala new file mode 100644 index 00000000..463122bd --- /dev/null +++ b/src/test/scala/chiselTests/BulkConnectSpec.scala @@ -0,0 +1,106 @@ +package chiselTests + +import chisel3._ +import chisel3.util.Decoupled +import chisel3.stage.ChiselStage +import chisel3.testers.BasicTester + +class BulkConnectSpec extends ChiselPropSpec { + property("Chisel connects should emit FIRRTL bulk connects when possible") { + val chirrtl = ChiselStage.emitChirrtl(new Module { + val io = IO(new Bundle { + val inMono = Input(Vec(4, UInt(8.W))) + val outMono = Output(Vec(4, UInt(8.W))) + val inBi = Input(Vec(4, UInt(8.W))) + val outBi = Output(Vec(4, UInt(8.W))) + }) + io.outMono := io.inMono + io.outBi <> io.inBi + }) + chirrtl should include("io.outMono <= io.inMono") + chirrtl should include("io.outBi <= io.inBi") + } + + property("Chisel connects should not emit FIRRTL bulk connects for Stringly-typed connections") { + object Foo { + import Chisel._ + // Chisel._ bundle + class BundleParent extends Bundle { + val foo = UInt(width = 8) + } + class BundleChild extends BundleParent { + val bar = UInt(width = 8) + } + } + + import Foo._ + + // chisel3._ bundle + class MyBundle(child: Boolean) extends Bundle { + val fizz = UInt(8.W) + val buzz = if (child) new BundleChild else new BundleParent + } + + val chirrtl = ChiselStage.emitChirrtl(new Module { + // Checking MonoConnect + val in = IO(Input(new MyBundle(true))) + val out = IO(Output(new MyBundle(false))) + out := in + + // Checking BulkConnect (with Decoupled) + val enq = IO(Flipped(Decoupled(new BundleChild))) + val deq = IO(Decoupled(new BundleParent)) + deq <> enq + }) + + chirrtl should include("out.buzz.foo <= in.buzz.foo") + chirrtl shouldNot include("deq <= enq") + } + + property("Chisel connects should not emit FIRRTL bulk connects between differing FIRRTL types") { + val chirrtl = ChiselStage.emitChirrtl(new Module { + val in = IO(Flipped(new Bundle { + val foo = Flipped(new Bundle { + val bar = Input(UInt(8.W)) + }) + })) + val out = IO(Output(new Bundle { + val foo = new Bundle { + val bar = UInt(8.W) + } + })) + // Both of these connections are legal in Chisel, but in and out do not have the same type + out := in + out <> in + }) + // out <- in is illegal FIRRTL + chirrtl should include("out.foo.bar <= in.foo.bar") + } + + property("Chisel connects should not emit a FIRRTL bulk connect for a bidirectional MonoConnect") { + val chirrtl = ChiselStage.emitChirrtl(new Module { + val enq = IO(Flipped(Decoupled(UInt(8.W)))) + val deq = IO(Decoupled(UInt(8.W))) + + // Implicitly create a MonoConnect from enq to a wire + // enq is a Decoupled and so has input/output signals + // We should not bulk connect in this case + val wire = WireDefault(enq) + dontTouch(wire) + deq <> enq + }) + + chirrtl shouldNot include("wire <= enq") + chirrtl should include("deq <= enq") + } + + property("MonoConnect should bulk connect undirectioned internal wires") { + val chirrtl = ChiselStage.emitChirrtl(new Module { + val io = IO(new Bundle {}) + val w1 = Wire(Vec(2, UInt(8.W))) + val w2 = Wire(Vec(2, UInt(8.W))) + w2 := w1 + }) + chirrtl should include("w2 <= w1") + } +} diff --git a/src/test/scala/chiselTests/BundleSpec.scala b/src/test/scala/chiselTests/BundleSpec.scala index 5dcbbefa..2d34b263 100644 --- a/src/test/scala/chiselTests/BundleSpec.scala +++ b/src/test/scala/chiselTests/BundleSpec.scala @@ -3,6 +3,7 @@ package chiselTests import chisel3._ +import chisel3.util.Decoupled import chisel3.stage.ChiselStage import chisel3.testers.BasicTester diff --git a/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala b/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala index 8210b120..70dcda48 100644 --- a/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala +++ b/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala @@ -74,7 +74,7 @@ object Chisel3Components { class Chisel3ModuleChiselRecordB extends Chisel3PassthroughModule(Flipped(new ChiselRecord)) } -class CompatibiltyInteroperabilitySpec extends ChiselFlatSpec { +class CompatibilityInteroperabilitySpec extends ChiselFlatSpec { "Modules defined in the Chisel._" should "successfully bulk connect in chisel3._" in { import chisel3._ diff --git a/src/test/scala/chiselTests/MixedVecSpec.scala b/src/test/scala/chiselTests/MixedVecSpec.scala index 16efafd4..ee19d653 100644 --- a/src/test/scala/chiselTests/MixedVecSpec.scala +++ b/src/test/scala/chiselTests/MixedVecSpec.scala @@ -280,4 +280,20 @@ class MixedVecSpec extends ChiselPropSpec with Utils { }) } } + + property("MixedVec connections should emit FIRRTL bulk connects when possible") { + val chirrtl = ChiselStage.emitChirrtl(new Module { + val io = IO(new Bundle { + val inMono = Input(MixedVec(Seq(UInt(8.W), UInt(16.W), UInt(4.W), UInt(7.W)))) + val outMono = Output(MixedVec(Seq(UInt(8.W), UInt(16.W), UInt(4.W), UInt(7.W)))) + val inBi = Input(MixedVec(Seq(UInt(8.W), UInt(16.W), UInt(4.W), UInt(7.W)))) + val outBi = Output(MixedVec(Seq(UInt(8.W), UInt(16.W), UInt(4.W), UInt(7.W)))) + }) + // Explicit upcast avoids weird issue where Scala 2.12 overloading resolution calls version of := accepting Seq[T] instead of normal Data version + io.outMono := (io.inMono: Data) + io.outBi <> io.inBi + }) + chirrtl should include("io.outMono <= io.inMono @[MixedVecSpec.scala") + chirrtl should include("io.outBi <= io.inBi @[MixedVecSpec.scala") + } } diff --git a/src/test/scala/chiselTests/RecordSpec.scala b/src/test/scala/chiselTests/RecordSpec.scala index da3840dd..30b55812 100644 --- a/src/test/scala/chiselTests/RecordSpec.scala +++ b/src/test/scala/chiselTests/RecordSpec.scala @@ -27,6 +27,17 @@ trait RecordSpecUtils { io.out <> io.in } + class ConnectionTestModule(output: => Record, input: => Record) extends Module { + val io = IO(new Bundle { + val inMono = Input(input) + val outMono = Output(output) + val inBi = Input(input) + val outBi = Output(output) + }) + io.outMono := io.inMono + io.outBi <> io.inBi + } + class RecordSerializationTest extends BasicTester { val recordType = new CustomBundle("fizz" -> UInt(16.W), "buzz" -> UInt(16.W)) val record = Wire(recordType) @@ -110,6 +121,14 @@ class RecordSpec extends ChiselFlatSpec with RecordSpecUtils with Utils { ChiselStage.elaborate { new MyModule(new MyBundle, fooBarType) } } + they should "emit FIRRTL bulk connects when possible" in { + val chirrtl = (new ChiselStage).emitChirrtl( + gen = new ConnectionTestModule(fooBarType, fooBarType) + ) + chirrtl should include("io.outMono <= io.inMono @[RecordSpec.scala") + chirrtl should include("io.outBi <= io.inBi @[RecordSpec.scala") + } + they should "not allow aliased fields" in { class AliasedFieldRecord extends Record { val foo = UInt(8.W) diff --git a/src/test/scala/chiselTests/VecLiteralSpec.scala b/src/test/scala/chiselTests/VecLiteralSpec.scala index 228f409b..fa97a8c8 100644 --- a/src/test/scala/chiselTests/VecLiteralSpec.scala +++ b/src/test/scala/chiselTests/VecLiteralSpec.scala @@ -434,7 +434,7 @@ class VecLiteralSpec extends ChiselFreeSpec with Utils { exc.getMessage should include("field 0 specified with non-literal value UInt") } - "vec literals are instantiated on connect" in { + "vec literals are instantiated on connect and are not bulk connected" in { class VecExample5 extends RawModule { val out = IO(Output(Vec(2, UInt(4.W)))) val bundle = Vec(2, UInt(4.W)).Lit( @@ -463,13 +463,12 @@ class VecLiteralSpec extends ChiselFreeSpec with Utils { out := bundle } - "vec literals can contain bundles" in { + "vec literals can contain bundles and should not be bulk connected" in { val chirrtl = (new chisel3.stage.ChiselStage).emitChirrtl(new VecExample, args = Array("--full-stacktrace")) chirrtl should include("""out[0].bar <= UInt<5>("h16")""") chirrtl should include("""out[0].foo <= UInt<6>("h2a")""") chirrtl should include("""out[1].bar <= UInt<2>("h3")""") chirrtl should include("""out[1].foo <= UInt<3>("h7")""") - } "vec literals can have bundle children" in { diff --git a/src/test/scala/chiselTests/experimental/DataView.scala b/src/test/scala/chiselTests/experimental/DataView.scala index 5ef062fa..0285a524 100644 --- a/src/test/scala/chiselTests/experimental/DataView.scala +++ b/src/test/scala/chiselTests/experimental/DataView.scala @@ -103,8 +103,8 @@ class DataViewSpec extends ChiselFlatSpec { buzz.viewAs[MyBundle] := in } val chirrtl = ChiselStage.emitChirrtl(new MyModule) - chirrtl should include("fizz.foo <= in.foo") - chirrtl should include("buzz.foo <= in.foo") + chirrtl should include("fizz <= in") + chirrtl should include("buzz <= in") } it should "handle viewing Vecs as their same concrete type" in { @@ -116,8 +116,8 @@ class DataViewSpec extends ChiselFlatSpec { buzz.viewAs[Vec[UInt]] := in } val chirrtl = ChiselStage.emitChirrtl(new MyModule) - chirrtl should include("fizz[0] <= in[0]") - chirrtl should include("buzz[0] <= in[0]") + chirrtl should include("fizz <= in") + chirrtl should include("buzz <= in") } it should "handle viewing Vecs as Bundles and vice versa" in { -- cgit v1.2.3 From f26df23bbe0ae9b7162ed70369f24b01d75a1493 Mon Sep 17 00:00:00 2001 From: mergify[bot] Date: Tue, 15 Mar 2022 01:09:04 +0000 Subject: [docs] Add Cookbook section on aliased Bundle fields (#2444) (#2448) (cherry picked from commit d4dd21191c63a3b125193a7ffb83d478aa945d5a) Co-authored-by: Jack Koenig --- docs/src/cookbooks/cookbook.md | 143 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 1 deletion(-) diff --git a/docs/src/cookbooks/cookbook.md b/docs/src/cookbooks/cookbook.md index 118db228..ae7c7bf6 100644 --- a/docs/src/cookbooks/cookbook.md +++ b/docs/src/cookbooks/cookbook.md @@ -20,6 +20,8 @@ Please note that these examples make use of [Chisel's scala-style printing](../e * [Can I make a 2D or 3D Vector?](#can-i-make-a-2D-or-3D-Vector) * [How do I create a Vector of Registers?](#how-do-i-create-a-vector-of-registers) * [How do I create a Reg of type Vec?](#how-do-i-create-a-reg-of-type-vec) +* Bundles + * [How do I deal with aliased Bundle fields?](#aliased-bundle-fields) * [How do I create a finite state machine?](#how-do-i-create-a-finite-state-machine-fsm) * [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) @@ -31,7 +33,7 @@ Please note that these examples make use of [Chisel's scala-style printing](../e * [How can I dynamically set/parametrize the name of a module?](#how-can-i-dynamically-setparametrize-the-name-of-a-module) * Directionality * [How do I strip directions from a bidirectional Bundle (or other Data)?](#how-do-i-strip-directions-from-a-bidirectional-bundle-or-other-data) - + ## Type Conversions ### How do I create a UInt from an instance of a Bundle? @@ -231,6 +233,145 @@ class Foo extends RawModule { } ``` +## Bundles + +### How do I deal with aliased Bundle fields? + +```scala mdoc:invisible:reset +import chisel3._ + +class Top[T <: Data](gen: T) extends Module { + val in = IO(Input(gen)) + val out = IO(Output(gen)) + out := in +} +``` + +Following the `gen` pattern when creating Bundles can result in some opaque error messages: + +```scala mdoc +class AliasedBundle[T <: Data](gen: T) extends Bundle { + val foo = gen + val bar = gen +} +``` + +```scala mdoc:crash +getVerilogString(new Top(new AliasedBundle(UInt(8.W)))) +``` + +This error is saying that fields `foo` and `bar` of `AliasedBundle` are the +exact same object in memory. +This is a problem for Chisel because we need to be able to distinguish uses of +`foo` and `bar` but cannot when they are referentially the same. + +Note that the following example looks different but will give you exactly the same issue: + +```scala mdoc +class AlsoAliasedBundle[T <: Data](val gen: T) extends Bundle { + // ^ This val makes `gen` a field, just like `foo` + val foo = gen +} +``` + +By making `gen` a `val`, it becomes a public field of the `class`, just like `foo`. + +```scala mdoc:crash +getVerilogString(new Top(new AlsoAliasedBundle(UInt(8.W)))) +``` + +There are several ways to solve this issue with their own advantages and disadvantages. + +#### 1. 0-arity function parameters + +Instead of passing an object as a parameter, you can pass a 0-arity function (a function with no arguments): + +```scala mdoc +class UsingAFunctionBundle[T <: Data](gen: () => T) extends Bundle { + val foo = gen() + val bar = gen() +} +``` + +Note that the type of `gen` is now `() => T`. +Because it is now a function and not a subtype of `Data`, you can safely make `gen` a `val` without +it becoming a hardware field of the `Bundle`. + +Note that this also means you must pass `gen` as a function, for example: + +```scala mdoc:silent +getVerilogString(new Top(new UsingAFunctionBundle(() => UInt(8.W)))) +``` + + **Warning**: you must ensure that `gen` creates fresh objects rather than capturing an already constructed value: + +```scala mdoc:crash +class MisusedFunctionArguments extends Module { + // This usage is correct + val in = IO(Input(new UsingAFunctionBundle(() => UInt(8.W)))) + + // This usage is incorrect + val fizz = UInt(8.W) + val out = IO(Output(new UsingAFunctionBundle(() => fizz))) +} +getVerilogString(new MisusedFunctionArguments) +``` +In the above example, value `fizz` and fields `foo` and `bar` of `out` are all the same object in memory. + + +#### 2. By-name function parameters + +Functionally the same as (1) but with more subtle syntax, you can use [Scala by-name function parameters](https://docs.scala-lang.org/tour/by-name-parameters.html): + +```scala mdoc +class UsingByNameParameters[T <: Data](gen: => T) extends Bundle { + val foo = gen + val bar = gen +} +``` + +With this usage, you do not include `() =>` when passing the argument: + +```scala mdoc:silent +getVerilogString(new Top(new UsingByNameParameters(UInt(8.W)))) +``` + +Note that as this is just syntactic sugar over (1), the [same warning applies](#aliased-warning). + +#### 3. Directioned Bundle fields + +You can alternatively wrap the fields with `Output(...)`, which creates fresh instances of the passed argument. +Chisel treats `Output` as the "default direction" so if all fields are outputs, the `Bundle` is functionally equivalent to a `Bundle` with no directioned fields. + +```scala mdoc +class DirectionedBundle[T <: Data](gen: T) extends Bundle { + val foo = Output(gen) + val bar = Output(gen) +} +``` + +```scala mdoc:invisible +getVerilogString(new Top(new DirectionedBundle(UInt(8.W)))) +``` + +This approach is admittedly a little ugly and may mislead others reading the code because it implies that this Bundle is intended to be used as an `Output`. + +#### 4. Call `.cloneType` directly + +You can also just call `.cloneType` on your `gen` argument directly. +While we try to hide this implementation detail from the user, `.cloneType` is the mechanism by which Chisel creates fresh instances of `Data` objects: + +```scala mdoc +class UsingCloneTypeBundle[T <: Data](gen: T) extends Bundle { + val foo = gen.cloneType + val bar = gen.cloneType +} +``` + +```scala mdoc:invisible +getVerilogString(new Top(new UsingCloneTypeBundle(UInt(8.W)))) +``` + ### How do I create a finite state machine (FSM)? The advised way is to use [`ChiselEnum`](https://www.chisel-lang.org/api/latest/chisel3/experimental/index.html#ChiselEnum=chisel3.experimental.EnumFactory) to construct enumerated types representing the state of the FSM. -- cgit v1.2.3