From 2a96767097264eade18ff26e1d8bce192383a190 Mon Sep 17 00:00:00 2001 From: SoyaOhnishi Date: Mon, 25 Jan 2021 11:15:52 +0900 Subject: Refactor EnumAnnotations and EnumFactory (#1747) This is a nit fix. no logic is changed. * Rename `typeName` to `enumTypeName` in ScalaDoc * Add return type at public method * Rename `enum_records` into `enumRecords` to retain name consistency Co-authored-by: Jack Koenig --- core/src/main/scala/chisel3/StrongEnum.scala | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/src/main/scala/chisel3/StrongEnum.scala b/core/src/main/scala/chisel3/StrongEnum.scala index 2d372a94..7d328eb7 100644 --- a/core/src/main/scala/chisel3/StrongEnum.scala +++ b/core/src/main/scala/chisel3/StrongEnum.scala @@ -18,7 +18,7 @@ object EnumAnnotations { /** An annotation for strong enum instances that are ''not'' inside of Vecs * * @param target the enum instance being annotated - * @param typeName the name of the enum's type (e.g. ''"mypackage.MyEnum"'') + * @param enumTypeName the name of the enum's type (e.g. ''"mypackage.MyEnum"'') */ case class EnumComponentAnnotation(target: Named, enumTypeName: String) extends SingleTargetAnnotation[Named] { def duplicate(n: Named): EnumComponentAnnotation = this.copy(target = n) @@ -50,11 +50,11 @@ object EnumAnnotations { * */ case class EnumVecAnnotation(target: Named, typeName: String, fields: Seq[Seq[String]]) extends SingleTargetAnnotation[Named] { - def duplicate(n: Named) = this.copy(target = n) + def duplicate(n: Named): EnumVecAnnotation = this.copy(target = n) } case class EnumVecChiselAnnotation(target: InstanceId, typeName: String, fields: Seq[Seq[String]]) extends ChiselAnnotation { - override def toFirrtl = EnumVecAnnotation(target.toNamed, typeName, fields) + override def toFirrtl: EnumVecAnnotation = EnumVecAnnotation(target.toNamed, typeName, fields) } /** An annotation for enum types (rather than enum ''instances''). @@ -225,11 +225,11 @@ abstract class EnumFactory { private[chisel3] var width: Width = 0.W private case class EnumRecord(inst: Type, name: String) - private val enum_records = mutable.ArrayBuffer.empty[EnumRecord] + private val enumRecords = mutable.ArrayBuffer.empty[EnumRecord] - private def enumNames = enum_records.map(_.name).toSeq - private def enumValues = enum_records.map(_.inst.litValue()).toSeq - private def enumInstances = enum_records.map(_.inst).toSeq + private def enumNames = enumRecords.map(_.name).toSeq + private def enumValues = enumRecords.map(_.inst.litValue()).toSeq + private def enumInstances = enumRecords.map(_.inst).toSeq private[chisel3] val enumTypeName = getClass.getName.init @@ -241,7 +241,7 @@ abstract class EnumFactory { def all: Seq[Type] = enumInstances private[chisel3] def nameOfValue(id: BigInt): Option[String] = { - enum_records.find(_.inst.litValue() == id).map(_.name) + enumRecords.find(_.inst.litValue() == id).map(_.name) } protected def Value: Type = macro EnumMacros.ValImpl @@ -253,7 +253,7 @@ abstract class EnumFactory { // We have to use UnknownWidth here, because we don't actually know what the final width will be result.bindToLiteral(id, UnknownWidth()) - enum_records.append(EnumRecord(result, name)) + enumRecords.append(EnumRecord(result, name)) width = (1 max id.bitLength).W id += 1 -- cgit v1.2.3 From d0db0b8b4661e64a79e8b14a24a4fa1baace1c3d Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 26 Jan 2021 17:15:57 -0800 Subject: Fix incorrect comment in ScalaDoc (#1756) --- core/src/main/scala/chisel3/Reg.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/chisel3/Reg.scala b/core/src/main/scala/chisel3/Reg.scala index b2b99cc1..bd9e5311 100644 --- a/core/src/main/scala/chisel3/Reg.scala +++ b/core/src/main/scala/chisel3/Reg.scala @@ -127,7 +127,7 @@ object RegNext { * val x = Wire(UInt()) * val y = Wire(UInt(8.W)) * val r1 = RegInit(x) // width will be inferred - * val r2 = RegInit(y) // width is set to 8 + * val r2 = RegInit(y) // width will be inferred * }}} * * 3. [[Aggregate]] initializer - width will be set to match the aggregate -- cgit v1.2.3 From 654db98e92c0ea96d89f0307c043951fdc73a257 Mon Sep 17 00:00:00 2001 From: SoyaOhnishi Date: Wed, 27 Jan 2021 11:29:47 +0900 Subject: Fix some typo and using foreach instead of map in BoringUtils (#1755) If a method passed to higher function does not return any value, it is prefer to use `foreach` instead of `map`. Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- .../chisel3/util/experimental/BoringUtils.scala | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/scala/chisel3/util/experimental/BoringUtils.scala b/src/main/scala/chisel3/util/experimental/BoringUtils.scala index 18551da8..f2a3e757 100644 --- a/src/main/scala/chisel3/util/experimental/BoringUtils.scala +++ b/src/main/scala/chisel3/util/experimental/BoringUtils.scala @@ -19,7 +19,7 @@ class BoringUtilsException(message: String) extends Exception(message) /** Utilities for generating synthesizable cross module references that "bore" through the hierarchy. The underlying * cross module connects are handled by FIRRTL's Wiring Transform. * - * Consider the following exmple where you want to connect a component in one module to a component in another. Module + * Consider the following example where you want to connect a component in one module to a component in another. Module * `Constant` has a wire tied to `42` and `Expect` will assert unless connected to `42`: * {{{ * class Constant extends Module { @@ -45,8 +45,8 @@ class BoringUtilsException(message: String) extends Exception(message) * * ===Hierarchical Boring=== * - * Hierarchcical boring involves connecting one sink instance to another source instance in a parent module. Below, - * module `Top` contains an instance of `Cosntant` and `Expect`. Using [[BoringUtils.bore]], we can connect + * Hierarchical boring involves connecting one sink instance to another source instance in a parent module. Below, + * module `Top` contains an instance of `Constant` and `Expect`. Using [[BoringUtils.bore]], we can connect * `constant.x` to `expect.y`. * * {{{ @@ -89,7 +89,7 @@ class BoringUtilsException(message: String) extends Exception(message) * ==Comments== * * Both hierarchical and non-hierarchical boring emit FIRRTL annotations that describe sources and sinks. These are - * matched by a `name` key that indicates they should be wired together. Hierarhical boring safely generates this name + * matched by a `name` key that indicates they should be wired together. Hierarchical boring safely generates this name * automatically. Non-hierarchical boring unsafely relies on user input to generate this name. Use of non-hierarchical * naming may result in naming conflicts that the user must handle. * @@ -115,7 +115,7 @@ object BoringUtils { /** Add a named source cross module reference * @param component source circuit component * @param name unique identifier for this source - * @param disableDedup disable dedupblication of this source component (this should be true if you are trying to wire + * @param disableDedup disable deduplication of this source component (this should be true if you are trying to wire * from specific identical sources differently) * @param uniqueName if true, this will use a non-conflicting name from the global namespace * @return the name used @@ -137,7 +137,7 @@ object BoringUtils { def transformClass = classOf[WiringTransform] }, new ChiselAnnotation { def toFirrtl = DontTouchAnnotation(component.toNamed) } ) ++ maybeDedup - annotations.map(annotate(_)) + annotations.foreach(annotate(_)) id } @@ -146,8 +146,8 @@ object BoringUtils { * @param name unique identifier for this sink that must resolve to * @param disableDedup disable deduplication of this sink component (this should be true if you are trying to wire * specific, identical sinks differently) - * @param forceExists if true, require that the provided `name` paramater already exists in the global namespace - * @throws BoringUtilsException if name is expected to exist and itdoesn't + * @param forceExists if true, require that the provided `name` parameter already exists in the global namespace + * @throws BoringUtilsException if name is expected to exist and it doesn't */ def addSink( component: InstanceId, @@ -169,7 +169,7 @@ object BoringUtils { Seq(new ChiselAnnotation with RunFirrtlTransform { def toFirrtl = SinkAnnotation(component.toNamed, name) def transformClass = classOf[WiringTransform] }) ++ maybeDedup - annotations.map(annotate(_)) + annotations.foreach(annotate(_)) } /** Connect a source to one or more sinks @@ -187,7 +187,7 @@ object BoringUtils { case _: Exception => "bore" } val genName = addSource(source, boringName, true, true) - sinks.map(addSink(_, genName, true, true)) + sinks.foreach(addSink(_, genName, true, true)) genName } -- cgit v1.2.3 From 445b5cecb267adcd556627ffea2486b20740d6d4 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Mon, 1 Feb 2021 12:44:24 -0800 Subject: Bump to Scala 2.12.13 (#1766) --- .github/workflows/test.yml | 4 ++-- build.sbt | 5 +++-- build.sc | 2 +- project/plugins.sbt | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f9c2b2b0..a9322492 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - scala: [2.12.12] + scala: [2.12.13] container: image: ucbbar/chisel3-tools options: --user github --entrypoint /bin/bash @@ -32,7 +32,7 @@ jobs: - name: Cache Scala uses: coursier/cache-action@v5 - name: Documentation (Scala 2.12 only) - if: matrix.scala == '2.12.12' + if: matrix.scala == '2.12.13' run: sbt ++${{ matrix.scala }} docs/mdoc - name: Test run: sbt ++${{ matrix.scala }} test diff --git a/build.sbt b/build.sbt index 5030c016..2400233a 100644 --- a/build.sbt +++ b/build.sbt @@ -15,8 +15,8 @@ lazy val commonSettings = Seq ( organization := "edu.berkeley.cs", version := "3.5-SNAPSHOT", autoAPIMappings := true, - scalaVersion := "2.12.12", - crossScalaVersions := Seq("2.12.12"), + scalaVersion := "2.12.13", + crossScalaVersions := Seq("2.12.13"), scalacOptions := Seq("-deprecation", "-feature", ), libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value, @@ -92,6 +92,7 @@ lazy val pluginScalaVersions = Seq( "2.12.10", "2.12.11", "2.12.12", + "2.12.13", ) lazy val plugin = (project in file("plugin")). diff --git a/build.sc b/build.sc index c37b748b..78b9307e 100644 --- a/build.sc +++ b/build.sc @@ -5,7 +5,7 @@ import coursier.maven.MavenRepository import $ivy.`com.lihaoyi::mill-contrib-buildinfo:$MILL_VERSION` import mill.contrib.buildinfo.BuildInfo -object chisel3 extends mill.Cross[chisel3CrossModule]("2.12.12") +object chisel3 extends mill.Cross[chisel3CrossModule]("2.12.13") // The following stanza is searched for and used when preparing releases. // Please retain it. diff --git a/project/plugins.sbt b/project/plugins.sbt index 5c73d56e..482c2201 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -16,7 +16,7 @@ addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.15") addSbtPlugin("com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "3.0.0") -addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.5" ) +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.16" ) addSbtPlugin("com.eed3si9n" % "sbt-sriracha" % "0.1.0") -- cgit v1.2.3 From 98ce9194e5d87fdd5be931b6cd516d180a6540cd Mon Sep 17 00:00:00 2001 From: Albert Magyar Date: Mon, 1 Feb 2021 14:06:39 -0800 Subject: Update reported width from div/rem to match FIRRTL results (#1748) * Update reported width from div/rem to match FIRRTL results * Add tests for width of % and / on UInt and SInt * Add loop-based test for known UInt/SInt op result widths Co-authored-by: Jack Koenig --- core/src/main/scala/chisel3/Bits.scala | 6 +-- .../main/scala/chisel3/internal/firrtl/IR.scala | 1 + src/test/scala/chiselTests/SIntOps.scala | 32 ++++++++++++ src/test/scala/chiselTests/UIntOps.scala | 33 ++++++++++++ src/test/scala/chiselTests/WidthSpec.scala | 59 ++++++++++++++++++++++ 5 files changed, 128 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/chisel3/Bits.scala b/core/src/main/scala/chisel3/Bits.scala index 6bd5a07c..c86e9208 100644 --- a/core/src/main/scala/chisel3/Bits.scala +++ b/core/src/main/scala/chisel3/Bits.scala @@ -448,7 +448,7 @@ sealed class UInt private[chisel3] (width: Width) extends Bits(width) with Num[U override def do_/ (that: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = binop(sourceInfo, UInt(this.width), DivideOp, that) override def do_% (that: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = - binop(sourceInfo, UInt(this.width), RemOp, that) + binop(sourceInfo, UInt(this.width min that.width), RemOp, that) override def do_* (that: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = binop(sourceInfo, UInt(this.width + that.width), TimesOp, that) @@ -762,9 +762,9 @@ sealed class SInt private[chisel3] (width: Width) extends Bits(width) with Num[S override def do_* (that: SInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): SInt = binop(sourceInfo, SInt(this.width + that.width), TimesOp, that) override def do_/ (that: SInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): SInt = - binop(sourceInfo, SInt(this.width), DivideOp, that) + binop(sourceInfo, SInt(this.width + 1), DivideOp, that) override def do_% (that: SInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): SInt = - binop(sourceInfo, SInt(this.width), RemOp, that) + binop(sourceInfo, SInt(this.width min that.width), RemOp, that) /** Multiplication operator * diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index 095c8a05..61f97ce6 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -158,6 +158,7 @@ object Width { sealed abstract class Width { type W = Int + def min(that: Width): Width = this.op(that, _ min _) def max(that: Width): Width = this.op(that, _ max _) def + (that: Width): Width = this.op(that, _ + _) def + (that: Int): Width = this.op(this, (a, b) => a + that) diff --git a/src/test/scala/chiselTests/SIntOps.scala b/src/test/scala/chiselTests/SIntOps.scala index 9aacc378..f2e238e9 100644 --- a/src/test/scala/chiselTests/SIntOps.scala +++ b/src/test/scala/chiselTests/SIntOps.scala @@ -116,4 +116,36 @@ class SIntOpsSpec extends ChiselPropSpec with Utils { assertTesterPasses(new SIntLitExtractTester) } + // We use WireDefault with 2 arguments because of + // https://www.chisel-lang.org/api/3.4.1/chisel3/WireDefault$.html + // Single Argument case 2 + property("modulo divide should give min width of arguments") { + assertKnownWidth(4) { + val x = WireDefault(SInt(8.W), DontCare) + val y = WireDefault(SInt(4.W), DontCare) + val op = x % y + WireDefault(chiselTypeOf(op), op) + } + assertKnownWidth(4) { + val x = WireDefault(SInt(4.W), DontCare) + val y = WireDefault(SInt(8.W), DontCare) + val op = x % y + WireDefault(chiselTypeOf(op), op) + } + } + + property("division should give the width of the numerator + 1") { + assertKnownWidth(9) { + val x = WireDefault(SInt(8.W), DontCare) + val y = WireDefault(SInt(4.W), DontCare) + val op = x / y + WireDefault(chiselTypeOf(op), op) + } + assertKnownWidth(5) { + val x = WireDefault(SInt(4.W), DontCare) + val y = WireDefault(SInt(8.W), DontCare) + val op = x / y + WireDefault(chiselTypeOf(op), op) + } + } } diff --git a/src/test/scala/chiselTests/UIntOps.scala b/src/test/scala/chiselTests/UIntOps.scala index bba06d11..62d00de2 100644 --- a/src/test/scala/chiselTests/UIntOps.scala +++ b/src/test/scala/chiselTests/UIntOps.scala @@ -153,4 +153,37 @@ class UIntOpsSpec extends ChiselPropSpec with Matchers with Utils { io.out := io.in.asBools()(2) }) } + + // We use WireDefault with 2 arguments because of + // https://www.chisel-lang.org/api/3.4.1/chisel3/WireDefault$.html + // Single Argument case 2 + property("modulo divide should give min width of arguments") { + assertKnownWidth(4) { + val x = WireDefault(UInt(8.W), DontCare) + val y = WireDefault(UInt(4.W), DontCare) + val op = x % y + WireDefault(chiselTypeOf(op), op) + } + assertKnownWidth(4) { + val x = WireDefault(UInt(4.W), DontCare) + val y = WireDefault(UInt(8.W), DontCare) + val op = x % y + WireDefault(chiselTypeOf(op), op) + } + } + + property("division should give the width of the numerator") { + assertKnownWidth(8) { + val x = WireDefault(UInt(8.W), DontCare) + val y = WireDefault(UInt(4.W), DontCare) + val op = x / y + WireDefault(chiselTypeOf(op), op) + } + assertKnownWidth(4) { + val x = WireDefault(UInt(4.W), DontCare) + val y = WireDefault(UInt(8.W), DontCare) + val op = x / y + WireDefault(chiselTypeOf(op), op) + } + } } diff --git a/src/test/scala/chiselTests/WidthSpec.scala b/src/test/scala/chiselTests/WidthSpec.scala index 2a33c1d6..34159214 100644 --- a/src/test/scala/chiselTests/WidthSpec.scala +++ b/src/test/scala/chiselTests/WidthSpec.scala @@ -186,3 +186,62 @@ class RegInitWidthSpec extends WireDefaultRegInitSpecImpl { def builder2[T <: Data](x: T, y: T): T = RegInit(x, y) } +class OpWidthSpec extends ChiselFlatSpec { + import firrtl._ + import firrtl.ir._ + + val maxWidth = 5 + val uIntOps: Seq[((UInt, UInt) => UInt, PrimOp)] = + Seq( + (_ +& _, PrimOps.Add), + (_ -& _, PrimOps.Sub), + (_ * _, PrimOps.Mul), + (_ / _, PrimOps.Div), + (_ % _, PrimOps.Rem), + (_ << _, PrimOps.Dshl), + (_ >> _, PrimOps.Dshr) + ) + + assertTesterPasses(new chisel3.testers.BasicTester { + for (i <- 0 to maxWidth) { + for (j <- 0 to maxWidth) { + for ((cOp, fOp) <- uIntOps) { + val args = Seq(i, j).map(w => Reference("", UIntType(IntWidth(w)))) + fOp.propagateType(DoPrim(fOp, args, Nil, UnknownType)) match { + case UIntType(IntWidth(w)) => + val x = 0.U(maxWidth.W).head(i) + val y = 0.U(maxWidth.W).head(j) + assert(w == cOp(x, y).getWidth) + } + } + } + } + stop() + }) + + val sIntOps: Seq[((SInt, SInt) => SInt, PrimOp)] = + Seq( + (_ +& _, PrimOps.Add), + (_ -& _, PrimOps.Sub), + (_ * _, PrimOps.Mul), + (_ / _, PrimOps.Div), + (_ % _, PrimOps.Rem) + ) + + assertTesterPasses(new chisel3.testers.BasicTester { + for (i <- 0 to maxWidth) { + for (j <- 0 to maxWidth) { + for ((cOp, fOp) <- sIntOps) { + val args = Seq(i, j).map(w => Reference("", SIntType(IntWidth(w)))) + fOp.propagateType(DoPrim(fOp, args, Nil, UnknownType)) match { + case SIntType(IntWidth(w)) => + val x = 0.U(maxWidth.W).head(i).asSInt + val y = 0.U(maxWidth.W).head(j).asSInt + assert(w == cOp(x, y).getWidth) + } + } + } + } + stop() + }) +} -- cgit v1.2.3 From f45216effc573d33d4aa4e525cff955ab332efbd Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Thu, 4 Feb 2021 00:36:12 +0000 Subject: Remove Deprecated APIs (#1730) --- core/src/main/scala/chisel3/Bits.scala | 26 -- core/src/main/scala/chisel3/Data.scala | 8 - core/src/main/scala/chisel3/core/package.scala | 288 --------------------- .../main/scala/chisel3/experimental/package.scala | 7 - core/src/main/scala/chisel3/internal/Error.scala | 7 - src/main/scala/chisel3/Driver.scala | 200 -------------- src/main/scala/chisel3/compatibility.scala | 3 - .../scala/chisel3/internal/firrtl/Emitter.scala | 2 +- src/main/scala/chisel3/util/BitPat.scala | 13 - src/main/scala/chisel3/util/BlackBoxUtils.scala | 3 - src/main/scala/chisel3/util/Conditional.scala | 9 - src/test/scala/chiselTests/AsyncResetSpec.scala | 3 +- src/test/scala/chiselTests/CompatibilitySpec.scala | 56 ---- src/test/scala/chiselTests/DriverSpec.scala | 101 -------- .../chiselTests/experimental/ForceNames.scala | 2 +- 15 files changed, 3 insertions(+), 725 deletions(-) delete mode 100644 core/src/main/scala/chisel3/core/package.scala delete mode 100644 src/test/scala/chiselTests/DriverSpec.scala diff --git a/core/src/main/scala/chisel3/Bits.scala b/core/src/main/scala/chisel3/Bits.scala index c86e9208..17829143 100644 --- a/core/src/main/scala/chisel3/Bits.scala +++ b/core/src/main/scala/chisel3/Bits.scala @@ -29,15 +29,6 @@ private[chisel3] sealed trait ToBoolable extends Element { /** @group SourceInfoTransformMacro */ def do_asBool(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool - - /** Casts this $coll to a [[Bool]] - * - * @note The width must be known and equal to 1 - */ - final def toBool(): Bool = macro SourceInfoWhiteboxTransform.noArg - - /** @group SourceInfoTransformMacro */ - def do_toBool(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool } /** A data type for values represented by a single bitvector. This provides basic bitwise operations. @@ -315,11 +306,6 @@ sealed abstract class Bits(private[chisel3] val width: Width) extends Element wi /** Returns the contents of this wire as a [[scala.collection.Seq]] of [[Bool]]. */ final def toBools(): Seq[Bool] = macro SourceInfoTransform.noArg - /** @group SourceInfoTransformMacro */ - @chiselRuntimeDeprecated - @deprecated("Use asBools instead", "3.2") - def do_toBools(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Seq[Bool] = do_asBools - /** Returns the contents of this wire as a [[scala.collection.Seq]] of [[Bool]]. */ final def asBools(): Seq[Bool] = macro SourceInfoTransform.noArg @@ -369,10 +355,6 @@ sealed abstract class Bits(private[chisel3] val width: Width) extends Element wi } } - @chiselRuntimeDeprecated - @deprecated("Use asBool instead", "3.2") - final def do_toBool(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = do_asBool - /** Concatenation operator * * @param that a hardware component @@ -591,10 +573,6 @@ sealed class UInt private[chisel3] (width: Width) extends Bits(width) with Num[U override def do_<= (that: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = compop(sourceInfo, LessEqOp, that) override def do_>= (that: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = compop(sourceInfo, GreaterEqOp, that) - @chiselRuntimeDeprecated - @deprecated("Use '=/=', which avoids potential precedence problems", "3.0") - final def != (that: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = this =/= that - /** Dynamic not equals operator * * @param that a hardware $coll @@ -877,10 +855,6 @@ sealed class SInt private[chisel3] (width: Width) extends Bits(width) with Num[S override def do_<= (that: SInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = compop(sourceInfo, LessEqOp, that) override def do_>= (that: SInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = compop(sourceInfo, GreaterEqOp, that) - @chiselRuntimeDeprecated - @deprecated("Use '=/=', which avoids potential precedence problems", "3.0") - final def != (that: SInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = this =/= that - /** Dynamic not equals operator * * @param that a hardware $coll diff --git a/core/src/main/scala/chisel3/Data.scala b/core/src/main/scala/chisel3/Data.scala index a1f6abf8..332527bf 100644 --- a/core/src/main/scala/chisel3/Data.scala +++ b/core/src/main/scala/chisel3/Data.scala @@ -554,14 +554,6 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { } } - @chiselRuntimeDeprecated - @deprecated("litArg is deprecated, use litOption or litTo*Option", "3.2") - def litArg(): Option[LitArg] = topBindingOpt match { - case Some(ElementLitBinding(litArg)) => Some(litArg) - case Some(BundleLitBinding(litMap)) => None // this API does not support Bundle literals - case _ => None - } - def isLit(): Boolean = litOption.isDefined /** diff --git a/core/src/main/scala/chisel3/core/package.scala b/core/src/main/scala/chisel3/core/package.scala deleted file mode 100644 index fa29f244..00000000 --- a/core/src/main/scala/chisel3/core/package.scala +++ /dev/null @@ -1,288 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chisel3 - -/** - * These definitions exist to deal with those clients that relied on chisel3.core. - * They are deprecated and will be removed in the future. - */ -package object core { - - @deprecated("Use the version in chisel3._", "3.2") - val CompileOptions = chisel3.CompileOptions - - @deprecated("Use the version in chisel3._", "3.2") - val Input = chisel3.Input - @deprecated("Use the version in chisel3._", "3.2") - val Output = chisel3.Output - @deprecated("Use the version in chisel3._", "3.2") - val Flipped = chisel3.Flipped - @deprecated("Use the version in chisel3._", "3.2") - val chiselTypeOf = chisel3.chiselTypeOf - - @deprecated("Use the version in chisel3._", "3.2") - type Data = chisel3.Data - - @deprecated("Use the version in chisel3._", "3.2") - val WireDefault = chisel3.WireDefault - - @deprecated("Use the version in chisel3._", "3.2") - val Clock = chisel3.Clock - @deprecated("Use the version in chisel3._", "3.2") - type Clock = chisel3.Clock - - @deprecated("Use the version in chisel3._", "3.2") - type Reset = chisel3.Reset - - @deprecated("Use the version in chisel3._", "3.2") - type Aggregate = chisel3.Aggregate - - @deprecated("Use the version in chisel3._", "3.2") - val Vec = chisel3.Vec - @deprecated("Use the version in chisel3._", "3.2") - val VecInit = chisel3.VecInit - @deprecated("Use the version in chisel3._", "3.2") - type Vec[T <: Data] = chisel3.Vec[T] - @deprecated("Use the version in chisel3._", "3.2") - type VecLike[T <: Data] = chisel3.VecLike[T] - @deprecated("Use the version in chisel3._", "3.2") - type Bundle = chisel3.Bundle - @deprecated("Use the version in chisel3._", "3.2") - type IgnoreSeqInBundle = chisel3.IgnoreSeqInBundle - @deprecated("Use the version in chisel3._", "3.2") - type Record = chisel3.Record - - @deprecated("Use the version in chisel3._", "3.2") - val assert = chisel3.assert - - @deprecated("Use the version in chisel3._", "3.2") - type Element = chisel3.Element - @deprecated("Use the version in chisel3._", "3.2") - type Bits = chisel3.Bits - - // These provide temporary compatibility for those who foolishly imported from chisel3.core - @deprecated("Avoid importing from chisel3.core, these are not public APIs and may change at any time. " + - " Use chisel3.RawModule instead. This alias will be removed in 3.4.", "since the beginning of time") - type RawModule = chisel3.RawModule - @deprecated("Avoid importing from chisel3.core, these are not public APIs and may change at any time. " + - "Use chisel3.MultiIOModule instead. This alias will be removed in 3.4.", "since the beginning of time") - type MultiIOModule = chisel3.Module - @deprecated("Avoid importing from chisel3.core, these are not public APIs and may change at any time. " + - " Use chisel3.RawModule instead. This alias will be removed in 3.4.", "since the beginning of time") - type UserModule = chisel3.RawModule - @deprecated("Avoid importing from chisel3.core, these are not public APIs and may change at any time. " + - "Use chisel3.MultiIOModule instead. This alias will be removed in 3.4.", "since the beginning of time") - type ImplicitModule = chisel3.Module - - @deprecated("Use the version in chisel3._", "3.2") - val Bits = chisel3.Bits - @deprecated("Use the version in chisel3._", "3.2") - type Num[T <: chisel3.Data] = chisel3.Num[T] - @deprecated("Use the version in chisel3._", "3.2") - type UInt = chisel3.UInt - @deprecated("Use the version in chisel3._", "3.2") - val UInt = chisel3.UInt - @deprecated("Use the version in chisel3._", "3.2") - type SInt = chisel3.SInt - @deprecated("Use the version in chisel3._", "3.2") - val SInt = chisel3.SInt - @deprecated("Use the version in chisel3._", "3.2") - type Bool = chisel3.Bool - @deprecated("Use the version in chisel3._", "3.2") - val Bool = chisel3.Bool - @deprecated("Use the version in chisel3._", "3.2") - val Mux = chisel3.Mux - - @deprecated("Use the version in chisel3._", "3.2") - type BlackBox = chisel3.BlackBox - - @deprecated("Use the version in chisel3._", "3.2") - val Mem = chisel3.Mem - @deprecated("Use the version in chisel3._", "3.2") - type MemBase[T <: chisel3.Data] = chisel3.MemBase[T] - @deprecated("Use the version in chisel3._", "3.2") - type Mem[T <: chisel3.Data] = chisel3.Mem[T] - @deprecated("Use the version in chisel3._", "3.2") - val SyncReadMem = chisel3.SyncReadMem - @deprecated("Use the version in chisel3._", "3.2") - type SyncReadMem[T <: chisel3.Data] = chisel3.SyncReadMem[T] - - @deprecated("Use the version in chisel3._", "3.2") - val Module = chisel3.Module - @deprecated("Use the version in chisel3._", "3.2") - type Module = chisel3.Module - - @deprecated("Use the version in chisel3._", "3.2") - val printf = chisel3.printf - - @deprecated("Use the version in chisel3._", "3.2") - val RegNext = chisel3.RegNext - @deprecated("Use the version in chisel3._", "3.2") - val RegInit = chisel3.RegInit - @deprecated("Use the version in chisel3._", "3.2") - val Reg = chisel3.Reg - - @deprecated("Use the version in chisel3._", "3.2") - val when = chisel3.when - @deprecated("Use the version in chisel3._", "3.2") - type WhenContext = chisel3.WhenContext - - @deprecated("Use the version in chisel3._", "3.2") - type Printable = chisel3.Printable - @deprecated("Use the version in chisel3._", "3.2") - val Printable = chisel3.Printable - @deprecated("Use the version in chisel3._", "3.2") - type Printables = chisel3.Printables - @deprecated("Use the version in chisel3._", "3.2") - val Printables = chisel3.Printables - @deprecated("Use the version in chisel3._", "3.2") - type PString = chisel3.PString - @deprecated("Use the version in chisel3._", "3.2") - val PString = chisel3.PString - @deprecated("Use the version in chisel3._", "3.2") - type FirrtlFormat = chisel3.FirrtlFormat - @deprecated("Use the version in chisel3._", "3.2") - val FirrtlFormat = chisel3.FirrtlFormat - @deprecated("Use the version in chisel3._", "3.2") - type Decimal = chisel3.Decimal - @deprecated("Use the version in chisel3._", "3.2") - val Decimal = chisel3.Decimal - @deprecated("Use the version in chisel3._", "3.2") - type Hexadecimal = chisel3.Hexadecimal - val Hexadecimal = chisel3.Hexadecimal - @deprecated("Use the version in chisel3._", "3.2") - type Binary = chisel3.Binary - @deprecated("Use the version in chisel3._", "3.2") - val Binary = chisel3.Binary - @deprecated("Use the version in chisel3._", "3.2") - type Character = chisel3.Character - @deprecated("Use the version in chisel3._", "3.2") - val Character = chisel3.Character - @deprecated("Use the version in chisel3._", "3.2") - type Name = chisel3.Name - @deprecated("Use the version in chisel3._", "3.2") - val Name = chisel3.Name - @deprecated("Use the version in chisel3._", "3.2") - type FullName = chisel3.FullName - @deprecated("Use the version in chisel3._", "3.2") - val FullName = chisel3.FullName - @deprecated("Use the version in chisel3._", "3.2") - val Percent = chisel3.Percent - - @deprecated("Use the version in chisel3.experimental._", "3.2") - type Param = chisel3.experimental.Param - @deprecated("Use the version in chisel3.experimental._", "3.2") - type IntParam = chisel3.experimental.IntParam - @deprecated("Use the version in chisel3.experimental._", "3.2") - val IntParam = chisel3.experimental.IntParam - @deprecated("Use the version in chisel3.experimental._", "3.2") - type DoubleParam = chisel3.experimental.DoubleParam - @deprecated("Use the version in chisel3.experimental._", "3.2") - val DoubleParam = chisel3.experimental.DoubleParam - @deprecated("Use the version in chisel3.experimental._", "3.2") - type StringParam = chisel3.experimental.StringParam - @deprecated("Use the version in chisel3.experimental._", "3.2") - val StringParam = chisel3.experimental.StringParam - @deprecated("Use the version in chisel3.experimental._", "3.2") - type RawParam = chisel3.experimental.RawParam - @deprecated("Use the version in chisel3.experimental._", "3.2") - val RawParam = chisel3.experimental.RawParam - - @deprecated("Use the version in chisel3.experimental._", "3.2") - type Analog = chisel3.experimental.Analog - @deprecated("Use the version in chisel3.experimental._", "3.2") - val Analog = chisel3.experimental.Analog - - @deprecated("Use the version in chisel3._", "3.2") - implicit class fromIntToWidth(int: Int) extends chisel3.fromIntToWidth(int) - - @deprecated("Use the version in chisel3.experimental._", "3.2") - val attach = chisel3.experimental.attach - - @deprecated("Use the version in chisel3.experimental._", "3.2") - type EnumType = chisel3.experimental.EnumType - @deprecated("Use the version in chisel3.experimental._", "3.2") - type EnumFactory = chisel3.experimental.EnumFactory - @deprecated("Use the version in chisel3.experimental._", "3.2") - val EnumAnnotations = chisel3.experimental.EnumAnnotations - - @deprecated("Use the version in chisel3._", "3.2") - val withClockAndReset = chisel3.withClockAndReset - @deprecated("Use the version in chisel3._", "3.2") - val withClock = chisel3.withClock - @deprecated("Use the version in chisel3._", "3.2") - val withReset = chisel3.withReset - - @deprecated("Use the version in chisel3._", "3.2") - val dontTouch = chisel3.dontTouch - - @deprecated("Use the version in chisel3.experimental._", "3.2") - type BaseModule = chisel3.experimental.BaseModule - @deprecated("Use the version in chisel3.experimental._", "3.2") - type ExtModule = chisel3.experimental.ExtModule - - @deprecated("Use the version in chisel3.experimental._", "3.2") - val IO = chisel3.experimental.IO - - @deprecated("Use the version in chisel3.experimental._", "3.2") - type FixedPoint = chisel3.experimental.FixedPoint - @deprecated("Use the version in chisel3.experimental._", "3.2") - val FixedPoint = chisel3.experimental.FixedPoint - @deprecated("Use the version in chisel3.experimental._", "3.2") - implicit class fromDoubleToLiteral(double: Double) extends experimental.FixedPoint.Implicits.fromDoubleToLiteral(double) - @deprecated("Use the version in chisel3.experimental._", "3.2") - implicit class fromIntToBinaryPoint(int: Int) extends chisel3.fromIntToBinaryPoint(int) - @deprecated("Use the version in chisel3.experimental._", "3.2") - type RunFirrtlTransform = chisel3.experimental.RunFirrtlTransform - - @deprecated("Use the version in chisel3.experimental._", "3.2") - val annotate = chisel3.experimental.annotate - - @deprecated("Use the version in chisel3.experimental._", "3.2") - val DataMirror = chisel3.experimental.DataMirror - @deprecated("Use the version in chisel3._", "3.2") - type ActualDirection = chisel3.ActualDirection - @deprecated("Use the version in chisel3._", "3.2") - val ActualDirection = chisel3.ActualDirection - - @deprecated("Use the version in chisel3.internal._", "3.2") - val requireIsHardware = chisel3.internal.requireIsHardware - @deprecated("Use the version in chisel3.internal._", "3.2") - val requireIsChiselType = chisel3.internal.requireIsChiselType - @deprecated("Use the version in chisel3.internal._", "3.2") - val BiConnect = chisel3.internal.BiConnect - @deprecated("Use the version in chisel3.internal._", "3.2") - val MonoConnect = chisel3.internal.MonoConnect - @deprecated("Use the version in chisel3.internal._", "3.2") - val BindingDirection = chisel3.internal.BindingDirection - @deprecated("Use the version in chisel3.internal._", "3.2") - type Binding = chisel3.internal.Binding - @deprecated("Use the version in chisel3.internal._", "3.2") - type TopBinding = chisel3.internal.TopBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type UnconstrainedBinding = chisel3.internal.UnconstrainedBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type ConstrainedBinding = chisel3.internal.ConstrainedBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type ReadOnlyBinding = chisel3.internal.ReadOnlyBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type OpBinding = chisel3.internal.OpBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type MemoryPortBinding = chisel3.internal.MemoryPortBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type PortBinding = chisel3.internal.PortBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type RegBinding = chisel3.internal.RegBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type WireBinding = chisel3.internal.WireBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type ChildBinding = chisel3.internal.ChildBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type DontCareBinding = chisel3.internal.DontCareBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type LitBinding = chisel3.internal.LitBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type ElementLitBinding = chisel3.internal.ElementLitBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type BundleLitBinding = chisel3.internal.BundleLitBinding -} diff --git a/core/src/main/scala/chisel3/experimental/package.scala b/core/src/main/scala/chisel3/experimental/package.scala index ec3f2a79..4dc7ba4b 100644 --- a/core/src/main/scala/chisel3/experimental/package.scala +++ b/core/src/main/scala/chisel3/experimental/package.scala @@ -20,13 +20,6 @@ package object experimental { type ChiselEnum = EnumFactory - @deprecated("Use the version in chisel3._", "3.2") - val withClockAndReset = chisel3.withClockAndReset - @deprecated("Use the version in chisel3._", "3.2") - val withClock = chisel3.withClock - @deprecated("Use the version in chisel3._", "3.2") - val withReset = chisel3.withReset - // Rocket Chip-style clonemodule /** A record containing the results of CloneModuleAsRecord diff --git a/core/src/main/scala/chisel3/internal/Error.scala b/core/src/main/scala/chisel3/internal/Error.scala index 6a1794ce..60bb6e2b 100644 --- a/core/src/main/scala/chisel3/internal/Error.scala +++ b/core/src/main/scala/chisel3/internal/Error.scala @@ -61,13 +61,6 @@ class ChiselException(message: String, cause: Throwable = null) extends Exceptio trimmedReverse.reverse.toArray } - /** trims the top of the stack of elements belonging to [[blacklistPackages]] - * then trims the bottom elements until it reaches [[builderName]] - * then continues trimming elements belonging to [[blacklistPackages]] - */ - @deprecated("This method will be removed in 3.4", "3.3") - def trimmedStackTrace: Array[StackTraceElement] = trimmedStackTrace(this) - def chiselStackTrace: String = { val trimmed = trimmedStackTrace(likelyCause) diff --git a/src/main/scala/chisel3/Driver.scala b/src/main/scala/chisel3/Driver.scala index 998f5ca0..fb564446 100644 --- a/src/main/scala/chisel3/Driver.scala +++ b/src/main/scala/chisel3/Driver.scala @@ -84,203 +84,3 @@ case class ChiselExecutionSuccess( */ @deprecated("This will be removed in Chisel 3.5", "Chisel 3.4") case class ChiselExecutionFailure(message: String) extends ChiselExecutionResult - -@deprecated("Please switch to chisel3.stage.ChiselStage", "3.2.4") -object Driver extends BackendCompilationUtilities { - - /** - * Elaborate the Module specified in the gen function into a Chisel IR Circuit. - * - * @param gen A function that creates a Module hierarchy. - * @return The resulting Chisel IR in the form of a Circuit. (TODO: Should be FIRRTL IR) - */ - @deprecated("Use ChiselStage.elaborate or use a ChiselStage class. This will be removed in 3.4.", "3.2.4") - def elaborate[T <: RawModule](gen: () => T): Circuit = internal.Builder.build(Module(gen()))._1 - - /** - * Convert the given Chisel IR Circuit to a FIRRTL Circuit. - * - * @param ir Chisel IR Circuit, generated e.g. by elaborate(). - */ - @deprecated("Use ChiselStage.convert or use a ChiselStage class. This will be removed in 3.4.", "3.2.4") - def toFirrtl(ir: Circuit): firrtl.ir.Circuit = Converter.convert(ir) - - /** - * Emit the Module specified in the gen function directly as a FIRRTL string without - * invoking FIRRTL. - * - * @param gen A function that creates a Module hierarchy. - */ - @deprecated("Use (new chisel3.stage.ChiselStage).emitChirrtl. This will be removed in 3.4.", "3.2.2") - def emit[T <: RawModule](gen: () => T): String = Driver.emit(elaborate(gen)) - - /** - * Emit the given Chisel IR Circuit as a FIRRTL string, without invoking FIRRTL. - * - * @param ir Chisel IR Circuit, generated e.g. by elaborate(). - */ - @deprecated("Use (new chisel3.stage.ChiselStage).emitChirrtl", "3.2.2") - def emit[T <: RawModule](ir: Circuit): String = Emitter.emit(ir) - - /** - * Elaborate the Module specified in the gen function into Verilog. - * - * @param gen A function that creates a Module hierarchy. - * @return A String containing the design in Verilog. - */ - @deprecated("Use (new chisel3.stage.ChiselStage).emitVerilog. This will be removed in 3.4.", "3.2.2") - def emitVerilog[T <: RawModule](gen: => T): String = { - execute(Array[String](), { () => gen }) match { - case ChiselExecutionSuccess(_, _, Some(firrtl.FirrtlExecutionSuccess(_, verilog))) => verilog - case _ => sys.error("Cannot get Verilog!") - } - } - - /** - * Dump the elaborated Chisel IR Circuit as a FIRRTL String, without invoking FIRRTL. - * - * If no File is given as input, it will dump to a default filename based on the name of the - * top Module. - * - * @param c Elaborated Chisel Circuit. - * @param optName File to dump to. If unspecified, defaults to ".fir". - * @return The File the circuit was dumped to. - */ - @deprecated("Migrate to chisel3.stage.ChiselStage. This will be removed in 3.4.", "3.2.4") - def dumpFirrtl(ir: Circuit, optName: Option[File]): File = { - val f = optName.getOrElse(new File(ir.name + ".fir")) - val w = new FileWriter(f) - w.write(Driver.emit(ir)) - w.close() - f - } - - /** - * Emit the annotations of a circuit - * - * @param ir The circuit containing annotations to be emitted - * @param optName An optional filename (will use s"\${ir.name}.json" otherwise) - */ - @deprecated("Migrate to chisel3.stage.ChiselStage. This will be removed in 3.4.", "3.2.4") - def dumpAnnotations(ir: Circuit, optName: Option[File]): File = { - val f = optName.getOrElse(new File(ir.name + ".anno.json")) - val w = new FileWriter(f) - w.write(JsonProtocol.serialize(ir.annotations.map(_.toFirrtl))) - w.close() - f - } - - /** - * Dump the elaborated Circuit to ProtoBuf. - * - * If no File is given as input, it will dump to a default filename based on the name of the - * top Module. - * - * @param c Elaborated Chisel Circuit. - * @param optFile Optional File to dump to. If unspecified, defaults to ".pb". - * @return The File the circuit was dumped to. - */ - @deprecated("Migrate to chisel3.stage.ChiselStage. This will be removed in 3.4.", "3.2.4") - def dumpProto(c: Circuit, optFile: Option[File]): File = { - val f = optFile.getOrElse(new File(c.name + ".pb")) - val ostream = new java.io.FileOutputStream(f) - // Lazily convert modules to make intermediate objects garbage collectable - val modules = c.components.map(m => () => Converter.convert(m)) - firrtl.proto.ToProto.writeToStreamFast(ostream, ir.NoInfo, modules, c.name) - f - } - - private var target_dir: Option[String] = None - @deprecated("Use chisel3.stage.ChiselStage with '--target-directory'. This will be removed in 3.4.", "3.2.2") - def parseArgs(args: Array[String]): Unit = { - for (i <- 0 until args.size) { - if (args(i) == "--targetDir") { - target_dir = Some(args(i + 1)) - } - } - } - - @deprecated("This has no effect on Chisel3 Driver! This will be removed in 3.4.", "3.2.2") - def targetDir(): String = { target_dir getOrElse new File(".").getCanonicalPath } - - /** - * Run the chisel3 compiler and possibly the firrtl compiler with options specified - * - * @param optionsManager The options specified - * @param dut The device under test - * @return An execution result with useful stuff, or failure with message - */ - @deprecated("Use chisel3.stage.ChiselStage.execute. This will be removed in 3.4.", "3.2.2") - def execute( - optionsManager: ExecutionOptionsManager with HasChiselExecutionOptions with HasFirrtlOptions, - dut: () => RawModule): ChiselExecutionResult = { - - val annos: AnnotationSeq = - Seq(DriverCompatibility.OptionsManagerAnnotation(optionsManager), ChiselGeneratorAnnotation(dut)) ++ - optionsManager.chiselOptions.toAnnotations ++ - optionsManager.firrtlOptions.toAnnotations ++ - optionsManager.commonOptions.toAnnotations - - val targets = - Seq( Dependency[DriverCompatibility.AddImplicitOutputFile], - Dependency[DriverCompatibility.AddImplicitOutputAnnotationFile], - Dependency[DriverCompatibility.DisableFirrtlStage], - Dependency[ChiselStage], - Dependency[DriverCompatibility.MutateOptionsManager], - Dependency[DriverCompatibility.ReEnableFirrtlStage], - Dependency[DriverCompatibility.FirrtlPreprocessing], - Dependency[chisel3.stage.phases.MaybeFirrtlStage] ) - val currentState = - Seq( Dependency[firrtl.stage.phases.DriverCompatibility.AddImplicitFirrtlFile], - Dependency[chisel3.stage.phases.Convert] ) - - val phases: Seq[Phase] = new PhaseManager(targets, currentState) { - override val wrappers = Seq( DeletedWrapper(_: Phase) ) - }.transformOrder - - val annosx = try { - phases.foldLeft(annos)( (a, p) => p.transform(a) ) - } catch { - /* ChiselStage and FirrtlStage can throw StageError. Since Driver is not a StageMain, it cannot catch these. While - * Driver is deprecated and removed in 3.2.1+, the Driver catches all errors. - */ - case e: StageError => annos - } - - view[ChiselExecutionResult](annosx) - } - - /** - * Run the chisel3 compiler and possibly the firrtl compiler with options specified via an array of Strings - * - * @param args The options specified, command line style - * @param dut The device under test - * @return An execution result with useful stuff, or failure with message - */ - @deprecated("Use chisel3.stage.ChiselStage.execute. This will be removed in 3.4.", "3.2.2") - def execute(args: Array[String], dut: () => RawModule): ChiselExecutionResult = { - val optionsManager = new ExecutionOptionsManager("chisel3") with HasChiselExecutionOptions with HasFirrtlOptions - - optionsManager.parse(args) match { - case true => - execute(optionsManager, dut) - case _ => - ChiselExecutionFailure("could not parse results") - } - } - - /** - * This is just here as command line way to see what the options are - * It will not successfully run - * TODO: Look into dynamic class loading as way to make this main useful - * - * @param args unused args - */ - @deprecated("Use chisel3.stage.ChiselMain. This will be removed in 3.4.", "3.2.2") - def main(args: Array[String]) { - execute(Array("--help"), null) - } - - val version = BuildInfo.version - val chiselVersionString = BuildInfo.toString -} diff --git a/src/main/scala/chisel3/compatibility.scala b/src/main/scala/chisel3/compatibility.scala index e62cba7d..3b9a3dcc 100644 --- a/src/main/scala/chisel3/compatibility.scala +++ b/src/main/scala/chisel3/compatibility.scala @@ -363,8 +363,6 @@ package object Chisel { implicit class fromIntToWidth(x: Int) extends chisel3.fromIntToWidth(x) type BackendCompilationUtilities = firrtl.util.BackendCompilationUtilities - @deprecated("Please switch to chisel3.stage.ChiselStage", "3.4") - val Driver = chisel3.Driver val ImplicitConversions = chisel3.util.ImplicitConversions // Deprecated as of Chisel3 @@ -455,7 +453,6 @@ package object Chisel { val Log2 = chisel3.util.Log2 - val unless = chisel3.util.unless type SwitchContext[T <: Bits] = chisel3.util.SwitchContext[T] val is = chisel3.util.is val switch = chisel3.util.switch diff --git a/src/main/scala/chisel3/internal/firrtl/Emitter.scala b/src/main/scala/chisel3/internal/firrtl/Emitter.scala index 354be0c0..ad4df80a 100644 --- a/src/main/scala/chisel3/internal/firrtl/Emitter.scala +++ b/src/main/scala/chisel3/internal/firrtl/Emitter.scala @@ -29,7 +29,7 @@ private class Emitter(circuit: Circuit) { case d: Clock => "Clock" case _: AsyncReset => "AsyncReset" case _: ResetType => "Reset" - case d: chisel3.core.EnumType => s"UInt${d.width}" + case d: chisel3.experimental.EnumType => s"UInt${d.width}" case d: UInt => s"UInt${d.width}" case d: SInt => s"SInt${d.width}" case d: FixedPoint => s"Fixed${d.width}${d.binaryPoint}" diff --git a/src/main/scala/chisel3/util/BitPat.scala b/src/main/scala/chisel3/util/BitPat.scala index 40563e23..12a421a0 100644 --- a/src/main/scala/chisel3/util/BitPat.scala +++ b/src/main/scala/chisel3/util/BitPat.scala @@ -91,12 +91,6 @@ object BitPat { /** @group SourceInfoTransformMacro */ def do_=/= (that: BitPat) (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = that =/= x - - final def != (that: BitPat): Bool = macro SourceInfoTransform.thatArg - @chiselRuntimeDeprecated - @deprecated("Use '=/=', which avoids potential precedence problems", "3.0") - def do_!= (that: BitPat) - (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = that != x } } @@ -125,11 +119,4 @@ sealed class BitPat(val value: BigInt, val mask: BigInt, width: Int) extends Sou !(this === that) } - def != (that: UInt): Bool = macro SourceInfoTransform.thatArg - @chiselRuntimeDeprecated - @deprecated("Use '=/=', which avoids potential precedence problems", "3.0") - def do_!= (that: UInt) - (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = { - this =/= that - } } diff --git a/src/main/scala/chisel3/util/BlackBoxUtils.scala b/src/main/scala/chisel3/util/BlackBoxUtils.scala index 21bd4dfa..3cd704b3 100644 --- a/src/main/scala/chisel3/util/BlackBoxUtils.scala +++ b/src/main/scala/chisel3/util/BlackBoxUtils.scala @@ -9,9 +9,6 @@ import firrtl.transforms.{BlackBoxPathAnno, BlackBoxResourceAnno, BlackBoxInline trait HasBlackBoxResource extends BlackBox { self: BlackBox => - @deprecated("Use addResource instead", "3.2") - def setResource(blackBoxResource: String): Unit = addResource(blackBoxResource) - /** Copies a resource file to the target directory * * Resource files are located in project_root/src/main/resources/. diff --git a/src/main/scala/chisel3/util/Conditional.scala b/src/main/scala/chisel3/util/Conditional.scala index b934f27f..1ac94bfe 100644 --- a/src/main/scala/chisel3/util/Conditional.scala +++ b/src/main/scala/chisel3/util/Conditional.scala @@ -12,15 +12,6 @@ import scala.reflect.macros.blackbox._ import chisel3._ import chisel3.internal.sourceinfo.SourceInfo -@deprecated("The unless conditional is deprecated, use when(!condition){...} instead", "3.2") -object unless { - /** Does the same thing as [[when$ when]], but with the condition inverted. - */ - def apply(c: Bool)(block: => Any) { - when (!c) { block } - } -} - /** Implementation details for [[switch]]. See [[switch]] and [[chisel3.util.is is]] for the * user-facing API. * @note DO NOT USE. This API is subject to change without warning. diff --git a/src/test/scala/chiselTests/AsyncResetSpec.scala b/src/test/scala/chiselTests/AsyncResetSpec.scala index a8e62fe8..d49f390c 100644 --- a/src/test/scala/chiselTests/AsyncResetSpec.scala +++ b/src/test/scala/chiselTests/AsyncResetSpec.scala @@ -209,7 +209,6 @@ class AsyncResetSpec extends ChiselFlatSpec with Utils { } it should "support Fixed regs" in { - import chisel3.experimental.{withReset => _, _} assertTesterPasses(new BasicTester { val reg = withReset(reset.asAsyncReset)(RegNext(-6.0.F(2.BP), 3.F(2.BP))) val (count, done) = Counter(true.B, 4) @@ -223,7 +222,7 @@ class AsyncResetSpec extends ChiselFlatSpec with Utils { } it should "support Interval regs" in { - import chisel3.experimental.{withReset => _, _} + import chisel3.experimental._ assertTesterPasses(new BasicTester { val reg = withReset(reset.asAsyncReset) { val x = RegInit(Interval(range"[0,13]"), 13.I) diff --git a/src/test/scala/chiselTests/CompatibilitySpec.scala b/src/test/scala/chiselTests/CompatibilitySpec.scala index 6a77c821..c7a68e7c 100644 --- a/src/test/scala/chiselTests/CompatibilitySpec.scala +++ b/src/test/scala/chiselTests/CompatibilitySpec.scala @@ -103,7 +103,6 @@ class CompatibiltySpec extends ChiselFlatSpec with ScalaCheckDrivenPropertyCheck Reverse(wire) shouldBe a [UInt] Cat(wire, wire) shouldBe a [UInt] Log2(wire) shouldBe a [UInt] - unless(Bool(false)) {} // 'switch' and 'is' are tested below in Risc Counter(2) shouldBe a [Counter] DecoupledIO(wire) shouldBe a [DecoupledIO[UInt]] @@ -353,13 +352,6 @@ class CompatibiltySpec extends ChiselFlatSpec with ScalaCheckDrivenPropertyCheck info("Deprecated method DC hasn't been removed") val bp = BitPat.DC(4) - - info("BitPat != UInt is a Bool") - (bp != UInt(4)) shouldBe a [Bool] - - /* This test does not work, but I'm not sure it's supposed to? It does *not* work on chisel3. */ - // info("UInt != BitPat is a Bool") - // (UInt(4) != bp) shouldBe a [Bool] } ChiselStage.elaborate(new Foo) @@ -474,22 +466,6 @@ class CompatibiltySpec extends ChiselFlatSpec with ScalaCheckDrivenPropertyCheck behavior of "Data methods" - it should "support legacy methods" in { - class Foo extends Module { - val io = IO(new Bundle{}) - - info("litArg works") - UInt(width=3).litArg() should be (None) - UInt(0, width=3).litArg() should be (Some(chisel3.internal.firrtl.ULit(0, 3.W))) - - info("toBits works") - val wire = Wire(UInt(width=4)) - Vec.fill(4)(wire).toBits.getWidth should be (wire.getWidth * 4) - } - - ChiselStage.elaborate(new Foo) - } - behavior of "Wire" it should "support legacy methods" in { @@ -542,9 +518,6 @@ class CompatibiltySpec extends ChiselFlatSpec with ScalaCheckDrivenPropertyCheck val u = UInt(8) val s = SInt(-4) - info("toBools works") - u.toBools shouldBe a [Seq[Bool]] - info("asBits works") s.asBits shouldBe a [Bits] @@ -553,35 +526,6 @@ class CompatibiltySpec extends ChiselFlatSpec with ScalaCheckDrivenPropertyCheck info("toUInt works") s.toUInt shouldBe a [UInt] - - info("toBool works") - UInt(1).toBool shouldBe a [Bool] - } - - ChiselStage.elaborate(new Foo) - } - - behavior of "UInt" - - it should "support legacy methods" in { - class Foo extends Module { - val io = new Bundle{} - - info("!= works") - (UInt(1) != UInt(1)) shouldBe a [Bool] - } - - ChiselStage.elaborate(new Foo) - } - - behavior of "SInt" - - it should "support legacy methods" in { - class Foo extends Module { - val io = new Bundle{} - - info("!= works") - (SInt(-1) != SInt(-1)) shouldBe a [Bool] } ChiselStage.elaborate(new Foo) diff --git a/src/test/scala/chiselTests/DriverSpec.scala b/src/test/scala/chiselTests/DriverSpec.scala deleted file mode 100644 index 3a78683b..00000000 --- a/src/test/scala/chiselTests/DriverSpec.scala +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chiselTests - -import java.io.File - -import chisel3._ -import firrtl.FirrtlExecutionSuccess -import org.scalacheck.Test.Failed -import org.scalatest.Succeeded -import org.scalatest.freespec.AnyFreeSpec -import org.scalatest.matchers.should.Matchers - -class DummyModule extends Module { - val io = IO(new Bundle { - val in = Input(UInt(1.W)) - val out = Output(UInt(1.W)) - }) - io.out := io.in -} - -class TypeErrorModule extends chisel3.Module { - val in = IO(Input(UInt(1.W))) - val out = IO(Output(SInt(1.W))) - out := in -} - -class DriverSpec extends AnyFreeSpec with Matchers with chiselTests.Utils { - "Driver's execute methods are used to run chisel and firrtl" - { - "options can be picked up from comand line with no args" in { - // NOTE: Since we don't provide any arguments (notably, "--target-dir"), - // the generated files will be created in the current directory. - val targetDir = "." - Driver.execute(Array.empty[String], () => new DummyModule) match { - case ChiselExecutionSuccess(_, _, Some(_: FirrtlExecutionSuccess)) => - val exts = List("anno.json", "fir", "v") - for (ext <- exts) { - val dummyOutput = new File(targetDir, "DummyModule" + "." + ext) - info(s"${dummyOutput.toString} exists") - dummyOutput.exists() should be(true) - dummyOutput.delete() - } - Succeeded - case _ => - Failed - } - } - - "options can be picked up from comand line setting top name" in { - val targetDir = "local-build" - Driver.execute(Array("-tn", "dm", "-td", targetDir), () => new DummyModule) match { - case ChiselExecutionSuccess(_, _, Some(_: FirrtlExecutionSuccess)) => - val exts = List("anno.json", "fir", "v") - for (ext <- exts) { - val dummyOutput = new File(targetDir, "dm" + "." + ext) - info(s"${dummyOutput.toString} exists") - dummyOutput.exists() should be(true) - dummyOutput.delete() - } - Succeeded - case _ => - Failed - } - - } - - "execute returns a chisel execution result" in { - val targetDir = "test_run_dir" - val args = Array("--compiler", "low", "--target-dir", targetDir) - - info("Driver returned a ChiselExecutionSuccess") - val result = Driver.execute(args, () => new DummyModule) - result shouldBe a[ChiselExecutionSuccess] - - info("emitted circuit included 'circuit DummyModule'") - val successResult = result.asInstanceOf[ChiselExecutionSuccess] - successResult.emitted should include ("circuit DummyModule") - - val dummyOutput = new File(targetDir, "DummyModule.lo.fir") - info(s"${dummyOutput.toString} exists") - dummyOutput.exists() should be(true) - dummyOutput.delete() - } - - "user errors show a trimmed stack trace" in { - val targetDir = "test_run_dir" - val args = Array("--compiler", "low", "--target-dir", targetDir) - - val (stdout, stderr, result) = grabStdOutErr { Driver.execute(args, () => new TypeErrorModule) } - - info("stdout shows a trimmed stack trace") - stdout should include ("Stack trace trimmed to user code only") - - info("stdout does not include FIRRTL information") - stdout should not include ("firrtl.") - - info("Driver returned a ChiselExecutionFailure") - result shouldBe a [ChiselExecutionFailure] - } - } -} diff --git a/src/test/scala/chiselTests/experimental/ForceNames.scala b/src/test/scala/chiselTests/experimental/ForceNames.scala index b3534f11..06f911e6 100644 --- a/src/test/scala/chiselTests/experimental/ForceNames.scala +++ b/src/test/scala/chiselTests/experimental/ForceNames.scala @@ -4,7 +4,7 @@ package chiselTests import firrtl._ import chisel3._ -import chisel3.core.annotate +import chisel3.experimental.annotate import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage} import chisel3.util.experimental.{ForceNameAnnotation, ForceNamesTransform, InlineInstance, forceName} import firrtl.annotations.{Annotation, ReferenceTarget} -- cgit v1.2.3 From 8fa46071c382b6fdecba86a9bab729526fa4fbe2 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 4 Feb 2021 10:07:56 -0800 Subject: Minor docs improvements (#1774) * Fix some botched formatting (replace ```mdoc scala with ```scala mdoc) * Replace some unnecessary uses of triple backticks with single backticks * Move appendix docs from wiki-deprecated/ to appendix/ * This will require an update on the website as well * Update Bundle literal docs--- docs/src/appendix/chisel3-vs-chisel2.md | 203 +++++++++++++++++++++ docs/src/appendix/experimental-features.md | 138 ++++++++++++++ docs/src/appendix/upgrading-from-scala-2-11.md | 88 +++++++++ docs/src/explanations/blackboxes.md | 50 ++--- docs/src/explanations/bundles-and-vecs.md | 24 +-- docs/src/wiki-deprecated/chisel3-vs-chisel2.md | 203 --------------------- docs/src/wiki-deprecated/experimental-features.md | 119 ------------ .../wiki-deprecated/upgrading-from-scala-2-11.md | 88 --------- 8 files changed, 464 insertions(+), 449 deletions(-) create mode 100644 docs/src/appendix/chisel3-vs-chisel2.md create mode 100644 docs/src/appendix/experimental-features.md create mode 100644 docs/src/appendix/upgrading-from-scala-2-11.md delete mode 100644 docs/src/wiki-deprecated/chisel3-vs-chisel2.md delete mode 100644 docs/src/wiki-deprecated/experimental-features.md delete mode 100644 docs/src/wiki-deprecated/upgrading-from-scala-2-11.md diff --git a/docs/src/appendix/chisel3-vs-chisel2.md b/docs/src/appendix/chisel3-vs-chisel2.md new file mode 100644 index 00000000..bfd20348 --- /dev/null +++ b/docs/src/appendix/chisel3-vs-chisel2.md @@ -0,0 +1,203 @@ +--- +layout: docs +title: "Chisel3 vs. Chisel2" +section: "chisel3" +--- + +# Chisel3 vs Chisel2 + +## Chisel2 Migration +For those moving from Chisel2, there were some backwards incompatible changes +and your RTL needs to be modified to work with Chisel3. The required +modifications are: + + - Wire declaration style: + ```scala + val wire = UInt(width = 15) + ``` + becomes (in Chisel3): + ```scala + val wire = Wire(UInt(15.W)) + ``` + + - I/O declaration style: + ```scala + val done = Bool(OUTPUT) + ``` + becomes (in Chisel3): + ```scala + val wire = Output(Bool()) + ``` + + - Sequential memories: + ```scala + val addr = Reg(UInt()) + val mem = Mem(UInt(8.W), 1024, seqRead = true) + val dout = when(enable) { mem(addr) } + ``` + becomes (in Chisel3): + ```scala + val addr = UInt() + val mem = SyncReadMem(1024, UInt(8.W)) + val dout = mem.read(addr, enable) + ``` + + Notice the address register is now internal to the SyncReadMem(), but the data + will still return on the subsequent cycle. + + - Generating Verilog with + ```scala + object Hello { + def main(args: Array[String]): Unit = { + chiselMain(Array("--backend", "v"), () => Module(new Hello())) + } + } + ``` + becomes (in Chisel3): + ```scala + object Hello { + def main(args: Array[String]): Unit = { + chisel3.Driver.execute(Array[String](), () => new Hello()) + } + } + ``` + + - Package changes: + - Chisel.log2Ceil -> chisel3.util.log2Ceil + - BitPat + - Decoupled is also in chisel3.util + +Please refer to the [Chisel3 compatibility section](https://github.com/ucb-bar/chisel#chisel3) +for instructions on preparing your Chisel2 designs for Chisel3. + +## Deprecated Usage +* `Vec(Reg)` should be replaced with `Reg(Vec)`, +* type-only vals (no associated data) must be wrapped in a `Wire()` if they will be the destination of a wiring operation (":=" or " < >"), +* masked bit patterns ('b??') should be created using BitPat(), not UInt() or Bits(), +* the `clone` method required for parameterized Bundles has been renamed `cloneType`, +* the con and alt inputs to a Mux must be type-compatible - both signed or both unsigned, +* bulk-connection to a node that has been procedurally assigned-to is illegal, +* `!=` is deprecated, use `=/=` instead, +* use `SyncReadMem(...)` instead of `Mem(..., seqRead)`, +* use `SyncReadMem(n:Int, out: => T)` instead of `SyncReadMem(out: => T, n:Int)`, +* use `SyncReadMem(...)` instead of `SeqMem(...)`, +* use `Mem(n:Int, t:T)` instead of `Mem(out:T, n:Int)`, +* use `Vec(n:Int, gen: => T)` instead of `Vec(gen: => T, n:Int)`, +* module io's must be wrapped in `IO()`. +* The methods `asInput`, `asOutput`, and `flip` should be replaced by the `Input()`, `Output()`, and `Flipped()` object apply methods. + +## Unsupported constructs +* `Mem(..., orderedWrites)` is no longer supported, +* masked writes are only supported for `Mem[Vec[_]]`, +* Chisel3 Vecs must all have the same type, unlike with Chisel2. Use `MixedVec` (see [Bundles and Vecs](../explanations/bundles-and-vecs)) for Vecs where the elements are of different types. +* connections between `UInt` and `SInt` are illegal. +* the `Node` class and object no longer exist (the class should have been private in Chisel2) +* `printf()` is defined in the Chisel object and produces simulation printf()'s. +To use the Scala `Predef.printf()`, you need to qualify it with `Predef`. +* in Chisel2, bulk-connects `<>` with unconnected source components do not update connections from the unconnected components. +** In Chisel3, bulk-connects strictly adhere to last connection semantics and unconnected OUTPUTs will be connected to INPUTs resulting in the assignment of random values to those inputs. +* In Chisel3, adding hardware inside `BlackBox` for simulation is no longer supported. (#289) +* `ChiselError` is gone + * Change `ChiselError.error("error msg")` to `throw new Error("error msg")` + * Change `ChiselError.info("info msg")` to `println("info msg")` +* In Chisel3, subword assignments are not supported. [Alternative constructions exist](https://github.com/freechipsproject/chisel3/wiki/Cookbook#how-do-i-do-subword-assignment-assign-to-some-bits-in-a-uint) in Chisel3. + +## Further changes +* The clock signal was renamed from `clk` to `clock` in Chisel3. +* Change `getWidth()` to `getWidth` + +## Packaging +Chisel3 is implemented as several packages. +The core DSL is differentiated from utility or library classes and objects, testers, and interpreters. +The prime components of the Chisel3 front end (the DSL and library objects) are: +* coreMacros - source locators provide Chisel line numbers for `firrtl` detected errors, +* chiselFrontend - main DSL components, +* chisel3 - compiler driver, interface packages, compatibility layer. + +Due to the wonders of `sbt`, you need only declare a dependency on the chisel3 package, and the others will be downloaded as required. + +The `firrtl` compiler is distributed as a separate package, and release versions will also be downloaded automatically as required. +If you choose to integrate the compiler into your own toolchain, or you're working with the development (master) branch of chisel3, you should clone the [firrtl](https://github.com/ucb-bar/firrtl) repo +and follow the instructions for installing the `firrtl` compiler. + +The testers in Chisel3 are distributed as a separate package. +If you intend to use them in your tests, you will either need to clone the [chisel-testers](https://github.com/ucb-bar/chisel-testers) repo +or declare a dependency on the published version of the package. +See the `build.sbt` file in either the [chisel-template](https://github.com/ucb-bar/chisel-template) or [chisel-tutorial](https://github.com/ucb-bar/chisel-tutorial) +repos for examples of the latter. + +## Simulation +Chisel2 was capable of directly generating a `C++` simulation from the Chisel code, or a harness for use with a `vcs` simulation. +Chisel3 relies on [verilator](http://www.veripool.org/wiki/verilator) to generate the `C++` simulation from the Verilog output of `firrtl`. +See the [Chisel3 README](https://github.com/ucb-bar/chisel3) for directions on installing `verilator`. + +## Compile Options and Front End Checks (Strict vs. NotStrict) +Chisel3 introduces a formal specification for hardware circuit graphs: FIRRTL, +and Chisel3 itself (the Scala library implementing the Chisel DSL), is a relatively thin front end that generates FIRRTL. +Since the `firrtl` parser needs to validate FIRRTL input, most of the checks that were performed in Chisel2 were eliminated +from the initial Chisel3 front end (the DRY principle). + +However, this does impact the ability to provide detailed messages for error conditions that could be detected in the Chisel3 +front end. The decision was made to optionally enable stricter error checking (for connections and the use of raw types versus +hardware objects), based on specific imports. +This allows designs to move from less strict front end checks (largely compatible with Chisel2), to stricter checking, +on a file by file basis, by adjusting specific import statements. + +```scala + import chisel3.core.ExplicitCompileOptions.Strict +``` + +enables stricter connection and usage checks, while + +```scala + import chisel3.core.ExplicitCompileOptions.NotStrict +``` + +defers these checks to the `firrtl` compiler. + +By default, the `Chisel` compatibility layer, invoked by: + +```scala + import Chisel._ +``` + +implicitly defines the compile options as `chisel3.core.ExplicitCompileOptions.NotStrict` + +whereas the Chisel3 package, invoked by: + +```scala + import chisel3._ +``` + +implicitly defines the compile options as `chisel3.core.ExplicitCompileOptions.Strict` + +Again, these implicit compile options definitions may be overridden by explicit imports. + +Currently, the specific error checks (found in [CompileOptions.scala](https://github.com/ucb-bar/chisel3/blob/master/chiselFrontend/src/main/scala/chisel3/core/CompileOptions.scala)) are: + +```scala + trait CompileOptions { + // Should Bundle connections require a strict match of fields. + // If true and the same fields aren't present in both source and sink, a MissingFieldException, + // MissingLeftFieldException, or MissingRightFieldException will be thrown. + val connectFieldsMustMatch: Boolean + // When creating an object that takes a type argument, the argument must be unbound (a pure type). + val declaredTypeMustBeUnbound: Boolean + // Module IOs should be wrapped in an IO() to define their bindings before the reset of the module is defined. + val requireIOWrap: Boolean + // If a connection operator fails, don't try the connection with the operands (source and sink) reversed. + val dontTryConnectionsSwapped: Boolean + // If connection directionality is not explicit, do not use heuristics to attempt to determine it. + val dontAssumeDirectionality: Boolean + // Issue a deprecation warning if Data.{flip, asInput,asOutput} is used + // instead of Flipped, Input, or Output. + val deprecateOldDirectionMethods: Boolean + // Check that referenced Data have actually been declared. + val checkSynthesizable: Boolean + } +``` + +`chisel3.core.ExplicitCompileOptions.Strict` sets all CompileOptions fields to true and +`chisel3.core.ExplicitCompileOptions.NotStrict` sets them all to false. +Clients are free to define their own settings for these options. +Examples may be found in the test [CompileOptionsSpec](https://github.com/ucb-bar/chisel3/blob/master/src/test/scala/chiselTests/CompileOptionsTest.scala) diff --git a/docs/src/appendix/experimental-features.md b/docs/src/appendix/experimental-features.md new file mode 100644 index 00000000..ba1a028d --- /dev/null +++ b/docs/src/appendix/experimental-features.md @@ -0,0 +1,138 @@ +--- +layout: docs +title: "Experimental Features" +section: "chisel3" +--- + +Chisel has a number of new features that are worth checking out. This page is an informal list of these features and projects. + +### FixedPoint +FixedPoint numbers are basic *Data* type along side of UInt, SInt, etc. Most common math and logic operations +are supported. Chisel allows both the width and binary point to be inferred by the Firrtl compiler which can simplify +circuit descriptions. See [FixedPointSpec](https://github.com/freechipsproject/chisel3/tree/master/src/test/scala/chiselTests/FixedPointSpec.scala) + +### Module Variants +The standard Chisel *Module* requires a `val io = IO(...)`, the experimental package introduces several +new ways of defining Modules +- BaseModule: no contents, instantiable +- BlackBox extends BaseModule +- UserDefinedModule extends BaseModule: this module can contain Chisel RTL. No default clock or reset lines. No default IO. - User should be able to specify non-io ports, ideally multiple of them. +- ImplicitModule extends UserModule: has clock, reset, and io, essentially current Chisel Module. +- RawModule: will be the user-facing version of UserDefinedModule +- Module: type-aliases to ImplicitModule, the user-facing version of ImplicitModule. + +### Bundle Literals + +Bundle literals can be constructed via an experimental import: + +```scala mdoc +import chisel3._ +import chisel3.experimental.BundleLiterals._ + +class MyBundle extends Bundle { + val a = UInt(8.W) + val b = Bool() +} + +class Example extends RawModule { + val out = IO(Output(new MyBundle)) + out := (new MyBundle).Lit(_.a -> 8.U, _.b -> true.B) +} +``` + +```scala mdoc:verilog +chisel3.stage.ChiselStage.emitVerilog(new Example) +``` + +Partial specification is allowed, defaulting any unconnected fields to 0 (regardless of type). + +```scala mdoc +class Example2 extends RawModule { + val out = IO(Output(new MyBundle)) + out := (new MyBundle).Lit(_.b -> true.B) +} +``` + +```scala mdoc:verilog +chisel3.stage.ChiselStage.emitVerilog(new Example2) +``` + +Bundle literals can also be nested arbitrarily. + +```scala mdoc +class ChildBundle extends Bundle { + val foo = UInt(8.W) +} + +class ParentBundle extends Bundle { + val a = UInt(8.W) + val b = new ChildBundle +} + +class Example3 extends RawModule { + val out = IO(Output(new ParentBundle)) + out := (new ParentBundle).Lit(_.a -> 123.U, _.b -> (new ChildBundle).Lit(_.foo -> 42.U)) +} +``` + +```scala mdoc:verilog +chisel3.stage.ChiselStage.emitVerilog(new Example3) +``` + +Vec literals are not yet supported. + +### Interval Type + +**Intervals** are a new experimental numeric type that comprises UInt, SInt and FixedPoint numbers. +It augments these types with range information, i.e. upper and lower numeric bounds. +This information can be used to exercise tighter programmatic control over the ultimate widths of +signals in the final circuit. The **Firrtl** compiler can infer this range information based on +operations and earlier values in the circuit. Intervals support all the ordinary bit and arithmetic operations +associated with UInt, SInt, and FixedPoint and adds the following methods for manipulating the range of +a **source** Interval with the IntervalRange of **target** Interval + +#### Clip -- Fit the value **source** into the IntervalRange of **target**, saturate if out of bounds +The clip method applied to an interval creates a new interval based on the argument to clip, +and constructs the necessary hardware so that the source Interval's value will be mapped into the new Interval. +Values that are outside the result range will be pegged to either maximum or minimum of result range as appropriate. + +> Generates necessary hardware to clip values, values greater than range are set to range.high, values lower than range are set to range min. + +#### Wrap -- Fit the value **source** into the IntervalRange of **target**, wrapping around if out of bounds +The wrap method applied to an interval creates a new interval based on the argument to wrap, +and constructs the necessary +hardware so that the source Interval's value will be mapped into the new Interval. +Values that are outside the result range will be wrapped until they fall within the result range. + +> Generates necessary hardware to wrap values, values greater than range are set to range.high, values lower than range are set to range min. + +> Does not handle out of range values that are less than half the minimum or greater than twice maximum + +#### Squeeze -- Fit the value **source** into the smallest IntervalRange based on source and target. +The squeeze method applied to an interval creates a new interval based on the argument to clip, the two ranges must overlap +behavior of squeeze with inputs outside of the produced range is undefined. + +> Generates no hardware, strictly a sizing operation + +##### Range combinations + +| Condition | A.clip(B) | A.wrap(B) | A.squeeze(B) | +| --------- | --------------- | --------------- | --------------- | +| A === B | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | +| A contains B | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | +| B contains A | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | +| A min < B min, A max in B | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | +| A min in B, A max > B max | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | +| A strictly less than B | error | error | error | +| A strictly greater than B | error | error | error | + + +#### Applying binary point operators to an Interval + +Consider a Interval with a binary point of 3: aaa.bbb + +| operation | after operation | binary point | lower | upper | meaning | +| --------- | --------------- | ------------ | ----- | ----- | ------- | +| setBinaryPoint(2) | aaa.bb | 2 | X | X | set the precision | +| shiftLeftBinaryPoint(2) | a.aabbb | 5 | X | X | increase the precision | +| shiftRighBinaryPoint(2) | aaaa.b | 1 | X | X | reduce the precision | diff --git a/docs/src/appendix/upgrading-from-scala-2-11.md b/docs/src/appendix/upgrading-from-scala-2-11.md new file mode 100644 index 00000000..26e2d252 --- /dev/null +++ b/docs/src/appendix/upgrading-from-scala-2-11.md @@ -0,0 +1,88 @@ +--- +layout: docs +title: "Upgrading From Scala 2.11" +section: "chisel3" +redirect_from: + - /chisel3/upgrading-from-scala-2-11.html +--- + + +```scala mdoc:invisible +import chisel3._ +``` + + +## Upgrading From Scala 2.11 to 2.12 + +As the latest (and probably last) release of Scala 2.11 (2.11.12) was released on 2 November 2017, the time has come to deprecate support for Scala 2.11. +Chisel 3.4 is the last version of Chisel that will support Scala 2.11, so users should upgrade to Scala 2.12 +This document is intended to help guide Chisel users through this process; both the "Why?" and the "How?". + +### Scala Versioning + + + +Scala versions have the following structure: `2.X.Y` where `X` is the _major version_ and `Y` is the _minor version_. +Note that while we keep the leading `2` constant, there is a project, [Dotty](https://dotty.epfl.ch/), that is slated to become Scala 3. + +Scala maintains both source and binary compatiblity between minor versions, but not between major versions. +Binary compatibility is defined at the level of the Java Byte Code (the `.class` or `.jar` files compiled from `.scala`). +This means that Scala projects that support multiple major versions of Scala must be compiled and published for each supported version. +When publishing artifacts to Maven repositories, this manifests as an appendix on the _Artifact ID_. +Taking Chisel v3.3.2 as an example, the "Artifact ID" is ["chisel3_2.12"](https://search.maven.org/artifact/edu.berkeley.cs/chisel3_2.12) +for Scala 2.12, and ["chisel3_2.11"](https://search.maven.org/artifact/edu.berkeley.cs/chisel3_2.11) for Scala 2.11. + +For more information, see the documentation on the Scala website: +* [Binary Compatibility of Scala Releases](https://docs.scala-lang.org/overviews/core/binary-compatibility-of-scala-releases.html) +* [Binary Compatibility for Library Authoers](https://docs.scala-lang.org/overviews/core/binary-compatibility-for-library-authors.html) + + +### How to Upgrade + +For most users, this is as simple as changing the `scalaVersion` field in your `build.sbt`: +```scala +scalaVersion := "2.11.12" +``` +Becomes +```scala +scalaVersion := "2.12.12" +``` +Now, the next time you run SBT, it will be using the Scala 2.12 version of Chisel 3 (as well as any other dependencies you have). + +### Common Issues + +As mentioned in the [previous section](#scala-versioning), Scala does *not* maintain source compatibilty between major versions. +Put another way, sometimes they break things in backwards incompatible ways. +This section includes some common issues that Chisel users run into and how to fix them. + +For complete information about changes, please see the [release notes for Scala 2.12.0](https://www.scala-lang.org/news/2.12.0/). + +#### Value is not a member of chisel3.Bundle + +The most common problem for Chisel users upgrading from Scala 2.11 to 2.12 is a change in Scala type inference. +This usually occurs in the context of `io` `Bundles` in `Modules`, given: +```scala mdoc:silent +class Foo extends Module { + val io = IO(new Bundle { + val in = Input(Bool()) + val out = Output(Bool()) + }) + + io.out := ~io.in +} +``` +You may see an error that says somethign like "value out is not a member of chisel3.Bundle": +``` +[error] /workspace/src/main/scala/gcd/Foo.scala:9:6: value out is not a member of chisel3.Bundle +[error] io.out := ~io.in +[error] ^ +[error] /workspace/src/main/scala/gcd/Foo.scala:9:17: value in is not a member of chisel3.Bundle +[error] io.out := ~io.in +[error] ^ +[error] two errors found +``` +This can be worked around by adding `-Xsource:2.11` to your `scalacOptions`. +This is most commonly set in your `build.sbt`. +For an example, see the [chisel-template's build.sbt](https://github.com/freechipsproject/chisel-template/blob/11f6ca470120908d167cb8dc3241953eb31d0acb/build.sbt#L10). + + diff --git a/docs/src/explanations/blackboxes.md b/docs/src/explanations/blackboxes.md index a8d5fe03..4ecd1ea0 100644 --- a/docs/src/explanations/blackboxes.md +++ b/docs/src/explanations/blackboxes.md @@ -9,7 +9,7 @@ for hardware constructs that cannot be described in Chisel and for connecting to Modules defined as a `BlackBox` will be instantiated in the generated Verilog, but no code will be generated to define the behavior of module. -Unlike Module, `BlackBox` has no implicit clock and reset. +Unlike Module, `BlackBox` has no implicit clock and reset. `BlackBox`'s clock and reset ports must be explicitly declared and connected to input signals. Ports declared in the IO Bundle will be generated with the requested name (ie. no preceding `io_`). @@ -34,7 +34,7 @@ class IBUFDS extends BlackBox(Map("DIFF_TERM" -> "TRUE", } class Top extends Module { - val io = IO(new Bundle{}) + val io = IO(new Bundle {}) val ibufds = Module(new IBUFDS) // connecting one of IBUFDS's input clock ports to Top's clock signal ibufds.io.I := clock @@ -52,12 +52,14 @@ IBUFDS #(.DIFF_TERM("TRUE"), .IOSTANDARD("DEFAULT")) ibufds ( ``` ### Providing Implementations for Blackboxes + Chisel provides the following ways of delivering the code underlying the blackbox. Consider the following blackbox that adds two real numbers together. The numbers are represented in chisel3 as 64-bit unsigned integers. + ```scala mdoc:silent:reset import chisel3._ class BlackBoxRealAdd extends BlackBox { - val io = IO(new Bundle() { + val io = IO(new Bundle { val in1 = Input(UInt(64.W)) val in2 = Input(UInt(64.W)) val out = Output(UInt(64.W)) @@ -66,6 +68,7 @@ class BlackBoxRealAdd extends BlackBox { ``` The implementation is described by the following verilog + ```verilog module BlackBoxRealAdd( input [63:0] in1, @@ -73,39 +76,43 @@ module BlackBoxRealAdd( output reg [63:0] out ); always @* begin - out <= $realtobits($bitstoreal(in1) + $bitstoreal(in2)); + out <= $realtobits($bitstoreal(in1) + $bitstoreal(in2)); end endmodule ``` ### Blackboxes with Verilog in a Resource File -In order to deliver the verilog snippet above to the backend simulator, chisel3 provides the following tools based on the chisel/firrtl [annotation system](../explanations/annotations). Add the trait ```HasBlackBoxResource``` to the declaration, and then call a function in the body to say where the system can find the verilog. The Module now looks like -```mdoc scala:silent:reset + +In order to deliver the verilog snippet above to the backend simulator, chisel3 provides the following tools based on the chisel/firrtl [annotation system](../explanations/annotations). Add the trait `HasBlackBoxResource` to the declaration, and then call a function in the body to say where the system can find the verilog. The Module now looks like + +```scala mdoc:silent:reset +import chisel3._ +import chisel3.util.HasBlackBoxResource + class BlackBoxRealAdd extends BlackBox with HasBlackBoxResource { - val io = IO(new Bundle() { + val io = IO(new Bundle { val in1 = Input(UInt(64.W)) val in2 = Input(UInt(64.W)) val out = Output(UInt(64.W)) }) - setResource("/real_math.v") + addResource("/real_math.v") } ``` -The verilog snippet above gets put into a resource file names ```real_math.v```. What is a resource file? It comes from + +The verilog snippet above gets put into a resource file names `real_math.v`. What is a resource file? It comes from a java convention of keeping files in a project that are automatically included in library distributions. In a typical chisel3 project, see [chisel-template](https://github.com/ucb-bar/chisel-template), this would be a directory in the - source hierarchy -``` -src/main/resources/real_math.v -``` + source hierarchy: `src/main/resources/real_math.v`. ### Blackboxes with In-line Verilog -It is also possible to place this verilog directly in the scala source. Instead of ```HasBlackBoxResource``` use - ```HasBlackBoxInline``` and instead of ```setResource``` use ```setInline```. The code will look like this. +It is also possible to place this verilog directly in the scala source. Instead of `HasBlackBoxResource` use + `HasBlackBoxInline` and instead of `setResource` use `setInline`. The code will look like this. + ```scala mdoc:silent:reset import chisel3._ import chisel3.util.HasBlackBoxInline class BlackBoxRealAdd extends BlackBox with HasBlackBoxInline { - val io = IO(new Bundle() { + val io = IO(new Bundle { val in1 = Input(UInt(64.W)) val in2 = Input(UInt(64.W)) val out = Output(UInt(64.W)) @@ -123,15 +130,16 @@ class BlackBoxRealAdd extends BlackBox with HasBlackBoxInline { """.stripMargin) } ``` -This technique will copy the inline verilog into the target directory under the name ```BlackBoxRealAdd.v``` + +This technique will copy the inline verilog into the target directory under the name `BlackBoxRealAdd.v` ### Under the Hood This mechanism of delivering verilog content to the testing backends is implemented via chisel/firrtl annotations. The -two methods, inline and resource, are two kinds of annotations that are created via the ```setInline``` and -```setResource``` methods calls. Those annotations are passed through to the chisel-testers which in turn passes them +two methods, inline and resource, are two kinds of annotations that are created via the `setInline` and +`setResource` methods calls. Those annotations are passed through to the chisel-testers which in turn passes them on to firrtl. The default firrtl verilog compilers have a pass that detects the annotations and moves the files or inline test into the build directory. For each unique file added, the transform adds a line to a file -black_box_verilog_files.f, this file is added to the command line constructed for verilator or vcs to inform them where +`black_box_verilog_files.f`, this file is added to the command line constructed for verilator or vcs to inform them where to look. The [dsptools project](https://github.com/ucb-bar/dsptools) is a good example of using this feature to build a real number simulation tester based on black boxes. @@ -144,4 +152,4 @@ construct scala implementations of the black boxes. The scala implementation co passed down to the interpreter by the execution harness. The interpreter is a scala simulation tester. Once again the dsptools project uses this mechanism and is a good place to look at it. > It is planned that the BlackBoxFactory will be replaced by integration with the annotation based blackbox methods ->stuff soon. +> stuff soon. diff --git a/docs/src/explanations/bundles-and-vecs.md b/docs/src/explanations/bundles-and-vecs.md index dcac31cd..bac9393a 100644 --- a/docs/src/explanations/bundles-and-vecs.md +++ b/docs/src/explanations/bundles-and-vecs.md @@ -3,14 +3,12 @@ layout: docs title: "Bundles and Vecs" section: "chisel3" --- -``` - -``` `Bundle` and `Vec` are classes that allow the user to expand the set of Chisel datatypes with aggregates of other types. Bundles group together several named fields of potentially different types into a coherent unit, much like a `struct` in C. Users define their own bundles by defining a class as a subclass of `Bundle`. + ```scala mdoc:silent import chisel3._ class MyFloat extends Bundle { @@ -25,24 +23,12 @@ class ModuleWithFloatWire extends RawModule { } ``` -> Currently, there is no way to create a bundle literal like ```8.U``` for ```UInt```s. Therefore, in order to create ->literals for bundles, we must declare a [[wire|Combinational-Circuits#wires]] of that bundle type, and then assign ->values to it. We are working on a way to declare bundle literals without requiring the creation of a Wire node and ->assigning to it. +You can create literal Bundles using the experimental [Bundle Literals](../appendix/experimental-features#bundle-literals) feature. -```scala mdoc:silent -class ModuleWithFloatConstant extends RawModule { - // Floating point constant. - val floatConst = Wire(new MyFloat) - floatConst.sign := true.B - floatConst.exponent := 10.U - floatConst.significand := 128.U -} -``` - -A Scala convention is to capitalize the name of new classes and we suggest you follow that convention in Chisel too. +Scala convention is to name classes using UpperCamelCase, and we suggest you follow that convention in your Chisel code. Vecs create an indexable vector of elements, and are constructed as follows: + ```scala mdoc:silent class ModuleWithVec extends RawModule { // Vector of 5 23-bit signed integers. @@ -63,6 +49,7 @@ superclass, `Data`. Every object that ultimately inherits from Bundles and Vecs can be arbitrarily nested to build complex data structures: + ```scala mdoc:silent class BigBundle extends Bundle { // Vector of 5 23-bit signed integers. @@ -152,6 +139,7 @@ Note that in the vast majority of cases, **this is not required** as Chisel can automatically. Here is an example of a parametrized bundle (`ExampleBundle`) that features a custom `cloneType`. + ```scala mdoc:silent class ExampleBundle(a: Int, b: Int) extends Bundle { val foo = UInt(a.W) diff --git a/docs/src/wiki-deprecated/chisel3-vs-chisel2.md b/docs/src/wiki-deprecated/chisel3-vs-chisel2.md deleted file mode 100644 index bfd20348..00000000 --- a/docs/src/wiki-deprecated/chisel3-vs-chisel2.md +++ /dev/null @@ -1,203 +0,0 @@ ---- -layout: docs -title: "Chisel3 vs. Chisel2" -section: "chisel3" ---- - -# Chisel3 vs Chisel2 - -## Chisel2 Migration -For those moving from Chisel2, there were some backwards incompatible changes -and your RTL needs to be modified to work with Chisel3. The required -modifications are: - - - Wire declaration style: - ```scala - val wire = UInt(width = 15) - ``` - becomes (in Chisel3): - ```scala - val wire = Wire(UInt(15.W)) - ``` - - - I/O declaration style: - ```scala - val done = Bool(OUTPUT) - ``` - becomes (in Chisel3): - ```scala - val wire = Output(Bool()) - ``` - - - Sequential memories: - ```scala - val addr = Reg(UInt()) - val mem = Mem(UInt(8.W), 1024, seqRead = true) - val dout = when(enable) { mem(addr) } - ``` - becomes (in Chisel3): - ```scala - val addr = UInt() - val mem = SyncReadMem(1024, UInt(8.W)) - val dout = mem.read(addr, enable) - ``` - - Notice the address register is now internal to the SyncReadMem(), but the data - will still return on the subsequent cycle. - - - Generating Verilog with - ```scala - object Hello { - def main(args: Array[String]): Unit = { - chiselMain(Array("--backend", "v"), () => Module(new Hello())) - } - } - ``` - becomes (in Chisel3): - ```scala - object Hello { - def main(args: Array[String]): Unit = { - chisel3.Driver.execute(Array[String](), () => new Hello()) - } - } - ``` - - - Package changes: - - Chisel.log2Ceil -> chisel3.util.log2Ceil - - BitPat - - Decoupled is also in chisel3.util - -Please refer to the [Chisel3 compatibility section](https://github.com/ucb-bar/chisel#chisel3) -for instructions on preparing your Chisel2 designs for Chisel3. - -## Deprecated Usage -* `Vec(Reg)` should be replaced with `Reg(Vec)`, -* type-only vals (no associated data) must be wrapped in a `Wire()` if they will be the destination of a wiring operation (":=" or " < >"), -* masked bit patterns ('b??') should be created using BitPat(), not UInt() or Bits(), -* the `clone` method required for parameterized Bundles has been renamed `cloneType`, -* the con and alt inputs to a Mux must be type-compatible - both signed or both unsigned, -* bulk-connection to a node that has been procedurally assigned-to is illegal, -* `!=` is deprecated, use `=/=` instead, -* use `SyncReadMem(...)` instead of `Mem(..., seqRead)`, -* use `SyncReadMem(n:Int, out: => T)` instead of `SyncReadMem(out: => T, n:Int)`, -* use `SyncReadMem(...)` instead of `SeqMem(...)`, -* use `Mem(n:Int, t:T)` instead of `Mem(out:T, n:Int)`, -* use `Vec(n:Int, gen: => T)` instead of `Vec(gen: => T, n:Int)`, -* module io's must be wrapped in `IO()`. -* The methods `asInput`, `asOutput`, and `flip` should be replaced by the `Input()`, `Output()`, and `Flipped()` object apply methods. - -## Unsupported constructs -* `Mem(..., orderedWrites)` is no longer supported, -* masked writes are only supported for `Mem[Vec[_]]`, -* Chisel3 Vecs must all have the same type, unlike with Chisel2. Use `MixedVec` (see [Bundles and Vecs](../explanations/bundles-and-vecs)) for Vecs where the elements are of different types. -* connections between `UInt` and `SInt` are illegal. -* the `Node` class and object no longer exist (the class should have been private in Chisel2) -* `printf()` is defined in the Chisel object and produces simulation printf()'s. -To use the Scala `Predef.printf()`, you need to qualify it with `Predef`. -* in Chisel2, bulk-connects `<>` with unconnected source components do not update connections from the unconnected components. -** In Chisel3, bulk-connects strictly adhere to last connection semantics and unconnected OUTPUTs will be connected to INPUTs resulting in the assignment of random values to those inputs. -* In Chisel3, adding hardware inside `BlackBox` for simulation is no longer supported. (#289) -* `ChiselError` is gone - * Change `ChiselError.error("error msg")` to `throw new Error("error msg")` - * Change `ChiselError.info("info msg")` to `println("info msg")` -* In Chisel3, subword assignments are not supported. [Alternative constructions exist](https://github.com/freechipsproject/chisel3/wiki/Cookbook#how-do-i-do-subword-assignment-assign-to-some-bits-in-a-uint) in Chisel3. - -## Further changes -* The clock signal was renamed from `clk` to `clock` in Chisel3. -* Change `getWidth()` to `getWidth` - -## Packaging -Chisel3 is implemented as several packages. -The core DSL is differentiated from utility or library classes and objects, testers, and interpreters. -The prime components of the Chisel3 front end (the DSL and library objects) are: -* coreMacros - source locators provide Chisel line numbers for `firrtl` detected errors, -* chiselFrontend - main DSL components, -* chisel3 - compiler driver, interface packages, compatibility layer. - -Due to the wonders of `sbt`, you need only declare a dependency on the chisel3 package, and the others will be downloaded as required. - -The `firrtl` compiler is distributed as a separate package, and release versions will also be downloaded automatically as required. -If you choose to integrate the compiler into your own toolchain, or you're working with the development (master) branch of chisel3, you should clone the [firrtl](https://github.com/ucb-bar/firrtl) repo -and follow the instructions for installing the `firrtl` compiler. - -The testers in Chisel3 are distributed as a separate package. -If you intend to use them in your tests, you will either need to clone the [chisel-testers](https://github.com/ucb-bar/chisel-testers) repo -or declare a dependency on the published version of the package. -See the `build.sbt` file in either the [chisel-template](https://github.com/ucb-bar/chisel-template) or [chisel-tutorial](https://github.com/ucb-bar/chisel-tutorial) -repos for examples of the latter. - -## Simulation -Chisel2 was capable of directly generating a `C++` simulation from the Chisel code, or a harness for use with a `vcs` simulation. -Chisel3 relies on [verilator](http://www.veripool.org/wiki/verilator) to generate the `C++` simulation from the Verilog output of `firrtl`. -See the [Chisel3 README](https://github.com/ucb-bar/chisel3) for directions on installing `verilator`. - -## Compile Options and Front End Checks (Strict vs. NotStrict) -Chisel3 introduces a formal specification for hardware circuit graphs: FIRRTL, -and Chisel3 itself (the Scala library implementing the Chisel DSL), is a relatively thin front end that generates FIRRTL. -Since the `firrtl` parser needs to validate FIRRTL input, most of the checks that were performed in Chisel2 were eliminated -from the initial Chisel3 front end (the DRY principle). - -However, this does impact the ability to provide detailed messages for error conditions that could be detected in the Chisel3 -front end. The decision was made to optionally enable stricter error checking (for connections and the use of raw types versus -hardware objects), based on specific imports. -This allows designs to move from less strict front end checks (largely compatible with Chisel2), to stricter checking, -on a file by file basis, by adjusting specific import statements. - -```scala - import chisel3.core.ExplicitCompileOptions.Strict -``` - -enables stricter connection and usage checks, while - -```scala - import chisel3.core.ExplicitCompileOptions.NotStrict -``` - -defers these checks to the `firrtl` compiler. - -By default, the `Chisel` compatibility layer, invoked by: - -```scala - import Chisel._ -``` - -implicitly defines the compile options as `chisel3.core.ExplicitCompileOptions.NotStrict` - -whereas the Chisel3 package, invoked by: - -```scala - import chisel3._ -``` - -implicitly defines the compile options as `chisel3.core.ExplicitCompileOptions.Strict` - -Again, these implicit compile options definitions may be overridden by explicit imports. - -Currently, the specific error checks (found in [CompileOptions.scala](https://github.com/ucb-bar/chisel3/blob/master/chiselFrontend/src/main/scala/chisel3/core/CompileOptions.scala)) are: - -```scala - trait CompileOptions { - // Should Bundle connections require a strict match of fields. - // If true and the same fields aren't present in both source and sink, a MissingFieldException, - // MissingLeftFieldException, or MissingRightFieldException will be thrown. - val connectFieldsMustMatch: Boolean - // When creating an object that takes a type argument, the argument must be unbound (a pure type). - val declaredTypeMustBeUnbound: Boolean - // Module IOs should be wrapped in an IO() to define their bindings before the reset of the module is defined. - val requireIOWrap: Boolean - // If a connection operator fails, don't try the connection with the operands (source and sink) reversed. - val dontTryConnectionsSwapped: Boolean - // If connection directionality is not explicit, do not use heuristics to attempt to determine it. - val dontAssumeDirectionality: Boolean - // Issue a deprecation warning if Data.{flip, asInput,asOutput} is used - // instead of Flipped, Input, or Output. - val deprecateOldDirectionMethods: Boolean - // Check that referenced Data have actually been declared. - val checkSynthesizable: Boolean - } -``` - -`chisel3.core.ExplicitCompileOptions.Strict` sets all CompileOptions fields to true and -`chisel3.core.ExplicitCompileOptions.NotStrict` sets them all to false. -Clients are free to define their own settings for these options. -Examples may be found in the test [CompileOptionsSpec](https://github.com/ucb-bar/chisel3/blob/master/src/test/scala/chiselTests/CompileOptionsTest.scala) diff --git a/docs/src/wiki-deprecated/experimental-features.md b/docs/src/wiki-deprecated/experimental-features.md deleted file mode 100644 index d36b2946..00000000 --- a/docs/src/wiki-deprecated/experimental-features.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -layout: docs -title: "Experimental Features" -section: "chisel3" ---- - -Chisel has a number of new features that are worth checking out. This page is an informal list of these features and projects. - -### FixedPoint -FixedPoint numbers are basic *Data* type along side of UInt, SInt, etc. Most common math and logic operations -are supported. Chisel allows both the width and binary point to be inferred by the Firrtl compiler which can simplify -circuit descriptions. See [FixedPointSpec](https://github.com/freechipsproject/chisel3/tree/master/src/test/scala/chiselTests/FixedPointSpec.scala) - -### Module Variants -The standard Chisel *Module* requires a ```val io = IO(...)```, the experimental package introduces several -new ways of defining Modules -- BaseModule: no contents, instantiable -- BlackBox extends BaseModule -- UserDefinedModule extends BaseModule: this module can contain Chisel RTL. No default clock or reset lines. No default IO. - User should be able to specify non-io ports, ideally multiple of them. -- ImplicitModule extends UserModule: has clock, reset, and io, essentially current Chisel Module. -- RawModule: will be the user-facing version of UserDefinedModule -- Module: type-aliases to ImplicitModule, the user-facing version of ImplicitModule. - -### Bundle Literals - -Chisel 3.2 introduces an experimental mechanism for Bundle literals in #820, but this feature is largely incomplete and not ready for user code yet. The following is provided as documentation for library writers who want to take a stab at using this mechanism for their library's bundles. - -```mdoc scala -class MyBundle extends Bundle { - val a = UInt(8.W) - val b = Bool() - - // Bundle literal constructor code, which will be auto-generated using macro annotations in - // the future. - import chisel3.core.BundleLitBinding - import chisel3.internal.firrtl.{ULit, Width} - - // Full bundle literal constructor - def Lit(aVal: UInt, bVal: Bool): MyBundle = { - val clone = cloneType - clone.selfBind(BundleLitBinding(Map( - clone.a -> litArgOfBits(aVal), - clone.b -> litArgOfBits(bVal) - ))) - clone - } - - // Partial bundle literal constructor - def Lit(aVal: UInt): MyBundle = { - val clone = cloneType - clone.selfBind(BundleLitBinding(Map( - clone.a -> litArgOfBits(aVal) - ))) - clone - } -} -``` - -Example usage: - -```scala -val outsideBundleLit = (new MyBundle).Lit(42.U, true.B) -``` - -### Interval Type - -**Intervals** are a new experimental numeric type that comprises UInt, SInt and FixedPoint numbers. -It augments these types with range information, i.e. upper and lower numeric bounds. -This information can be used to exercise tighter programmatic control over the ultimate widths of -signals in the final circuit. The **Firrtl** compiler can infer this range information based on -operations and earlier values in the circuit. Intervals support all the ordinary bit and arithmetic operations -associated with UInt, SInt, and FixedPoint and adds the following methods for manipulating the range of -a **source** Interval with the IntervalRange of **target** Interval - -#### Clip -- Fit the value **source** into the IntervalRange of **target**, saturate if out of bounds -The clip method applied to an interval creates a new interval based on the argument to clip, -and constructs the necessary hardware so that the source Interval's value will be mapped into the new Interval. -Values that are outside the result range will be pegged to either maximum or minimum of result range as appropriate. - -> Generates necessary hardware to clip values, values greater than range are set to range.high, values lower than range are set to range min. - -#### Wrap -- Fit the value **source** into the IntervalRange of **target**, wrapping around if out of bounds -The wrap method applied to an interval creates a new interval based on the argument to wrap, -and constructs the necessary -hardware so that the source Interval's value will be mapped into the new Interval. -Values that are outside the result range will be wrapped until they fall within the result range. - -> Generates necessary hardware to wrap values, values greater than range are set to range.high, values lower than range are set to range min. - -> Does not handle out of range values that are less than half the minimum or greater than twice maximum - -#### Squeeze -- Fit the value **source** into the smallest IntervalRange based on source and target. -The squeeze method applied to an interval creates a new interval based on the argument to clip, the two ranges must overlap -behavior of squeeze with inputs outside of the produced range is undefined. - -> Generates no hardware, strictly a sizing operation - -##### Range combinations - -| Condition | A.clip(B) | A.wrap(B) | A.squeeze(B) | -| --------- | --------------- | --------------- | --------------- | -| A === B | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | -| A contains B | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | -| B contains A | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | -| A min < B min, A max in B | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | -| A min in B, A max > B max | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | -| A strictly less than B | error | error | error | -| A strictly greater than B | error | error | error | - - -#### Applying binary point operators to an Interval - -Consider a Interval with a binary point of 3: aaa.bbb - -| operation | after operation | binary point | lower | upper | meaning | -| --------- | --------------- | ------------ | ----- | ----- | ------- | -| setBinaryPoint(2) | aaa.bb | 2 | X | X | set the precision | -| shiftLeftBinaryPoint(2) | a.aabbb | 5 | X | X | increase the precision | -| shiftRighBinaryPoint(2) | aaaa.b | 1 | X | X | reduce the precision | diff --git a/docs/src/wiki-deprecated/upgrading-from-scala-2-11.md b/docs/src/wiki-deprecated/upgrading-from-scala-2-11.md deleted file mode 100644 index 26e2d252..00000000 --- a/docs/src/wiki-deprecated/upgrading-from-scala-2-11.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -layout: docs -title: "Upgrading From Scala 2.11" -section: "chisel3" -redirect_from: - - /chisel3/upgrading-from-scala-2-11.html ---- - - -```scala mdoc:invisible -import chisel3._ -``` - - -## Upgrading From Scala 2.11 to 2.12 - -As the latest (and probably last) release of Scala 2.11 (2.11.12) was released on 2 November 2017, the time has come to deprecate support for Scala 2.11. -Chisel 3.4 is the last version of Chisel that will support Scala 2.11, so users should upgrade to Scala 2.12 -This document is intended to help guide Chisel users through this process; both the "Why?" and the "How?". - -### Scala Versioning - - - -Scala versions have the following structure: `2.X.Y` where `X` is the _major version_ and `Y` is the _minor version_. -Note that while we keep the leading `2` constant, there is a project, [Dotty](https://dotty.epfl.ch/), that is slated to become Scala 3. - -Scala maintains both source and binary compatiblity between minor versions, but not between major versions. -Binary compatibility is defined at the level of the Java Byte Code (the `.class` or `.jar` files compiled from `.scala`). -This means that Scala projects that support multiple major versions of Scala must be compiled and published for each supported version. -When publishing artifacts to Maven repositories, this manifests as an appendix on the _Artifact ID_. -Taking Chisel v3.3.2 as an example, the "Artifact ID" is ["chisel3_2.12"](https://search.maven.org/artifact/edu.berkeley.cs/chisel3_2.12) -for Scala 2.12, and ["chisel3_2.11"](https://search.maven.org/artifact/edu.berkeley.cs/chisel3_2.11) for Scala 2.11. - -For more information, see the documentation on the Scala website: -* [Binary Compatibility of Scala Releases](https://docs.scala-lang.org/overviews/core/binary-compatibility-of-scala-releases.html) -* [Binary Compatibility for Library Authoers](https://docs.scala-lang.org/overviews/core/binary-compatibility-for-library-authors.html) - - -### How to Upgrade - -For most users, this is as simple as changing the `scalaVersion` field in your `build.sbt`: -```scala -scalaVersion := "2.11.12" -``` -Becomes -```scala -scalaVersion := "2.12.12" -``` -Now, the next time you run SBT, it will be using the Scala 2.12 version of Chisel 3 (as well as any other dependencies you have). - -### Common Issues - -As mentioned in the [previous section](#scala-versioning), Scala does *not* maintain source compatibilty between major versions. -Put another way, sometimes they break things in backwards incompatible ways. -This section includes some common issues that Chisel users run into and how to fix them. - -For complete information about changes, please see the [release notes for Scala 2.12.0](https://www.scala-lang.org/news/2.12.0/). - -#### Value is not a member of chisel3.Bundle - -The most common problem for Chisel users upgrading from Scala 2.11 to 2.12 is a change in Scala type inference. -This usually occurs in the context of `io` `Bundles` in `Modules`, given: -```scala mdoc:silent -class Foo extends Module { - val io = IO(new Bundle { - val in = Input(Bool()) - val out = Output(Bool()) - }) - - io.out := ~io.in -} -``` -You may see an error that says somethign like "value out is not a member of chisel3.Bundle": -``` -[error] /workspace/src/main/scala/gcd/Foo.scala:9:6: value out is not a member of chisel3.Bundle -[error] io.out := ~io.in -[error] ^ -[error] /workspace/src/main/scala/gcd/Foo.scala:9:17: value in is not a member of chisel3.Bundle -[error] io.out := ~io.in -[error] ^ -[error] two errors found -``` -This can be worked around by adding `-Xsource:2.11` to your `scalacOptions`. -This is most commonly set in your `build.sbt`. -For an example, see the [chisel-template's build.sbt](https://github.com/freechipsproject/chisel-template/blob/11f6ca470120908d167cb8dc3241953eb31d0acb/build.sbt#L10). - - -- cgit v1.2.3 From e3b6cf4e7230ce12174832fca48d1384553b02f6 Mon Sep 17 00:00:00 2001 From: John's Brew Date: Fri, 5 Feb 2021 19:38:44 +0100 Subject: Add file line to source link from scaladoc (#1776) Signed-off-by: Jean Bruant --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 2400233a..fc19d2ad 100644 --- a/build.sbt +++ b/build.sbt @@ -196,7 +196,7 @@ lazy val chisel = (project in file(".")). } else { s"v${version.value}" } - s"https://github.com/freechipsproject/chisel3/tree/$branch/€{FILE_PATH}.scala" + s"https://github.com/chipsalliance/chisel3/tree/$branch€{FILE_PATH_EXT}#L€{FILE_LINE}" } ) ) -- cgit v1.2.3 From 2ed343e2305b7c22000f3f46fa81d73a369907eb Mon Sep 17 00:00:00 2001 From: Vladimir Milovanović Date: Mon, 8 Feb 2021 20:55:48 +0100 Subject: Parametrized Mem- & SyncReadMem-based implementation of the Queue class (#1740) * Added SyncReadMem-based implementation of the Queue class * Rework of the parametrized Queue class SyncReadMem-based implementation * Modification of the parametrized Queue class SyncReadMem-based implementation * Limiting the visibility of the read address for SyncReadMem-based Queue Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- src/main/scala/chisel3/util/Decoupled.scala | 26 ++++++++++---- src/test/scala/chiselTests/QueueSpec.scala | 56 ++++++++++++++--------------- 2 files changed, 47 insertions(+), 35 deletions(-) diff --git a/src/main/scala/chisel3/util/Decoupled.scala b/src/main/scala/chisel3/util/Decoupled.scala index 032d731d..006001ec 100644 --- a/src/main/scala/chisel3/util/Decoupled.scala +++ b/src/main/scala/chisel3/util/Decoupled.scala @@ -186,6 +186,7 @@ class QueueIO[T <: Data](private val gen: T, val entries: Int) extends Bundle * combinationally coupled. * @param flow True if the inputs can be consumed on the same cycle (the inputs "flow" through the queue immediately). * The ''valid'' signals are coupled. + * @param useSyncReadMem True uses SyncReadMem instead of Mem as an internal memory element. * * @example {{{ * val q = Module(new Queue(UInt(), 16)) @@ -197,7 +198,8 @@ class QueueIO[T <: Data](private val gen: T, val entries: Int) extends Bundle class Queue[T <: Data](val gen: T, val entries: Int, val pipe: Boolean = false, - val flow: Boolean = false) + val flow: Boolean = false, + val useSyncReadMem: Boolean = false) (implicit compileOptions: chisel3.CompileOptions) extends Module() { require(entries > -1, "Queue must have non-negative number of entries") @@ -215,7 +217,7 @@ class Queue[T <: Data](val gen: T, val io = IO(new QueueIO(genType, entries)) - val ram = Mem(entries, genType) + val ram = if (useSyncReadMem) SyncReadMem(entries, genType, SyncReadMem.WriteFirst) else Mem(entries, genType) val enq_ptr = Counter(entries) val deq_ptr = Counter(entries) val maybe_full = RegInit(false.B) @@ -239,7 +241,15 @@ class Queue[T <: Data](val gen: T, io.deq.valid := !empty io.enq.ready := !full - io.deq.bits := ram(deq_ptr.value) + + if (useSyncReadMem) { + val deq_ptr_next = Mux(deq_ptr.value === (entries.U - 1.U), 0.U, deq_ptr.value + 1.U) + val r_addr = WireDefault(Mux(do_deq, deq_ptr_next, deq_ptr.value)) + io.deq.bits := ram.read(r_addr) + } + else { + io.deq.bits := ram(deq_ptr.value) + } if (flow) { when (io.enq.valid) { io.deq.valid := true.B } @@ -285,7 +295,8 @@ object Queue enq: ReadyValidIO[T], entries: Int = 2, pipe: Boolean = false, - flow: Boolean = false): DecoupledIO[T] = { + flow: Boolean = false, + useSyncReadMem: Boolean = false): DecoupledIO[T] = { if (entries == 0) { val deq = Wire(new DecoupledIO(chiselTypeOf(enq.bits))) deq.valid := enq.valid @@ -293,7 +304,7 @@ object Queue enq.ready := deq.ready deq } else { - val q = Module(new Queue(chiselTypeOf(enq.bits), entries, pipe, flow)) + val q = Module(new Queue(chiselTypeOf(enq.bits), entries, pipe, flow, useSyncReadMem)) q.io.enq.valid := enq.valid // not using <> so that override is allowed q.io.enq.bits := enq.bits enq.ready := q.io.enq.ready @@ -311,8 +322,9 @@ object Queue enq: ReadyValidIO[T], entries: Int = 2, pipe: Boolean = false, - flow: Boolean = false): IrrevocableIO[T] = { - val deq = apply(enq, entries, pipe, flow) + flow: Boolean = false, + useSyncReadMem: Boolean = false): IrrevocableIO[T] = { + val deq = apply(enq, entries, pipe, flow, useSyncReadMem) require(entries > 0, "Zero-entry queues don't guarantee Irrevocability") val irr = Wire(new IrrevocableIO(chiselTypeOf(deq.bits))) irr.bits := deq.bits diff --git a/src/test/scala/chiselTests/QueueSpec.scala b/src/test/scala/chiselTests/QueueSpec.scala index 9dc7f120..3555a13c 100644 --- a/src/test/scala/chiselTests/QueueSpec.scala +++ b/src/test/scala/chiselTests/QueueSpec.scala @@ -9,8 +9,8 @@ import chisel3.testers.BasicTester import chisel3.util._ import chisel3.util.random.LFSR -class ThingsPassThroughTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int) extends BasicTester { - val q = Module(new Queue(UInt(bitWidth.W), queueDepth)) +class ThingsPassThroughTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends BasicTester { + val q = Module(new Queue(UInt(bitWidth.W), queueDepth, useSyncReadMem = useSyncReadMem)) val elems = VecInit(elements.map { _.asUInt() }) @@ -34,8 +34,8 @@ class ThingsPassThroughTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int } } -class QueueReasonableReadyValid(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int) extends BasicTester { - val q = Module(new Queue(UInt(bitWidth.W), queueDepth)) +class QueueReasonableReadyValid(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends BasicTester { + val q = Module(new Queue(UInt(bitWidth.W), queueDepth, useSyncReadMem = useSyncReadMem)) val elems = VecInit(elements.map { _.asUInt() }) @@ -62,8 +62,8 @@ class QueueReasonableReadyValid(elements: Seq[Int], queueDepth: Int, bitWidth: I } } -class CountIsCorrectTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int) extends BasicTester { - val q = Module(new Queue(UInt(bitWidth.W), queueDepth)) +class CountIsCorrectTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends BasicTester { + val q = Module(new Queue(UInt(bitWidth.W), queueDepth, useSyncReadMem = useSyncReadMem)) val elems = VecInit(elements.map { _.asUInt(bitWidth.W) }) @@ -89,8 +89,8 @@ class CountIsCorrectTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, t } } -class QueueSinglePipeTester(elements: Seq[Int], bitWidth: Int, tap: Int) extends BasicTester { - val q = Module(new Queue(UInt(bitWidth.W), 1, pipe = true)) +class QueueSinglePipeTester(elements: Seq[Int], bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends BasicTester { + val q = Module(new Queue(UInt(bitWidth.W), 1, pipe = true, useSyncReadMem = useSyncReadMem)) val elems = VecInit(elements.map { _.asUInt(bitWidth.W) }) @@ -115,8 +115,8 @@ class QueueSinglePipeTester(elements: Seq[Int], bitWidth: Int, tap: Int) extends } } -class QueuePipeTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int) extends BasicTester { - val q = Module(new Queue(UInt(bitWidth.W), queueDepth, pipe = true)) +class QueuePipeTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends BasicTester { + val q = Module(new Queue(UInt(bitWidth.W), queueDepth, pipe = true, useSyncReadMem = useSyncReadMem)) val elems = VecInit(elements.map { _.asUInt(bitWidth.W) }) @@ -141,8 +141,8 @@ class QueuePipeTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: I } } -class QueueFlowTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int) extends BasicTester { - val q = Module(new Queue(UInt(bitWidth.W), queueDepth, flow = true)) +class QueueFlowTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends BasicTester { + val q = Module(new Queue(UInt(bitWidth.W), queueDepth, flow = true, useSyncReadMem = useSyncReadMem)) val elems = VecInit(elements.map { _.asUInt() }) @@ -169,9 +169,9 @@ class QueueFlowTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: I } } -class QueueFactoryTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int) extends BasicTester { +class QueueFactoryTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends BasicTester { val enq = Wire(Decoupled(UInt(bitWidth.W))) - val deq = Queue(enq, queueDepth) + val deq = Queue(enq, queueDepth, useSyncReadMem = useSyncReadMem) val elems = VecInit(elements.map { _.asUInt() @@ -202,70 +202,70 @@ class QueueSpec extends ChiselPropSpec { implicit val noShrinkInt = Shrink[Int](_ => Stream.empty) property("Queue should have things pass through") { - forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15)) { (depth, se, tap) => + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { assertTesterPasses { - new ThingsPassThroughTester(se._2, depth, se._1, tap) + new ThingsPassThroughTester(se._2, depth, se._1, tap, isSync) } } } } property("Queue should have reasonable ready/valid") { - forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15)) { (depth, se, tap) => + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { assertTesterPasses { - new QueueReasonableReadyValid(se._2, depth, se._1, tap) + new QueueReasonableReadyValid(se._2, depth, se._1, tap, isSync) } } } } property("Queue should have correct count") { - forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15)) { (depth, se, tap) => + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { assertTesterPasses { - new CountIsCorrectTester(se._2, depth, se._1, tap) + new CountIsCorrectTester(se._2, depth, se._1, tap, isSync) } } } } property("Queue pipe should work for 1-element queues") { - forAll(safeUIntN(20), Gen.choose(0, 15)) { (se, tap) => + forAll(safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (se, tap, isSync) => whenever(se._1 >= 1 && se._2.nonEmpty) { assertTesterPasses { - new QueueSinglePipeTester(se._2, se._1, tap) + new QueueSinglePipeTester(se._2, se._1, tap, isSync) } } } } property("Queue pipe should work for more general queues") { - forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15)) { (depth, se, tap) => + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { assertTesterPasses { - new QueuePipeTester(se._2, depth, se._1, tap) + new QueuePipeTester(se._2, depth, se._1, tap, isSync) } } } } property("Queue flow should work") { - forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15)) { (depth, se, tap) => + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { assertTesterPasses { - new QueueFlowTester(se._2, depth, se._1, tap) + new QueueFlowTester(se._2, depth, se._1, tap, isSync) } } } } property("Queue companion object factory method should work") { - forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15)) { (depth, se, tap) => + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => whenever(se._1 >= 1 && se._2.nonEmpty) { assertTesterPasses { - new QueueFactoryTester(se._2, depth, se._1, tap) + new QueueFactoryTester(se._2, depth, se._1, tap, isSync) } } } -- cgit v1.2.3 From 53b620478ddab1faa96512e473fa198f7f1fcf50 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 9 Feb 2021 17:12:37 -0800 Subject: Add no-plugin-tests for testing Chisel without the compiler plugin This is a new SBT build unit that symlinks in some files from the normal chisel project tests, but builds them without the compiler plugin. --- .github/workflows/test.yml | 2 +- build.sbt | 5 ++ .../src/test/scala/chisel3/testers/TestUtils.scala | 1 + .../src/test/scala/chiselTests/ChiselSpec.scala | 1 + .../test/scala/chiselTests/InstanceNameSpec.scala | 1 + src/test/scala/chiselTests/ChiselSpec.scala | 56 +------------------- .../chiselTests/ChiselTestUtilitiesSpec.scala | 60 ++++++++++++++++++++++ 7 files changed, 70 insertions(+), 56 deletions(-) create mode 120000 no-plugin-tests/src/test/scala/chisel3/testers/TestUtils.scala create mode 120000 no-plugin-tests/src/test/scala/chiselTests/ChiselSpec.scala create mode 120000 no-plugin-tests/src/test/scala/chiselTests/InstanceNameSpec.scala create mode 100644 src/test/scala/chiselTests/ChiselTestUtilitiesSpec.scala diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a9322492..6d9fe475 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,7 +35,7 @@ jobs: if: matrix.scala == '2.12.13' run: sbt ++${{ matrix.scala }} docs/mdoc - name: Test - run: sbt ++${{ matrix.scala }} test + run: sbt ++${{ matrix.scala }} test noPluginTests/test - name: Binary compatibility run: sbt ++${{ matrix.scala }} mimaReportBinaryIssues diff --git a/build.sbt b/build.sbt index fc19d2ad..5c051ff5 100644 --- a/build.sbt +++ b/build.sbt @@ -201,6 +201,11 @@ lazy val chisel = (project in file(".")). ) ) +lazy val noPluginTests = (project in file ("no-plugin-tests")). + dependsOn(chisel). + settings(commonSettings: _*). + settings(chiselSettings: _*) + lazy val docs = project // new documentation project .in(file("docs-target")) // important: it must not be docs/ .dependsOn(chisel) diff --git a/no-plugin-tests/src/test/scala/chisel3/testers/TestUtils.scala b/no-plugin-tests/src/test/scala/chisel3/testers/TestUtils.scala new file mode 120000 index 00000000..f3ebcbce --- /dev/null +++ b/no-plugin-tests/src/test/scala/chisel3/testers/TestUtils.scala @@ -0,0 +1 @@ +../../../../../../src/test/scala/chisel3/testers/TestUtils.scala \ No newline at end of file diff --git a/no-plugin-tests/src/test/scala/chiselTests/ChiselSpec.scala b/no-plugin-tests/src/test/scala/chiselTests/ChiselSpec.scala new file mode 120000 index 00000000..9b257eeb --- /dev/null +++ b/no-plugin-tests/src/test/scala/chiselTests/ChiselSpec.scala @@ -0,0 +1 @@ +../../../../../src/test/scala/chiselTests/ChiselSpec.scala \ No newline at end of file diff --git a/no-plugin-tests/src/test/scala/chiselTests/InstanceNameSpec.scala b/no-plugin-tests/src/test/scala/chiselTests/InstanceNameSpec.scala new file mode 120000 index 00000000..e8eca65f --- /dev/null +++ b/no-plugin-tests/src/test/scala/chiselTests/InstanceNameSpec.scala @@ -0,0 +1 @@ +../../../../../src/test/scala/chiselTests/InstanceNameSpec.scala \ No newline at end of file diff --git a/src/test/scala/chiselTests/ChiselSpec.scala b/src/test/scala/chiselTests/ChiselSpec.scala index 843b3192..8df680d6 100644 --- a/src/test/scala/chiselTests/ChiselSpec.scala +++ b/src/test/scala/chiselTests/ChiselSpec.scala @@ -90,62 +90,8 @@ trait ChiselRunners extends Assertions with BackendCompilationUtilities { /** Spec base class for BDD-style testers. */ abstract class ChiselFlatSpec extends AnyFlatSpec with ChiselRunners with Matchers -class ChiselTestUtilitiesSpec extends ChiselFlatSpec { - import org.scalatest.exceptions.TestFailedException - // Who tests the testers? - "assertKnownWidth" should "error when the expected width is wrong" in { - val caught = intercept[ChiselException] { - assertKnownWidth(7) { - Wire(UInt(8.W)) - } - } - assert(caught.getCause.isInstanceOf[TestFailedException]) - } - - it should "error when the width is unknown" in { - a [ChiselException] shouldBe thrownBy { - assertKnownWidth(7) { - Wire(UInt()) - } - } - } - - it should "work if the width is correct" in { - assertKnownWidth(8) { - Wire(UInt(8.W)) - } - } - - "assertInferredWidth" should "error if the width is known" in { - val caught = intercept[ChiselException] { - assertInferredWidth(8) { - Wire(UInt(8.W)) - } - } - assert(caught.getCause.isInstanceOf[TestFailedException]) - } - - it should "error if the expected width is wrong" in { - a [TestFailedException] shouldBe thrownBy { - assertInferredWidth(8) { - val w = Wire(UInt()) - w := 2.U(2.W) - w - } - } - } - - it should "pass if the width is correct" in { - assertInferredWidth(4) { - val w = Wire(UInt()) - w := 2.U(4.W) - w - } - } -} - /** Spec base class for property-based testers. */ -class ChiselPropSpec extends PropSpec with ChiselRunners with ScalaCheckPropertyChecks with Matchers { +abstract class ChiselPropSpec extends PropSpec with ChiselRunners with ScalaCheckPropertyChecks with Matchers { // Constrain the default number of instances generated for every use of forAll. implicit override val generatorDrivenConfig: PropertyCheckConfiguration = diff --git a/src/test/scala/chiselTests/ChiselTestUtilitiesSpec.scala b/src/test/scala/chiselTests/ChiselTestUtilitiesSpec.scala new file mode 100644 index 00000000..6d3d526c --- /dev/null +++ b/src/test/scala/chiselTests/ChiselTestUtilitiesSpec.scala @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests + +import chisel3._ +import org.scalatest.exceptions.TestFailedException + +class ChiselTestUtilitiesSpec extends ChiselFlatSpec { + // Who tests the testers? + "assertKnownWidth" should "error when the expected width is wrong" in { + val caught = intercept[ChiselException] { + assertKnownWidth(7) { + Wire(UInt(8.W)) + } + } + assert(caught.getCause.isInstanceOf[TestFailedException]) + } + + it should "error when the width is unknown" in { + a [ChiselException] shouldBe thrownBy { + assertKnownWidth(7) { + Wire(UInt()) + } + } + } + + it should "work if the width is correct" in { + assertKnownWidth(8) { + Wire(UInt(8.W)) + } + } + + "assertInferredWidth" should "error if the width is known" in { + val caught = intercept[ChiselException] { + assertInferredWidth(8) { + Wire(UInt(8.W)) + } + } + assert(caught.getCause.isInstanceOf[TestFailedException]) + } + + it should "error if the expected width is wrong" in { + a [TestFailedException] shouldBe thrownBy { + assertInferredWidth(8) { + val w = Wire(UInt()) + w := 2.U(2.W) + w + } + } + } + + it should "pass if the width is correct" in { + assertInferredWidth(4) { + val w = Wire(UInt()) + w := 2.U(4.W) + w + } + } +} + -- cgit v1.2.3 From 0a0d7c6aac4326f2127d6d95efa5a4e10c81946c Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Mon, 12 Oct 2020 21:02:27 -0700 Subject: Make it possible to GC Data instances No longer create a pointer from parent to every HasId, only do it by default for BaseModules and MemBases. Add pointer from parent to Data upon binding the Data. * Add MemTypeBinding for port types of Mems This binding is similar to the SampleElementBinding for Vecs in that these Data are not truly hardware, but are represented in the FIRRTL IR and thus need some representation. * Call _onModuleClose on unbound Records This maintains some corner-case behavior that is nevertheless relied upon. It ensures that refs are set for the elements of Records, even if they are not bound to any real hardware. --- core/src/main/scala/chisel3/Aggregate.scala | 6 ++++-- core/src/main/scala/chisel3/BlackBox.scala | 2 ++ core/src/main/scala/chisel3/Data.scala | 5 +++-- core/src/main/scala/chisel3/Element.scala | 3 ++- core/src/main/scala/chisel3/Mem.scala | 4 ++++ core/src/main/scala/chisel3/Module.scala | 13 +++++++++++++ core/src/main/scala/chisel3/RawModule.scala | 2 ++ core/src/main/scala/chisel3/experimental/Analog.scala | 3 ++- core/src/main/scala/chisel3/internal/Binding.scala | 4 ++++ core/src/main/scala/chisel3/internal/Builder.scala | 1 - src/test/scala/chiselTests/CompatibilitySpec.scala | 12 ++++++++++++ 11 files changed, 48 insertions(+), 7 deletions(-) diff --git a/core/src/main/scala/chisel3/Aggregate.scala b/core/src/main/scala/chisel3/Aggregate.scala index 403fcdba..f07b2543 100644 --- a/core/src/main/scala/chisel3/Aggregate.scala +++ b/core/src/main/scala/chisel3/Aggregate.scala @@ -20,7 +20,8 @@ class AliasedAggregateFieldException(message: String) extends ChiselException(me * of) other Data objects. */ sealed abstract class Aggregate extends Data { - private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection) { + private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection): Unit = { + _parent.foreach(_.addId(this)) binding = target val resolvedDirection = SpecifiedDirection.fromParent(parentDirection, specifiedDirection) @@ -166,7 +167,8 @@ sealed class Vec[T <: Data] private[chisel3] (gen: => T, val length: Int) case _ => false } - private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection) { + private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection): Unit = { + _parent.foreach(_.addId(this)) binding = target val resolvedDirection = SpecifiedDirection.fromParent(parentDirection, specifiedDirection) diff --git a/core/src/main/scala/chisel3/BlackBox.scala b/core/src/main/scala/chisel3/BlackBox.scala index 03543790..8ba4b612 100644 --- a/core/src/main/scala/chisel3/BlackBox.scala +++ b/core/src/main/scala/chisel3/BlackBox.scala @@ -81,6 +81,8 @@ package experimental { id._onModuleClose } + closeUnboundIds(names) + val firrtlPorts = getModulePorts map {port => Port(port, port.specifiedDirection)} val component = DefBlackBox(this, name, firrtlPorts, SpecifiedDirection.Unspecified, params) _component = Some(component) diff --git a/core/src/main/scala/chisel3/Data.scala b/core/src/main/scala/chisel3/Data.scala index 332527bf..377a94e6 100644 --- a/core/src/main/scala/chisel3/Data.scala +++ b/core/src/main/scala/chisel3/Data.scala @@ -339,13 +339,14 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { private[chisel3] final def isSynthesizable: Boolean = _binding.map { case ChildBinding(parent) => parent.isSynthesizable case _: TopBinding => true - case _: SampleElementBinding[_] => false + case (_: SampleElementBinding[_] | _: MemTypeBinding[_]) => false }.getOrElse(false) private[chisel3] def topBindingOpt: Option[TopBinding] = _binding.flatMap { case ChildBinding(parent) => parent.topBindingOpt case bindingVal: TopBinding => Some(bindingVal) case SampleElementBinding(parent) => parent.topBindingOpt + case _: MemTypeBinding[_] => None } private[chisel3] def topBinding: TopBinding = topBindingOpt.get @@ -355,7 +356,7 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { * node is the top-level. * binding and direction are valid after this call completes. */ - private[chisel3] def bind(target: Binding, parentDirection: SpecifiedDirection = SpecifiedDirection.Unspecified) + private[chisel3] def bind(target: Binding, parentDirection: SpecifiedDirection = SpecifiedDirection.Unspecified): Unit // Both _direction and _resolvedUserDirection are saved versions of computed variables (for // efficiency, avoid expensive recomputation of frequent operations). diff --git a/core/src/main/scala/chisel3/Element.scala b/core/src/main/scala/chisel3/Element.scala index 55415f3d..0c99ff70 100644 --- a/core/src/main/scala/chisel3/Element.scala +++ b/core/src/main/scala/chisel3/Element.scala @@ -17,7 +17,8 @@ abstract class Element extends Data { def widthKnown: Boolean = width.known def name: String = getRef.name - private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection) { + private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection): Unit = { + _parent.foreach(_.addId(this)) binding = target val resolvedDirection = SpecifiedDirection.fromParent(parentDirection, specifiedDirection) direction = ActualDirection.fromSpecified(resolvedDirection) diff --git a/core/src/main/scala/chisel3/Mem.scala b/core/src/main/scala/chisel3/Mem.scala index a60b31ac..90525bfa 100644 --- a/core/src/main/scala/chisel3/Mem.scala +++ b/core/src/main/scala/chisel3/Mem.scala @@ -35,6 +35,7 @@ object Mem { } val mt = t.cloneTypeFull val mem = new Mem(mt, size) + mt.bind(MemTypeBinding(mem)) pushCommand(DefMemory(sourceInfo, mem, mt, size)) mem } @@ -45,6 +46,8 @@ object Mem { } sealed abstract class MemBase[T <: Data](val t: T, val length: BigInt) extends HasId with NamedComponent with SourceInfoDoc { + _parent.foreach(_.addId(this)) + // REVIEW TODO: make accessors (static/dynamic, read/write) combinations consistent. /** Creates a read accessor into the memory with static addressing. See the @@ -174,6 +177,7 @@ object SyncReadMem { } val mt = t.cloneTypeFull val mem = new SyncReadMem(mt, size, ruw) + mt.bind(MemTypeBinding(mem)) pushCommand(DefSeqMemory(sourceInfo, mem, mt, size, ruw)) mem } diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala index d34211f1..9f8087bf 100644 --- a/core/src/main/scala/chisel3/Module.scala +++ b/core/src/main/scala/chisel3/Module.scala @@ -209,6 +209,8 @@ package experimental { */ // TODO: seal this? abstract class BaseModule extends HasId { + _parent.foreach(_.addId(this)) + // // Builder Internals - this tracks which Module RTL construction belongs to. // @@ -382,6 +384,17 @@ package experimental { names } + /** Invokes _onModuleClose on HasIds found via reflection but not bound to hardware + * (thus not part of _ids) + * This maintains old naming behavior for non-hardware Data + */ + private[chisel3] def closeUnboundIds(names: HashMap[HasId, String]): Unit = { + val idLookup = _ids.toSet + for ((id, _) <- names if !idLookup(id)) { + id._onModuleClose + } + } + /** Compatibility function. Allows Chisel2 code which had ports without the IO wrapper to * compile under Bindings checks. Does nothing in non-compatibility mode. * diff --git a/core/src/main/scala/chisel3/RawModule.scala b/core/src/main/scala/chisel3/RawModule.scala index 0adacedb..d2ba6e84 100644 --- a/core/src/main/scala/chisel3/RawModule.scala +++ b/core/src/main/scala/chisel3/RawModule.scala @@ -98,6 +98,8 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) id._onModuleClose } + closeUnboundIds(names) + val firrtlPorts = getModulePorts map { port: Data => // Special case Vec to make FIRRTL emit the direction of its // element. diff --git a/core/src/main/scala/chisel3/experimental/Analog.scala b/core/src/main/scala/chisel3/experimental/Analog.scala index df76fd70..2ce2c86d 100644 --- a/core/src/main/scala/chisel3/experimental/Analog.scala +++ b/core/src/main/scala/chisel3/experimental/Analog.scala @@ -45,7 +45,8 @@ final class Analog private (private[chisel3] val width: Width) extends Element { // Define setter/getter pairing // Analog can only be bound to Ports and Wires (and Unbound) - private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection) { + private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection): Unit = { + _parent.foreach(_.addId(this)) SpecifiedDirection.fromParent(parentDirection, specifiedDirection) match { case SpecifiedDirection.Unspecified | SpecifiedDirection.Flip => case x => throwException(s"Analog may not have explicit direction, got '$x'") diff --git a/core/src/main/scala/chisel3/internal/Binding.scala b/core/src/main/scala/chisel3/internal/Binding.scala index 4442c62e..9e17aded 100644 --- a/core/src/main/scala/chisel3/internal/Binding.scala +++ b/core/src/main/scala/chisel3/internal/Binding.scala @@ -110,6 +110,10 @@ case class ChildBinding(parent: Data) extends Binding { case class SampleElementBinding[T <: Data](parent: Vec[T]) extends Binding { def location = parent.topBinding.location } +/** Special binding for Mem types */ +case class MemTypeBinding[T <: Data](parent: MemBase[T]) extends Binding { + def location: Option[BaseModule] = parent._parent +} // A DontCare element has a specific Binding, somewhat like a literal. // It is a source (RHS). It may only be connected/applied to sinks. case class DontCareBinding() extends UnconstrainedBinding diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index b7772aea..31d4666c 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -84,7 +84,6 @@ trait InstanceId { private[chisel3] trait HasId extends InstanceId { private[chisel3] def _onModuleClose: Unit = {} private[chisel3] val _parent: Option[BaseModule] = Builder.currentModule - _parent.foreach(_.addId(this)) private[chisel3] val _id: Long = Builder.idGen.next diff --git a/src/test/scala/chiselTests/CompatibilitySpec.scala b/src/test/scala/chiselTests/CompatibilitySpec.scala index c7a68e7c..2d4ad517 100644 --- a/src/test/scala/chiselTests/CompatibilitySpec.scala +++ b/src/test/scala/chiselTests/CompatibilitySpec.scala @@ -451,6 +451,18 @@ class CompatibiltySpec extends ChiselFlatSpec with ScalaCheckDrivenPropertyCheck ChiselStage.elaborate(new Foo) } + it should "support data-types of mixed directionality" in { + class Foo extends Module { + val io = IO(new Bundle {}) + val tpe = new Bundle { val foo = UInt(OUTPUT, width = 4); val bar = UInt(width = 4) } + // NOTE for some reason, the old bug this hit did not occur when `tpe` is inlined + val mem = SeqMem(tpe, 8) + mem(3.U) + + } + ChiselStage.elaborate((new Foo)) + } + behavior of "debug" it should "still exist" in { -- cgit v1.2.3 From 2b5466c7773c8cd7a08c48aa00d9365cbb205fd2 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Wed, 10 Feb 2021 22:19:09 -0500 Subject: Fix stack trace trimming across Driver/ChiselStage (#1771) * Handle MemTypeBinding in Analog Signed-off-by: Schuyler Eldridge * Fix stack trace trimming across ChiselStage Fix bug in stack trace trimming behavior. Now, the following is what happens: 1. The Builder, if catching accumulated errors, will now throw a ChiselException with a Scala-trimmed Stack trace. Previously, this would throw the full excpetion. 2. The Elaborate phase handles stack trace trimming. By default, any Throwable thrown during elaboration will have its stack trace *mutably* trimmed and is rethrown. A logger.error is printed stating that there was an error during elaboration and how the user can turn on the full stack trace. If the --full-stacktrace option is on, then the Throwable is not caught and only the first logger.error (saying that elaboration failed) will be printed. 3. ChiselStage (the class), ChiselStage$ (the object), and ChiselMain all inherit the behavior of (2). Mutable stack trace trimming behavior is moved into an implicit class (previously this was defined on ChiselException only) so this can be applied to any Throwable. No StageErrors are now thrown anymore. However, StageErrors may still be caught by ChiselMain (since it is a StageMain). Testing is added for ChiselMain, ChiselStage, and ChiselStage$ to test all this behavior. Signed-off-by: Schuyler Eldridge --- .../main/scala/chisel3/experimental/Analog.scala | 2 +- core/src/main/scala/chisel3/internal/Error.scala | 77 ++++++++- .../scala/chisel3/stage/ChiselAnnotations.scala | 6 +- src/main/scala/chisel3/stage/ChiselStage.scala | 22 +-- .../scala/chisel3/stage/phases/Elaborate.scala | 15 +- .../chiselTests/ChiselTestUtilitiesSpec.scala | 9 +- src/test/scala/chiselTests/OneHotMuxSpec.scala | 12 +- .../scala/chiselTests/stage/ChiselMainSpec.scala | 191 +++++++++++++++------ .../scala/chiselTests/stage/ChiselStageSpec.scala | 64 ++++++- 9 files changed, 301 insertions(+), 97 deletions(-) diff --git a/core/src/main/scala/chisel3/experimental/Analog.scala b/core/src/main/scala/chisel3/experimental/Analog.scala index 2ce2c86d..6cca81f5 100644 --- a/core/src/main/scala/chisel3/experimental/Analog.scala +++ b/core/src/main/scala/chisel3/experimental/Analog.scala @@ -56,6 +56,7 @@ final class Analog private (private[chisel3] val width: Width) extends Element { case ChildBinding(parent) => parent.topBinding // See https://github.com/freechipsproject/chisel3/pull/946 case SampleElementBinding(parent) => parent.topBinding + case a: MemTypeBinding[_] => a } targetTopBinding match { @@ -83,4 +84,3 @@ final class Analog private (private[chisel3] val width: Width) extends Element { object Analog { def apply(width: Width): Analog = new Analog(width) } - diff --git a/core/src/main/scala/chisel3/internal/Error.scala b/core/src/main/scala/chisel3/internal/Error.scala index 60bb6e2b..134f4c87 100644 --- a/core/src/main/scala/chisel3/internal/Error.scala +++ b/core/src/main/scala/chisel3/internal/Error.scala @@ -5,12 +5,82 @@ package chisel3.internal import scala.annotation.tailrec import scala.collection.mutable.{ArrayBuffer, LinkedHashMap} -class ChiselException(message: String, cause: Throwable = null) extends Exception(message, cause) { +object ExceptionHelpers { + + /** Root packages that are not typically relevant to Chisel user code. */ + final val packageTrimlist: Set[String] = Set("chisel3", "scala", "java", "jdk", "sun", "sbt") + + /** The object name of Chisel's internal `Builder`. */ + final val builderName: String = chisel3.internal.Builder.getClass.getName + + /** Return a stack trace element that looks like `... (someMessage)`. + * @param message an optional message to include + */ + def ellipsis(message: Option[String] = None): StackTraceElement = + new StackTraceElement("..", " ", message.getOrElse(""), -1) + + /** Utility methods that can be added to exceptions. + */ + implicit class ThrowableHelpers(throwable: Throwable) { + + /** For an exception, mutably trim a stack trace to user code only. + * + * This does the following actions to the stack trace: + * + * 1. From the top, remove elements while the (root) package matches the packageTrimlist + * 2. Optionally, from the bottom, remove elements until the class matches an anchor + * 3. From the anchor (or the bottom), remove elements while the (root) package matches the packageTrimlist + * + * @param packageTrimlist packages that should be removed from the stack trace + * @param anchor an optional class name at which user execution might begin, e.g., a main object + * @return nothing as this mutates the exception directly + */ + def trimStackTraceToUserCode( + packageTrimlist: Set[String] = packageTrimlist, + anchor: Option[String] = Some(builderName) + ): Unit = { + def inTrimlist(ste: StackTraceElement) = { + val packageName = ste.getClassName().takeWhile(_ != '.') + packageTrimlist.contains(packageName) + } + + // Step 1: Remove elements from the top in the package trimlist + ((a: Array[StackTraceElement]) => a.view.dropWhile(inTrimlist)) + // Step 2: Optionally remove elements from the bottom until the anchor + .andThen(_.reverse) + .andThen( a => + anchor match { + case Some(b) => a.dropWhile(ste => !ste.getClassName.startsWith(b)) + case None => a + } + ) + // Step 3: Remove elements from the bottom in the package trimlist + .andThen(_.dropWhile(inTrimlist)) + // Step 4: Reverse back to the original order + .andThen(_.reverse.toArray) + // Step 5: Add ellipsis stack trace elements and "--full-stacktrace" info + .andThen(a => + ellipsis() +: + a :+ + ellipsis() :+ + ellipsis(Some("Stack trace trimmed to user code only. Rerun with --full-stacktrace to see the full stack trace"))) + // Step 5: Mutate the stack trace in this exception + .andThen(throwable.setStackTrace(_)) + .apply(throwable.getStackTrace) + } + + } + +} + +class ChiselException(message: String, cause: Throwable = null) extends Exception(message, cause, true, true) { /** Package names whose stack trace elements should be trimmed when generating a trimmed stack trace */ + @deprecated("Use ExceptionHelpers.packageTrimlist. This will be removed in Chisel 3.6", "3.5") val blacklistPackages: Set[String] = Set("chisel3", "scala", "java", "sun", "sbt") /** The object name of Chisel's internal `Builder`. Everything stack trace element after this will be trimmed. */ + @deprecated("Use ExceptionHelpers.builderName. This will be removed in Chisel 3.6", "3.5") val builderName: String = chisel3.internal.Builder.getClass.getName /** Examine a [[Throwable]], to extract all its causes. Innermost cause is first. @@ -27,7 +97,7 @@ class ChiselException(message: String, cause: Throwable = null) extends Exceptio /** Returns true if an exception contains */ private def containsBuilder(throwable: Throwable): Boolean = throwable.getStackTrace().collectFirst { - case ste if ste.getClassName().startsWith(builderName) => throwable + case ste if ste.getClassName().startsWith(ExceptionHelpers.builderName) => throwable }.isDefined /** Examine this [[ChiselException]] and it's causes for the first [[Throwable]] that contains a stack trace including @@ -142,7 +212,8 @@ private[chisel3] class ErrorLog { } if (!allErrors.isEmpty) { - throwException("Fatal errors during hardware elaboration") + throw new ChiselException("Fatal errors during hardware elaboration. Look above for error list.") + with scala.util.control.NoStackTrace } else { // No fatal errors, clear accumulated warnings since they've been reported errors.clear() diff --git a/src/main/scala/chisel3/stage/ChiselAnnotations.scala b/src/main/scala/chisel3/stage/ChiselAnnotations.scala index bbe86ab4..04246fc5 100644 --- a/src/main/scala/chisel3/stage/ChiselAnnotations.scala +++ b/src/main/scala/chisel3/stage/ChiselAnnotations.scala @@ -56,13 +56,9 @@ case class ChiselGeneratorAnnotation(gen: () => RawModule) extends NoTargetAnnot /** Run elaboration on the Chisel module generator function stored by this [[firrtl.annotations.Annotation]] */ - def elaborate: AnnotationSeq = try { + def elaborate: AnnotationSeq = { val (circuit, dut) = Builder.build(Module(gen())) Seq(ChiselCircuitAnnotation(circuit), DesignAnnotation(dut)) - } catch { - case e @ (_: OptionsException | _: ChiselException) => throw e - case e: Throwable => - throw new ChiselException(s"Exception thrown when elaborating ChiselGeneratorAnnotation", e) } } diff --git a/src/main/scala/chisel3/stage/ChiselStage.scala b/src/main/scala/chisel3/stage/ChiselStage.scala index aae7ad8d..1bcf5124 100644 --- a/src/main/scala/chisel3/stage/ChiselStage.scala +++ b/src/main/scala/chisel3/stage/ChiselStage.scala @@ -13,7 +13,7 @@ import firrtl.{ VerilogEmitter, SystemVerilogEmitter } -import firrtl.options.{Dependency, Phase, PhaseManager, Shell, Stage, StageError, StageMain} +import firrtl.options.{Dependency, Phase, PhaseManager, Shell, Stage, StageMain} import firrtl.options.phases.DeletedWrapper import firrtl.stage.{FirrtlCircuitAnnotation, FirrtlCli, RunFirrtlTransformAnnotation} import firrtl.options.Viewer.view @@ -28,7 +28,7 @@ class ChiselStage extends Stage { override def prerequisites = Seq.empty override def optionalPrerequisites = Seq.empty - override def optionalPrerequisiteOf = Seq.empty + override def optionalPrerequisiteOf = Seq(Dependency[firrtl.stage.FirrtlStage]) override def invalidates(a: Phase) = false val shell: Shell = new Shell("chisel") with ChiselCli with FirrtlCli @@ -42,23 +42,7 @@ class ChiselStage extends Stage { } } - def run(annotations: AnnotationSeq): AnnotationSeq = try { - phaseManager.transform(annotations) - } catch { - case ce: ChiselException => - val stackTrace = if (!view[ChiselOptions](annotations).printFullStackTrace) { - ce.chiselStackTrace - } else { - val sw = new StringWriter - ce.printStackTrace(new PrintWriter(sw)) - sw.toString - } - Predef - .augmentString(stackTrace) - .lines - .foreach(line => println(s"${ErrorLog.errTag} $line")) - throw new StageError(cause=ce) - } + def run(annotations: AnnotationSeq): AnnotationSeq = phaseManager.transform(annotations) /** Convert a Chisel module to a CHIRRTL string * @param gen a call-by-name Chisel module diff --git a/src/main/scala/chisel3/stage/phases/Elaborate.scala b/src/main/scala/chisel3/stage/phases/Elaborate.scala index 04cfc33e..df39b66b 100644 --- a/src/main/scala/chisel3/stage/phases/Elaborate.scala +++ b/src/main/scala/chisel3/stage/phases/Elaborate.scala @@ -6,6 +6,7 @@ import java.io.{PrintWriter, StringWriter} import chisel3.ChiselException import chisel3.internal.ErrorLog +import chisel3.internal.ExceptionHelpers.ThrowableHelpers import chisel3.stage.{ChiselGeneratorAnnotation, ChiselOptions} import firrtl.AnnotationSeq import firrtl.options.Viewer.view @@ -21,8 +22,18 @@ class Elaborate extends Phase { override def invalidates(a: Phase) = false def transform(annotations: AnnotationSeq): AnnotationSeq = annotations.flatMap { - case a: ChiselGeneratorAnnotation => a.elaborate - case a => Some(a) + case a: ChiselGeneratorAnnotation => try { + a.elaborate + } catch { + /* if any throwable comes back and we're in "stack trace trimming" mode, then print an error and trim the stack trace + */ + case scala.util.control.NonFatal(a) => + if (!view[ChiselOptions](annotations).printFullStackTrace) { + a.trimStackTraceToUserCode() + } + throw(a) + } + case a => Some(a) } } diff --git a/src/test/scala/chiselTests/ChiselTestUtilitiesSpec.scala b/src/test/scala/chiselTests/ChiselTestUtilitiesSpec.scala index 6d3d526c..40358d11 100644 --- a/src/test/scala/chiselTests/ChiselTestUtilitiesSpec.scala +++ b/src/test/scala/chiselTests/ChiselTestUtilitiesSpec.scala @@ -8,16 +8,15 @@ import org.scalatest.exceptions.TestFailedException class ChiselTestUtilitiesSpec extends ChiselFlatSpec { // Who tests the testers? "assertKnownWidth" should "error when the expected width is wrong" in { - val caught = intercept[ChiselException] { + intercept[TestFailedException] { assertKnownWidth(7) { Wire(UInt(8.W)) } } - assert(caught.getCause.isInstanceOf[TestFailedException]) } it should "error when the width is unknown" in { - a [ChiselException] shouldBe thrownBy { + intercept[ChiselException] { assertKnownWidth(7) { Wire(UInt()) } @@ -31,12 +30,11 @@ class ChiselTestUtilitiesSpec extends ChiselFlatSpec { } "assertInferredWidth" should "error if the width is known" in { - val caught = intercept[ChiselException] { + intercept[TestFailedException] { assertInferredWidth(8) { Wire(UInt(8.W)) } } - assert(caught.getCause.isInstanceOf[TestFailedException]) } it should "error if the expected width is wrong" in { @@ -57,4 +55,3 @@ class ChiselTestUtilitiesSpec extends ChiselFlatSpec { } } } - diff --git a/src/test/scala/chiselTests/OneHotMuxSpec.scala b/src/test/scala/chiselTests/OneHotMuxSpec.scala index 887843d4..7608a3e7 100644 --- a/src/test/scala/chiselTests/OneHotMuxSpec.scala +++ b/src/test/scala/chiselTests/OneHotMuxSpec.scala @@ -37,26 +37,18 @@ class OneHotMuxSpec extends AnyFreeSpec with Matchers with ChiselRunners { } } "simple one hot mux with all fixed width bundles but with different bundles should Not work" in { - try { + intercept[IllegalArgumentException] { assertTesterPasses(new DifferentBundleOneHotTester) - } catch { - case a: ChiselException => a.getCause match { - case _: IllegalArgumentException => - } } } "UIntToOH with output width greater than 2^(input width)" in { assertTesterPasses(new UIntToOHTester) } "UIntToOH should not accept width of zero (until zero-width wires are fixed" in { - try { + intercept[IllegalArgumentException] { assertTesterPasses(new BasicTester { val out = UIntToOH(0.U, 0) }) - } catch { - case a: ChiselException => a.getCause match { - case _: IllegalArgumentException => - } } } diff --git a/src/test/scala/chiselTests/stage/ChiselMainSpec.scala b/src/test/scala/chiselTests/stage/ChiselMainSpec.scala index 0fc42fc6..1634e765 100644 --- a/src/test/scala/chiselTests/stage/ChiselMainSpec.scala +++ b/src/test/scala/chiselTests/stage/ChiselMainSpec.scala @@ -10,6 +10,8 @@ import chisel3.aop.inspecting.{InspectingAspect, InspectorAspect} import org.scalatest.GivenWhenThen import org.scalatest.featurespec.AnyFeatureSpec import org.scalatest.matchers.should.Matchers +import org.scalatest.Inside._ +import org.scalatest.EitherValues._ import scala.io.Source import firrtl.Parser @@ -32,7 +34,7 @@ object ChiselMainSpec { /** A module that fails a requirement */ class FailingRequirementModule extends RawModule { - require(false) + require(false, "the user wrote a failing requirement") } /** A module that triggers a Builder.error (as opposed to exception) */ @@ -69,14 +71,35 @@ class ChiselMainSpec extends AnyFeatureSpec with GivenWhenThen with Matchers wit args: Array[String], generator: Option[Class[_ <: RawModule]] = None, files: Seq[String] = Seq.empty, - stdout: Option[String] = None, - stderr: Option[String] = None, + stdout: Seq[Either[String, String]] = Seq.empty, + stderr: Seq[Either[String, String]] = Seq.empty, result: Int = 0, fileChecks: Map[String, File => Unit] = Map.empty) { def testName: String = "args" + args.mkString("_") def argsString: String = args.mkString(" ") } + /** A test of ChiselMain that is going to involve catching an exception. + * @param args command line arguments (excluding --module) to pass in + * @param generator the module to build (used to generate --module) + * @param message snippets of text that should appear (Right) or not appear (Left) in the exception message + * @param stdout snippets of text that should appear (Right) or not appear (Left) in STDOUT + * @param stderr snippets of text that should appear (Right) or not appear (Left) in STDERR + * @param stackTrace snippets of text that should appear (Right) or not appear (Left) in the stack trace + * @tparam the type of exception that should occur + */ + case class ChiselMainExceptionTest[A <: Throwable]( + args: Array[String], + generator: Option[Class[_ <: RawModule]] = None, + message: Seq[Either[String, String]] = Seq.empty, + stdout: Seq[Either[String, String]] = Seq.empty, + stderr: Seq[Either[String, String]] = Seq.empty, + stackTrace: Seq[Either[String, String]] = Seq.empty + ) { + def testName: String = "args" + args.mkString("_") + def argsString: String = args.mkString(" ") + } + def runStageExpectFiles(p: ChiselMainTest): Unit = { Scenario(s"""User runs Chisel Stage with '${p.argsString}'""") { val f = new ChiselMainFixture @@ -85,32 +108,33 @@ class ChiselMainSpec extends AnyFeatureSpec with GivenWhenThen with Matchers wit p.files.foreach( f => new File(td.buildDir + s"/$f").delete() ) When(s"""the user tries to compile with '${p.argsString}'""") + val module: Array[String] = + (if (p.generator.nonEmpty) { Array("--module", p.generator.get.getName) } + else { Array.empty[String] }) + f.stage.main(Array("-td", td.buildDir.toString) ++ module ++ p.args) val (stdout, stderr, result) = grabStdOutErr { catchStatus { - val module: Array[String] = Array("foo") ++ - (if (p.generator.nonEmpty) { Array("--module", p.generator.get.getName) } - else { Array.empty[String] }) f.stage.main(Array("-td", td.buildDir.toString) ++ module ++ p.args) } } - p.stdout match { - case Some(a) => + p.stdout.foreach { + case Right(a) => Then(s"""STDOUT should include "$a"""") stdout should include (a) - case None => - Then(s"nothing should print to STDOUT") - stdout should be (empty) + case Left(a) => + Then(s"""STDOUT should not include "$a"""") + stdout should not include (a) } - p.stderr match { - case Some(a) => - And(s"""STDERR should include "$a"""") + p.stderr.foreach { + case Right(a) => + Then(s"""STDERR should include "$a"""") stderr should include (a) - case None => - And(s"nothing should print to STDERR") - stderr should be (empty) + case Left(a) => + Then(s"""STDERR should not include "$a"""") + stderr should not include (a) } p.result match { @@ -131,56 +155,128 @@ class ChiselMainSpec extends AnyFeatureSpec with GivenWhenThen with Matchers wit } } + /** Run a ChiselMainExceptionTest and verify that all the properties it spells out hold. + * @param p the test to run + * @tparam the type of the exception to catch (you shouldn't have to explicitly provide this) + */ + def runStageExpectException[A <: Throwable: scala.reflect.ClassTag](p: ChiselMainExceptionTest[A]): Unit = { + Scenario(s"""User runs Chisel Stage with '${p.argsString}'""") { + val f = new ChiselMainFixture + val td = new TargetDirectoryFixture(p.testName) + + When(s"""the user tries to compile with '${p.argsString}'""") + val module: Array[String] = + (if (p.generator.nonEmpty) { Array("--module", p.generator.get.getName) } + else { Array.empty[String] }) + val (stdout, stderr, result) = + grabStdOutErr { + catchStatus { + intercept[A] { + f.stage.main(Array("-td", td.buildDir.toString) ++ module ++ p.args) + } + } + } + + Then("the expected exception was thrown") + result should be a ('right) + val exception = result.right.get + info(s""" - Exception was a "${exception.getClass.getName}"""") + + val message = exception.getMessage + p.message.foreach { + case Right(a) => + Then(s"""STDOUT should include "$a"""") + message should include (a) + case Left(a) => + Then(s"""STDOUT should not include "$a"""") + message should not include (a) + } + + p.stdout.foreach { + case Right(a) => + Then(s"""STDOUT should include "$a"""") + stdout should include (a) + case Left(a) => + Then(s"""STDOUT should not include "$a"""") + stdout should not include (a) + } + + p.stderr.foreach { + case Right(a) => + Then(s"""STDERR should include "$a"""") + stderr should include (a) + case Left(a) => + Then(s"""STDERR should not include "$a"""") + stderr should not include (a) + } + + val stackTraceString = exception.getStackTrace.mkString("\n") + p.stackTrace.foreach { + case Left(a) => + And(s"""the stack does not include "$a"""") + stackTraceString should not include (a) + case Right(a) => + And(s"""the stack trace includes "$a"""") + stackTraceString should include (a) + } + + } + } + info("As a Chisel user") info("I compile a design") Feature("show elaborating message") { runStageExpectFiles( ChiselMainTest(args = Array("-X", "high"), - generator = Some(classOf[SameTypesModule]), - stdout = Some("Done elaborating.") + generator = Some(classOf[SameTypesModule]) ) ) } info("I screw up and compile some bad code") - Feature("Stack trace trimming") { + Feature("Stack trace trimming of ChiselException") { Seq( - ChiselMainTest(args = Array("-X", "low"), - generator = Some(classOf[DifferentTypesModule]), - stdout = Some("Stack trace trimmed to user code only"), - result = 1), - ChiselMainTest(args = Array("-X", "high", "--full-stacktrace"), - generator = Some(classOf[DifferentTypesModule]), - stdout = Some("org.scalatest"), - result = 1) - ).foreach(runStageExpectFiles) + ChiselMainExceptionTest[chisel3.internal.ChiselException]( + args = Array("-X", "low"), + generator = Some(classOf[DifferentTypesModule]), + stackTrace = Seq(Left("java"), Right(classOf[DifferentTypesModule].getName)) + ), + ChiselMainExceptionTest[chisel3.internal.ChiselException]( + args = Array("-X", "low", "--full-stacktrace"), + generator = Some(classOf[DifferentTypesModule]), + stackTrace = Seq(Right("java"), Right(classOf[DifferentTypesModule].getName)) + ) + ).foreach(runStageExpectException) } - Feature("Report properly trimmed stack traces") { + Feature("Stack trace trimming of user exceptions") { Seq( - ChiselMainTest(args = Array("-X", "low"), - generator = Some(classOf[FailingRequirementModule]), - stdout = Some("requirement failed"), - result = 1), - ChiselMainTest(args = Array("-X", "low", "--full-stacktrace"), - generator = Some(classOf[FailingRequirementModule]), - stdout = Some("chisel3.internal.ChiselException"), - result = 1) - ).foreach(runStageExpectFiles) + ChiselMainExceptionTest[java.lang.IllegalArgumentException]( + args = Array("-X", "low"), + generator = Some(classOf[FailingRequirementModule]), + stackTrace = Seq(Right(classOf[FailingRequirementModule].getName), Left("java")) + ), + ChiselMainExceptionTest[java.lang.IllegalArgumentException]( + args = Array("-X", "low", "--full-stacktrace"), + generator = Some(classOf[FailingRequirementModule]), + stackTrace = Seq(Right(classOf[FailingRequirementModule].getName), Right("java")) + ) + ).foreach(runStageExpectException) } - Feature("Builder.error source locator") { + Feature("Stack trace trimming and Builder.error errors") { Seq( - ChiselMainTest(args = Array("-X", "none"), + ChiselMainExceptionTest[chisel3.internal.ChiselException]( + args = Array("-X", "low"), generator = Some(classOf[BuilderErrorModule]), - stdout = Some("ChiselMainSpec.scala:41: Invalid bit range (3,-1) in class chiselTests.stage.ChiselMainSpec$BuilderErrorModule"), - result = 1) - ).foreach(runStageExpectFiles) + message = Seq(Right("Fatal errors during hardware elaboration")), + stdout = Seq(Right("ChiselMainSpec.scala:43: Invalid bit range (3,-1) in class chiselTests.stage.ChiselMainSpec$BuilderErrorModule")) + ) + ).foreach(runStageExpectException) } Feature("Specifying a custom output file") { runStageExpectFiles(ChiselMainTest( args = Array("--chisel-output-file", "Foo", "--no-run-firrtl"), generator = Some(classOf[SameTypesModule]), - stdout = Some(""), files = Seq("Foo.fir"), fileChecks = Map( "Foo.fir" -> { file => @@ -192,7 +288,6 @@ class ChiselMainSpec extends AnyFeatureSpec with GivenWhenThen with Matchers wit runStageExpectFiles(ChiselMainTest( args = Array("--chisel-output-file", "Foo.pb", "--no-run-firrtl"), generator = Some(classOf[SameTypesModule]), - stdout = Some(""), files = Seq("Foo.pb"), fileChecks = Map( "Foo.pb" -> { file => @@ -209,10 +304,10 @@ class ChiselMainSpec extends AnyFeatureSpec with GivenWhenThen with Matchers wit Seq( ChiselMainTest(args = Array( "-X", "high", "--with-aspect", "chiselTests.stage.TestClassAspect" ), generator = Some(classOf[SameTypesModule]), - stdout = Some("Ran inspectingAspect")), + stdout = Seq(Right("Ran inspectingAspect"))), ChiselMainTest(args = Array( "-X", "high", "--with-aspect", "chiselTests.stage.TestObjectAspect" ), generator = Some(classOf[SameTypesModule]), - stdout = Some("Ran inspectingAspect")) + stdout = Seq(Right("Ran inspectingAspect"))) ).foreach(runStageExpectFiles) } diff --git a/src/test/scala/chiselTests/stage/ChiselStageSpec.scala b/src/test/scala/chiselTests/stage/ChiselStageSpec.scala index 98bbb2ea..167e414b 100644 --- a/src/test/scala/chiselTests/stage/ChiselStageSpec.scala +++ b/src/test/scala/chiselTests/stage/ChiselStageSpec.scala @@ -30,6 +30,10 @@ object ChiselStageSpec { out := memory(bar.out) } + class UserExceptionModule extends RawModule { + assert(false, "User threw an exception") + } + } class ChiselStageSpec extends AnyFlatSpec with Matchers with Utils { @@ -40,13 +44,13 @@ class ChiselStageSpec extends AnyFlatSpec with Matchers with Utils { val stage = new ChiselStage } - behavior of "ChiselStage.emitChirrtl" + behavior of "ChiselStage$.emitChirrtl" it should "return a CHIRRTL string" in { ChiselStage.emitChirrtl(new Foo) should include ("infer mport") } - behavior of "ChiselStage.emitFirrtl" + behavior of "ChiselStage$.emitFirrtl" it should "return a High FIRRTL string" in { ChiselStage.emitFirrtl(new Foo) should include ("mem memory") @@ -58,7 +62,7 @@ class ChiselStageSpec extends AnyFlatSpec with Matchers with Utils { .emitFirrtl(new Foo, args) should include ("module Bar") } - behavior of "ChiselStage.emitVerilog" + behavior of "ChiselStage$.emitVerilog" it should "return a Verilog string" in { ChiselStage.emitVerilog(new Foo) should include ("endmodule") @@ -142,4 +146,58 @@ class ChiselStageSpec extends AnyFlatSpec with Matchers with Utils { exactly (1, order) should be (Dependency[chisel3.stage.phases.Elaborate]) } + behavior of "ChiselStage$ exception handling" + + it should "truncate a user exception" in { + info("The user's java.lang.AssertionError was thrown") + val exception = intercept[java.lang.AssertionError] { + ChiselStage.emitChirrtl(new UserExceptionModule) + } + + val message = exception.getMessage + info("The exception includes the user's message") + message should include ("User threw an exception") + + info("The stack trace is trimmed") + exception.getStackTrace.mkString("\n") should not include ("java") + } + + behavior of "ChiselStage exception handling" + + it should "truncate a user exception" in { + info("The user's java.lang.AssertionError was thrown") + val exception = intercept[java.lang.AssertionError] { + (new ChiselStage).emitChirrtl(new UserExceptionModule) + } + + info(s""" - Exception was a ${exception.getClass.getName}""") + + val message = exception.getMessage + info("The exception includes the user's message") + message should include ("User threw an exception") + + val stackTrace = exception.getStackTrace.mkString("\n") + info("The stack trace is trimmed") + stackTrace should not include ("java") + + info("The stack trace include information about running --full-stacktrace") + stackTrace should include ("--full-stacktrace") + } + + it should """not truncate a user exception with "--full-stacktrace"""" in { + info("The user's java.lang.AssertionError was thrown") + val exception = intercept[java.lang.AssertionError] { + (new ChiselStage).emitChirrtl(new UserExceptionModule, Array("--full-stacktrace")) + } + + info(s""" - Exception was a ${exception.getClass.getName}""") + + val message = exception.getMessage + info("The exception includes the user's message") + message should include ("User threw an exception") + + info("The stack trace is not trimmed") + exception.getStackTrace.mkString("\n") should include ("java") + } + } -- cgit v1.2.3 From c2ba4098d0a2f7ca056ea198d68b1d3bfaf40f3b Mon Sep 17 00:00:00 2001 From: edwardcwang Date: Tue, 23 Feb 2021 22:29:33 -0500 Subject: Aggregate: fix typo (#1789) Not a Pokémon--- core/src/main/scala/chisel3/Aggregate.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/chisel3/Aggregate.scala b/core/src/main/scala/chisel3/Aggregate.scala index f07b2543..6942313e 100644 --- a/core/src/main/scala/chisel3/Aggregate.scala +++ b/core/src/main/scala/chisel3/Aggregate.scala @@ -521,7 +521,7 @@ abstract class Record(private[chisel3] implicit val compileOptions: CompileOptio * val b = Bool() * } * - * (mew MyBundle).Lit( + * (new MyBundle).Lit( * _.a -> 42.U, * _.b -> true.B * ) -- cgit v1.2.3 From 923ccbde1353e37f0948d3c5d94b49965dc6d950 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Sat, 27 Feb 2021 05:01:10 +0800 Subject: Expose AnnotationSeq to Module. (#1731) --- core/src/main/scala/chisel3/Module.scala | 1 + core/src/main/scala/chisel3/aop/Aspect.scala | 7 ++++++- core/src/main/scala/chisel3/internal/Builder.scala | 11 ++++------- .../scala/chisel3/aop/injecting/InjectingAspect.scala | 2 +- src/main/scala/chisel3/stage/ChiselAnnotations.scala | 6 +----- src/main/scala/chisel3/stage/phases/AspectPhase.scala | 2 +- src/main/scala/chisel3/stage/phases/Elaborate.scala | 15 +++++++-------- .../scala/chisel3/util/experimental/getAnnotations.scala | 9 +++++++++ src/test/scala/chiselTests/Module.scala | 15 ++++++++++++++- 9 files changed, 44 insertions(+), 24 deletions(-) create mode 100644 src/main/scala/chisel3/util/experimental/getAnnotations.scala diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala index 9f8087bf..ede9ccc6 100644 --- a/core/src/main/scala/chisel3/Module.scala +++ b/core/src/main/scala/chisel3/Module.scala @@ -14,6 +14,7 @@ import chisel3.internal.firrtl._ import chisel3.internal.sourceinfo.{InstTransform, SourceInfo, UnlocatableSourceInfo} import chisel3.experimental.BaseModule import _root_.firrtl.annotations.{IsModule, ModuleName, ModuleTarget} +import _root_.firrtl.AnnotationSeq object Module extends SourceInfoDoc { /** A wrapper method that all Module instantiations must be wrapped in diff --git a/core/src/main/scala/chisel3/aop/Aspect.scala b/core/src/main/scala/chisel3/aop/Aspect.scala index 59add417..be9b8975 100644 --- a/core/src/main/scala/chisel3/aop/Aspect.scala +++ b/core/src/main/scala/chisel3/aop/Aspect.scala @@ -12,6 +12,10 @@ import firrtl.AnnotationSeq * @tparam T Type of top-level module */ abstract class Aspect[T <: RawModule] extends Annotation with Unserializable with NoTargetAnnotation { + /** variable to save [[AnnotationSeq]] from [[chisel3.stage.phases.AspectPhase]] + * to be used at [[chisel3.aop.injecting.InjectorAspect]], exposes annotations to [[chisel3.internal.DynamicContext]] + */ + private[aop] var annotationsInAspect: AnnotationSeq = Seq() /** Convert this Aspect to a seq of FIRRTL annotation * @param top * @return @@ -22,7 +26,8 @@ abstract class Aspect[T <: RawModule] extends Annotation with Unserializable wit * @param top * @return */ - private[chisel3] def resolveAspect(top: RawModule): AnnotationSeq = { + private[chisel3] def resolveAspect(top: RawModule, remainingAnnotations: AnnotationSeq): AnnotationSeq = { + annotationsInAspect = remainingAnnotations toAnnotation(top.asInstanceOf[T]) } } diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index 31d4666c..e95384cd 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -9,7 +9,8 @@ import chisel3.experimental._ import chisel3.internal.firrtl._ import chisel3.internal.naming._ import _root_.firrtl.annotations.{CircuitName, ComponentName, IsMember, ModuleName, Named, ReferenceTarget} -import _root_.firrtl.annotations.AnnotationUtils.{validComponentName} +import _root_.firrtl.annotations.AnnotationUtils.validComponentName +import _root_.firrtl.AnnotationSeq import chisel3.internal.Builder.Prefix import logger.LazyLogging @@ -305,7 +306,7 @@ private[chisel3] class ChiselContext() { var prefixStack: Prefix = Nil } -private[chisel3] class DynamicContext() { +private[chisel3] class DynamicContext(val annotationSeq: AnnotationSeq) { val globalNamespace = Namespace.empty val components = ArrayBuffer[Component]() val annotations = ArrayBuffer[ChiselAnnotation]() @@ -364,6 +365,7 @@ private[chisel3] object Builder extends LazyLogging { def globalNamespace: Namespace = dynamicContext.globalNamespace def components: ArrayBuffer[Component] = dynamicContext.components def annotations: ArrayBuffer[ChiselAnnotation] = dynamicContext.annotations + def annotationSeq: AnnotationSeq = dynamicContext.annotationSeq def namingStack: NamingStack = dynamicContext.namingStack // Puts a prefix string onto the prefix stack @@ -632,11 +634,6 @@ private[chisel3] object Builder extends LazyLogging { } } - - def build[T <: RawModule](f: => T): (Circuit, T) = { - build(f, new DynamicContext()) - } - private [chisel3] def build[T <: RawModule](f: => T, dynamicContext: DynamicContext): (Circuit, T) = { dynamicContextVar.withValue(Some(dynamicContext)) { checkScalaVersion() diff --git a/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala b/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala index 39590b93..170bfbad 100644 --- a/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala +++ b/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala @@ -54,7 +54,7 @@ abstract class InjectorAspect[T <: RawModule, M <: RawModule]( * @return */ final def toAnnotation(modules: Iterable[M], circuit: String, moduleNames: Seq[String]): AnnotationSeq = { - val dynamicContext = new DynamicContext() + val dynamicContext = new DynamicContext(annotationsInAspect) // Add existing module names into the namespace. If injection logic instantiates new modules // which would share the same name, they will get uniquified accordingly moduleNames.foreach { n => diff --git a/src/main/scala/chisel3/stage/ChiselAnnotations.scala b/src/main/scala/chisel3/stage/ChiselAnnotations.scala index 04246fc5..b071c60f 100644 --- a/src/main/scala/chisel3/stage/ChiselAnnotations.scala +++ b/src/main/scala/chisel3/stage/ChiselAnnotations.scala @@ -56,11 +56,7 @@ case class ChiselGeneratorAnnotation(gen: () => RawModule) extends NoTargetAnnot /** Run elaboration on the Chisel module generator function stored by this [[firrtl.annotations.Annotation]] */ - def elaborate: AnnotationSeq = { - val (circuit, dut) = Builder.build(Module(gen())) - Seq(ChiselCircuitAnnotation(circuit), DesignAnnotation(dut)) - } - + def elaborate: AnnotationSeq = (new chisel3.stage.phases.Elaborate).transform(Seq(this)) } object ChiselGeneratorAnnotation extends HasShellOptions { diff --git a/src/main/scala/chisel3/stage/phases/AspectPhase.scala b/src/main/scala/chisel3/stage/phases/AspectPhase.scala index 1a27b8fa..72965861 100644 --- a/src/main/scala/chisel3/stage/phases/AspectPhase.scala +++ b/src/main/scala/chisel3/stage/phases/AspectPhase.scala @@ -29,7 +29,7 @@ class AspectPhase extends Phase { case other => Seq(other) } if(dut.isDefined) { - val newAnnotations = aspects.flatMap { _.resolveAspect(dut.get) } + val newAnnotations = aspects.flatMap { _.resolveAspect(dut.get, remainingAnnotations) } remainingAnnotations ++ newAnnotations } else annotations } diff --git a/src/main/scala/chisel3/stage/phases/Elaborate.scala b/src/main/scala/chisel3/stage/phases/Elaborate.scala index df39b66b..e8f2623e 100644 --- a/src/main/scala/chisel3/stage/phases/Elaborate.scala +++ b/src/main/scala/chisel3/stage/phases/Elaborate.scala @@ -2,15 +2,13 @@ package chisel3.stage.phases -import java.io.{PrintWriter, StringWriter} - -import chisel3.ChiselException -import chisel3.internal.ErrorLog +import chisel3.Module import chisel3.internal.ExceptionHelpers.ThrowableHelpers -import chisel3.stage.{ChiselGeneratorAnnotation, ChiselOptions} +import chisel3.internal.{Builder, DynamicContext} +import chisel3.stage.{ChiselCircuitAnnotation, ChiselGeneratorAnnotation, ChiselOptions, DesignAnnotation} import firrtl.AnnotationSeq +import firrtl.options.Phase import firrtl.options.Viewer.view -import firrtl.options.{OptionsException, Phase} /** Elaborate all [[chisel3.stage.ChiselGeneratorAnnotation]]s into [[chisel3.stage.ChiselCircuitAnnotation]]s. */ @@ -22,8 +20,9 @@ class Elaborate extends Phase { override def invalidates(a: Phase) = false def transform(annotations: AnnotationSeq): AnnotationSeq = annotations.flatMap { - case a: ChiselGeneratorAnnotation => try { - a.elaborate + case ChiselGeneratorAnnotation(gen) => try { + val (circuit, dut) = Builder.build(Module(gen()), new DynamicContext(annotations)) + Seq(ChiselCircuitAnnotation(circuit), DesignAnnotation(dut)) } catch { /* if any throwable comes back and we're in "stack trace trimming" mode, then print an error and trim the stack trace */ diff --git a/src/main/scala/chisel3/util/experimental/getAnnotations.scala b/src/main/scala/chisel3/util/experimental/getAnnotations.scala new file mode 100644 index 00000000..dc9b75ee --- /dev/null +++ b/src/main/scala/chisel3/util/experimental/getAnnotations.scala @@ -0,0 +1,9 @@ +package chisel3.util.experimental + +import chisel3.internal.Builder +import firrtl.AnnotationSeq + +object getAnnotations { + /** Returns the global Annotations */ + def apply(): AnnotationSeq = Builder.annotationSeq +} diff --git a/src/test/scala/chiselTests/Module.scala b/src/test/scala/chiselTests/Module.scala index 932c94a5..bc9c524a 100644 --- a/src/test/scala/chiselTests/Module.scala +++ b/src/test/scala/chiselTests/Module.scala @@ -3,8 +3,10 @@ package chiselTests import chisel3._ -import chisel3.stage.ChiselStage import chisel3.experimental.DataMirror +import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage, NoRunFirrtlCompilerAnnotation} +import firrtl.annotations.NoTargetAnnotation +import firrtl.options.Unserializable class SimpleIO extends Bundle { val in = Input(UInt(32.W)) @@ -140,6 +142,17 @@ class ModuleSpec extends ChiselPropSpec with Utils { assert(checkModule(this)) }) } + + property("object chisel3.util.experimental.getAnnotations should return current annotations.") { + case class DummyAnnotation() extends NoTargetAnnotation with Unserializable + (new ChiselStage).transform(Seq( + ChiselGeneratorAnnotation(() => new RawModule { + assert(chisel3.util.experimental.getAnnotations().contains(DummyAnnotation())) + }), + DummyAnnotation(), + NoRunFirrtlCompilerAnnotation)) + } + property("DataMirror.modulePorts should work") { ChiselStage.elaborate(new Module { val io = IO(new Bundle { }) -- cgit v1.2.3 From 43de37fdd48bcb6bbc861553715b06be3c67f6bf Mon Sep 17 00:00:00 2001 From: Jerry Zhao Date: Mon, 1 Mar 2021 11:39:33 -0800 Subject: Fix conversions between DecoupledIO and IrrevocableIO (#1781) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- src/main/scala/chisel3/util/Decoupled.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/chisel3/util/Decoupled.scala b/src/main/scala/chisel3/util/Decoupled.scala index 006001ec..33682020 100644 --- a/src/main/scala/chisel3/util/Decoupled.scala +++ b/src/main/scala/chisel3/util/Decoupled.scala @@ -108,7 +108,7 @@ object Decoupled @chiselName def apply[T <: Data](irr: IrrevocableIO[T]): DecoupledIO[T] = { require(DataMirror.directionOf(irr.bits) == Direction.Output, "Only safe to cast produced Irrevocable bits to Decoupled.") - val d = Wire(new DecoupledIO(irr.bits)) + val d = Wire(new DecoupledIO(chiselTypeOf(irr.bits))) d.bits := irr.bits d.valid := irr.valid irr.ready := d.ready @@ -139,7 +139,7 @@ object Irrevocable */ def apply[T <: Data](dec: DecoupledIO[T]): IrrevocableIO[T] = { require(DataMirror.directionOf(dec.bits) == Direction.Input, "Only safe to cast consumed Decoupled bits to Irrevocable.") - val i = Wire(new IrrevocableIO(dec.bits)) + val i = Wire(new IrrevocableIO(chiselTypeOf(dec.bits))) dec.bits := i.bits dec.valid := i.valid i.ready := dec.ready -- cgit v1.2.3 From 73184b7374bf4e7bccd609093c9614a5b95ea690 Mon Sep 17 00:00:00 2001 From: chrisbaldwin2 Date: Tue, 2 Mar 2021 15:12:17 -0500 Subject: Adding ChiselEnum Documentation Entry (#1795) * Adding ChiselEnum Documentation Entry Added documentation for the ChiselEnum type with verified examples * Fixed some doc ambiguity and repeated emitVerilog calls * Added ChiselStage and commented out package definition since packages cannot be declared in single files * Fixed issue with ChiselStage not being able to generate a module with parameters and bad package imports * Opps on not adding _ after import * Update docs/src/explanations/chisel-enum.md Co-authored-by: Megan Wachs * Update docs/src/explanations/chisel-enum.md Co-authored-by: Megan Wachs * Modified Bundle for ci and made changes to select naming scheme * Update docs/src/explanations/chisel-enum.md Co-authored-by: Megan Wachs * Update docs/src/explanations/chisel-enum.md Co-authored-by: Megan Wachs * Update docs/src/explanations/chisel-enum.md Co-authored-by: Megan Wachs * Update docs/src/explanations/chisel-enum.md Co-authored-by: Megan Wachs * Added missing backticks * Added space around error block quote * Fixed md paragraph in code * Update docs/src/explanations/chisel-enum.md Co-authored-by: Megan Wachs * Update docs/src/explanations/chisel-enum.md Co-authored-by: Schuyler Eldridge * Update docs/src/explanations/chisel-enum.md Co-authored-by: Schuyler Eldridge * Update docs/src/explanations/chisel-enum.md Co-authored-by: Schuyler Eldridge * Update docs/src/explanations/chisel-enum.md Co-authored-by: Megan Wachs * Fixed some comments and formatting Co-authored-by: Megan Wachs Co-authored-by: Schuyler Eldridge --- docs/src/explanations/chisel-enum.md | 134 +++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 docs/src/explanations/chisel-enum.md diff --git a/docs/src/explanations/chisel-enum.md b/docs/src/explanations/chisel-enum.md new file mode 100644 index 00000000..c4ee6af6 --- /dev/null +++ b/docs/src/explanations/chisel-enum.md @@ -0,0 +1,134 @@ +# ChiselEnum + +The ChiselEnum type can be used to reduce the chance of error when encoding mux selectors, opcodes, and functional unit operations. In contrast with`Chisel.util.Enum`, `ChiselEnum` are subclasses of `Data`, which means that they can be used to define fields in `Bundle`s, including in `IO`s. + + +## Functionality and Examples + +```scala mdoc +// Imports used in the following examples +import chisel3._ +import chisel3.util._ +import chisel3.stage.ChiselStage +import chisel3.experimental.ChiselEnum +``` + +Below we see ChiselEnum being used as mux select for a RISC-V core. While wrapping the object in a package is not required, it is highly recommended as it allows for the type to be used in multiple files more easily. + +```scala mdoc +// package CPUTypes { + object AluMux1Sel extends ChiselEnum { + val selectRS1, selectPC = Value + /** How the values will be mapped + "selectRS1" -> 0.U, + "selectPC" -> 1.U + */ + } +// } +``` + +Here we see a mux using the AluMux1Sel to select between different inputs. + +```scala mdoc +import AluMux1Sel._ + +class AluMux1Bundle extends Bundle { + val aluMux1Sel = Input( AluMux1Sel() ) + val rs1Out = Input(Bits(32.W)) + val pcOut = Input(Bits(32.W)) + val aluMux1Out = Output(Bits(32.W)) +} + +class AluMux1File extends Module { + val io = IO(new AluMux1Bundle) + + // Default value for aluMux1Out + io.aluMux1Out := 0.U + + switch (io.aluMux1Sel) { + is (selectRS1) { + io.aluMux1Out := io.rs1Out + } + is (selectPC) { + io.aluMux1Out := io.pcOut + } + } +} +``` +```scala mdoc:verilog +ChiselStage.emitVerilog(new AluMux1File ) +``` + +ChiselEnum also allows for the user to define variables by passing in the value shown below. Note that the value must be increasing or else + + > chisel3.internal.ChiselException: Exception thrown when elaborating ChiselGeneratorAnnotation + +is thrown during Verilog generation. + +```scala mdoc +object Opcode extends ChiselEnum { + val load = Value(0x03.U) // i "load" -> 000_0011 + val imm = Value(0x13.U) // i "imm" -> 001_0011 + val auipc = Value(0x17.U) // u "auipc" -> 001_0111 + val store = Value(0x23.U) // s "store" -> 010_0011 + val reg = Value(0x33.U) // r "reg" -> 011_0011 + val lui = Value(0x37.U) // u "lui" -> 011_0111 + val br = Value(0x63.U) // b "br" -> 110_0011 + val jalr = Value(0x67.U) // i "jalr" -> 110_0111 + val jal = Value(0x6F.U) // j "jal" -> 110_1111 +} +``` + +The user can 'jump' to a value and continue incrementing by passing a start point then using a regular Value assignment. + +```scala mdoc +object BranchFunct3 extends ChiselEnum { + val beq, bne = Value + val blt = Value(4.U) + val bge, bltu, bgeu = Value + /** How the values will be mapped + "beq" -> 0.U, + "bne" -> 1.U, + "blt" -> 4.U, + "bge" -> 5.U, + "bltu" -> 6.U, + "bgeu" -> 7.U + */ +} +``` + +## Testing + +When testing your modules, the `.Type` and `.litValue` attributes allow for the the objects to be passed as parameters and for the value to be converted to BigInt type. Note that BigInts cannot be casted to Int with `.asInstanceOf[Int]`, they use their own methods like `toInt`. Please review the [scala.math.BigInt](https://www.scala-lang.org/api/2.12.5/scala/math/BigInt.html) page for more details! + +```scala mdoc +def expectedSel(sel: AluMux1Sel.Type): Boolean = sel match { + case AluMux1Sel.selectRS1 => (sel.litValue == 0) + case AluMux1Sel.selectPC => (sel.litValue == 1) + case _ => false +} +``` + +The ChiselEnum type also has methods `.all` and `.getWidth` where `all` returns all of the enum instances and `getWidth` returns the width of the hardware type. + +## Workarounds + +As of 2/26/2021, the width of the values is always inferred. To work around this, you can add an extra `Value` that forces the width that is desired. This is shown in the example below, where we add a field `ukn` to force the width to be 3 bits wide: + +```scala mdoc +object StoreFunct3 extends ChiselEnum { + val sb, sh, sw = Value + val ukn = Value(7.U) + /** How the values will be mapped + "sb" -> 0.U, + "sh" -> 1.U, + "sw" -> 2.U + */ +} +``` + +Signed values are not supported so if you want the value signed, you must cast the UInt with `.asSInt`. + +## Additional Resources + +The ChiselEnum type is much more powerful than stated above. It allows for Sequence, Vec, and Bundle assignments, as well as a `.next` operation to allow for stepping through sequential states and an `.isValid` for checking that a hardware value is a valid `Value`. The source code for the ChiselEnum can be found [here](https://github.com/chipsalliance/chisel3/blob/2a96767097264eade18ff26e1d8bce192383a190/core/src/main/scala/chisel3/StrongEnum.scala) in the class `EnumFactory`. Examples of the ChiselEnum operations can be found [here](https://github.com/chipsalliance/chisel3/blob/dd6871b8b3f2619178c2a333d9d6083805d99e16/src/test/scala/chiselTests/StrongEnum.scala). -- cgit v1.2.3 From 36e722394cef921967232c63c6e50f5d6bbc0d26 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Wed, 3 Mar 2021 11:15:31 -0800 Subject: Add header for chisel-enum.md (#1800) --- docs/src/explanations/chisel-enum.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/src/explanations/chisel-enum.md b/docs/src/explanations/chisel-enum.md index c4ee6af6..cb017239 100644 --- a/docs/src/explanations/chisel-enum.md +++ b/docs/src/explanations/chisel-enum.md @@ -1,3 +1,9 @@ +--- +layout: docs +title: "Enumerations" +section: "chisel3" +--- + # ChiselEnum The ChiselEnum type can be used to reduce the chance of error when encoding mux selectors, opcodes, and functional unit operations. In contrast with`Chisel.util.Enum`, `ChiselEnum` are subclasses of `Data`, which means that they can be used to define fields in `Bundle`s, including in `IO`s. -- cgit v1.2.3 From 9ea57e031f834841609a30031ddab08fe4f47029 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Date: Thu, 11 Mar 2021 18:25:54 -0300 Subject: Import memory files inline for Verilog generation (#1805) This annotation adds memory import with inline generation for the emmiter. Supports both readmemh and readmemb statements based on argument.--- .gitignore | 3 + .../util/experimental/LoadMemoryTransform.scala | 82 +++++++++++++++++++++- .../scala/chiselTests/LoadMemoryFromFileSpec.scala | 53 +++++++++++++- 3 files changed, 136 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 5e727151..5da2d258 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ test_run_dir *~ \#*\# .\#* +.metals +.bloop +metals.sbt diff --git a/src/main/scala/chisel3/util/experimental/LoadMemoryTransform.scala b/src/main/scala/chisel3/util/experimental/LoadMemoryTransform.scala index d91d97b7..93981485 100644 --- a/src/main/scala/chisel3/util/experimental/LoadMemoryTransform.scala +++ b/src/main/scala/chisel3/util/experimental/LoadMemoryTransform.scala @@ -40,7 +40,7 @@ case class ChiselLoadMemoryAnnotation[T <: Data]( } -/** [[loadMemoryFromFile]] is an annotation generator that helps with loading a memory from a text file. This relies on +/** [[loadMemoryFromFile]] is an annotation generator that helps with loading a memory from a text file as a bind module. This relies on * Verilator and Verilog's `\$readmemh` or `\$readmemb`. The [[https://github.com/freechipsproject/treadle Treadle * backend]] can also recognize this annotation and load memory at run-time. * @@ -116,6 +116,86 @@ object loadMemoryFromFile { } } + +/** [[loadMemoryFromFileInline]] is an annotation generator that helps with loading a memory from a text file inlined in + * the Verilog module. This relies on Verilator and Verilog's `\$readmemh` or `\$readmemb`. + * The [[https://github.com/freechipsproject/treadle Treadlebackend]] can also recognize this annotation and load memory at run-time. + * + * This annotation, when the FIRRTL compiler runs, triggers the [[MemoryFileInlineAnnotation]] that will add Verilog + * directives inlined to the module enabling the specified memories to be initialized from files. + * The module supports both `hex` and `bin` files by passing the appropriate [[MemoryLoadFileType.FileType]] argument with + * [[MemoryLoadFileType.Hex]] or [[MemoryLoadFileType.Binary]]. Hex is the default. + * + * ==Example module== + * + * Consider a simple Module containing a memory: + * {{{ + * import chisel3._ + * class UsesMem(memoryDepth: Int, memoryType: Data) extends Module { + * val io = IO(new Bundle { + * val address = Input(UInt(memoryType.getWidth.W)) + * val value = Output(memoryType) + * }) + * val memory = Mem(memoryDepth, memoryType) + * io.value := memory(io.address) + * } + * }}} + * + * ==Above module with annotation== + * + * To load this memory from the file `/workspace/workdir/mem1.hex.txt` just add an import and annotate the memory: + * {{{ + * import chisel3._ + * import chisel3.util.experimental.loadMemoryFromFileInline // <<-- new import here + * class UsesMem(memoryDepth: Int, memoryType: Data) extends Module { + * val io = IO(new Bundle { + * val address = Input(UInt(memoryType.getWidth.W)) + * val value = Output(memoryType) + * }) + * val memory = Mem(memoryDepth, memoryType) + * io.value := memory(io.address) + * loadMemoryFromFileInline(memory, "/workspace/workdir/mem1.hex.txt") // <<-- Note the annotation here + * } + * }}} + * + * ==Example file format== + * + * A memory file should consist of ASCII text in either hex or binary format. The following example shows such a + * file formatted to use hex: + * {{{ + * 0 + * 7 + * d + * 15 + * }}} + * + * A binary file can be similarly constructed. + * Chisel does not validate the file format or existence. It is supposed to be in a path accessible by the synthesis + * tool together with the generated Verilog. + * + * @see Chisel3 Wiki entry on + * [[https://github.com/freechipsproject/chisel3/wiki/Chisel-Memories#loading-memories-in-simulation "Loading Memories + * in Simulation"]] + */ +object loadMemoryFromFileInline { + + + /** Annotate a memory such that it can be initialized inline using a file + * @param memory the memory + * @param fileName the file used for initialization + * @param hexOrBinary whether the file uses a hex or binary number representation + */ + def apply[T <: Data]( + memory: MemBase[T], + fileName: String, + hexOrBinary: MemoryLoadFileType.FileType = MemoryLoadFileType.Hex + ): Unit = { + annotate(new ChiselAnnotation { + override def toFirrtl = MemoryFileInlineAnnotation(memory.toTarget, fileName, hexOrBinary) + }) + } +} + /** This transform only is activated if Verilog is being generated (determined by presence of the proper emit * annotation) when activated it creates additional Verilog files that contain modules bound to the modules that * contain an initializable memory diff --git a/src/test/scala/chiselTests/LoadMemoryFromFileSpec.scala b/src/test/scala/chiselTests/LoadMemoryFromFileSpec.scala index 3be649e1..8a998496 100644 --- a/src/test/scala/chiselTests/LoadMemoryFromFileSpec.scala +++ b/src/test/scala/chiselTests/LoadMemoryFromFileSpec.scala @@ -6,7 +6,7 @@ import java.io.File import chisel3._ import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage} -import chisel3.util.experimental.loadMemoryFromFile +import chisel3.util.experimental.{loadMemoryFromFile,loadMemoryFromFileInline} import chisel3.util.log2Ceil import firrtl.FirrtlExecutionSuccess import firrtl.annotations.MemoryLoadFileType @@ -33,6 +33,26 @@ class UsesThreeMems(memoryDepth: Int, memoryType: Data) extends Module { io.value3 := memory3(io.address) } +class UsesThreeMemsInline(memoryDepth: Int, memoryType: Data, memoryFile: String, hexOrBinary: MemoryLoadFileType.FileType) extends Module { + val io = IO(new Bundle { + val address = Input(UInt(memoryType.getWidth.W)) + val value1 = Output(memoryType) + val value2 = Output(memoryType) + val value3 = Output(memoryType) + }) + + val memory1 = Mem(memoryDepth, memoryType) + val memory2 = Mem(memoryDepth, memoryType) + val memory3 = Mem(memoryDepth, memoryType) + loadMemoryFromFileInline(memory1, memoryFile, hexOrBinary) + loadMemoryFromFileInline(memory2, memoryFile, hexOrBinary) + loadMemoryFromFileInline(memory3, memoryFile, hexOrBinary) + + io.value1 := memory1(io.address) + io.value2 := memory2(io.address) + io.value3 := memory3(io.address) +} + class UsesMem(memoryDepth: Int, memoryType: Data) extends Module { val io = IO(new Bundle { val address = Input(UInt(memoryType.getWidth.W)) @@ -205,4 +225,35 @@ class LoadMemoryFromFileSpec extends AnyFreeSpec with Matchers { file.delete() } + "Module with more than one hex memory inline should work" in { + val testDirName = "test_run_dir/load_three_memory_spec_inline" + + val result = (new ChiselStage).execute( + args = Array("-X", "verilog", "--target-dir", testDirName), + annotations = Seq(ChiselGeneratorAnnotation(() => new UsesThreeMemsInline(memoryDepth = 8, memoryType = UInt(16.W), "./testmem.h", MemoryLoadFileType.Hex))) + ) + val dir = new File(testDirName) + val file = new File(dir, s"UsesThreeMemsInline.v") + file.exists() should be (true) + val fileText = io.Source.fromFile(file).getLines().mkString("\n") + fileText should include (s"""$$readmemh("./testmem.h", memory1);""") + fileText should include (s"""$$readmemh("./testmem.h", memory2);""") + fileText should include (s"""$$readmemh("./testmem.h", memory3);""") + } + + "Module with more than one bin memory inline should work" in { + val testDirName = "test_run_dir/load_three_memory_spec_inline" + + val result = (new ChiselStage).execute( + args = Array("-X", "verilog", "--target-dir", testDirName), + annotations = Seq(ChiselGeneratorAnnotation(() => new UsesThreeMemsInline(memoryDepth = 8, memoryType = UInt(16.W), "testmem.bin", MemoryLoadFileType.Binary))) + ) + val dir = new File(testDirName) + val file = new File(dir, s"UsesThreeMemsInline.v") + file.exists() should be (true) + val fileText = io.Source.fromFile(file).getLines().mkString("\n") + fileText should include (s"""$$readmemb("testmem.bin", memory1);""") + fileText should include (s"""$$readmemb("testmem.bin", memory2);""") + fileText should include (s"""$$readmemb("testmem.bin", memory3);""") + } } -- cgit v1.2.3 From e80e9a3ba775efd2121b17a39b6d712e1cf8fac2 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Wed, 10 Feb 2021 17:19:36 -0800 Subject: [plugin] Split ChiselComponent into its own file --- .../chisel3/internal/plugin/ChiselComponent.scala | 214 +++++++++++++++++++++ .../chisel3/internal/plugin/ChiselPlugin.scala | 213 +------------------- 2 files changed, 217 insertions(+), 210 deletions(-) create mode 100644 plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala new file mode 100644 index 00000000..f86b8322 --- /dev/null +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.internal.plugin + +import scala.collection.mutable +import scala.reflect.internal.Flags +import scala.tools.nsc +import scala.tools.nsc.{Global, Phase} +import scala.tools.nsc.plugins.PluginComponent +import scala.tools.nsc.transform.TypingTransformers + +// The component of the chisel plugin. Not sure exactly what the difference is between +// a Plugin and a PluginComponent. +class ChiselComponent(val global: Global) extends PluginComponent with TypingTransformers { + import global._ + val runsAfter: List[String] = List[String]("typer") + val phaseName: String = "chiselcomponent" + def newPhase(_prev: Phase): ChiselComponentPhase = new ChiselComponentPhase(_prev) + class ChiselComponentPhase(prev: Phase) extends StdPhase(prev) { + override def name: String = phaseName + def apply(unit: CompilationUnit): Unit = { + // This plugin doesn't work on Scala 2.11. Rather than complicate the sbt build flow, + // instead we just check the version and if its an early Scala version, the plugin does nothing + if(scala.util.Properties.versionNumberString.split('.')(1).toInt >= 12) { + unit.body = new MyTypingTransformer(unit).transform(unit.body) + } + } + } + + class MyTypingTransformer(unit: CompilationUnit) + extends TypingTransformer(unit) { + + private def shouldMatchGen(bases: Tree*): Type => Boolean = { + val cache = mutable.HashMap.empty[Type, Boolean] + val baseTypes = bases.map(inferType) + + // If subtype of one of the base types, it's a match! + def terminate(t: Type): Boolean = baseTypes.exists(t <:< _) + + // Recurse through subtype hierarchy finding containers + // Seen is only updated when we recurse into type parameters, thus it is typically small + def recShouldMatch(s: Type, seen: Set[Type]): Boolean = { + def outerMatches(t: Type): Boolean = { + val str = t.toString + str.startsWith("Option[") || str.startsWith("Iterable[") + } + if (terminate(s)) { + true + } else if (seen.contains(s)) { + false + } else if (outerMatches(s)) { + // These are type parameters, loops *are* possible here + recShouldMatch(s.typeArgs.head, seen + s) + } else { + // This is the standard inheritance hierarchy, Scalac catches loops here + s.parents.exists( p => recShouldMatch(p, seen) ) + } + } + + // If doesn't match container pattern, exit early + def earlyExit(t: Type): Boolean = { + !(t.matchesPattern(inferType(tq"Iterable[_]")) || t.matchesPattern(inferType(tq"Option[_]"))) + } + + // Return function so that it captures the cache + { q: Type => + cache.getOrElseUpdate(q, { + // First check if a match, then check early exit, then recurse + if(terminate(q)){ + true + } else if(earlyExit(q)) { + false + } else { + recShouldMatch(q, Set.empty) + } + }) + } + } + + + private val shouldMatchData : Type => Boolean = shouldMatchGen(tq"chisel3.Data") + private val shouldMatchDataOrMem : Type => Boolean = shouldMatchGen(tq"chisel3.Data", tq"chisel3.MemBase[_]") + private val shouldMatchModule : Type => Boolean = shouldMatchGen(tq"chisel3.experimental.BaseModule") + + // Given a type tree, infer the type and return it + private def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe + + // Indicates whether a ValDef is properly formed to get name + private def okVal(dd: ValDef): Boolean = { + + // These were found through trial and error + def okFlags(mods: Modifiers): Boolean = { + val badFlags = Set( + Flag.PARAM, + Flag.SYNTHETIC, + Flag.DEFERRED, + Flags.TRIEDCOOKING, + Flags.CASEACCESSOR, + Flags.PARAMACCESSOR + ) + badFlags.forall{ x => !mods.hasFlag(x)} + } + + // Ensure expression isn't null, as you can't call `null.autoName("myname")` + val isNull = dd.rhs match { + case Literal(Constant(null)) => true + case _ => false + } + + okFlags(dd.mods) && !isNull && dd.rhs != EmptyTree + } + // TODO Unify with okVal + private def okUnapply(dd: ValDef): Boolean = { + + // These were found through trial and error + def okFlags(mods: Modifiers): Boolean = { + val badFlags = Set( + Flag.PARAM, + Flag.DEFERRED, + Flags.TRIEDCOOKING, + Flags.CASEACCESSOR, + Flags.PARAMACCESSOR + ) + val goodFlags = Set( + Flag.SYNTHETIC, + Flag.ARTIFACT + ) + goodFlags.forall(f => mods.hasFlag(f)) && badFlags.forall(f => !mods.hasFlag(f)) + } + + // Ensure expression isn't null, as you can't call `null.autoName("myname")` + val isNull = dd.rhs match { + case Literal(Constant(null)) => true + case _ => false + } + val tpe = inferType(dd.tpt) + definitions.isTupleType(tpe) && okFlags(dd.mods) && !isNull && dd.rhs != EmptyTree + } + + private def findUnapplyNames(tree: Tree): Option[List[String]] = { + val applyArgs: Option[List[Tree]] = tree match { + case Match(_, List(CaseDef(_, _, Apply(_, args)))) => Some(args) + case _ => None + } + applyArgs.flatMap { args => + var ok = true + val result = mutable.ListBuffer[String]() + args.foreach { + case Ident(TermName(name)) => result += name + // Anything unexpected and we abort + case _ => ok = false + } + if (ok) Some(result.toList) else None + } + } + + // Whether this val is directly enclosed by a Bundle type + private def inBundle(dd: ValDef): Boolean = { + dd.symbol.logicallyEnclosingMember.thisType <:< inferType(tq"chisel3.Bundle") + } + + private def stringFromTermName(name: TermName): String = + name.toString.trim() // Remove trailing space (Scalac implementation detail) + + // Method called by the compiler to modify source tree + override def transform(tree: Tree): Tree = tree match { + // Check if a subtree is a candidate + case dd @ ValDef(mods, name, tpt, rhs) if okVal(dd) => + val tpe = inferType(tpt) + // If a Data and in a Bundle, just get the name but not a prefix + if (shouldMatchData(tpe) && inBundle(dd)) { + val str = stringFromTermName(name) + val newRHS = transform(rhs) // chisel3.internal.plugin.autoNameRecursively + val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + } + // If a Data or a Memory, get the name and a prefix + else if (shouldMatchDataOrMem(tpe)) { + val str = stringFromTermName(name) + val newRHS = transform(rhs) + val prefixed = q"chisel3.experimental.prefix.apply[$tpt](name=$str)(f=$newRHS)" + val named = q"chisel3.internal.plugin.autoNameRecursively($str)($prefixed)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + // If an instance, just get a name but no prefix + } else if (shouldMatchModule(tpe)) { + val str = stringFromTermName(name) + val newRHS = transform(rhs) + val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + } else { + // Otherwise, continue + super.transform(tree) + } + case dd @ ValDef(mods, name, tpt, rhs @ Match(_, _)) if okUnapply(dd) => + val tpe = inferType(tpt) + val fieldsOfInterest: List[Boolean] = tpe.typeArgs.map(shouldMatchData) + // Only transform if at least one field is of interest + if (fieldsOfInterest.reduce(_ || _)) { + findUnapplyNames(rhs) match { + case Some(names) => + val onames: List[Option[String]] = fieldsOfInterest.zip(names).map { case (ok, name) => if (ok) Some(name) else None } + val named = q"chisel3.internal.plugin.autoNameRecursivelyProduct($onames)($rhs)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + case None => // It's not clear how this could happen but we don't want to crash + super.transform(tree) + } + } else { + super.transform(tree) + } + // Otherwise, continue + case _ => super.transform(tree) + } + } +} \ No newline at end of file diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala index 59be7588..f8a1752c 100644 --- a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala @@ -1,15 +1,10 @@ -// See LICENSE for license details. +// SPDX-License-Identifier: Apache-2.0 package chisel3.internal.plugin import scala.tools.nsc -import nsc.{Global, Phase} -import nsc.plugins.Plugin -import nsc.plugins.PluginComponent -import scala.reflect.internal.Flags -import scala.tools.nsc.transform.TypingTransformers - -import scala.collection.mutable +import nsc.Global +import nsc.plugins.{Plugin, PluginComponent} // The plugin to be run by the Scala compiler during compilation of Chisel code class ChiselPlugin(val global: Global) extends Plugin { @@ -18,206 +13,4 @@ class ChiselPlugin(val global: Global) extends Plugin { val components: List[PluginComponent] = List[PluginComponent](new ChiselComponent(global)) } -// The component of the chisel plugin. Not sure exactly what the difference is between -// a Plugin and a PluginComponent. -class ChiselComponent(val global: Global) extends PluginComponent with TypingTransformers { - import global._ - val runsAfter: List[String] = List[String]("typer") - val phaseName: String = "chiselcomponent" - def newPhase(_prev: Phase): ChiselComponentPhase = new ChiselComponentPhase(_prev) - class ChiselComponentPhase(prev: Phase) extends StdPhase(prev) { - override def name: String = phaseName - def apply(unit: CompilationUnit): Unit = { - // This plugin doesn't work on Scala 2.11. Rather than complicate the sbt build flow, - // instead we just check the version and if its an early Scala version, the plugin does nothing - if(scala.util.Properties.versionNumberString.split('.')(1).toInt >= 12) { - unit.body = new MyTypingTransformer(unit).transform(unit.body) - } - } - } - - class MyTypingTransformer(unit: CompilationUnit) - extends TypingTransformer(unit) { - - private def shouldMatchGen(bases: Tree*): Type => Boolean = { - val cache = mutable.HashMap.empty[Type, Boolean] - val baseTypes = bases.map(inferType) - - // If subtype of one of the base types, it's a match! - def terminate(t: Type): Boolean = baseTypes.exists(t <:< _) - - // Recurse through subtype hierarchy finding containers - // Seen is only updated when we recurse into type parameters, thus it is typically small - def recShouldMatch(s: Type, seen: Set[Type]): Boolean = { - def outerMatches(t: Type): Boolean = { - val str = t.toString - str.startsWith("Option[") || str.startsWith("Iterable[") - } - if (terminate(s)) { - true - } else if (seen.contains(s)) { - false - } else if (outerMatches(s)) { - // These are type parameters, loops *are* possible here - recShouldMatch(s.typeArgs.head, seen + s) - } else { - // This is the standard inheritance hierarchy, Scalac catches loops here - s.parents.exists( p => recShouldMatch(p, seen) ) - } - } - - // If doesn't match container pattern, exit early - def earlyExit(t: Type): Boolean = { - !(t.matchesPattern(inferType(tq"Iterable[_]")) || t.matchesPattern(inferType(tq"Option[_]"))) - } - - // Return function so that it captures the cache - { q: Type => - cache.getOrElseUpdate(q, { - // First check if a match, then check early exit, then recurse - if(terminate(q)){ - true - } else if(earlyExit(q)) { - false - } else { - recShouldMatch(q, Set.empty) - } - }) - } - } - - - private val shouldMatchData : Type => Boolean = shouldMatchGen(tq"chisel3.Data") - private val shouldMatchDataOrMem : Type => Boolean = shouldMatchGen(tq"chisel3.Data", tq"chisel3.MemBase[_]") - private val shouldMatchModule : Type => Boolean = shouldMatchGen(tq"chisel3.experimental.BaseModule") - - // Given a type tree, infer the type and return it - private def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe - // Indicates whether a ValDef is properly formed to get name - private def okVal(dd: ValDef): Boolean = { - - // These were found through trial and error - def okFlags(mods: Modifiers): Boolean = { - val badFlags = Set( - Flag.PARAM, - Flag.SYNTHETIC, - Flag.DEFERRED, - Flags.TRIEDCOOKING, - Flags.CASEACCESSOR, - Flags.PARAMACCESSOR - ) - badFlags.forall{ x => !mods.hasFlag(x)} - } - - // Ensure expression isn't null, as you can't call `null.autoName("myname")` - val isNull = dd.rhs match { - case Literal(Constant(null)) => true - case _ => false - } - - okFlags(dd.mods) && !isNull && dd.rhs != EmptyTree - } - // TODO Unify with okVal - private def okUnapply(dd: ValDef): Boolean = { - - // These were found through trial and error - def okFlags(mods: Modifiers): Boolean = { - val badFlags = Set( - Flag.PARAM, - Flag.DEFERRED, - Flags.TRIEDCOOKING, - Flags.CASEACCESSOR, - Flags.PARAMACCESSOR - ) - val goodFlags = Set( - Flag.SYNTHETIC, - Flag.ARTIFACT - ) - goodFlags.forall(f => mods.hasFlag(f)) && badFlags.forall(f => !mods.hasFlag(f)) - } - - // Ensure expression isn't null, as you can't call `null.autoName("myname")` - val isNull = dd.rhs match { - case Literal(Constant(null)) => true - case _ => false - } - val tpe = inferType(dd.tpt) - definitions.isTupleType(tpe) && okFlags(dd.mods) && !isNull && dd.rhs != EmptyTree - } - - private def findUnapplyNames(tree: Tree): Option[List[String]] = { - val applyArgs: Option[List[Tree]] = tree match { - case Match(_, List(CaseDef(_, _, Apply(_, args)))) => Some(args) - case _ => None - } - applyArgs.flatMap { args => - var ok = true - val result = mutable.ListBuffer[String]() - args.foreach { - case Ident(TermName(name)) => result += name - // Anything unexpected and we abort - case _ => ok = false - } - if (ok) Some(result.toList) else None - } - } - - // Whether this val is directly enclosed by a Bundle type - private def inBundle(dd: ValDef): Boolean = { - dd.symbol.logicallyEnclosingMember.thisType <:< inferType(tq"chisel3.Bundle") - } - - private def stringFromTermName(name: TermName): String = - name.toString.trim() // Remove trailing space (Scalac implementation detail) - - // Method called by the compiler to modify source tree - override def transform(tree: Tree): Tree = tree match { - // Check if a subtree is a candidate - case dd @ ValDef(mods, name, tpt, rhs) if okVal(dd) => - val tpe = inferType(tpt) - // If a Data and in a Bundle, just get the name but not a prefix - if (shouldMatchData(tpe) && inBundle(dd)) { - val str = stringFromTermName(name) - val newRHS = transform(rhs) // chisel3.internal.plugin.autoNameRecursively - val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" - treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) - } - // If a Data or a Memory, get the name and a prefix - else if (shouldMatchDataOrMem(tpe)) { - val str = stringFromTermName(name) - val newRHS = transform(rhs) - val prefixed = q"chisel3.experimental.prefix.apply[$tpt](name=$str)(f=$newRHS)" - val named = q"chisel3.internal.plugin.autoNameRecursively($str)($prefixed)" - treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) - // If an instance, just get a name but no prefix - } else if (shouldMatchModule(tpe)) { - val str = stringFromTermName(name) - val newRHS = transform(rhs) - val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" - treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) - } else { - // Otherwise, continue - super.transform(tree) - } - case dd @ ValDef(mods, name, tpt, rhs @ Match(_, _)) if okUnapply(dd) => - val tpe = inferType(tpt) - val fieldsOfInterest: List[Boolean] = tpe.typeArgs.map(shouldMatchData) - // Only transform if at least one field is of interest - if (fieldsOfInterest.reduce(_ || _)) { - findUnapplyNames(rhs) match { - case Some(names) => - val onames: List[Option[String]] = fieldsOfInterest.zip(names).map { case (ok, name) => if (ok) Some(name) else None } - val named = q"chisel3.internal.plugin.autoNameRecursivelyProduct($onames)($rhs)" - treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) - case None => // It's not clear how this could happen but we don't want to crash - super.transform(tree) - } - } else { - super.transform(tree) - } - // Otherwise, continue - case _ => super.transform(tree) - } - } -} -- cgit v1.2.3 From 1494231212425fd09f915d819102ca5cdef0dfcf Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 11 Feb 2021 18:12:48 -0800 Subject: [plugin] Implement autoclonetype in the compiler plugin --- core/src/main/scala/chisel3/Aggregate.scala | 56 +++++-- core/src/main/scala/chisel3/internal/Builder.scala | 8 + .../test/scala/chiselTests/AutoClonetypeSpec.scala | 1 + .../scala/chiselTests/AutoNestedCloneSpec.scala | 1 + .../MissingCloneBindingExceptionSpec.scala | 55 +++++++ .../chisel3/internal/plugin/BundleComponent.scala | 125 ++++++++++++++ .../chisel3/internal/plugin/ChiselComponent.scala | 7 +- .../chisel3/internal/plugin/ChiselPlugin.scala | 6 +- src/test/scala/chisel3/testers/TestUtils.scala | 12 ++ src/test/scala/chiselTests/AutoClonetypeSpec.scala | 179 +++++++++++++++++---- .../scala/chiselTests/AutoNestedCloneSpec.scala | 73 +++++---- .../MissingCloneBindingExceptionSpec.scala | 55 ------- 12 files changed, 443 insertions(+), 135 deletions(-) create mode 120000 no-plugin-tests/src/test/scala/chiselTests/AutoClonetypeSpec.scala create mode 120000 no-plugin-tests/src/test/scala/chiselTests/AutoNestedCloneSpec.scala create mode 100644 no-plugin-tests/src/test/scala/chiselTests/MissingCloneBindingExceptionSpec.scala create mode 100644 plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala delete mode 100644 src/test/scala/chiselTests/MissingCloneBindingExceptionSpec.scala diff --git a/core/src/main/scala/chisel3/Aggregate.scala b/core/src/main/scala/chisel3/Aggregate.scala index 6942313e..30276230 100644 --- a/core/src/main/scala/chisel3/Aggregate.scala +++ b/core/src/main/scala/chisel3/Aggregate.scala @@ -798,6 +798,13 @@ abstract class Bundle(implicit compileOptions: CompileOptions) extends Record { case _ => None } + /** Indicates if a concrete Bundle class was compiled using the compiler plugin + * + * Used for optimizing Chisel's performance and testing Chisel itself + * @note This should not be used in user code! + */ + protected def _usingPlugin: Boolean = false + // Memoize the outer instance for autoclonetype, especially where this is context-dependent // (like the outer module or enclosing Bundles). private var _outerInst: Option[Object] = None @@ -808,7 +815,33 @@ abstract class Bundle(implicit compileOptions: CompileOptions) extends Record { private val _containingModule: Option[BaseModule] = Builder.currentModule private val _containingBundles: Seq[Bundle] = Builder.updateBundleStack(this) - override def cloneType : this.type = { + private def checkClone(clone: Bundle): Unit = { + for ((name, field) <- elements) { + if (clone.elements(name) eq field) { + throw new AutoClonetypeException( + s"Automatically cloned $clone has field '$name' aliased with base $this." + + " In the future, this will be solved automatically by the compiler plugin." + + " For now, ensure Chisel types used in the Bundle definition are passed through constructor arguments," + + " or wrapped in Input(...), Output(...), or Flipped(...) if appropriate." + + " As a last resort, you can override cloneType manually." + ) + } + } + + } + + override def cloneType: this.type = { + val clone = _cloneTypeImpl.asInstanceOf[this.type] + checkClone(clone) + clone + } + + /** Implementation of cloneType using runtime reflection. This should _never_ be overridden or called in user-code + * + * @note This is overridden by the compiler plugin (it is never called when using the plugin) + */ + protected def _cloneTypeImpl: Bundle = { + assert(Builder.allowReflectiveAutoCloneType, "reflective autoclonetype is disallowed, this should only happen in testing") // This attempts to infer constructor and arguments to clone this Bundle subtype without // requiring the user explicitly overriding cloneType. import scala.language.existentials @@ -816,24 +849,19 @@ abstract class Bundle(implicit compileOptions: CompileOptions) extends Record { val clazz = this.getClass - def autoClonetypeError(desc: String): Nothing = { - throw new AutoClonetypeException(s"Unable to automatically infer cloneType on $clazz: $desc") - } + def autoClonetypeError(desc: String): Nothing = + throw new AutoClonetypeException( + s"Unable to automatically infer cloneType on $clazz. " + + "cloneType is now implemented by the Chisel compiler plugin so please ensure you are using it in your build. " + + "If you cannot use the compiler plugin or you are using it and you still see this message, please file an issue and let us know. " + + s"For those not using the plugin, here is the 'runtime reflection' cloneType error message: $desc" + ) def validateClone(clone: Bundle, equivDiagnostic: String): Unit = { if (!clone.typeEquivalent(this)) { autoClonetypeError(s"Automatically cloned $clone not type-equivalent to base $this. " + equivDiagnostic) } - - for ((name, field) <- elements) { - if (clone.elements(name) eq field) { - autoClonetypeError(s"Automatically cloned $clone has field $name aliased with base $this." + - " In the future, this can be solved by wrapping the field in Field(...)," + - " see https://github.com/freechipsproject/chisel3/pull/909." + - " For now, ensure Chisel types used in the Bundle definition are passed through constructor arguments," + - " or wrapped in Input(...), Output(...), or Flipped(...) if appropriate.") - } - } + checkClone(clone) } val mirror = runtimeMirror(clazz.getClassLoader) diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index e95384cd..b1016a2e 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -311,6 +311,8 @@ private[chisel3] class DynamicContext(val annotationSeq: AnnotationSeq) { val components = ArrayBuffer[Component]() val annotations = ArrayBuffer[ChiselAnnotation]() var currentModule: Option[BaseModule] = None + // This is only used for testing, it can be removed if the plugin becomes mandatory + var allowReflectiveAutoCloneType = true /** Contains a mapping from a elaborated module to their aspect * Set by [[ModuleAspect]] @@ -530,6 +532,12 @@ private[chisel3] object Builder extends LazyLogging { dynamicContext.currentReset = newReset } + // This should only be used for testing + def allowReflectiveAutoCloneType: Boolean = dynamicContext.allowReflectiveAutoCloneType + def allowReflectiveAutoCloneType_=(value: Boolean): Unit = { + dynamicContext.allowReflectiveAutoCloneType = value + } + def forcedClock: Clock = currentClock.getOrElse( throwException("Error: No implicit clock.") ) diff --git a/no-plugin-tests/src/test/scala/chiselTests/AutoClonetypeSpec.scala b/no-plugin-tests/src/test/scala/chiselTests/AutoClonetypeSpec.scala new file mode 120000 index 00000000..ae3a7597 --- /dev/null +++ b/no-plugin-tests/src/test/scala/chiselTests/AutoClonetypeSpec.scala @@ -0,0 +1 @@ +../../../../../src/test/scala/chiselTests/AutoClonetypeSpec.scala \ No newline at end of file diff --git a/no-plugin-tests/src/test/scala/chiselTests/AutoNestedCloneSpec.scala b/no-plugin-tests/src/test/scala/chiselTests/AutoNestedCloneSpec.scala new file mode 120000 index 00000000..4ff3aa4f --- /dev/null +++ b/no-plugin-tests/src/test/scala/chiselTests/AutoNestedCloneSpec.scala @@ -0,0 +1 @@ +../../../../../src/test/scala/chiselTests/AutoNestedCloneSpec.scala \ No newline at end of file diff --git a/no-plugin-tests/src/test/scala/chiselTests/MissingCloneBindingExceptionSpec.scala b/no-plugin-tests/src/test/scala/chiselTests/MissingCloneBindingExceptionSpec.scala new file mode 100644 index 00000000..28673495 --- /dev/null +++ b/no-plugin-tests/src/test/scala/chiselTests/MissingCloneBindingExceptionSpec.scala @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests +import Chisel.ChiselException +import chisel3.stage.ChiselStage +import org.scalatest._ +import org.scalatest.matchers.should.Matchers + +class MissingCloneBindingExceptionSpec extends ChiselFlatSpec with Matchers with Utils { + behavior of "missing cloneType in Chisel3" + ( the [ChiselException] thrownBy extractCause[ChiselException] { + import chisel3._ + + class Test extends Module { + class TestIO(w: Int) extends Bundle { + val a = Input(Vec(4, UInt(w.W))) + } + + val io = IO(new TestIO(32)) + } + + class TestTop extends Module { + val io = IO(new Bundle {}) + + val subs = VecInit(Seq.fill(2) { + Module(new Test).io + }) + } + + ChiselStage.elaborate(new TestTop) + }).getMessage should include("make all parameters immutable") + + behavior of "missing cloneType in Chisel2" + ( the [ChiselException] thrownBy extractCause[ChiselException] { + import Chisel._ + + class Test extends Module { + class TestIO(w: Int) extends Bundle { + val a = Vec(4, UInt(width = w)).asInput + } + + val io = IO(new TestIO(32)) + } + + class TestTop extends Module { + val io = IO(new Bundle {}) + + val subs = Vec.fill(2) { + Module(new Test).io + } + } + + ChiselStage.elaborate(new TestTop) + }).getMessage should include("make all parameters immutable") +} diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala new file mode 100644 index 00000000..0711e08c --- /dev/null +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.internal.plugin + +import scala.collection.mutable +import scala.tools.nsc +import scala.tools.nsc.{Global, Phase} +import scala.tools.nsc.plugins.PluginComponent +import scala.tools.nsc.symtab.Flags +import scala.tools.nsc.transform.TypingTransformers + +// TODO This component could also implement val elements in Bundles +private[plugin] class BundleComponent(val global: Global) extends PluginComponent with TypingTransformers { + import global._ + + val phaseName: String = "chiselbundlephase" + val runsAfter: List[String] = "typer" :: Nil + def newPhase(prev: Phase): Phase = new BundlePhase(prev) + + private class BundlePhase(prev: Phase) extends StdPhase(prev) { + override def name: String = phaseName + def apply(unit: CompilationUnit): Unit = { + // This plugin doesn't work on Scala 2.11 nor Scala 3. Rather than complicate the sbt build flow, + // instead we just check the version and if its an early Scala version, the plugin does nothing + val scalaVersion = scala.util.Properties.versionNumberString.split('.') + if (scalaVersion(0).toInt == 2 && scalaVersion(1).toInt >= 12) { + unit.body = new MyTypingTransformer(unit).transform(unit.body) + } + } + } + + private class MyTypingTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { + + def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe + + val bundleTpe = inferType(tq"chisel3.Bundle") + val dataTpe = inferType(tq"chisel3.Data") + + // Not cached because it should only be run once per class (thus once per Type) + def isBundle(sym: Symbol): Boolean = sym.tpe <:< bundleTpe + + val isDataCache = new mutable.HashMap[Type, Boolean] + // Cached because this is run on every argument to every Bundle + def isData(sym: Symbol): Boolean = isDataCache.getOrElseUpdate(sym.tpe, sym.tpe <:< dataTpe) + + def cloneTypeFull(tree: Tree): Tree = + localTyper.typed(q"chisel3.experimental.DataMirror.internal.chiselTypeClone[${tree.tpe}]($tree)") + + def isNullaryMethodNamed(name: String, defdef: DefDef): Boolean = + defdef.name.decodedName.toString == name && defdef.tparams.isEmpty && defdef.vparamss.isEmpty + + def getConstructorAndParams(body: List[Tree]): (Option[DefDef], Seq[Symbol]) = { + val paramAccessors = mutable.ListBuffer[Symbol]() + var primaryConstructor: Option[DefDef] = None + body.foreach { + case acc: ValDef if acc.symbol.isParamAccessor => + paramAccessors += acc.symbol + case con: DefDef if con.symbol.isPrimaryConstructor => + primaryConstructor = Some(con) + case d: DefDef if isNullaryMethodNamed("_cloneTypeImpl", d) => + val msg = "Users cannot override _cloneTypeImpl. Let the compiler plugin generate it. If you must, override cloneType instead." + global.globalError(d.pos, msg) + case d: DefDef if isNullaryMethodNamed("_usingPlugin", d) => + val msg = "Users cannot override _usingPlugin, it is for the compiler plugin's use only." + global.globalError(d.pos, msg) + case _ => + } + (primaryConstructor, paramAccessors.toList) + } + + + override def transform(tree: Tree): Tree = tree match { + + case bundle: ClassDef if isBundle(bundle.symbol) && !bundle.mods.hasFlag(Flag.ABSTRACT) => + + // ==================== Generate _cloneTypeImpl ==================== + val (con, params) = getConstructorAndParams(bundle.impl.body) + if (con.isEmpty) { + global.reporter.warning(bundle.pos, "Unable to determine primary constructor!") + return super.transform(tree) + } + val constructor = con.get + + val thiz = gen.mkAttributedThis(bundle.symbol) + + // The params have spaces after them (Scalac implementation detail) + val paramLookup: String => Symbol = params.map(sym => sym.name.toString.trim -> sym).toMap + + // Create a this. for each field matching order of constructor arguments + // List of Lists because we can have multiple parameter lists + val conArgs: List[List[Tree]] = + constructor.vparamss.map(_.map { vp => + val p = paramLookup(vp.name.toString) + // Make this. + val select = gen.mkAttributedSelect(thiz, p) + // Clone any Data parameters to avoid field aliasing, need full clone to include direction + if (isData(vp.symbol)) cloneTypeFull(select) else select + }) + + val ttpe = Ident(bundle.symbol) + val neww = localTyper.typed(New(ttpe, conArgs)) + + // Create the symbol for the method and have it be associated with the Bundle class + val cloneTypeSym = bundle.symbol.newMethod(TermName("_cloneTypeImpl"), bundle.symbol.pos.focus, Flag.OVERRIDE | Flag.PROTECTED) + // Handwritten cloneTypes don't have the Method flag set, unclear if it matters + cloneTypeSym.resetFlag(Flags.METHOD) + // Need to set the type to chisel3.Bundle for the override to work + cloneTypeSym.setInfo(NullaryMethodType(bundleTpe)) + + val cloneTypeImpl = localTyper.typed(DefDef(cloneTypeSym, neww)) + + // ==================== Generate _usingPlugin ==================== + // Unclear why quasiquotes work here but didn't for cloneTypeSym, maybe they could. + val usingPlugin = localTyper.typed(q"override protected def _usingPlugin: Boolean = true") + + val withMethods = deriveClassDef(bundle) { t => + deriveTemplate(t)(_ :+ cloneTypeImpl :+ usingPlugin) + } + + super.transform(localTyper.typed(withMethods)) + + case _ => super.transform(tree) + } + } +} diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala index f86b8322..b1302ba3 100644 --- a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala @@ -19,9 +19,10 @@ class ChiselComponent(val global: Global) extends PluginComponent with TypingTra class ChiselComponentPhase(prev: Phase) extends StdPhase(prev) { override def name: String = phaseName def apply(unit: CompilationUnit): Unit = { - // This plugin doesn't work on Scala 2.11. Rather than complicate the sbt build flow, + // This plugin doesn't work on Scala 2.11 nor Scala 3. Rather than complicate the sbt build flow, // instead we just check the version and if its an early Scala version, the plugin does nothing - if(scala.util.Properties.versionNumberString.split('.')(1).toInt >= 12) { + val scalaVersion = scala.util.Properties.versionNumberString.split('.') + if (scalaVersion(0).toInt == 2 && scalaVersion(1).toInt >= 12) { unit.body = new MyTypingTransformer(unit).transform(unit.body) } } @@ -211,4 +212,4 @@ class ChiselComponent(val global: Global) extends PluginComponent with TypingTra case _ => super.transform(tree) } } -} \ No newline at end of file +} diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala index f8a1752c..d5eb21cc 100644 --- a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala @@ -10,7 +10,9 @@ import nsc.plugins.{Plugin, PluginComponent} class ChiselPlugin(val global: Global) extends Plugin { val name = "chiselplugin" val description = "Plugin for Chisel 3 Hardware Description Language" - val components: List[PluginComponent] = List[PluginComponent](new ChiselComponent(global)) + val components: List[PluginComponent] = List[PluginComponent]( + new ChiselComponent(global), + new BundleComponent(global) + ) } - diff --git a/src/test/scala/chisel3/testers/TestUtils.scala b/src/test/scala/chisel3/testers/TestUtils.scala index 12712bf7..c72c779a 100644 --- a/src/test/scala/chisel3/testers/TestUtils.scala +++ b/src/test/scala/chisel3/testers/TestUtils.scala @@ -3,10 +3,22 @@ package chisel3.testers import TesterDriver.Backend +import chisel3.{Bundle, RawModule} +import chisel3.internal.firrtl.Circuit +import chisel3.stage.ChiselStage import firrtl.AnnotationSeq object TestUtils { // Useful because TesterDriver.Backend is chisel3 package private def containsBackend(annos: AnnotationSeq): Boolean = annos.collectFirst { case b: Backend => b }.isDefined + + // Allows us to check that the compiler plugin cloneType is actually working + val usingPlugin: Boolean = (new Bundle { def check = _usingPlugin }).check + def elaborateNoReflectiveAutoCloneType(f: => RawModule): Circuit = { + ChiselStage.elaborate { + chisel3.internal.Builder.allowReflectiveAutoCloneType = !usingPlugin + f + } + } } diff --git a/src/test/scala/chiselTests/AutoClonetypeSpec.scala b/src/test/scala/chiselTests/AutoClonetypeSpec.scala index b791297d..a3da109a 100644 --- a/src/test/scala/chiselTests/AutoClonetypeSpec.scala +++ b/src/test/scala/chiselTests/AutoClonetypeSpec.scala @@ -3,7 +3,7 @@ package chiselTests import chisel3._ -import chisel3.stage.ChiselStage +import chisel3.testers.TestUtils class BundleWithIntArg(val i: Int) extends Bundle { val out = UInt(i.W) @@ -65,10 +65,14 @@ class NestedAnonymousBundle extends Bundle { // Not necessarily good style (and not necessarily recommended), but allowed to preserve compatibility. class BundleWithArgumentField(val x: Data, val y: Data) extends Bundle +// TODO all `.suggestNames` are due to https://github.com/chipsalliance/chisel3/issues/1802 class AutoClonetypeSpec extends ChiselFlatSpec with Utils { + val usingPlugin: Boolean = TestUtils.usingPlugin + val elaborate = TestUtils.elaborateNoReflectiveAutoCloneType _ + "Bundles with Scala args" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(new Bundle{}) + elaborate { new Module { + val io = IO(new Bundle{}).suggestName("io") val myWire = Wire(new BundleWithIntArg(8)) assert(myWire.i == 8) @@ -76,8 +80,8 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { } "Bundles with Scala implicit args" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(new Bundle{}) + elaborate { new Module { + val io = IO(new Bundle{}).suggestName("io") implicit val implicitInt: Int = 4 val myWire = Wire(new BundleWithImplicit()) @@ -87,8 +91,8 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { } "Bundles with Scala explicit and impicit args" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(new Bundle{}) + elaborate { new Module { + val io = IO(new Bundle{}).suggestName("io") implicit val implicitInt: Int = 4 val myWire = Wire(new BundleWithArgAndImplicit(8)) @@ -99,16 +103,16 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { } "Subtyped Bundles" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(new Bundle{}) + elaborate { new Module { + val io = IO(new Bundle{}).suggestName("io") val myWire = Wire(new SubBundle(8, 4)) assert(myWire.i == 8) assert(myWire.i2 == 4) } } - ChiselStage.elaborate { new Module { - val io = IO(new Bundle{}) + elaborate { new Module { + val io = IO(new Bundle{}).suggestName("io") val myWire = Wire(new SubBundleVal(8, 4)) @@ -117,68 +121,84 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { } } } - "Subtyped Bundles that don't clone well" should "be caught" in { - a [ChiselException] should be thrownBy extractCause[ChiselException] { - ChiselStage.elaborate { new Module { - val io = IO(new Bundle{}) - val myWire = Wire(new SubBundleInvalid(8, 4)) - } } + def checkSubBundleInvalid() = { + elaborate { new Module { + val io = IO(new Bundle{}).suggestName("io") + val myWire = Wire(new SubBundleInvalid(8, 4)) + } } + } + if (usingPlugin) { + "Subtyped Bundles that don't clone well" should "be now be supported!" in { + checkSubBundleInvalid() + } + } else { + "Subtyped Bundles that don't clone well" should "be caught" in { + a [ChiselException] should be thrownBy extractCause[ChiselException] { + checkSubBundleInvalid() + } } } "Inner bundles with Scala args" should "not need clonetype" in { - ChiselStage.elaborate { new ModuleWithInner } + elaborate { new ModuleWithInner } } "Bundles with arguments as fields" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(Output(new BundleWithArgumentField(UInt(8.W), UInt(8.W)))) + elaborate { new Module { + val io = IO(Output(new BundleWithArgumentField(UInt(8.W), UInt(8.W)))).suggestName("io") io.x := 1.U io.y := 1.U } } } + it should "also work when giving directions to the fields" in { + elaborate { new Module { + val io = IO(new BundleWithArgumentField(Input(UInt(8.W)), Output(UInt(8.W)))).suggestName("io") + io.y := io.x + } } + } + "Bundles inside companion objects" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(Output(new CompanionObjectWithBundle.Inner)) + elaborate { new Module { + val io = IO(Output(new CompanionObjectWithBundle.Inner)).suggestName("io") io.data := 1.U } } } "Parameterized bundles inside companion objects" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(Output(new CompanionObjectWithBundle.ParameterizedInner(8))) + elaborate { new Module { + val io = IO(Output(new CompanionObjectWithBundle.ParameterizedInner(8))).suggestName("io") io.data := 1.U } } } "Nested directioned anonymous Bundles" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(new NestedAnonymousBundle) + elaborate { new Module { + val io = IO(new NestedAnonymousBundle).suggestName("io") val a = WireDefault(io) io.a.a := 1.U } } } "3.0 null compatibility" should "not need clonetype" in { - ChiselStage.elaborate { new Module { + elaborate { new Module { class InnerClassThing { def createBundle: Bundle = new Bundle { val a = Output(UInt(8.W)) } } - val io = IO((new InnerClassThing).createBundle) + val io = IO((new InnerClassThing).createBundle).suggestName("io") val a = WireDefault(io) } } } "Aliased fields" should "be caught" in { a [ChiselException] should be thrownBy extractCause[ChiselException] { - ChiselStage.elaborate { new Module { + elaborate { new Module { val bundleFieldType = UInt(8.W) val io = IO(Output(new Bundle { val a = bundleFieldType - })) + })).suggestName("io") io.a := 0.U } } } @@ -190,8 +210,8 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { val a = typeTuple._1 } - ChiselStage.elaborate { new Module { - val io = IO(Output(new BadBundle(UInt(8.W), 1))) + elaborate { new Module { + val io = IO(Output(new BadBundle(UInt(8.W), 1))).suggestName("io") io.a := 0.U } } } @@ -204,7 +224,7 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { val x = Output(UInt(3.W)) })) } - ChiselStage.elaborate { new TestModule } + elaborate { new TestModule } } "Wrapped IO construction with parent references" should "not fail for autoclonetype" in { @@ -216,6 +236,97 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { val x = Output(UInt(blah.W)) })) } - ChiselStage.elaborate { new TestModule(3) } + elaborate { new TestModule(3) } + } + + "Autoclonetype" should "support Bundles with if-blocks" in { + class MyModule(n: Int) extends MultiIOModule { + val io = IO(new Bundle { + val in = Input(UInt(8.W)) + val out = Output(UInt(8.W)) + if (n > 4) { + println("Here we are!") + } + }) + io.out := io.in + } + elaborate(new MyModule(3)) + } + + // New tests from the plugin + if (usingPlugin) { + behavior of "Compiler Plugin Autoclonetype" + + it should "support Bundles with non-val parameters" in { + class MyBundle(i: Int) extends Bundle { + val foo = UInt(i.W) + } + elaborate { new MultiIOModule { + val in = IO(Input(new MyBundle(8))) + val out = IO(Output(new MyBundle(8))) + out := in + }} + } + + it should "support type-parameterized Bundles" in { + class MyBundle[T <: Data](gen: T) extends Bundle { + val foo = gen + } + elaborate { new MultiIOModule { + val in = IO(Input(new MyBundle(UInt(8.W)))) + val out = IO(Output(new MyBundle(UInt(8.W)))) + out := in + }} + } + + it should "support Bundles with non-val implicit parameters" in { + class MyBundle(implicit i: Int) extends Bundle { + val foo = UInt(i.W) + } + elaborate { new MultiIOModule { + implicit val x = 8 + val in = IO(Input(new MyBundle)) + val out = IO(Output(new MyBundle)) + out := in + }} + } + + it should "support Bundles with multiple parameter lists" in { + class MyBundle(i: Int)(j: Int, jj: Int)(k: UInt) extends Bundle { + val foo = UInt((i + j + jj + k.getWidth).W) + } + elaborate { + new MultiIOModule { + val in = IO(Input(new MyBundle(8)(8, 8)(UInt(8.W)))) + val out = IO(Output(new MyBundle(8)(8, 8)(UInt(8.W)))) + out := in + } + } + } + + it should "support Bundles that implement their own cloneType" in { + class MyBundle(i: Int) extends Bundle { + val foo = UInt(i.W) + override def cloneType = new MyBundle(i).asInstanceOf[this.type] + } + elaborate { new MultiIOModule { + val in = IO(Input(new MyBundle(8))) + val out = IO(Output(new MyBundle(8))) + out := in + }} + } + + it should "support Bundles that capture type parameters from their parent scope" in { + class MyModule[T <: Data](gen: T) extends MultiIOModule { + class MyBundle(n: Int) extends Bundle { + val foo = Vec(n, gen) + } + val in = IO(Input(new MyBundle(4))) + val out = IO(Output(new MyBundle(4))) + out := in + } + elaborate(new MyModule(UInt(8.W))) + } + } } diff --git a/src/test/scala/chiselTests/AutoNestedCloneSpec.scala b/src/test/scala/chiselTests/AutoNestedCloneSpec.scala index 8e40ad20..401766e2 100644 --- a/src/test/scala/chiselTests/AutoNestedCloneSpec.scala +++ b/src/test/scala/chiselTests/AutoNestedCloneSpec.scala @@ -1,10 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 package chiselTests -import Chisel.ChiselException -import org.scalatest._ import chisel3._ -import chisel3.stage.ChiselStage +import chisel3.testers.TestUtils import org.scalatest.matchers.should.Matchers class BundleWithAnonymousInner(val w: Int) extends Bundle { @@ -13,11 +11,15 @@ class BundleWithAnonymousInner(val w: Int) extends Bundle { } } +// TODO all `.suggestNames` are due to https://github.com/chipsalliance/chisel3/issues/1802 class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { + val usingPlugin: Boolean = TestUtils.usingPlugin + val elaborate = TestUtils.elaborateNoReflectiveAutoCloneType _ + behavior of "autoCloneType of inner Bundle in Chisel3" it should "clone a doubly-nested inner bundle successfully" in { - ChiselStage.elaborate { + elaborate { class Outer(val w: Int) extends Module { class Middle(val w: Int) { class InnerIOType extends Bundle { @@ -25,7 +27,7 @@ class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { } def getIO: InnerIOType = new InnerIOType } - val io = IO(new Bundle {}) + val io = IO(new Bundle {}).suggestName("io") val myWire = Wire((new Middle(w)).getIO) } new Outer(2) @@ -33,9 +35,9 @@ class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { } it should "clone an anonymous inner bundle successfully" in { - ChiselStage.elaborate { + elaborate { class TestTop(val w: Int) extends Module { - val io = IO(new Bundle {}) + val io = IO(new Bundle {}).suggestName("io") val myWire = Wire(new Bundle{ val a = UInt(w.W) }) } new TestTop(2) @@ -43,18 +45,18 @@ class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { } it should "pick the correct $outer instance for an anonymous inner bundle" in { - ChiselStage.elaborate { + elaborate { class Inner(val w: Int) extends Module { val io = IO(new Bundle{ val in = Input(UInt(w.W)) val out = Output(UInt(w.W)) - }) + }).suggestName("io") } class Outer(val w: Int) extends Module { val io = IO(new Bundle{ val in = Input(UInt(w.W)) val out = Output(UInt(w.W)) - }) + }).suggestName("io") val i = Module(new Inner(w)) val iw = Wire(chiselTypeOf(i.io)) iw <> io @@ -65,9 +67,9 @@ class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { } it should "clone an anonymous, bound, inner bundle of another bundle successfully" in { - ChiselStage.elaborate { + elaborate { class TestModule(w: Int) extends Module { - val io = IO(new BundleWithAnonymousInner(w) ) + val io = IO(new BundleWithAnonymousInner(w) ).suggestName("io") val w0 = WireDefault(io) val w1 = WireDefault(io.inner) } @@ -76,14 +78,14 @@ class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { } it should "clone an anonymous, inner bundle of a Module, bound to another bundle successfully" in { - ChiselStage.elaborate { + elaborate { class TestModule(w: Int) extends Module { val bun = new Bundle { val foo = UInt(w.W) } val io = IO(new Bundle { val inner = Input(bun) - }) + }).suggestName("io") val w0 = WireDefault(io) val w1 = WireDefault(io.inner) } @@ -92,31 +94,48 @@ class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { } it should "clone a double-nested anonymous Bundle" in { - ChiselStage.elaborate { + elaborate { class TestModule() extends Module { val io = IO(new Bundle { val inner = Input(new Bundle { val x = UInt(8.W) }) - }) + }).suggestName("io") } new TestModule() } } - // Test ignored because the compatibility null-inserting autoclonetype doesn't trip this - ignore should "fail on anonymous doubly-nested inner bundle with clear error" in { - intercept[ChiselException] { extractCause[ChiselException] { ChiselStage.elaborate { - class Outer(val w: Int) extends Module { - class Middle(val w: Int) { - def getIO: Bundle = new Bundle { - val in = Input(UInt(w.W)) + if (usingPlugin) { + // This works with the plugin, but is a null pointer exception when using reflective autoclonetype + it should "support an anonymous doubly-nested inner bundle" in { + elaborate { + class Outer(val w: Int) extends Module { + class Middle(val w: Int) { + def getIO: Bundle = new Bundle { + val in = Input(UInt(w.W)) + } } + val io = IO(new Bundle {}).suggestName("io") + val myWire = Wire((new Middle(w)).getIO) } - val io = IO(new Bundle {}) - val myWire = Wire((new Middle(w)).getIO) + new Outer(2) } - new Outer(2) - }}}.getMessage should include("Unable to determine instance") + } + + it should "support anonymous Inner bundles that capture type parameters from outer Bundles" in { + elaborate(new MultiIOModule { + class MyBundle[T <: Data](n: Int, gen: T) extends Bundle { + val foo = new Bundle { + val x = Input(Vec(n, gen)) + } + val bar = Output(Option(new { def mkBundle = new Bundle { val x = Vec(n, gen) }}).get.mkBundle) + } + val io = IO(new MyBundle(4, UInt(8.W))) + val myWire = WireInit(io.foo) + val myWire2 = WireInit(io.bar) + io.bar.x := io.foo.x + }) + } } } diff --git a/src/test/scala/chiselTests/MissingCloneBindingExceptionSpec.scala b/src/test/scala/chiselTests/MissingCloneBindingExceptionSpec.scala deleted file mode 100644 index 28673495..00000000 --- a/src/test/scala/chiselTests/MissingCloneBindingExceptionSpec.scala +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chiselTests -import Chisel.ChiselException -import chisel3.stage.ChiselStage -import org.scalatest._ -import org.scalatest.matchers.should.Matchers - -class MissingCloneBindingExceptionSpec extends ChiselFlatSpec with Matchers with Utils { - behavior of "missing cloneType in Chisel3" - ( the [ChiselException] thrownBy extractCause[ChiselException] { - import chisel3._ - - class Test extends Module { - class TestIO(w: Int) extends Bundle { - val a = Input(Vec(4, UInt(w.W))) - } - - val io = IO(new TestIO(32)) - } - - class TestTop extends Module { - val io = IO(new Bundle {}) - - val subs = VecInit(Seq.fill(2) { - Module(new Test).io - }) - } - - ChiselStage.elaborate(new TestTop) - }).getMessage should include("make all parameters immutable") - - behavior of "missing cloneType in Chisel2" - ( the [ChiselException] thrownBy extractCause[ChiselException] { - import Chisel._ - - class Test extends Module { - class TestIO(w: Int) extends Bundle { - val a = Vec(4, UInt(width = w)).asInput - } - - val io = IO(new TestIO(32)) - } - - class TestTop extends Module { - val io = IO(new Bundle {}) - - val subs = Vec.fill(2) { - Module(new Test).io - } - } - - ChiselStage.elaborate(new TestTop) - }).getMessage should include("make all parameters immutable") -} -- cgit v1.2.3 From a8d32388ffa8c29a3b0f9e78ab6cd917b92954cd Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 4 Mar 2021 18:24:08 -0800 Subject: [plugin] Stop autoclonetype stack traces when using plugin The compiler plugin obviates the need for using stack traces to determine outer objects in autoclonetype. When the plugin was used to compile a given Bundle, it will no longer collect a stack trace upon construction. This should have massive benefits to elaboration runtime. --- core/src/main/scala/chisel3/Aggregate.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/chisel3/Aggregate.scala b/core/src/main/scala/chisel3/Aggregate.scala index 30276230..c5a917fa 100644 --- a/core/src/main/scala/chisel3/Aggregate.scala +++ b/core/src/main/scala/chisel3/Aggregate.scala @@ -809,11 +809,11 @@ abstract class Bundle(implicit compileOptions: CompileOptions) extends Record { // (like the outer module or enclosing Bundles). private var _outerInst: Option[Object] = None - // For autoclonetype, record possible candidates for outer instance. + // For reflective autoclonetype, record possible candidates for outer instance. // _outerInst should always take precedence, since it should be propagated from the original // object which has the most accurate context. - private val _containingModule: Option[BaseModule] = Builder.currentModule - private val _containingBundles: Seq[Bundle] = Builder.updateBundleStack(this) + private val _containingModule: Option[BaseModule] = if (_usingPlugin) None else Builder.currentModule + private val _containingBundles: Seq[Bundle] = if (_usingPlugin) Nil else Builder.updateBundleStack(this) private def checkClone(clone: Bundle): Unit = { for ((name, field) <- elements) { -- cgit v1.2.3 From 3bea6167159737b379f37031c3beef27337be06d Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Fri, 12 Mar 2021 15:33:58 -0800 Subject: [plugin] Disable BundleComponent by default, add option to enable --- build.sbt | 2 ++ .../chisel3/internal/plugin/BundleComponent.scala | 15 +++++++++++++-- .../chisel3/internal/plugin/ChiselPlugin.scala | 21 ++++++++++++++++++++- src/test/scala/chiselTests/AutoClonetypeSpec.scala | 21 +++++++++++++++++++-- 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index 5c051ff5..42fcece3 100644 --- a/build.sbt +++ b/build.sbt @@ -179,6 +179,8 @@ lazy val chisel = (project in file(".")). mimaPreviousArtifacts := Set(), libraryDependencies += defaultVersions("treadle") % "test", scalacOptions in Test ++= Seq("-language:reflectiveCalls"), + // Only used in Test for 3.4.x, used in Compile in 3.5 + scalacOptions in Test += "-P:chiselplugin:useBundlePlugin", scalacOptions in Compile in doc ++= Seq( "-diagrams", "-groups", diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala index 0711e08c..96851e95 100644 --- a/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala @@ -10,7 +10,9 @@ import scala.tools.nsc.symtab.Flags import scala.tools.nsc.transform.TypingTransformers // TODO This component could also implement val elements in Bundles -private[plugin] class BundleComponent(val global: Global) extends PluginComponent with TypingTransformers { +private[plugin] class BundleComponent(val global: Global, arguments: ChiselPluginArguments) + extends PluginComponent + with TypingTransformers { import global._ val phaseName: String = "chiselbundlephase" @@ -23,8 +25,17 @@ private[plugin] class BundleComponent(val global: Global) extends PluginComponen // This plugin doesn't work on Scala 2.11 nor Scala 3. Rather than complicate the sbt build flow, // instead we just check the version and if its an early Scala version, the plugin does nothing val scalaVersion = scala.util.Properties.versionNumberString.split('.') - if (scalaVersion(0).toInt == 2 && scalaVersion(1).toInt >= 12) { + val scalaVersionOk = scalaVersion(0).toInt == 2 && scalaVersion(1).toInt >= 12 + if (scalaVersionOk && arguments.useBundlePlugin) { unit.body = new MyTypingTransformer(unit).transform(unit.body) + } else { + val reason = if (!scalaVersionOk) { + s"invalid Scala version '${scala.util.Properties.versionNumberString}'" + } else { + s"not enabled via '${arguments.useBundlePluginFullOpt}'" + } + // Enable this with scalacOption '-Ylog:chiselbundlephase' + global.log(s"Skipping BundleComponent on account of $reason.") } } } diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala index d5eb21cc..a0651e1d 100644 --- a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala @@ -6,13 +6,32 @@ import scala.tools.nsc import nsc.Global import nsc.plugins.{Plugin, PluginComponent} +private[plugin] case class ChiselPluginArguments(var useBundlePlugin: Boolean = false) { + def useBundlePluginOpt = "useBundlePlugin" + def useBundlePluginFullOpt = s"-P:chiselplugin:$useBundlePluginOpt" +} + // The plugin to be run by the Scala compiler during compilation of Chisel code class ChiselPlugin(val global: Global) extends Plugin { val name = "chiselplugin" val description = "Plugin for Chisel 3 Hardware Description Language" + private val arguments = ChiselPluginArguments() val components: List[PluginComponent] = List[PluginComponent]( new ChiselComponent(global), - new BundleComponent(global) + new BundleComponent(global, arguments) ) + + override def init(options: List[String], error: String => Unit): Boolean = { + for (option <- options) { + if (option == arguments.useBundlePluginOpt) { + arguments.useBundlePlugin = true + } else { + error(s"Option not understood: '$option'") + } + } + true + } + + } diff --git a/src/test/scala/chiselTests/AutoClonetypeSpec.scala b/src/test/scala/chiselTests/AutoClonetypeSpec.scala index a3da109a..a6e5562a 100644 --- a/src/test/scala/chiselTests/AutoClonetypeSpec.scala +++ b/src/test/scala/chiselTests/AutoClonetypeSpec.scala @@ -4,6 +4,7 @@ package chiselTests import chisel3._ import chisel3.testers.TestUtils +import chisel3.util.QueueIO class BundleWithIntArg(val i: Int) extends Bundle { val out = UInt(i.W) @@ -65,6 +66,11 @@ class NestedAnonymousBundle extends Bundle { // Not necessarily good style (and not necessarily recommended), but allowed to preserve compatibility. class BundleWithArgumentField(val x: Data, val y: Data) extends Bundle +// Needs to be top-level so that reflective autoclonetype works +class InheritingBundle extends QueueIO(UInt(8.W), 8) { + val error = Output(Bool()) +} + // TODO all `.suggestNames` are due to https://github.com/chipsalliance/chisel3/issues/1802 class AutoClonetypeSpec extends ChiselFlatSpec with Utils { val usingPlugin: Boolean = TestUtils.usingPlugin @@ -253,10 +259,21 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { elaborate(new MyModule(3)) } + behavior of "Compiler Plugin Autoclonetype" + + // Necessary test for 3.4.x, but we will break this (for non-plugin users) in 3.5 + it should "NOT break code that extends chisel3.util Bundles (whether they use the plugin or not)" in { + class MyModule extends MultiIOModule { + val io = IO(new InheritingBundle) + io.deq <> io.enq + io.count := 0.U + io.error := true.B + } + elaborate(new MyModule) + } + // New tests from the plugin if (usingPlugin) { - behavior of "Compiler Plugin Autoclonetype" - it should "support Bundles with non-val parameters" in { class MyBundle(i: Int) extends Bundle { val foo = UInt(i.W) -- cgit v1.2.3 From 96436ae018b3631decb8384ce404ada0daa5d645 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Mon, 15 Mar 2021 16:55:06 -0700 Subject: allowReflectiveAutoCloneType must work outside of Builder context (#1811) --- core/src/main/scala/chisel3/internal/Builder.scala | 8 ++++++-- src/test/scala/chiselTests/AutoClonetypeSpec.scala | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index b1016a2e..084bdc88 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -532,8 +532,12 @@ private[chisel3] object Builder extends LazyLogging { dynamicContext.currentReset = newReset } - // This should only be used for testing - def allowReflectiveAutoCloneType: Boolean = dynamicContext.allowReflectiveAutoCloneType + // This should only be used for testing, must be true outside of Builder context + def allowReflectiveAutoCloneType: Boolean = { + dynamicContextVar.value + .map(_.allowReflectiveAutoCloneType) + .getOrElse(true) + } def allowReflectiveAutoCloneType_=(value: Boolean): Unit = { dynamicContext.allowReflectiveAutoCloneType = value } diff --git a/src/test/scala/chiselTests/AutoClonetypeSpec.scala b/src/test/scala/chiselTests/AutoClonetypeSpec.scala index a6e5562a..e0e6b2f0 100644 --- a/src/test/scala/chiselTests/AutoClonetypeSpec.scala +++ b/src/test/scala/chiselTests/AutoClonetypeSpec.scala @@ -127,6 +127,10 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { } } } + "Autoclonetype" should "work outside of a builder context" in { + new BundleWithIntArg(8).cloneType + } + def checkSubBundleInvalid() = { elaborate { new Module { val io = IO(new Bundle{}).suggestName("io") -- cgit v1.2.3 From 492a71d6d4d3acef39f29345835637bca028a089 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Wed, 17 Mar 2021 14:00:27 -0400 Subject: Fix incorrect usage of emitFirrtl in test (#1817) Change a test to use emitChirrtl instead of emitFirrtl. This test isn't supposed to be running the Scala FIRRTL Compiler, but the latter method causes this to happen. Signed-off-by: Schuyler Eldridge --- .../scala/chiselTests/experimental/verification/VerificationSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/scala/chiselTests/experimental/verification/VerificationSpec.scala b/src/test/scala/chiselTests/experimental/verification/VerificationSpec.scala index 52293abb..fe642156 100644 --- a/src/test/scala/chiselTests/experimental/verification/VerificationSpec.scala +++ b/src/test/scala/chiselTests/experimental/verification/VerificationSpec.scala @@ -28,7 +28,7 @@ class VerificationSpec extends ChiselPropSpec { } property("basic equality check should work") { - val fir = ChiselStage.emitFirrtl(new VerificationModule) + val fir = ChiselStage.emitChirrtl(new VerificationModule) val lines = fir.split("\n").map(_.trim) assertContains(lines, "cover(clock, _T, UInt<1>(\"h1\"), \"\") @[VerificationSpec.scala 16:15]") assertContains(lines, "assume(clock, _T_2, UInt<1>(\"h1\"), \"\") @[VerificationSpec.scala 18:18]") -- cgit v1.2.3 From ac094a5e8f7fb8237b54d4d6b9e0b7b759b44ef7 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Thu, 18 Mar 2021 12:28:09 -0400 Subject: Don't toggle top.cpp clock and reset on same cycle (#1820) Change top.cpp to deassert reset one time unit before the clock asserts. This avoids a Verilator simultation issue in top.cpp where the eval() function is only called once per simultation loop. If the clock and reset are both changed and eval() is only called once, then any combinational update due to a change in reset is not visible to the sequential logic. This avoids issues where the downstream compilation utilities move synchronous reset logic outside of an always block that describes a synchronous reset flip flop. Reset now deasserts on time unit 10 and the clock ticks on time unit 11. h/t @albert-magyar Signed-off-by: Schuyler Eldridge --- src/main/resources/chisel3/top.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/resources/chisel3/top.cpp b/src/main/resources/chisel3/top.cpp index 4e9c1433..a3475e2f 100644 --- a/src/main/resources/chisel3/top.cpp +++ b/src/main/resources/chisel3/top.cpp @@ -47,8 +47,15 @@ int main(int argc, char** argv) { cout << "Starting simulation!\n"; while (!Verilated::gotFinish() && main_time < timeout) { - if (main_time > 10) { - top->reset = 0; // Deassert reset + // Deassert reset on timestep 10. This needs to occur before the clock + // asserts on timestep 11 because there is a single call to top->eval() in + // this loop. Verilator evaluates sequential logic (always blocks) before + // combinational logic during top->eval(). Staggering the reset update is + // necessary to produce the same simulation behavior independent of whether + // or not the generated Verilog puts synchronous reset logic inside or + // outside its associated always block. + if (main_time == 10) { + top->reset = 0; } if ((main_time % 10) == 1) { top->clock = 1; // Toggle clock @@ -92,4 +99,3 @@ int main(int argc, char** argv) { if (tfp) tfp->close(); #endif } - -- cgit v1.2.3 From 2a56c6540e914611ac12647e157aec4c5c595758 Mon Sep 17 00:00:00 2001 From: Boyang Han Date: Fri, 19 Mar 2021 01:28:56 +0800 Subject: Add toString method to BitPat (#1819) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- src/main/scala/chisel3/util/BitPat.scala | 7 +++++++ src/test/scala/chiselTests/util/BitPatSpec.scala | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 src/test/scala/chiselTests/util/BitPatSpec.scala diff --git a/src/main/scala/chisel3/util/BitPat.scala b/src/main/scala/chisel3/util/BitPat.scala index 12a421a0..5a40d312 100644 --- a/src/main/scala/chisel3/util/BitPat.scala +++ b/src/main/scala/chisel3/util/BitPat.scala @@ -119,4 +119,11 @@ sealed class BitPat(val value: BigInt, val mask: BigInt, width: Int) extends Sou !(this === that) } + override def toString = { + "BitPat(" + + (0 until width).map(i => + if (((mask >> i) & 1) == 1) if (((value >> i) & 1) == 1) "1" else "0" else "?" + ).reverse.reduce(_ + _) + + ")" + } } diff --git a/src/test/scala/chiselTests/util/BitPatSpec.scala b/src/test/scala/chiselTests/util/BitPatSpec.scala new file mode 100644 index 00000000..a6c0acf7 --- /dev/null +++ b/src/test/scala/chiselTests/util/BitPatSpec.scala @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.util + +import chisel3.util.BitPat +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + + +class BitPatSpec extends AnyFlatSpec with Matchers { + behavior of classOf[BitPat].toString + + it should "convert a BitPat to readable form" in { + val testPattern = "0" * 32 + "1" * 32 + "?" * 32 + "?01" * 32 + BitPat("b" + testPattern).toString should be (s"BitPat($testPattern)") + } +} -- cgit v1.2.3 From f1ad5b58e8a749d558758288d03ce75bf6b8ff9c Mon Sep 17 00:00:00 2001 From: Megan Wachs Date: Thu, 18 Mar 2021 16:47:58 -0700 Subject: Reorganize website docs (#1806) Updates to chisel3 documentation for website: * guard code examples with mdoc and fix errors encountered along the way * move some website content here vs splitting the content across two repos * Bring in the interval-types and loading memories content so that it will be visible from the website * remove all references to the wiki (deprecated) * Remove reference to Wiki from the README * fix tabbing and compile of chisel3-vs-chisel2 section * Appendix: faqs now guarded and compile * FAQs: move to resources section--- README.md | 1 - docs/README.md | 7 +- docs/src/appendix/appendix.md | 13 + docs/src/appendix/chisel3-vs-chisel2.md | 62 +-- docs/src/appendix/experimental-features.md | 68 ++- docs/src/cookbooks/cookbook.md | 471 +++++++++++++++++++++ docs/src/cookbooks/cookbooks.md | 15 + docs/src/cookbooks/troubleshooting.md | 64 +++ docs/src/developers/developers.md | 12 + docs/src/developers/sbt-subproject.md | 36 ++ docs/src/developers/style.md | 293 +++++++++++++ docs/src/developers/test-coverage.md | 23 + docs/src/explanations/annotations.md | 8 +- docs/src/explanations/bundles-and-vecs.md | 2 +- docs/src/explanations/combinational-circuits.md | 84 ++++ docs/src/explanations/data-types.md | 136 ++++++ docs/src/explanations/explanations.md | 39 ++ docs/src/explanations/functional-abstraction.md | 34 ++ .../src/explanations/functional-module-creation.md | 120 ++++++ .../src/explanations/interfaces-and-connections.md | 149 +++++++ docs/src/explanations/memories.md | 176 ++++++++ docs/src/explanations/modules.md | 138 ++++++ docs/src/explanations/motivation.md | 44 ++ docs/src/explanations/muxes-and-input-selection.md | 62 +++ docs/src/explanations/operators.md | 65 +++ .../polymorphism-and-parameterization.md | 237 +++++++++++ docs/src/explanations/ports.md | 67 +++ docs/src/explanations/printing.md | 137 ++++++ docs/src/explanations/reset.md | 179 ++++++++ docs/src/explanations/sequential-circuits.md | 50 +++ docs/src/explanations/supported-hardware.md | 11 + docs/src/explanations/unconnected-wires.md | 138 ++++++ docs/src/explanations/width-inference.md | 40 ++ docs/src/introduction.md | 41 ++ docs/src/resources/faqs.md | 284 +++++++++++++ docs/src/resources/resources.md | 17 + docs/src/wiki-deprecated/combinational-circuits.md | 81 ---- docs/src/wiki-deprecated/cookbook.md | 470 -------------------- docs/src/wiki-deprecated/data-types.md | 134 ------ docs/src/wiki-deprecated/developers.md | 11 - docs/src/wiki-deprecated/faqs.md | 246 ----------- docs/src/wiki-deprecated/functional-abstraction.md | 29 -- .../wiki-deprecated/functional-module-creation.md | 79 ---- .../wiki-deprecated/interfaces-and-connections.md | 144 ------- docs/src/wiki-deprecated/interval-type.md | 55 --- docs/src/wiki-deprecated/introduction.md | 64 --- docs/src/wiki-deprecated/memories.md | 170 -------- docs/src/wiki-deprecated/modules.md | 135 ------ .../wiki-deprecated/muxes-and-input-selection.md | 56 --- docs/src/wiki-deprecated/operators.md | 63 --- .../polymorphism-and-parameterization.md | 209 --------- docs/src/wiki-deprecated/ports.md | 67 --- docs/src/wiki-deprecated/printing.md | 130 ------ docs/src/wiki-deprecated/reset.md | 177 -------- docs/src/wiki-deprecated/resources.md | 14 - docs/src/wiki-deprecated/sbt-subproject.md | 36 -- docs/src/wiki-deprecated/sequential-circuits.md | 42 -- docs/src/wiki-deprecated/style.md | 275 ------------ docs/src/wiki-deprecated/supported-hardware.md | 8 - docs/src/wiki-deprecated/test-coverage.md | 21 - docs/src/wiki-deprecated/troubleshooting.md | 57 --- docs/src/wiki-deprecated/unconnected-wires.md | 112 ----- docs/src/wiki-deprecated/width-inference.md | 37 -- 63 files changed, 3284 insertions(+), 2961 deletions(-) create mode 100644 docs/src/appendix/appendix.md create mode 100644 docs/src/cookbooks/cookbook.md create mode 100644 docs/src/cookbooks/cookbooks.md create mode 100644 docs/src/cookbooks/troubleshooting.md create mode 100644 docs/src/developers/developers.md create mode 100644 docs/src/developers/sbt-subproject.md create mode 100644 docs/src/developers/style.md create mode 100644 docs/src/developers/test-coverage.md create mode 100644 docs/src/explanations/combinational-circuits.md create mode 100644 docs/src/explanations/data-types.md create mode 100644 docs/src/explanations/explanations.md create mode 100644 docs/src/explanations/functional-abstraction.md create mode 100644 docs/src/explanations/functional-module-creation.md create mode 100644 docs/src/explanations/interfaces-and-connections.md create mode 100644 docs/src/explanations/memories.md create mode 100644 docs/src/explanations/modules.md create mode 100644 docs/src/explanations/motivation.md create mode 100644 docs/src/explanations/muxes-and-input-selection.md create mode 100644 docs/src/explanations/operators.md create mode 100644 docs/src/explanations/polymorphism-and-parameterization.md create mode 100644 docs/src/explanations/ports.md create mode 100644 docs/src/explanations/printing.md create mode 100644 docs/src/explanations/reset.md create mode 100644 docs/src/explanations/sequential-circuits.md create mode 100644 docs/src/explanations/supported-hardware.md create mode 100644 docs/src/explanations/unconnected-wires.md create mode 100644 docs/src/explanations/width-inference.md create mode 100644 docs/src/introduction.md create mode 100644 docs/src/resources/faqs.md create mode 100644 docs/src/resources/resources.md delete mode 100644 docs/src/wiki-deprecated/combinational-circuits.md delete mode 100644 docs/src/wiki-deprecated/cookbook.md delete mode 100644 docs/src/wiki-deprecated/data-types.md delete mode 100644 docs/src/wiki-deprecated/developers.md delete mode 100644 docs/src/wiki-deprecated/faqs.md delete mode 100644 docs/src/wiki-deprecated/functional-abstraction.md delete mode 100644 docs/src/wiki-deprecated/functional-module-creation.md delete mode 100644 docs/src/wiki-deprecated/interfaces-and-connections.md delete mode 100644 docs/src/wiki-deprecated/interval-type.md delete mode 100644 docs/src/wiki-deprecated/introduction.md delete mode 100644 docs/src/wiki-deprecated/memories.md delete mode 100644 docs/src/wiki-deprecated/modules.md delete mode 100644 docs/src/wiki-deprecated/muxes-and-input-selection.md delete mode 100644 docs/src/wiki-deprecated/operators.md delete mode 100644 docs/src/wiki-deprecated/polymorphism-and-parameterization.md delete mode 100644 docs/src/wiki-deprecated/ports.md delete mode 100644 docs/src/wiki-deprecated/printing.md delete mode 100644 docs/src/wiki-deprecated/reset.md delete mode 100644 docs/src/wiki-deprecated/resources.md delete mode 100644 docs/src/wiki-deprecated/sbt-subproject.md delete mode 100644 docs/src/wiki-deprecated/sequential-circuits.md delete mode 100644 docs/src/wiki-deprecated/style.md delete mode 100644 docs/src/wiki-deprecated/supported-hardware.md delete mode 100644 docs/src/wiki-deprecated/test-coverage.md delete mode 100644 docs/src/wiki-deprecated/troubleshooting.md delete mode 100644 docs/src/wiki-deprecated/unconnected-wires.md delete mode 100644 docs/src/wiki-deprecated/width-inference.md diff --git a/README.md b/README.md index 9abe18f4..b9298520 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,6 @@ These simulation-based verification tools are available for Chisel: ### Useful Resources - [**Cheat Sheet**](https://github.com/freechipsproject/chisel-cheatsheet/releases/latest/download/chisel_cheatsheet.pdf), a 2-page reference of the base Chisel syntax and libraries -- [**Wiki**](https://github.com/chipsalliance/chisel3/wiki), which contains various feature-specific tutorials and frequently-asked questions. - [**ScalaDoc**](https://www.chisel-lang.org/api/latest/chisel3/index.html), a listing, description, and examples of the functionality exposed by Chisel - [**Gitter**](https://gitter.im/chipsalliance/chisel3), where you can ask questions or discuss anything Chisel - [**Website**](https://www.chisel-lang.org) ([source](https://github.com/freechipsproject/www.chisel-lang.org/)) diff --git a/docs/README.md b/docs/README.md index dfc7f934..2a34fc45 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,8 +8,7 @@ as part of the PR CI checks, forcing the documentation to remain current with the codebase. The `src` folder contains the source from which these are generated. -Previous Wiki documentation, now hosted by the website, is contained in the `src/wiki-deprecated` directory. -We are in the process of converting this documentation into the four categories as described in +Our documentation is organized into the four categories as described in [Divio's documentation system](https://documentation.divio.com/). The four documentation types are: @@ -22,9 +21,7 @@ Our documentation strategy for this repository is as follows: * Any new public API requires reference documentation. * Any new user-facing feature requires explanation documentation. * Any bugfixes, corner-cases, or answers to commonly asked questions requires a how-to guide. - * For now, tutorials are kept in a separate repository. We are working hosting them here. - * Old documentation is contained in the `src/wiki-deprecated` directory and is being incrementally converted to these - categories. + * Tutorials are kept in a separate repository. This documentation is hosted on the Chisel [website](https://www.chisel-lang.org). diff --git a/docs/src/appendix/appendix.md b/docs/src/appendix/appendix.md new file mode 100644 index 00000000..48106fe4 --- /dev/null +++ b/docs/src/appendix/appendix.md @@ -0,0 +1,13 @@ +--- +layout: docs +title: "Appendix" +section: "chisel3" +--- + +# Appendix + +This section covers some less-common Chisel topics. + +* [Differences between Chisel3 and Chisel2](chisel3-vs-chisel2) +* [Experimental Features](experimental-features) +* [Upgrading from Scala 2.11](upgrading-from-scala-2-11) diff --git a/docs/src/appendix/chisel3-vs-chisel2.md b/docs/src/appendix/chisel3-vs-chisel2.md index bfd20348..cafd29e3 100644 --- a/docs/src/appendix/chisel3-vs-chisel2.md +++ b/docs/src/appendix/chisel3-vs-chisel2.md @@ -5,6 +5,9 @@ section: "chisel3" --- # Chisel3 vs Chisel2 +```scala mdoc:invisible +import chisel3._ +``` ## Chisel2 Migration For those moving from Chisel2, there were some backwards incompatible changes @@ -12,55 +15,66 @@ and your RTL needs to be modified to work with Chisel3. The required modifications are: - Wire declaration style: - ```scala - val wire = UInt(width = 15) - ``` +```scala + val wire = UInt(width = 15) +``` becomes (in Chisel3): - ```scala - val wire = Wire(UInt(15.W)) - ``` +```scala mdoc:compile-only + val wire = Wire(UInt(15.W)) +``` - I/O declaration style: - ```scala +```scala val done = Bool(OUTPUT) - ``` +``` becomes (in Chisel3): - ```scala +```scala mdoc:compile-only val wire = Output(Bool()) - ``` +``` - Sequential memories: - ```scala +```scala mdoc:invisible +import chisel3._ +val enable = Bool() +``` + +```scala val addr = Reg(UInt()) val mem = Mem(UInt(8.W), 1024, seqRead = true) val dout = when(enable) { mem(addr) } - ``` +``` becomes (in Chisel3): - ```scala +```scala mdoc:compile-only val addr = UInt() val mem = SyncReadMem(1024, UInt(8.W)) val dout = mem.read(addr, enable) - ``` +``` Notice the address register is now internal to the SyncReadMem(), but the data will still return on the subsequent cycle. - - Generating Verilog with - ```scala + - Generating Verilog for a module: +```scala mdoc:invisible +import chisel3._ +class Hello extends RawModule +``` + +```scala object Hello { def main(args: Array[String]): Unit = { chiselMain(Array("--backend", "v"), () => Module(new Hello())) } } - ``` +``` becomes (in Chisel3): - ```scala +```scala mdoc:compile-only + import chisel3.stage.ChiselStage object Hello { def main(args: Array[String]): Unit = { - chisel3.Driver.execute(Array[String](), () => new Hello()) + (new ChiselStage).emitVerilog(new Hello()) } } - ``` +``` - Package changes: - Chisel.log2Ceil -> chisel3.util.log2Ceil @@ -143,14 +157,14 @@ hardware objects), based on specific imports. This allows designs to move from less strict front end checks (largely compatible with Chisel2), to stricter checking, on a file by file basis, by adjusting specific import statements. -```scala - import chisel3.core.ExplicitCompileOptions.Strict +```scala mdoc:compile-only + import chisel3.ExplicitCompileOptions.Strict ``` enables stricter connection and usage checks, while -```scala - import chisel3.core.ExplicitCompileOptions.NotStrict +```scala mdoc:compile-only + import chisel3.ExplicitCompileOptions.NotStrict ``` defers these checks to the `firrtl` compiler. diff --git a/docs/src/appendix/experimental-features.md b/docs/src/appendix/experimental-features.md index ba1a028d..0e3f50c8 100644 --- a/docs/src/appendix/experimental-features.md +++ b/docs/src/appendix/experimental-features.md @@ -6,12 +6,18 @@ section: "chisel3" Chisel has a number of new features that are worth checking out. This page is an informal list of these features and projects. -### FixedPoint +[FixedPoint](#fixed-point) +[Module Variants](#module-variants) +[Module Variants](#bundle-literals) +[Interval Type](#interval-type) +[Loading Memories in Simulation](#loading-memories) + +### FixedPoint FixedPoint numbers are basic *Data* type along side of UInt, SInt, etc. Most common math and logic operations are supported. Chisel allows both the width and binary point to be inferred by the Firrtl compiler which can simplify circuit descriptions. See [FixedPointSpec](https://github.com/freechipsproject/chisel3/tree/master/src/test/scala/chiselTests/FixedPointSpec.scala) -### Module Variants +### Module Variants The standard Chisel *Module* requires a `val io = IO(...)`, the experimental package introduces several new ways of defining Modules - BaseModule: no contents, instantiable @@ -21,7 +27,7 @@ new ways of defining Modules - RawModule: will be the user-facing version of UserDefinedModule - Module: type-aliases to ImplicitModule, the user-facing version of ImplicitModule. -### Bundle Literals +### Bundle Literals Bundle literals can be constructed via an experimental import: @@ -81,7 +87,7 @@ chisel3.stage.ChiselStage.emitVerilog(new Example3) Vec literals are not yet supported. -### Interval Type +### Interval Type **Intervals** are a new experimental numeric type that comprises UInt, SInt and FixedPoint numbers. It augments these types with range information, i.e. upper and lower numeric bounds. @@ -136,3 +142,57 @@ Consider a Interval with a binary point of 3: aaa.bbb | setBinaryPoint(2) | aaa.bb | 2 | X | X | set the precision | | shiftLeftBinaryPoint(2) | a.aabbb | 5 | X | X | increase the precision | | shiftRighBinaryPoint(2) | aaaa.b | 1 | X | X | reduce the precision | + +## Loading Memories in simulation + +Chisel now supports an experimental method for annotating memories to be loaded from a text file containing +hex or binary numbers. When using verilog simulation it uses the `$readmemh` or `$readmemb` +verilog extension. The treadle simulator can also load memories using the same annotation. + +### How to annotate +Assuming you have a memory in a Module +```scala mdoc:invisible +import chisel3._ +val memoryDepth = 8 +val memoryType = Bool() +``` +```scala +val memory = Mem(memoryDepth, memoryType) +``` + +At the top of your file just add the import +```scala mdoc:silent +import chisel3.util.experimental.loadMemoryFromFile +``` +Now just add the memory annotation using +```scala + loadMemoryFromFile(memory, "/workspace/workdir/mem1.txt") +``` +The default is to use `$readmemh` (which assumes all numbers in the file are in ascii hex), +bu to use ascii binary there is an optional third argument. You will need to add an additional import. +```scala mdoc:silent +import firrtl.annotations.MemoryLoadFileType +``` +```scala + loadMemoryFromFile(memory, "/workspace/workdir/mem1.txt", MemoryLoadFileType.Binary) +``` +See: [ComplexMemoryLoadingSpec.scala](https://freechipsproject/chisel-testers/src/test/scala/examples/ComplexMemoryLoadingSpec.scala) and +[LoadMemoryFromFileSpec.scala](https://github.com/freechipsproject/chisel-testers/src/test/scala/examples/LoadMemoryFromFileSpec.scala) +for working examples. + +### Notes on files +There is no simple answer to where to put this file. It's probably best to create a resource directory somewhere and reference that through a full path. Because these files may be large, we did not want to copy them. +> Don't forget there is no decimal option, so a 10 in an input file will be 16 decimal + +### Aggregate memories +Aggregate memories are supported but in bit of a clunky way. Since they will be split up into a memory per field, the following convention was adopted. When specifying the file for such a memory the file name should be regarded as a template. If the memory is a Bundle e.g. +```scala mdoc:compile-only +class MemDataType extends Bundle { + val a = UInt(16.W) + val b = UInt(32.W) + val c = Bool() +} +``` +The memory will be split into `memory_a`, `memory_b`, and `memory_c`. Similarly if a load file is specified as `"memory-load.txt"` the simulation will expect that there will be three files, `"memory-load_a.txt"`, `"memory-load_b.txt"`, `"memory-load_c.txt"` + +> Note: The use of `_` and that the memory field name is added before any file suffix. The suffix is optional but if present is considered to be the text after the last `.` in the file name. diff --git a/docs/src/cookbooks/cookbook.md b/docs/src/cookbooks/cookbook.md new file mode 100644 index 00000000..6a8eeebf --- /dev/null +++ b/docs/src/cookbooks/cookbook.md @@ -0,0 +1,471 @@ +--- +layout: docs +title: "General Cookbook" +section: "chisel3" +--- + +# General Cookbook + + +Please note that these examples make use of [Chisel's scala-style printing](../explanations/printing#scala-style). + +* Converting Chisel Types to/from UInt + * [How do I create a UInt from an instance of a Bundle?](#how-do-i-create-a-uint-from-an-instance-of-a-bundle) + * [How do I create a Bundle from a UInt?](#how-do-i-create-a-bundle-from-a-uint) + * [How do I create a Vec of Bools from a UInt?](#how-do-i-create-a-vec-of-bools-from-a-uint) + * [How do I create a UInt from a Vec of Bool?](#how-do-i-create-a-uint-from-a-vec-of-bool) +* Vectors and Registers + * [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) +* [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) +* [How do I create an optional I/O?](#how-do-i-create-an-optional-io) +* 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) + * [How do I get Chisel to name the results of vector reads properly?](#how-do-i-get-chisel-to-name-the-results-of-vector-reads-properly) + * [How can I dynamically set/parametrize the name of a module?](#how-can-i-dynamically-setparametrize-the-name-of-a-module) + +## Converting Chisel Types to/from UInt + +### How do I create a UInt from an instance of a Bundle? + +Call [`asUInt`](https://www.chisel-lang.org/api/latest/chisel3/Bundle.html#asUInt():chisel3.UInt) on the [`Bundle`](https://www.chisel-lang.org/api/latest/chisel3/Bundle.html) instance. + +```scala mdoc:silent:reset +import chisel3._ + +class MyBundle extends Bundle { + val foo = UInt(4.W) + val bar = UInt(4.W) +} + +class Foo extends RawModule { + val bundle = Wire(new MyBundle) + bundle.foo := 0xc.U + bundle.bar := 0x3.U + val uint = bundle.asUInt +} +``` + +### How do I create a Bundle from a UInt? + +Use the [`asTypeOf`](https://www.chisel-lang.org/api/latest/chisel3/UInt.html#asTypeOf[T%3C:chisel3.Data](that:T):T) method to reinterpret the [`UInt`](https://www.chisel-lang.org/api/latest/chisel3/UInt.html) as the type of the [`Bundle`](https://www.chisel-lang.org/api/latest/chisel3/Bundle.html). + +```scala mdoc:silent:reset +import chisel3._ + +class MyBundle extends Bundle { + val foo = UInt(4.W) + val bar = UInt(4.W) +} + +class Foo extends RawModule { + val uint = 0xb4.U + val bundle = uint.asTypeOf(new MyBundle) +} +``` + +### How do I create a Vec of Bools from a UInt? + +Use [`VecInit`](https://www.chisel-lang.org/api/latest/chisel3/VecInit$.html) given a `Seq[Bool]` generated using the [`asBools`](https://www.chisel-lang.org/api/latest/chisel3/UInt.html#asBools():Seq[chisel3.Bool]) method. + +```scala mdoc:silent:reset +import chisel3._ + +class Foo extends RawModule { + val uint = 0xc.U + val vec = VecInit(uint.asBools) +} +``` + +### How do I create a UInt from a Vec of Bool? + +Use the builtin function [`asUInt`](https://www.chisel-lang.org/api/latest/chisel3/Vec.html#asUInt():chisel3.UInt) + +```scala mdoc:silent:reset +import chisel3._ + +class Foo extends RawModule { + val vec = VecInit(true.B, false.B, true.B, true.B) + val uint = vec.asUInt +} +``` + +## Vectors and Registers + +### How do I create a Vector of Registers? + +**Rule! Use Reg of Vec not Vec of Reg!** + +You create a [Reg of type Vec](#how-do-i-create-a-reg-of-type-vec). Because Vecs are a *type* (like `UInt`, `Bool`) rather than a *value*, we must bind the Vec to some concrete *value*. + +### How do I create a Reg of type Vec? + +For more information, the API Documentation for [`Vec`](https://www.chisel-lang.org/api/latest/chisel3/Vec.html) provides more information. + +```scala mdoc:silent:reset +import chisel3._ + +class Foo extends RawModule { + val regOfVec = Reg(Vec(4, UInt(32.W))) // Register of 32-bit UInts + regOfVec(0) := 123.U // Assignments to elements of the Vec + regOfVec(1) := 456.U + regOfVec(2) := 789.U + regOfVec(3) := regOfVec(0) + + // Reg of Vec of 32-bit UInts initialized to zero + // Note that Seq.fill constructs 4 32-bit UInt literals with the value 0 + // VecInit(...) then constructs a Wire of these literals + // The Reg is then initialized to the value of the Wire (which gives it the same type) + val initRegOfVec = RegInit(VecInit(Seq.fill(4)(0.U(32.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. +State transitions are then handled with [`switch`](https://www.chisel-lang.org/api/latest/chisel3/util/switch$.html)/[`is`](https://www.chisel-lang.org/api/latest/chisel3/util/is$.html) and [`when`](https://www.chisel-lang.org/api/latest/chisel3/when$.html)/[`.elsewhen`](https://www.chisel-lang.org/api/latest/chisel3/WhenContext.html#elsewhen(elseCond:=%3Echisel3.Bool)(block:=%3EUnit)(implicitsourceInfo:chisel3.internal.sourceinfo.SourceInfo,implicitcompileOptions:chisel3.CompileOptions):chisel3.WhenContext)/[`.otherwise`](https://www.chisel-lang.org/api/latest/chisel3/WhenContext.html#otherwise(block:=%3EUnit)(implicitsourceInfo:chisel3.internal.sourceinfo.SourceInfo,implicitcompileOptions:chisel3.CompileOptions):Unit). + +```scala mdoc:silent:reset +import chisel3._ +import chisel3.util.{switch, is} +import chisel3.experimental.ChiselEnum + +object DetectTwoOnes { + object State extends ChiselEnum { + val sNone, sOne1, sTwo1s = Value + } +} + +/* This FSM detects two 1's one after the other */ +class DetectTwoOnes extends Module { + import DetectTwoOnes.State + import DetectTwoOnes.State._ + + val io = IO(new Bundle { + val in = Input(Bool()) + val out = Output(Bool()) + val state = Output(State()) + }) + + val state = RegInit(sNone) + + io.out := (state === sTwo1s) + io.state := state + + switch (state) { + is (sNone) { + when (io.in) { + state := sOne1 + } + } + is (sOne1) { + when (io.in) { + state := sTwo1s + } .otherwise { + state := sNone + } + } + is (sTwo1s) { + when (!io.in) { + state := sNone + } + } + } +} +``` + +Note: the `is` statement can take multiple conditions e.g. `is (sTwo1s, sOne1) { ... }`. + +### How do I unpack a value ("reverse concatenation") like in Verilog? + +In Verilog, you can do something like the following which will unpack a the value `z`: + +```verilog +wire [1:0] a; +wire [3:0] b; +wire [2:0] c; +wire [8:0] z = [...]; +assign {a,b,c} = z; +``` + +Unpacking often corresponds to reinterpreting an unstructured data type as a structured data type. +Frequently, this structured type is used prolifically in the design, and has been declared as in the following example: + +```scala mdoc:silent:reset +import chisel3._ + +class MyBundle extends Bundle { + val a = UInt(2.W) + val b = UInt(4.W) + val c = UInt(3.W) +} +``` + +The easiest way to accomplish this in Chisel would be: + +```scala mdoc:silent +class Foo extends RawModule { + val z = Wire(UInt(9.W)) + z := DontCare // This is a dummy connection + val unpacked = z.asTypeOf(new MyBundle) + printf("%d", unpacked.a) + printf("%d", unpacked.b) + printf("%d", unpacked.c) +} +``` + +If you **really** need to do this for a one-off case (Think thrice! It is likely you can better structure the code using bundles), then rocket-chip has a [Split utility](https://github.com/freechipsproject/rocket-chip/blob/723af5e6b69e07b5f94c46269a208a8d65e9d73b/src/main/scala/util/Misc.scala#L140) which can accomplish this. + +### How do I do subword assignment (assign to some bits in a UInt)? + +You may try to do something like the following where you want to assign only some bits of a Chisel type. +Below, the left-hand side connection to `io.out(0)` is not allowed. + +```scala mdoc:silent:reset +import chisel3._ +import chisel3.stage.{ChiselStage, ChiselGeneratorAnnotation} + +class Foo extends Module { + val io = IO(new Bundle { + val bit = Input(Bool()) + val out = Output(UInt(10.W)) + }) + io.out(0) := io.bit +} +``` + +If you try to compile this, you will get an error. +```scala mdoc:crash +(new ChiselStage).execute(Array("-X", "verilog"), Seq(new ChiselGeneratorAnnotation(() => new Foo))) +``` + +Chisel3 *does not support subword assignment*. +The reason for this is that subword assignment generally hints at a better abstraction with an aggregate/structured types, i.e., a `Bundle` or a `Vec`. + +If you must express it this way, one approach is to blast your `UInt` to a `Vec` of `Bool` and back: + +```scala mdoc:silent:reset +import chisel3._ + +class Foo extends Module { + val io = IO(new Bundle { + val in = Input(UInt(10.W)) + val bit = Input(Bool()) + val out = Output(UInt(10.W)) + }) + val bools = VecInit(io.in.asBools) + bools(0) := io.bit + io.out := bools.asUInt +} +``` + + +### How do I create an optional I/O? + +The following example is a module which includes the optional port `out2` only if the given parameter is `true`. + +```scala mdoc:silent:reset +import chisel3._ + +class ModuleWithOptionalIOs(flag: Boolean) extends Module { + val io = IO(new Bundle { + val in = Input(UInt(12.W)) + val out = Output(UInt(12.W)) + val out2 = if (flag) Some(Output(UInt(12.W))) else None + }) + + io.out := io.in + if (flag) { + io.out2.get := io.in + } +} +``` + +The following is an example where an entire `IO` is optional: + +```scala mdoc:silent:reset +import chisel3._ + +class ModuleWithOptionalIO(flag: Boolean) extends Module { + val in = if (flag) Some(IO(Input(Bool()))) else None + val out = IO(Output(Bool())) + + out := in.getOrElse(false.B) +} +``` + +## Predictable Naming + +### How do I get Chisel to name signals properly in blocks like when/withClockAndReset? + +To get Chisel to name signals (wires and registers) declared inside of blocks like `when`, `withClockAndReset`, etc, use the [`@chiselName`](https://www.chisel-lang.org/api/latest/chisel3/experimental/package$$chiselName.html) annotation as shown below: + +```scala mdoc:silent:reset +import chisel3._ +import chisel3.experimental.chiselName + +@chiselName +class TestMod extends Module { + val io = IO(new Bundle { + val a = Input(Bool()) + val b = Output(UInt(4.W)) + }) + when (io.a) { + val innerReg = RegInit(5.U(4.W)) + innerReg := innerReg + 1.U + io.b := innerReg + } .otherwise { + io.b := 10.U + } +} +``` + +Note that you will need to add the following line to your project's `build.sbt` file. + +```scala +addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full) +``` + +If we compile this module *without* `@chiselName`, Chisel is not able to name `innerReg` correctly (notice the `_T`): + +```scala mdoc:passthrough +import chisel3.stage.{ChiselStage, ChiselGeneratorAnnotation} +import firrtl.annotations.DeletedAnnotation +import firrtl.EmittedVerilogCircuitAnnotation + +class TestModWithout extends Module { + override val desiredName = "TestMod" + val io = IO(new Bundle { + val a = Input(Bool()) + val b = Output(UInt(4.W)) + }) + when (io.a) { + val innerReg = RegInit(5.U(4.W)) + innerReg := innerReg + 1.U + io.b := innerReg + } .otherwise { + io.b := 10.U + } +} + +(new ChiselStage) + .execute(Array("-X", "verilog"), Seq(ChiselGeneratorAnnotation(() => new TestModWithout))) + .collectFirst{ case DeletedAnnotation(_, a: EmittedVerilogCircuitAnnotation) => a.value.value } + .foreach(a => println(s"""|```verilog + |$a + |```""".stripMargin)) +``` + +However, if we use `@chiselName` then the register previously called `_T` is now `innerReg`: +```scala mdoc:passthrough +(new ChiselStage) + .execute(Array("-X", "verilog"), Seq(ChiselGeneratorAnnotation(() => new TestMod))) + .collectFirst{ case DeletedAnnotation(_, a: EmittedVerilogCircuitAnnotation) => a.value.value } + .foreach(a => println(s"""|```verilog + |$a + |```""".stripMargin)) +``` +### How do I get Chisel to name the results of vector reads properly? +Currently, name information is lost when using dynamic indexing. For example: +```scala mdoc:silent +class Foo extends Module { + val io = IO(new Bundle { + val in = Input(Vec(4, Bool())) + val idx = Input(UInt(2.W)) + val en = Input(Bool()) + val out = Output(Bool()) + }) + + val x = io.in(io.idx) + val y = x && io.en + io.out := y +} +``` + +The above code loses the `x` name, instead using `_GEN_3` (the other `_GEN_*` signals are expected). +```verilog +module Foo( + input clock, + input reset, + input io_in_0, + input io_in_1, + input io_in_2, + input io_in_3, + input [1:0] io_idx, + input io_en, + output io_out +); + wire _GEN_1; // @[main.scala 15:13] + wire _GEN_2; // @[main.scala 15:13] + wire _GEN_3; // @[main.scala 15:13] + assign _GEN_1 = 2'h1 == io_idx ? io_in_1 : io_in_0; // @[main.scala 15:13] + assign _GEN_2 = 2'h2 == io_idx ? io_in_2 : _GEN_1; // @[main.scala 15:13] + assign _GEN_3 = 2'h3 == io_idx ? io_in_3 : _GEN_2; // @[main.scala 15:13] + assign io_out = _GEN_3 & io_en; // @[main.scala 16:10] +endmodule +``` + +This can be worked around by creating a wire and connecting the dynamic index to the wire: +```scala +val x = WireInit(io.in(io.idx)) +``` + +Which produces: +```verilog +module Foo( + input clock, + input reset, + input io_in_0, + input io_in_1, + input io_in_2, + input io_in_3, + input [1:0] io_idx, + input io_en, + output io_out +); + wire _GEN_1; + wire _GEN_2; + wire x; + assign _GEN_1 = 2'h1 == io_idx ? io_in_1 : io_in_0; + assign _GEN_2 = 2'h2 == io_idx ? io_in_2 : _GEN_1; + assign x = 2'h3 == io_idx ? io_in_3 : _GEN_2; + assign io_out = x & io_en; // @[main.scala 16:10] +endmodule +``` +### How can I dynamically set/parametrize the name of a module? + +You can override the `desiredName` function. This works with normal Chisel modules and `BlackBox`es. Example: + +```scala mdoc:silent:reset +import chisel3._ + +class Coffee extends BlackBox { + val io = IO(new Bundle { + val I = Input(UInt(32.W)) + val O = Output(UInt(32.W)) + }) + override def desiredName = "Tea" +} + +class Salt extends Module { + val io = IO(new Bundle {}) + val drink = Module(new Coffee) + override def desiredName = "SodiumMonochloride" +} +``` + +Elaborating the Chisel module `Salt` yields our "desire name" for `Salt` and `Coffee` in the output Verilog: +```scala mdoc:passthrough +import chisel3.stage.{ChiselStage, ChiselGeneratorAnnotation} +import firrtl.annotations.DeletedAnnotation +import firrtl.EmittedVerilogCircuitAnnotation + +(new ChiselStage) + .execute(Array("-X", "verilog"), Seq(ChiselGeneratorAnnotation(() => new Salt))) + .collectFirst{ case DeletedAnnotation(_, a: EmittedVerilogCircuitAnnotation) => a.value.value } + .foreach(a => println(s"""|```verilog + |$a + |```""".stripMargin)) +``` diff --git a/docs/src/cookbooks/cookbooks.md b/docs/src/cookbooks/cookbooks.md new file mode 100644 index 00000000..ee6f5e45 --- /dev/null +++ b/docs/src/cookbooks/cookbooks.md @@ -0,0 +1,15 @@ +--- +layout: docs +title: "Cookbooks" +section: "chisel3" +--- + +# Cookbooks + +Welcome to the Chisel Cookbooks, where we capture frequently-used design patterns or troubleshooting questions. +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) +* [Naming Cookbook](naming) +* [Troubleshooting Guide](troubleshooting) diff --git a/docs/src/cookbooks/troubleshooting.md b/docs/src/cookbooks/troubleshooting.md new file mode 100644 index 00000000..f8a0cec1 --- /dev/null +++ b/docs/src/cookbooks/troubleshooting.md @@ -0,0 +1,64 @@ +--- +layout: docs +title: "Troubleshooting" +section: "chisel3" +--- + +# Troubleshooting + + +This page is a starting point for recording common and not so common problems in developing with Chisel3. In particular, those situations where there is a work around that will keep you going. + +### `type mismatch` specifying width/value of a `UInt`/`SInt` + +*I have some old code that used to work correctly in chisel2 (and still does if I use the `import Chisel._` compatibility layer) +but causes a `type mismatch` error in straight chisel3:* + +```scala mdoc:silent:fail +class TestBlock extends Module { + val io = IO(new Bundle { + val output = Output(UInt(width=3)) + }) +} +``` +*produces* +```bash +type mismatch; +[error] found : Int(3) +[error] required: chisel3.internal.firrtl.Width +[error] val output = Output(UInt(width=3)) +``` + +The single argument, multi-function object/constructors from chisel2 have been removed from chisel3. +It was felt these were too prone to error and made it difficult to diagnose error conditions in chisel3 code. + +In chisel3, the single argument to the `UInt`/`SInt` object/constructor specifies the *width* and must be a `Width` type. +Although there are no automatic conversions from `Int` to `Width`, an `Int` may be converted to a `Width` by applying the `W` method to an `Int`. +In chisel3, the above code becomes: +```scala mdoc:silent +import chisel3._ + +class TestBlock extends Module { + val io = IO(new Bundle { + val output = Output(UInt(3.W)) + }) +} +``` +`UInt`/`SInt` literals may be created from an `Int` with the application of either the `U` or `S` method. + +```scala mdoc:fail +UInt(42) +``` + +in chisel2, becomes +```scala mdoc:silent +42.U +``` +in chisel3 + +A literal with a specific width is created by calling the `U` or `S` method with a `W` argument. +Use: +```scala mdoc:silent +1.S(8.W) +``` +to create an 8-bit wide (signed) literal with value 1. diff --git a/docs/src/developers/developers.md b/docs/src/developers/developers.md new file mode 100644 index 00000000..b2b5dff8 --- /dev/null +++ b/docs/src/developers/developers.md @@ -0,0 +1,12 @@ +--- +layout: docs +title: "Developers" +section: "chisel3" +--- + +# Developer Documentation + +Tips and tricks for Chisel developers: + +* [Embedding Chisel as an sbt subproject](sbt-subproject) +* [Test Coverage](test-coverage) diff --git a/docs/src/developers/sbt-subproject.md b/docs/src/developers/sbt-subproject.md new file mode 100644 index 00000000..44d2916b --- /dev/null +++ b/docs/src/developers/sbt-subproject.md @@ -0,0 +1,36 @@ +--- +layout: docs +title: "Developers" +section: "chisel3" +--- + +# Chisel as an sbt subproject + +In order to use the constructs defined in the Chisel3 library, those definitions must be made available to the Scala +compiler at the time a project dependent on them is compiled. +For sbt-based builds there are fundamentally two ways to do this: +* provide a library dependency on the published Chisel3 jars via sbt's `libraryDependencies` setting, +* clone the Chisel3 git repository and include the source code as a subproject of a dependent project. + +The former of these two approaches is used by the chisel-tutorial project. +It is the simplest approach and assumes you do not require tight control over Chisel3 source code and are content with the +published release versions of Chisel3. + +The latter approach should be used by Chisel3 projects that require finer control over Chisel3 source code. + +It's hard to predict in advance the future requirements of a project, and it would be advantageous to be able to +switch between the two approaches relatively easily. +In order to accomplish this, we provide the `sbt-chisel-dep` plugin that allows the developer to concisely specify +Chisel3 subproject dependencies and switch between subproject and library dependency support based on the presence of +a directory (or symbolic link) in the root of the dependent project. + +The chisel-template project uses this plugin to support switching between either dependency (subproject or library). +By default, the chisel-template project does not contain a chisel3 subproject directory, and hence, uses a library dependency +on chisel3 (and related Chisel3 projects). +However, if you clone the chisel3 GitHub project from the root directory of the chisel-template project, creating a chisel3 +subdirectory, the `sbt-chisel-dep` plugin will take note of the chisel3 project subdirectory, +and provide an sbt subproject dependency in place of the library dependency. + +Checkout the [README for the `sbt-chisel-dep`](https://github.com/ucb-bar/sbt-chisel-dep) project for instructions on its usage. + +Example versions of the build.sbt and specification of the sbt-chisel-dep plugin are available from the [skeleton branch of the chisel-template repository](https://github.com/ucb-bar/chisel-template/tree/skeleton). diff --git a/docs/src/developers/style.md b/docs/src/developers/style.md new file mode 100644 index 00000000..80e1e083 --- /dev/null +++ b/docs/src/developers/style.md @@ -0,0 +1,293 @@ +--- +layout: docs +title: "Style Guide" +section: "chisel3" +--- + +# Chisel Developers Style Guide + +This document describes the syle used within the `chisel3` +and related projects (`firrtl`, `treadle`, etc). It does not +capture requirements for code which is written using these libraries, +although projects may choose to adopt these guidelines. + +The Chisel style guide reflects the [Google Java style +guide](http://google.github.io/styleguide/javaguide.html) and the [General Public Scala style +guide](http://docs.scala-lang.org/style/). The specific rules below are to clarify +the style used for the chisel3 repo and repos related to Chisel (Firrtl). + + +**Goal:** Readability and consistency are the main purposes of the style guide. +Writing your code so someone else (or yourself) can grok it later is important +to code health and quality. + +## Filenames +The source file name consists of the case-sensitive name of the top-level class +it contains, plus ".scala". + +## Packages + +Package definitions must contain the full path to the package from scala. If +you create a subpackage, it should go in a subdirectory. + + package directory.name.to.get.you.to.your.source + +As in Scala, packages follow the [Java package naming convention](https://google.github.io/styleguide/javaguide.html#s5.2.1-package-names). +Note that these guidelines call for all lowercase, no underscores. + +```scala +// Do this +package hardware.chips.topsecret.masterplan + +// Not this +package hardware.chips.veryObvious.bad_style +``` + +We also suggest you do not use chisel3 as a package, and especially do not use it +as the final (innermost) package. + +```scala +// Don't do this +package hardware.chips.newchip.superfastcomponent.chisel3 + +// This will lead to instantiating package members like so: +val module = Module(new chisel3.FastModule) + +// Which collides with the chisel namespace +import chisel3._ +``` + +## Imports +Avoid wildcard ( ._ ) imports, with the exception of chisel3._ +All other imports must call out used methods. +`import chisel3._` must be first, and separated from remaining imports with an extra blank line. + +**Reason:** This makes it clear where methods are defined. + +Any remaining imports must be listed alphabetically. + +```scala +import chisel3._ + +import the.other.thing.that.i.reference.inline +import the.other.things.that.i.reference.{ClassOne, ClassTwo} + + +val myInline = inline.MakeAnInline() +val myClassOne = new ClassOne +``` + +## Tests +Test classes are named starting with the name of the class they are testing, and +ending with "Test". +Test files must start with the name of the class you are testing and end with +"Test.scala". +Test files should reside in a subdirectory called "tests". +The tests package should be composed of the package class you are testing. + +```scala +package class.under.test.class +package tests +``` + +## Comments +We use scaladoc to automatically generate documentation from the source code. + +```scala +/** Multiple lines of ScalaDoc text are written here, + * wrapped normally... + */ +public int method(String p1) { ... } +``` + +... or in this single-line example: + +```scala +/** An especially short bit of Javadoc. */ +``` + +Write documentation as if the person reading it knows more about Scala and +Chisel than you. If you find comments in the code consider breaking them up +into seperate methods. + +## Module Classes and Instances + +Modules can take different forms in Chisel. The first form is similar to Verilog, where +you instance the module and then hook it up. In this case `Module(new MyMod())` is +returning a reference to the module. + +```scala +val myMod = Module(new MyMod()) +myMod.io <> hookUp +``` + +The second form is a more programmatic inline style with factory methods. In this case, +Queue is actually returning the part of the IO bundle representing the queue's +output. The factory method takes the input IO to the queue and an optional parameter +for depth. + +```scala +val queueOut = Queue(queueIn, depth=10) +``` + +The latter can be used for composing multiple functions into a single line. + +```scala +val queueOut = Queue( + Arbitrate.byRoundRobin( + Queue(a), // depth assumed to be 1 + Queue(b, depth=3), + Queue(c, depth=4) + ), + depth=10 +) +``` + +## Naming Conventions + +Chisel follows the [Scala Naming Conventions](http://docs.scala-lang.org/style/naming-conventions.html). +In general, Chisel code should use `lowerCamelCase` for variable naming (ie. the first letter +of each word is capitalized except for the first word) and `UpperCamelCase` for class names. + +> Using these guidelines can result in verilog which is noncompliant with common verilog coding standards, e.g. +the [lowRISC verilog coding style](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md#naming). +Authors of Chisel code that translates to hardware constructs might therefore prefer to use `snake_case`. However, +generated code can always be transformed to meet various emitted code requirements, so the official Chisel style +guide follows the Scala convention. + +### Why CamelCase instead of Snake\_Case? + +The compiler inserts underscores when splitting Chisel/FIRRTL aggregate types +into Verilog types. The compiler uses underscores to preserve the original +structure of the data in the resulting Verilog. Because of the special meaning +of underscores in Chisel-generated Verilog, their use in naming is **strongly** +discouraged. + +Consider the following Chisel code: + +```scala +val msg = Wire(new Bundle { + val valid = Bool() + val addr = UInt(32) + val data = UInt(64) +}) +val msg_rec = Wire(Bool()) +``` + +Which compiles to the Verilog: + +```verilog +wire msg_valid; +wire [31:0] msg_addr; +wire [63:0] msg_data; +wire msg_rec; +``` + +The Verilog maintains the structure of the original aggregate wire `msg`. +However, because we named another variable `msg_rec`, it appears in the Verilog +as if `msg` had 4 fields instead of its actual 3! If we instead follow the +lowerCamelCase for values naming convention, the resulting Verilog makes more +sense: + +```scala +val msg = Wire(new Bundle { + val valid = Bool() + val addr = UInt(32) + val data = UInt(64) +}) +val msgRec = Wire(Bool()) +``` + +And its resulting Verilog: + +```verilog +wire msg_valid; +wire [31:0] msg_addr; +wire [63:0] msg_data; +wire msgRec; +``` + +Much better. + +### Modules and Bundles (Classes, Traits, and Objects) + +Modules are Scala classes and thus use UpperCamelCase. + +```scala +class ModuleNamingExample extends Module { + ... +} +``` + +Similarly, other classes (Chisel & Scala) should be UpperCamelCase as well. + +```scala +trait UsefulScalaUtilities { + def isEven(n: Int): Boolean = (n % 2) == 0 + def isOdd(n: Int): Boolean = !isEven(n) +} + +class MyCustomBundle extends Bundle { + ... +} +// Companion object to MyCustomBundle +object MyCustomBundle { + ... +} + +``` + +### Values and Methods + +Values and methods should use lowerCamelCase. (Unless the value is a constant.) + +```scala +val mySuperReg = Reg(init = 0.asUInt(32)) +def myImportantMethod(a: UInt): Bool = a < 23.asUInt +``` + +### Constants + +Unlike the Google Java style, constants use UpperCamelCase, which is in line +with the official [Scala Naming +Conventions](https://docs.scala-lang.org/style/naming-conventions.html). +Constants are final fields (val or object) whose contents are deeply immutable +and belong to a package object or an object. Examples: + +```scala +// Constants +object Constants { + val Number = 5 + val Names = "Ed" :: "Ann" :: Nil + val Ages = Map("Ed" -> 35, "Ann" -> 32) +} + +// Not constants +class NonConstantsInClass { + val inClass: String = "in-class" +} + +object nonConstantsInObject { + var varString = "var-string" + val mutableCollection: scala.collection.mutable.Set[String] + val mutableElements = Set(mutable) +} +``` + +### UpperCamelCase vs. lowerCamelCase + +There is more than one reasonable way to covert English prose into camel case. +We follow the convention defined in the [Google Java style +guide](https://google.github.io/styleguide/javaguide.html#s5.3-camel-case). The +potentially non-obvious rule being to treat acronymns as words for the purpose +of camel case. + +Note that the casing of the original words is almost entirely disregarded. +Example: + +Prose form | UpperCamelCase | lowerCamelCase | Incorrect +:------------- | :------------- | :------------- | :------------ +find GCD | FindGcd | findGcd | ~~findGCD~~ +state for FSM | StateForFsm | stateForFsm | ~~stateForFSM~~ +mock dut | MockDut | mockDut | ~~MockDUT~~ +FIFO Generator | FifoGenerator | fifoGenerator | ~~FIFOGenerator~~ diff --git a/docs/src/developers/test-coverage.md b/docs/src/developers/test-coverage.md new file mode 100644 index 00000000..099db4bb --- /dev/null +++ b/docs/src/developers/test-coverage.md @@ -0,0 +1,23 @@ +--- +layout: docs +title: "Test Coverage" +section: "chisel3" +--- + +# Test Coverage + +## Test Coverage Setup + +Chisel's sbt build instructions contain the requisite plug-in (sbt-scoverage) for generating test coverage information. Please see the [sbt-scoverage web page](https://github.com/scoverage/sbt-scoverage) for details on the plug-in. +The tests themselves are found in `src/test/scala`. + +## Generating A Test Coverage Report + +Use the following sequence of sbt commands to generate a test coverage report: +``` +sbt clean coverage test +sbt coverageReport +``` +The coverage reports should be found in `target/scala-x.yy/scoverage-report/{scoverage.xml,index.html}` where `x.yy` corresponds to the version of Scala used to compile Firrtl and the tests. +`scoverage.xml` is useful if you want to analyze the results programmatically. +`index.html` is designed for navigation with a web browser, allowing one to drill down to invidual statements covered (or not) by the tests. diff --git a/docs/src/explanations/annotations.md b/docs/src/explanations/annotations.md index 19d24605..510ebca5 100644 --- a/docs/src/explanations/annotations.md +++ b/docs/src/explanations/annotations.md @@ -4,6 +4,8 @@ title: "Annotations" section: "chisel3" --- +# Annotations + `Annotation`s are metadata containers associated with zero or more "things" in a FIRRTL circuit. Commonly, `Annotation`s are used to communicate information from Chisel to a specific, custom FIRRTL `Transform`. In this way `Annotation`s can be viewed as the "arguments" that a specific `Transform` consumes. @@ -132,10 +134,8 @@ class ModC(widthC: Int) extends Module { Compiling this circuit to Verilog will then result in the `InfoTransform` running and the added `println`s showing information about the components annotated. -```scala mdoc +```scala mdoc:compile-only import chisel3.stage.{ChiselStage, ChiselGeneratorAnnotation} -// This currently doesn't work because of mdoc limitations. However, it will work -// in your normal Scala code. -//(new ChiselStage).execute(Array.empty, Seq(ChiselGeneratorAnnotation(() => new ModC(4)))) +(new ChiselStage).execute(Array.empty, Seq(ChiselGeneratorAnnotation(() => new ModC(4)))) ``` diff --git a/docs/src/explanations/bundles-and-vecs.md b/docs/src/explanations/bundles-and-vecs.md index bac9393a..78626c42 100644 --- a/docs/src/explanations/bundles-and-vecs.md +++ b/docs/src/explanations/bundles-and-vecs.md @@ -64,7 +64,7 @@ Note that the builtin Chisel primitive and aggregate classes do not require the `new` when creating an instance, whereas new user datatypes will. A Scala `apply` constructor can be defined so that a user datatype also does not require `new`, as described in -[Function Constructor](../wiki-deprecated/functional-module-creation). +[Function Constructor](../explanations/functional-module-creation). ### Flipping Bundles diff --git a/docs/src/explanations/combinational-circuits.md b/docs/src/explanations/combinational-circuits.md new file mode 100644 index 00000000..b9e5b8c6 --- /dev/null +++ b/docs/src/explanations/combinational-circuits.md @@ -0,0 +1,84 @@ +--- +layout: docs +title: "Combinational Circuits" +section: "chisel3" +--- + +# Combinational Circuits + +A circuit is represented as a graph of nodes in Chisel. Each node is +a hardware operator that has zero or more inputs and that drives one +output. A literal, introduced above, is a degenerate kind of node +that has no inputs and drives a constant value on its output. One way +to create and wire together nodes is using textual expressions. For +example, we can express a simple combinational logic circuit +using the following expression: + +```scala +(a & b) | (~c & d) +``` + +The syntax should look familiar, with `&` and `|` +representing bitwise-AND and -OR respectively, and `~` +representing bitwise-NOT. The names `a` through `d` +represent named wires of some (unspecified) width. + +Any simple expression can be converted directly into a circuit tree, +with named wires at the leaves and operators forming the internal +nodes. The final circuit output of the expression is taken from the +operator at the root of the tree, in this example, the bitwise-OR. + +Simple expressions can build circuits in the shape of trees, but to +construct circuits in the shape of arbitrary directed acyclic graphs +(DAGs), we need to describe fan-out. In Chisel, we do this by naming +a wire that holds a subexpression that we can then reference multiple +times in subsequent expressions. We name a wire in Chisel by +declaring a variable. For example, consider the select expression, +which is used twice in the following multiplexer description: +```scala +val sel = a | b +val out = (sel & in1) | (~sel & in0) +``` + +The keyword `val` is part of Scala, and is used to name variables +that have values that won't change. It is used here to name the +Chisel wire, `sel`, holding the output of the first bitwise-OR +operator so that the output can be used multiple times in the second +expression. + +### Wires + +Chisel also supports wires as hardware nodes to which one can assign values or connect other nodes. + +```scala +val myNode = Wire(UInt(8.W)) +when (isReady) { + myNode := 255.U +} .otherwise { + myNode := 0.U +} +``` + +```scala +val myNode = Wire(UInt(8.W)) +when (input > 128.U) { + myNode := 255.U +} .elsewhen (input > 64.U) { + myNode := 1.U +} .otherwise { + myNode := 0.U +} +``` + +Note that the last connection to a Wire takes effect. For example, the following two Chisel circuits are equivalent: + +```scala +val myNode = Wire(UInt(8.W)) +myNode := 10.U +myNode := 0.U +``` + +```scala +val myNode = Wire(UInt(8.W)) +myNode := 0.U +``` diff --git a/docs/src/explanations/data-types.md b/docs/src/explanations/data-types.md new file mode 100644 index 00000000..6ac6077b --- /dev/null +++ b/docs/src/explanations/data-types.md @@ -0,0 +1,136 @@ +--- +layout: docs +title: "Data Types" +section: "chisel3" +--- + +# Chisel Data Types + +Chisel datatypes are used to specify the type of values held in state +elements or flowing on wires. While hardware designs ultimately +operate on vectors of binary digits, other more abstract +representations for values allow clearer specifications and help the +tools generate more optimal circuits. In Chisel, a raw collection of +bits is represented by the ```Bits``` type. Signed and unsigned integers +are considered subsets of fixed-point numbers and are represented by +types ```SInt``` and ```UInt``` respectively. Signed fixed-point +numbers, including integers, are represented using two's-complement +format. Boolean values are represented as type ```Bool```. Note +that these types are distinct from Scala's builtin types such as +```Int``` or ```Boolean```. + +> There is a new experimental type **Interval** which gives the developer more control of the type by allowing the definition of an IntervalRange. See: [Interval Type](../appendix/experimental-features#interval-type) + +Additionally, Chisel defines `Bundles` for making +collections of values with named fields (similar to ```structs``` in +other languages), and ```Vecs``` for indexable collections of +values. + +Bundles and Vecs will be covered later. + +Constant or literal values are expressed using Scala integers or +strings passed to constructors for the types: +```scala +1.U // decimal 1-bit lit from Scala Int. +"ha".U // hexadecimal 4-bit lit from string. +"o12".U // octal 4-bit lit from string. +"b1010".U // binary 4-bit lit from string. + +5.S // signed decimal 4-bit lit from Scala Int. +-8.S // negative decimal 4-bit lit from Scala Int. +5.U // unsigned decimal 3-bit lit from Scala Int. + +8.U(4.W) // 4-bit unsigned decimal, value 8. +-152.S(32.W) // 32-bit signed decimal, value -152. + +true.B // Bool lits from Scala lits. +false.B +``` +Underscores can be used as separators in long string literals to aid +readability, but are ignored when creating the value, e.g.: +```scala +"h_dead_beef".U // 32-bit lit of type UInt +``` + +By default, the Chisel compiler will size each constant to the minimum +number of bits required to hold the constant, including a sign bit for +signed types. Bit widths can also be specified explicitly on +literals, as shown below. Note that (`.W` is used to cast a Scala Int +to a Chisel width) +```scala +"ha".asUInt(8.W) // hexadecimal 8-bit lit of type UInt +"o12".asUInt(6.W) // octal 6-bit lit of type UInt +"b1010".asUInt(12.W) // binary 12-bit lit of type UInt + +5.asSInt(7.W) // signed decimal 7-bit lit of type SInt +5.asUInt(8.W) // unsigned decimal 8-bit lit of type UInt +``` + +For literals of type ```UInt```, the value is +zero-extended to the desired bit width. For literals of type +```SInt```, the value is sign-extended to fill the desired bit width. +If the given bit width is too small to hold the argument value, then a +Chisel error is generated. + +>We are working on a more concise literal syntax for Chisel using +symbolic prefix operators, but are stymied by the limitations of Scala +operator overloading and have not yet settled on a syntax that is +actually more readable than constructors taking strings. + +>We have also considered allowing Scala literals to be automatically +converted to Chisel types, but this can cause type ambiguity and +requires an additional import. + +>The SInt and UInt types will also later support an optional exponent +field to allow Chisel to automatically produce optimized fixed-point +arithmetic circuits. + +## Casting + +We can also cast types in Chisel: + +```scala +val sint = 3.S(4.W) // 4-bit SInt + +val uint = sint.asUInt // cast SInt to UInt +uint.asSInt // cast UInt to SInt +``` + +**NOTE**: `asUInt`/`asSInt` with an explicit width can **not** be used to cast (convert) between Chisel datatypes. +No width parameter is accepted, as Chisel will automatically pad or truncate as required when the objects are connected. + +We can also perform casts on clocks, though you should be careful about this, since clocking (especially in ASIC) requires special attention: + +```scala +val bool: Bool = false.B // always-low wire +val clock = bool.asClock // always-low clock + +clock.asUInt // convert clock to UInt (width 1) +clock.asUInt.asBool // convert clock to Bool (Chisel 3.2+) +clock.asUInt.toBool // convert clock to Bool (Chisel 3.0 and 3.1 only) +``` + +## Analog/BlackBox type + +(Experimental, Chisel 3.1+) + +Chisel supports an `Analog` type (equivalent to Verilog `inout`) that can be used to support arbitrary nets in Chisel. This includes analog wires, tri-state/bi-directional wires, and power nets (with appropriate annotations). + +`Analog` is an undirectioned type, and so it is possible to connect multiple `Analog` nets together using the `attach` operator. It is possible to connect an `Analog` **once** using `<>` but illegal to do it more than once. + +```scala +val a = IO(Analog(1.W)) +val b = IO(Analog(1.W)) +val c = IO(Analog(1.W)) + +// Legal +attach(a, b) +attach(a, c) + +// Legal +a <> b + +// Illegal - connects 'a' multiple times +a <> b +a <> c +``` diff --git a/docs/src/explanations/explanations.md b/docs/src/explanations/explanations.md new file mode 100644 index 00000000..01894ad7 --- /dev/null +++ b/docs/src/explanations/explanations.md @@ -0,0 +1,39 @@ +--- +layout: docs +title: "Explanations" +section: "chisel3" +--- + +# Explanations + +Explanation documentation gives background and context. +They can also explain why things are so - design decisions, +historical reasons, technical constraints. + +If you are just getting started with Chisel, we suggest you +read these documents in the following order: + +* [Motivation](motivation) +* [Supported Hardware](supported-hardware) +* [Data Types](data-types) +* [Bundles and Vecs](bundles-and-vecs) +* [Combinational Circuits](combinational-circuits) +* [Operators](operators) +* [Width Inference](width-inference) +* [Functional Abstraction](functional-abstraction) +* [Ports](ports) +* [Modules](modules) +* [Sequential Circuits](sequential-circuits) +* [Memories](memories) +* [Interfaces and Connections](interfaces-and-connections) +* [Black Boxes](blackboxes) +* [Enumerations](enumerations) +* [Functional Module Creation](functional-module-creation) +* [Muxes and Input Selection](muxes-and-input-selection) +* [Multiple Clock Domains](multi-clock) +* [Reset](reset) +* [Polymorphism and Paramterization](polymorphism-and-parameterization) +* [Printing in Chisel](printing) +* [Naming](naming) +* [Unconnected Wires](unconnected-wires) +* [Annotations](annotations) diff --git a/docs/src/explanations/functional-abstraction.md b/docs/src/explanations/functional-abstraction.md new file mode 100644 index 00000000..4e3900b6 --- /dev/null +++ b/docs/src/explanations/functional-abstraction.md @@ -0,0 +1,34 @@ +--- +layout: docs +title: "Functional Abstraction" +section: "chisel3" +--- + +# Functional Abstraction + +We can define functions to factor out a repeated piece of logic that +we later reuse multiple times in a design. For example, we can wrap +up our earlier example of a simple combinational logic block as +follows: + +```scala mdoc:invisible +import chisel3._ +``` + +```scala mdoc:silent +def clb(a: UInt, b: UInt, c: UInt, d: UInt): UInt = + (a & b) | (~c & d) +``` + +where ```clb``` is the function which takes ```a```, ```b```, +```c```, ```d``` as arguments and returns a wire to the output of a +boolean circuit. The ```def``` keyword is part of Scala and +introduces a function definition, with each argument followed by a colon then its type, +and the function return type given after the colon following the +argument list. The equals (```=})```sign separates the function argument list from the function +definition. + +We can then use the block in another circuit as follows: +```scala mdoc:silent +val out = clb(a,b,c,d) +``` diff --git a/docs/src/explanations/functional-module-creation.md b/docs/src/explanations/functional-module-creation.md new file mode 100644 index 00000000..407edb1d --- /dev/null +++ b/docs/src/explanations/functional-module-creation.md @@ -0,0 +1,120 @@ +--- +layout: docs +title: "Functional Module Creation" +section: "chisel3" +--- + +# Functional Module Creation + +Objects in Scala have a pre-existing creation function (method) called `apply`. +When an object is used as value in an expression (which basically means that the constructor was called), this method determines the returned value. +When dealing with hardware modules, one would expect the module output to be representative of the hardware module's functionality. +Therefore, we would sometimes like the module output to be the value returned when using the object as a value in an expression. +Since hardware modules are represented as Scala objects, this can be done by defining the object's `apply` method to return the module's output. +This can be referred to as creating a functional interface for module construction. +If we apply this on the standard mux2 example, we would to return the mux2 output ports when we used mux2 in an expression. +Implementing this requires building a constructor that takes multiplexer inputs as parameters and returns the multiplexer output: + +```scala mdoc:silent +import chisel3._ + +class Mux2 extends Module { + val io = IO(new Bundle { + val sel = Input(Bool()) + val in0 = Input(UInt()) + val in1 = Input(UInt()) + val out = Output(UInt()) + }) + io.out := Mux(io.sel, io.in0, io.in1) +} + +object Mux2 { + def apply(sel: UInt, in0: UInt, in1: UInt) = { + val m = Module(new Mux2) + m.io.in0 := in0 + m.io.in1 := in1 + m.io.sel := sel + m.io.out + } +} +``` + +As we can see in the code example, we defined the `apply` method to take the Mux2 inputs as the method parameters, and return the Mux2 output as the function's return value. +By defining modules in this way, it is easier to later implement larger and more complex version of this regular module. +For example, we previously implemented Mux4 like this: + +```scala mdoc:silent +class Mux4 extends Module { + val io = IO(new Bundle { + val in0 = Input(UInt(1.W)) + val in1 = Input(UInt(1.W)) + val in2 = Input(UInt(1.W)) + val in3 = Input(UInt(1.W)) + val sel = Input(UInt(2.W)) + val out = Output(UInt(1.W)) + }) + val m0 = Module(new Mux2) + m0.io.sel := io.sel(0) + m0.io.in0 := io.in0 + m0.io.in1 := io.in1 + + val m1 = Module(new Mux2) + m1.io.sel := io.sel(0) + m1.io.in0 := io.in2 + m1.io.in1 := io.in3 + + val m3 = Module(new Mux2) + m3.io.sel := io.sel(1) + m3.io.in0 := m0.io.out + m3.io.in1 := m1.io.out + + io.out := m3.io.out +} +``` + +However, by using the creation function we redefined for Mux2, we can now use the Mux2 outputs as values of the modules themselves +when writing the Mux4 output expression: + +```scala mdoc:invisible:reset +// We need to re-do this to allow us to `reset` +// and then re-define Mux4 +import chisel3._ + +class Mux2 extends Module { + val io = IO(new Bundle { + val sel = Input(Bool()) + val in0 = Input(UInt()) + val in1 = Input(UInt()) + val out = Output(UInt()) + }) + io.out := Mux(io.sel, io.in0, io.in1) +} + +object Mux2 { + def apply(sel: UInt, in0: UInt, in1: UInt) = { + val m = Module(new Mux2) + m.io.in0 := in0 + m.io.in1 := in1 + m.io.sel := sel + m.io.out + } +} +``` + +```scala mdoc:silent +class Mux4 extends Module { + val io = IO(new Bundle { + val in0 = Input(UInt(1.W)) + val in1 = Input(UInt(1.W)) + val in2 = Input(UInt(1.W)) + val in3 = Input(UInt(1.W)) + val sel = Input(UInt(2.W)) + val out = Output(UInt(1.W)) + }) + io.out := Mux2(io.sel(1), + Mux2(io.sel(0), io.in0, io.in1), + Mux2(io.sel(0), io.in2, io.in3)) +} +``` + +This allows us to write more intuitively readable hardware connection descriptions, which are similar to software expression evaluation. diff --git a/docs/src/explanations/interfaces-and-connections.md b/docs/src/explanations/interfaces-and-connections.md new file mode 100644 index 00000000..9f48b642 --- /dev/null +++ b/docs/src/explanations/interfaces-and-connections.md @@ -0,0 +1,149 @@ +--- +layout: docs +title: "Interfaces and Connections" +section: "chisel3" +--- + +# Interfaces & Bulk Connections + +For more sophisticated modules it is often useful to define and instantiate interface classes while defining the IO for a module. First and foremost, interface classes promote reuse allowing users to capture once and for all common interfaces in a useful form. + +Secondly, interfaces allow users to dramatically reduce wiring by supporting bulk connections between producer and consumer modules. Finally, users can make changes in large interfaces in one place reducing the number of updates required when adding or removing pieces of the interface. + +Note that Chisel has some built-in standard interface which should be used whenever possible for interoperability (e.g. Decoupled). + +## Ports: Subclasses & Nesting + +As we saw earlier, users can define their own interfaces by defining a class that subclasses Bundle. For example, a user could define a simple link for hand-shaking data as follows: + +```scala mdoc:invisible +import chisel3._ +``` + +```scala mdoc:silent +class SimpleLink extends Bundle { + val data = Output(UInt(16.W)) + val valid = Output(Bool()) +} +``` + +We can then extend SimpleLink by adding parity bits using bundle inheritance: +```scala mdoc:silent +class PLink extends SimpleLink { + val parity = Output(UInt(5.W)) +} +``` +In general, users can organize their interfaces into hierarchies using inheritance. + +From there we can define a filter interface by nesting two PLinks into a new FilterIO bundle: +```scala mdoc:silent +class FilterIO extends Bundle { + val x = Flipped(new PLink) + val y = new PLink +} +``` +where flip recursively changes the direction of a bundle, changing input to output and output to input. + +We can now define a filter by defining a filter class extending module: +```scala mdoc:silent +class Filter extends Module { + val io = IO(new FilterIO) + // ... +} +``` +where the io field contains FilterIO. + +## Bundle Vectors + +Beyond single elements, vectors of elements form richer hierarchical interfaces. For example, in order to create a crossbar with a vector of inputs, producing a vector of outputs, and selected by a UInt input, we utilize the Vec constructor: +```scala mdoc:silent +import chisel3.util.log2Ceil +class CrossbarIo(n: Int) extends Bundle { + val in = Vec(n, Flipped(new PLink)) + val sel = Input(UInt(log2Ceil(n).W)) + val out = Vec(n, new PLink) +} +``` +where Vec takes a size as the first argument and a block returning a port as the second argument. + +## Bulk Connections + +We can now compose two filters into a filter block as follows: +```scala mdoc:silent +class Block extends Module { + val io = IO(new FilterIO) + val f1 = Module(new Filter) + val f2 = Module(new Filter) + f1.io.x <> io.x + f1.io.y <> f2.io.x + f2.io.y <> io.y +} +``` +where <> bulk connects interfaces of opposite gender between sibling modules or interfaces of the same gender between parent/child modules. + +Bulk connections connect leaf ports of the same name to each other. If the names do not match or are missing, Chisel does not generate a connection. + +Caution: bulk connections should only be used with **directioned elements** (like IOs), and is not magical (e.g. connecting two wires isn't supported since Chisel can't necessarily figure out the directions automatically [chisel3#603](https://github.com/freechipsproject/chisel3/issues/603)). + +## The standard ready-valid interface (ReadyValidIO / Decoupled) + +Chisel provides a standard interface for [ready-valid interfaces](http://inst.eecs.berkeley.edu/~cs150/Documents/Interfaces.pdf). +A ready-valid interface consists of a `ready` signal, a `valid` signal, and some data stored in `bits`. +The `ready` bit indicates that a consumer is *ready* to consume data. +The `valid` bit indicates that a producer has *valid* data on `bits`. +When both `ready` and `valid` are asserted, a data transfer from the producer to the consumer takes place. +A convenience method `fire` is provided that is asserted if both `ready` and `valid` are asserted. + +Usually, we use the utility function [`Decoupled()`](https://chisel.eecs.berkeley.edu/api/latest/chisel3/util/Decoupled$.html) to turn any type into a ready-valid interface rather than directly using [ReadyValidIO](http://chisel.eecs.berkeley.edu/api/latest/chisel3/util/ReadyValidIO.html). + +* `Decoupled(...)` creates a producer / output ready-valid interface (i.e. bits is an output). +* `Flipped(Decoupled(...))` creates a consumer / input ready-valid interface (i.e. bits is an input). + +Take a look at the following example Chisel code to better understand exactly what is generated: + +```scala mdoc:silent:reset +import chisel3._ +import chisel3.util.Decoupled + +/** + * Using Decoupled(...) creates a producer interface. + * i.e. it has bits as an output. + * This produces the following ports: + * input io_readyValid_ready, + * output io_readyValid_valid, + * output [31:0] io_readyValid_bits + */ +class ProducingData extends Module { + val io = IO(new Bundle { + val readyValid = Decoupled(UInt(32.W)) + }) + // do something with io.readyValid.ready + io.readyValid.valid := true.B + io.readyValid.bits := 5.U +} + +/** + * Using Flipped(Decoupled(...)) creates a consumer interface. + * i.e. it has bits as an input. + * This produces the following ports: + * output io_readyValid_ready, + * input io_readyValid_valid, + * input [31:0] io_readyValid_bits + */ +class ConsumingData extends Module { + val io = IO(new Bundle { + val readyValid = Flipped(Decoupled(UInt(32.W))) + }) + io.readyValid.ready := false.B + // do something with io.readyValid.valid + // do something with io.readyValid.bits +} +``` + +`DecoupledIO` is a ready-valid interface with the *convention* that there are no guarantees placed on deasserting `ready` or `valid` or on the stability of `bits`. +That means `ready` and `valid` can also be deasserted without a data transfer. + +`IrrevocableIO` is a ready-valid interface with the *convention* that the value of `bits` will not change while `valid` is asserted and `ready` is deasserted. +Also the consumer shall keep `ready` asserted after a cycle where `ready` was high and `valid` was low. +Note that the *irrevocable* constraint *is only a convention* and cannot be enforced by the interface. +Chisel does not automatically generate checkers or assertions to enforce the *irrevocable* convention. diff --git a/docs/src/explanations/memories.md b/docs/src/explanations/memories.md new file mode 100644 index 00000000..792d176e --- /dev/null +++ b/docs/src/explanations/memories.md @@ -0,0 +1,176 @@ +--- +layout: docs +title: "Memories" +section: "chisel3" +--- + +# Memories + +Chisel provides facilities for creating both read only and read/write memories. + +## ROM + +Users can define read only memories with a `Vec`: +```scala mdoc:invisible +import chisel3._ +``` +``` scala mdoc:compile-only + VecInit(inits: Seq[T]) + VecInit(elt0: T, elts: T*) +``` + +where `inits` is a sequence of initial `Data` literals that initialize the ROM. For example, users cancreate a small ROM initialized to 1, 2, 4, 8 and loop through all values using a counter as an address generator as follows: + +``` scala mdoc:compile-only + val m = VecInit(Array(1.U, 2.U, 4.U, 8.U)) + val r = m(counter(m.length.U)) +``` + +We can create an *n* value sine lookup table using a ROM initialized as follows: + +``` scala mdoc:silent + def sinTable(amp: Double, n: Int) = { + val times = + (0 until n).map(i => (i*2*Pi)/(n.toDouble-1) - Pi) + val inits = + times.map(t => round(amp * sin(t)).asSInt(32.W)) + VecInit(inits) + } + def sinWave(amp: Double, n: Int) = + sinTable(amp, n)(counter(n.U)) +``` + +where `amp` is used to scale the fixpoint values stored in the ROM. + +## Read-Write Memories + +Memories are given special treatment in Chisel since hardware implementations of memory vary greatly. For example, FPGA memories are instantiated quite differently from ASIC memories. Chisel defines a memory abstraction that can map to either simple Verilog behavioural descriptions or to instances of memory modules that are available from external memory generators provided by foundry or IP vendors. + + +### `SyncReadMem`: sequential/synchronous-read, sequential/synchronous-write + +Chisel has a construct called `SyncReadMem` for sequential/synchronous-read, sequential/synchronous-write memories. These `SyncReadMem`s will likely be synthesized to technology SRAMs (as opposed to register banks). + +If the same memory address is both written and sequentially read on the same clock edge, or if a sequential read enable is cleared, then the read data is undefined. + +Values on the read data port are not guaranteed to be held until the next read cycle. If that is the desired behavior, external logic to hold the last read value must be added. + +#### Read port/write port +Ports into `SyncReadMem`s are created by applying a `UInt` index. A 1024-entry SRAM with one write port and one read port might be expressed as follows: + +```scala mdoc:silent +import chisel3._ +class ReadWriteSmem extends Module { + val width: Int = 32 + val io = IO(new Bundle { + val enable = Input(Bool()) + val write = Input(Bool()) + val addr = Input(UInt(10.W)) + val dataIn = Input(UInt(width.W)) + val dataOut = Output(UInt(width.W)) + }) + + val mem = SyncReadMem(1024, UInt(width.W)) + // Create one write port and one read port + mem.write(io.addr, io.dataIn) + io.dataOut := mem.read(io.addr, io.enable) +} +``` + +Below is an example waveform of the one write port/one read port `SyncReadMem` with [masks](#masks). Note that the signal names will differ from the exact wire names generated for the `SyncReadMem`. With masking, it is also possible that multiple RTL arrays will be generated with the behavior below. + +![read/write ports example waveform](https://svg.wavedrom.com/github/freechipsproject/www.chisel-lang.org/master/docs/src/main/tut/chisel3/memories_waveforms/smem_read_write.json) + +#### Single-ported +Single-ported SRAMs can be inferred when the read and write conditions are mutually exclusive in the same `when` chain: + +```scala mdoc:silent +import chisel3._ +class RWSmem extends Module { + val width: Int = 32 + val io = IO(new Bundle { + val enable = Input(Bool()) + val write = Input(Bool()) + val addr = Input(UInt(10.W)) + val dataIn = Input(UInt(width.W)) + val dataOut = Output(UInt(width.W)) + }) + + val mem = SyncReadMem(1024, UInt(width.W)) + io.dataOut := DontCare + when(io.enable) { + val rdwrPort = mem(io.addr) + when (io.write) { rdwrPort := io.dataIn } + .otherwise { io.dataOut := rdwrPort } + } +} +``` + +(The `DontCare` is there to make Chisel's [unconnected wire detection](unconnected-wires) aware that reading while writing is undefined.) + +Here is an example single read/write port waveform, with [masks](#masks) (again, generated signal names and number of arrays may differ): + +![read/write ports example waveform](https://svg.wavedrom.com/github/freechipsproject/www.chisel-lang.org/master/docs/src/main/tut/chisel3/memories_waveforms/smem_rw.json) + +### `Mem`: combinational/asynchronous-read, sequential/synchronous-write + +Chisel supports random-access memories via the `Mem` construct. Writes to `Mem`s are combinational/asynchronous-read, sequential/synchronous-write. These `Mem`s will likely be synthesized to register banks, since most SRAMs in modern technologies (FPGA, ASIC) tend to no longer support combinational (asynchronous) reads. + +Creating asynchronous-read versions of the examples above simply involves replacing `SyncReadMem` with `Mem`. + +### Masks + +Chisel memories also support write masks for subword writes. Chisel will infer masks if the data type of the memory is a vector. To infer a mask, specify the `mask` argument of the `write` function which creates write ports. A given masked length is written if the corresponding mask bit is set. For example, in the example below, if the 0th bit of mask is true, it will write the lower byte of the data at corresponding address. + +```scala mdoc:silent +import chisel3._ +class MaskedReadWriteSmem extends Module { + val width: Int = 8 + val io = IO(new Bundle { + val enable = Input(Bool()) + val write = Input(Bool()) + val addr = Input(UInt(10.W)) + val mask = Input(Vec(4, Bool())) + val dataIn = Input(Vec(4, UInt(width.W))) + val dataOut = Output(Vec(4, UInt(width.W))) + }) + + // Create a 32-bit wide memory that is byte-masked + val mem = SyncReadMem(1024, Vec(4, UInt(width.W))) + // Write with mask + mem.write(io.addr, io.dataIn, io.mask) + io.dataOut := mem.read(io.addr, io.enable) +} +``` + +Here is an example of masks with readwrite ports: + +```scala mdoc:silent +import chisel3._ +class MaskedRWSmem extends Module { + val width: Int = 32 + val io = IO(new Bundle { + val enable = Input(Bool()) + val write = Input(Bool()) + val mask = Input(Vec(2, Bool())) + val addr = Input(UInt(10.W)) + val dataIn = Input(Vec(2, UInt(width.W))) + val dataOut = Output(Vec(2, UInt(width.W))) + }) + + val mem = SyncReadMem(1024, Vec(2, UInt(width.W))) + io.dataOut := DontCare + when(io.enable) { + val rdwrPort = mem(io.addr) + when (io.write) { + when(io.mask(0)) { + rdwrPort(0) := io.dataIn(0) + } + when(io.mask(1)) { + rdwrPort(1) := io.dataIn(1) + } + }.otherwise { io.dataOut := rdwrPort } + } +} +``` + diff --git a/docs/src/explanations/modules.md b/docs/src/explanations/modules.md new file mode 100644 index 00000000..32cbff2e --- /dev/null +++ b/docs/src/explanations/modules.md @@ -0,0 +1,138 @@ +--- +layout: docs +title: "Modules" +section: "chisel3" +--- + +# Modules + +Chisel *modules* are very similar to Verilog *modules* in +defining a hierarchical structure in the generated circuit. + +The hierarchical module namespace is accessible in downstream tools +to aid in debugging and physical layout. A user-defined module is +defined as a *class* which: + + - inherits from `Module`, + - contains at least one interface wrapped in a Module's `IO()` method (traditionally stored in a port field named ```io```), and + - wires together subcircuits in its constructor. + +As an example, consider defining your own two-input multiplexer as a +module: +```scala mdoc:silent +import chisel3._ +class Mux2IO extends Bundle { + val sel = Input(UInt(1.W)) + val in0 = Input(UInt(1.W)) + val in1 = Input(UInt(1.W)) + val out = Output(UInt(1.W)) +} + +class Mux2 extends Module { + val io = IO(new Mux2IO) + io.out := (io.sel & io.in1) | (~io.sel & io.in0) +} +``` + +The wiring interface to a module is a collection of ports in the +form of a ```Bundle```. The interface to the module is defined +through a field named ```io```. For ```Mux2```, ```io``` is +defined as a bundle with four fields, one for each multiplexer port. + +The ```:=``` assignment operator, used here in the body of the +definition, is a special operator in Chisel that wires the input of +left-hand side to the output of the right-hand side. + +### Module Hierarchy + +We can now construct circuit hierarchies, where we build larger modules out +of smaller sub-modules. For example, we can build a 4-input +multiplexer module in terms of the ```Mux2``` module by wiring +together three 2-input multiplexers: + +```scala mdoc:silent +class Mux4IO extends Bundle { + val in0 = Input(UInt(1.W)) + val in1 = Input(UInt(1.W)) + val in2 = Input(UInt(1.W)) + val in3 = Input(UInt(1.W)) + val sel = Input(UInt(2.W)) + val out = Output(UInt(1.W)) +} +class Mux4 extends Module { + val io = IO(new Mux4IO) + + val m0 = Module(new Mux2) + m0.io.sel := io.sel(0) + m0.io.in0 := io.in0 + m0.io.in1 := io.in1 + + val m1 = Module(new Mux2) + m1.io.sel := io.sel(0) + m1.io.in0 := io.in2 + m1.io.in1 := io.in3 + + val m3 = Module(new Mux2) + m3.io.sel := io.sel(1) + m3.io.in0 := m0.io.out + m3.io.in1 := m1.io.out + + io.out := m3.io.out +} +``` + +We again define the module interface as ```io``` and wire up the +inputs and outputs. In this case, we create three ```Mux2``` +children modules, using the ```Module``` constructor function and +the Scala ```new``` keyword to create a +new object. We then wire them up to one another and to the ports of +the ```Mux4``` interface. + +Note: Chisel `Module`s have an implicit clock (called `clock`) and +an implicit reset (called `reset`). To create modules without implicit +clock and reset, Chisel provides `RawModule`. + +> Historical Note: Prior to Chisel 3.5, Modules were restricted to only +having a single user-defined port named `io`. There was also a type called +`MultiIOModule` that provided implicit clock and reset while allowing the +user to define as many ports as they want. This is now the functionality +of `Module`. + +### `RawModule` + +A `RawModule` is a module that **does not provide an implicit clock and reset.** +This can be useful when interfacing a Chisel module with a design that expects +a specific naming convention for clock or reset. + +Then we can use it in place of *Module* usage : +```scala mdoc:silent +import chisel3.{RawModule, withClockAndReset} + +class Foo extends Module { + val io = IO(new Bundle{ + val a = Input(Bool()) + val b = Output(Bool()) + }) + io.b := !io.a +} + +class FooWrapper extends RawModule { + val a_i = IO(Input(Bool())) + val b_o = IO(Output(Bool())) + val clk = Input(Clock()) + val rstn = Input(Bool()) + + val foo = withClockAndReset(clk, !rstn){ Module(new Foo) } + + foo.io.a := a_i + b_o := foo.io.b +} +``` + +In the example above, the `RawModule` is used to change the reset polarity +of module `SlaveSpi`. Indeed, the reset is active high by default in Chisel +modules, then using `withClockAndReset(clock, !rstn)` we can use an active low +reset in entire design. + +The clock is just wired as it, but if needed, `RawModule` can be used in +conjunction with `BlackBox` to connect a differential clock input for example. diff --git a/docs/src/explanations/motivation.md b/docs/src/explanations/motivation.md new file mode 100644 index 00000000..afe02a7b --- /dev/null +++ b/docs/src/explanations/motivation.md @@ -0,0 +1,44 @@ +--- +layout: docs +title: "Motivation" +section: "chisel3" +--- + +# Motivation -- "Why Chisel?" + +We were motivated to develop a new hardware language by years of +struggle with existing hardware description languages in our research +projects and hardware design courses. _Verilog_ and _VHDL_ were developed +as hardware _simulation_ languages, and only later did they become +a basis for hardware _synthesis_. Much of the semantics of these +languages are not appropriate for hardware synthesis and, in fact, +many constructs are simply not synthesizable. Other constructs are +non-intuitive in how they map to hardware implementations, or their +use can accidentally lead to highly inefficient hardware structures. +While it is possible to use a subset of these languages and still get +acceptable results, they nonetheless present a cluttered and confusing +specification model, particularly in an instructional setting. + +However, our strongest motivation for developing a new hardware +language is our desire to change the way that electronic system design +takes place. We believe that it is important to not only teach +students how to design circuits, but also to teach them how to design +*circuit generators* ---programs that automatically generate +designs from a high-level set of design parameters and constraints. +Through circuit generators, we hope to leverage the hard work of +design experts and raise the level of design abstraction for everyone. +To express flexible and scalable circuit construction, circuit +generators must employ sophisticated programming techniques to make +decisions concerning how to best customize their output circuits +according to high-level parameter values and constraints. While +Verilog and VHDL include some primitive constructs for programmatic +circuit generation, they lack the powerful facilities present in +modern programming languages, such as object-oriented programming, +type inference, support for functional programming, and reflection. + +Instead of building a new hardware design language from scratch, we +chose to embed hardware construction primitives within an existing +language. We picked Scala not only because it includes the +programming features we feel are important for building circuit +generators, but because it was specifically developed as a base for +domain-specific languages. diff --git a/docs/src/explanations/muxes-and-input-selection.md b/docs/src/explanations/muxes-and-input-selection.md new file mode 100644 index 00000000..ae087e83 --- /dev/null +++ b/docs/src/explanations/muxes-and-input-selection.md @@ -0,0 +1,62 @@ +--- +layout: docs +title: "Muxes and Input Selection" +section: "chisel3" +--- + +# Muxes and Input Selection + +Selecting inputs is very useful in hardware description, and therefore Chisel provides several built-in generic input-selection implementations. + +### Mux +The first one is `Mux`. This is a 2-input selector. Unlike the `Mux2` example which was presented previously, the built-in `Mux` allows +the inputs (`in0` and `in1`) to be any datatype as long as they are the same subclass of `Data`. + +By using the functional module creation feature presented in the previous section, we can create multi-input selector in a simple way: + +```scala +Mux(c1, a, Mux(c2, b, Mux(..., default))) +``` + +### MuxCase + +The nested `Mux` is not necessary since Chisel also provides the built-in `MuxCase`, which implements that exact feature. +`MuxCase` is an n-way `Mux`, which can be used as follows: + +```scala +MuxCase(default, Array(c1 -> a, c2 -> b, ...)) +``` + +Where each selection dependency is represented as a tuple in a Scala +array [ condition -> selected_input_port ]. + +### MuxLookup +Chisel also provides `MuxLookup` which is an n-way indexed multiplexer: + +```scala +MuxLookup(idx, default, + Array(0.U -> a, 1.U -> b, ...)) +``` + +This is the same as a `MuxCase`, where the conditions are all index based selection: + +```scala +MuxCase(default, + Array((idx === 0.U) -> a, + (idx === 1.U) -> b, ...)) +``` + +Note that the conditions/cases/selectors (eg. c1, c2) must be in parentheses. + +### Mux1H +Another ```Mux``` utility is the one-hot mux, ```Mux1H```. It takes a sequence of selectors and values and returns the value associated with the one selector that is set. If zero or multiple selectors are set the behavior is undefined. For example: + +```scala + val hotValue = chisel3.util.Mux1H(Seq( + io.selector(0) -> 2.U, + io.selector(1) -> 4.U, + io.selector(2) -> 8.U, + io.selector(4) -> 11.U, + )) +``` +```Mux1H``` whenever possible generates *Firrtl* that is readily optimizable as low depth and/or tree. This optimization is not possible when the values are of type ```FixedPoint``` or an aggregate type that contains ```FixedPoint```s and results instead as a simple ```Mux``` tree. This behavior could be sub-optimal. As ```FixedPoint``` is still *experimental* this behavior may change in the future. diff --git a/docs/src/explanations/operators.md b/docs/src/explanations/operators.md new file mode 100644 index 00000000..231a53fa --- /dev/null +++ b/docs/src/explanations/operators.md @@ -0,0 +1,65 @@ +--- +layout: docs +title: "Operators" +section: "chisel3" +--- + +# Chisel Operators + +Chisel defines a set of hardware operators: + +| Operation | Explanation | +| --------- | --------- | +| **Bitwise operators** | **Valid on:** SInt, UInt, Bool | +| `val invertedX = ~x` | Bitwise NOT | +| `val hiBits = x & "h_ffff_0000".U` | Bitwise AND | +| `val flagsOut = flagsIn \| overflow` | Bitwise OR | +| `val flagsOut = flagsIn ^ toggle` | Bitwise XOR | +| **Bitwise reductions.** | **Valid on:** SInt and UInt. Returns Bool. | +| `val allSet = x.andR` | AND reduction | +| `val anySet = x.orR` | OR reduction | +| `val parity = x.xorR` | XOR reduction | +| **Equality comparison.** | **Valid on:** SInt, UInt, and Bool. Returns Bool. | +| `val equ = x === y` | Equality | +| `val neq = x =/= y` | Inequality | +| **Shifts** | **Valid on:** SInt and UInt | +| `val twoToTheX = 1.S << x` | Logical shift left | +| `val hiBits = x >> 16.U` | Right shift (logical on UInt and arithmetic on SInt). | +| **Bitfield manipulation** | **Valid on:** SInt, UInt, and Bool. | +| `val xLSB = x(0)` | Extract single bit, LSB has index 0. | +| `val xTopNibble = x(15, 12)` | Extract bit field from end to start bit position. | +| `val usDebt = Fill(3, "hA".U)` | Replicate a bit string multiple times. | +| `val float = Cat(sign, exponent, mantissa)` | Concatenates bit fields, with first argument on left. | +| **Logical Operations** | **Valid on:** Bool +| `val sleep = !busy` | Logical NOT | +| `val hit = tagMatch && valid` | Logical AND | +| `val stall = src1busy || src2busy` | Logical OR | +| `val out = Mux(sel, inTrue, inFalse)` | Two-input mux where sel is a Bool | +| **Arithmetic operations** | **Valid on Nums:** SInt and UInt. | +| `val sum = a + b` *or* `val sum = a +% b` | Addition (without width expansion) | +| `val sum = a +& b` | Addition (with width expansion) | +| `val diff = a - b` *or* `val diff = a -% b` | Subtraction (without width expansion) | +| `val diff = a -& b` | Subtraction (with width expansion) | +| `val prod = a * b` | Multiplication | +| `val div = a / b` | Division | +| `val mod = a % b` | Modulus | +| **Arithmetic comparisons** | **Valid on Nums:** SInt and UInt. Returns Bool. | +| `val gt = a > b` | Greater than | +| `val gte = a >= b` | Greater than or equal | +| `val lt = a < b` | Less than | +| `val lte = a <= b` | Less than or equal | + +>Our choice of operator names was constrained by the Scala language. +We have to use triple equals```===``` for equality and ```=/=``` +for inequality to allow the +native Scala equals operator to remain usable. + +The Chisel operator precedence is not directly defined as part of the Chisel language. +Practically, it is determined by the evaluation order of the circuit, +which natuarally follows the [Scala operator precedence](https://docs.scala-lang.org/tour/operators.html). +If in doubt of operator precedence, use parentheses. + +> The Chisel/Scala operator precedence is similar but +not identical to precedence in Java or C. Verilog has the same operator precedence as C, but VHDL +does not. Verilog has precedence ordering for logic operations, but in VHDL +those operators have the same precedence and are evaluated from left to right. diff --git a/docs/src/explanations/polymorphism-and-parameterization.md b/docs/src/explanations/polymorphism-and-parameterization.md new file mode 100644 index 00000000..94b896b1 --- /dev/null +++ b/docs/src/explanations/polymorphism-and-parameterization.md @@ -0,0 +1,237 @@ +--- +layout: docs +title: "Polymorphism and Parameterization" +section: "chisel3" +--- + +# Polymorphism and Paramterization + +_This section is advanced and can be skipped at first reading._ + +Scala is a strongly typed language and uses parameterized types to specify generic functions and classes. +In this section, we show how Chisel users can define their own reusable functions and classes using parameterized classes. + +## Parameterized Functions + +Earlier we defined `Mux2` on `Bool`, but now we show how we can define a generic multiplexer function. +We define this function as taking a boolean condition and con and alt arguments (corresponding to then and else expressions) of type `T`: + +```scala +def Mux[T <: Bits](c: Bool, con: T, alt: T): T = { ... } +``` + +where `T` is required to be a subclass of `Bits`. +Scala ensures that in each usage of `Mux`, it can find a common superclass of the actual con and alt argument types, +otherwise it causes a Scala compilation type error. +For example, + +```scala +Mux(c, UInt(10), UInt(11)) +``` + +yields a `UInt` wire because the `con` and `alt` arguments are each of type `UInt`. + + + +## Parameterized Classes + +Like parameterized functions, we can also parameterize classes to make them more reusable. +For instance, we can generalize the Filter class to use any kind of link. +We do so by parameterizing the `FilterIO` class and defining the constructor to take a single argument `gen` of type `T` as below. +```scala mdoc:invisible +import chisel3._ +``` +```scala mdoc:silent +class FilterIO[T <: Data](gen: T) extends Bundle { + val x = Input(gen) + val y = Output(gen) +} +``` + +We can now define `Filter` by defining a module class that also takes a link type constructor argument and passes it through to the `FilterIO` interface constructor: + +```scala mdoc:silent +class Filter[T <: Data](gen: T) extends Module { + val io = IO(new FilterIO(gen)) + // ... +} +``` + +We can now define a `PLink`-based `Filter` as follows: +```scala mdoc:invisible +class SimpleLink extends Bundle { + val data = Output(UInt(16.W)) + val valid = Output(Bool()) +} +class PLink extends SimpleLink { + val parity = Output(UInt(5.W)) +} +``` +```scala mdoc:compile-only +val f = Module(new Filter(new PLink)) +``` + +A generic FIFO could be defined as follows: + +```scala mdoc:silent +import chisel3.util.log2Up + +class DataBundle extends Bundle { + val a = UInt(32.W) + val b = UInt(32.W) +} + +class Fifo[T <: Data](gen: T, n: Int) extends Module { + val io = IO(new Bundle { + val enqVal = Input(Bool()) + val enqRdy = Output(Bool()) + val deqVal = Output(Bool()) + val deqRdy = Input(Bool()) + val enqDat = Input(gen) + val deqDat = Output(gen) + }) + val enqPtr = RegInit(0.U((log2Up(n)).W)) + val deqPtr = RegInit(0.U((log2Up(n)).W)) + val isFull = RegInit(false.B) + val doEnq = io.enqRdy && io.enqVal + val doDeq = io.deqRdy && io.deqVal + val isEmpty = !isFull && (enqPtr === deqPtr) + val deqPtrInc = deqPtr + 1.U + val enqPtrInc = enqPtr + 1.U + val isFullNext = Mux(doEnq && ~doDeq && (enqPtrInc === deqPtr), + true.B, Mux(doDeq && isFull, false.B, + isFull)) + enqPtr := Mux(doEnq, enqPtrInc, enqPtr) + deqPtr := Mux(doDeq, deqPtrInc, deqPtr) + isFull := isFullNext + val ram = Mem(n, gen) + when (doEnq) { + ram(enqPtr) := io.enqDat + } + io.enqRdy := !isFull + io.deqVal := !isEmpty + ram(deqPtr) <> io.deqDat +} +``` + +An Fifo with 8 elements of type DataBundle could then be instantiated as: + +```scala mdoc:compile-only +val fifo = Module(new Fifo(new DataBundle, 8)) +``` + +It is also possible to define a generic decoupled (ready/valid) interface: +```scala mdoc:invisible:reset +import chisel3._ +class DataBundle extends Bundle { + val a = UInt(32.W) + val b = UInt(32.W) +} +``` + +```scala mdoc:silent +class DecoupledIO[T <: Data](data: T) extends Bundle { + val ready = Input(Bool()) + val valid = Output(Bool()) + val bits = Output(data) +} +``` + +This template can then be used to add a handshaking protocol to any +set of signals: + +```scala mdoc:silent +class DecoupledDemo extends DecoupledIO(new DataBundle) +``` + +The FIFO interface can be now be simplified as follows: + +```scala mdoc:silent +class Fifo[T <: Data](data: T, n: Int) extends Module { + val io = IO(new Bundle { + val enq = Flipped(new DecoupledIO(data)) + val deq = new DecoupledIO(data) + }) + // ... +} +``` + +## Parametrization based on Modules + +You can also parametrize modules based on other modules rather than just types. The following is an example of a module parametrized by other modules as opposed to e.g. types. + +```scala mdoc:silent +import chisel3.RawModule +import chisel3.experimental.BaseModule +import chisel3.stage.ChiselStage + +// Provides a more specific interface since generic Module +// provides no compile-time information on generic module's IOs. +trait MyAdder { + def in1: UInt + def in2: UInt + def out: UInt +} + +class Mod1 extends RawModule with MyAdder { + val in1 = IO(Input(UInt(8.W))) + val in2 = IO(Input(UInt(8.W))) + val out = IO(Output(UInt(8.W))) + out := in1 + in2 +} + +class Mod2 extends RawModule with MyAdder { + val in1 = IO(Input(UInt(8.W))) + val in2 = IO(Input(UInt(8.W))) + val out = IO(Output(UInt(8.W))) + out := in1 - in2 +} + +class X[T <: BaseModule with MyAdder](genT: => T) extends Module { + val io = IO(new Bundle { + val in1 = Input(UInt(8.W)) + val in2 = Input(UInt(8.W)) + val out = Output(UInt(8.W)) + }) + val subMod = Module(genT) + io.out := subMod.out + subMod.in1 := io.in1 + subMod.in2 := io.in2 +} + +println(ChiselStage.emitVerilog(new X(new Mod1))) +println(ChiselStage.emitVerilog(new X(new Mod2))) +``` +```scala mdoc:passthrough +println(ChiselStage.emitVerilog(new X(new Mod1))) +println(ChiselStage.emitVerilog(new X(new Mod2))) +``` diff --git a/docs/src/explanations/ports.md b/docs/src/explanations/ports.md new file mode 100644 index 00000000..ce38cf22 --- /dev/null +++ b/docs/src/explanations/ports.md @@ -0,0 +1,67 @@ +--- +layout: docs +title: "Ports" +section: "chisel3" +--- + +# Ports + +Ports are used as interfaces to hardware components. A port is simply +any `Data` object that has directions assigned to its members. + +Chisel provides port constructors to allow a direction to be added +(input or output) to an object at construction time. Primitive port +constructors wrap the type of the port in `Input` or `Output`. + +An example port declaration is as follows: +```scala mdoc:invisible +import chisel3._ +``` +```scala mdoc +class Decoupled extends Bundle { + val ready = Output(Bool()) + val data = Input(UInt(32.W)) + val valid = Input(Bool()) +} +``` + +After defining ```Decoupled```, it becomes a new type that can be +used as needed for module interfaces or for named collections of +wires. + +By folding directions into the object declarations, Chisel is able to +provide powerful wiring constructs described later. + +## Inspecting Module ports + +(Chisel 3.2+) + +Chisel 3.2 introduced `DataMirror.modulePorts` which can be used to inspect the IOs of any Chisel module (this includes modules in both `import chisel3._` and `import Chisel._`, as well as BlackBoxes from each package). +Here is an example of how to use this API: + +```scala mdoc +import chisel3.experimental.DataMirror +import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage} + +class Adder extends Module { + val a = IO(Input(UInt(8.W))) + val b = IO(Input(UInt(8.W))) + val c = IO(Output(UInt(8.W))) + c := a +& b +} + +class Test extends Module { + val adder = Module(new Adder) + // for debug only + adder.a := DontCare + adder.b := DontCare + + // Inspect ports of adder + // See the result below. + DataMirror.modulePorts(adder).foreach { case (name, port) => { + println(s"Found port $name: $port") + }} +} + +(new ChiselStage).execute(Array.empty, Seq(ChiselGeneratorAnnotation(() => new Test))) +``` \ No newline at end of file diff --git a/docs/src/explanations/printing.md b/docs/src/explanations/printing.md new file mode 100644 index 00000000..abd1a427 --- /dev/null +++ b/docs/src/explanations/printing.md @@ -0,0 +1,137 @@ +--- +layout: docs +title: "Printing" +section: "chisel3" +--- + +# Printing in Chisel + +Chisel provides the `printf` function for debugging purposes. It comes in two flavors: + +* [Scala-style](#scala-style) +* [C-style](#c-style) + +### Scala-style + +Chisel also supports printf in a style similar to [Scala's String Interpolation](http://docs.scala-lang.org/overviews/core/string-interpolation.html). Chisel provides a custom string interpolator `p` which can be used as follows: + +```scala mdoc:invisible +import chisel3._ +``` +```scala mdoc:compile-only +val myUInt = 33.U +printf(p"myUInt = $myUInt") // myUInt = 33 +``` + +Note that when concatenating `p"..."` strings, you need to start with a `p"..."` string: + +```scala mdoc:compile-only +// Does not interpolate the second string +val myUInt = 33.U +printf("my normal string" + p"myUInt = $myUInt") +``` + +#### Simple formatting + +Other formats are available as follows: + +```scala mdoc:compile-only +val myUInt = 33.U +// Hexadecimal +printf(p"myUInt = 0x${Hexadecimal(myUInt)}") // myUInt = 0x21 +// Binary +printf(p"myUInt = ${Binary(myUInt)}") // myUInt = 100001 +// Character +printf(p"myUInt = ${Character(myUInt)}") // myUInt = ! +``` + +We recognize that the format specifiers are verbose, so we are working on a more concise syntax. + +#### Aggregate data-types + +Chisel provides default custom "pretty-printing" for Vecs and Bundles. The default printing of a Vec is similar to printing a Seq or List in Scala while printing a Bundle is similar to printing a Scala Map. + +```scala mdoc:compile-only +val myVec = VecInit(5.U, 10.U, 13.U) +printf(p"myVec = $myVec") // myVec = Vec(5, 10, 13) + +val myBundle = Wire(new Bundle { + val foo = UInt() + val bar = UInt() +}) +myBundle.foo := 3.U +myBundle.bar := 11.U +printf(p"myBundle = $myBundle") // myBundle = Bundle(a -> 3, b -> 11) +``` + +#### Custom Printing + +Chisel also provides the ability to specify _custom_ printing for user-defined Bundles. + +```scala mdoc:compile-only +class Message extends Bundle { + val valid = Bool() + val addr = UInt(32.W) + val length = UInt(4.W) + val data = UInt(64.W) + override def toPrintable: Printable = { + val char = Mux(valid, 'v'.U, '-'.U) + p"Message:\n" + + p" valid : ${Character(char)}\n" + + p" addr : 0x${Hexadecimal(addr)}\n" + + p" length : $length\n" + + p" data : 0x${Hexadecimal(data)}\n" + } +} + +val myMessage = Wire(new Message) +myMessage.valid := true.B +myMessage.addr := "h1234".U +myMessage.length := 10.U +myMessage.data := "hdeadbeef".U + +printf(p"$myMessage") +``` + +Which prints the following: + +``` +Message: + valid : v + addr : 0x00001234 + length : 10 + data : 0x00000000deadbeef +``` + +Notice the use of `+` between `p` interpolated "strings". The results of `p` interpolation can be concatenated by using the `+` operator. For more information, please see the documentation + +### C-Style + +Chisel provides `printf` in a similar style to its C namesake. It accepts a double-quoted format string and a variable number of arguments which will then be printed on rising clock edges. Chisel supports the following format specifiers: + +| Format Specifier | Meaning | +| :-----: | :-----: | +| `%d` | decimal number | +| `%x` | hexadecimal number | +| `%b` | binary number | +| `%c` | 8-bit ASCII character | +| `%%` | literal percent | + +It also supports a small set of escape characters: + +| Escape Character | Meaning | +| :-----: | :-----: | +| `\n` | newline | +| `\t` | tab | +| `\"` | literal double quote | +| `\'` | literal single quote | +| `\\` | literal backslash | + +Note that single quotes do not require escaping, but are legal to escape. + +Thus printf can be used in a way very similar to how it is used in C: + +```scala mdoc:compile-only +val myUInt = 32.U +printf("myUInt = %d", myUInt) // myUInt = 32 +``` diff --git a/docs/src/explanations/reset.md b/docs/src/explanations/reset.md new file mode 100644 index 00000000..a99a39e3 --- /dev/null +++ b/docs/src/explanations/reset.md @@ -0,0 +1,179 @@ +--- +layout: docs +title: "Reset" +section: "chisel3" +--- + +# Reset + +```scala mdoc:invisible +import chisel3._ + +class Submodule extends Module +``` + +As of Chisel 3.2.0, Chisel 3 supports both synchronous and asynchronous reset, +meaning that it can natively emit both synchronous and asynchronously reset registers. + +The type of register that is emitted is based on the type of the reset signal associated +with the register. + +There are three types of reset that implement a common trait `Reset`: +* `Bool` - constructed with `Bool()`. Also known as "synchronous reset". +* `AsyncReset` - constructed with `AsyncReset()`. Also known as "asynchronous reset". +* `Reset` - constructed with `Reset()`. Also known as "abstract reset". + +For implementation reasons, the concrete Scala type is `ResetType`. Stylistically we avoid `ResetType`, instead using the common trait `Reset`. + +Registers with reset signals of type `Bool` are emitted as synchronous reset flops. +Registers with reset signals of type `AsyncReset` are emitted as asynchronouly reset flops. +Registers with reset signals of type `Reset` will have their reset type _inferred_ during FIRRTL compilation. + +### Reset Inference + +FIRRTL will infer a concrete type for any signals of type abstract `Reset`. +The rules are as follows: +1. An abstract `Reset` with only signals of type `AsyncReset`, abstract `Reset`, and `DontCare` +in both its fan-in and fan-out will infer to be of type `AsyncReset` +2. An abstract `Reset` with signals of both types `Bool` and `AsyncReset` in its fan-in and fan-out +is an error. +3. Otherwise, an abstract `Reset` will infer to type `Bool`. + +You can think about (3) as the mirror of (1) replacing `AsyncReset` with `Bool` with the additional +rule that abstract `Reset`s with neither `AsyncReset` nor `Bool` in their fan-in and fan-out will +default to type `Bool`. +This "default" case is uncommon and implies that reset signal is ultimately driven by a `DontCare`. + +### Implicit Reset + +A `Module`'s `reset` is of type abstract `Reset`. +Prior to Chisel 3.2.0, the type of this field was `Bool`. +For backwards compatability, if the top-level module has an implicit reset, its type will default to `Bool`. + +#### Setting Implicit Reset Type + +_New in Chisel 3.3.0_ + +If you would like to set the reset type from within a Module (including the top-level `Module`), +rather than relying on _Reset Inference_, you can mixin one of the following traits: +* `RequireSyncReset` - sets the type of `reset` to `Bool` +* `RequireAsyncReset` - sets the type of `reset` to `AsyncReset` + +For example: + +```scala mdoc:silent +class MyAlwaysSyncResetModule extends Module with RequireSyncReset { + val mySyncResetReg = RegInit(false.B) // reset is of type Bool +} +``` + +```scala mdoc:silent +class MyAlwaysAsyncResetModule extends Module with RequireAsyncReset { + val myAsyncResetReg = RegInit(false.B) // reset is of type AsyncReset +} +``` + +**Note:** This sets the concrete type, but the Scala type will remain `Reset`, so casting may still be necessary. +This comes up most often when using a reset of type `Bool` in logic. + + +### Reset-Agnostic Code + +The purpose of abstract `Reset` is to make it possible to design hardware that is agnostic to the +reset discipline used. +This enables code reuse for utilities and designs where the reset discipline does not matter to +the functionality of the block. + +Consider the two example modules below which are agnostic to the type of reset used within them: + +```scala mdoc:silent +class ResetAgnosticModule extends Module { + val io = IO(new Bundle { + val out = UInt(4.W) + }) + val resetAgnosticReg = RegInit(0.U(4.W)) + resetAgnosticReg := resetAgnosticReg + 1.U + io.out := resetAgnosticReg +} + +class ResetAgnosticRawModule extends RawModule { + val clk = IO(Input(Clock())) + val rst = IO(Input(Reset())) + val out = IO(Output(UInt(8.W))) + + val resetAgnosticReg = withClockAndReset(clk, rst)(RegInit(0.U(8.W))) + resetAgnosticReg := resetAgnosticReg + 1.U + out := resetAgnosticReg +} +``` + +These modules can be used in both synchronous and asynchronous reset domains. +Their reset types will be inferred based on the context within which they are used. + +### Forcing Reset Type + +You can set the type of a Module's implicit reset as described [above](#setting-implicit-reset-type). + +You can also cast to force the concrete type of reset. +* `.asBool` will reinterpret a `Reset` as `Bool` +* `.asAsyncReset` will reinterpret a `Reset` as `AsyncReset`. + +You can then use `withReset` to use a cast reset as the implicit reset. +See ["Multiple Clock Domains"](../explanations/multi-clock) for more information about `withReset`. + + +The following will make `myReg` as well as both `resetAgnosticReg`s synchronously reset: + +```scala mdoc:silent +class ForcedSyncReset extends Module { + // withReset's argument becomes the implicit reset in its scope + withReset (reset.asBool) { + val myReg = RegInit(0.U) + val myModule = Module(new ResetAgnosticModule) + + // RawModules do not have implicit resets so withReset has no effect + val myRawModule = Module(new ResetAgnosticRawModule) + // We must drive the reset port manually + myRawModule.rst := Module.reset // Module.reset grabs the current implicit reset + } +} +``` + +The following will make `myReg` as well as both `resetAgnosticReg`s asynchronously reset: + +```scala mdoc:silent +class ForcedAysncReset extends Module { + // withReset's argument becomes the implicit reset in its scope + withReset (reset.asAsyncReset){ + val myReg = RegInit(0.U) + val myModule = Module(new ResetAgnosticModule) // myModule.reset is connected implicitly + + // RawModules do not have implicit resets so withReset has no effect + val myRawModule = Module(new ResetAgnosticRawModule) + // We must drive the reset port manually + myRawModule.rst := Module.reset // Module.reset grabs the current implicit reset + } +} +``` + +**Note:** such casts (`asBool` and `asAsyncReset`) are not checked by FIRRTL. +In doing such a cast, you as the designer are effectively telling the compiler +that you know what you are doing and to force the type as cast. + +### Last-Connect Semantics + +It is **not** legal to override the reset type using last-connect semantics +unless you are overriding a `DontCare`: + +```scala mdoc:silent +class MyModule extends Module { + val resetBool = Wire(Reset()) + resetBool := DontCare + resetBool := false.B // this is fine + withReset(resetBool) { + val mySubmodule = Module(new Submodule()) + } + resetBool := true.B // this is fine + resetBool := false.B.asAsyncReset // this will error in FIRRTL +} +``` diff --git a/docs/src/explanations/sequential-circuits.md b/docs/src/explanations/sequential-circuits.md new file mode 100644 index 00000000..938416ac --- /dev/null +++ b/docs/src/explanations/sequential-circuits.md @@ -0,0 +1,50 @@ +--- +layout: docs +title: "Sequential Circuits" +section: "chisel3" +--- + +# Sequential Circuits + +```scala mdoc:invisible +import chisel3._ +val in = Bool() +``` +The simplest form of state element supported by Chisel is a positive edge-triggered register, which can be instantiated as: +``` scala mdoc:compile-only +val reg = RegNext(in) +``` +This circuit has an output that is a copy of the input signal `in` delayed by one clock cycle. Note that we do not have to specify the type of Reg as it will be automatically inferred from its input when instantiated in this way. In the current version of Chisel, clock and reset are global signals that are implicitly included where needed. + +Note that registers which do not specify an initial value will not change value upon toggling the reset signal. + +Using registers, we can quickly define a number of useful circuit constructs. For example, a rising-edge detector that takes a boolean signal in and outputs true when the current value is true and the previous value is false is given by: + +```scala mdoc:silent +def risingedge(x: Bool) = x && !RegNext(x) +``` +Counters are an important sequential circuit. To construct an up-counter that counts up to a maximum value, max, then wraps around back to zero (i.e., modulo max+1), we write: +```scala mdoc:silent +def counter(max: UInt) = { + val x = RegInit(0.asUInt(max.getWidth.W)) + x := Mux(x === max, 0.U, x + 1.U) + x +} +``` +The counter register is created in the counter function with a reset value of 0 (with width large enough to hold max), to which the register will be initialized when the global reset for the circuit is asserted. The := assignment to x in counter wires an update combinational circuit which increments the counter value unless it hits the max at which point it wraps back to zero. Note that when x appears on the right-hand side of an assignment, its output is referenced, whereas when on the left-hand side, its input is referenced. +Counters can be used to build a number of useful sequential circuits. For example, we can build a pulse generator by outputting true when a counter reaches zero: +```scala mdoc:silent +// Produce pulse every n cycles. +def pulse(n: UInt) = counter(n - 1.U) === 0.U +``` +A square-wave generator can then be toggled by the pulse train, toggling between true and false on each pulse: +```scala mdoc:silent +// Flip internal state when input true. +def toggle(p: Bool) = { + val x = RegInit(false.B) + x := Mux(p, !x, x) + x +} +// Square wave of a given period. +def squareWave(period: UInt) = toggle(pulse(period >> 1)) +``` diff --git a/docs/src/explanations/supported-hardware.md b/docs/src/explanations/supported-hardware.md new file mode 100644 index 00000000..fe3909e7 --- /dev/null +++ b/docs/src/explanations/supported-hardware.md @@ -0,0 +1,11 @@ +--- +layout: docs +title: "Supported Hardware" +section: "chisel3" +--- + +# Supported Hardware + +While Chisel focuses on binary logic, Chisel can support analog and tri-state wires with the `Analog` type - see [Datatypes in Chisel](data-types). + +We focus on binary logic designs as they constitute the vast majority of designs in practice. Tri-state logic are poorly supported standard industry flows and require special/controlled hard macros in order to be done. diff --git a/docs/src/explanations/unconnected-wires.md b/docs/src/explanations/unconnected-wires.md new file mode 100644 index 00000000..48012d12 --- /dev/null +++ b/docs/src/explanations/unconnected-wires.md @@ -0,0 +1,138 @@ +--- +layout: docs +title: "Unconnected Wires" +section: "chisel3" +--- + +# Unconnected Wires + +The Invalidate API [(#645)](https://github.com/freechipsproject/chisel3/pull/645) adds support to Chisel +for reporting unconnected wires as errors. + +Prior to this pull request, Chisel automatically generated a firrtl `is invalid` for `Module IO()`, and each `Wire()` definition. +This made it difficult to detect cases where output signals were never driven. +Chisel now supports a `DontCare` element, which may be connected to an output signal, indicating that that signal is intentionally not driven. +Unless a signal is driven by hardware or connected to a `DontCare`, Firrtl will complain with a "not fully initialized" error. + +### API + +Output signals may be connected to DontCare, generating a `is invalid` when the corresponding firrtl is emitted. + +```scala mdoc:invisible +import chisel3._ +``` +```scala mdoc:silent + +class Out extends Bundle { + val debug = Bool() + val debugOption = Bool() +} +val io = new Bundle { val out = new Out } +``` + +```scala mdoc:compile-only +io.out.debug := true.B +io.out.debugOption := DontCare +``` + +This indicates that the signal `io.out.debugOption` is intentionally not driven and firrtl should not issue a "not fully initialized" +error for this signal. + +This can be applied to aggregates as well as individual signals: +```scala mdoc:invisible +import chisel3._ +``` +```scala mdoc:silent +import chisel3._ +class ModWithVec extends Module { + // ... + val nElements = 5 + val io = IO(new Bundle { + val outs = Output(Vec(nElements, Bool())) + }) + io.outs <> DontCare + // ... +} + +class TrivialInterface extends Bundle { + val in = Input(Bool()) + val out = Output(Bool()) +} + +class ModWithTrivalInterface extends Module { + // ... + val io = IO(new TrivialInterface) + io <> DontCare + // ... +} +``` + +This feature is controlled by `CompileOptions.explicitInvalidate` and is set to `false` in `NotStrict` (Chisel2 compatibility mode), +and `true` in `Strict` mode. + +You can selectively enable this for Chisel2 compatibility mode by providing your own explicit `compileOptions`, +either for a group of Modules (via inheritance): +```scala mdoc:silent +abstract class ExplicitInvalidateModule extends Module()(chisel3.ExplicitCompileOptions.NotStrict.copy(explicitInvalidate = true)) +``` +or on a per-Module basis: +```scala mdoc:silent +class MyModule extends Module { + override val compileOptions = chisel3.ExplicitCompileOptions.NotStrict.copy(explicitInvalidate = true) + val io = IO(new Bundle { /* ... */ } ) + // ... +} +``` + +Or conversely, disable this stricter checking (which is now the default in pure chisel3): +```scala mdoc:silent +abstract class ImplicitInvalidateModule extends Module()(chisel3.ExplicitCompileOptions.Strict.copy(explicitInvalidate = false)) +``` +or on a per-Module basis: +```scala mdoc:invisible:reset +import chisel3._ +``` +```scala mdoc:silent +class MyModule extends Module { + override val compileOptions = chisel3.ExplicitCompileOptions.Strict.copy(explicitInvalidate = false) + val io = IO(new Bundle { /* ... */ } ) + // ... +} +``` + +Please see the corresponding [API tests](https://github.com/freechipsproject/chisel3/blob/master/src/test/scala/chiselTests/InvalidateAPISpec.scala) +for examples. + +### Determining the unconnected element + +I have an interface with 42 wires. +Which one of them is unconnected? + +The firrtl error message should contain something like: +```bash +firrtl.passes.CheckInitialization$RefNotInitializedException: @[:@6.4] : [module Router] Reference io is not fully initialized. + @[Decoupled.scala 38:19:@48.12] : node _GEN_23 = mux(and(UInt<1>("h1"), eq(UInt<2>("h3"), _T_84)), _GEN_2, VOID) @[Decoupled.scala 38:19:@48.12] + @[Router.scala 78:30:@44.10] : node _GEN_36 = mux(_GEN_0.ready, _GEN_23, VOID) @[Router.scala 78:30:@44.10] + @[Router.scala 75:26:@39.8] : node _GEN_54 = mux(io.in.valid, _GEN_36, VOID) @[Router.scala 75:26:@39.8] + @[Router.scala 70:50:@27.6] : node _GEN_76 = mux(io.load_routing_table_request.valid, VOID, _GEN_54) @[Router.scala 70:50:@27.6] + @[Router.scala 65:85:@19.4] : node _GEN_102 = mux(_T_62, VOID, _GEN_76) @[Router.scala 65:85:@19.4] + : io.outs[3].bits.body <= _GEN_102 +``` +The first line is the initial error report. +Successive lines, indented and beginning with source line information indicate connections involving the problematic signal. +Unfortunately, if these are `when` conditions involving muxes, they may be difficult to decipher. +The last line of the group, indented and beginning with a `:` should indicate the uninitialized signal component. +This example (from the [Router tutorial](https://github.com/ucb-bar/chisel-tutorial/blob/release/src/main/scala/examples/Router.scala)) +was produced when the output queue bits were not initialized. +The old code was: +```scala + io.outs.foreach { out => out.noenq() } +``` +which initialized the queue's `valid` bit, but did not initialize the actual output values. +The fix was: +```scala + io.outs.foreach { out => + out.bits := 0.U.asTypeOf(out.bits) + out.noenq() + } +``` diff --git a/docs/src/explanations/width-inference.md b/docs/src/explanations/width-inference.md new file mode 100644 index 00000000..66d9736d --- /dev/null +++ b/docs/src/explanations/width-inference.md @@ -0,0 +1,40 @@ +--- +layout: docs +title: "Width Inference" +section: "chisel3" +--- + +# Width Inference + +Chisel provides bit width inference to reduce design effort. Users are encouraged to manually specify widths of ports and registers to prevent any surprises, but otherwise unspecified widths will be inferred by the Firrtl compiler. + +For all circuit components declared with unspecified widths, the FIRRTL compiler will infer the minimum possible width that maintains the legality of all its incoming connections. Implicit here is that inference is done in a right to left fashion in the sense of an assignment statement in chisel, i.e. from the left hand side from the right hand side. If a component has no incoming connections, and the width is unspecified, then an error is thrown to indicate that the width could not be inferred. + +For module input ports with unspecified widths, the inferred width is the minimum possible width that maintains the legality of all incoming connections to all instantiations of the module. +The width of a ground-typed multiplexor expression is the maximum of its two corresponding input widths. For multiplexing aggregate-typed expressions, the resulting widths of each leaf subelement is the maximum of its corresponding two input leaf subelement widths. +The width of a conditionally valid expression is the width of its input expression. For the full formal description see the [Firrtl Spec](https://github.com/freechipsproject/firrtl/blob/master/spec/spec.pdf). + +Hardware operators have output widths as defined by the following set of rules: + +| operation | bit width | +| --------- | --------- | +| `z = x + y` *or* `z = x +% y` | `w(z) = max(w(x), w(y))` | +| `z = x +& y` | `w(z) = max(w(x), w(y)) + 1` | +| `z = x - y` *or* `z = x -% y` | `w(z) = max(w(x), w(y))` | +| `z = x -& y` | `w(z) = max(w(x), w(y)) + 1` | +| `z = x & y` | `w(z) = max(w(x), w(y))` | +| `z = Mux(c, x, y)` | `w(z) = max(w(x), w(y))` | +| `z = w * y` | `w(z) = w(x) + w(y)` | +| `z = x << n` | `w(z) = w(x) + maxNum(n)` | +| `z = x >> n` | `w(z) = w(x) - minNum(n)` | +| `z = Cat(x, y)` | `w(z) = w(x) + w(y)` | +| `z = Fill(n, x)` | `w(z) = w(x) * maxNum(n)` | + +>where for instance `w(z)` is the bit width of wire `z`, and the `&` +rule applies to all bitwise logical operations. + +Given a path of connections that begins with an unspecified width element (most commonly a top-level input), then the compiler will throw an exception indicating a certain width was uninferrable. + +A common "gotcha" comes from truncating addition and subtraction with the operators `+` and `-`. Users who want the result to maintain the full, expanded precision of the addition or subtraction should use the expanding operators `+&` and `-&`. + +> The default truncating operation comes from Chisel's history as a microprocessor design language. diff --git a/docs/src/introduction.md b/docs/src/introduction.md new file mode 100644 index 00000000..3b68aae2 --- /dev/null +++ b/docs/src/introduction.md @@ -0,0 +1,41 @@ +--- +layout: docs +title: "Introduction" +section: "chisel3" +--- + +# An Introduction to Chisel + +_Chisel_ (Constructing +Hardware In a Scala Embedded Language) is a hardware +construction language embedded in the high-level programming language +Scala. + Chisel is a library of special class +definitions, predefined objects, and usage conventions within [Scala](https://www.scala-lang.org/), +so when you write Chisel you are actually writing a Scala +program that constructs a hardware graph. +As you gain experience and want to make your code simpler or more +reusable, you will find it important to leverage the underlying power +of the Scala language. We recommend you consult one of the excellent +Scala books to become more expert in Scala programming. + +For a tutorial covering both Chisel and Scala, see the +[**online Chisel Bootcamp**](https://mybinder.org/v2/gh/freechipsproject/chisel-bootcamp/master). + +For quick reference "How-To" guides see the [Cookbooks](cookbooks/cookbooks). + +For a deeper introduction to key concepts in Chisel see the [Explanations](explanations/explanations). + +The [API Documentation](https://www.chisel-lang.org/api/) gives the detailed reference for the Chisel source code. +Note that previous versions can be found via the sidebar menu at [https://www.chisel-lang.org/chisel3]. + +The [Resources](resources/resources) provides links to other useful resources for learning and working with Chisel. + +The [Appendix](appendix/appendix) covers some more advanced topics. + +The [Developers](developers/developers) section provides information for those working on the Chisel library itself. + +>Throughout these pages, we format commentary on our design choices as in +this paragraph. You should be able to skip the commentary sections +and still fully understand how to use Chisel, but we hope you'll find +them interesting. diff --git a/docs/src/resources/faqs.md b/docs/src/resources/faqs.md new file mode 100644 index 00000000..debdfcbe --- /dev/null +++ b/docs/src/resources/faqs.md @@ -0,0 +1,284 @@ +--- +layout: docs +title: "Frequently Asked Questions" +section: "chisel3" +--- + +# Frequently Asked Questions + +* [Where should I start if I want to learn Chisel?](#where-should-i-start-if-i-want-to-learn-chisel) +* [How do I ... in Chisel?](#how-do-i-do--eg-like-that-in-verilog-in-chisel) +* [How can I contribute to Chisel?](#how-can-i-contribute-to-chisel) +* [What is the difference between release and master branches?](#what-is-the-difference-between-release-and-master-branches) +* [Why DecoupledIO instead of ReadyValidIO?](#why-decoupledio-instead-of-readyvalidio) +* [Why do I have to wrap module instantiations in `Module(...)`?](#why-do-i-have-to-wrap-module-instantiations-in-module) +* [Why Chisel?](#why-chisel) +* [Does Chisel support X and Z logic values?](#does-chisel-support-x-and-z-logic-values) +* [I just want some Verilog; what do I do?](#get-me-verilog) +* [I just want some FIRRTL; what do I do?](#get-me-firrtl) +* [Why doesn't Chisel tell me which wires aren't connected?](#why-doesnt-chisel-tell-me-which-wires-arent-connected) +* [What does `Reference ... is not fully initialized.` mean?](#what-does-reference--is-not-fully-initialized-mean) +* [Can I specify behavior before and after generated initial blocks?](#can-i-specify-behavior-before-and-after-generated-initial-blocks) + +### Where should I start if I want to learn Chisel? + +We recommend the [Chisel Bootcamp](https://github.com/freechipsproject/chisel-bootcamp) for getting started with Chisel. + +### How do I do ... (e.g. like that in Verilog) in Chisel? + +See the [cookbooks](../cookbooks/cookbook). + +### How can I contribute to Chisel? + +A good to place to start is to fill out the [How Can I Contribute Form](https://docs.google.com/forms/d/e/1FAIpQLSfwTTY8GkfSZ2sU2T2mNpfNMpIM70GlXOrjqiHoC9ZBvwn_CA/viewform). + +### What is the difference between release and master branches? + +We have two main branches for each main Chisel project: + +- `master` +- `release` + +`master` is the main development branch and it is updated frequently (often several times a day). +Although we endeavour to keep the `master` branches in sync, they may drift out of sync for a day or two. +We do not publish the `master` branches. +If you wish to use them, you need to clone the GitHub repositories and use `sbt publishLocal` to make them available on your local machine. + +The `release` branches are updated less often (currently bi-weekly) and we try to guarantee they are in sync. +We publish these to Sonatype/Maven on a bi-weekly basis. + +In general, you can not mix `release` and `master` branches and assume they will work. + +The default branches for the user-facing repositories (chisel-template and chisel-tutorial) are the `release` branches - these should always *just work* for new users as they use the `release` branches of chisel projects. + +If you want to use something more current than the `release` branch, you should `git checkout master` for all the chisel repos you intend to use, then `sbt publishLocal` them in this order: + +- firrtl +- firrtl-interpreter +- chisel3 +- chisel-testers + +Then, if you're working with the user-facing repositories: + +- chisel-tutorial +- chisel-template + +Since this is a substantial amount of work (with no guarantee of success), unless you are actively involved in Chisel development, we encourage you to stick with the `release` branches and their respective dependencies. + +### Why DecoupledIO instead of ReadyValidIO? + +There are multiple kinds of Ready/Valid interfaces that impose varying restrictions on the producers and consumers. Chisel currently provides the following: + +* [DecoupledIO](https://chisel.eecs.berkeley.edu/api/index.html#chisel3.util.DecoupledIO) - No guarantees +* [IrrevocableIO](https://chisel.eecs.berkeley.edu/api/index.html#chisel3.util.IrrevocableIO) - Producer promises to not change the value of 'bits' after a cycle where 'valid' is high and 'ready' is low. Additionally, once 'valid' is raised it will never be lowered until after 'ready' has also been raised. + +### Why do I have to wrap module instantiations in `Module(...)`? + +In short: Limitations of Scala + +Chisel Modules are written by defining a [Scala class](http://docs.scala-lang.org/tutorials/tour/classes.html) and implementing its constructor. As elaboration runs, Chisel constructs a hardware AST from these Modules. The compiler needs hooks to run before and after the actual construction of the Module object. In Scala, superclasses are fully initialized before subclasses, so by extending Module, Chisel has the ability to run some initialization code before the user's Module is constructed. However, there is no such hook to run after the Module object is initialized. By wrapping Module instantiations in the Module object's apply method (ie. `Module(...)`), Chisel is able to perform post-initialization actions. There is a [proposed solution](https://issues.scala-lang.org/browse/SI-4330), so eventually this requirement will be lifted, but for now, wrap those Modules! + +### Why Chisel? + +Borrowed from [Chisel Motivation](../explanations/motivation) + +>We were motivated to develop a new hardware language by years of +struggle with existing hardware description languages in our research +projects and hardware design courses. _Verilog_ and _VHDL_ were developed +as hardware _simulation_ languages, and only later did they become +a basis for hardware _synthesis_. Much of the semantics of these +languages are not appropriate for hardware synthesis and, in fact, +many constructs are simply not synthesizable. Other constructs are +non-intuitive in how they map to hardware implementations, or their +use can accidently lead to highly inefficient hardware structures. +While it is possible to use a subset of these languages and still get +acceptable results, they nonetheless present a cluttered and confusing +specification model, particularly in an instructional setting. + +>However, our strongest motivation for developing a new hardware +language is our desire to change the way that electronic system design +takes place. We believe that it is important to not only teach +students how to design circuits, but also to teach them how to design +*circuit generators* ---programs that automatically generate +designs from a high-level set of design parameters and constraints. +Through circuit generators, we hope to leverage the hard work of +design experts and raise the level of design abstraction for everyone. +To express flexible and scalable circuit construction, circuit +generators must employ sophisticated programming techniques to make +decisions concerning how to best customize their output circuits +according to high-level parameter values and constraints. While +Verilog and VHDL include some primitive constructs for programmatic +circuit generation, they lack the powerful facilities present in +modern programming languages, such as object-oriented programming, +type inference, support for functional programming, and reflection. + +>Instead of building a new hardware design language from scratch, we +chose to embed hardware construction primitives within an existing +language. We picked Scala not only because it includes the +programming features we feel are important for building circuit +generators, but because it was specifically developed as a base for +domain-specific languages. + +### Does Chisel support X and Z logic values + +Chisel does not directly support Verilog logic values ```x``` *unknown* and ```z``` *high-impedance*. There are a number of reasons to want to avoid these values. See:[The Dangers of Living With An X](http://infocenter.arm.com/help/topic/com.arm.doc.arp0009a/Verilog_X_Bugs.pdf) and [Malicious LUT: A stealthy FPGA Trojan injected and triggered by the design flow](http://ieeexplore.ieee.org/document/7827620/). Chisel has it's own eco-system of unit and functional testers that limit the need for ```x``` and ```z``` and their omission simplify language implementation, design, and testing. The circuits created by chisel do not preclude developers from using ```x``` and ```z``` in downstream toolchains as they see fit. + +### Get me Verilog +I wrote a module and I want to see the Verilog; what do I do? + +Here's a simple hello world module in a file HelloWorld.scala. + + +```scala +package intro +``` +```scala mdoc:silent +import chisel3._ +class HelloWorld extends Module { + val io = IO(new Bundle{}) + printf("hello world\n") +} +``` + +Add the following +```scala mdoc:silent +import chisel3.stage.ChiselStage +object VerilogMain extends App { + (new ChiselStage).emitVerilog(new HelloWorld) +} +``` +Now you can get some Verilog. Start sbt: +``` +bash> sbt +> run-main intro.VerilogMain +[info] Running intro.VerilogMain +[info] [0.004] Elaborating design... +[info] [0.100] Done elaborating. +[success] Total time: 1 s, completed Jan 12, 2017 6:24:03 PM +``` +or as a one-liner: +``` +bash> sbt 'runMain intro.VerilogMain' +``` +After either of the above there will be a HelloWorld.v file in the current directory: +```scala mdoc:invisible +val verilog = ChiselStage.emitVerilog(new HelloWorld) +``` +```scala mdoc:passthrough +println("```verilog") +println(verilog) +println("```") +``` + +You can see additional options with +``` +bash> sbt 'runMain intro.HelloWorld --help' +``` +This will return a comprehensive usage line with available options. + +For example to place the output in a directory name buildstuff use +``` +bash> sbt 'runMain intro.HelloWorld --target-dir buildstuff --top-name HelloWorld' +``` + +Alternatively, you can also use the sbt console to invoke the Verilog driver: + +``` +$ sbt +> console +[info] Starting scala interpreter... +Welcome to Scala 2.12.13 (OpenJDK 64-Bit Server VM, Java 1.8.0_275). +Type in expressions for evaluation. Or try :help. + +scala> (new chisel3.stage.ChiselStage).emitVerilog(new HelloWorld()) +chisel3.Driver.execute(Array[String](), () => new HelloWorld) +Elaborating design... +Done elaborating. +res1: String = +"module HelloWorld( + input clock, + input reset +); +... +``` + +As before, there should be a HelloWorld.v file in the current directory. + +Note: Using the following, without the `new`, +will ONLY return the string representation, and will not emit a `.v` file: + +```scala mdoc:silent +ChiselStage.emitVerilog(new HelloWorld()) +``` + +### Get me FIRRTL + +If for some reason you don't want the Verilog (e.g. maybe you want to run some custom transformations before exporting to Verilog), then use something along these lines (replace Multiplier with your module): + +```scala +package intro +``` +```scala mdoc:silent:reset + +import chisel3._ +import chisel3.stage.ChiselStage + +class MyFirrtlModule extends Module { + val io = IO(new Bundle{}) +} + +object FirrtlMain extends App { + (new ChiselStage).emitFirrtl(new MyFirrtlModule) +} +``` + +Run it with: + +``` +sbt 'runMain intro.FirrtlMain' +``` +```scala mdoc:invisible +val theFirrtl = ChiselStage.emitFirrtl(new MyFirrtlModule) +``` +```scala mdoc:passthrough +println("```") +println(theFirrtl) +println("```") +``` + +Alternatively, you can also use the sbt console to invoke the FIRRTL driver directly (replace MyFirrtlModule with your module name): + +``` +$ sbt +> console +[info] Starting scala interpreter... +Welcome to Scala 2.12.13 (OpenJDK 64-Bit Server VM, Java 1.8.0_275). +Type in expressions for evaluation. Or try :help. + +scala> (new chisel3.stage.ChiselStage).emitFirrtl(new MyFirrtlModule) +Elaborating design... +Done elaborating. +res3: String = ... +``` + +### Why doesn't Chisel tell me which wires aren't connected? + +As of commit [c313e13](https://github.com/freechipsproject/chisel3/commit/c313e137d4e562ef20195312501840ceab8cbc6a) it can! +Read more at [Unconnected Wires](../explanations/unconnected-wires) for details. + +### What does `Reference ... is not fully initialized.` mean? + +It means that you have unconnected wires in your design which could be an indication of a design bug. + +In Chisel2 compatibility mode (`NotStrict` compile options), chisel generates firrtl code that disables firrtl's initialized wire checks. +In pure chisel3 (`Strict` compile options), the generated firrtl code does not contain these disablers (`is invalid`). +Output wires that are not driven (not connected) are reported by firrtl as `not fully initialized`. +Read more at [Unconnected Wires](../explanations/unconnected-wires) for details on solving the problem. + +### Can I specify behavior before and after generated initial blocks? +Users may define the following macros if they wish to specify behavior before or after emitted initial blocks. + +* `BEFORE_INITIAL`, which is called before the emitted (non-empty) initial block if it is defined +* `AFTER_INITIAL`, which is called after the emitted (non-empty) initial block if it is defined + +These macros may be useful for turning coverage on and off. diff --git a/docs/src/resources/resources.md b/docs/src/resources/resources.md new file mode 100644 index 00000000..fadd26a1 --- /dev/null +++ b/docs/src/resources/resources.md @@ -0,0 +1,17 @@ +--- +layout: docs +title: "Resources and References" +section: "chisel3" +--- + +# Chisel Resources + +The *best resource* to learn about Chisel is the [**online Chisel Bootcamp**](https://mybinder.org/v2/gh/freechipsproject/chisel-bootcamp/master). This runs in your browser and assumes no prior Scala knowledge. (You may also run this locally via the [backing chisel-bootcamp GitHub repository](https://github.com/freechipsproject/chisel-bootcamp).) + +When you're ready to build your own circuits in Chisel, **we recommend starting from the [Chisel Template](https://github.com/freechipsproject/chisel-template) repository**, which provides a pre-configured project, example design, and testbench. Follow the [chisel-template readme](https://github.com/freechipsproject/chisel-template) to get started. + +The following additional resources and references may be useful: + +- [Chisel Cheatsheet](https://github.com/freechipsproject/chisel-cheatsheet/releases/latest/download/chisel_cheatsheet.pdf) +- [Digital Design With Chisel](https://github.com/schoeberl/chisel-book) +- [Frequently Asked Questions](faqs) diff --git a/docs/src/wiki-deprecated/combinational-circuits.md b/docs/src/wiki-deprecated/combinational-circuits.md deleted file mode 100644 index 813549c9..00000000 --- a/docs/src/wiki-deprecated/combinational-circuits.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -layout: docs -title: "Combinational Circuits" -section: "chisel3" ---- -A circuit is represented as a graph of nodes in Chisel. Each node is -a hardware operator that has zero or more inputs and that drives one -output. A literal, introduced above, is a degenerate kind of node -that has no inputs and drives a constant value on its output. One way -to create and wire together nodes is using textual expressions. For -example, we can express a simple combinational logic circuit -using the following expression: - -```scala -(a & b) | (~c & d) -``` - -The syntax should look familiar, with `&` and `|` -representing bitwise-AND and -OR respectively, and `~` -representing bitwise-NOT. The names `a` through `d` -represent named wires of some (unspecified) width. - -Any simple expression can be converted directly into a circuit tree, -with named wires at the leaves and operators forming the internal -nodes. The final circuit output of the expression is taken from the -operator at the root of the tree, in this example, the bitwise-OR. - -Simple expressions can build circuits in the shape of trees, but to -construct circuits in the shape of arbitrary directed acyclic graphs -(DAGs), we need to describe fan-out. In Chisel, we do this by naming -a wire that holds a subexpression that we can then reference multiple -times in subsequent expressions. We name a wire in Chisel by -declaring a variable. For example, consider the select expression, -which is used twice in the following multiplexer description: -```scala -val sel = a | b -val out = (sel & in1) | (~sel & in0) -``` - -The keyword `val` is part of Scala, and is used to name variables -that have values that won't change. It is used here to name the -Chisel wire, `sel`, holding the output of the first bitwise-OR -operator so that the output can be used multiple times in the second -expression. - -### Wires - -Chisel also supports wires as hardware nodes to which one can assign values or connect other nodes. - -```scala -val myNode = Wire(UInt(8.W)) -when (isReady) { - myNode := 255.U -} .otherwise { - myNode := 0.U -} -``` - -```scala -val myNode = Wire(UInt(8.W)) -when (input > 128.U) { - myNode := 255.U -} .elsewhen (input > 64.U) { - myNode := 1.U -} .otherwise { - myNode := 0.U -} -``` - -Note that the last connection to a Wire takes effect. For example, the following two Chisel circuits are equivalent: - -```scala -val myNode = Wire(UInt(8.W)) -myNode := 10.U -myNode := 0.U -``` - -```scala -val myNode = Wire(UInt(8.W)) -myNode := 0.U -``` diff --git a/docs/src/wiki-deprecated/cookbook.md b/docs/src/wiki-deprecated/cookbook.md deleted file mode 100644 index 9a10a689..00000000 --- a/docs/src/wiki-deprecated/cookbook.md +++ /dev/null @@ -1,470 +0,0 @@ ---- -layout: docs -title: "Cookbook" -section: "chisel3" ---- - -Welcome to the Chisel cookbook. This cookbook is still in early stages. If you have any requests or examples to share, please [file an issue](https://github.com/ucb-bar/chisel3/issues/new) and let us know! - -Please note that these examples make use of [Chisel's scala-style printing](printing#scala-style). - -* Converting Chisel Types to/from UInt - * [How do I create a UInt from an instance of a Bundle?](#how-do-i-create-a-uint-from-an-instance-of-a-bundle) - * [How do I create a Bundle from a UInt?](#how-do-i-create-a-bundle-from-a-uint) - * [How do I create a Vec of Bools from a UInt?](#how-do-i-create-a-vec-of-bools-from-a-uint) - * [How do I create a UInt from a Vec of Bool?](#how-do-i-create-a-uint-from-a-vec-of-bool) -* Vectors and Registers - * [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) -* [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) -* [How do I create an optional I/O?](#how-do-i-create-an-optional-io) -* 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) - * [How do I get Chisel to name the results of vector reads properly?](#how-do-i-get-chisel-to-name-the-results-of-vector-reads-properly) - * [How can I dynamically set/parametrize the name of a module?](#how-can-i-dynamically-setparametrize-the-name-of-a-module) - -## Converting Chisel Types to/from UInt - -### How do I create a UInt from an instance of a Bundle? - -Call [`asUInt`](https://www.chisel-lang.org/api/latest/chisel3/Bundle.html#asUInt():chisel3.UInt) on the [`Bundle`](https://www.chisel-lang.org/api/latest/chisel3/Bundle.html) instance. - -```scala mdoc:silent:reset -import chisel3._ - -class MyBundle extends Bundle { - val foo = UInt(4.W) - val bar = UInt(4.W) -} - -class Foo extends RawModule { - val bundle = Wire(new MyBundle) - bundle.foo := 0xc.U - bundle.bar := 0x3.U - val uint = bundle.asUInt -} -``` - -### How do I create a Bundle from a UInt? - -Use the [`asTypeOf`](https://www.chisel-lang.org/api/latest/chisel3/UInt.html#asTypeOf[T%3C:chisel3.Data](that:T):T) method to reinterpret the [`UInt`](https://www.chisel-lang.org/api/latest/chisel3/UInt.html) as the type of the [`Bundle`](https://www.chisel-lang.org/api/latest/chisel3/Bundle.html). - -```scala mdoc:silent:reset -import chisel3._ - -class MyBundle extends Bundle { - val foo = UInt(4.W) - val bar = UInt(4.W) -} - -class Foo extends RawModule { - val uint = 0xb4.U - val bundle = uint.asTypeOf(new MyBundle) -} -``` - -### How do I create a Vec of Bools from a UInt? - -Use [`VecInit`](https://www.chisel-lang.org/api/latest/chisel3/VecInit$.html) given a `Seq[Bool]` generated using the [`asBools`](https://www.chisel-lang.org/api/latest/chisel3/UInt.html#asBools():Seq[chisel3.Bool]) method. - -```scala mdoc:silent:reset -import chisel3._ - -class Foo extends RawModule { - val uint = 0xc.U - val vec = VecInit(uint.asBools) -} -``` - -### How do I create a UInt from a Vec of Bool? - -Use the builtin function [`asUInt`](https://www.chisel-lang.org/api/latest/chisel3/Vec.html#asUInt():chisel3.UInt) - -```scala mdoc:silent:reset -import chisel3._ - -class Foo extends RawModule { - val vec = VecInit(true.B, false.B, true.B, true.B) - val uint = vec.asUInt -} -``` - -## Vectors and Registers - -### How do I create a Vector of Registers? - -**Rule! Use Reg of Vec not Vec of Reg!** - -You create a [Reg of type Vec](#how-do-i-create-a-reg-of-type-vec). Because Vecs are a *type* (like `UInt`, `Bool`) rather than a *value*, we must bind the Vec to some concrete *value*. - -### How do I create a Reg of type Vec? - -For more information, the API Documentation for [`Vec`](https://www.chisel-lang.org/api/latest/chisel3/Vec.html) provides more information. - -```scala mdoc:silent:reset -import chisel3._ - -class Foo extends RawModule { - val regOfVec = Reg(Vec(4, UInt(32.W))) // Register of 32-bit UInts - regOfVec(0) := 123.U // Assignments to elements of the Vec - regOfVec(1) := 456.U - regOfVec(2) := 789.U - regOfVec(3) := regOfVec(0) - - // Reg of Vec of 32-bit UInts initialized to zero - // Note that Seq.fill constructs 4 32-bit UInt literals with the value 0 - // VecInit(...) then constructs a Wire of these literals - // The Reg is then initialized to the value of the Wire (which gives it the same type) - val initRegOfVec = RegInit(VecInit(Seq.fill(4)(0.U(32.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. -State transitions are then handled with [`switch`](https://www.chisel-lang.org/api/latest/chisel3/util/switch$.html)/[`is`](https://www.chisel-lang.org/api/latest/chisel3/util/is$.html) and [`when`](https://www.chisel-lang.org/api/latest/chisel3/when$.html)/[`.elsewhen`](https://www.chisel-lang.org/api/latest/chisel3/WhenContext.html#elsewhen(elseCond:=%3Echisel3.Bool)(block:=%3EUnit)(implicitsourceInfo:chisel3.internal.sourceinfo.SourceInfo,implicitcompileOptions:chisel3.CompileOptions):chisel3.WhenContext)/[`.otherwise`](https://www.chisel-lang.org/api/latest/chisel3/WhenContext.html#otherwise(block:=%3EUnit)(implicitsourceInfo:chisel3.internal.sourceinfo.SourceInfo,implicitcompileOptions:chisel3.CompileOptions):Unit). - -```scala mdoc:silent:reset -import chisel3._ -import chisel3.util.{switch, is} -import chisel3.experimental.ChiselEnum - -object DetectTwoOnes { - object State extends ChiselEnum { - val sNone, sOne1, sTwo1s = Value - } -} - -/* This FSM detects two 1's one after the other */ -class DetectTwoOnes extends Module { - import DetectTwoOnes.State - import DetectTwoOnes.State._ - - val io = IO(new Bundle { - val in = Input(Bool()) - val out = Output(Bool()) - val state = Output(State()) - }) - - val state = RegInit(sNone) - - io.out := (state === sTwo1s) - io.state := state - - switch (state) { - is (sNone) { - when (io.in) { - state := sOne1 - } - } - is (sOne1) { - when (io.in) { - state := sTwo1s - } .otherwise { - state := sNone - } - } - is (sTwo1s) { - when (!io.in) { - state := sNone - } - } - } -} -``` - -Note: the `is` statement can take multiple conditions e.g. `is (sTwo1s, sOne1) { ... }`. - -### How do I unpack a value ("reverse concatenation") like in Verilog? - -In Verilog, you can do something like the following which will unpack a the value `z`: - -```verilog -wire [1:0] a; -wire [3:0] b; -wire [2:0] c; -wire [8:0] z = [...]; -assign {a,b,c} = z; -``` - -Unpacking often corresponds to reinterpreting an unstructured data type as a structured data type. -Frequently, this structured type is used prolifically in the design, and has been declared as in the following example: - -```scala mdoc:silent:reset -import chisel3._ - -class MyBundle extends Bundle { - val a = UInt(2.W) - val b = UInt(4.W) - val c = UInt(3.W) -} -``` - -The easiest way to accomplish this in Chisel would be: - -```scala mdoc:silent -class Foo extends RawModule { - val z = Wire(UInt(9.W)) - z := DontCare // This is a dummy connection - val unpacked = z.asTypeOf(new MyBundle) - printf("%d", unpacked.a) - printf("%d", unpacked.b) - printf("%d", unpacked.c) -} -``` - -If you **really** need to do this for a one-off case (Think thrice! It is likely you can better structure the code using bundles), then rocket-chip has a [Split utility](https://github.com/freechipsproject/rocket-chip/blob/723af5e6b69e07b5f94c46269a208a8d65e9d73b/src/main/scala/util/Misc.scala#L140) which can accomplish this. - -### How do I do subword assignment (assign to some bits in a UInt)? - -You may try to do something like the following where you want to assign only some bits of a Chisel type. -Below, the left-hand side connection to `io.out(0)` is not allowed. - -```scala mdoc:silent:reset -import chisel3._ -import chisel3.stage.{ChiselStage, ChiselGeneratorAnnotation} - -class Foo extends Module { - val io = IO(new Bundle { - val bit = Input(Bool()) - val out = Output(UInt(10.W)) - }) - io.out(0) := io.bit -} -``` - -If you try to compile this, you will get an error. -```scala mdoc:crash -(new ChiselStage).execute(Array("-X", "verilog"), Seq(new ChiselGeneratorAnnotation(() => new Foo))) -``` - -Chisel3 *does not support subword assignment*. -The reason for this is that subword assignment generally hints at a better abstraction with an aggregate/structured types, i.e., a `Bundle` or a `Vec`. - -If you must express it this way, one approach is to blast your `UInt` to a `Vec` of `Bool` and back: - -```scala mdoc:silent:reset -import chisel3._ - -class Foo extends Module { - val io = IO(new Bundle { - val in = Input(UInt(10.W)) - val bit = Input(Bool()) - val out = Output(UInt(10.W)) - }) - val bools = VecInit(io.in.asBools) - bools(0) := io.bit - io.out := bools.asUInt -} -``` - - -### How do I create an optional I/O? - -The following example is a module which includes the optional port `out2` only if the given parameter is `true`. - -```scala mdoc:silent:reset -import chisel3._ - -class ModuleWithOptionalIOs(flag: Boolean) extends Module { - val io = IO(new Bundle { - val in = Input(UInt(12.W)) - val out = Output(UInt(12.W)) - val out2 = if (flag) Some(Output(UInt(12.W))) else None - }) - - io.out := io.in - if (flag) { - io.out2.get := io.in - } -} -``` - -The following is an example where an entire `IO` is optional: - -```scala mdoc:silent:reset -import chisel3._ - -class ModuleWithOptionalIO(flag: Boolean) extends Module { - val in = if (flag) Some(IO(Input(Bool()))) else None - val out = IO(Output(Bool())) - - out := in.getOrElse(false.B) -} -``` - -## Predictable Naming - -### How do I get Chisel to name signals properly in blocks like when/withClockAndReset? - -To get Chisel to name signals (wires and registers) declared inside of blocks like `when`, `withClockAndReset`, etc, use the [`@chiselName`](https://www.chisel-lang.org/api/latest/chisel3/experimental/package$$chiselName.html) annotation as shown below: - -```scala mdoc:silent:reset -import chisel3._ -import chisel3.experimental.chiselName - -@chiselName -class TestMod extends Module { - val io = IO(new Bundle { - val a = Input(Bool()) - val b = Output(UInt(4.W)) - }) - when (io.a) { - val innerReg = RegInit(5.U(4.W)) - innerReg := innerReg + 1.U - io.b := innerReg - } .otherwise { - io.b := 10.U - } -} -``` - -Note that you will need to add the following line to your project's `build.sbt` file. - -```scala -addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full) -``` - -If we compile this module *without* `@chiselName`, Chisel is not able to name `innerReg` correctly (notice the `_T`): - -```scala mdoc:passthrough -import chisel3.stage.{ChiselStage, ChiselGeneratorAnnotation} -import firrtl.annotations.DeletedAnnotation -import firrtl.EmittedVerilogCircuitAnnotation - -class TestModWithout extends Module { - override val desiredName = "TestMod" - val io = IO(new Bundle { - val a = Input(Bool()) - val b = Output(UInt(4.W)) - }) - when (io.a) { - val innerReg = RegInit(5.U(4.W)) - innerReg := innerReg + 1.U - io.b := innerReg - } .otherwise { - io.b := 10.U - } -} - -(new ChiselStage) - .execute(Array("-X", "verilog"), Seq(ChiselGeneratorAnnotation(() => new TestModWithout))) - .collectFirst{ case DeletedAnnotation(_, a: EmittedVerilogCircuitAnnotation) => a.value.value } - .foreach(a => println(s"""|```verilog - |$a - |```""".stripMargin)) -``` - -However, if we use `@chiselName` then the register previously called `_T` is now `innerReg`: -```scala mdoc:passthrough -(new ChiselStage) - .execute(Array("-X", "verilog"), Seq(ChiselGeneratorAnnotation(() => new TestMod))) - .collectFirst{ case DeletedAnnotation(_, a: EmittedVerilogCircuitAnnotation) => a.value.value } - .foreach(a => println(s"""|```verilog - |$a - |```""".stripMargin)) -``` -### How do I get Chisel to name the results of vector reads properly? -Currently, name information is lost when using dynamic indexing. For example: -```scala -class Foo extends Module { - val io = IO(new Bundle { - val in = Input(Vec(4, Bool())) - val idx = Input(UInt(2.W)) - val en = Input(Bool()) - val out = Output(Bool()) - }) - - val x = io.in(io.idx) - val y = x && io.en - io.out := y -} -``` - -The above code loses the `x` name, instead using `_GEN_3` (the other `_GEN_*` signals are expected). -```verilog -module Foo( - input clock, - input reset, - input io_in_0, - input io_in_1, - input io_in_2, - input io_in_3, - input [1:0] io_idx, - input io_en, - output io_out -); - wire _GEN_1; // @[main.scala 15:13] - wire _GEN_2; // @[main.scala 15:13] - wire _GEN_3; // @[main.scala 15:13] - assign _GEN_1 = 2'h1 == io_idx ? io_in_1 : io_in_0; // @[main.scala 15:13] - assign _GEN_2 = 2'h2 == io_idx ? io_in_2 : _GEN_1; // @[main.scala 15:13] - assign _GEN_3 = 2'h3 == io_idx ? io_in_3 : _GEN_2; // @[main.scala 15:13] - assign io_out = _GEN_3 & io_en; // @[main.scala 16:10] -endmodule -``` - -This can be worked around by creating a wire and connecting the dynamic index to the wire: -```scala -val x = WireInit(io.in(io.idx)) -``` - -Which produces: -```verilog -module Foo( - input clock, - input reset, - input io_in_0, - input io_in_1, - input io_in_2, - input io_in_3, - input [1:0] io_idx, - input io_en, - output io_out -); - wire _GEN_1; - wire _GEN_2; - wire x; - assign _GEN_1 = 2'h1 == io_idx ? io_in_1 : io_in_0; - assign _GEN_2 = 2'h2 == io_idx ? io_in_2 : _GEN_1; - assign x = 2'h3 == io_idx ? io_in_3 : _GEN_2; - assign io_out = x & io_en; // @[main.scala 16:10] -endmodule -``` -### How can I dynamically set/parametrize the name of a module? - -You can override the `desiredName` function. This works with normal Chisel modules and `BlackBox`es. Example: - -```scala mdoc:silent:reset -import chisel3._ - -class Coffee extends BlackBox { - val io = IO(new Bundle { - val I = Input(UInt(32.W)) - val O = Output(UInt(32.W)) - }) - override def desiredName = "Tea" -} - -class Salt extends Module { - val io = IO(new Bundle {}) - val drink = Module(new Coffee) - override def desiredName = "SodiumMonochloride" -} -``` - -Elaborating the Chisel module `Salt` yields our "desire name" for `Salt` and `Coffee` in the output Verilog: -```scala mdoc:passthrough -import chisel3.stage.{ChiselStage, ChiselGeneratorAnnotation} -import firrtl.annotations.DeletedAnnotation -import firrtl.EmittedVerilogCircuitAnnotation - -(new ChiselStage) - .execute(Array("-X", "verilog"), Seq(ChiselGeneratorAnnotation(() => new Salt))) - .collectFirst{ case DeletedAnnotation(_, a: EmittedVerilogCircuitAnnotation) => a.value.value } - .foreach(a => println(s"""|```verilog - |$a - |```""".stripMargin)) -``` diff --git a/docs/src/wiki-deprecated/data-types.md b/docs/src/wiki-deprecated/data-types.md deleted file mode 100644 index e931247f..00000000 --- a/docs/src/wiki-deprecated/data-types.md +++ /dev/null @@ -1,134 +0,0 @@ ---- -layout: docs -title: "Data Types" -section: "chisel3" ---- - -Chisel datatypes are used to specify the type of values held in state -elements or flowing on wires. While hardware designs ultimately -operate on vectors of binary digits, other more abstract -representations for values allow clearer specifications and help the -tools generate more optimal circuits. In Chisel, a raw collection of -bits is represented by the ```Bits``` type. Signed and unsigned integers -are considered subsets of fixed-point numbers and are represented by -types ```SInt``` and ```UInt``` respectively. Signed fixed-point -numbers, including integers, are represented using two's-complement -format. Boolean values are represented as type ```Bool```. Note -that these types are distinct from Scala's builtin types such as -```Int``` or ```Boolean```. - -> There is a new experimental type **Interval** which gives the developer more control of the type by allowing the definition of an IntervalRange. See: [Interval Type](interval-type) - -Additionally, Chisel defines `Bundles` for making -collections of values with named fields (similar to ```structs``` in -other languages), and ```Vecs``` for indexable collections of -values. - -Bundles and Vecs will be covered later. - -Constant or literal values are expressed using Scala integers or -strings passed to constructors for the types: -```scala -1.U // decimal 1-bit lit from Scala Int. -"ha".U // hexadecimal 4-bit lit from string. -"o12".U // octal 4-bit lit from string. -"b1010".U // binary 4-bit lit from string. - -5.S // signed decimal 4-bit lit from Scala Int. --8.S // negative decimal 4-bit lit from Scala Int. -5.U // unsigned decimal 3-bit lit from Scala Int. - -8.U(4.W) // 4-bit unsigned decimal, value 8. --152.S(32.W) // 32-bit signed decimal, value -152. - -true.B // Bool lits from Scala lits. -false.B -``` -Underscores can be used as separators in long string literals to aid -readability, but are ignored when creating the value, e.g.: -```scala -"h_dead_beef".U // 32-bit lit of type UInt -``` - -By default, the Chisel compiler will size each constant to the minimum -number of bits required to hold the constant, including a sign bit for -signed types. Bit widths can also be specified explicitly on -literals, as shown below. Note that (`.W` is used to cast a Scala Int -to a Chisel width) -```scala -"ha".asUInt(8.W) // hexadecimal 8-bit lit of type UInt -"o12".asUInt(6.W) // octal 6-bit lit of type UInt -"b1010".asUInt(12.W) // binary 12-bit lit of type UInt - -5.asSInt(7.W) // signed decimal 7-bit lit of type SInt -5.asUInt(8.W) // unsigned decimal 8-bit lit of type UInt -``` - -For literals of type ```UInt```, the value is -zero-extended to the desired bit width. For literals of type -```SInt```, the value is sign-extended to fill the desired bit width. -If the given bit width is too small to hold the argument value, then a -Chisel error is generated. - ->We are working on a more concise literal syntax for Chisel using -symbolic prefix operators, but are stymied by the limitations of Scala -operator overloading and have not yet settled on a syntax that is -actually more readable than constructors taking strings. - ->We have also considered allowing Scala literals to be automatically -converted to Chisel types, but this can cause type ambiguity and -requires an additional import. - ->The SInt and UInt types will also later support an optional exponent -field to allow Chisel to automatically produce optimized fixed-point -arithmetic circuits. - -## Casting - -We can also cast types in Chisel: - -```scala -val sint = 3.S(4.W) // 4-bit SInt - -val uint = sint.asUInt // cast SInt to UInt -uint.asSInt // cast UInt to SInt -``` - -**NOTE**: `asUInt`/`asSInt` with an explicit width can **not** be used to cast (convert) between Chisel datatypes. -No width parameter is accepted, as Chisel will automatically pad or truncate as required when the objects are connected. - -We can also perform casts on clocks, though you should be careful about this, since clocking (especially in ASIC) requires special attention: - -```scala -val bool: Bool = false.B // always-low wire -val clock = bool.asClock // always-low clock - -clock.asUInt // convert clock to UInt (width 1) -clock.asUInt.asBool // convert clock to Bool (Chisel 3.2+) -clock.asUInt.toBool // convert clock to Bool (Chisel 3.0 and 3.1 only) -``` - -## Analog/BlackBox type - -(Experimental, Chisel 3.1+) - -Chisel supports an `Analog` type (equivalent to Verilog `inout`) that can be used to support arbitrary nets in Chisel. This includes analog wires, tri-state/bi-directional wires, and power nets (with appropriate annotations). - -`Analog` is an undirectioned type, and so it is possible to connect multiple `Analog` nets together using the `attach` operator. It is possible to connect an `Analog` **once** using `<>` but illegal to do it more than once. - -```scala -val a = IO(Analog(1.W)) -val b = IO(Analog(1.W)) -val c = IO(Analog(1.W)) - -// Legal -attach(a, b) -attach(a, c) - -// Legal -a <> b - -// Illegal - connects 'a' multiple times -a <> b -a <> c -``` diff --git a/docs/src/wiki-deprecated/developers.md b/docs/src/wiki-deprecated/developers.md deleted file mode 100644 index 4c133c54..00000000 --- a/docs/src/wiki-deprecated/developers.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -layout: docs -title: "Developers" -section: "chisel3" ---- -# Developer Documentation - -Tips and tricks for Chisel developers. - -* [Embedding Chisel as an sbt subproject](sbt-subproject) -* [Test Coverage](test-coverage) diff --git a/docs/src/wiki-deprecated/faqs.md b/docs/src/wiki-deprecated/faqs.md deleted file mode 100644 index 45694cdc..00000000 --- a/docs/src/wiki-deprecated/faqs.md +++ /dev/null @@ -1,246 +0,0 @@ ---- -layout: docs -title: "Frequently Asked Questions" -section: "chisel3" ---- - -* [Where should I start if I want to learn Chisel?](#where-should-i-start-if-i-want-to-learn-chisel) -* [How do I ... in Chisel?](#how-do-i-do--eg-like-that-in-verilog-in-chisel) -* [How can I contribute to Chisel?](#how-can-i-contribute-to-chisel) -* [What is the difference between release and master branches?](#what-is-the-difference-between-release-and-master-branches) -* [Why DecoupledIO instead of ReadyValidIO?](#why-decoupledio-instead-of-readyvalidio) -* [Why do I have to wrap module instantiations in `Module(...)`?](#why-do-i-have-to-wrap-module-instantiations-in-module) -* [Why Chisel?](#why-chisel) -* [Does Chisel support X and Z logic values?](#does-chisel-support-x-and-z-logic-values) -* [I just want some Verilog; what do I do?](#get-me-verilog) -* [I just want some FIRRTL; what do I do?](#get-me-firrtl) -* [Why doesn't Chisel tell me which wires aren't connected?](#why-doesnt-chisel-tell-me-which-wires-arent-connected) -* [What does `Reference ... is not fully initialized.` mean?](#what-does-reference--is-not-fully-initialized-mean) -* [Can I specify behavior before and after generated initial blocks?](#can-i-specify-behavior-before-and-after-generated-initial-blocks) - -### Where should I start if I want to learn Chisel? - -We recommend the [Chisel Bootcamp](https://github.com/freechipsproject/chisel-bootcamp) for getting started with Chisel. - -### How do I do ... (e.g. like that in Verilog) in Chisel? - -See the [cookbook](cookbook). - -### How can I contribute to Chisel? - -A good to place to start is to fill out the [How Can I Contribute Form](https://docs.google.com/forms/d/e/1FAIpQLSfwTTY8GkfSZ2sU2T2mNpfNMpIM70GlXOrjqiHoC9ZBvwn_CA/viewform). - -### What is the difference between release and master branches? - -We have two main branches for each main Chisel project: - -- `master` -- `release` - -`master` is the main development branch and it is updated frequently (often several times a day). -Although we endeavour to keep the `master` branches in sync, they may drift out of sync for a day or two. -We do not publish the `master` branches. -If you wish to use them, you need to clone the GitHub repositories and use `sbt publishLocal` to make them available on your local machine. - -The `release` branches are updated less often (currently bi-weekly) and we try to guarantee they are in sync. -We publish these to Sonatype/Maven on a bi-weekly basis. - -In general, you can not mix `release` and `master` branches and assume they will work. - -The default branches for the user-facing repositories (chisel-template and chisel-tutorial) are the `release` branches - these should always *just work* for new users as they use the `release` branches of chisel projects. - -If you want to use something more current than the `release` branch, you should `git checkout master` for all the chisel repos you intend to use, then `sbt publishLocal` them in this order: - -- firrtl -- firrtl-interpreter -- chisel3 -- chisel-testers - -Then, if you're working with the user-facing repositories: - -- chisel-tutorial -- chisel-template - -Since this is a substantial amount of work (with no guarantee of success), unless you are actively involved in Chisel development, we encourage you to stick with the `release` branches and their respective dependencies. - -### Why DecoupledIO instead of ReadyValidIO? - -There are multiple kinds of Ready/Valid interfaces that impose varying restrictions on the producers and consumers. Chisel currently provides the following: - -* [DecoupledIO](https://chisel.eecs.berkeley.edu/api/index.html#chisel3.util.DecoupledIO) - No guarantees -* [IrrevocableIO](https://chisel.eecs.berkeley.edu/api/index.html#chisel3.util.IrrevocableIO) - Producer promises to not change the value of 'bits' after a cycle where 'valid' is high and 'ready' is low. Additionally, once 'valid' is raised it will never be lowered until after 'ready' has also been raised. - -### Why do I have to wrap module instantiations in `Module(...)`? - -In short: Limitations of Scala - -Chisel Modules are written by defining a [Scala class](http://docs.scala-lang.org/tutorials/tour/classes.html) and implementing its constructor. As elaboration runs, Chisel constructs a hardware AST from these Modules. The compiler needs hooks to run before and after the actual construction of the Module object. In Scala, superclasses are fully initialized before subclasses, so by extending Module, Chisel has the ability to run some initialization code before the user's Module is constructed. However, there is no such hook to run after the Module object is initialized. By wrapping Module instantiations in the Module object's apply method (ie. `Module(...)`), Chisel is able to perform post-initialization actions. There is a [proposed solution](https://issues.scala-lang.org/browse/SI-4330), so eventually this requirement will be lifted, but for now, wrap those Modules! - -### Why Chisel? - -Borrowed from [Chisel Introduction](introduction) - ->We were motivated to develop a new hardware language by years of -struggle with existing hardware description languages in our research -projects and hardware design courses. _Verilog_ and _VHDL_ were developed -as hardware _simulation_ languages, and only later did they become -a basis for hardware _synthesis_. Much of the semantics of these -languages are not appropriate for hardware synthesis and, in fact, -many constructs are simply not synthesizable. Other constructs are -non-intuitive in how they map to hardware implementations, or their -use can accidently lead to highly inefficient hardware structures. -While it is possible to use a subset of these languages and still get -acceptable results, they nonetheless present a cluttered and confusing -specification model, particularly in an instructional setting. - ->However, our strongest motivation for developing a new hardware -language is our desire to change the way that electronic system design -takes place. We believe that it is important to not only teach -students how to design circuits, but also to teach them how to design -*circuit generators* ---programs that automatically generate -designs from a high-level set of design parameters and constraints. -Through circuit generators, we hope to leverage the hard work of -design experts and raise the level of design abstraction for everyone. -To express flexible and scalable circuit construction, circuit -generators must employ sophisticated programming techniques to make -decisions concerning how to best customize their output circuits -according to high-level parameter values and constraints. While -Verilog and VHDL include some primitive constructs for programmatic -circuit generation, they lack the powerful facilities present in -modern programming languages, such as object-oriented programming, -type inference, support for functional programming, and reflection. - ->Instead of building a new hardware design language from scratch, we -chose to embed hardware construction primitives within an existing -language. We picked Scala not only because it includes the -programming features we feel are important for building circuit -generators, but because it was specifically developed as a base for -domain-specific languages. - -### Does Chisel support X and Z logic values - -Chisel does not directly support Verilog logic values ```x``` *unknown* and ```z``` *high-impedance*. There are a number of reasons to want to avoid these values. See:[The Dangers of Living With An X](http://infocenter.arm.com/help/topic/com.arm.doc.arp0009a/Verilog_X_Bugs.pdf) and [Malicious LUT: A stealthy FPGA Trojan injected and triggered by the design flow](http://ieeexplore.ieee.org/document/7827620/). Chisel has it's own eco-system of unit and functional testers that limit the need for ```x``` and ```z``` and their omission simplify language implementation, design, and testing. The circuits created by chisel do not preclude developers from using ```x``` and ```z``` in downstream toolchains as they see fit. - -### Get me Verilog -I wrote a module and I want to see the Verilog; what do I do? - -Here's a simple hello world module in a file HelloWorld.scala. - -```scala -package intro -import chisel3._ -class HelloWorld extends Module { - val io = IO(new Bundle{}) - printf("hello world\n") -} -``` -Add the following -```scala -object HelloWorld extends App { - chisel3.Driver.execute(args, () => new HelloWorld) -} -``` -Now you can get some Verilog. Start sbt: -``` -bash> sbt -> run-main intro.HelloWorld -[info] Running examples.HelloWorld -[info] [0.004] Elaborating design... -[info] [0.100] Done elaborating. -[success] Total time: 1 s, completed Jan 12, 2017 6:24:03 PM -``` -or as a one-liner: -``` -bash> sbt 'runMain intro.HelloWorld' -``` -After either of the above there will be a HelloWorld.v file in the current directory. - -You can see additional options with -``` -bash> sbt 'runMain intro.HelloWorld --help' -``` -This will return a comprehensive usage line with available options. - -For example to place the output in a directory name buildstuff use -``` -bash> sbt 'runMain intro.HelloWorld --target-dir buildstuff --top-name HelloWorld' -``` - -Alternatively, you can also use the sbt console to invoke the Verilog driver: - -``` -$ sbt -> console -[info] Starting scala interpreter... -[info] -Welcome to Scala 2.11.8 (OpenJDK 64-Bit Server VM, Java 1.8.0_121). -Type in expressions for evaluation. Or try :help. -scala> chisel3.Driver.execute(Array[String](), () => new HelloWorld) -chisel3.Driver.execute(Array[String](), () => new HelloWorld) -[info] [0.014] Elaborating design... -[info] [0.306] Done elaborating. -Total FIRRTL Compile Time: 838.8 ms -res3: chisel3.ChiselExecutionResult = [...] -``` - -As before, there should be a HelloWorld.v file in the current directory. - -### Get me FIRRTL - -If for some reason you don't want the Verilog (e.g. maybe you want to run some custom transformations before exporting to Verilog), then use something along these lines (replace Multiplier with your module): - -```scala -package intro - -import chisel3._ -import java.io.File - -object Main extends App { - val f = new File("Multiplier.fir") - chisel3.Driver.dumpFirrtl(chisel3.Driver.elaborate(() => new Multiplier), Option(f)) -} -``` - -Run it with: - -``` -sbt 'runMain intro.Main' -``` - -Alternatively, you can also use the sbt console to invoke the FIRRTL driver directly (replace HelloWorld with your module name): - -``` -$ sbt -> console -[info] Starting scala interpreter... -[info] -Welcome to Scala 2.11.11 (OpenJDK 64-Bit Server VM, Java 1.8.0_151). -Type in expressions for evaluation. Or try :help. -scala> chisel3.Driver.dumpFirrtl(chisel3.Driver.elaborate(() => new HelloWorld), Option(new java.io.File("output.fir"))) -chisel3.Driver.dumpFirrtl(chisel3.Driver.elaborate(() => new HelloWorld), Option(new java.io.File("output.fir"))) -[info] [0.000] Elaborating design... -[info] [0.001] Done elaborating. -res3: java.io.File = output.fir -``` - -### Why doesn't Chisel tell me which wires aren't connected? - -As of commit [c313e13](https://github.com/freechipsproject/chisel3/commit/c313e137d4e562ef20195312501840ceab8cbc6a) it can! -Please visit the wiki page [Unconnected Wires](unconnected-wires) for details. - -### What does `Reference ... is not fully initialized.` mean? - -It means that you have unconnected wires in your design which could be an indication of a design bug. - -In Chisel2 compatibility mode (`NotStrict` compile options), chisel generates firrtl code that disables firrtl's initialized wire checks. -In pure chisel3 (`Strict` compile options), the generated firrtl code does not contain these disablers (`is invalid`). -Output wires that are not driven (not connected) are reported by firrtl as `not fully initialized`. -Please visit the wiki page [Unconnected Wires](unconnected-wires) for details on solving the problem. - -### Can I specify behavior before and after generated initial blocks? -Users may define the following macros if they wish to specify behavior before or after emitted initial blocks. - -* `BEFORE_INITIAL`, which is called before the emitted (non-empty) initial block if it is defined -* `AFTER_INITIAL`, which is called after the emitted (non-empty) initial block if it is defined - -These macros may be useful for turning coverage on and off. diff --git a/docs/src/wiki-deprecated/functional-abstraction.md b/docs/src/wiki-deprecated/functional-abstraction.md deleted file mode 100644 index 04dde7d1..00000000 --- a/docs/src/wiki-deprecated/functional-abstraction.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -layout: docs -title: "Functional Abstraction" -section: "chisel3" ---- -We can define functions to factor out a repeated piece of logic that -we later reuse multiple times in a design. For example, we can wrap -up our earlier example of a simple combinational logic block as -follows: -```scala -def clb(a: UInt, b: UInt, c: UInt, d: UInt): UInt = - (a & b) | (~c & d) -``` - -where ```clb``` is the function which takes ```a```, ```b```, -```c```, ```d``` as arguments and returns a wire to the output of a -boolean circuit. The ```def``` keyword is part of Scala and -introduces a function definition, with each argument followed by a colon then its type, -and the function return type given after the colon following the -argument list. The equals (```=})```sign separates the function argument list from the function -definition. - -We can then use the block in another circuit as follows: -```scala -val out = clb(a,b,c,d) -``` - -We will later describe many powerful ways to use functions to -construct hardware using Scala's functional programming support. diff --git a/docs/src/wiki-deprecated/functional-module-creation.md b/docs/src/wiki-deprecated/functional-module-creation.md deleted file mode 100644 index 3e2e95bc..00000000 --- a/docs/src/wiki-deprecated/functional-module-creation.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -layout: docs -title: "Functional Module Creation" -section: "chisel3" ---- -Objects in Scala have a pre-existing creation function (method) called `apply`. -When an object is used as value in an expression (which basically means that the constructor was called), this method determines the returned value. -When dealing with hardware modules, one would expect the module output to be representative of the hardware module's functionality. -Therefore, we would sometimes like the module output to be the value returned when using the object as a value in an expression. -Since hardware modules are represented as Scala objects, this can be done by defining the object's `apply` method to return the module's output. -This can be referred to as creating a functional interface for module construction. -If we apply this on the standard mux2 example, we would to return the mux2 output ports when we used mux2 in an expression. -Implementing this requires building a constructor that takes multiplexer inputs as parameters and returns the multiplexer output: - -```scala -object Mux2 { - def apply(sel: UInt, in0: UInt, in1: UInt) = { - val m = Module(new Mux2) - m.io.in0 := in0 - m.io.in1 := in1 - m.io.sel := sel - m.io.out - } -} -``` - -As we can see in the code example, we defined the `apply` method to take the Mux2 inputs as the method parameters, and return the Mux2 output as the function's return value. -By defining modules in this way, it is easier to later implement larger and more complex version of this regular module. -For example, we previously implemented Mux4 like this: - -```scala -class Mux4 extends Module { - val io = IO(new Bundle { - val in0 = Input(UInt(1.W)) - val in1 = Input(UInt(1.W)) - val in2 = Input(UInt(1.W)) - val in3 = Input(UInt(1.W)) - val sel = Input(UInt(2.W)) - val out = Output(UInt(1.W)) - }) - val m0 = Module(new Mux2) - m0.io.sel := io.sel(0) - m0.io.in0 := io.in0 - m0.io.in1 := io.in1 - - val m1 = Module(new Mux2) - m1.io.sel := io.sel(0) - m1.io.in0 := io.in2 - m1.io.in1 := io.in3 - - val m3 = Module(new Mux2) - m3.io.sel := io.sel(1) - m3.io.in0 := m0.io.out - m3.io.in1 := m1.io.out - - io.out := m3.io.out -} -``` - -However, by using the creation function we redefined for Mux2, we can now use the Mux2 outputs as values of the modules themselves -when writing the Mux4 output expression: - -```scala -class Mux4 extends Module { - val io = IO(new Bundle { - val in0 = Input(UInt(1.W)) - val in1 = Input(UInt(1.W)) - val in2 = Input(UInt(1.W)) - val in3 = Input(UInt(1.W)) - val sel = Input(UInt(2.W)) - val out = Output(UInt(1.W)) - }) - io.out := Mux2(io.sel(1), - Mux2(io.sel(0), io.in0, io.in1), - Mux2(io.sel(0), io.in2, io.in3)) -} -``` - -This allows to write more intuitively readable hardware connection descriptions, which are similar to software expression evaluation. diff --git a/docs/src/wiki-deprecated/interfaces-and-connections.md b/docs/src/wiki-deprecated/interfaces-and-connections.md deleted file mode 100644 index 5902f5cd..00000000 --- a/docs/src/wiki-deprecated/interfaces-and-connections.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -layout: docs -title: "Interfaces and Connections" -section: "chisel3" ---- -# Interfaces & Bulk Connections - -For more sophisticated modules it is often useful to define and instantiate interface classes while defining the IO for a module. First and foremost, interface classes promote reuse allowing users to capture once and for all common interfaces in a useful form. - -Secondly, interfaces allow users to dramatically reduce wiring by supporting bulk connections between producer and consumer modules. Finally, users can make changes in large interfaces in one place reducing the number of updates required when adding or removing pieces of the interface. - -Note that Chisel has some built-in standard interface which should be used whenever possible for interoperability (e.g. Decoupled). - -## Ports: Subclasses & Nesting - -As we saw earlier, users can define their own interfaces by defining a class that subclasses Bundle. For example, a user could define a simple link for hand-shaking data as follows: - -```scala -class SimpleLink extends Bundle { - val data = Output(UInt(16.W)) - val valid = Output(Bool()) -} -``` - -We can then extend SimpleLink by adding parity bits using bundle inheritance: -```scala -class PLink extends SimpleLink { - val parity = Output(UInt(5.W)) -} -``` -In general, users can organize their interfaces into hierarchies using inheritance. - -From there we can define a filter interface by nesting two PLinks into a new FilterIO bundle: -```scala -class FilterIO extends Bundle { - val x = Flipped(new PLink) - val y = new PLink -} -``` -where flip recursively changes the direction of a bundle, changing input to output and output to input. - -We can now define a filter by defining a filter class extending module: -```scala -class Filter extends Module { - val io = IO(new FilterIO) - ... -} -``` -where the io field contains FilterIO. - -## Bundle Vectors - -Beyond single elements, vectors of elements form richer hierarchical interfaces. For example, in order to create a crossbar with a vector of inputs, producing a vector of outputs, and selected by a UInt input, we utilize the Vec constructor: -```scala -import chisel3.util.log2Ceil -class CrossbarIo(n: Int) extends Bundle { - val in = Vec(n, Flipped(new PLink)) - val sel = Input(UInt(log2Ceil(n).W)) - val out = Vec(n, new PLink) -} -``` -where Vec takes a size as the first argument and a block returning a port as the second argument. - -## Bulk Connections - -We can now compose two filters into a filter block as follows: -```scala -class Block extends Module { - val io = IO(new FilterIO) - val f1 = Module(new Filter) - val f2 = Module(new Filter) - f1.io.x <> io.x - f1.io.y <> f2.io.x - f2.io.y <> io.y -} -``` -where <> bulk connects interfaces of opposite gender between sibling modules or interfaces of the same gender between parent/child modules. - -Bulk connections connect leaf ports of the same name to each other. If the names do not match or are missing, Chisel does not generate a connection. - -Caution: bulk connections should only be used with **directioned elements** (like IOs), and is not magical (e.g. connecting two wires isn't supported since Chisel can't necessarily figure out the directions automatically [chisel3#603](https://github.com/freechipsproject/chisel3/issues/603)). - -## The standard ready-valid interface (ReadyValidIO / Decoupled) - -Chisel provides a standard interface for [ready-valid interfaces](http://inst.eecs.berkeley.edu/~cs150/Documents/Interfaces.pdf). -A ready-valid interface consists of a `ready` signal, a `valid` signal, and some data stored in `bits`. -The `ready` bit indicates that a consumer is *ready* to consume data. -The `valid` bit indicates that a producer has *valid* data on `bits`. -When both `ready` and `valid` are asserted, a data transfer from the producer to the consumer takes place. -A convenience method `fire` is provided that is asserted if both `ready` and `valid` are asserted. - -Usually, we use the utility function [`Decoupled()`](https://chisel.eecs.berkeley.edu/api/latest/chisel3/util/Decoupled$.html) to turn any type into a ready-valid interface rather than directly using [ReadyValidIO](http://chisel.eecs.berkeley.edu/api/latest/chisel3/util/ReadyValidIO.html). - -* `Decoupled(...)` creates a producer / output ready-valid interface (i.e. bits is an output). -* `Flipped(Decoupled(...))` creates a consumer / input ready-valid interface (i.e. bits is an input). - -Take a look at the following example Chisel code to better understand exactly what is generated: - -```scala -import chisel3._ -import chisel3.util.Decoupled - -/** - * Using Decoupled(...) creates a producer interface. - * i.e. it has bits as an output. - * This produces the following ports: - * input io_readyValid_ready, - * output io_readyValid_valid, - * output [31:0] io_readyValid_bits - */ -class ProducingData extends Module { - val io = IO(new Bundle { - val readyValid = Decoupled(UInt(32.W)) - }) - // do something with io.readyValid.ready - io.readyValid.valid := true.B - io.readyValid.bits := 5.U -} - -/** - * Using Flipped(Decoupled(...)) creates a consumer interface. - * i.e. it has bits as an input. - * This produces the following ports: - * output io_readyValid_ready, - * input io_readyValid_valid, - * input [31:0] io_readyValid_bits - */ -class ConsumingData extends Module { - val io = IO(new Bundle { - val readyValid = Flipped(Decoupled(UInt(32.W))) - }) - io.readyValid.ready := false.B - // do something with io.readyValid.valid - // do something with io.readyValid.bits -} -``` - -`DecoupledIO` is a ready-valid interface with the *convention* that there are no guarantees placed on deasserting `ready` or `valid` or on the stability of `bits`. -That means `ready` and `valid` can also be deasserted without a data transfer. - -`IrrevocableIO` is a ready-valid interface with the *convention* that the value of `bits` will not change while `valid` is asserted and `ready` is deasserted. -Also the consumer shall keep `ready` asserted after a cycle where `ready` was high and `valid` was low. -Note that the *irrevocable* constraint *is only a convention* and cannot be enforced by the interface. -Chisel does not automatically generate checkers or assertions to enforce the *irrevocable* convention. diff --git a/docs/src/wiki-deprecated/interval-type.md b/docs/src/wiki-deprecated/interval-type.md deleted file mode 100644 index 7f33461e..00000000 --- a/docs/src/wiki-deprecated/interval-type.md +++ /dev/null @@ -1,55 +0,0 @@ -# Intervals -**Intervals** are a new experimental numeric type that comprises UInt, SInt and FixedPoint numbers. -It augments these types with range information, i.e. upper and lower numeric bounds. -This information can be used to exercise tighter programmatic control over the ultimate widths of -signals in the final circuit. The **Firrtl** compiler can infer this range information based on -operations and earlier values in the circuit. Intervals support all the ordinary bit and arithmetic operations -associated with UInt, SInt, and FixedPoint and adds the following methods for manipulating the range of -a **source** Interval with the IntervalRange of **target** Interval - -### Clip -- Fit the value **source** into the IntervalRange of **target**, saturate if out of bounds -The clip method applied to an interval creates a new interval based on the argument to clip, -and constructs the necessary hardware so that the source Interval's value will be mapped into the new Interval. -Values that are outside the result range will be pegged to either maximum or minimum of result range as appropriate. - -> Generates necessary hardware to clip values, values greater than range are set to range.high, values lower than range are set to range min. - -### Wrap -- Fit the value **source** into the IntervalRange of **target**, wrapping around if out of bounds -The wrap method applied to an interval creates a new interval based on the argument to wrap, -and constructs the necessary -hardware so that the source Interval's value will be mapped into the new Interval. -Values that are outside the result range will be wrapped until they fall within the result range. - -> Generates necessary hardware to wrap values, values greater than range are set to range.high, values lower than range are set to range min. - -> Does not handle out of range values that are less than half the minimum or greater than twice maximum - -### Squeeze -- Fit the value **source** into the smallest IntervalRange based on source and target. -The squeeze method applied to an interval creates a new interval based on the argument to clip, the two ranges must overlap -behavior of squeeze with inputs outside of the produced range is undefined. - -> Generates no hardware, strictly a sizing operation - -#### Range combinations - -| Condition | A.clip(B) | A.wrap(B) | A.squeeze(B) | -| --------- | --------------- | --------------- | --------------- | -| A === B | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | -| A contains B | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | -| B contains A | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | -| A min < B min, A max in B | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | -| A min in B, A max > B max | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | max(Alo, Blo), min(Ahi, Bhi) | -| A strictly less than B | error | error | error | -| A strictly greater than B | error | error | error | - - -### Applying binary point operators to an Interval - -Consider a Interval with a binary point of 3: aaa.bbb - -| operation | after operation | binary point | lower | upper | meaning | -| --------- | --------------- | ------------ | ----- | ----- | ------- | -| setBinaryPoint(2) | aaa.bb | 2 | X | X | set the precision | -| shiftLeftBinaryPoint(2) | a.aabbb | 5 | X | X | increase the precision | -| shiftRighBinaryPoint(2) | aaaa.b | 1 | X | X | reduce the precision | - diff --git a/docs/src/wiki-deprecated/introduction.md b/docs/src/wiki-deprecated/introduction.md deleted file mode 100644 index 43fc9887..00000000 --- a/docs/src/wiki-deprecated/introduction.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -layout: docs -title: "Introduction" -section: "chisel3" ---- -This document is a tutorial introduction to _Chisel_ (Constructing -Hardware In a Scala Embedded Language). Chisel is a hardware -construction language embedded in the high-level programming language -Scala. At some point we will provide a proper reference manual, in -addition to more tutorial examples. In the meantime, this document -along with a lot of trial and error should set you on your way to -using Chisel. _Chisel is really only a set of special class -definitions, predefined objects, and usage conventions within Scala, -so when you write Chisel you are actually writing a Scala -program that constructs a hardware graph._ However, for the tutorial we don't presume that you -understand how to program in Scala. We will point out necessary Scala -features through the Chisel examples we give, and significant hardware -designs can be completed using only the material contained herein. -But as you gain experience and want to make your code simpler or more -reusable, you will find it important to leverage the underlying power -of the Scala language. We recommend you consult one of the excellent -Scala books to become more expert in Scala programming. - ->Through the tutorial, we format commentary on our design choices as in -this paragraph. You should be able to skip the commentary sections -and still fully understand how to use Chisel, but we hope you'll find -them interesting. - ->We were motivated to develop a new hardware language by years of -struggle with existing hardware description languages in our research -projects and hardware design courses. _Verilog_ and _VHDL_ were developed -as hardware _simulation_ languages, and only later did they become -a basis for hardware _synthesis_. Much of the semantics of these -languages are not appropriate for hardware synthesis and, in fact, -many constructs are simply not synthesizable. Other constructs are -non-intuitive in how they map to hardware implementations, or their -use can accidentally lead to highly inefficient hardware structures. -While it is possible to use a subset of these languages and still get -acceptable results, they nonetheless present a cluttered and confusing -specification model, particularly in an instructional setting. - ->However, our strongest motivation for developing a new hardware -language is our desire to change the way that electronic system design -takes place. We believe that it is important to not only teach -students how to design circuits, but also to teach them how to design -*circuit generators* ---programs that automatically generate -designs from a high-level set of design parameters and constraints. -Through circuit generators, we hope to leverage the hard work of -design experts and raise the level of design abstraction for everyone. -To express flexible and scalable circuit construction, circuit -generators must employ sophisticated programming techniques to make -decisions concerning how to best customize their output circuits -according to high-level parameter values and constraints. While -Verilog and VHDL include some primitive constructs for programmatic -circuit generation, they lack the powerful facilities present in -modern programming languages, such as object-oriented programming, -type inference, support for functional programming, and reflection. - ->Instead of building a new hardware design language from scratch, we -chose to embed hardware construction primitives within an existing -language. We picked Scala not only because it includes the -programming features we feel are important for building circuit -generators, but because it was specifically developed as a base for -domain-specific languages. diff --git a/docs/src/wiki-deprecated/memories.md b/docs/src/wiki-deprecated/memories.md deleted file mode 100644 index e4b9a80b..00000000 --- a/docs/src/wiki-deprecated/memories.md +++ /dev/null @@ -1,170 +0,0 @@ ---- -layout: docs -title: "Memories" -section: "chisel3" ---- -Chisel provides facilities for creating both read only and read/write memories. - -## ROM - -Users can define read only memories with a `Vec`: - -``` scala - VecInit(inits: Seq[T]) - VecInit(elt0: T, elts: T*) -``` - -where `inits` is a sequence of initial `Data` literals that initialize the ROM. For example, users cancreate a small ROM initialized to 1, 2, 4, 8 and loop through all values using a counter as an address generator as follows: - -``` scala - val m = VecInit(Array(1.U, 2.U, 4.U, 8.U)) - val r = m(counter(m.length.U)) -``` - -We can create an *n* value sine lookup table using a ROM initialized as follows: - -``` scala - def sinTable(amp: Double, n: Int) = { - val times = - (0 until n).map(i => (i*2*Pi)/(n.toDouble-1) - Pi) - val inits = - times.map(t => round(amp * sin(t)).asSInt(32.W)) - VecInit(inits) - } - def sinWave(amp: Double, n: Int) = - sinTable(amp, n)(counter(n.U)) -``` - -where `amp` is used to scale the fixpoint values stored in the ROM. - -## Memories - -Memories are given special treatment in Chisel since hardware implementations of memory vary greatly. For example, FPGA memories are instantiated quite differently from ASIC memories. Chisel defines a memory abstraction that can map to either simple Verilog behavioural descriptions or to instances of memory modules that are available from external memory generators provided by foundry or IP vendors. - - -### `SyncReadMem`: sequential/synchronous-read, sequential/synchronous-write - -Chisel has a construct called `SyncReadMem` for sequential/synchronous-read, sequential/synchronous-write memories. These `SyncReadMem`s will likely be synthesized to technology SRAMs (as opposed to register banks). - -If the same memory address is both written and sequentially read on the same clock edge, or if a sequential read enable is cleared, then the read data is undefined. - -Values on the read data port are not guaranteed to be held until the next read cycle. If that is the desired behavior, external logic to hold the last read value must be added. - -#### Read port/write port -Ports into `SyncReadMem`s are created by applying a `UInt` index. A 1024-entry SRAM with one write port and one read port might be expressed as follows: - -```scala mdoc:silent -import chisel3._ -class ReadWriteSmem extends Module { - val width: Int = 32 - val io = IO(new Bundle { - val enable = Input(Bool()) - val write = Input(Bool()) - val addr = Input(UInt(10.W)) - val dataIn = Input(UInt(width.W)) - val dataOut = Output(UInt(width.W)) - }) - - val mem = SyncReadMem(1024, UInt(width.W)) - // Create one write port and one read port - mem.write(io.addr, io.dataIn) - io.dataOut := mem.read(io.addr, io.enable) -} -``` - -Below is an example waveform of the one write port/one read port `SyncReadMem` with [masks](#masks). Note that the signal names will differ from the exact wire names generated for the `SyncReadMem`. With masking, it is also possible that multiple RTL arrays will be generated with the behavior below. - -![read/write ports example waveform](https://svg.wavedrom.com/github/freechipsproject/www.chisel-lang.org/master/docs/src/main/tut/chisel3/memories_waveforms/smem_read_write.json) - -#### Single-ported -Single-ported SRAMs can be inferred when the read and write conditions are mutually exclusive in the same `when` chain: - -```scala mdoc:silent -import chisel3._ -class RWSmem extends Module { - val width: Int = 32 - val io = IO(new Bundle { - val enable = Input(Bool()) - val write = Input(Bool()) - val addr = Input(UInt(10.W)) - val dataIn = Input(UInt(width.W)) - val dataOut = Output(UInt(width.W)) - }) - - val mem = SyncReadMem(1024, UInt(width.W)) - io.dataOut := DontCare - when(io.enable) { - val rdwrPort = mem(io.addr) - when (io.write) { rdwrPort := io.dataIn } - .otherwise { io.dataOut := rdwrPort } - } -} -``` - -(The `DontCare` is there to make Chisel's [unconnected wire detection](unconnected-wires) aware that reading while writing is undefined.) - -Here is an example single read/write port waveform, with [masks](#masks) (again, generated signal names and number of arrays may differ): - -![read/write ports example waveform](https://svg.wavedrom.com/github/freechipsproject/www.chisel-lang.org/master/docs/src/main/tut/chisel3/memories_waveforms/smem_rw.json) - -### `Mem`: combinational/asynchronous-read, sequential/synchronous-write - -Chisel supports random-access memories via the `Mem` construct. Writes to `Mem`s are combinational/asynchronous-read, sequential/synchronous-write. These `Mem`s will likely be synthesized to register banks, since most SRAMs in modern technologies (FPGA, ASIC) tend to no longer support combinational (asynchronous) reads. - -Creating asynchronous-read versions of the examples above simply involves replacing `SyncReadMem` with `Mem`. - -### Masks - -Chisel memories also support write masks for subword writes. Chisel will infer masks if the data type of the memory is a vector. To infer a mask, specify the `mask` argument of the `write` function which creates write ports. A given masked length is written if the corresponding mask bit is set. For example, in the example below, if the 0th bit of mask is true, it will write the lower byte of the data at corresponding address. - -```scala mdoc:silent -import chisel3._ -class MaskedReadWriteSmem extends Module { - val width: Int = 8 - val io = IO(new Bundle { - val enable = Input(Bool()) - val write = Input(Bool()) - val addr = Input(UInt(10.W)) - val mask = Input(Vec(4, Bool())) - val dataIn = Input(Vec(4, UInt(width.W))) - val dataOut = Output(Vec(4, UInt(width.W))) - }) - - // Create a 32-bit wide memory that is byte-masked - val mem = SyncReadMem(1024, Vec(4, UInt(width.W))) - // Write with mask - mem.write(io.addr, io.dataIn, io.mask) - io.dataOut := mem.read(io.addr, io.enable) -} -``` - -Here is an example of masks with readwrite ports: - -```scala mdoc:silent -import chisel3._ -class MaskedRWSmem extends Module { - val width: Int = 32 - val io = IO(new Bundle { - val enable = Input(Bool()) - val write = Input(Bool()) - val mask = Input(Vec(2, Bool())) - val addr = Input(UInt(10.W)) - val dataIn = Input(Vec(2, UInt(width.W))) - val dataOut = Output(Vec(2, UInt(width.W))) - }) - - val mem = SyncReadMem(1024, Vec(2, UInt(width.W))) - io.dataOut := DontCare - when(io.enable) { - val rdwrPort = mem(io.addr) - when (io.write) { - when(io.mask(0)) { - rdwrPort(0) := io.dataIn(0) - } - when(io.mask(1)) { - rdwrPort(1) := io.dataIn(1) - } - }.otherwise { io.dataOut := rdwrPort } - } -} -``` diff --git a/docs/src/wiki-deprecated/modules.md b/docs/src/wiki-deprecated/modules.md deleted file mode 100644 index 23006c9c..00000000 --- a/docs/src/wiki-deprecated/modules.md +++ /dev/null @@ -1,135 +0,0 @@ ---- -layout: docs -title: "Modules" -section: "chisel3" ---- -Chisel *modules* are very similar to Verilog *modules* in -defining a hierarchical structure in the generated circuit. - -The hierarchical module namespace is accessible in downstream tools -to aid in debugging and physical layout. A user-defined module is -defined as a *class* which: - - - inherits from `Module`, - - contains at least one interface wrapped in a Module's `IO()` method (traditionally stored in a port field named ```io```), and - - wires together subcircuits in its constructor. - -As an example, consider defining your own two-input multiplexer as a -module: -```scala mdoc:silent -import chisel3._ -class Mux2IO extends Bundle { - val sel = Input(UInt(1.W)) - val in0 = Input(UInt(1.W)) - val in1 = Input(UInt(1.W)) - val out = Output(UInt(1.W)) -} - -class Mux2 extends Module { - val io = IO(new Mux2IO) - io.out := (io.sel & io.in1) | (~io.sel & io.in0) -} -``` - -The wiring interface to a module is a collection of ports in the -form of a ```Bundle```. The interface to the module is defined -through a field named ```io```. For ```Mux2```, ```io``` is -defined as a bundle with four fields, one for each multiplexer port. - -The ```:=``` assignment operator, used here in the body of the -definition, is a special operator in Chisel that wires the input of -left-hand side to the output of the right-hand side. - -### Module Hierarchy - -We can now construct circuit hierarchies, where we build larger modules out -of smaller sub-modules. For example, we can build a 4-input -multiplexer module in terms of the ```Mux2``` module by wiring -together three 2-input multiplexers: - -```scala mdoc:silent -class Mux4IO extends Bundle { - val in0 = Input(UInt(1.W)) - val in1 = Input(UInt(1.W)) - val in2 = Input(UInt(1.W)) - val in3 = Input(UInt(1.W)) - val sel = Input(UInt(2.W)) - val out = Output(UInt(1.W)) -} -class Mux4 extends Module { - val io = IO(new Mux4IO) - - val m0 = Module(new Mux2) - m0.io.sel := io.sel(0) - m0.io.in0 := io.in0 - m0.io.in1 := io.in1 - - val m1 = Module(new Mux2) - m1.io.sel := io.sel(0) - m1.io.in0 := io.in2 - m1.io.in1 := io.in3 - - val m3 = Module(new Mux2) - m3.io.sel := io.sel(1) - m3.io.in0 := m0.io.out - m3.io.in1 := m1.io.out - - io.out := m3.io.out -} -``` - -We again define the module interface as ```io``` and wire up the -inputs and outputs. In this case, we create three ```Mux2``` -children modules, using the ```Module``` constructor function and -the Scala ```new``` keyword to create a -new object. We then wire them up to one another and to the ports of -the ```Mux4``` interface. - -Note: Chisel `Module`s have an implicit clock (called `clock`) and -an implicit reset (called `reset`). To create modules without implicit -clock and reset, Chisel provides `RawModule`. - -> Historical Note: Prior to Chisel 3.5, Modules were restricted to only -having a single user-defined port named `io`. There was also a type called -`MultiIOModule` that provided implicit clock and reset while allowing the -user to define as many ports as they want. This is now the functionality -of `Module`. - -### `RawModule` - -A `RawModule` is a module that **does not provide an implicit clock and reset.** -This can be useful when interfacing a Chisel module with a design that expects -a specific naming convention for clock or reset. - -Then we can use it in place of *Module* usage : -```scala mdoc:silent -import chisel3.{RawModule, withClockAndReset} - -class Foo extends Module { - val io = IO(new Bundle{ - val a = Input(Bool()) - val b = Output(Bool()) - }) - io.b := !io.a -} - -class FooWrapper extends RawModule { - val a_i = IO(Input(Bool())) - val b_o = IO(Output(Bool())) - val clk = Input(Clock()) - val rstn = Input(Bool()) - - val foo = withClockAndReset(clk, !rstn){ Module(new Foo) } - - foo.io.a := a_i - b_o := foo.io.b -} -``` - -In the example above, the `RawModule` is used to change the reset polarity -of module `SlaveSpi`. Indeed, the reset is active high by default in Chisel -modules, then using `withClockAndReset(clock, !rstn)` we can use an active low -reset in entire design. - -The clock is just wired as it, but if needed, `RawModule` can be used in -conjunction with `BlackBox` to connect a differential clock input for example. diff --git a/docs/src/wiki-deprecated/muxes-and-input-selection.md b/docs/src/wiki-deprecated/muxes-and-input-selection.md deleted file mode 100644 index fd7b7e7f..00000000 --- a/docs/src/wiki-deprecated/muxes-and-input-selection.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -layout: docs -title: "Muxes and Input Selection" -section: "chisel3" ---- -Selecting inputs is very useful in hardware description, and therefore Chisel provides several built-in generic input-selection implementations. -### Mux -The first one is `Mux`. This is a 2-input selector. Unlike the `Mux2` example which was presented previously, the built-in `Mux` allows -the inputs (`in0` and `in1`) to be any datatype as long as they are the same subclass of `Data`. - -by using the functional module creation feature presented in the previous section, we can create multi-input selector in a simple way: - -```scala -Mux(c1, a, Mux(c2, b, Mux(..., default))) -``` - -### MuxCase -However, this is not necessary since Chisel also provides the built-in `MuxCase`, which implements that exact feature. -`MuxCase` is an n-way `Mux`, which can be used as follows: - -```scala -MuxCase(default, Array(c1 -> a, c2 -> b, ...)) -``` - -Where each selection dependency is represented as a tuple in a Scala -array [ condition -> selected_input_port ]. - -### MuxLookup -Chisel also provides `MuxLookup` which is an n-way indexed multiplexer: - -```scala -MuxLookup(idx, default, - Array(0.U -> a, 1.U -> b, ...)) -``` - -This is the same as a `MuxCase`, where the conditions are all index based selection: - -```scala -MuxCase(default, - Array((idx === 0.U) -> a, - (idx === 1.U) -> b, ...)) -``` - -Note that the conditions/cases/selectors (eg. c1, c2) must be in parentheses. - -### Mux1H -Another ```Mux``` utility is ```Mux1H``` that takes a sequence of selectors and values and returns the value associated with the one selector that is set. If zero or multiple selectors are set the behavior is undefined. For example: -```scala - val hotValue = chisel3.util.oneHotMux(Seq( - io.selector(0) -> 2.U, - io.selector(1) -> 4.U, - io.selector(2) -> 8.U, - io.selector(4) -> 11.U, - )) -``` -```oneHotMux``` whenever possible generates *Firrtl* that is readily optimizable as low depth and/or tree. This optimization is not possible when the values are of type ```FixedPoint``` or an aggregate type that contains ```FixedPoint```s and results instead as a simple ```Mux``` tree. This behavior could be sub-optimal. As ```FixedPoint``` is still *experimental* this behavior may change in the future. diff --git a/docs/src/wiki-deprecated/operators.md b/docs/src/wiki-deprecated/operators.md deleted file mode 100644 index c23e2009..00000000 --- a/docs/src/wiki-deprecated/operators.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -layout: docs -title: "Operators" -section: "chisel3" ---- -### List of operators -Chisel defines a set of hardware operators: - -| Operation | Explanation | -| --------- | --------- | -| **Bitwise operators** | **Valid on:** SInt, UInt, Bool | -| `val invertedX = ~x` | Bitwise NOT | -| `val hiBits = x & "h_ffff_0000".U` | Bitwise AND | -| `val flagsOut = flagsIn \| overflow` | Bitwise OR | -| `val flagsOut = flagsIn ^ toggle` | Bitwise XOR | -| **Bitwise reductions.** | **Valid on:** SInt and UInt. Returns Bool. | -| `val allSet = x.andR` | AND reduction | -| `val anySet = x.orR` | OR reduction | -| `val parity = x.xorR` | XOR reduction | -| **Equality comparison.** | **Valid on:** SInt, UInt, and Bool. Returns Bool. | -| `val equ = x === y` | Equality | -| `val neq = x =/= y` | Inequality | -| **Shifts** | **Valid on:** SInt and UInt | -| `val twoToTheX = 1.S << x` | Logical shift left | -| `val hiBits = x >> 16.U` | Right shift (logical on UInt and arithmetic on SInt). | -| **Bitfield manipulation** | **Valid on:** SInt, UInt, and Bool. | -| `val xLSB = x(0)` | Extract single bit, LSB has index 0. | -| `val xTopNibble = x(15, 12)` | Extract bit field from end to start bit position. | -| `val usDebt = Fill(3, "hA".U)` | Replicate a bit string multiple times. | -| `val float = Cat(sign, exponent, mantissa)` | Concatenates bit fields, with first argument on left. | -| **Logical Operations** | **Valid on:** Bool -| `val sleep = !busy` | Logical NOT | -| `val hit = tagMatch && valid` | Logical AND | -| `val stall = src1busy || src2busy` | Logical OR | -| `val out = Mux(sel, inTrue, inFalse)` | Two-input mux where sel is a Bool | -| **Arithmetic operations** | **Valid on Nums:** SInt and UInt. | -| `val sum = a + b` *or* `val sum = a +% b` | Addition (without width expansion) | -| `val sum = a +& b` | Addition (with width expansion) | -| `val diff = a - b` *or* `val diff = a -% b` | Subtraction (without width expansion) | -| `val diff = a -& b` | Subtraction (with width expansion) | -| `val prod = a * b` | Multiplication | -| `val div = a / b` | Division | -| `val mod = a % b` | Modulus | -| **Arithmetic comparisons** | **Valid on Nums:** SInt and UInt. Returns Bool. | -| `val gt = a > b` | Greater than | -| `val gte = a >= b` | Greater than or equal | -| `val lt = a < b` | Less than | -| `val lte = a <= b` | Less than or equal | - ->Our choice of operator names was constrained by the Scala language. -We have to use triple equals```===``` for equality and ```=/=``` -for inequality to allow the -native Scala equals operator to remain usable. - -The Chisel operator precedence is not directly defined as part of the Chisel language. -Practically, it is determined by the evaluation order of the circuit, -which natuarally follows the [Scala operator precedence](https://docs.scala-lang.org/tour/operators.html). -If in doubt of operator precedence, use parentheses. - -> The Chisel/Scala operator precedence is similar but -not identical to precedence in Java or C. Verilog has the same operator precedence as C, but VHDL -does not. Verilog has precedence ordering for logic operations, but in VHDL -those operators have the same precedence and are evaluated from left to right. diff --git a/docs/src/wiki-deprecated/polymorphism-and-parameterization.md b/docs/src/wiki-deprecated/polymorphism-and-parameterization.md deleted file mode 100644 index c652baca..00000000 --- a/docs/src/wiki-deprecated/polymorphism-and-parameterization.md +++ /dev/null @@ -1,209 +0,0 @@ ---- -layout: docs -title: "Polymorphism and Parameterization" -section: "chisel3" ---- -_This section is advanced and can be skipped at first reading._ - -Scala is a strongly typed language and uses parameterized types to specify generic functions and classes. -In this section, we show how Chisel users can define their own reusable functions and classes using parameterized classes. - -# Parameterized Functions - -Earlier we defined `Mux2` on `Bool`, but now we show how we can define a generic multiplexer function. -We define this function as taking a boolean condition and con and alt arguments (corresponding to then and else expressions) of type `T`: - -```scala -def Mux[T <: Bits](c: Bool, con: T, alt: T): T = { ... } -``` - -where `T` is required to be a subclass of `Bits`. -Scala ensures that in each usage of `Mux`, it can find a common superclass of the actual con and alt argument types, -otherwise it causes a Scala compilation type error. -For example, - -```scala -Mux(c, UInt(10), UInt(11)) -``` - -yields a `UInt` wire because the `con` and `alt` arguments are each of type `UInt`. - - - -# Parameterized Classes - -Like parameterized functions, we can also parameterize classes to make them more reusable. -For instance, we can generalize the Filter class to use any kind of link. -We do so by parameterizing the `FilterIO` class and defining the constructor to take a single argument `gen` of type `T` as below. - -```scala -class FilterIO[T <: Data](gen: T) extends Bundle { - val x = Input(gen) - val y = Output(gen) -} -``` - -We can now define `Filter` by defining a module class that also takes a link type constructor argument and passes it through to the `FilterIO` interface constructor: - -```scala -class Filter[T <: Data](gen: T) extends Module { - val io = IO(new FilterIO(gen)) - ... -} -``` - -We can now define a `PLink`-based `Filter` as follows: - -```scala -val f = Module(new Filter(new PLink)) -``` - -A generic FIFO could be defined as follows: - -```scala -class DataBundle extends Bundle { - val a = UInt(32.W) - val b = UInt(32.W) -} - -class Fifo[T <: Data](gen: T, n: Int) extends Module { - val io = IO(new Bundle { - val enqVal = Input(Bool()) - val enqRdy = Output(Bool()) - val deqVal = Output(Bool()) - val deqRdy = Input(Bool()) - val enqDat = Input(gen) - val deqDat = Output(gen) - }) - val enqPtr = RegInit(0.asUInt(sizeof(n).W)) - val deqPtr = RegInit(0.asUInt(sizeof(n).W)) - val isFull = RegInit(false.B) - val doEnq = io.enqRdy && io.enqVal - val doDeq = io.deqRdy && io.deqVal - val isEmpty = !isFull && (enqPtr === deqPtr) - val deqPtrInc = deqPtr + 1.U - val enqPtrInc = enqPtr + 1.U - val isFullNext = Mux(doEnq && ~doDeq && (enqPtrInc === deqPtr), - true.B, Mux(doDeq && isFull, false.B, - isFull)) - enqPtr := Mux(doEnq, enqPtrInc, enqPtr) - deqPtr := Mux(doDeq, deqPtrInc, deqPtr) - isFull := isFullNext - val ram = Mem(n) - when (doEnq) { - ram(enqPtr) := io.enqDat - } - io.enqRdy := !isFull - io.deqVal := !isEmpty - ram(deqPtr) <> io.deqDat -} -``` - -An Fifo with 8 elements of type DataBundle could then be instantiated as: - -```scala -val fifo = Module(new Fifo(new DataBundle, 8)) -``` - -It is also possible to define a generic decoupled (ready/valid) interface: - -```scala -class DecoupledIO[T <: Data](data: T) extends Bundle { - val ready = Input(Bool()) - val valid = Output(Bool()) - val bits = Output(data) -} -``` - -This template can then be used to add a handshaking protocol to any -set of signals: - -```scala -class DecoupledDemo extends DecoupledIO(new DataBundle) -``` - -The FIFO interface can be now be simplified as follows: - -```scala -class Fifo[T <: Data](data: T, n: Int) extends Module { - val io = IO(new Bundle { - val enq = Flipped(new DecoupledIO(data)) - val deq = new DecoupledIO(data) - }) - ... -} -``` - -# Parametrization based on Modules - -You can also parametrize modules based on other modules rather than just types. The following is an example of a module parametrized by other modules as opposed to e.g. types. - -```scala -import chisel3.experimental.{BaseModule, RawModule} - -// Provides a more specific interface since generic Module -// provides no compile-time information on generic module's IOs. -trait MyAdder { - def in1: UInt - def in2: UInt - def out: UInt -} - -class Mod1 extends RawModule with MyAdder { - val in1 = IO(Input(UInt(8.W))) - val in2 = IO(Input(UInt(8.W))) - val out = IO(Output(UInt(8.W))) - out := in1 + in2 -} - -class Mod2 extends RawModule with MyAdder { - val in1 = IO(Input(UInt(8.W))) - val in2 = IO(Input(UInt(8.W))) - val out = IO(Output(UInt(8.W))) - out := in1 - in2 -} - -class X[T <: BaseModule with MyAdder](genT: => T) extends Module { - val io = IO(new Bundle { - val in1 = Input(UInt(8.W)) - val in2 = Input(UInt(8.W)) - val out = Output(UInt(8.W)) - }) - val subMod = Module(genT) - io.out := subMod.out - subMod.in1 := io.in1 - subMod.in2 := io.in2 -} - -println(chisel3.Driver.emitVerilog(new X(new Mod1))) -println(chisel3.Driver.emitVerilog(new X(new Mod2))) -``` diff --git a/docs/src/wiki-deprecated/ports.md b/docs/src/wiki-deprecated/ports.md deleted file mode 100644 index 251ce243..00000000 --- a/docs/src/wiki-deprecated/ports.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -layout: docs -title: "Ports" -section: "chisel3" ---- -Ports are used as interfaces to hardware components. A port is simply -any `Data` object that has directions assigned to its members. - -Chisel provides port constructors to allow a direction to be added -(input or output) to an object at construction time. Primitive port -constructors wrap the type of the port in `Input` or `Output`. - -An example port declaration is as follows: -```scala -class Decoupled extends Bundle { - val ready = Output(Bool()) - val data = Input(UInt(32.W)) - val valid = Input(Bool()) -} -``` - -After defining ```Decoupled```, it becomes a new type that can be -used as needed for module interfaces or for named collections of -wires. - -By folding directions into the object declarations, Chisel is able to -provide powerful wiring constructs described later. - -## Inspecting Module ports - -(Chisel 3.2+) - -Chisel 3.2 introduced `DataMirror.modulePorts` which can be used to inspect the IOs of any Chisel module (this includes modules in both `import chisel3._` and `import Chisel._`, as well as BlackBoxes from each package). -Here is an example of how to use this API: - -```scala -import chisel3.experimental.DataMirror - -class Adder extends Module { - val a = IO(Input(UInt(8.W))) - val b = IO(Input(UInt(8.W))) - val c = IO(Output(UInt(8.W))) - c := a +& b -} - -class Test extends Module { - val adder = Module(new Adder) - // for debug only - adder.a := DontCare - adder.b := DontCare - - // Inspect ports of adder - // Prints something like this - /** - * Found port clock: Clock(IO clock in Adder) - * Found port reset: Bool(IO reset in Adder) - * Found port a: UInt<8>(IO a in Adder) - * Found port b: UInt<8>(IO b in Adder) - * Found port c: UInt<8>(IO c in Adder) - */ - DataMirror.modulePorts(adder).foreach { case (name, port) => { - println(s"Found port $name: $port") - }} -} - -chisel3.Driver.execute(Array[String](), () => new Test) -``` diff --git a/docs/src/wiki-deprecated/printing.md b/docs/src/wiki-deprecated/printing.md deleted file mode 100644 index 36dcfaae..00000000 --- a/docs/src/wiki-deprecated/printing.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -layout: docs -title: "Printing" -section: "chisel3" ---- -Chisel provides the `printf` function for debugging purposes. It comes in two flavors: - -* [Scala-style](#scala-style) -* [C-style](#c-style) - -### Scala-style - -Chisel also supports printf in a style similar to [Scala's String Interpolation](http://docs.scala-lang.org/overviews/core/string-interpolation.html). Chisel provides a custom string interpolator `p` which can be used as follows: - -```scala -val myUInt = 33.U -printf(p"myUInt = $myUInt") // myUInt = 33 -``` - -Note that when concatenating `p"..."` strings, you need to start with a `p"..."` string: - -```scala -// Does not interpolate the second string -val myUInt = 33.U -printf("my normal string" + p"myUInt = $myUInt") -``` - -#### Simple formatting - -Other formats are available as follows: - -```scala -// Hexadecimal -printf(p"myUInt = 0x${Hexadecimal(myUInt)}") // myUInt = 0x21 -// Binary -printf(p"myUInt = ${Binary(myUInt)}") // myUInt = 100001 -// Character -printf(p"myUInt = ${Character(myUInt)}") // myUInt = ! -``` - -We recognize that the format specifiers are verbose, so we are working on a more concise syntax. - -#### Aggregate data-types - -Chisel provides default custom "pretty-printing" for Vecs and Bundles. The default printing of a Vec is similar to printing a Seq or List in Scala while printing a Bundle is similar to printing a Scala Map. - -```scala -val myVec = Vec(5.U, 10.U, 13.U) -printf(p"myVec = $myVec") // myVec = Vec(5, 10, 13) - -val myBundle = Wire(new Bundle { - val foo = UInt() - val bar = UInt() -}) -myBundle.foo := 3.U -myBundle.bar := 11.U -printf(p"myBundle = $myBundle") // myBundle = Bundle(a -> 3, b -> 11) -``` - -#### Custom Printing - -Chisel also provides the ability to specify _custom_ printing for user-defined Bundles. - -```scala -class Message extends Bundle { - val valid = Bool() - val addr = UInt(32.W) - val length = UInt(4.W) - val data = UInt(64.W) - override def toPrintable: Printable = { - val char = Mux(valid, 'v'.U, '-'.U) - p"Message:\n" + - p" valid : ${Character(char)}\n" + - p" addr : 0x${Hexadecimal(addr)}\n" + - p" length : $length\n" + - p" data : 0x${Hexadecimal(data)}\n" - } -} - -val myMessage = Wire(new Message) -myMessage.valid := true.B -myMessage.addr := "h1234".U -myMessage.length := 10.U -myMessage.data := "hdeadbeef".U - -printf(p"$myMessage") -``` - -Which prints the following: - -``` -Message: - valid : v - addr : 0x00001234 - length : 10 - data : 0x00000000deadbeef -``` - -Notice the use of `+` between `p` interpolated "strings". The results of `p` interpolation can be concatenated by using the `+` operator. For more information, please see the documentation - -### C-Style - -Chisel provides `printf` in a similar style to its C namesake. It accepts a double-quoted format string and a variable number of arguments which will then be printed on rising clock edges. Chisel supports the following format specifiers: - -| Format Specifier | Meaning | -| :-----: | :-----: | -| `%d` | decimal number | -| `%x` | hexadecimal number | -| `%b` | binary number | -| `%c` | 8-bit ASCII character | -| `%%` | literal percent | - -It also supports a small set of escape characters: - -| Escape Character | Meaning | -| :-----: | :-----: | -| `\n` | newline | -| `\t` | tab | -| `\"` | literal double quote | -| `\'` | literal single quote | -| `\\` | literal backslash | - -Note that single quotes do not require escaping, but are legal to escape. - -Thus printf can be used in a way very similar to how it is used in C: - -```scala -val myUInt = 32.U -printf("myUInt = %d", myUInt) // myUInt = 32 -``` diff --git a/docs/src/wiki-deprecated/reset.md b/docs/src/wiki-deprecated/reset.md deleted file mode 100644 index f5e4a24a..00000000 --- a/docs/src/wiki-deprecated/reset.md +++ /dev/null @@ -1,177 +0,0 @@ ---- -layout: docs -title: "Reset" -section: "chisel3" ---- - -```scala mdoc:invisible -import chisel3._ - -class Submodule extends Module -``` - -As of Chisel 3.2.0, Chisel 3 supports both synchronous and asynchronous reset, -meaning that it can natively emit both synchronous and asynchronously reset registers. - -The type of register that is emitted is based on the type of the reset signal associated -with the register. - -There are three types of reset that implement a common trait `Reset`: -* `Bool` - constructed with `Bool()`. Also known as "synchronous reset". -* `AsyncReset` - constructed with `AsyncReset()`. Also known as "asynchronous reset". -* `Reset` - constructed with `Reset()`. Also known as "abstract reset". - -For implementation reasons, the concrete Scala type is `ResetType`. Stylistically we avoid `ResetType`, instead using the common trait `Reset`. - -Registers with reset signals of type `Bool` are emitted as synchronous reset flops. -Registers with reset signals of type `AsyncReset` are emitted as asynchronouly reset flops. -Registers with reset signals of type `Reset` will have their reset type _inferred_ during FIRRTL compilation. - -### Reset Inference - -FIRRTL will infer a concrete type for any signals of type abstract `Reset`. -The rules are as follows: -1. An abstract `Reset` with only signals of type `AsyncReset`, abstract `Reset`, and `DontCare` -in both its fan-in and fan-out will infer to be of type `AsyncReset` -2. An abstract `Reset` with signals of both types `Bool` and `AsyncReset` in its fan-in and fan-out -is an error. -3. Otherwise, an abstract `Reset` will infer to type `Bool`. - -You can think about (3) as the mirror of (1) replacing `AsyncReset` with `Bool` with the additional -rule that abstract `Reset`s with neither `AsyncReset` nor `Bool` in their fan-in and fan-out will -default to type `Bool`. -This "default" case is uncommon and implies that reset signal is ultimately driven by a `DontCare`. - -### Implicit Reset - -A `Module`'s `reset` is of type abstract `Reset`. -Prior to Chisel 3.2.0, the type of this field was `Bool`. -For backwards compatability, if the top-level module has an implicit reset, its type will default to `Bool`. - -#### Setting Implicit Reset Type - -_New in Chisel 3.3.0_ - -If you would like to set the reset type from within a Module (including the top-level `Module`), -rather than relying on _Reset Inference_, you can mixin one of the following traits: -* `RequireSyncReset` - sets the type of `reset` to `Bool` -* `RequireAsyncReset` - sets the type of `reset` to `AsyncReset` - -For example: - -```scala mdoc:silent -class MyAlwaysSyncResetModule extends Module with RequireSyncReset { - val mySyncResetReg = RegInit(false.B) // reset is of type Bool -} -``` - -```scala mdoc:silent -class MyAlwaysAsyncResetModule extends Module with RequireAsyncReset { - val myAsyncResetReg = RegInit(false.B) // reset is of type AsyncReset -} -``` - -**Note:** This sets the concrete type, but the Scala type will remain `Reset`, so casting may still be necessary. -This comes up most often when using a reset of type `Bool` in logic. - - -### Reset-Agnostic Code - -The purpose of abstract `Reset` is to make it possible to design hardware that is agnostic to the -reset discipline used. -This enables code reuse for utilities and designs where the reset discipline does not matter to -the functionality of the block. - -Consider the two example modules below which are agnostic to the type of reset used within them: - -```scala mdoc:silent -class ResetAgnosticModule extends Module { - val io = IO(new Bundle { - val out = UInt(4.W) - }) - val resetAgnosticReg = RegInit(0.U(4.W)) - resetAgnosticReg := resetAgnosticReg + 1.U - io.out := resetAgnosticReg -} - -class ResetAgnosticRawModule extends RawModule { - val clk = IO(Input(Clock())) - val rst = IO(Input(Reset())) - val out = IO(Output(UInt(8.W))) - - val resetAgnosticReg = withClockAndReset(clk, rst)(RegInit(0.U(8.W))) - resetAgnosticReg := resetAgnosticReg + 1.U - out := resetAgnosticReg -} -``` - -These modules can be used in both synchronous and asynchronous reset domains. -Their reset types will be inferred based on the context within which they are used. - -### Forcing Reset Type - -You can set the type of a Module's implicit reset as described [above](#setting-implicit-reset-type). - -You can also cast to force the concrete type of reset. -* `.asBool` will reinterpret a `Reset` as `Bool` -* `.asAsyncReset` will reinterpret a `Reset` as `AsyncReset`. - -You can then use `withReset` to use a cast reset as the implicit reset. -See ["Multiple Clock Domains"](../explanations/multi-clock) for more information about `withReset`. - - -The following will make `myReg` as well as both `resetAgnosticReg`s synchronously reset: - -```scala mdoc:silent -class ForcedSyncReset extends Module { - // withReset's argument becomes the implicit reset in its scope - withReset (reset.asBool) { - val myReg = RegInit(0.U) - val myModule = Module(new ResetAgnosticModule) - - // RawModules do not have implicit resets so withReset has no effect - val myRawModule = Module(new ResetAgnosticRawModule) - // We must drive the reset port manually - myRawModule.rst := Module.reset // Module.reset grabs the current implicit reset - } -} -``` - -The following will make `myReg` as well as both `resetAgnosticReg`s asynchronously reset: - -```scala mdoc:silent -class ForcedAysncReset extends Module { - // withReset's argument becomes the implicit reset in its scope - withReset (reset.asAsyncReset){ - val myReg = RegInit(0.U) - val myModule = Module(new ResetAgnosticModule) // myModule.reset is connected implicitly - - // RawModules do not have implicit resets so withReset has no effect - val myRawModule = Module(new ResetAgnosticRawModule) - // We must drive the reset port manually - myRawModule.rst := Module.reset // Module.reset grabs the current implicit reset - } -} -``` - -**Note:** such casts (`asBool` and `asAsyncReset`) are not checked by FIRRTL. -In doing such a cast, you as the designer are effectively telling the compiler -that you know what you are doing and to force the type as cast. - -### Last-Connect Semantics - -It is **not** legal to override the reset type using last-connect semantics -unless you are overriding a `DontCare`: - -```scala mdoc:silent -class MyModule extends Module { - val resetBool = Wire(Reset()) - resetBool := DontCare - resetBool := false.B // this is fine - withReset(resetBool) { - val mySubmodule = Module(new Submodule()) - } - resetBool := true.B // this is fine - resetBool := false.B.asAsyncReset // this will error in FIRRTL -} -``` diff --git a/docs/src/wiki-deprecated/resources.md b/docs/src/wiki-deprecated/resources.md deleted file mode 100644 index 8b75be49..00000000 --- a/docs/src/wiki-deprecated/resources.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: docs -title: "Resources and References" -section: "chisel3" ---- - -The *best resource* to learn about Chisel is the [**online Chisel Bootcamp**](https://mybinder.org/v2/gh/freechipsproject/chisel-bootcamp/master). This runs in your browser and assumes no prior Scala knowledge. (You may also run this locally via the [backing chisel-bootcamp GitHub repository](https://github.com/freechipsproject/chisel-bootcamp).) - -When you're ready to build your own circuits in Chisel, **we recommend starting from the [Chisel Template](https://github.com/freechipsproject/chisel-template) repository**, which provides a pre-configured project, example design, and testbench. Follow the [chisel-template readme](https://github.com/freechipsproject/chisel-template) to get started. - -The following additional resources and references may be useful: - -- [Chisel Cheatsheet](https://github.com/freechipsproject/chisel-cheatsheet/releases/latest/download/chisel_cheatsheet.pdf) -- [Digital Design With Chisel](https://github.com/schoeberl/chisel-book) diff --git a/docs/src/wiki-deprecated/sbt-subproject.md b/docs/src/wiki-deprecated/sbt-subproject.md deleted file mode 100644 index 77273800..00000000 --- a/docs/src/wiki-deprecated/sbt-subproject.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -layout: docs -title: "Developers" -section: "chisel3" ---- - -## Chisel as an sbt subproject - -In order to use the constructs defined in the Chisel3 library, those definitions must be made available to the Scala -compiler at the time a project dependent on them is compiled. -For sbt-based builds there are fundamentally two ways to do this: -* provide a library dependency on the published Chisel3 jars via sbt's `libraryDependencies` setting, -* clone the Chisel3 git repository and include the source code as a subproject of a dependent project. - -The former of these two approaches is used by the chisel-tutorial project. -It is the simplest approach and assumes you do not require tight control over Chisel3 source code and are content with the -published release versions of Chisel3. - -The latter approach should be used by Chisel3 projects that require finer control over Chisel3 source code. - -It's hard to predict in advance the future requirements of a project, and it would be advantageous to be able to -switch between the two approaches relatively easily. -In order to accomplish this, we provide the `sbt-chisel-dep` plugin that allows the developer to concisely specify -Chisel3 subproject dependencies and switch between subproject and library dependency support based on the presence of -a directory (or symbolic link) in the root of the dependent project. - -The chisel-template project uses this plugin to support switching between either dependency (subproject or library). -By default, the chisel-template project does not contain a chisel3 subproject directory, and hence, uses a library dependency -on chisel3 (and related Chisel3 projects). -However, if you clone the chisel3 GitHub project from the root directory of the chisel-template project, creating a chisel3 -subdirectory, the `sbt-chisel-dep` plugin will take note of the chisel3 project subdirectory, -and provide an sbt subproject dependency in place of the library dependency. - -Checkout the [README for the `sbt-chisel-dep`](https://github.com/ucb-bar/sbt-chisel-dep) project for instructions on its usage. - -Example versions of the build.sbt and specification of the sbt-chisel-dep plugin are available from the [skeleton branch of the chisel-template repository](https://github.com/ucb-bar/chisel-template/tree/skeleton). diff --git a/docs/src/wiki-deprecated/sequential-circuits.md b/docs/src/wiki-deprecated/sequential-circuits.md deleted file mode 100644 index 10294403..00000000 --- a/docs/src/wiki-deprecated/sequential-circuits.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -layout: docs -title: "Sequential Circuits" -section: "chisel3" ---- -The simplest form of state element supported by Chisel is a positive edge-triggered register, which can be instantiated as: -```scala -val reg = RegNext(in) -``` -This circuit has an output that is a copy of the input signal `in` delayed by one clock cycle. Note that we do not have to specify the type of Reg as it will be automatically inferred from its input when instantiated in this way. In the current version of Chisel, clock and reset are global signals that are implicitly included where needed. - -Note that registers which do not specify an initial value will not change value upon toggling the reset signal. - -Using registers, we can quickly define a number of useful circuit constructs. For example, a rising-edge detector that takes a boolean signal in and outputs true when the current value is true and the previous value is false is given by: -```scala -def risingedge(x: Bool) = x && !RegNext(x) -``` -Counters are an important sequential circuit. To construct an up-counter that counts up to a maximum value, max, then wraps around back to zero (i.e., modulo max+1), we write: -```scala -def counter(max: UInt) = { - val x = RegInit(0.asUInt(max.getWidth)) - x := Mux(x === max, 0.U, x + 1.U) - x -} -``` -The counter register is created in the counter function with a reset value of 0 (with width large enough to hold max), to which the register will be initialized when the global reset for the circuit is asserted. The := assignment to x in counter wires an update combinational circuit which increments the counter value unless it hits the max at which point it wraps back to zero. Note that when x appears on the right-hand side of an assignment, its output is referenced, whereas when on the left-hand side, its input is referenced. -Counters can be used to build a number of useful sequential circuits. For example, we can build a pulse generator by outputting true when a counter reaches zero: -```scala -// Produce pulse every n cycles. -def pulse(n: UInt) = counter(n - 1.U) === 0.U -``` -A square-wave generator can then be toggled by the pulse train, toggling between true and false on each pulse: -```scala -// Flip internal state when input true. -def toggle(p: Bool) = { - val x = RegInit(false.B) - x := Mux(p, !x, x) - x -} -// Square wave of a given period. -def squareWave(period: UInt) = toggle(pulse(period/2)) -``` diff --git a/docs/src/wiki-deprecated/style.md b/docs/src/wiki-deprecated/style.md deleted file mode 100644 index c845f7b4..00000000 --- a/docs/src/wiki-deprecated/style.md +++ /dev/null @@ -1,275 +0,0 @@ -# Chisel Style Guide - -The Chisel style guide reflects the [Google Java style -guide](http://google.github.io/styleguide/javaguide.html) and the [General Public Scala style -guide](http://docs.scala-lang.org/style/). Specific rules below are to clarify -the style used for the chisel3 repo and repos related to Chisel (Firrtl). - -**Goal:** Readability and consistency are the main purposes of the style guide. -Writing your code so someone else (or yourself) can grok it later is important -to code health and quality. - -## Filenames -The source file name consists of the case-sensitive name of the top-level class -it contains, plus ".scala". - -## Packages - -Package definitions must contain the full path to the package from scala. If -you create a subpackage, it should go in a subdirectory. - - package directory.name.to.get.you.to.your.source - -As in Scala, packages follow the [Java package naming convention](https://google.github.io/styleguide/javaguide.html#s5.2.1-package-names). -Note that these guidelines call for all lowercase, no underscores. - -```scala -// Do this -package hardware.chips.topsecret.masterplan - -// Not this -package hardware.chips.veryObvious.bad_style -``` - -We also suggest you do not use chisel3 as a package, and especially do not use it -as the final (innermost) package. - -```scala -// Don't do this -package hardware.chips.newchip.superfastcomponent.chisel3 - -// This will lead to instantiating package members like so: -val module = Module(new chisel3.FastModule) - -// Which collides with the chisel namespace -import chisel3._ -``` - -## Imports -Avoid wildcard ( ._ ) imports, with the exception of chisel3._ -All other imports must call out used methods. -`import chisel3._` must be first, and separated from remaining imports with an extra blank line. - -**Reason:** This makes it clear where methods are defined. - -Any remaining imports must be listed alphabetically. - -```scala -import chisel3._ - -import the.other.thing.that.i.reference.inline -import the.other.things.that.i.reference.{ClassOne, ClassTwo} - - -val myInline = inline.MakeAnInline() -val myClassOne = new ClassOne -``` - -## Tests -Test classes are named starting with the name of the class they are testing, and -ending with "Test". -Test files must start with the name of the class you are testing and end with -"Test.scala". -Test files should reside in a subdirectory called "tests". -The tests package should be composed of the package class you are testing. - -```scala -package class.under.test.class -package tests -``` - -## Comments -We use scaladoc to automatically generate documentation from the source code. - -```scala -/** Multiple lines of ScalaDoc text are written here, - * wrapped normally... - */ -public int method(String p1) { ... } -``` - -... or in this single-line example: - -```scala -/** An especially short bit of Javadoc. */ -``` - -Write documentation as if the person reading it knows more about Scala and -Chisel than you. If you find comments in the code consider breaking them up -into seperate methods. - -## Module Classes and Instances - -Modules can take different forms in Chisel. The first form is similar to Verilog, where -you instance the module and then hook it up. In this case `Module(new MyMod())` is -returning a reference to the module. - -```scala -val myMod = Module(new MyMod()) -myMod.io <> hookUp -``` - -The second form is a more programmatic inline style with factory methods. In this case, -Queue is actually returning the part of the IO bundle representing the queue's -output. The factory method takes the input IO to the queue and an optional parameter -for depth. - -```scala -val queueOut = Queue(queueIn, depth=10) -``` - -The latter can be used for composing multiple functions into a single line. - -```scala -val queueOut = Queue( - Arbitrate.byRoundRobin( - Queue(a), // depth assumed to be 1 - Queue(b, depth=3), - Queue(c, depth=4) - ), - depth=10 -) -``` - -## Naming Conventions - -Chisel follows the [Scala Naming Conventions](http://docs.scala-lang.org/style/naming-conventions.html). -In general, Chisel code should use CamelCase for naming (ie. the first letter -of each word is capitalized except sometimes the first word). - -### Why CamelCase instead of Snake\_Case? - -The compiler inserts underscores when splitting Chisel/FIRRTL aggregate types -into Verilog types. The compiler uses underscores to preserve the original -structure of the data in the resulting Verilog. Because of the special meaning -of underscores in Chisel-generated Verilog, their use in naming is **strongly** -discouraged. - -Consider the following Chisel code: - -```scala -val msg = Wire(new Bundle { - val valid = Bool() - val addr = UInt(32) - val data = UInt(64) -}) -val msg_rec = Wire(Bool()) -``` - -Which compiles to the Verilog: - -```verilog -wire msg_valid; -wire [31:0] msg_addr; -wire [63:0] msg_data; -wire msg_rec; -``` - -The Verilog maintains the structure of the original aggregate wire `msg`. -However, because we named another variable `msg_rec`, it appears in the Verilog -as if `msg` had 4 fields instead of its actual 3! If we instead follow the -lowerCamelCase for values naming convention, the resulting Verilog makes more -sense: - -```scala -val msg = Wire(new Bundle { - val valid = Bool() - val addr = UInt(32) - val data = UInt(64) -}) -val msgRec = Wire(Bool()) -``` - -And its resulting Verilog: - -```verilog -wire msg_valid; -wire [31:0] msg_addr; -wire [63:0] msg_data; -wire msgRec; -``` - -Much better. - -### Modules and Bundles (Classes, Traits, and Objects) - -Modules are Scala classes and thus use UpperCamelCase. - -```scala -class ModuleNamingExample extends Module { - ... -} -``` - -Similarly, other classes (Chisel & Scala) should be UpperCamelCase as well. - -```scala -trait UsefulScalaUtilities { - def isEven(n: Int): Boolean = (n % 2) == 0 - def isOdd(n: Int): Boolean = !isEven(n) -} - -class MyCustomBundle extends Bundle { - ... -} -// Companion object to MyCustomBundle -object MyCustomBundle { - ... -} - -``` - -### Values and Methods - -Values and methods should use lowerCamelCase. (Unless the value is a constant.) - -```scala -val mySuperReg = Reg(init = 0.asUInt(32)) -def myImportantMethod(a: UInt): Bool = a < 23.asUInt -``` - -### Constants - -Unlike the Google Java style, constants use UpperCamelCase, which is in line -with the official [Scala Naming -Conventions](https://docs.scala-lang.org/style/naming-conventions.html). -Constants are final fields (val or object) whose contents are deeply immutable -and belong to a package object or an object. Examples: - -```scala -// Constants -object Constants { - val Number = 5 - val Names = "Ed" :: "Ann" :: Nil - val Ages = Map("Ed" -> 35, "Ann" -> 32) -} - -// Not constants -class NonConstantsInClass { - val inClass: String = "in-class" -} - -object nonConstantsInObject { - var varString = "var-string" - val mutableCollection: scala.collection.mutable.Set[String] - val mutableElements = Set(mutable) -} -``` - -### UpperCamelCase vs. lowerCamelCase - -There is more than one reasonable way to covert English prose into camel case. -We follow the convention defined in the [Google Java style -guide](https://google.github.io/styleguide/javaguide.html#s5.3-camel-case). The -potentially non-obvious rule being to treat acronymns as words for the purpose -of camel case. - -Note that the casing of the original words is almost entirely disregarded. -Example: - -Prose form | UpperCamelCase | lowerCamelCase | Incorrect -:------------- | :------------- | :------------- | :------------ -find GCD | FindGcd | findGcd | ~~findGCD~~ -state for FSM | StateForFsm | stateForFsm | ~~stateForFSM~~ -mock dut | MockDut | mockDut | ~~MockDUT~~ -FIFO Generator | FifoGenerator | fifoGenerator | ~~FIFOGenerator~~ diff --git a/docs/src/wiki-deprecated/supported-hardware.md b/docs/src/wiki-deprecated/supported-hardware.md deleted file mode 100644 index 97f82698..00000000 --- a/docs/src/wiki-deprecated/supported-hardware.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -layout: docs -title: "Supported Hardware" -section: "chisel3" ---- -While Chisel focuses on binary logic, Chisel can support analog and tri-state wires with the `Analog` type - see [Datatypes in Chisel](data-types). - -We focus on binary logic designs as they constitute the vast majority of designs in practice. Tri-state logic are poorly supported standard industry flows and require special/controlled hard macros in order to be done. diff --git a/docs/src/wiki-deprecated/test-coverage.md b/docs/src/wiki-deprecated/test-coverage.md deleted file mode 100644 index a3a622a2..00000000 --- a/docs/src/wiki-deprecated/test-coverage.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -layout: docs -title: "Test Coverage" -section: "chisel3" ---- - -## Test Coverage Setup - -Chisel's sbt build instructions contain the requisite plug-in (sbt-scoverage) for generating test coverage information. Please see the [sbt-scoverage web page](https://github.com/scoverage/sbt-scoverage) for details on the plug-in. -The tests themselves are found in `src/test/scala`. - -## Generating A Test Coverage Report - -Use the following sequence of sbt commands to generate a test coverage report: -``` -sbt clean coverage test -sbt coverageReport -``` -The coverage reports should be found in `target/scala-x.yy/scoverage-report/{scoverage.xml,index.html}` where `x.yy` corresponds to the version of Scala used to compile Firrtl and the tests. -`scoverage.xml` is useful if you want to analyze the results programmatically. -`index.html` is designed for navigation with a web browser, allowing one to drill down to invidual statements covered (or not) by the tests. diff --git a/docs/src/wiki-deprecated/troubleshooting.md b/docs/src/wiki-deprecated/troubleshooting.md deleted file mode 100644 index 333adec4..00000000 --- a/docs/src/wiki-deprecated/troubleshooting.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -layout: docs -title: "Troubleshooting" -section: "chisel3" ---- -This page is a starting point for recording common and not so common problems in developing with Chisel3. In particular, those situations where there is a work around that will keep you going. - -### `type mismatch` specifying width/value of a `UInt`/`SInt` - -*I have some old code that used to work correctly in chisel2 (and still does if I use the `import Chisel._` compatibility layer) -but causes a `type mismatch` error in straight chisel3:* - -```scala -class TestBlock extends Module { - val io = IO(new Bundle { - val output = Output(UInt(width=3)) - }) -} -``` -*produces* -```bash -type mismatch; -[error] found : Int(3) -[error] required: chisel3.internal.firrtl.Width -[error] val output = Output(UInt(width=3)) -``` - -The single argument, multi-function object/constructors from chisel2 have been removed from chisel3. -It was felt these were too prone to error and made it difficult to diagnose error conditions in chisel3 code. - -In chisel3, the single argument to the `UInt`/`SInt` object/constructor specifies the *width* and must be a `Width` type. -Although there are no automatic conversions from `Int` to `Width`, an `Int` may be converted to a `Width` by applying the `W` method to an `Int`. -In chisel3, the above code becomes: -```scala -class TestBlock extends Module { - val io = IO(new Bundle { - val output = Output(UInt(3.W)) - }) -} -``` -`UInt`/`SInt` literals may be created from an `Int` with the application of either the `U` or `S` method. - -```scala -UInt(42) -``` -in chisel2, becomes -```scala -42.U -``` -in chisel3 - -A literal with a specific width is created by calling the `U` or `S` method with a `W` argument. -Use: -```scala -1.S(8.W) -``` -to create an 8-bit wide (signed) literal with value 1. diff --git a/docs/src/wiki-deprecated/unconnected-wires.md b/docs/src/wiki-deprecated/unconnected-wires.md deleted file mode 100644 index 1fa12e59..00000000 --- a/docs/src/wiki-deprecated/unconnected-wires.md +++ /dev/null @@ -1,112 +0,0 @@ ---- -layout: docs -title: "Unconnected Wires" -section: "chisel3" ---- -The Invalidate API [(#645)](https://github.com/freechipsproject/chisel3/pull/645) adds support to chisel -for reporting unconnected wires as errors. - -Prior to this pull request, chisel automatically generated a firrtl `is invalid` for `Module IO()`, and each `Wire()` definition. -This made it difficult to detect cases where output signals were never driven. -Chisel now supports a `DontCare` element, which may be connected to an output signal, indicating that that signal is intentionally not driven. -Unless a signal is driven by hardware or connected to a `DontCare`, Firrtl will complain with a "not fully initialized" error. - -### API - -Output signals may be connected to DontCare, generating a `is invalid` when the corresponding firrtl is emitted. -```scala -io.out.debug := true.B -io.out.debugOption := DontCare -``` -This indicates that the signal `io.out.debugOption` is intentionally not driven and firrtl should not issue a "not fully initialized" -error for this signal. - -This can be applied to aggregates as well as individual signals: -```scala -{ - ... - val nElements = 5 - val io = IO(new Bundle { - val outs = Output(Vec(nElements, Bool())) - }) - io.outs <> DontCare - ... -} - -class TrivialInterface extends Bundle { - val in = Input(Bool()) - val out = Output(Bool()) -} - -{ - ... - val io = IO(new TrivialInterface) - io <> DontCare - ... -} -``` - -This feature is controlled by `CompileOptions.explicitInvalidate` and is set to `false` in `NotStrict` (Chisel2 compatibility mode), -and `true` in `Strict` mode. - -You can selectively enable this for Chisel2 compatibility mode by providing your own explicit `compileOptions`, -either for a group of Modules (via inheritance): -```scala -abstract class ExplicitInvalidateModule extends Module()(chisel3.core.ExplicitCompileOptions.NotStrict.copy(explicitInvalidate = true)) -``` -or on a per-Module basis: -```scala -class MyModule extends Module { - override val compileOptions = chisel3.core.ExplicitCompileOptions.NotStrict.copy(explicitInvalidate = true) - ... -} -``` - -Or conversely, disable this stricter checking (which is now the default in pure chisel3): -```scala -abstract class ImplicitInvalidateModule extends Module()(chisel3.core.ExplicitCompileOptions.Strict.copy(explicitInvalidate = false)) -``` -or on a per-Module basis: -```scala -class MyModule extends Module { - override val compileOptions = chisel3.core.ExplicitCompileOptions.Strict.copy(explicitInvalidate = false) - ... -} -``` - -Please see the corresponding [API tests](https://github.com/freechipsproject/chisel3/blob/master/src/test/scala/chiselTests/InvalidateAPISpec.scala) -for examples. - -### Determining the unconnected element - -I have an interface with 42 wires. -Which one of them is unconnected? - -The firrtl error message should contain something like: -```bash -firrtl.passes.CheckInitialization$RefNotInitializedException: @[:@6.4] : [module Router] Reference io is not fully initialized. - @[Decoupled.scala 38:19:@48.12] : node _GEN_23 = mux(and(UInt<1>("h1"), eq(UInt<2>("h3"), _T_84)), _GEN_2, VOID) @[Decoupled.scala 38:19:@48.12] - @[Router.scala 78:30:@44.10] : node _GEN_36 = mux(_GEN_0.ready, _GEN_23, VOID) @[Router.scala 78:30:@44.10] - @[Router.scala 75:26:@39.8] : node _GEN_54 = mux(io.in.valid, _GEN_36, VOID) @[Router.scala 75:26:@39.8] - @[Router.scala 70:50:@27.6] : node _GEN_76 = mux(io.load_routing_table_request.valid, VOID, _GEN_54) @[Router.scala 70:50:@27.6] - @[Router.scala 65:85:@19.4] : node _GEN_102 = mux(_T_62, VOID, _GEN_76) @[Router.scala 65:85:@19.4] - : io.outs[3].bits.body <= _GEN_102 -``` -The first line is the initial error report. -Successive lines, indented and beginning with source line information indicate connections involving the problematic signal. -Unfortunately, if these are `when` conditions involving muxes, they may be difficult to decipher. -The last line of the group, indented and beginning with a `:` should indicate the uninitialized signal component. -This example (from the [Router tutorial](https://github.com/ucb-bar/chisel-tutorial/blob/release/src/main/scala/examples/Router.scala)) -was produced when the output queue bits were not initialized. -The old code was: -```scala - io.outs.foreach { out => out.noenq() } -``` -which initialized the queue's `valid` bit, but did not initialize the actual output values. -The fix was: -```scala - io.outs.foreach { out => - out.bits := 0.U.asTypeOf(out.bits) - out.noenq() - } -``` diff --git a/docs/src/wiki-deprecated/width-inference.md b/docs/src/wiki-deprecated/width-inference.md deleted file mode 100644 index 60f9d519..00000000 --- a/docs/src/wiki-deprecated/width-inference.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -layout: docs -title: "Width Inference" -section: "chisel3" ---- -Chisel provides bit width inference to reduce design effort. Users are encouraged to manually specify widths of ports and registers to prevent any surprises, but otherwise unspecified widths will be inferred by the Firrtl compiler. - -For all circuit components declared with unspecified widths, the FIRRTL compiler will infer the minimum possible width that maintains the legality of all its incoming connections. Implicit here is that inference is done in a right to left fashion in the sense of an assignment statement in chisel, i.e. from the left hand side from the right hand side. If a component has no incoming connections, and the width is unspecified, then an error is thrown to indicate that the width could not be inferred. - -For module input ports with unspecified widths, the inferred width is the minimum possible width that maintains the legality of all incoming connections to all instantiations of the module. -The width of a ground-typed multiplexor expression is the maximum of its two corresponding input widths. For multiplexing aggregate-typed expressions, the resulting widths of each leaf subelement is the maximum of its corresponding two input leaf subelement widths. -The width of a conditionally valid expression is the width of its input expression. For the full formal description see the [Firrtl Spec](https://github.com/freechipsproject/firrtl/blob/master/spec/spec.pdf). - -Hardware operators have output widths as defined by the following set of rules: - -| operation | bit width | -| --------- | --------- | -| `z = x + y` *or* `z = x +% y` | `w(z) = max(w(x), w(y))` | -| `z = x +& y` | `w(z) = max(w(x), w(y)) + 1` | -| `z = x - y` *or* `z = x -% y` | `w(z) = max(w(x), w(y))` | -| `z = x -& y` | `w(z) = max(w(x), w(y)) + 1` | -| `z = x & y` | `w(z) = max(w(x), w(y))` | -| `z = Mux(c, x, y)` | `w(z) = max(w(x), w(y))` | -| `z = w * y` | `w(z) = w(x) + w(y)` | -| `z = x << n` | `w(z) = w(x) + maxNum(n)` | -| `z = x >> n` | `w(z) = w(x) - minNum(n)` | -| `z = Cat(x, y)` | `w(z) = w(x) + w(y)` | -| `z = Fill(n, x)` | `w(z) = w(x) * maxNum(n)` | - ->where for instance `w(z)` is the bit width of wire `z`, and the `&` -rule applies to all bitwise logical operations. - -Given a path of connections that begins with an unspecified width element (most commonly a top-level input), then the compiler will throw an exception indicating a certain width was uninferrable. - -A common "gotcha" comes from truncating addition and subtraction with the operators `+` and `-`. Users who want the result to maintain the full, expanded precision of the addition or subtraction should use the expanding operators `+&` and `-&`. - -> The default truncating operation comes from Chisel's history as a microprocessor design language. -- cgit v1.2.3 From 03ec2156988ddef35272365fb86415511eafb4e2 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 18 Mar 2021 23:30:05 -0700 Subject: Update the FAQ and add doc on versioning (#1827) * Update the FAQ and add doc on versioning * Update modules.md Co-authored-by: Megan Wachs --- docs/src/appendix/appendix.md | 1 + docs/src/appendix/versioning.md | 35 ++++++++++++++++ docs/src/explanations/modules.md | 4 +- docs/src/resources/faqs.md | 86 +++++----------------------------------- 4 files changed, 48 insertions(+), 78 deletions(-) create mode 100644 docs/src/appendix/versioning.md diff --git a/docs/src/appendix/appendix.md b/docs/src/appendix/appendix.md index 48106fe4..0efedbfe 100644 --- a/docs/src/appendix/appendix.md +++ b/docs/src/appendix/appendix.md @@ -11,3 +11,4 @@ This section covers some less-common Chisel topics. * [Differences between Chisel3 and Chisel2](chisel3-vs-chisel2) * [Experimental Features](experimental-features) * [Upgrading from Scala 2.11](upgrading-from-scala-2-11) +* [Versioning](versioning) diff --git a/docs/src/appendix/versioning.md b/docs/src/appendix/versioning.md new file mode 100644 index 00000000..8c5ef91c --- /dev/null +++ b/docs/src/appendix/versioning.md @@ -0,0 +1,35 @@ +--- +layout: docs +title: "Versioning" +section: "chisel3" +--- + +# Chisel Project Versioning + +Chisel and related projects follow a versioning scheme similar to [PVP](https://pvp.haskell.org/). +Project versions are of the form `A.B.C` where `A.B` specifies the _Major_ version and `C` specifies the _Minor_ version. +Projects maintain _binary compatibility_ between minor versions of the same major version. +For example, a project compiled against Chisel3 version 3.4.0 can be used with Chisel3 version 3.4.2 or 3.4.15 without recompilation. + +# Compatible Versions + +Historically, due to a mistake in versioning with chisel-iotesters as well as some projects originating later than others, +the compatible versions of Chisel-related projects has not been obvious. +We are taking steps to improve the situation by bringing the major versions more in line (the `B` part in `A.B.C`), +but the inconsistencies remain in previously published versions. + +Please use the following table to determine which versions of the related projects are compatible. +In particular, versions of projects in this table were compiled against the version of any dependencies listed in the same row. +For example, `chisel-iotesters` version 1.4 was compiled against `chisel3` version 3.3. + +| chisel3 | chiseltest | chisel-iotesters | firrtl | treadle | diagrammer | firrtl-interpreter2 | +| ------- | ---------- | ---------------- | ------ | ------- | ---------- | ----- | +| 3.4 | 0.3 | 1.5 | 1.4 | 1.3 | 1.3 | 1.4 | +| 3.3 | 0.2 | 1.4 | 1.3 | 1.2 | 1.2 | 1.3 | +| 3.2 | 0.11 | 1.3 | 1.2 | 1.1 | 1.1 | 1.2 | +| 3.1 | - | 1.2 | 1.1 | 1.0 | 1.0 | 1.1 | +| 3.0 | - | 1.1 | 1.0 | -3 | - | 1.0 | + +1 chiseltest 0.1 was published under artifact name [chisel-testers2](https://search.maven.org/search?q=a:chisel-testers2_2.12) (0.2 was published under both artifact names) +2 Replaced by Treadle, in maintenance mode only since version 1.1 +3 Treadle was preceded by the firrtl-interpreter diff --git a/docs/src/explanations/modules.md b/docs/src/explanations/modules.md index 32cbff2e..f82a14d6 100644 --- a/docs/src/explanations/modules.md +++ b/docs/src/explanations/modules.md @@ -119,8 +119,8 @@ class Foo extends Module { class FooWrapper extends RawModule { val a_i = IO(Input(Bool())) val b_o = IO(Output(Bool())) - val clk = Input(Clock()) - val rstn = Input(Bool()) + val clk = IO(Input(Clock())) + val rstn = IO(Input(Bool())) val foo = withClockAndReset(clk, !rstn){ Module(new Foo) } diff --git a/docs/src/resources/faqs.md b/docs/src/resources/faqs.md index debdfcbe..feefa4d0 100644 --- a/docs/src/resources/faqs.md +++ b/docs/src/resources/faqs.md @@ -8,8 +8,8 @@ section: "chisel3" * [Where should I start if I want to learn Chisel?](#where-should-i-start-if-i-want-to-learn-chisel) * [How do I ... in Chisel?](#how-do-i-do--eg-like-that-in-verilog-in-chisel) +* [What versions of the various projects work together?](#what-versions) * [How can I contribute to Chisel?](#how-can-i-contribute-to-chisel) -* [What is the difference between release and master branches?](#what-is-the-difference-between-release-and-master-branches) * [Why DecoupledIO instead of ReadyValidIO?](#why-decoupledio-instead-of-readyvalidio) * [Why do I have to wrap module instantiations in `Module(...)`?](#why-do-i-have-to-wrap-module-instantiations-in-module) * [Why Chisel?](#why-chisel) @@ -28,42 +28,13 @@ We recommend the [Chisel Bootcamp](https://github.com/freechipsproject/chisel-bo See the [cookbooks](../cookbooks/cookbook). -### How can I contribute to Chisel? - -A good to place to start is to fill out the [How Can I Contribute Form](https://docs.google.com/forms/d/e/1FAIpQLSfwTTY8GkfSZ2sU2T2mNpfNMpIM70GlXOrjqiHoC9ZBvwn_CA/viewform). - -### What is the difference between release and master branches? - -We have two main branches for each main Chisel project: - -- `master` -- `release` - -`master` is the main development branch and it is updated frequently (often several times a day). -Although we endeavour to keep the `master` branches in sync, they may drift out of sync for a day or two. -We do not publish the `master` branches. -If you wish to use them, you need to clone the GitHub repositories and use `sbt publishLocal` to make them available on your local machine. +### What versions of the various projects work together? -The `release` branches are updated less often (currently bi-weekly) and we try to guarantee they are in sync. -We publish these to Sonatype/Maven on a bi-weekly basis. +See [Chisel Project Versioning](../appendix/versioning). -In general, you can not mix `release` and `master` branches and assume they will work. - -The default branches for the user-facing repositories (chisel-template and chisel-tutorial) are the `release` branches - these should always *just work* for new users as they use the `release` branches of chisel projects. - -If you want to use something more current than the `release` branch, you should `git checkout master` for all the chisel repos you intend to use, then `sbt publishLocal` them in this order: - -- firrtl -- firrtl-interpreter -- chisel3 -- chisel-testers - -Then, if you're working with the user-facing repositories: - -- chisel-tutorial -- chisel-template +### How can I contribute to Chisel? -Since this is a substantial amount of work (with no guarantee of success), unless you are actively involved in Chisel development, we encourage you to stick with the `release` branches and their respective dependencies. +Check out the [Contributor Documentation](https://github.com/chipsalliance/chisel3#contributor-documentation) in the chisel3 repository. ### Why DecoupledIO instead of ReadyValidIO? @@ -80,48 +51,11 @@ Chisel Modules are written by defining a [Scala class](http://docs.scala-lang.or ### Why Chisel? -Borrowed from [Chisel Motivation](../explanations/motivation) - ->We were motivated to develop a new hardware language by years of -struggle with existing hardware description languages in our research -projects and hardware design courses. _Verilog_ and _VHDL_ were developed -as hardware _simulation_ languages, and only later did they become -a basis for hardware _synthesis_. Much of the semantics of these -languages are not appropriate for hardware synthesis and, in fact, -many constructs are simply not synthesizable. Other constructs are -non-intuitive in how they map to hardware implementations, or their -use can accidently lead to highly inefficient hardware structures. -While it is possible to use a subset of these languages and still get -acceptable results, they nonetheless present a cluttered and confusing -specification model, particularly in an instructional setting. - ->However, our strongest motivation for developing a new hardware -language is our desire to change the way that electronic system design -takes place. We believe that it is important to not only teach -students how to design circuits, but also to teach them how to design -*circuit generators* ---programs that automatically generate -designs from a high-level set of design parameters and constraints. -Through circuit generators, we hope to leverage the hard work of -design experts and raise the level of design abstraction for everyone. -To express flexible and scalable circuit construction, circuit -generators must employ sophisticated programming techniques to make -decisions concerning how to best customize their output circuits -according to high-level parameter values and constraints. While -Verilog and VHDL include some primitive constructs for programmatic -circuit generation, they lack the powerful facilities present in -modern programming languages, such as object-oriented programming, -type inference, support for functional programming, and reflection. - ->Instead of building a new hardware design language from scratch, we -chose to embed hardware construction primitives within an existing -language. We picked Scala not only because it includes the -programming features we feel are important for building circuit -generators, but because it was specifically developed as a base for -domain-specific languages. +Please see [Chisel Motivation](../explanations/motivation) ### Does Chisel support X and Z logic values -Chisel does not directly support Verilog logic values ```x``` *unknown* and ```z``` *high-impedance*. There are a number of reasons to want to avoid these values. See:[The Dangers of Living With An X](http://infocenter.arm.com/help/topic/com.arm.doc.arp0009a/Verilog_X_Bugs.pdf) and [Malicious LUT: A stealthy FPGA Trojan injected and triggered by the design flow](http://ieeexplore.ieee.org/document/7827620/). Chisel has it's own eco-system of unit and functional testers that limit the need for ```x``` and ```z``` and their omission simplify language implementation, design, and testing. The circuits created by chisel do not preclude developers from using ```x``` and ```z``` in downstream toolchains as they see fit. +Chisel does not directly support Verilog logic values ```x``` *unknown* and ```z``` *high-impedance*. There are a number of reasons to want to avoid these values. See:[The Dangers of Living With An X](http://infocenter.arm.com/help/topic/com.arm.doc.arp0009a/Verilog_X_Bugs.pdf) and [Malicious LUT: A stealthy FPGA Trojan injected and triggered by the design flow](http://ieeexplore.ieee.org/document/7827620/). Chisel has its own eco-system of unit and functional testers that limit the need for ```x``` and ```z``` and their omission simplify language implementation, design, and testing. The circuits created by chisel do not preclude developers from using ```x``` and ```z``` in downstream toolchains as they see fit. ### Get me Verilog I wrote a module and I want to see the Verilog; what do I do? @@ -213,7 +147,7 @@ ChiselStage.emitVerilog(new HelloWorld()) ### Get me FIRRTL -If for some reason you don't want the Verilog (e.g. maybe you want to run some custom transformations before exporting to Verilog), then use something along these lines (replace Multiplier with your module): +If for some reason you don't want the Verilog (e.g. maybe you want to run some custom transformations before exporting to Verilog), then use something along these lines: ```scala package intro @@ -263,8 +197,8 @@ res3: String = ... ### Why doesn't Chisel tell me which wires aren't connected? -As of commit [c313e13](https://github.com/freechipsproject/chisel3/commit/c313e137d4e562ef20195312501840ceab8cbc6a) it can! -Read more at [Unconnected Wires](../explanations/unconnected-wires) for details. +As long as your code uses `import chisel3._` (and not `import Chisel._`), it does! +See [Unconnected Wires](../explanations/unconnected-wires) for details. ### What does `Reference ... is not fully initialized.` mean? -- cgit v1.2.3 From 26461d500f402310cb3adf914b636be3a3a8e442 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 23 Mar 2021 14:54:57 -0700 Subject: Make plugin autoclonetype always on (#1826) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- build.sbt | 2 -- .../chisel3/internal/plugin/ChiselPlugin.scala | 6 ++++-- src/test/scala/chiselTests/AutoClonetypeSpec.scala | 21 ++++++++++----------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/build.sbt b/build.sbt index 42fcece3..5c051ff5 100644 --- a/build.sbt +++ b/build.sbt @@ -179,8 +179,6 @@ lazy val chisel = (project in file(".")). mimaPreviousArtifacts := Set(), libraryDependencies += defaultVersions("treadle") % "test", scalacOptions in Test ++= Seq("-language:reflectiveCalls"), - // Only used in Test for 3.4.x, used in Compile in 3.5 - scalacOptions in Test += "-P:chiselplugin:useBundlePlugin", scalacOptions in Compile in doc ++= Seq( "-diagrams", "-groups", diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala index a0651e1d..23082329 100644 --- a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala @@ -5,8 +5,9 @@ package chisel3.internal.plugin import scala.tools.nsc import nsc.Global import nsc.plugins.{Plugin, PluginComponent} +import scala.reflect.internal.util.NoPosition -private[plugin] case class ChiselPluginArguments(var useBundlePlugin: Boolean = false) { +private[plugin] case class ChiselPluginArguments(var useBundlePlugin: Boolean = true) { def useBundlePluginOpt = "useBundlePlugin" def useBundlePluginFullOpt = s"-P:chiselplugin:$useBundlePluginOpt" } @@ -24,7 +25,8 @@ class ChiselPlugin(val global: Global) extends Plugin { override def init(options: List[String], error: String => Unit): Boolean = { for (option <- options) { if (option == arguments.useBundlePluginOpt) { - arguments.useBundlePlugin = true + val msg = s"'${arguments.useBundlePluginFullOpt}' is now default behavior, you can stop using the scalacOption." + global.reporter.warning(NoPosition, msg) } else { error(s"Option not understood: '$option'") } diff --git a/src/test/scala/chiselTests/AutoClonetypeSpec.scala b/src/test/scala/chiselTests/AutoClonetypeSpec.scala index e0e6b2f0..57e00e99 100644 --- a/src/test/scala/chiselTests/AutoClonetypeSpec.scala +++ b/src/test/scala/chiselTests/AutoClonetypeSpec.scala @@ -265,19 +265,18 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { behavior of "Compiler Plugin Autoclonetype" - // Necessary test for 3.4.x, but we will break this (for non-plugin users) in 3.5 - it should "NOT break code that extends chisel3.util Bundles (whether they use the plugin or not)" in { - class MyModule extends MultiIOModule { - val io = IO(new InheritingBundle) - io.deq <> io.enq - io.count := 0.U - io.error := true.B - } - elaborate(new MyModule) - } - // New tests from the plugin if (usingPlugin) { + it should "NOT break code that extends chisel3.util Bundles if they use the plugin" in { + class MyModule extends MultiIOModule { + val io = IO(new InheritingBundle) + io.deq <> io.enq + io.count := 0.U + io.error := true.B + } + elaborate(new MyModule) + } + it should "support Bundles with non-val parameters" in { class MyBundle(i: Int) extends Bundle { val foo = UInt(i.W) -- cgit v1.2.3 From 7b8fbbe8913a2eb92f50948dc2be50dc482fb58d Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Sat, 27 Mar 2021 03:04:51 +0000 Subject: CCC 2021 placeholder (#1794) * Add CCC2021 info * Update README.md * Update README.md--- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index b9298520..04aaf81f 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,20 @@ ## Upcoming Events +### Chisel Dev Meeting Chisel/FIRRTL development meetings happen every Monday and Tuesday from 1100--1200 PT. Call-in info and meeting notes are available [here](https://docs.google.com/document/d/1BLP2DYt59DqI-FgFCcjw8Ddl4K-WU0nHmQu0sZ_wAGo/). +### Chisel Community Conference 2021, Shanghai, China. 6/24/2021 +The ShanghaiTech University and Institute of Software, Chinese Academy of Sciences will hold the first annual RISC-V Summit China (https://riscvsummitchina.github.io/) at the ShanghaiTech Zhangjiang campus from June 22 to 24, 2021, and the Chisel Community Conference will follow this conference from June 25 to 26, 2021. + +This Summit is the annual international event promoting Chisel, the new hardware design language that facilitates advanced circuit generation and design reuse for both ASIC and FPGA digital logic designs. This conference brings together the new experience from community and industry with tutorials, exhibitions, and more. + +This year’s conference program will demonstrate exciting new features of Chisel and amazing projects implemented by Chisel/FIRRTL. The call for speakers for the Chisel Summit is open from Monday, March 29 and speakers will be notified in late April or early May. + +CFP Link will be updated soon. + --- [![Join the chat at https://gitter.im/chipsalliance/chisel3](https://badges.gitter.im/chipsalliance/chisel3.svg)](https://gitter.im/chipsalliance/chisel3?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -- cgit v1.2.3 From 9c9bc131b031764332aeb2b175ae8abc500b8801 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Mon, 29 Mar 2021 14:56:22 -0700 Subject: Provide useful message on Vec.apply require (#1838) --- core/src/main/scala/chisel3/Aggregate.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/chisel3/Aggregate.scala b/core/src/main/scala/chisel3/Aggregate.scala index c5a917fa..df992bb9 100644 --- a/core/src/main/scala/chisel3/Aggregate.scala +++ b/core/src/main/scala/chisel3/Aggregate.scala @@ -342,7 +342,7 @@ object VecInit extends SourceInfoDoc { // DummyImplicit or additional type parameter will break some code. // Check that types are homogeneous. Width mismatch for Elements is safe. - require(!elts.isEmpty) + require(elts.nonEmpty, "Vec hardware values are not allowed to be empty") elts.foreach(requireIsHardware(_, "vec element")) val vec = Wire(Vec(elts.length, cloneSupertype(elts, "Vec"))) -- cgit v1.2.3 From a54d37d2864d86f6374037c49c81901a613275c5 Mon Sep 17 00:00:00 2001 From: Kalamár Ödön Date: Wed, 31 Mar 2021 20:15:38 +0200 Subject: Fix formatting issue of links (#1844) --- docs/src/appendix/experimental-features.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/appendix/experimental-features.md b/docs/src/appendix/experimental-features.md index 0e3f50c8..c989ec97 100644 --- a/docs/src/appendix/experimental-features.md +++ b/docs/src/appendix/experimental-features.md @@ -6,11 +6,11 @@ section: "chisel3" Chisel has a number of new features that are worth checking out. This page is an informal list of these features and projects. -[FixedPoint](#fixed-point) -[Module Variants](#module-variants) -[Module Variants](#bundle-literals) -[Interval Type](#interval-type) -[Loading Memories in Simulation](#loading-memories) +- [FixedPoint](#fixed-point) +- [Module Variants](#module-variants) +- [Module Variants](#bundle-literals) +- [Interval Type](#interval-type) +- [Loading Memories in Simulation](#loading-memories) ### FixedPoint FixedPoint numbers are basic *Data* type along side of UInt, SInt, etc. Most common math and logic operations -- cgit v1.2.3 From d3e2b346d4edfb3f380e4d8dbfc96b540791275e Mon Sep 17 00:00:00 2001 From: XinJun Ma Date: Thu, 1 Apr 2021 22:38:24 +0800 Subject: Fix Gitter chat room link (#1848) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 04aaf81f..a283a68c 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ CFP Link will be updated soon. --- -[![Join the chat at https://gitter.im/chipsalliance/chisel3](https://badges.gitter.im/chipsalliance/chisel3.svg)](https://gitter.im/chipsalliance/chisel3?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Join the chat at https://gitter.im/freechipsproject/chisel3](https://badges.gitter.im/chipsalliance/chisel3.svg)](https://gitter.im/freechipsproject/chisel3?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![CircleCI](https://circleci.com/gh/chipsalliance/chisel3/tree/master.svg?style=shield)](https://circleci.com/gh/chipsalliance/chisel3/tree/master) [![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/chipsalliance/chisel3.svg?label=release)](https://github.com/chipsalliance/chisel3/releases/latest) -- cgit v1.2.3 From e0da5ae47f3674bdd2018a672028290c927274e1 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Date: Wed, 7 Apr 2021 16:01:51 -0300 Subject: Add documentation guide about memory initialization (#1850) * Add documentation guide about memory initialization * Move information to experimental and add ref--- docs/src/appendix/experimental-features.md | 150 +++++++++++++++++++++++------ docs/src/explanations/memories.md | 15 ++- 2 files changed, 135 insertions(+), 30 deletions(-) diff --git a/docs/src/appendix/experimental-features.md b/docs/src/appendix/experimental-features.md index c989ec97..363f7a06 100644 --- a/docs/src/appendix/experimental-features.md +++ b/docs/src/appendix/experimental-features.md @@ -10,7 +10,7 @@ Chisel has a number of new features that are worth checking out. This page is a - [Module Variants](#module-variants) - [Module Variants](#bundle-literals) - [Interval Type](#interval-type) -- [Loading Memories in Simulation](#loading-memories) +- [Loading Memories for simulation or FPGA initialization](#loading-memories) ### FixedPoint FixedPoint numbers are basic *Data* type along side of UInt, SInt, etc. Most common math and logic operations @@ -143,49 +143,142 @@ Consider a Interval with a binary point of 3: aaa.bbb | shiftLeftBinaryPoint(2) | a.aabbb | 5 | X | X | increase the precision | | shiftRighBinaryPoint(2) | aaaa.b | 1 | X | X | reduce the precision | -## Loading Memories in simulation +## Loading Memories for simulation or FPGA initialization -Chisel now supports an experimental method for annotating memories to be loaded from a text file containing -hex or binary numbers. When using verilog simulation it uses the `$readmemh` or `$readmemb` -verilog extension. The treadle simulator can also load memories using the same annotation. +Chisel supports multiple experimental methods for annotating memories to be loaded from a text file containing hex or binary data. When using verilog simulation it uses the `$readmemh` or `$readmemb` verilog extension. The treadle simulator can also load memories using the same annotation. -### How to annotate -Assuming you have a memory in a Module -```scala mdoc:invisible +### Inline initialization with external file + +Memories can be initialized by generating inline `readmemh` or `readmemb` statements in the output Verilog. + +The function `loadMemoryFromFileInline` from `chisel3.util.experimental` allows the memory to be initialized by the synthesis software from the specified file. Chisel does not validate the file contents nor its location. Both the memory initialization file and the Verilog source should be accessible for the toolchain. + +```scala mdoc:silent import chisel3._ -val memoryDepth = 8 -val memoryType = Bool() -``` -```scala -val memory = Mem(memoryDepth, memoryType) +import chisel3.util.experimental.loadMemoryFromFileInline + +class InitMemInline(memoryFile: String = "") extends Module { + val width: Int = 32 + val io = IO(new Bundle { + val enable = Input(Bool()) + val write = Input(Bool()) + val addr = Input(UInt(10.W)) + val dataIn = Input(UInt(width.W)) + val dataOut = Output(UInt(width.W)) + }) + + val mem = SyncReadMem(1024, UInt(width.W)) + // Initialize memory + if (memoryFile.trim().nonEmpty) { + loadMemoryFromFileInline(mem, memoryFile) + } + io.dataOut := DontCare + when(io.enable) { + val rdwrPort = mem(io.addr) + when (io.write) { rdwrPort := io.dataIn } + .otherwise { io.dataOut := rdwrPort } + } +} ``` -At the top of your file just add the import +The default is to use `$readmemh` (which assumes all numbers in the file are in ascii hex), +but to use ascii binary there is an optional `hexOrBinary` argument which can be set to `MemoryLoadFileType.Hex` or `MemoryLoadFileType.Binary`. You will need to add an additional import. + +By default, the inline initialization will generate the memory `readmem` statements inside an `ifndef SYNTHESIS` block, which suits ASIC workflow. + +Some synthesis tools (like Synplify and Yosys) define `SYNTHESIS` so the `readmem` statement is not read when inside this block. + +To control this, one can use the `MemoryNoSynthInit` and `MemorySynthInit` annotations from `firrtl.annotations`. The former which is the default setting when no annotation is present generates `readmem` inside the block. Using the latter, the statement are generated outside the `ifndef` block so it can be used by FPGA synthesis tools. + +Below an example for initialization suited for FPGA workflows: + ```scala mdoc:silent -import chisel3.util.experimental.loadMemoryFromFile -``` -Now just add the memory annotation using -```scala - loadMemoryFromFile(memory, "/workspace/workdir/mem1.txt") +import chisel3._ +import chisel3.util.experimental.loadMemoryFromFileInline +import chisel3.experimental.{annotate, ChiselAnnotation} +import firrtl.annotations.MemorySynthInit + +class InitMemInlineFPGA(memoryFile: String = "") extends Module { + val width: Int = 32 + val io = IO(new Bundle { + val enable = Input(Bool()) + val write = Input(Bool()) + val addr = Input(UInt(10.W)) + val dataIn = Input(UInt(width.W)) + val dataOut = Output(UInt(width.W)) + }) + + // Notice the annotation below + annotate(new ChiselAnnotation { + override def toFirrtl = + MemorySynthInit + }) + + val mem = SyncReadMem(1024, UInt(width.W)) + if (memoryFile.trim().nonEmpty) { + loadMemoryFromFileInline(mem, memoryFile) + } + io.dataOut := DontCare + when(io.enable) { + val rdwrPort = mem(io.addr) + when (io.write) { rdwrPort := io.dataIn } + .otherwise { io.dataOut := rdwrPort } + } +} ``` -The default is to use `$readmemh` (which assumes all numbers in the file are in ascii hex), -bu to use ascii binary there is an optional third argument. You will need to add an additional import. + +#### SystemVerilog Bind Initialization + +Chisel can also initialize memories by generating a SV bind module with `readmemh` or `readmemb` statements by using the function `loadMemoryFromFile` from `chisel3.util.experimental`. + ```scala mdoc:silent -import firrtl.annotations.MemoryLoadFileType +import chisel3._ +import chisel3.util.experimental.loadMemoryFromFile + +class InitMemBind(val bits: Int, val size: Int, filename: String) extends Module { + val io = IO(new Bundle { + val nia = Input(UInt(bits.W)) + val insn = Output(UInt(32.W)) + }) + + val memory = Mem(size, UInt(32.W)) + io.insn := memory(io.nia >> 2); + loadMemoryFromFile(memory, filename) +} ``` -```scala - loadMemoryFromFile(memory, "/workspace/workdir/mem1.txt", MemoryLoadFileType.Binary) + +Which generates the bind module: + +```verilog +module BindsTo_0_Foo( + input clock, + input reset, + input [31:0] io_nia, + output [31:0] io_insn +); + +initial begin + $readmemh("test.hex", Foo.memory); +end +endmodule + +bind Foo BindsTo_0_Foo BindsTo_0_Foo_Inst(.*); ``` -See: [ComplexMemoryLoadingSpec.scala](https://freechipsproject/chisel-testers/src/test/scala/examples/ComplexMemoryLoadingSpec.scala) and -[LoadMemoryFromFileSpec.scala](https://github.com/freechipsproject/chisel-testers/src/test/scala/examples/LoadMemoryFromFileSpec.scala) -for working examples. ### Notes on files -There is no simple answer to where to put this file. It's probably best to create a resource directory somewhere and reference that through a full path. Because these files may be large, we did not want to copy them. + +There is no simple answer to where to put the `hex` or `bin` file with the initial contents. It's probably best to create a resource directory somewhere and reference that through a full path or place the file beside the generated Verilog. Another option is adding the path to the memory file in the synthesis tool path. Because these files may be large, Chisel does not copy them. > Don't forget there is no decimal option, so a 10 in an input file will be 16 decimal +See: [ComplexMemoryLoadingSpec.scala](https://github.com/freechipsproject/chisel-testers/blob/master/src/test/scala/examples/ComplexMemoryLoadingSpec.scala) and +[LoadMemoryFromFileSpec.scala](https://github.com/freechipsproject/chisel-testers/blob/master/src/test/scala/examples/LoadMemoryFromFileSpec.scala) +for working examples. + + ### Aggregate memories + Aggregate memories are supported but in bit of a clunky way. Since they will be split up into a memory per field, the following convention was adopted. When specifying the file for such a memory the file name should be regarded as a template. If the memory is a Bundle e.g. + ```scala mdoc:compile-only class MemDataType extends Bundle { val a = UInt(16.W) @@ -193,6 +286,7 @@ class MemDataType extends Bundle { val c = Bool() } ``` + The memory will be split into `memory_a`, `memory_b`, and `memory_c`. Similarly if a load file is specified as `"memory-load.txt"` the simulation will expect that there will be three files, `"memory-load_a.txt"`, `"memory-load_b.txt"`, `"memory-load_c.txt"` > Note: The use of `_` and that the memory field name is added before any file suffix. The suffix is optional but if present is considered to be the text after the last `.` in the file name. diff --git a/docs/src/explanations/memories.md b/docs/src/explanations/memories.md index 792d176e..09ac4c8d 100644 --- a/docs/src/explanations/memories.md +++ b/docs/src/explanations/memories.md @@ -11,9 +11,11 @@ Chisel provides facilities for creating both read only and read/write memories. ## ROM Users can define read only memories with a `Vec`: + ```scala mdoc:invisible import chisel3._ ``` + ``` scala mdoc:compile-only VecInit(inits: Seq[T]) VecInit(elt0: T, elts: T*) @@ -56,6 +58,7 @@ If the same memory address is both written and sequentially read on the same clo Values on the read data port are not guaranteed to be held until the next read cycle. If that is the desired behavior, external logic to hold the last read value must be added. #### Read port/write port + Ports into `SyncReadMem`s are created by applying a `UInt` index. A 1024-entry SRAM with one write port and one read port might be expressed as follows: ```scala mdoc:silent @@ -79,9 +82,11 @@ class ReadWriteSmem extends Module { Below is an example waveform of the one write port/one read port `SyncReadMem` with [masks](#masks). Note that the signal names will differ from the exact wire names generated for the `SyncReadMem`. With masking, it is also possible that multiple RTL arrays will be generated with the behavior below. -![read/write ports example waveform](https://svg.wavedrom.com/github/freechipsproject/www.chisel-lang.org/master/docs/src/main/tut/chisel3/memories_waveforms/smem_read_write.json) +![read/write ports example waveform](https://svg.wavedrom.com/github/freechipsproject/www.chisel-lang.org/master/docs/src/main/resources/json/smem_read_write.json) + #### Single-ported + Single-ported SRAMs can be inferred when the read and write conditions are mutually exclusive in the same `when` chain: ```scala mdoc:silent @@ -110,7 +115,7 @@ class RWSmem extends Module { Here is an example single read/write port waveform, with [masks](#masks) (again, generated signal names and number of arrays may differ): -![read/write ports example waveform](https://svg.wavedrom.com/github/freechipsproject/www.chisel-lang.org/master/docs/src/main/tut/chisel3/memories_waveforms/smem_rw.json) +![read/write ports example waveform](https://svg.wavedrom.com/github/freechipsproject/www.chisel-lang.org/master/docs/src/main/resources/json/smem_rw.json) ### `Mem`: combinational/asynchronous-read, sequential/synchronous-write @@ -174,3 +179,9 @@ class MaskedRWSmem extends Module { } ``` +### Memory Initialization + +Chisel memories can be initialized from an external `binary` or `hex` file emitting proper Verilog for synthesis or simulation. There are multiple modes of initialization. + +For more information, check the experimental docs on [Loading Memories](../appendix/experimental-features#loading-memories) feature. + -- cgit v1.2.3 From a67ddb239c5cfb41b94f11633d2a44055cb54e44 Mon Sep 17 00:00:00 2001 From: Megan Wachs Date: Wed, 7 Apr 2021 16:17:32 -0700 Subject: Update PULL_REQUEST_TEMPLATE.md (#1856) --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1a661ea8..bcc4d289 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -44,7 +44,7 @@ Text from here to the end of the body will be considered for inclusion in the re ### Reviewer Checklist (only modified by reviewer) - [ ] Did you add the appropriate labels? -- [ ] Did you mark the proper milestone (3.2.x, 3.3.x, 3.4.0, 3.5.0) ? +- [ ] Did you mark the proper milestone (3.2.x, 3.3.x, 3.4.x, 3.5.0) ? - [ ] Did you review? - [ ] Did you check whether all relevant Contributor checkboxes have been checked? - [ ] Did you mark as `Please Merge`? -- cgit v1.2.3 From 6e22d91ed31206292f9f8b02a89fe18885ad602d Mon Sep 17 00:00:00 2001 From: Megan Wachs Date: Mon, 12 Apr 2021 11:19:20 -0700 Subject: Add "how to tie off to 0" to the Cookbook (#1857) --- docs/src/cookbooks/cookbook.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/src/cookbooks/cookbook.md b/docs/src/cookbooks/cookbook.md index 6a8eeebf..b85ceadc 100644 --- a/docs/src/cookbooks/cookbook.md +++ b/docs/src/cookbooks/cookbook.md @@ -12,6 +12,7 @@ Please note that these examples make use of [Chisel's scala-style printing](../e * Converting Chisel Types to/from UInt * [How do I create a UInt from an instance of a Bundle?](#how-do-i-create-a-uint-from-an-instance-of-a-bundle) * [How do I create a Bundle from a UInt?](#how-do-i-create-a-bundle-from-a-uint) + * [How can I tieoff a Bundle/Vec to 0?](#how-can-i-tieoff-a-bundlevec-to-0) * [How do I create a Vec of Bools from a UInt?](#how-do-i-create-a-vec-of-bools-from-a-uint) * [How do I create a UInt from a Vec of Bool?](#how-do-i-create-a-uint-from-a-vec-of-bool) * Vectors and Registers @@ -66,6 +67,36 @@ class Foo extends RawModule { } ``` +### How can I tieoff a Bundle/Vec to 0? + +You can use `asTypeOf` as above. If you don't want to worry about the type of the thing +you are tying off, you can use `chiselTypeOf`: + +```scala mdoc:silent:reset +import chisel3._ + +class MyBundle extends Bundle { + val foo = UInt(4.W) + val bar = Vec(4, UInt(1.W)) +} + +class Foo(typ: Data) extends RawModule { + val bundleA = IO(Output(typ)) + val bundleB = IO(Output(typ)) + + // typ is already a Chisel Data Type, so can use it directly here, but you + // need to know that bundleA is of type typ + bundleA := 0.U.asTypeOf(typ) + + // bundleB is a Hardware data IO(Output(...)) so need to call chiselTypeOf, + // but this will work no matter the type of bundleB: + bundleB := 0.U.asTypeOf(chiselTypeOf(bundleB)) +} + +class Bar extends RawModule { + val foo = Module(new Foo(new MyBundle())) +} +``` ### How do I create a Vec of Bools from a UInt? Use [`VecInit`](https://www.chisel-lang.org/api/latest/chisel3/VecInit$.html) given a `Seq[Bool]` generated using the [`asBools`](https://www.chisel-lang.org/api/latest/chisel3/UInt.html#asBools():Seq[chisel3.Bool]) method. -- cgit v1.2.3 From f39ec2ef5a3b2140b43d631056c2f974ca1895d5 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Date: Wed, 14 Apr 2021 22:38:41 -0300 Subject: Fix doc formatting and generation (#1863) * Remove space between backticks and language * Make code examples in memories explanation work Co-authored-by: Jack Koenig --- docs/src/explanations/memories.md | 40 +++++++++++----------- .../polymorphism-and-parameterization.md | 9 +++-- docs/src/explanations/sequential-circuits.md | 2 +- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/docs/src/explanations/memories.md b/docs/src/explanations/memories.md index 09ac4c8d..1f2f83be 100644 --- a/docs/src/explanations/memories.md +++ b/docs/src/explanations/memories.md @@ -10,36 +10,36 @@ Chisel provides facilities for creating both read only and read/write memories. ## ROM -Users can define read only memories with a `Vec`: +Users can define read-only memories by constructing a `Vec` with `VecInit`. +`VecInit` can except either a variable-argument number of `Data` literals or a `Seq[Data]` literals that initialize the ROM. ```scala mdoc:invisible import chisel3._ +// This is bad, don't do this, use a val +def counter = util.Counter(true.B, 4)._1 +val Pi = 3.14 +def sin(t: Double): Double = t // What should this be? ``` -``` scala mdoc:compile-only - VecInit(inits: Seq[T]) - VecInit(elt0: T, elts: T*) -``` - -where `inits` is a sequence of initial `Data` literals that initialize the ROM. For example, users cancreate a small ROM initialized to 1, 2, 4, 8 and loop through all values using a counter as an address generator as follows: +For example, users can create a small ROM initialized to 1, 2, 4, 8 and loop through all values using a counter as an address generator as follows: -``` scala mdoc:compile-only - val m = VecInit(Array(1.U, 2.U, 4.U, 8.U)) - val r = m(counter(m.length.U)) +```scala mdoc:compile-only +val m = VecInit(1.U, 2.U, 4.U, 8.U) +val r = m(counter(m.length.U)) ``` We can create an *n* value sine lookup table using a ROM initialized as follows: -``` scala mdoc:silent - def sinTable(amp: Double, n: Int) = { - val times = - (0 until n).map(i => (i*2*Pi)/(n.toDouble-1) - Pi) - val inits = - times.map(t => round(amp * sin(t)).asSInt(32.W)) - VecInit(inits) - } - def sinWave(amp: Double, n: Int) = - sinTable(amp, n)(counter(n.U)) +```scala mdoc:compile-only +def sinTable(amp: Double, n: Int) = { + val times = + (0 until n).map(i => (i*2*Pi)/(n.toDouble-1) - Pi) + val inits = + times.map(t => Math.round(amp * sin(t)).asSInt(32.W)) + VecInit(inits) +} +def sinWave(amp: Double, n: Int) = + sinTable(amp, n)(counter(n.U)) ``` where `amp` is used to scale the fixpoint values stored in the ROM. diff --git a/docs/src/explanations/polymorphism-and-parameterization.md b/docs/src/explanations/polymorphism-and-parameterization.md index 94b896b1..9b69ed05 100644 --- a/docs/src/explanations/polymorphism-and-parameterization.md +++ b/docs/src/explanations/polymorphism-and-parameterization.md @@ -231,7 +231,10 @@ class X[T <: BaseModule with MyAdder](genT: => T) extends Module { println(ChiselStage.emitVerilog(new X(new Mod1))) println(ChiselStage.emitVerilog(new X(new Mod2))) ``` -```scala mdoc:passthrough -println(ChiselStage.emitVerilog(new X(new Mod1))) -println(ChiselStage.emitVerilog(new X(new Mod2))) + +Output: + +```scala mdoc:verilog +ChiselStage.emitVerilog(new X(new Mod1)) +ChiselStage.emitVerilog(new X(new Mod2)) ``` diff --git a/docs/src/explanations/sequential-circuits.md b/docs/src/explanations/sequential-circuits.md index 938416ac..36bbb1aa 100644 --- a/docs/src/explanations/sequential-circuits.md +++ b/docs/src/explanations/sequential-circuits.md @@ -11,7 +11,7 @@ import chisel3._ val in = Bool() ``` The simplest form of state element supported by Chisel is a positive edge-triggered register, which can be instantiated as: -``` scala mdoc:compile-only +```scala mdoc:compile-only val reg = RegNext(in) ``` This circuit has an output that is a copy of the input signal `in` delayed by one clock cycle. Note that we do not have to specify the type of Reg as it will be automatically inferred from its input when instantiated in this way. In the current version of Chisel, clock and reset are global signals that are implicitly included where needed. -- cgit v1.2.3 From 87bb0cc3c05f0aea103a0abec9e6b6dcff6050ad Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 15 Apr 2021 11:20:37 -0700 Subject: Improve Mergify labeling of backports (#1865) Using https://github.com/ucb-bar/chisel-repo-tools/pull/31--- .mergify.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index 38d5ad17..d3534b36 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -22,6 +22,8 @@ pull_request_rules: backport: branches: - 3.4.x + labels: + - Backport ignore_conflicts: true label_conflicts: bp-conflict label: @@ -37,6 +39,8 @@ pull_request_rules: branches: - 3.3.x - 3.4.x + labels: + - Backport ignore_conflicts: true label_conflicts: bp-conflict label: @@ -53,18 +57,13 @@ pull_request_rules: - 3.2.x - 3.3.x - 3.4.x + labels: + - Backport ignore_conflicts: true label_conflicts: bp-conflict label: add: - Backported -- name: label Mergify backport PR - conditions: - - title~=\(bp \#\d+\) - actions: - label: - add: - - Backport - name: automatic squash-and-mege of 3.2.x backport PRs conditions: - status-success=all tests passed -- cgit v1.2.3 From 0cc35a26d29b24b9c876afd875fe3cb1ef24056c Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Fri, 16 Apr 2021 03:49:45 +0800 Subject: update CCC2021 info (#1861) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- README.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a283a68c..0c608a15 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,61 @@ Chisel/FIRRTL development meetings happen every Monday and Tuesday from 1100--12 Call-in info and meeting notes are available [here](https://docs.google.com/document/d/1BLP2DYt59DqI-FgFCcjw8Ddl4K-WU0nHmQu0sZ_wAGo/). ### Chisel Community Conference 2021, Shanghai, China. 6/24/2021 -The ShanghaiTech University and Institute of Software, Chinese Academy of Sciences will hold the first annual RISC-V Summit China (https://riscvsummitchina.github.io/) at the ShanghaiTech Zhangjiang campus from June 22 to 24, 2021, and the Chisel Community Conference will follow this conference from June 25 to 26, 2021. -This Summit is the annual international event promoting Chisel, the new hardware design language that facilitates advanced circuit generation and design reuse for both ASIC and FPGA digital logic designs. This conference brings together the new experience from community and industry with tutorials, exhibitions, and more. - -This year’s conference program will demonstrate exciting new features of Chisel and amazing projects implemented by Chisel/FIRRTL. The call for speakers for the Chisel Summit is open from Monday, March 29 and speakers will be notified in late April or early May. - -CFP Link will be updated soon. +The Chisel Community Conference China 2021 (CCC2021) is planned for June 25, +2021 at the ShanghaiTech University. CCC is an annual gathering of Chisel +community enthusiasts and technical exchange workshop. +With the support of the Chisel development community, this conference will +bring together designers and developers with hands-on experience in Chisel +from home and abroad to share cutting-edge results and experiences from the +open source community and industry. + +Session topics include and are not limited to +* CPU Core (recommended but not restricted to RISC-V) implementations +* SoC implementations +* Verification +* Simulation +* Synthesis +* Education +* Experience sharing + +Types of manuscripts. +* Technical Presentations: case studies or problem-oriented presentations on +original research, breakthrough ideas, or insights into future trends. +Sessions should provide specific examples and include both practical and +theoretical information. The length of time is about 20 minutes. +* Lightning talks: 5 to 10 minutes, either pre-registered or on-site (depending +on the time of the conference), can present and promote a specific Chisel +project. + +The presentation submission language is required to be in English, and both +English and Chinese are acceptable for the presentation language. +Reviewers (subject to change at that time). +* Jack Koenig +* Adam Izraelevitz +* Edward Wang +* Jiuyang Liu + +Key Timeline. +Submission deadline: April 25, 2021 +Manuscript topics and abstracts should be submitted by the submission +deadline, and will be reviewed and selected by Chisel developers. +Notification of acceptance: by May 12, 2021 +Final manuscript deadline: May 30, 2021 +A full version of the manuscript should be submitted by the final deadline, and +Chisel developers will quality review and suggest final changes. + +Mail submission method. +``` +Subject: - [CCC] Your Topic +CC: Jiuyang Liu +CC: Jack Koenig +CC: Adam Izraelevitz +CC: Edward Wang +Body: Abstract of your paper. +Attachment: pdf only slides +All submissions are welcome. +``` --- -- cgit v1.2.3 From 83eeda2efebb1e3e3d66365d388f5de627766d47 Mon Sep 17 00:00:00 2001 From: Fabien Marteau Date: Mon, 19 Apr 2021 20:01:27 +0200 Subject: Update polymorphism-and-parameterization.md (#1868) nitpick--- docs/src/explanations/polymorphism-and-parameterization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/explanations/polymorphism-and-parameterization.md b/docs/src/explanations/polymorphism-and-parameterization.md index 9b69ed05..b388ee5b 100644 --- a/docs/src/explanations/polymorphism-and-parameterization.md +++ b/docs/src/explanations/polymorphism-and-parameterization.md @@ -4,7 +4,7 @@ title: "Polymorphism and Parameterization" section: "chisel3" --- -# Polymorphism and Paramterization +# Polymorphism and Parameterization _This section is advanced and can be skipped at first reading._ -- cgit v1.2.3 From da5e5cb512cc9249a58b951f750b63707c1c8d83 Mon Sep 17 00:00:00 2001 From: Martin Schoeberl Date: Wed, 21 Apr 2021 20:36:28 +0200 Subject: Add a link to the Chisel book (#1872) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 0c608a15..bd782aec 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,10 @@ The [**online Chisel Bootcamp**](https://mybinder.org/v2/gh/freechipsproject/chi The [**classic Chisel tutorial**](https://github.com/ucb-bar/chisel-tutorial) contains small exercises and runs on your computer. +### A Textbook on Chisel + +If you like a textbook to learn Chisel and also a bit of digital design in general, you may be interested in reading [**Digital Design with Chisel**](http://www.imm.dtu.dk/~masca/chisel-book.html). It is available in English, Chinese, Japanese, and Vietnamese. + ### Build Your Own Chisel Projects See [the setup instructions](https://github.com/chipsalliance/chisel3/blob/master/SETUP.md) for how to set up your environment to run Chisel locally. -- cgit v1.2.3 From 2c7264a6d923e2d1dc645c8b7dec2add7fb6cfbc Mon Sep 17 00:00:00 2001 From: Deborah Soung Date: Wed, 21 Apr 2021 14:47:03 -0700 Subject: fixing context bug (#1874) --- .../chisel3/aop/injecting/InjectingAspect.scala | 17 +++++++++------ src/test/scala/chiselTests/aop/InjectionSpec.scala | 25 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala b/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala index 170bfbad..768680ed 100644 --- a/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala +++ b/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala @@ -54,13 +54,14 @@ abstract class InjectorAspect[T <: RawModule, M <: RawModule]( * @return */ final def toAnnotation(modules: Iterable[M], circuit: String, moduleNames: Seq[String]): AnnotationSeq = { - val dynamicContext = new DynamicContext(annotationsInAspect) - // Add existing module names into the namespace. If injection logic instantiates new modules - // which would share the same name, they will get uniquified accordingly - moduleNames.foreach { n => - dynamicContext.globalNamespace.name(n) - } RunFirrtlTransformAnnotation(new InjectingTransform) +: modules.map { module => + val dynamicContext = new DynamicContext(annotationsInAspect) + // Add existing module names into the namespace. If injection logic instantiates new modules + // which would share the same name, they will get uniquified accordingly + moduleNames.foreach { n => + dynamicContext.globalNamespace.name(n) + } + val (chiselIR, _) = Builder.build(Module(new ModuleAspect(module) { module match { case x: Module => withClockAndReset(x.clock, x.reset) { injection(module) } @@ -75,11 +76,15 @@ abstract class InjectorAspect[T <: RawModule, M <: RawModule]( val annotations = chiselIR.annotations.map(_.toFirrtl).filterNot{ a => a.isInstanceOf[DesignAnnotation[_]] } + /** Statements to be injected via aspect. */ val stmts = mutable.ArrayBuffer[ir.Statement]() + /** Modules to be injected via aspect. */ val modules = Aspect.getFirrtl(chiselIR.copy(components = comps)).modules.flatMap { + // for "container" modules, inject their statements case m: firrtl.ir.Module if m.name == module.name => stmts += m.body Nil + // for modules to be injected case other: firrtl.ir.DefModule => Seq(other) } diff --git a/src/test/scala/chiselTests/aop/InjectionSpec.scala b/src/test/scala/chiselTests/aop/InjectionSpec.scala index c9fa2e5e..a28501a5 100644 --- a/src/test/scala/chiselTests/aop/InjectionSpec.scala +++ b/src/test/scala/chiselTests/aop/InjectionSpec.scala @@ -5,6 +5,7 @@ package chiselTests.aop import chisel3.testers.{BasicTester, TesterDriver} import chiselTests.{ChiselFlatSpec, Utils} import chisel3._ +import chisel3.aop.Select import chisel3.aop.injecting.InjectingAspect import logger.{LogLevel, LogLevelAnnotation} @@ -14,6 +15,11 @@ object InjectionHierarchy { val moduleSubmoduleA = Module(new SubmoduleA) } + class MultiModuleInjectionTester extends BasicTester { + val subA0 = Module(new SubmoduleA) + val subA1 = Module(new SubmoduleA) + } + class SubmoduleA extends Module { val io = IO(new Bundle { val out = Output(Bool()) @@ -104,6 +110,17 @@ class InjectionSpec extends ChiselFlatSpec with Utils { } ) + val multiModuleInjectionAspect = InjectingAspect( + { top: MultiModuleInjectionTester => + Select.collectDeep(top) { case m: SubmoduleA => m } + }, + { m: Module => + val wire = Wire(Bool()) + wire := m.reset.asBool() + dontTouch(wire) + stop() + } + ) "Test" should "pass if inserted the correct values" in { assertTesterPasses{ new AspectTester(Seq(0, 1, 2)) } @@ -142,4 +159,12 @@ class InjectionSpec extends ChiselFlatSpec with Utils { Seq(addingExternalModules) ++ TesterDriver.verilatorOnly ) } + + "Injection into multiple submodules of the same class" should "work" in { + assertTesterPasses( + {new MultiModuleInjectionTester}, + Nil, + Seq(multiModuleInjectionAspect) ++ TesterDriver.verilatorOnly + ) + } } -- cgit v1.2.3 From 804271ea9bae61998811de0de42e476178ed8f75 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Mon, 26 Apr 2021 12:00:34 -0700 Subject: Fix Gitter link in README (#1879) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bd782aec..1d91840a 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ These simulation-based verification tools are available for Chisel: - [**Cheat Sheet**](https://github.com/freechipsproject/chisel-cheatsheet/releases/latest/download/chisel_cheatsheet.pdf), a 2-page reference of the base Chisel syntax and libraries - [**ScalaDoc**](https://www.chisel-lang.org/api/latest/chisel3/index.html), a listing, description, and examples of the functionality exposed by Chisel -- [**Gitter**](https://gitter.im/chipsalliance/chisel3), where you can ask questions or discuss anything Chisel +- [**Gitter**](https://gitter.im/freechipsproject/chisel3), where you can ask questions or discuss anything Chisel - [**Website**](https://www.chisel-lang.org) ([source](https://github.com/freechipsproject/www.chisel-lang.org/)) If you are migrating from Chisel2, see [the migration guide](https://www.chisel-lang.org/chisel3/chisel3-vs-chisel2.html). -- cgit v1.2.3 From c71428023749b22718016f37fe68f5ddd358b5fe Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Mon, 26 Apr 2021 15:39:54 -0700 Subject: Add some error context to Converter .getRefs (#1878) --- .../scala/chisel3/internal/firrtl/Converter.scala | 87 +++++++++++++--------- 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala index aefbf8ab..ff0fa770 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -3,9 +3,9 @@ package chisel3.internal.firrtl import chisel3._ import chisel3.experimental._ -import chisel3.internal.sourceinfo.{NoSourceInfo, SourceLine, SourceInfo} +import chisel3.internal.sourceinfo.{NoSourceInfo, SourceInfo, SourceLine, UnlocatableSourceInfo} import firrtl.{ir => fir} -import chisel3.internal.{castToInt, throwException} +import chisel3.internal.{HasId, castToInt, throwException} import scala.annotation.tailrec import scala.collection.immutable.Queue @@ -24,6 +24,16 @@ private[chisel3] object Converter { case Percent => ("%%", List.empty) } + def getRef(id: HasId, sourceInfo: SourceInfo): Arg = + id.getOptionRef.getOrElse { + val module = id._parent.map(m => s" '$id' was defined in module '$m'.").getOrElse("") + val loc = sourceInfo.makeMessage(" " + _) + val link = "https://github.com/chipsalliance/chisel3/issues/new" + val msg = s"Internal error! Could not get ref for '$id'$loc!$module " + + s"This is a bug in Chisel, please file an issue at '$link'." + throwException(msg) + } + def convert(info: SourceInfo): fir.Info = info match { case _: NoSourceInfo => fir.NoInfo case SourceLine(fn, line, col) => fir.FileInfo(fir.StringLit(s"$fn $line:$col")) @@ -41,37 +51,37 @@ private[chisel3] object Converter { // TODO // * Memoize? // * Move into the Chisel IR? - def convert(arg: Arg, ctx: Component): fir.Expression = arg match { + def convert(arg: Arg, ctx: Component, info: SourceInfo): fir.Expression = arg match { case Node(id) => - convert(id.getRef, ctx) + convert(getRef(id, info), ctx, info) case Ref(name) => fir.Reference(name, fir.UnknownType) case Slot(imm, name) => - fir.SubField(convert(imm, ctx), name, fir.UnknownType) + fir.SubField(convert(imm, ctx, info), name, fir.UnknownType) case Index(imm, ILit(idx)) => - fir.SubIndex(convert(imm, ctx), castToInt(idx, "Index"), fir.UnknownType) + fir.SubIndex(convert(imm, ctx, info), castToInt(idx, "Index"), fir.UnknownType) case Index(imm, value) => - fir.SubAccess(convert(imm, ctx), convert(value, ctx), fir.UnknownType) + fir.SubAccess(convert(imm, ctx, info), convert(value, ctx, info), fir.UnknownType) case ModuleIO(mod, name) => if (mod eq ctx.id) fir.Reference(name, fir.UnknownType) - else fir.SubField(fir.Reference(mod.getRef.name, fir.UnknownType), name, fir.UnknownType) + else fir.SubField(fir.Reference(getRef(mod, info).name, fir.UnknownType), name, fir.UnknownType) case u @ ULit(n, UnknownWidth()) => fir.UIntLiteral(n, fir.IntWidth(u.minWidth)) case ULit(n, w) => fir.UIntLiteral(n, convert(w)) case slit @ SLit(n, w) => fir.SIntLiteral(n, convert(w)) val unsigned = if (n < 0) (BigInt(1) << slit.width.get) + n else n - val uint = convert(ULit(unsigned, slit.width), ctx) + val uint = convert(ULit(unsigned, slit.width), ctx, info) fir.DoPrim(firrtl.PrimOps.AsSInt, Seq(uint), Seq.empty, fir.UnknownType) // TODO Simplify case fplit @ FPLit(n, w, bp) => val unsigned = if (n < 0) (BigInt(1) << fplit.width.get) + n else n - val uint = convert(ULit(unsigned, fplit.width), ctx) + val uint = convert(ULit(unsigned, fplit.width), ctx, info) val lit = bp.asInstanceOf[KnownBinaryPoint].value fir.DoPrim(firrtl.PrimOps.AsFixedPoint, Seq(uint), Seq(lit), fir.UnknownType) case intervalLit @ IntervalLit(n, w, bp) => val unsigned = if (n < 0) (BigInt(1) << intervalLit.width.get) + n else n - val uint = convert(ULit(unsigned, intervalLit.width), ctx) + val uint = convert(ULit(unsigned, intervalLit.width), ctx, info) val lit = bp.asInstanceOf[KnownBinaryPoint].value fir.DoPrim(firrtl.PrimOps.AsInterval, Seq(uint), Seq(n, n, lit), fir.UnknownType) case lit: ILit => @@ -84,7 +94,7 @@ private[chisel3] object Converter { val consts = e.args.collect { case ILit(i) => i } val args = e.args.flatMap { case _: ILit => None - case other => Some(convert(other, ctx)) + case other => Some(convert(other, ctx, e.sourceInfo)) } val expr = e.op.name match { case "mux" => @@ -95,44 +105,45 @@ private[chisel3] object Converter { } Some(fir.DefNode(convert(e.sourceInfo), e.name, expr)) case e @ DefWire(info, id) => - Some(fir.DefWire(convert(info), e.name, extractType(id))) + Some(fir.DefWire(convert(info), e.name, extractType(id, info))) case e @ DefReg(info, id, clock) => - Some(fir.DefRegister(convert(info), e.name, extractType(id), convert(clock, ctx), - firrtl.Utils.zero, convert(id.getRef, ctx))) + Some(fir.DefRegister(convert(info), e.name, extractType(id, info), convert(clock, ctx, info), + firrtl.Utils.zero, convert(getRef(id, info), ctx, info))) case e @ DefRegInit(info, id, clock, reset, init) => - Some(fir.DefRegister(convert(info), e.name, extractType(id), convert(clock, ctx), - convert(reset, ctx), convert(init, ctx))) + Some(fir.DefRegister(convert(info), e.name, extractType(id, info), convert(clock, ctx, info), + convert(reset, ctx, info), convert(init, ctx, info))) case e @ DefMemory(info, id, t, size) => - Some(firrtl.CDefMemory(convert(info), e.name, extractType(t), size, false)) + Some(firrtl.CDefMemory(convert(info), e.name, extractType(t, info), size, false)) case e @ DefSeqMemory(info, id, t, size, ruw) => - Some(firrtl.CDefMemory(convert(info), e.name, extractType(t), size, true, ruw)) + Some(firrtl.CDefMemory(convert(info), e.name, extractType(t, info), size, true, ruw)) case e: DefMemPort[_] => + val info = e.sourceInfo Some(firrtl.CDefMPort(convert(e.sourceInfo), e.name, fir.UnknownType, - e.source.fullName(ctx), Seq(convert(e.index, ctx), convert(e.clock, ctx)), convert(e.dir))) + e.source.fullName(ctx), Seq(convert(e.index, ctx, info), convert(e.clock, ctx, info)), convert(e.dir))) case Connect(info, loc, exp) => - Some(fir.Connect(convert(info), convert(loc, ctx), convert(exp, ctx))) + Some(fir.Connect(convert(info), convert(loc, ctx, info), convert(exp, ctx, info))) case BulkConnect(info, loc, exp) => - Some(fir.PartialConnect(convert(info), convert(loc, ctx), convert(exp, ctx))) + Some(fir.PartialConnect(convert(info), convert(loc, ctx, info), convert(exp, ctx, info))) case Attach(info, locs) => - Some(fir.Attach(convert(info), locs.map(l => convert(l, ctx)))) + Some(fir.Attach(convert(info), locs.map(l => convert(l, ctx, info)))) case DefInvalid(info, arg) => - Some(fir.IsInvalid(convert(info), convert(arg, ctx))) + Some(fir.IsInvalid(convert(info), convert(arg, ctx, info))) case e @ DefInstance(info, id, _) => Some(fir.DefInstance(convert(info), e.name, id.name)) case Stop(info, clock, ret) => - Some(fir.Stop(convert(info), ret, convert(clock, ctx), firrtl.Utils.one)) + Some(fir.Stop(convert(info), ret, convert(clock, ctx, info), firrtl.Utils.one)) case Printf(info, clock, pable) => val (fmt, args) = unpack(pable, ctx) Some(fir.Print(convert(info), fir.StringLit(fmt), - args.map(a => convert(a, ctx)), convert(clock, ctx), firrtl.Utils.one)) + args.map(a => convert(a, ctx, info)), convert(clock, ctx, info), firrtl.Utils.one)) case Verification(op, info, clk, pred, msg) => val firOp = op match { case Formal.Assert => fir.Formal.Assert case Formal.Assume => fir.Formal.Assume case Formal.Cover => fir.Formal.Cover } - Some(fir.Verification(firOp, convert(info), convert(clk, ctx), - convert(pred, ctx), firrtl.Utils.one, fir.StringLit(msg))) + Some(fir.Verification(firOp, convert(info), convert(clk, ctx, info), + convert(pred, ctx, info), firrtl.Utils.one, fir.StringLit(msg))) case _ => None } @@ -173,7 +184,7 @@ private[chisel3] object Converter { // Please see WhenFrame for more details case None => cmds.head match { case WhenBegin(info, pred) => - val when = fir.Conditionally(convert(info), convert(pred, ctx), fir.EmptyStmt, fir.EmptyStmt) + val when = fir.Conditionally(convert(info), convert(pred, ctx, info), fir.EmptyStmt, fir.EmptyStmt) val frame = WhenFrame(when, acc, false) rec(Queue.empty, frame +: scope)(cmds.tail) case WhenEnd(info, depth, _) => @@ -221,7 +232,9 @@ private[chisel3] object Converter { case d => d.specifiedDirection } - def extractType(data: Data, clearDir: Boolean = false): fir.Type = data match { + def extractType(data: Data, info: SourceInfo): fir.Type = extractType(data, false, info) + + def extractType(data: Data, clearDir: Boolean, info: SourceInfo): fir.Type = data match { case _: Clock => fir.ClockType case _: AsyncReset => fir.AsyncResetType case _: ResetType => fir.ResetType @@ -231,16 +244,16 @@ private[chisel3] object Converter { case d: FixedPoint => fir.FixedType(convert(d.width), convert(d.binaryPoint)) case d: Interval => fir.IntervalType(d.range.lowerBound, d.range.upperBound, d.range.firrtlBinaryPoint) case d: Analog => fir.AnalogType(convert(d.width)) - case d: Vec[_] => fir.VectorType(extractType(d.sample_element, clearDir), d.length) + case d: Vec[_] => fir.VectorType(extractType(d.sample_element, clearDir, info), d.length) case d: Record => val childClearDir = clearDir || d.specifiedDirection == SpecifiedDirection.Input || d.specifiedDirection == SpecifiedDirection.Output def eltField(elt: Data): fir.Field = (childClearDir, firrtlUserDirOf(elt)) match { - case (true, _) => fir.Field(elt.getRef.name, fir.Default, extractType(elt, true)) + case (true, _) => fir.Field(getRef(elt, info).name, fir.Default, extractType(elt, true, info)) case (false, SpecifiedDirection.Unspecified | SpecifiedDirection.Output) => - fir.Field(elt.getRef.name, fir.Default, extractType(elt, false)) + fir.Field(getRef(elt, info).name, fir.Default, extractType(elt, false, info)) case (false, SpecifiedDirection.Flip | SpecifiedDirection.Input) => - fir.Field(elt.getRef.name, fir.Flip, extractType(elt, false)) + fir.Field(getRef(elt, info).name, fir.Flip, extractType(elt, false, info)) } fir.BundleType(d.elements.toIndexedSeq.reverse.map { case (_, e) => eltField(e) }) } @@ -251,6 +264,7 @@ private[chisel3] object Converter { case StringParam(value) => fir.StringParam(name, fir.StringLit(value)) case RawParam(value) => fir.RawStringParam(name, value) } + def convert(port: Port, topDir: SpecifiedDirection = SpecifiedDirection.Unspecified): fir.Port = { val resolvedDir = SpecifiedDirection.fromParent(topDir, port.dir) val dir = resolvedDir match { @@ -261,8 +275,9 @@ private[chisel3] object Converter { case SpecifiedDirection.Input | SpecifiedDirection.Output => true case SpecifiedDirection.Unspecified | SpecifiedDirection.Flip => false } - val tpe = extractType(port.id, clearDir) - fir.Port(fir.NoInfo, port.id.getRef.name, dir, tpe) + val info = UnlocatableSourceInfo // Unfortunately there is no source locator for ports ATM + val tpe = extractType(port.id, clearDir, info) + fir.Port(fir.NoInfo, getRef(port.id, info).name, dir, tpe) } def convert(component: Component): fir.DefModule = component match { -- cgit v1.2.3 From 23b3fe8a6a2db92599bb0775626425056d47d1de Mon Sep 17 00:00:00 2001 From: Megan Wachs Date: Mon, 26 Apr 2021 16:02:54 -0700 Subject: Cookbooks: make examples more clear and remove naming (#1881) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- docs/src/cookbooks/cookbook.md | 92 ++++++++++++------------------------------ 1 file changed, 26 insertions(+), 66 deletions(-) diff --git a/docs/src/cookbooks/cookbook.md b/docs/src/cookbooks/cookbook.md index b85ceadc..290a6c82 100644 --- a/docs/src/cookbooks/cookbook.md +++ b/docs/src/cookbooks/cookbook.md @@ -46,6 +46,10 @@ class Foo extends RawModule { bundle.foo := 0xc.U bundle.bar := 0x3.U val uint = bundle.asUInt + printf(p"$uint") // 195 + + // Test + assert(uint === 0xc3.U) } ``` @@ -64,6 +68,12 @@ class MyBundle extends Bundle { class Foo extends RawModule { val uint = 0xb4.U val bundle = uint.asTypeOf(new MyBundle) + + printf(p"$bundle") // Bundle(foo -> 11, bar -> 4) + + // Test + assert(bundle.foo === 0xb.U) + assert(bundle.bar === 0x4.U) } ``` @@ -107,6 +117,14 @@ import chisel3._ class Foo extends RawModule { val uint = 0xc.U val vec = VecInit(uint.asBools) + + printf(p"$vec") // Vec(0, 0, 1, 1) + + // Test + assert(vec(0) === false.B) + assert(vec(1) === false.B) + assert(vec(2) === true.B) + assert(vec(3) === true.B) } ``` @@ -120,6 +138,13 @@ import chisel3._ class Foo extends RawModule { val vec = VecInit(true.B, false.B, true.B, true.B) val uint = vec.asUInt + + printf(p"$uint") // 13 + + // Test + // (remember leftmost Bool in Vec is low order bit) + assert(0xd.U === uint) + } ``` @@ -331,73 +356,8 @@ class ModuleWithOptionalIO(flag: Boolean) extends Module { ### How do I get Chisel to name signals properly in blocks like when/withClockAndReset? -To get Chisel to name signals (wires and registers) declared inside of blocks like `when`, `withClockAndReset`, etc, use the [`@chiselName`](https://www.chisel-lang.org/api/latest/chisel3/experimental/package$$chiselName.html) annotation as shown below: - -```scala mdoc:silent:reset -import chisel3._ -import chisel3.experimental.chiselName - -@chiselName -class TestMod extends Module { - val io = IO(new Bundle { - val a = Input(Bool()) - val b = Output(UInt(4.W)) - }) - when (io.a) { - val innerReg = RegInit(5.U(4.W)) - innerReg := innerReg + 1.U - io.b := innerReg - } .otherwise { - io.b := 10.U - } -} -``` - -Note that you will need to add the following line to your project's `build.sbt` file. - -```scala -addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full) -``` - -If we compile this module *without* `@chiselName`, Chisel is not able to name `innerReg` correctly (notice the `_T`): - -```scala mdoc:passthrough -import chisel3.stage.{ChiselStage, ChiselGeneratorAnnotation} -import firrtl.annotations.DeletedAnnotation -import firrtl.EmittedVerilogCircuitAnnotation - -class TestModWithout extends Module { - override val desiredName = "TestMod" - val io = IO(new Bundle { - val a = Input(Bool()) - val b = Output(UInt(4.W)) - }) - when (io.a) { - val innerReg = RegInit(5.U(4.W)) - innerReg := innerReg + 1.U - io.b := innerReg - } .otherwise { - io.b := 10.U - } -} +Use the compiler plugin, and check out the [Naming Cookbook](#naming) if that still does not do what you want. -(new ChiselStage) - .execute(Array("-X", "verilog"), Seq(ChiselGeneratorAnnotation(() => new TestModWithout))) - .collectFirst{ case DeletedAnnotation(_, a: EmittedVerilogCircuitAnnotation) => a.value.value } - .foreach(a => println(s"""|```verilog - |$a - |```""".stripMargin)) -``` - -However, if we use `@chiselName` then the register previously called `_T` is now `innerReg`: -```scala mdoc:passthrough -(new ChiselStage) - .execute(Array("-X", "verilog"), Seq(ChiselGeneratorAnnotation(() => new TestMod))) - .collectFirst{ case DeletedAnnotation(_, a: EmittedVerilogCircuitAnnotation) => a.value.value } - .foreach(a => println(s"""|```verilog - |$a - |```""".stripMargin)) -``` ### How do I get Chisel to name the results of vector reads properly? Currently, name information is lost when using dynamic indexing. For example: ```scala mdoc:silent -- cgit v1.2.3 From 6deb379b1d8bafc81a605f60476bf0f24eac60b4 Mon Sep 17 00:00:00 2001 From: Chick Markley Date: Tue, 27 Apr 2021 12:17:17 -0700 Subject: Introduce VecLiterals (#1834) This PR provides for support for Vec literals. They can be one of two forms Inferred: ``` Vec.Lit(0x1.U, 0x2.U) ``` or explicit: ``` Vec(2, UInt(4.W)).Lit(0 -> 0x1.U, 1 -> 0x2.U) ``` - Explicit form allows for partial, or sparse, literals. - Vec literals can be used as Register initializers - Arbitrary nesting (consistent with type constraints is allowed)--- core/src/main/scala/chisel3/Aggregate.scala | 274 +++++++++-- core/src/main/scala/chisel3/Data.scala | 6 + core/src/main/scala/chisel3/Element.scala | 4 + .../main/scala/chisel3/experimental/package.scala | 33 +- core/src/main/scala/chisel3/internal/Binding.scala | 4 + .../main/scala/chisel3/internal/firrtl/IR.scala | 24 + docs/src/appendix/experimental-features.md | 82 +++- src/test/scala/chiselTests/BundleLiteralSpec.scala | 23 +- src/test/scala/chiselTests/ChiselSpec.scala | 25 +- src/test/scala/chiselTests/VecLiteralSpec.scala | 526 +++++++++++++++++++++ 10 files changed, 950 insertions(+), 51 deletions(-) create mode 100644 src/test/scala/chiselTests/VecLiteralSpec.scala diff --git a/core/src/main/scala/chisel3/Aggregate.scala b/core/src/main/scala/chisel3/Aggregate.scala index df992bb9..c0b965b6 100644 --- a/core/src/main/scala/chisel3/Aggregate.scala +++ b/core/src/main/scala/chisel3/Aggregate.scala @@ -2,18 +2,19 @@ package chisel3 +import chisel3.experimental.VecLiterals.AddVecLiteralConstructor + import scala.collection.immutable.ListMap import scala.collection.mutable.{HashSet, LinkedHashMap} import scala.language.experimental.macros - -import chisel3.experimental.BaseModule -import chisel3.experimental.BundleLiteralException -import chisel3.experimental.EnumType +import chisel3.experimental.{BaseModule, BundleLiteralException, ChiselEnum, EnumType, VecLiteralException} import chisel3.internal._ import chisel3.internal.Builder.pushCommand import chisel3.internal.firrtl._ import chisel3.internal.sourceinfo._ +import scala.collection.mutable + class AliasedAggregateFieldException(message: String) extends ChiselException(message) /** An abstract class for data types that solely consist of (are an aggregate @@ -51,16 +52,19 @@ sealed abstract class Aggregate extends Data { */ override def litOption: Option[BigInt] = { // Shift the accumulated value by our width and add in our component, masked by our width. - def shiftAdd(accumulator: Option[BigInt], elt: Data): Option[BigInt] = (accumulator, elt.litOption()) match { - case (Some(accumulator), Some(eltLit)) => - val width = elt.width.get - val masked = ((BigInt(1) << width) - 1) & eltLit // also handles the negative case with two's complement - Some((accumulator << width) + masked) - case (None, _) => None - case (_, None) => None + def shiftAdd(accumulator: Option[BigInt], elt: Data): Option[BigInt] = { + (accumulator, elt.litOption()) match { + case (Some(accumulator), Some(eltLit)) => + val width = elt.width.get + val masked = ((BigInt(1) << width) - 1) & eltLit // also handles the negative case with two's complement + Some((accumulator << width) + masked) + case (None, _) => None + case (_, None) => None + } } + topBindingOpt match { - case Some(BundleLitBinding(_)) => + case Some(BundleLitBinding(_)) | Some(VecLitBinding(_)) => getElements .reverse .foldLeft[Option[BigInt]](Some(BigInt(0)))(shiftAdd) @@ -73,6 +77,7 @@ sealed abstract class Aggregate extends Data { def getElements: Seq[Data] private[chisel3] def width: Width = getElements.map(_.width).foldLeft(0.W)(_ + _) + private[chisel3] def legacyConnect(that: Data)(implicit sourceInfo: SourceInfo): Unit = { // If the source is a DontCare, generate a DefInvalid for the sink, // otherwise, issue a Connect. @@ -83,11 +88,50 @@ sealed abstract class Aggregate extends Data { } } + // Returns pairs of all fields, element-level and containers, in a Record and their path names + private[chisel3] def getRecursiveFields(data: Data, path: String): Seq[(Data, String)] = data match { + case data: Record => + data.elements.map { case (fieldName, fieldData) => + getRecursiveFields(fieldData, s"$path.$fieldName") + }.fold(Seq(data -> path)) { + _ ++ _ + } + case data: Vec[_] => + data.getElements.zipWithIndex.map { case (fieldData, fieldIndex) => + getRecursiveFields(fieldData, path = s"$path($fieldIndex)") + }.fold(Seq(data -> path)) { + _ ++ _ + } + case data => Seq(data -> path) // we don't support or recurse into other Aggregate types here + } + + + // Returns pairs of corresponding fields between two Records of the same type + private[chisel3] def getMatchedFields(x: Data, y: Data): Seq[(Data, Data)] = (x, y) match { + case (x: Element, y: Element) => + require(x typeEquivalent y) + Seq(x -> y) + case (x: Record, y: Record) => + (x.elements zip y.elements).map { case ((xName, xElt), (yName, yElt)) => + require(xName == yName) // assume fields returned in same, deterministic order + getMatchedFields(xElt, yElt) + }.fold(Seq(x -> y)) { + _ ++ _ + } + case (x: Vec[_], y: Vec[_]) => + (x.getElements zip y.getElements).map { case (xElt, yElt) => + getMatchedFields(xElt, yElt) + }.fold(Seq(x -> y)) { + _ ++ _ + } + } + override def do_asUInt(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = { SeqUtils.do_asUInt(flatten.map(_.asUInt())) } + private[chisel3] override def connectFromBits(that: Bits)(implicit sourceInfo: SourceInfo, - compileOptions: CompileOptions): Unit = { + compileOptions: CompileOptions): Unit = { var i = 0 val bits = if (that.isLit) that else WireDefault(UInt(this.width), that) // handles width padding for (x <- flatten) { @@ -155,9 +199,18 @@ trait VecFactory extends SourceInfoDoc { */ sealed class Vec[T <: Data] private[chisel3] (gen: => T, val length: Int) extends Aggregate with VecLike[T] { + override def toString: String = { + val bindingString = topBindingOpt match { + case Some(VecLitBinding(vecLitBinding)) => + val contents = vecLitBinding.zipWithIndex.map { case ((data, lit), index) => + s"$index=$lit" + }.mkString(", ") + s"($contents)" + case _ => bindingToString + } val elementType = sample_element.cloneType - s"$elementType[$length]$bindingToString" + s"$elementType[$length]$bindingString" } private[chisel3] override def typeEquivalent(that: Data): Boolean = that match { @@ -318,6 +371,154 @@ sealed class Vec[T <: Data] private[chisel3] (gen: => T, val length: Int) } curLayer(0) } + + /** Creates a Vec literal of this type with specified values. this must be a chisel type. + * + * @param elementInitializers literal values, specified as a pair of the Vec field to the literal value. + * The Vec field is specified as a function from an object of this type to the field. + * Fields that aren't initialized to DontCare, and assignment to a wire will overwrite any + * existing value with DontCare. + * @return a Vec literal of this type with subelement values specified + * + * Vec(2, UInt(8.W)).Lit( + * 1 -> 0x0A.U, + * 2 -> 0x0B.U + * ) + * }}} + */ + private[chisel3] def _makeLit(elementInitializers: (Int, T)*)(implicit sourceInfo: SourceInfo, + compileOptions: CompileOptions): this.type = { + + def checkLiteralConstruction(): Unit = { + val dupKeys = elementInitializers.map { x => x._1 }.groupBy(x => x).flatMap { case (k, v) => + if (v.length > 1) { + Some(k, v.length) + } else { + None + } + } + if (dupKeys.nonEmpty) { + throw new VecLiteralException( + s"VecLiteral: has duplicated indices ${dupKeys.map { case (k, n) => s"$k($n times)" }.mkString(",")}" + ) + } + + val outOfRangeIndices = elementInitializers.map(_._1).filter { case index => index < 0 || index >= length } + if (outOfRangeIndices.nonEmpty) { + throw new VecLiteralException( + s"VecLiteral: The following indices (${outOfRangeIndices.mkString(",")}) " + + s"are less than zero or greater or equal to than Vec length" + ) + } + cloneSupertype(elementInitializers.map(_._2), s"Vec.Lit(...)") + + // look for literals of this vec that are wider than the vec's type + val badLits = elementInitializers.flatMap { + case (index, lit) => + (sample_element.width, lit.width) match { + case (KnownWidth(m), KnownWidth(n)) => + if (m < n) Some(index -> lit) else None + case (KnownWidth(_), _) => + None + case (UnknownWidth(), _) => + None + case _ => + Some(index -> lit) + } + case _ => None + } + if (badLits.nonEmpty) { + throw new VecLiteralException( + s"VecLiteral: Vec[$gen] has the following incorrectly typed or sized initializers: " + + badLits.map { case (a, b) => s"$a -> $b" }.mkString(",") + ) + } + + } + + requireIsChiselType(this, "vec literal constructor model") + checkLiteralConstruction() + + val clone = cloneType + val cloneFields = getRecursiveFields(clone, "(vec root)").toMap + + // Create the Vec literal binding from litArgs of arguments + val vecLitLinkedMap = new mutable.LinkedHashMap[Data, LitArg]() + elementInitializers.sortBy { case (a, _) => a }.foreach { case (fieldIndex, value) => + val field = clone.apply(fieldIndex) + val fieldName = cloneFields.getOrElse(field, + throw new VecLiteralException(s"field $field (with value $value) is not a field," + + s" ensure the field is specified as a function returning a field on an object of class ${this.getClass}," + + s" eg '_.a' to select hypothetical bundle field 'a'") + ) + + val valueBinding = value.topBindingOpt match { + case Some(litBinding: LitBinding) => litBinding + case _ => throw new VecLiteralException(s"field $fieldIndex specified with non-literal value $value") + } + + field match { // Get the litArg(s) for this field + case bitField: Bits => + if (!field.typeEquivalent(bitField)) { + throw new VecLiteralException( + s"VecLit: Literal specified at index $fieldIndex ($value) does not match Vec type $sample_element" + ) + } + if (bitField.getWidth > field.getWidth) { + throw new VecLiteralException( + s"VecLit: Literal specified at index $fieldIndex ($value) is too wide for Vec type $sample_element" + ) + } + val litArg = valueBinding match { + case ElementLitBinding(litArg) => litArg + case BundleLitBinding(litMap) => litMap.getOrElse(value, + throw new BundleLiteralException(s"Field $fieldName specified with unspecified value") + ) + case VecLitBinding(litMap) => litMap.getOrElse(value, + throw new VecLiteralException(s"Field $fieldIndex specified with unspecified value")) + } + val adjustedLitArg = litArg.cloneWithWidth(sample_element.width) + vecLitLinkedMap(bitField) = adjustedLitArg + + case recordField: Record => + if (!(recordField.typeEquivalent(value))) { + throw new VecLiteralException(s"field $fieldIndex $recordField specified with non-type-equivalent value $value") + } + // Copy the source BundleLitBinding with fields (keys) remapped to the clone + val remap = getMatchedFields(value, recordField).toMap + valueBinding.asInstanceOf[BundleLitBinding].litMap.map { case (valueField, valueValue) => + vecLitLinkedMap(remap(valueField)) = valueValue + } + + case vecField: Vec[_] => + if (!(vecField typeEquivalent value)) { + throw new VecLiteralException(s"field $fieldIndex $vecField specified with non-type-equivalent value $value") + } + // Copy the source VecLitBinding with vecFields (keys) remapped to the clone + val remap = getMatchedFields(value, vecField).toMap + value.topBinding.asInstanceOf[VecLitBinding].litMap.map { case (valueField, valueValue) => + vecLitLinkedMap(remap(valueField)) = valueValue + } + + case enumField: EnumType => { + if (!(enumField typeEquivalent value)) { + throw new VecLiteralException(s"field $fieldIndex $enumField specified with non-type-equivalent enum value $value") + } + val litArg = valueBinding match { + case ElementLitBinding(litArg) => litArg + case _ => + throw new VecLiteralException(s"field $fieldIndex $enumField could not bematched with $valueBinding") + } + vecLitLinkedMap(field) = litArg + } + + case _ => throw new VecLiteralException(s"unsupported field $fieldIndex of type $field") + } + } + + clone.bind(VecLitBinding(ListMap(vecLitLinkedMap.toSeq:_*))) + clone + } } object VecInit extends SourceInfoDoc { @@ -528,25 +729,6 @@ abstract class Record(private[chisel3] implicit val compileOptions: CompileOptio * }}} */ private[chisel3] def _makeLit(elems: (this.type => (Data, Data))*): this.type = { - // Returns pairs of all fields, element-level and containers, in a Record and their path names - def getRecursiveFields(data: Data, path: String): Seq[(Data, String)] = data match { - case data: Record => data.elements.map { case (fieldName, fieldData) => - getRecursiveFields(fieldData, s"$path.$fieldName") - }.fold(Seq(data -> path)) { _ ++ _ } - case data => Seq(data -> path) // we don't support or recurse into other Aggregate types here - } - - // Returns pairs of corresponding fields between two Records of the same type - def getMatchedFields(x: Data, y: Data): Seq[(Data, Data)] = (x, y) match { - case (x: Element, y: Element) => - require(x typeEquivalent y) - Seq(x -> y) - case (x: Record, y: Record) => - (x.elements zip y.elements).map { case ((xName, xElt), (yName, yElt)) => - require(xName == yName) // assume fields returned in same, deterministic order - getMatchedFields(xElt, yElt) - }.fold(Seq(x -> y)) { _ ++ _ } - } requireIsChiselType(this, "bundle literal constructor model") val clone = cloneType @@ -572,9 +754,15 @@ abstract class Record(private[chisel3] implicit val compileOptions: CompileOptio val litArg = valueBinding match { case ElementLitBinding(litArg) => litArg case BundleLitBinding(litMap) => litMap.getOrElse(value, - throw new BundleLiteralException(s"Field $fieldName specified with unspecified value")) + throw new BundleLiteralException(s"Field $fieldName specified with unspecified value") + ) + case VecLitBinding(litMap) => litMap.getOrElse(value, + throw new VecLiteralException(s"Vec literal $fieldName specified with out literal values") + ) + } Seq(field -> litArg) + case field: Record => if (!(field typeEquivalent value)) { throw new BundleLiteralException(s"field $fieldName $field specified with non-type-equivalent value $value") @@ -584,18 +772,33 @@ abstract class Record(private[chisel3] implicit val compileOptions: CompileOptio value.topBinding.asInstanceOf[BundleLitBinding].litMap.map { case (valueField, valueValue) => remap(valueField) -> valueValue } + + case vecField: Vec[_] => + if (!(vecField typeEquivalent value)) { + throw new BundleLiteralException(s"field $fieldName $vecField specified with non-type-equivalent value $value") + } + // Copy the source BundleLitBinding with fields (keys) remapped to the clone + val remap = getMatchedFields(value, vecField).toMap + value.topBinding.asInstanceOf[VecLitBinding].litMap.map { case (valueField, valueValue) => + remap(valueField) -> valueValue + } + case field: EnumType => { if (!(field typeEquivalent value)) { throw new BundleLiteralException(s"field $fieldName $field specified with non-type-equivalent enum value $value") } val litArg = valueBinding match { case ElementLitBinding(litArg) => litArg + case _ => + throw new BundleLiteralException(s"field $fieldName $field could not be matched with $valueBinding") } Seq(field -> litArg) } case _ => throw new BundleLiteralException(s"unsupported field $fieldName of type $field") } - } // don't convert to a Map yet to preserve duplicate keys + } + + // don't convert to a Map yet to preserve duplicate keys val duplicates = bundleLitMap.map(_._1).groupBy(identity).collect { case (x, elts) if elts.size > 1 => x } if (!duplicates.isEmpty) { val duplicateNames = duplicates.map(cloneFields(_)).mkString(", ") @@ -694,6 +897,7 @@ class AutoClonetypeException(message: String) extends ChiselException(message) package experimental { class BundleLiteralException(message: String) extends ChiselException(message) + class VecLiteralException(message: String) extends ChiselException(message) } diff --git a/core/src/main/scala/chisel3/Data.scala b/core/src/main/scala/chisel3/Data.scala index 377a94e6..0241f248 100644 --- a/core/src/main/scala/chisel3/Data.scala +++ b/core/src/main/scala/chisel3/Data.scala @@ -392,6 +392,7 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { case Some(DontCareBinding()) => s"(DontCare)" case Some(ElementLitBinding(litArg)) => s"(unhandled literal)" case Some(BundleLitBinding(litMap)) => s"(unhandled bundle literal)" + case Some(VecLitBinding(litMap)) => s"(unhandled vec literal)" }).getOrElse("") // Return ALL elements at root of this type. @@ -491,6 +492,11 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { case Some(litArg) => litArg case _ => materializeWire() // FIXME FIRRTL doesn't have Bundle literal expressions } + case Some(VecLitBinding(litMap)) => + litMap.get(this) match { + case Some(litArg) => litArg + case _ => materializeWire() // FIXME FIRRTL doesn't have Vec literal expressions + } case Some(DontCareBinding()) => materializeWire() // FIXME FIRRTL doesn't have a DontCare expression so materialize a Wire // Non-literals diff --git a/core/src/main/scala/chisel3/Element.scala b/core/src/main/scala/chisel3/Element.scala index 0c99ff70..40291b12 100644 --- a/core/src/main/scala/chisel3/Element.scala +++ b/core/src/main/scala/chisel3/Element.scala @@ -30,6 +30,10 @@ abstract class Element extends Data { case Some(litArg) => Some(ElementLitBinding(litArg)) case _ => Some(DontCareBinding()) } + case Some(VecLitBinding(litMap)) => litMap.get(this) match { + case Some(litArg) => Some(ElementLitBinding(litArg)) + case _ => Some(DontCareBinding()) + } case topBindingOpt => topBindingOpt } diff --git a/core/src/main/scala/chisel3/experimental/package.scala b/core/src/main/scala/chisel3/experimental/package.scala index 4dc7ba4b..e8360430 100644 --- a/core/src/main/scala/chisel3/experimental/package.scala +++ b/core/src/main/scala/chisel3/experimental/package.scala @@ -2,6 +2,8 @@ package chisel3 +import chisel3.internal.sourceinfo.SourceInfo + /** Package for experimental features, which may have their API changed, be removed, etc. * * Because its contents won't necessarily have the same level of stability and support as @@ -124,10 +126,39 @@ package object experimental { object BundleLiterals { implicit class AddBundleLiteralConstructor[T <: Record](x: T) { - def Lit(elems: (T => (Data, Data))*): T = { + def Lit(elems: (T => (Data, Data))*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { + x._makeLit(elems: _*) + } + } + } + + /** This class provides the `Lit` method needed to define a `Vec` literal + */ + object VecLiterals { + implicit class AddVecLiteralConstructor[T <: Data](x: Vec[T]) { + /** Given a generator of a list tuples of the form [Int, Data] + * constructs a Vec literal, parallel concept to `BundleLiteral` + * + * @param elems tuples of an index and a literal value + * @return + */ + def Lit(elems: (Int, T)*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[T] = { x._makeLit(elems: _*) } } + + implicit class AddObjectLiteralConstructor(x: Vec.type) { + /** This provides an literal construction method for cases using + * object `Vec` as in `Vec.Lit(1.U, 2.U)` + */ + def Lit[T <: Data](elems: T*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[T] = { + require(elems.nonEmpty, s"Lit.Vec(...) must have at least one element") + val indexElements = elems.zipWithIndex.map { case (element, index) => (index, element)} + val widestElement = elems.maxBy(_.getWidth) + val vec: Vec[T] = Vec.apply(indexElements.length, chiselTypeOf(widestElement)) + vec.Lit(indexElements:_*) + } + } } // Use to add a prefix to any component generated in input scope diff --git a/core/src/main/scala/chisel3/internal/Binding.scala b/core/src/main/scala/chisel3/internal/Binding.scala index 9e17aded..8a3c4330 100644 --- a/core/src/main/scala/chisel3/internal/Binding.scala +++ b/core/src/main/scala/chisel3/internal/Binding.scala @@ -6,6 +6,8 @@ import chisel3._ import chisel3.experimental.BaseModule import chisel3.internal.firrtl.LitArg +import scala.collection.immutable.ListMap + /** Requires that a node is hardware ("bound") */ object requireIsHardware { @@ -123,3 +125,5 @@ sealed trait LitBinding extends UnconstrainedBinding with ReadOnlyBinding case class ElementLitBinding(litArg: LitArg) extends LitBinding // Literal binding attached to the root of a Bundle, containing literal values of its children. case class BundleLitBinding(litMap: Map[Data, LitArg]) extends LitBinding +// Literal binding attached to the root of a Vec, containing literal values of its children. +case class VecLitBinding(litMap: ListMap[Data, LitArg]) extends LitBinding diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index 61f97ce6..81b4f7ab 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -91,6 +91,14 @@ abstract class LitArg(val num: BigInt, widthArg: Width) extends Arg { elem } + /** Provides a mechanism that LitArgs can have their width adjusted + * to match other members of a VecLiteral + * + * @param newWidth the new width for this + * @return + */ + def cloneWithWidth(newWidth: Width): this.type + protected def minWidth: Int if (forcedWidth) { require(widthArg.get >= minWidth, @@ -106,6 +114,10 @@ case class ULit(n: BigInt, w: Width) extends LitArg(n, w) { def name: String = "UInt" + width + "(\"h0" + num.toString(16) + "\")" def minWidth: Int = 1 max n.bitLength + def cloneWithWidth(newWidth: Width): this.type = { + ULit(n, newWidth).asInstanceOf[this.type] + } + require(n >= 0, s"UInt literal ${n} is negative") } @@ -115,6 +127,10 @@ case class SLit(n: BigInt, w: Width) extends LitArg(n, w) { s"asSInt(${ULit(unsigned, width).name})" } def minWidth: Int = 1 + n.bitLength + + def cloneWithWidth(newWidth: Width): this.type = { + SLit(n, newWidth).asInstanceOf[this.type] + } } case class FPLit(n: BigInt, w: Width, binaryPoint: BinaryPoint) extends LitArg(n, w) { @@ -123,6 +139,10 @@ case class FPLit(n: BigInt, w: Width, binaryPoint: BinaryPoint) extends LitArg(n s"asFixedPoint(${ULit(unsigned, width).name}, ${binaryPoint.asInstanceOf[KnownBinaryPoint].value})" } def minWidth: Int = 1 + n.bitLength + + def cloneWithWidth(newWidth: Width): this.type = { + FPLit(n, newWidth, binaryPoint).asInstanceOf[this.type] + } } case class IntervalLit(n: BigInt, w: Width, binaryPoint: BinaryPoint) extends LitArg(n, w) { @@ -135,6 +155,10 @@ case class IntervalLit(n: BigInt, w: Width, binaryPoint: BinaryPoint) extends Li IntervalRange.getBound(isClosed = true, BigDecimal(n)), IntervalRange.getRangeWidth(binaryPoint)) } def minWidth: Int = 1 + n.bitLength + + def cloneWithWidth(newWidth: Width): this.type = { + IntervalLit(n, newWidth, binaryPoint).asInstanceOf[this.type] + } } case class Ref(name: String) extends Arg diff --git a/docs/src/appendix/experimental-features.md b/docs/src/appendix/experimental-features.md index 363f7a06..4b1208aa 100644 --- a/docs/src/appendix/experimental-features.md +++ b/docs/src/appendix/experimental-features.md @@ -85,7 +85,87 @@ class Example3 extends RawModule { chisel3.stage.ChiselStage.emitVerilog(new Example3) ``` -Vec literals are not yet supported. +### Vec Literals + +Vec literals are very similar to Bundle literals and can be constructed via an experimental import. +They can be constructed in two forms, with type and length inferred as in: + +```scala mdoc +import chisel3._ +import chisel3.experimental.VecLiterals._ + +class VecExample1 extends Module { + val out = IO(Output(Vec(2, UInt(4.W)))) + out := Vec.Lit(0xa.U, 0xbb.U) +} +``` +```scala mdoc:verilog +chisel3.stage.ChiselStage.emitVerilog(new VecExample1) +``` + +or explicitly as in: + +```scala mdoc +import chisel3._ +import chisel3.experimental.VecLiterals._ + +class VecExample1a extends Module { + val out = IO(Output(Vec(2, UInt(4.W)))) + out := Vec(2, UInt(4.W)).Lit(0 -> 1.U, 1 -> 2.U) +} +``` + +```scala mdoc:verilog +chisel3.stage.ChiselStage.emitVerilog(new VecExample1a) +``` + +The following examples all use the explicit form. +With the explicit form partial specification is allowed. +When used with as a `Reg` `reset` value, only specified indices of the `Reg`'s `Vec` +will be reset + +```scala mdoc +class VecExample2 extends RawModule { + val out = IO(Output(Vec(4, UInt(4.W)))) + out := Vec(4, UInt(4.W)).Lit(0 -> 1.U, 3 -> 7.U) +} +``` + +```scala mdoc:verilog +chisel3.stage.ChiselStage.emitVerilog(new VecExample2) +``` + +Registers can be initialized from Vec literals + +```scala mdoc +class VecExample3 extends Module { + val out = IO(Output(Vec(4, UInt(8.W)))) + val y = RegInit( + Vec(4, UInt(8.W)).Lit(0 -> 0xAB.U(8.W), 1 -> 0xCD.U(8.W), 2 -> 0xEF.U(8.W), 3 -> 0xFF.U(8.W)) + ) + out := y +} +``` + +```scala mdoc:verilog +chisel3.stage.ChiselStage.emitVerilog(new VecExample3) +``` + +Vec literals can also be nested arbitrarily. + +```scala mdoc +class VecExample5 extends RawModule { + val out = IO(Output(Vec(2, new ChildBundle))) + out := Vec(2, new ChildBundle).Lit( + 0 -> (new ChildBundle).Lit(_.foo -> 42.U), + 1 -> (new ChildBundle).Lit(_.foo -> 7.U) + ) +} +``` + +```scala mdoc:verilog +chisel3.stage.ChiselStage.emitVerilog(new VecExample5) +``` ### Interval Type diff --git a/src/test/scala/chiselTests/BundleLiteralSpec.scala b/src/test/scala/chiselTests/BundleLiteralSpec.scala index 2a3ce2c9..b4adde4a 100644 --- a/src/test/scala/chiselTests/BundleLiteralSpec.scala +++ b/src/test/scala/chiselTests/BundleLiteralSpec.scala @@ -6,9 +6,8 @@ import chisel3._ import chisel3.stage.ChiselStage import chisel3.testers.BasicTester import chisel3.experimental.BundleLiterals._ -import chisel3.experimental.BundleLiteralException -import chisel3.experimental.ChiselEnum -import chisel3.experimental.FixedPoint +import chisel3.experimental.VecLiterals.AddVecLiteralConstructor +import chisel3.experimental.{BundleLiteralException, ChiselEnum, ChiselRange, FixedPoint, Interval} class BundleLiteralSpec extends ChiselFlatSpec with Utils { object MyEnum extends ChiselEnum { @@ -76,6 +75,24 @@ class BundleLiteralSpec extends ChiselFlatSpec with Utils { } } } + "bundle literals of vec literals" should "work" in { + assertTesterPasses(new BasicTester { + val range = range"[0,4].2" + val bundleWithVecs = new Bundle { + val a = Vec(2, UInt(4.W)) + val b = Vec(2, Interval(range)) + }.Lit( + _.a -> Vec(2, UInt(4.W)).Lit(0 -> 0xA.U, 1 -> 0xB.U), + _.b -> Vec(2, Interval(range)).Lit(0 -> (1.5).I(range), 1 -> (0.25).I(range)) + ) + chisel3.assert(bundleWithVecs.a(0) === 0xA.U) + chisel3.assert(bundleWithVecs.a(1) === 0xB.U) + chisel3.assert(bundleWithVecs.b(0) === (1.5).I(range)) + chisel3.assert(bundleWithVecs.b(1) === (0.25).I(range)) + stop() + }) + } + "partial bundle literals" should "work in RTL" in { assertTesterPasses{ new BasicTester{ val bundleLit = (new MyBundle).Lit(_.a -> 42.U) diff --git a/src/test/scala/chiselTests/ChiselSpec.scala b/src/test/scala/chiselTests/ChiselSpec.scala index 8df680d6..9503089a 100644 --- a/src/test/scala/chiselTests/ChiselSpec.scala +++ b/src/test/scala/chiselTests/ChiselSpec.scala @@ -2,22 +2,22 @@ package chiselTests -import org.scalatest._ -import org.scalatest.prop._ -import org.scalatest.flatspec.AnyFlatSpec -import org.scalacheck._ import chisel3._ -import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage} +import chisel3.aop.Aspect +import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage, NoRunFirrtlCompilerAnnotation, PrintFullStackTraceAnnotation} import chisel3.testers._ -import firrtl.{AnnotationSeq, CommonOptions, EmittedVerilogCircuitAnnotation, ExecutionOptionsManager, FirrtlExecutionFailure, FirrtlExecutionSuccess, HasFirrtlOptions} -import firrtl.annotations.{Annotation, DeletedAnnotation} +import firrtl.annotations.Annotation import firrtl.util.BackendCompilationUtilities +import firrtl.{AnnotationSeq, EmittedVerilogCircuitAnnotation} +import org.scalacheck._ +import org.scalatest._ +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.matchers.should._ +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks + import java.io.ByteArrayOutputStream import java.security.Permission - -import chisel3.aop.Aspect -import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage, NoRunFirrtlCompilerAnnotation, PrintFullStackTraceAnnotation} -import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks import scala.reflect.ClassTag /** Common utility functions for Chisel unit tests. */ @@ -90,6 +90,9 @@ trait ChiselRunners extends Assertions with BackendCompilationUtilities { /** Spec base class for BDD-style testers. */ abstract class ChiselFlatSpec extends AnyFlatSpec with ChiselRunners with Matchers +/** Spec base class for BDD-style testers. */ +abstract class ChiselFreeSpec extends AnyFreeSpec with ChiselRunners with Matchers + /** Spec base class for property-based testers. */ abstract class ChiselPropSpec extends PropSpec with ChiselRunners with ScalaCheckPropertyChecks with Matchers { diff --git a/src/test/scala/chiselTests/VecLiteralSpec.scala b/src/test/scala/chiselTests/VecLiteralSpec.scala new file mode 100644 index 00000000..d11289e1 --- /dev/null +++ b/src/test/scala/chiselTests/VecLiteralSpec.scala @@ -0,0 +1,526 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests + +import chisel3._ +import chisel3.experimental.BundleLiterals.AddBundleLiteralConstructor +import chisel3.experimental.VecLiterals._ +import chisel3.experimental.{ChiselEnum, FixedPoint, VecLiteralException} +import chisel3.stage.ChiselStage +import chisel3.testers.BasicTester +import chisel3.util.Counter +import scala.language.reflectiveCalls + +class VecLiteralSpec extends ChiselFreeSpec with Utils { + object MyEnum extends ChiselEnum { + val sA, sB, sC = Value + } + object MyEnumB extends ChiselEnum { + val sA, sB = Value + } + + "Vec literals should work with chisel Enums" in { + val enumVec = Vec(3, MyEnum()).Lit(0 -> MyEnum.sA, 1 -> MyEnum.sB, 2-> MyEnum.sC) + enumVec(0).toString should include (MyEnum.sA.toString) + enumVec(1).toString should include (MyEnum.sB.toString) + enumVec(2).toString should include (MyEnum.sC.toString) + } + + "improperly constructed vec literals should be detected" - { + "indices in vec literal muse be greater than zero and less than length" in { + val e = intercept[VecLiteralException] { + Vec(2, UInt(4.W)).Lit(0 -> 1.U, 1 -> 2.U, 2 -> 3.U, 3 -> 4.U, -2 -> 7.U) + } + e.getMessage should include ( + "VecLiteral: The following indices (2,3,-2) are less than zero or greater or equal to than Vec length" + ) + } + + "indices in vec literals must not be repeated" in { + val e = intercept[VecLiteralException] { + Vec(2, UInt(4.W)).Lit(0 -> 1.U, 1 -> 2.U, 2 -> 3.U, 2 -> 3.U, 2 -> 3.U, 3 -> 4.U) + } + e.getMessage should include("VecLiteral: has duplicated indices 2(3 times)") + } + "lits must fit in vec element width" in { + val e = intercept[VecLiteralException] { + Vec(2, SInt(4.W)).Lit(0 -> 0xab.S, 1 -> 0xbc.S) + } + e.getMessage should include( + "VecLiteral: Vec[SInt<4>] has the following incorrectly typed or sized initializers: " + + "0 -> SInt<9>(171),1 -> SInt<9>(188)" + ) + } + + "all lits must be the same type but width can be equal or smaller than the Vec's element width" in { + val v = Vec(2, SInt(4.W)).Lit(0 -> 1.S, 1 -> -2.S) + v(0).toString should include(1.S(4.W).toString) + v(1).toString should include((-2).S(4.W).toString) + v.toString should include ("SInt<4>[2](0=SLit(1,<4>), 1=SLit(-2,<4>)") + } + + "all lits must be the same type but width cannot be greater than Vec's element width" in { + val e = intercept[VecLiteralException] { + val v = Vec(2, SInt(4.W)).Lit(0 -> 11.S, 1 -> -0xffff.S) + } + e.getMessage should include( + "VecLiteral: Vec[SInt<4>] has the following incorrectly typed or sized initializers: 0 -> SInt<5>(11),1 -> SInt<17>(-65535)" + ) + } + } + + //NOTE: I had problems where this would not work if this class declaration was inside test scope + class HasVecInit extends Module { + val initValue = Vec(4, UInt(8.W)).Lit(0 -> 0xAB.U(8.W), 1 -> 0xCD.U(8.W), 2 -> 0xEF.U(8.W), 3 -> 0xFF.U(8.W)) + val y = RegInit(initValue) + } + + "Vec literals should work when used to initialize a reg of vec" in { + val firrtl = (new ChiselStage).emitFirrtl(new HasVecInit, args = Array("--full-stacktrace")) + firrtl should include("""_y_WIRE[0] <= UInt<8>("hab")""") + firrtl should include("""_y_WIRE[1] <= UInt<8>("hcd")""") + firrtl should include("""_y_WIRE[2] <= UInt<8>("hef")""") + firrtl should include("""_y_WIRE[3] <= UInt<8>("hff")""") + firrtl should include(""" reset => (reset, _y_WIRE)""".stripMargin) + } + + //NOTE: I had problems where this would not work if this class declaration was inside test scope + class HasPartialVecInit extends Module { + val initValue = Vec(4, UInt(8.W)).Lit(0 -> 0xAB.U(8.W), 2 -> 0xEF.U(8.W), 3 -> 0xFF.U(8.W)) + val y = RegInit(initValue) + } + + "Vec literals should work when used to partially initialize a reg of vec" in { + val firrtl = (new ChiselStage).emitFirrtl(new HasPartialVecInit, args = Array("--full-stacktrace")) + firrtl should include("""_y_WIRE[0] <= UInt<8>("hab")""") + firrtl should include("""_y_WIRE[1] is invalid""") + firrtl should include("""_y_WIRE[2] <= UInt<8>("hef")""") + firrtl should include("""_y_WIRE[3] <= UInt<8>("hff")""") + firrtl should include(""" reset => (reset, _y_WIRE)""".stripMargin) + } + + class ResetRegWithPartialVecLiteral extends Module { + val in = IO(Input(Vec(4, UInt(8.W)))) + val out = IO(Output(Vec(4, UInt(8.W)))) + val initValue = Vec(4, UInt(8.W)).Lit(0 -> 0xAB.U(8.W), 2 -> 0xEF.U(8.W), 3 -> 0xFF.U(8.W)) + val y = RegInit(initValue) + when(in(1) > 0.U) { + y(1) := in(1) + } + when(in(2) > 0.U) { + y(2) := in(2) + } + out := y + } + + "Vec literals should only init specified fields when used to partially initialize a reg of vec" in { + println(ChiselStage.emitFirrtl(new ResetRegWithPartialVecLiteral)) + assertTesterPasses(new BasicTester { + val m = Module(new ResetRegWithPartialVecLiteral) + val (counter, wrapped) = Counter(true.B, 8) + m.in := DontCare + when(counter < 2.U) { + m.in(1) := 0xff.U + m.in(2) := 0xff.U + }.elsewhen(counter === 2.U) { + chisel3.assert(m.out(1) === 0xff.U) + chisel3.assert(m.out(2) === 0xff.U) + }.elsewhen(counter === 3.U) { + m.in(1) := 0.U + m.in(2) := 0.U + m.reset := true.B + }.elsewhen(counter > 2.U) { + // m.out(1) should not be reset, m.out(2) should be reset + chisel3.assert(m.out(1) === 0xff.U) + chisel3.assert(m.out(2) === 0xEF.U) + } + when(wrapped) { + stop() + } + }) + } + + "lowest of vec literal contains least significant bits and " in { + val y = Vec(4, UInt(8.W)).Lit(0 -> 0xAB.U(8.W), 1 -> 0xCD.U(8.W), 2 -> 0xEF.U(8.W), 3 -> 0xFF.U(8.W)) + y.litValue() should be(BigInt("FFEFCDAB", 16)) + } + + "the order lits are specified does not matter" in { + val y = Vec(4, UInt(8.W)).Lit(3 -> 0xFF.U(8.W), 2 -> 0xEF.U(8.W), 1 -> 0xCD.U(8.W), 0 -> 0xAB.U(8.W)) + y.litValue() should be(BigInt("FFEFCDAB", 16)) + } + + "regardless of the literals widths, packing should be done based on the width of the Vec's gen" in { + val z = Vec(4, UInt(8.W)).Lit(0 -> 0x2.U, 1 -> 0x2.U, 2 -> 0x2.U, 3 -> 0x3.U) + z.litValue() should be(BigInt("03020202", 16)) + } + + "packing sparse vec lits should not pack, litOption returns None" in { + // missing sub-listeral for index 2 + val z = Vec(4, UInt(8.W)).Lit(0 -> 0x2.U, 1 -> 0x2.U, 3 -> 0x3.U) + + z.litOption should be(None) + } + + "registers can be initialized with a Vec literal" in { + assertTesterPasses(new BasicTester { + val y = RegInit(Vec(4, UInt(8.W)).Lit(0 -> 0xAB.U(8.W), 1 -> 0xCD.U(8.W), 2 -> 0xEF.U(8.W), 3 -> 0xFF.U(8.W))) + chisel3.assert(y.asUInt === BigInt("FFEFCDAB", 16).U) + stop() + }) + } + + "how does asUInt work" in { + assertTesterPasses(new BasicTester { + val vec1 = Vec(4, UInt(16.W)).Lit(0 -> 0xDD.U, 1 -> 0xCC.U, 2 -> 0xBB.U, 3 -> 0xAA.U) + + val vec2 = VecInit(Seq(0xDD.U, 0xCC.U, 0xBB.U, 0xAA.U)) + printf("vec1 %x\n", vec1.asUInt()) + printf("vec2 %x\n", vec2.asUInt()) + stop() + }) + } + + "Vec literals uint conversion" in { + class M1 extends Module { + val out1 = IO(Output(UInt(64.W))) + val out2 = IO(Output(UInt(64.W))) + + val v1 = Vec(4, UInt(16.W)).Lit(0 -> 0xDD.U, 1 -> 0xCC.U, 2 -> 0xBB.U, 3 -> 0xAA.U) + out1 := v1.asUInt + + val v2 = VecInit(0xDD.U(16.W), 0xCC.U, 0xBB.U, 0xAA.U) + out2 := v2.asUInt + } + + assertTesterPasses(new BasicTester { + val m = Module(new M1) + chisel3.assert(m.out1 === m.out2) + stop() + }) + } + + "VecLits should work properly with .asUInt" in { + val outsideVecLit = Vec(4, UInt(16.W)).Lit(0 -> 0xDD.U, 1 -> 0xCC.U, 2 -> 0xBB.U, 3 -> 0xAA.U) + + assertTesterPasses { + new BasicTester { + chisel3.assert(outsideVecLit(0) === 0xDD.U, s"v(0)") + stop() + } + } + } + + "bundle literals should work in RTL" in { + val outsideVecLit = Vec(4, UInt(16.W)).Lit(0 -> 0xDD.U, 1 -> 0xCC.U, 2 -> 0xBB.U, 3 -> 0xAA.U) + + assertTesterPasses { + new BasicTester { + chisel3.assert(outsideVecLit(0) === 0xDD.U, s"v(0)") + chisel3.assert(outsideVecLit(1) === 0xCC.U) + chisel3.assert(outsideVecLit(2) === 0xBB.U) + chisel3.assert(outsideVecLit(3) === 0xAA.U) + + chisel3.assert(outsideVecLit.litValue().U === outsideVecLit.asUInt()) + + val insideVecLit = Vec(4, UInt(16.W)).Lit(0 -> 0xDD.U, 1 -> 0xCC.U, 2 -> 0xBB.U, 3 -> 0xAA.U) + chisel3.assert(insideVecLit(0) === 0xDD.U) + chisel3.assert(insideVecLit(1) === 0xCC.U) + chisel3.assert(insideVecLit(2) === 0xBB.U) + chisel3.assert(insideVecLit(3) === 0xAA.U) + + chisel3.assert(insideVecLit(0) === outsideVecLit(0)) + chisel3.assert(insideVecLit(1) === outsideVecLit(1)) + chisel3.assert(insideVecLit(2) === outsideVecLit(2)) + chisel3.assert(insideVecLit(3) === outsideVecLit(3)) + + val vecWire1 = Wire(Vec(4, UInt(16.W))) + vecWire1 := outsideVecLit + + chisel3.assert(vecWire1(0) === 0xDD.U) + chisel3.assert(vecWire1(1) === 0xCC.U) + chisel3.assert(vecWire1(2) === 0xBB.U) + chisel3.assert(vecWire1(3) === 0xAA.U) + + val vecWire2 = Wire(Vec(4, UInt(16.W))) + vecWire2 := insideVecLit + + chisel3.assert(vecWire2(0) === 0xDD.U) + chisel3.assert(vecWire2(1) === 0xCC.U) + chisel3.assert(vecWire2(2) === 0xBB.U) + chisel3.assert(vecWire2(3) === 0xAA.U) + + stop() + } + } + } + + "partial vec literals should work in RTL" in { + assertTesterPasses{ new BasicTester{ + val vecLit = Vec(4, UInt(8.W)).Lit(0 -> 42.U, 2 -> 5.U) + chisel3.assert(vecLit(0) === 42.U) + chisel3.assert(vecLit(2) === 5.U) + + val vecWire = Wire(Vec(4, UInt(8.W))) + vecWire := vecLit + + chisel3.assert(vecWire(0) === 42.U) + chisel3.assert(vecWire(2) === 5.U) + + stop() + }} + } + + "nested vec literals should be constructable" in { + val outerVec = Vec(2, Vec(3, UInt(4.W))).Lit( + 0 -> Vec(3, UInt(4.W)).Lit(0 -> 1.U, 1 -> 2.U, 2 -> 3.U), + 1 -> Vec(3, UInt(4.W)).Lit(0 -> 4.U, 1 -> 5.U, 2 -> 6.U) + ) + + outerVec.litValue() should be (BigInt("654321", 16)) + outerVec(0).litValue() should be (BigInt("321", 16)) + outerVec(1).litValue() should be (BigInt("654", 16)) + outerVec(0)(0).litValue() should be (BigInt(1)) + outerVec(0)(1).litValue() should be (BigInt(2)) + outerVec(0)(2).litValue() should be (BigInt(3)) + outerVec(1)(0).litValue() should be (BigInt(4)) + outerVec(1)(1).litValue() should be (BigInt(5)) + outerVec(1)(2).litValue() should be (BigInt(6)) + } + + "contained vecs should work" in { + assertTesterPasses{ new BasicTester { + val outerVec = Vec(2, Vec(3, UInt(4.W))).Lit( + 0 -> Vec(3, UInt(4.W)).Lit(0 -> 1.U, 1 -> 2.U, 2 -> 3.U), + 1 -> Vec(3, UInt(4.W)).Lit(0 -> 4.U, 1 -> 5.U, 2 -> 6.U) + ) + + chisel3.assert(outerVec(0)(0) === 1.U) + chisel3.assert(outerVec(0)(1) === 2.U) + chisel3.assert(outerVec(0)(2) === 3.U) + chisel3.assert(outerVec(1)(0) === 4.U) + chisel3.assert(outerVec(1)(1) === 5.U) + chisel3.assert(outerVec(1)(2) === 6.U) + + val v0 = outerVec(0) + val v1 = outerVec(1) + chisel3.assert(v0(0) === 1.U) + chisel3.assert(v0(1) === 2.U) + chisel3.assert(v0(2) === 3.U) + chisel3.assert(v1(0) === 4.U) + chisel3.assert(v1(1) === 5.U) + chisel3.assert(v1(2) === 6.U) + + stop() + }} + } + + //TODO: decide what behavior here should be + "This doesn't work should it" ignore { + assertTesterPasses { + new BasicTester { + def vecFactory = Vec(2, FixedPoint(8.W, 4.BP)) + + val vecWire1 = Wire(Output(vecFactory)) + val vecLit1 = vecFactory.Lit(0 -> (1.5).F(8.W, 4.BP)) + val vecLit2 = vecFactory.Lit(1 -> (3.25).F(8.W, 4.BP)) + + vecWire1 := vecLit1 + vecWire1 := vecLit2 + printf("vw1(0) %x vw1(1) %x\n", vecWire1(0).asUInt(), vecWire1(1).asUInt()) + chisel3.assert(vecWire1(0) === (1.5).F(8.W, 4.BP)) + chisel3.assert(vecWire1(1) === (3.25).F(8.W, 4.BP)) + stop() + } + } + } + + "partially initialized Vec literals should assign" in { + assertTesterPasses { + new BasicTester { + def vecFactory = Vec(2, FixedPoint(8.W, 4.BP)) + + val vecWire1 = Wire(Output(vecFactory)) + val vecWire2 = Wire(Output(vecFactory)) + val vecLit1 = vecFactory.Lit(0 -> (1.5).F(8.W, 4.BP)) + val vecLit2 = vecFactory.Lit(1 -> (3.25).F(8.W, 4.BP)) + + vecWire1 := vecLit1 + vecWire2 := vecLit2 + vecWire1(1) := (0.5).F(8.W, 4.BP) + printf("vw1(0) %x vw1(1) %x\n", vecWire1(0).asUInt(), vecWire1(1).asUInt()) + chisel3.assert(vecWire1(0) === (1.5).F(8.W, 4.BP)) + chisel3.assert(vecWire1(1) === (0.5).F(8.W, 4.BP)) // Last connect won + chisel3.assert(vecWire2(1) === (3.25).F(8.W, 4.BP)) + stop() + } + } + } + + "Vec literals should work as register reset values" in { + assertTesterPasses { + new BasicTester { + val r = RegInit(Vec(3, UInt(11.W)).Lit(0 -> 0xA.U, 1 -> 0xB.U, 2 -> 0xC.U)) + r := (r.asUInt + 1.U).asTypeOf(Vec(3, UInt(11.W))) // prevent constprop + + // check reset values on first cycle out of reset + chisel3.assert(r(0) === 0xA.U) + chisel3.assert(r(1) === 0xB.U) + chisel3.assert(r(2) === 0xC.U) + stop() + } + } + } + + "partially initialized Vec literals should work as register reset values" in { + assertTesterPasses { + new BasicTester { + val r = RegInit(Vec(3, UInt(11.W)).Lit(0 -> 0xA.U, 2 -> 0xC.U)) + r := (r.asUInt + 1.U).asTypeOf(Vec(3, UInt(11.W))) // prevent constprop + // check reset values on first cycle out of reset + chisel3.assert(r(0) === 0xA.U) + chisel3.assert(r(2) === 0xC.U) + stop() + } + } + } + + "Fields extracted from Vec Literals should work as register reset values" in { + assertTesterPasses { + new BasicTester { + val r = RegInit(Vec(3, UInt(11.W)).Lit(0 -> 0xA.U, 2 -> 0xC.U).apply(0)) + r := r + 1.U // prevent const prop + chisel3.assert(r === 0xA.U) // coming out of reset + stop() + } + } + } + + "DontCare fields extracted from Vec Literals should work as register reset values" in { + assertTesterPasses { + new BasicTester { + val r = RegInit(Vec(3, Bool()).Lit(0 -> true.B).apply(2)) + r := reset.asBool + printf(p"r = $r\n") // Can't assert because reset value is DontCare + stop() + } + } + } + + "DontCare fields extracted from Vec Literals should work in other Expressions" in { + assertTesterPasses { + new BasicTester { + val x = Vec(3, Bool()).Lit(0 -> true.B).apply(2) || true.B + chisel3.assert(x === true.B) + stop() + } + } + } + + "vec literals with non-literal values should fail" in { + val exc = intercept[VecLiteralException] { + extractCause[VecLiteralException] { + ChiselStage.elaborate { + new RawModule { + (Vec(3, UInt(11.W)).Lit(0 -> UInt())) + } + } + } + } + exc.getMessage should include("field 0 specified with non-literal value UInt") + } + + "vec literals are instantiated on connect" in { + class VecExample5 extends RawModule { + val out = IO(Output(Vec(2, UInt(4.W)))) + val bundle = Vec(2, UInt(4.W)).Lit( + 0 -> 0xa.U, + 1 -> 0xb.U + ) + out := bundle + } + + val firrtl = (new chisel3.stage.ChiselStage).emitFirrtl(new VecExample5, args = Array("--full-stacktrace")) + firrtl should include("""out[0] <= UInt<4>("ha")""") + firrtl should include("""out[1] <= UInt<4>("hb")""") + } + + class SubBundle extends Bundle { + val foo = UInt(8.W) + val bar = UInt(4.W) + } + + class VecExample extends RawModule { + val out = IO(Output(Vec(2, new SubBundle))) + val bundle = Vec(2, new SubBundle).Lit( + 0 -> (new SubBundle).Lit(_.foo -> 42.U, _.bar -> 22.U), + 1 -> (new SubBundle).Lit(_.foo -> 7.U, _.bar -> 3.U) + ) + out := bundle + } + + "vec literals can contain bundles" in { + val chirrtl = (new chisel3.stage.ChiselStage).emitChirrtl(new VecExample, args = Array("--full-stacktrace")) + chirrtl should include("""out[0].bar <= UInt<5>("h016")""") + chirrtl should include("""out[0].foo <= UInt<6>("h02a")""") + chirrtl should include("""out[1].bar <= UInt<2>("h03")""") + chirrtl should include("""out[1].foo <= UInt<3>("h07")""") + + } + + "vec literals can have bundle children" in { + val vec = Vec(2, new SubBundle).Lit( + 0 -> (new SubBundle).Lit(_.foo -> 0xab.U, _.bar -> 0xc.U), + 1 -> (new SubBundle).Lit(_.foo -> 0xde.U, _.bar -> 0xf.U) + ) + vec.litValue().toString(16) should be("defabc") + } + + "vec literals can have bundle children assembled incrementally" in { + val bundle1 = (new SubBundle).Lit(_.foo -> 0xab.U, _.bar -> 0xc.U) + val bundle2 = (new SubBundle).Lit(_.foo -> 0xde.U, _.bar -> 0xf.U) + + bundle1.litValue().toString(16) should be("abc") + bundle2.litValue().toString(16) should be("def") + + val vec = Vec(2, new SubBundle).Lit(0 -> bundle1, 1 -> bundle2) + + vec.litValue().toString(16) should be("defabc") + } + + "bundles can contain vec lits" in { + val vec1 = Vec(3, UInt(4.W)).Lit(0 -> 0xa.U, 1 -> 0xb.U, 2 -> 0xc.U) + val vec2 = Vec(2, UInt(4.W)).Lit(0 -> 0xd.U, 1 -> 0xe.U) + val bundle = (new Bundle { + val foo = Vec(3, UInt(4.W)) + val bar = Vec(2, UInt(4.W)) + }).Lit(_.foo -> vec1, _.bar -> vec2) + bundle.litValue().toString(16) should be("cbaed") + } + + "bundles can contain vec lits in-line" in { + val bundle = (new Bundle { + val foo = Vec(3, UInt(4.W)) + val bar = Vec(2, UInt(4.W)) + }).Lit( + _.foo -> Vec(3, UInt(4.W)).Lit(0 -> 0xa.U, 1 -> 0xb.U, 2 -> 0xc.U), + _.bar -> Vec(2, UInt(4.W)).Lit(0 -> 0xd.U, 1 -> 0xe.U) + ) + bundle.litValue().toString(16) should be("cbaed") + } + + "Vec.Lit is a trivial Vec literal factory" in { + val vec = Vec.Lit(0xa.U, 0xb.U) + vec(0).litValue() should be(0xa) + vec(1).litValue() should be(0xb) + } + + "Vec.Lit bases it's element width on the widest literal supplied" in { + val vec = Vec.Lit(0xa.U, 0xbbbb.U) + vec(0).litValue() should be(0xa) + vec(1).litValue() should be(0xbbbb) + vec.length should be(2) + vec.getWidth should be(16 * 2) + vec.litValue() should be(BigInt("bbbb000a", 16)) + } +} -- cgit v1.2.3 From 9fdea534f83578f745ec22cc7e530105f9fd67f7 Mon Sep 17 00:00:00 2001 From: Megan Wachs Date: Wed, 28 Apr 2021 10:57:13 -0700 Subject: Cookbook: clean up desiredName example (#1886) * Cookbook: clean up desiredName example * Update cookbook.md--- docs/src/cookbooks/cookbook.md | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/docs/src/cookbooks/cookbook.md b/docs/src/cookbooks/cookbook.md index 290a6c82..1b47ad14 100644 --- a/docs/src/cookbooks/cookbook.md +++ b/docs/src/cookbooks/cookbook.md @@ -447,16 +447,13 @@ class Salt extends Module { } ``` -Elaborating the Chisel module `Salt` yields our "desire name" for `Salt` and `Coffee` in the output Verilog: -```scala mdoc:passthrough -import chisel3.stage.{ChiselStage, ChiselGeneratorAnnotation} -import firrtl.annotations.DeletedAnnotation -import firrtl.EmittedVerilogCircuitAnnotation - -(new ChiselStage) - .execute(Array("-X", "verilog"), Seq(ChiselGeneratorAnnotation(() => new Salt))) - .collectFirst{ case DeletedAnnotation(_, a: EmittedVerilogCircuitAnnotation) => a.value.value } - .foreach(a => println(s"""|```verilog - |$a - |```""".stripMargin)) +Elaborating the Chisel module `Salt` yields our "desired names" for `Salt` and `Coffee` in the output Verilog: +```scala mdoc:silent +import chisel3.stage.ChiselStage + +ChiselStage.emitVerilog(new Salt) +``` + +```scala mdoc:verilog +ChiselStage.emitVerilog(new Salt) ``` -- cgit v1.2.3 From 953eb3e0f246c081c6ce86f4b2ac80a4cc059724 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Thu, 29 Apr 2021 06:38:40 +0200 Subject: Update sbt-scoverage to 1.7.0 (#1887) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 482c2201..4ecf2ba4 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -4,7 +4,7 @@ resolvers += Classpaths.sbtPluginReleases resolvers += "jgit-repo" at "https://download.eclipse.org/jgit/maven" -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.7.0") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.0") -- cgit v1.2.3 From 8be86e461b9d9a6198c8153b3badda568af8be61 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Thu, 29 Apr 2021 06:59:24 +0200 Subject: Update scopt to 4.0.1 (#1815) Co-authored-by: Jiuyang Liu Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- build.sbt | 2 +- build.sc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 5c051ff5..a9e18216 100644 --- a/build.sbt +++ b/build.sbt @@ -61,7 +61,7 @@ lazy val chiselSettings = Seq ( libraryDependencies ++= Seq( "org.scalatest" %% "scalatest" % "3.1.2" % "test", "org.scalatestplus" %% "scalacheck-1-14" % "3.1.1.1" % "test", - "com.github.scopt" %% "scopt" % "3.7.1" + "com.github.scopt" %% "scopt" % "4.0.1" ), ) ++ ( // Tests from other projects may still run concurrently diff --git a/build.sc b/build.sc index 78b9307e..80a96fa0 100644 --- a/build.sc +++ b/build.sc @@ -107,7 +107,7 @@ class chisel3CrossModule(val crossScalaVersion: String) extends CommonModule wit override def ivyDeps = m.ivyDeps() ++ Agg( ivy"org.scalatest::scalatest:3.1.2", ivy"org.scalatestplus::scalacheck-1-14:3.1.1.1", - ivy"com.github.scopt::scopt:3.7.1" + ivy"com.github.scopt::scopt:4.0.1" ) ++ m.treadleIvyDeps override def moduleDeps = super.moduleDeps ++ treadleModule -- cgit v1.2.3 From 45960e956194fdb91aeb75cfb61846ddab08e997 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Thu, 29 Apr 2021 07:22:44 +0200 Subject: Update sbt-scalafix to 0.9.27 (#1842) Co-authored-by: Jiuyang Liu Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 4ecf2ba4..d6f8fae0 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -25,4 +25,4 @@ addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.8.1") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.5") // From FIRRTL for building from source -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.19") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.27") -- cgit v1.2.3 From 441812548b1ccb9d9d73aec5bc759fa19b9e92a7 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Thu, 29 Apr 2021 07:46:48 +0200 Subject: Update sbt-mdoc to 2.2.20 (#1870) Co-authored-by: Jiuyang Liu Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index d6f8fae0..6ba47bfd 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -16,7 +16,7 @@ addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.15") addSbtPlugin("com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "3.0.0") -addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.16" ) +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.20" ) addSbtPlugin("com.eed3si9n" % "sbt-sriracha" % "0.1.0") -- cgit v1.2.3 From 992a0a69e7a064abfeef737fcfff22cb96ec8b65 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Thu, 29 Apr 2021 08:05:19 +0200 Subject: Update sbt-ci-release to 1.5.7 (#1832) Co-authored-by: Jiuyang Liu Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 6ba47bfd..9ad3d988 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -22,7 +22,7 @@ addSbtPlugin("com.eed3si9n" % "sbt-sriracha" % "0.1.0") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.8.1") -addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.5") +addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") // From FIRRTL for building from source addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.27") -- cgit v1.2.3 From 4d8fed00225d15221cf32177ea9147b20d0b91f7 Mon Sep 17 00:00:00 2001 From: Kevin Laeufer Date: Thu, 29 Apr 2021 11:52:20 -0700 Subject: verification: guard statements with module reset (#1891) --- .../experimental/verification/package.scala | 24 ++++++++++++---------- .../verification/VerificationSpec.scala | 11 ++++++++-- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/core/src/main/scala/chisel3/experimental/verification/package.scala b/core/src/main/scala/chisel3/experimental/verification/package.scala index 5c71bd5f..816299a3 100644 --- a/core/src/main/scala/chisel3/experimental/verification/package.scala +++ b/core/src/main/scala/chisel3/experimental/verification/package.scala @@ -2,9 +2,8 @@ package chisel3.experimental -import chisel3.{Bool, CompileOptions} +import chisel3._ import chisel3.internal.Builder -import chisel3.internal.Builder.pushCommand import chisel3.internal.firrtl.{Formal, Verification} import chisel3.internal.sourceinfo.SourceInfo @@ -13,9 +12,10 @@ package object verification { def apply(predicate: Bool, msg: String = "")( implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Unit = { - val clock = Builder.forcedClock - pushCommand(Verification(Formal.Assert, sourceInfo, clock.ref, - predicate.ref, msg)) + when (!Module.reset.asBool) { + val clock = Module.clock + Builder.pushCommand(Verification(Formal.Assert, sourceInfo, clock.ref, predicate.ref, msg)) + } } } @@ -23,9 +23,10 @@ package object verification { def apply(predicate: Bool, msg: String = "")( implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Unit = { - val clock = Builder.forcedClock - pushCommand(Verification(Formal.Assume, sourceInfo, clock.ref, - predicate.ref, msg)) + when (!Module.reset.asBool) { + val clock = Module.clock + Builder.pushCommand(Verification(Formal.Assume, sourceInfo, clock.ref, predicate.ref, msg)) + } } } @@ -33,9 +34,10 @@ package object verification { def apply(predicate: Bool, msg: String = "")( implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Unit = { - val clock = Builder.forcedClock - pushCommand(Verification(Formal.Cover, sourceInfo, clock.ref, - predicate.ref, msg)) + val clock = Module.clock + when (!Module.reset.asBool) { + Builder.pushCommand(Verification(Formal.Cover, sourceInfo, clock.ref, predicate.ref, msg)) + } } } } diff --git a/src/test/scala/chiselTests/experimental/verification/VerificationSpec.scala b/src/test/scala/chiselTests/experimental/verification/VerificationSpec.scala index fe642156..86d6418c 100644 --- a/src/test/scala/chiselTests/experimental/verification/VerificationSpec.scala +++ b/src/test/scala/chiselTests/experimental/verification/VerificationSpec.scala @@ -30,8 +30,15 @@ class VerificationSpec extends ChiselPropSpec { property("basic equality check should work") { val fir = ChiselStage.emitChirrtl(new VerificationModule) val lines = fir.split("\n").map(_.trim) + + // reset guard around the verification statement + assertContains(lines, "when _T_2 : @[VerificationSpec.scala 16:15]") assertContains(lines, "cover(clock, _T, UInt<1>(\"h1\"), \"\") @[VerificationSpec.scala 16:15]") - assertContains(lines, "assume(clock, _T_2, UInt<1>(\"h1\"), \"\") @[VerificationSpec.scala 18:18]") - assertContains(lines, "assert(clock, _T_3, UInt<1>(\"h1\"), \"\") @[VerificationSpec.scala 19:18]") + + assertContains(lines, "when _T_6 : @[VerificationSpec.scala 18:18]") + assertContains(lines, "assume(clock, _T_4, UInt<1>(\"h1\"), \"\") @[VerificationSpec.scala 18:18]") + + assertContains(lines, "when _T_9 : @[VerificationSpec.scala 19:18]") + assertContains(lines, "assert(clock, _T_7, UInt<1>(\"h1\"), \"\") @[VerificationSpec.scala 19:18]") } } -- cgit v1.2.3 From c5861176887bfa529277e686df09a42aeceb6cd7 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 29 Apr 2021 16:18:06 -0700 Subject: Scala 2.13 support (#1751) --- .github/workflows/test.yml | 2 +- build.sbt | 31 ++- .../scala/collection/immutable/package.scala | 13 ++ core/src/main/scala/chisel3/Aggregate.scala | 12 +- core/src/main/scala/chisel3/Bits.scala | 1 + core/src/main/scala/chisel3/Data.scala | 45 +++-- core/src/main/scala/chisel3/Module.scala | 4 +- core/src/main/scala/chisel3/Num.scala | 2 +- core/src/main/scala/chisel3/RawModule.scala | 1 - core/src/main/scala/chisel3/internal/Binding.scala | 4 +- core/src/main/scala/chisel3/internal/Builder.scala | 2 +- core/src/main/scala/chisel3/internal/Error.scala | 6 +- core/src/main/scala/chisel3/internal/Namer.scala | 14 +- core/src/main/scala/chisel3/package.scala | 3 - .../chisel3/internal/plugin/BundleComponent.scala | 136 ------------- .../chisel3/internal/plugin/ChiselComponent.scala | 215 --------------------- .../chisel3/internal/plugin/ChiselPlugin.scala | 39 ---- .../chisel3/internal/plugin/BundleComponent.scala | 136 +++++++++++++ .../chisel3/internal/plugin/ChiselComponent.scala | 215 +++++++++++++++++++++ .../chisel3/internal/plugin/ChiselPlugin.scala | 39 ++++ src/main/scala/chisel3/aop/Select.scala | 8 +- .../chisel3/aop/injecting/InjectingAspect.scala | 2 +- src/main/scala/chisel3/compatibility.scala | 2 +- src/main/scala/chisel3/util/MixedVec.scala | 4 + src/test/scala/chiselTests/ChiselSpec.scala | 2 +- src/test/scala/chiselTests/ExtModule.scala | 16 +- src/test/scala/examples/VendingMachineUtils.scala | 2 +- 27 files changed, 495 insertions(+), 461 deletions(-) create mode 100644 core/src/main/scala-2.12/scala/collection/immutable/package.scala delete mode 100644 plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala delete mode 100644 plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala delete mode 100644 plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala create mode 100644 plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala create mode 100644 plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala create mode 100644 plugin/src/main/scala/chisel3/internal/plugin/ChiselPlugin.scala diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6d9fe475..5d321bfa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - scala: [2.12.13] + scala: [2.13.5, 2.12.13] container: image: ucbbar/chisel3-tools options: --user github --entrypoint /bin/bash diff --git a/build.sbt b/build.sbt index a9e18216..60b83a3e 100644 --- a/build.sbt +++ b/build.sbt @@ -15,12 +15,23 @@ lazy val commonSettings = Seq ( organization := "edu.berkeley.cs", version := "3.5-SNAPSHOT", autoAPIMappings := true, - scalaVersion := "2.12.13", - crossScalaVersions := Seq("2.12.13"), - scalacOptions := Seq("-deprecation", "-feature", - ), + scalaVersion := "2.13.5", + crossScalaVersions := Seq("2.13.5", "2.12.13"), + scalacOptions := Seq("-deprecation", "-feature"), libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value, - addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full), + // Macros paradise is integrated into 2.13 but requires a scalacOption + scalacOptions ++= { + CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, n)) if n >= 13 => "-Ymacro-annotations" :: Nil + case _ => Nil + } + }, + libraryDependencies ++= { + CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, n)) if n >= 13 => Nil + case _ => compilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full) :: Nil + } + } ) lazy val publishSettings = Seq ( @@ -93,6 +104,12 @@ lazy val pluginScalaVersions = Seq( "2.12.11", "2.12.12", "2.12.13", + "2.13.0", + "2.13.1", + "2.13.2", + "2.13.3", + "2.13.4", + "2.13.5" ) lazy val plugin = (project in file("plugin")). @@ -108,9 +125,7 @@ lazy val plugin = (project in file("plugin")). crossTarget := { // workaround for https://github.com/sbt/sbt/issues/5097 target.value / s"scala-${scalaVersion.value}" - }, - // Only publish for Scala 2.12 - publish / skip := !scalaVersion.value.startsWith("2.12") + } ). settings( mimaPreviousArtifacts := { diff --git a/core/src/main/scala-2.12/scala/collection/immutable/package.scala b/core/src/main/scala-2.12/scala/collection/immutable/package.scala new file mode 100644 index 00000000..7ae87d9b --- /dev/null +++ b/core/src/main/scala-2.12/scala/collection/immutable/package.scala @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 + +package scala.collection + +import scala.collection.immutable.ListMap + +package object immutable { + val SeqMap = ListMap + type SeqMap[K, +V] = ListMap[K, V] + + val VectorMap = ListMap + type VectorMap[K, +V] = ListMap[K, V] +} diff --git a/core/src/main/scala/chisel3/Aggregate.scala b/core/src/main/scala/chisel3/Aggregate.scala index c0b965b6..0031e53b 100644 --- a/core/src/main/scala/chisel3/Aggregate.scala +++ b/core/src/main/scala/chisel3/Aggregate.scala @@ -4,7 +4,7 @@ package chisel3 import chisel3.experimental.VecLiterals.AddVecLiteralConstructor -import scala.collection.immutable.ListMap +import scala.collection.immutable.{SeqMap, VectorMap} import scala.collection.mutable.{HashSet, LinkedHashMap} import scala.language.experimental.macros import chisel3.experimental.{BaseModule, BundleLiteralException, ChiselEnum, EnumType, VecLiteralException} @@ -516,7 +516,7 @@ sealed class Vec[T <: Data] private[chisel3] (gen: => T, val length: Int) } } - clone.bind(VecLitBinding(ListMap(vecLitLinkedMap.toSeq:_*))) + clone.bind(VecLitBinding(VectorMap(vecLitLinkedMap.toSeq:_*))) clone } } @@ -595,7 +595,7 @@ object VecInit extends SourceInfoDoc { /** A trait for [[Vec]]s containing common hardware generators for collection * operations. */ -trait VecLike[T <: Data] extends collection.IndexedSeq[T] with HasId with SourceInfoDoc { +trait VecLike[T <: Data] extends IndexedSeq[T] with HasId with SourceInfoDoc { def apply(p: UInt): T = macro CompileOptionsTransform.pArg /** @group SourceInfoTransformMacro */ @@ -835,7 +835,7 @@ abstract class Record(private[chisel3] implicit val compileOptions: CompileOptio s"$className$bindingString" } - val elements: ListMap[String, Data] + def elements: SeqMap[String, Data] /** Name for Pretty Printing */ def className: String = this.getClass.getSimpleName @@ -958,7 +958,7 @@ abstract class Bundle(implicit compileOptions: CompileOptions) extends Record { * assert(uint === "h12345678".U) // This will pass * }}} */ - final lazy val elements: ListMap[String, Data] = { + final lazy val elements: SeqMap[String, Data] = { val nameMap = LinkedHashMap[String, Data]() for (m <- getPublicFields(classOf[Bundle])) { getBundleField(m) match { @@ -985,7 +985,7 @@ abstract class Bundle(implicit compileOptions: CompileOptions) extends Record { } } } - ListMap(nameMap.toSeq sortWith { case ((an, a), (bn, b)) => (a._id > b._id) || ((a eq b) && (an > bn)) }: _*) + VectorMap(nameMap.toSeq sortWith { case ((an, a), (bn, b)) => (a._id > b._id) || ((a eq b) && (an > bn)) }: _*) } /** diff --git a/core/src/main/scala/chisel3/Bits.scala b/core/src/main/scala/chisel3/Bits.scala index 17829143..aa5b1abb 100644 --- a/core/src/main/scala/chisel3/Bits.scala +++ b/core/src/main/scala/chisel3/Bits.scala @@ -1147,6 +1147,7 @@ sealed class Bool() extends UInt(1.W) with Reset { package experimental { import chisel3.internal.firrtl.BinaryPoint + import chisel3.internal.requireIsHardware // Fix ambiguous import /** Chisel types that have binary points support retrieving * literal values as `Double` or `BigDecimal` diff --git a/core/src/main/scala/chisel3/Data.scala b/core/src/main/scala/chisel3/Data.scala index 0241f248..0832161e 100644 --- a/core/src/main/scala/chisel3/Data.scala +++ b/core/src/main/scala/chisel3/Data.scala @@ -122,6 +122,7 @@ object ActualDirection { } package experimental { + import chisel3.internal.requireIsHardware // Fix ambiguous import /** Experimental hardware construction reflection API */ @@ -763,35 +764,33 @@ object WireDefault { } } -package internal { - /** RHS (source) for Invalidate API. - * Causes connection logic to emit a DefInvalid when connected to an output port (or wire). - */ - private[chisel3] object InternalDontCare extends Element { - // This object should be initialized before we execute any user code that refers to it, - // otherwise this "Chisel" object will end up on the UserModule's id list. - // We make it private to chisel3 so it has to be accessed through the package object. +/** RHS (source) for Invalidate API. + * Causes connection logic to emit a DefInvalid when connected to an output port (or wire). + */ +final case object DontCare extends Element { + // This object should be initialized before we execute any user code that refers to it, + // otherwise this "Chisel" object will end up on the UserModule's id list. + // We make it private to chisel3 so it has to be accessed through the package object. - private[chisel3] override val width: Width = UnknownWidth() + private[chisel3] override val width: Width = UnknownWidth() - bind(DontCareBinding(), SpecifiedDirection.Output) - override def cloneType: this.type = DontCare + bind(DontCareBinding(), SpecifiedDirection.Output) + override def cloneType: this.type = DontCare - override def toString: String = "DontCare()" + override def toString: String = "DontCare()" - override def litOption: Option[BigInt] = None + override def litOption: Option[BigInt] = None - def toPrintable: Printable = PString("DONTCARE") + def toPrintable: Printable = PString("DONTCARE") - private[chisel3] def connectFromBits(that: Bits)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Unit = { - Builder.error("connectFromBits: DontCare cannot be a connection sink (LHS)") - } + private[chisel3] def connectFromBits(that: Bits)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Unit = { + Builder.error("connectFromBits: DontCare cannot be a connection sink (LHS)") + } - def do_asUInt(implicit sourceInfo: chisel3.internal.sourceinfo.SourceInfo, compileOptions: CompileOptions): UInt = { - Builder.error("DontCare does not have a UInt representation") - 0.U - } - // DontCare's only match themselves. - private[chisel3] def typeEquivalent(that: Data): Boolean = that == DontCare + def do_asUInt(implicit sourceInfo: chisel3.internal.sourceinfo.SourceInfo, compileOptions: CompileOptions): UInt = { + Builder.error("DontCare does not have a UInt representation") + 0.U } + // DontCare's only match themselves. + private[chisel3] def typeEquivalent(that: Data): Boolean = that == DontCare } diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala index ede9ccc6..b204be8d 100644 --- a/core/src/main/scala/chisel3/Module.scala +++ b/core/src/main/scala/chisel3/Module.scala @@ -4,9 +4,7 @@ package chisel3 import scala.collection.immutable.ListMap import scala.collection.mutable.{ArrayBuffer, HashMap} -import scala.collection.JavaConversions._ import scala.language.experimental.macros -import java.util.IdentityHashMap import chisel3.internal._ import chisel3.internal.Builder._ @@ -139,6 +137,8 @@ abstract class Module(implicit moduleCompileOptions: CompileOptions) extends Raw package experimental { + import chisel3.internal.requireIsChiselType // Fix ambiguous import + object IO { /** Constructs a port for the current Module * diff --git a/core/src/main/scala/chisel3/Num.scala b/core/src/main/scala/chisel3/Num.scala index e1af9bdb..70f2fbf0 100644 --- a/core/src/main/scala/chisel3/Num.scala +++ b/core/src/main/scala/chisel3/Num.scala @@ -226,7 +226,7 @@ trait NumObject { */ def toBigInt(x: BigDecimal, binaryPoint: Int): BigInt = { val multiplier = math.pow(2, binaryPoint) - val result = (x * multiplier).rounded.toBigInt() + val result = (x * multiplier).rounded.toBigInt result } diff --git a/core/src/main/scala/chisel3/RawModule.scala b/core/src/main/scala/chisel3/RawModule.scala index d2ba6e84..f678c587 100644 --- a/core/src/main/scala/chisel3/RawModule.scala +++ b/core/src/main/scala/chisel3/RawModule.scala @@ -4,7 +4,6 @@ package chisel3 import scala.collection.mutable.{ArrayBuffer, HashMap} import scala.util.Try -import scala.collection.JavaConversions._ import scala.language.experimental.macros import chisel3.experimental.BaseModule diff --git a/core/src/main/scala/chisel3/internal/Binding.scala b/core/src/main/scala/chisel3/internal/Binding.scala index 8a3c4330..300803ce 100644 --- a/core/src/main/scala/chisel3/internal/Binding.scala +++ b/core/src/main/scala/chisel3/internal/Binding.scala @@ -6,7 +6,7 @@ import chisel3._ import chisel3.experimental.BaseModule import chisel3.internal.firrtl.LitArg -import scala.collection.immutable.ListMap +import scala.collection.immutable.VectorMap /** Requires that a node is hardware ("bound") */ @@ -126,4 +126,4 @@ case class ElementLitBinding(litArg: LitArg) extends LitBinding // Literal binding attached to the root of a Bundle, containing literal values of its children. case class BundleLitBinding(litMap: Map[Data, LitArg]) extends LitBinding // Literal binding attached to the root of a Vec, containing literal values of its children. -case class VecLitBinding(litMap: ListMap[Data, LitArg]) extends LitBinding +case class VecLitBinding(litMap: VectorMap[Data, LitArg]) extends LitBinding diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index 084bdc88..e1e4d460 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -655,7 +655,7 @@ private[chisel3] object Builder extends LazyLogging { errors.checkpoint() logger.warn("Done elaborating.") - (Circuit(components.last.name, components, annotations), mod) + (Circuit(components.last.name, components.toSeq, annotations.toSeq), mod) } } initializeSingletons() diff --git a/core/src/main/scala/chisel3/internal/Error.scala b/core/src/main/scala/chisel3/internal/Error.scala index 134f4c87..454be360 100644 --- a/core/src/main/scala/chisel3/internal/Error.scala +++ b/core/src/main/scala/chisel3/internal/Error.scala @@ -45,7 +45,7 @@ object ExceptionHelpers { } // Step 1: Remove elements from the top in the package trimlist - ((a: Array[StackTraceElement]) => a.view.dropWhile(inTrimlist)) + ((a: Array[StackTraceElement]) => a.dropWhile(inTrimlist)) // Step 2: Optionally remove elements from the bottom until the anchor .andThen(_.reverse) .andThen( a => @@ -125,10 +125,10 @@ class ChiselException(message: String, cause: Throwable = null) extends Exceptio } val trimmedLeft = throwable.getStackTrace().view.dropWhile(isBlacklisted) - val trimmedReverse = trimmedLeft.reverse + val trimmedReverse = trimmedLeft.toIndexedSeq.reverse.view .dropWhile(ste => !ste.getClassName.startsWith(builderName)) .dropWhile(isBlacklisted) - trimmedReverse.reverse.toArray + trimmedReverse.toIndexedSeq.reverse.toArray } def chiselStackTrace: String = { diff --git a/core/src/main/scala/chisel3/internal/Namer.scala b/core/src/main/scala/chisel3/internal/Namer.scala index 1694d71d..c6e36cb6 100644 --- a/core/src/main/scala/chisel3/internal/Namer.scala +++ b/core/src/main/scala/chisel3/internal/Namer.scala @@ -8,9 +8,8 @@ import chisel3.experimental.NoChiselNamePrefix import scala.collection.mutable.Stack import scala.collection.mutable.ListBuffer -import scala.collection.JavaConversions._ - import java.util.IdentityHashMap +import scala.collection.JavaConverters._ /** Recursive Function Namer overview * @@ -81,7 +80,14 @@ class NamingContext extends NamingContextInterface { def addDescendant(ref: Any, descendant: NamingContext) { ref match { case ref: AnyRef => - descendants.getOrElseUpdate(ref, ListBuffer[NamingContext]()) += descendant + // getOrElseUpdate + val l = descendants.get(ref) + val buf = if (l != null) l else { + val value = ListBuffer[NamingContext]() + descendants.put(ref, value) + value + } + buf += descendant case _ => anonymousDescendants += descendant } } @@ -111,7 +117,7 @@ class NamingContext extends NamingContextInterface { } } - for (descendant <- descendants.values().flatten) { + for (descendant <- descendants.values.asScala.flatten) { // Where we have a broken naming link, just ignore the missing parts descendant.namePrefix(prefix) } diff --git a/core/src/main/scala/chisel3/package.scala b/core/src/main/scala/chisel3/package.scala index d5a4bfae..64cfa8b9 100644 --- a/core/src/main/scala/chisel3/package.scala +++ b/core/src/main/scala/chisel3/package.scala @@ -207,9 +207,6 @@ package object chisel3 { a.allElements } def getModulePorts(m: Module): Seq[Port] = m.getPorts - // Invalidate API - a DontCare element for explicit assignment to outputs, - // indicating the signal is intentionally not driven. - val DontCare = chisel3.internal.InternalDontCare class BindingException(message: String) extends ChiselException(message) /** A function expected a Chisel type but got a hardware object diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala deleted file mode 100644 index 96851e95..00000000 --- a/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala +++ /dev/null @@ -1,136 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chisel3.internal.plugin - -import scala.collection.mutable -import scala.tools.nsc -import scala.tools.nsc.{Global, Phase} -import scala.tools.nsc.plugins.PluginComponent -import scala.tools.nsc.symtab.Flags -import scala.tools.nsc.transform.TypingTransformers - -// TODO This component could also implement val elements in Bundles -private[plugin] class BundleComponent(val global: Global, arguments: ChiselPluginArguments) - extends PluginComponent - with TypingTransformers { - import global._ - - val phaseName: String = "chiselbundlephase" - val runsAfter: List[String] = "typer" :: Nil - def newPhase(prev: Phase): Phase = new BundlePhase(prev) - - private class BundlePhase(prev: Phase) extends StdPhase(prev) { - override def name: String = phaseName - def apply(unit: CompilationUnit): Unit = { - // This plugin doesn't work on Scala 2.11 nor Scala 3. Rather than complicate the sbt build flow, - // instead we just check the version and if its an early Scala version, the plugin does nothing - val scalaVersion = scala.util.Properties.versionNumberString.split('.') - val scalaVersionOk = scalaVersion(0).toInt == 2 && scalaVersion(1).toInt >= 12 - if (scalaVersionOk && arguments.useBundlePlugin) { - unit.body = new MyTypingTransformer(unit).transform(unit.body) - } else { - val reason = if (!scalaVersionOk) { - s"invalid Scala version '${scala.util.Properties.versionNumberString}'" - } else { - s"not enabled via '${arguments.useBundlePluginFullOpt}'" - } - // Enable this with scalacOption '-Ylog:chiselbundlephase' - global.log(s"Skipping BundleComponent on account of $reason.") - } - } - } - - private class MyTypingTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { - - def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe - - val bundleTpe = inferType(tq"chisel3.Bundle") - val dataTpe = inferType(tq"chisel3.Data") - - // Not cached because it should only be run once per class (thus once per Type) - def isBundle(sym: Symbol): Boolean = sym.tpe <:< bundleTpe - - val isDataCache = new mutable.HashMap[Type, Boolean] - // Cached because this is run on every argument to every Bundle - def isData(sym: Symbol): Boolean = isDataCache.getOrElseUpdate(sym.tpe, sym.tpe <:< dataTpe) - - def cloneTypeFull(tree: Tree): Tree = - localTyper.typed(q"chisel3.experimental.DataMirror.internal.chiselTypeClone[${tree.tpe}]($tree)") - - def isNullaryMethodNamed(name: String, defdef: DefDef): Boolean = - defdef.name.decodedName.toString == name && defdef.tparams.isEmpty && defdef.vparamss.isEmpty - - def getConstructorAndParams(body: List[Tree]): (Option[DefDef], Seq[Symbol]) = { - val paramAccessors = mutable.ListBuffer[Symbol]() - var primaryConstructor: Option[DefDef] = None - body.foreach { - case acc: ValDef if acc.symbol.isParamAccessor => - paramAccessors += acc.symbol - case con: DefDef if con.symbol.isPrimaryConstructor => - primaryConstructor = Some(con) - case d: DefDef if isNullaryMethodNamed("_cloneTypeImpl", d) => - val msg = "Users cannot override _cloneTypeImpl. Let the compiler plugin generate it. If you must, override cloneType instead." - global.globalError(d.pos, msg) - case d: DefDef if isNullaryMethodNamed("_usingPlugin", d) => - val msg = "Users cannot override _usingPlugin, it is for the compiler plugin's use only." - global.globalError(d.pos, msg) - case _ => - } - (primaryConstructor, paramAccessors.toList) - } - - - override def transform(tree: Tree): Tree = tree match { - - case bundle: ClassDef if isBundle(bundle.symbol) && !bundle.mods.hasFlag(Flag.ABSTRACT) => - - // ==================== Generate _cloneTypeImpl ==================== - val (con, params) = getConstructorAndParams(bundle.impl.body) - if (con.isEmpty) { - global.reporter.warning(bundle.pos, "Unable to determine primary constructor!") - return super.transform(tree) - } - val constructor = con.get - - val thiz = gen.mkAttributedThis(bundle.symbol) - - // The params have spaces after them (Scalac implementation detail) - val paramLookup: String => Symbol = params.map(sym => sym.name.toString.trim -> sym).toMap - - // Create a this. for each field matching order of constructor arguments - // List of Lists because we can have multiple parameter lists - val conArgs: List[List[Tree]] = - constructor.vparamss.map(_.map { vp => - val p = paramLookup(vp.name.toString) - // Make this. - val select = gen.mkAttributedSelect(thiz, p) - // Clone any Data parameters to avoid field aliasing, need full clone to include direction - if (isData(vp.symbol)) cloneTypeFull(select) else select - }) - - val ttpe = Ident(bundle.symbol) - val neww = localTyper.typed(New(ttpe, conArgs)) - - // Create the symbol for the method and have it be associated with the Bundle class - val cloneTypeSym = bundle.symbol.newMethod(TermName("_cloneTypeImpl"), bundle.symbol.pos.focus, Flag.OVERRIDE | Flag.PROTECTED) - // Handwritten cloneTypes don't have the Method flag set, unclear if it matters - cloneTypeSym.resetFlag(Flags.METHOD) - // Need to set the type to chisel3.Bundle for the override to work - cloneTypeSym.setInfo(NullaryMethodType(bundleTpe)) - - val cloneTypeImpl = localTyper.typed(DefDef(cloneTypeSym, neww)) - - // ==================== Generate _usingPlugin ==================== - // Unclear why quasiquotes work here but didn't for cloneTypeSym, maybe they could. - val usingPlugin = localTyper.typed(q"override protected def _usingPlugin: Boolean = true") - - val withMethods = deriveClassDef(bundle) { t => - deriveTemplate(t)(_ :+ cloneTypeImpl :+ usingPlugin) - } - - super.transform(localTyper.typed(withMethods)) - - case _ => super.transform(tree) - } - } -} diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala deleted file mode 100644 index b1302ba3..00000000 --- a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala +++ /dev/null @@ -1,215 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chisel3.internal.plugin - -import scala.collection.mutable -import scala.reflect.internal.Flags -import scala.tools.nsc -import scala.tools.nsc.{Global, Phase} -import scala.tools.nsc.plugins.PluginComponent -import scala.tools.nsc.transform.TypingTransformers - -// The component of the chisel plugin. Not sure exactly what the difference is between -// a Plugin and a PluginComponent. -class ChiselComponent(val global: Global) extends PluginComponent with TypingTransformers { - import global._ - val runsAfter: List[String] = List[String]("typer") - val phaseName: String = "chiselcomponent" - def newPhase(_prev: Phase): ChiselComponentPhase = new ChiselComponentPhase(_prev) - class ChiselComponentPhase(prev: Phase) extends StdPhase(prev) { - override def name: String = phaseName - def apply(unit: CompilationUnit): Unit = { - // This plugin doesn't work on Scala 2.11 nor Scala 3. Rather than complicate the sbt build flow, - // instead we just check the version and if its an early Scala version, the plugin does nothing - val scalaVersion = scala.util.Properties.versionNumberString.split('.') - if (scalaVersion(0).toInt == 2 && scalaVersion(1).toInt >= 12) { - unit.body = new MyTypingTransformer(unit).transform(unit.body) - } - } - } - - class MyTypingTransformer(unit: CompilationUnit) - extends TypingTransformer(unit) { - - private def shouldMatchGen(bases: Tree*): Type => Boolean = { - val cache = mutable.HashMap.empty[Type, Boolean] - val baseTypes = bases.map(inferType) - - // If subtype of one of the base types, it's a match! - def terminate(t: Type): Boolean = baseTypes.exists(t <:< _) - - // Recurse through subtype hierarchy finding containers - // Seen is only updated when we recurse into type parameters, thus it is typically small - def recShouldMatch(s: Type, seen: Set[Type]): Boolean = { - def outerMatches(t: Type): Boolean = { - val str = t.toString - str.startsWith("Option[") || str.startsWith("Iterable[") - } - if (terminate(s)) { - true - } else if (seen.contains(s)) { - false - } else if (outerMatches(s)) { - // These are type parameters, loops *are* possible here - recShouldMatch(s.typeArgs.head, seen + s) - } else { - // This is the standard inheritance hierarchy, Scalac catches loops here - s.parents.exists( p => recShouldMatch(p, seen) ) - } - } - - // If doesn't match container pattern, exit early - def earlyExit(t: Type): Boolean = { - !(t.matchesPattern(inferType(tq"Iterable[_]")) || t.matchesPattern(inferType(tq"Option[_]"))) - } - - // Return function so that it captures the cache - { q: Type => - cache.getOrElseUpdate(q, { - // First check if a match, then check early exit, then recurse - if(terminate(q)){ - true - } else if(earlyExit(q)) { - false - } else { - recShouldMatch(q, Set.empty) - } - }) - } - } - - - private val shouldMatchData : Type => Boolean = shouldMatchGen(tq"chisel3.Data") - private val shouldMatchDataOrMem : Type => Boolean = shouldMatchGen(tq"chisel3.Data", tq"chisel3.MemBase[_]") - private val shouldMatchModule : Type => Boolean = shouldMatchGen(tq"chisel3.experimental.BaseModule") - - // Given a type tree, infer the type and return it - private def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe - - // Indicates whether a ValDef is properly formed to get name - private def okVal(dd: ValDef): Boolean = { - - // These were found through trial and error - def okFlags(mods: Modifiers): Boolean = { - val badFlags = Set( - Flag.PARAM, - Flag.SYNTHETIC, - Flag.DEFERRED, - Flags.TRIEDCOOKING, - Flags.CASEACCESSOR, - Flags.PARAMACCESSOR - ) - badFlags.forall{ x => !mods.hasFlag(x)} - } - - // Ensure expression isn't null, as you can't call `null.autoName("myname")` - val isNull = dd.rhs match { - case Literal(Constant(null)) => true - case _ => false - } - - okFlags(dd.mods) && !isNull && dd.rhs != EmptyTree - } - // TODO Unify with okVal - private def okUnapply(dd: ValDef): Boolean = { - - // These were found through trial and error - def okFlags(mods: Modifiers): Boolean = { - val badFlags = Set( - Flag.PARAM, - Flag.DEFERRED, - Flags.TRIEDCOOKING, - Flags.CASEACCESSOR, - Flags.PARAMACCESSOR - ) - val goodFlags = Set( - Flag.SYNTHETIC, - Flag.ARTIFACT - ) - goodFlags.forall(f => mods.hasFlag(f)) && badFlags.forall(f => !mods.hasFlag(f)) - } - - // Ensure expression isn't null, as you can't call `null.autoName("myname")` - val isNull = dd.rhs match { - case Literal(Constant(null)) => true - case _ => false - } - val tpe = inferType(dd.tpt) - definitions.isTupleType(tpe) && okFlags(dd.mods) && !isNull && dd.rhs != EmptyTree - } - - private def findUnapplyNames(tree: Tree): Option[List[String]] = { - val applyArgs: Option[List[Tree]] = tree match { - case Match(_, List(CaseDef(_, _, Apply(_, args)))) => Some(args) - case _ => None - } - applyArgs.flatMap { args => - var ok = true - val result = mutable.ListBuffer[String]() - args.foreach { - case Ident(TermName(name)) => result += name - // Anything unexpected and we abort - case _ => ok = false - } - if (ok) Some(result.toList) else None - } - } - - // Whether this val is directly enclosed by a Bundle type - private def inBundle(dd: ValDef): Boolean = { - dd.symbol.logicallyEnclosingMember.thisType <:< inferType(tq"chisel3.Bundle") - } - - private def stringFromTermName(name: TermName): String = - name.toString.trim() // Remove trailing space (Scalac implementation detail) - - // Method called by the compiler to modify source tree - override def transform(tree: Tree): Tree = tree match { - // Check if a subtree is a candidate - case dd @ ValDef(mods, name, tpt, rhs) if okVal(dd) => - val tpe = inferType(tpt) - // If a Data and in a Bundle, just get the name but not a prefix - if (shouldMatchData(tpe) && inBundle(dd)) { - val str = stringFromTermName(name) - val newRHS = transform(rhs) // chisel3.internal.plugin.autoNameRecursively - val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" - treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) - } - // If a Data or a Memory, get the name and a prefix - else if (shouldMatchDataOrMem(tpe)) { - val str = stringFromTermName(name) - val newRHS = transform(rhs) - val prefixed = q"chisel3.experimental.prefix.apply[$tpt](name=$str)(f=$newRHS)" - val named = q"chisel3.internal.plugin.autoNameRecursively($str)($prefixed)" - treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) - // If an instance, just get a name but no prefix - } else if (shouldMatchModule(tpe)) { - val str = stringFromTermName(name) - val newRHS = transform(rhs) - val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" - treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) - } else { - // Otherwise, continue - super.transform(tree) - } - case dd @ ValDef(mods, name, tpt, rhs @ Match(_, _)) if okUnapply(dd) => - val tpe = inferType(tpt) - val fieldsOfInterest: List[Boolean] = tpe.typeArgs.map(shouldMatchData) - // Only transform if at least one field is of interest - if (fieldsOfInterest.reduce(_ || _)) { - findUnapplyNames(rhs) match { - case Some(names) => - val onames: List[Option[String]] = fieldsOfInterest.zip(names).map { case (ok, name) => if (ok) Some(name) else None } - val named = q"chisel3.internal.plugin.autoNameRecursivelyProduct($onames)($rhs)" - treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) - case None => // It's not clear how this could happen but we don't want to crash - super.transform(tree) - } - } else { - super.transform(tree) - } - // Otherwise, continue - case _ => super.transform(tree) - } - } -} diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala deleted file mode 100644 index 23082329..00000000 --- a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chisel3.internal.plugin - -import scala.tools.nsc -import nsc.Global -import nsc.plugins.{Plugin, PluginComponent} -import scala.reflect.internal.util.NoPosition - -private[plugin] case class ChiselPluginArguments(var useBundlePlugin: Boolean = true) { - def useBundlePluginOpt = "useBundlePlugin" - def useBundlePluginFullOpt = s"-P:chiselplugin:$useBundlePluginOpt" -} - -// The plugin to be run by the Scala compiler during compilation of Chisel code -class ChiselPlugin(val global: Global) extends Plugin { - val name = "chiselplugin" - val description = "Plugin for Chisel 3 Hardware Description Language" - private val arguments = ChiselPluginArguments() - val components: List[PluginComponent] = List[PluginComponent]( - new ChiselComponent(global), - new BundleComponent(global, arguments) - ) - - override def init(options: List[String], error: String => Unit): Boolean = { - for (option <- options) { - if (option == arguments.useBundlePluginOpt) { - val msg = s"'${arguments.useBundlePluginFullOpt}' is now default behavior, you can stop using the scalacOption." - global.reporter.warning(NoPosition, msg) - } else { - error(s"Option not understood: '$option'") - } - } - true - } - - -} - diff --git a/plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala b/plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala new file mode 100644 index 00000000..96851e95 --- /dev/null +++ b/plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.internal.plugin + +import scala.collection.mutable +import scala.tools.nsc +import scala.tools.nsc.{Global, Phase} +import scala.tools.nsc.plugins.PluginComponent +import scala.tools.nsc.symtab.Flags +import scala.tools.nsc.transform.TypingTransformers + +// TODO This component could also implement val elements in Bundles +private[plugin] class BundleComponent(val global: Global, arguments: ChiselPluginArguments) + extends PluginComponent + with TypingTransformers { + import global._ + + val phaseName: String = "chiselbundlephase" + val runsAfter: List[String] = "typer" :: Nil + def newPhase(prev: Phase): Phase = new BundlePhase(prev) + + private class BundlePhase(prev: Phase) extends StdPhase(prev) { + override def name: String = phaseName + def apply(unit: CompilationUnit): Unit = { + // This plugin doesn't work on Scala 2.11 nor Scala 3. Rather than complicate the sbt build flow, + // instead we just check the version and if its an early Scala version, the plugin does nothing + val scalaVersion = scala.util.Properties.versionNumberString.split('.') + val scalaVersionOk = scalaVersion(0).toInt == 2 && scalaVersion(1).toInt >= 12 + if (scalaVersionOk && arguments.useBundlePlugin) { + unit.body = new MyTypingTransformer(unit).transform(unit.body) + } else { + val reason = if (!scalaVersionOk) { + s"invalid Scala version '${scala.util.Properties.versionNumberString}'" + } else { + s"not enabled via '${arguments.useBundlePluginFullOpt}'" + } + // Enable this with scalacOption '-Ylog:chiselbundlephase' + global.log(s"Skipping BundleComponent on account of $reason.") + } + } + } + + private class MyTypingTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { + + def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe + + val bundleTpe = inferType(tq"chisel3.Bundle") + val dataTpe = inferType(tq"chisel3.Data") + + // Not cached because it should only be run once per class (thus once per Type) + def isBundle(sym: Symbol): Boolean = sym.tpe <:< bundleTpe + + val isDataCache = new mutable.HashMap[Type, Boolean] + // Cached because this is run on every argument to every Bundle + def isData(sym: Symbol): Boolean = isDataCache.getOrElseUpdate(sym.tpe, sym.tpe <:< dataTpe) + + def cloneTypeFull(tree: Tree): Tree = + localTyper.typed(q"chisel3.experimental.DataMirror.internal.chiselTypeClone[${tree.tpe}]($tree)") + + def isNullaryMethodNamed(name: String, defdef: DefDef): Boolean = + defdef.name.decodedName.toString == name && defdef.tparams.isEmpty && defdef.vparamss.isEmpty + + def getConstructorAndParams(body: List[Tree]): (Option[DefDef], Seq[Symbol]) = { + val paramAccessors = mutable.ListBuffer[Symbol]() + var primaryConstructor: Option[DefDef] = None + body.foreach { + case acc: ValDef if acc.symbol.isParamAccessor => + paramAccessors += acc.symbol + case con: DefDef if con.symbol.isPrimaryConstructor => + primaryConstructor = Some(con) + case d: DefDef if isNullaryMethodNamed("_cloneTypeImpl", d) => + val msg = "Users cannot override _cloneTypeImpl. Let the compiler plugin generate it. If you must, override cloneType instead." + global.globalError(d.pos, msg) + case d: DefDef if isNullaryMethodNamed("_usingPlugin", d) => + val msg = "Users cannot override _usingPlugin, it is for the compiler plugin's use only." + global.globalError(d.pos, msg) + case _ => + } + (primaryConstructor, paramAccessors.toList) + } + + + override def transform(tree: Tree): Tree = tree match { + + case bundle: ClassDef if isBundle(bundle.symbol) && !bundle.mods.hasFlag(Flag.ABSTRACT) => + + // ==================== Generate _cloneTypeImpl ==================== + val (con, params) = getConstructorAndParams(bundle.impl.body) + if (con.isEmpty) { + global.reporter.warning(bundle.pos, "Unable to determine primary constructor!") + return super.transform(tree) + } + val constructor = con.get + + val thiz = gen.mkAttributedThis(bundle.symbol) + + // The params have spaces after them (Scalac implementation detail) + val paramLookup: String => Symbol = params.map(sym => sym.name.toString.trim -> sym).toMap + + // Create a this. for each field matching order of constructor arguments + // List of Lists because we can have multiple parameter lists + val conArgs: List[List[Tree]] = + constructor.vparamss.map(_.map { vp => + val p = paramLookup(vp.name.toString) + // Make this. + val select = gen.mkAttributedSelect(thiz, p) + // Clone any Data parameters to avoid field aliasing, need full clone to include direction + if (isData(vp.symbol)) cloneTypeFull(select) else select + }) + + val ttpe = Ident(bundle.symbol) + val neww = localTyper.typed(New(ttpe, conArgs)) + + // Create the symbol for the method and have it be associated with the Bundle class + val cloneTypeSym = bundle.symbol.newMethod(TermName("_cloneTypeImpl"), bundle.symbol.pos.focus, Flag.OVERRIDE | Flag.PROTECTED) + // Handwritten cloneTypes don't have the Method flag set, unclear if it matters + cloneTypeSym.resetFlag(Flags.METHOD) + // Need to set the type to chisel3.Bundle for the override to work + cloneTypeSym.setInfo(NullaryMethodType(bundleTpe)) + + val cloneTypeImpl = localTyper.typed(DefDef(cloneTypeSym, neww)) + + // ==================== Generate _usingPlugin ==================== + // Unclear why quasiquotes work here but didn't for cloneTypeSym, maybe they could. + val usingPlugin = localTyper.typed(q"override protected def _usingPlugin: Boolean = true") + + val withMethods = deriveClassDef(bundle) { t => + deriveTemplate(t)(_ :+ cloneTypeImpl :+ usingPlugin) + } + + super.transform(localTyper.typed(withMethods)) + + case _ => super.transform(tree) + } + } +} diff --git a/plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala b/plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala new file mode 100644 index 00000000..b1302ba3 --- /dev/null +++ b/plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.internal.plugin + +import scala.collection.mutable +import scala.reflect.internal.Flags +import scala.tools.nsc +import scala.tools.nsc.{Global, Phase} +import scala.tools.nsc.plugins.PluginComponent +import scala.tools.nsc.transform.TypingTransformers + +// The component of the chisel plugin. Not sure exactly what the difference is between +// a Plugin and a PluginComponent. +class ChiselComponent(val global: Global) extends PluginComponent with TypingTransformers { + import global._ + val runsAfter: List[String] = List[String]("typer") + val phaseName: String = "chiselcomponent" + def newPhase(_prev: Phase): ChiselComponentPhase = new ChiselComponentPhase(_prev) + class ChiselComponentPhase(prev: Phase) extends StdPhase(prev) { + override def name: String = phaseName + def apply(unit: CompilationUnit): Unit = { + // This plugin doesn't work on Scala 2.11 nor Scala 3. Rather than complicate the sbt build flow, + // instead we just check the version and if its an early Scala version, the plugin does nothing + val scalaVersion = scala.util.Properties.versionNumberString.split('.') + if (scalaVersion(0).toInt == 2 && scalaVersion(1).toInt >= 12) { + unit.body = new MyTypingTransformer(unit).transform(unit.body) + } + } + } + + class MyTypingTransformer(unit: CompilationUnit) + extends TypingTransformer(unit) { + + private def shouldMatchGen(bases: Tree*): Type => Boolean = { + val cache = mutable.HashMap.empty[Type, Boolean] + val baseTypes = bases.map(inferType) + + // If subtype of one of the base types, it's a match! + def terminate(t: Type): Boolean = baseTypes.exists(t <:< _) + + // Recurse through subtype hierarchy finding containers + // Seen is only updated when we recurse into type parameters, thus it is typically small + def recShouldMatch(s: Type, seen: Set[Type]): Boolean = { + def outerMatches(t: Type): Boolean = { + val str = t.toString + str.startsWith("Option[") || str.startsWith("Iterable[") + } + if (terminate(s)) { + true + } else if (seen.contains(s)) { + false + } else if (outerMatches(s)) { + // These are type parameters, loops *are* possible here + recShouldMatch(s.typeArgs.head, seen + s) + } else { + // This is the standard inheritance hierarchy, Scalac catches loops here + s.parents.exists( p => recShouldMatch(p, seen) ) + } + } + + // If doesn't match container pattern, exit early + def earlyExit(t: Type): Boolean = { + !(t.matchesPattern(inferType(tq"Iterable[_]")) || t.matchesPattern(inferType(tq"Option[_]"))) + } + + // Return function so that it captures the cache + { q: Type => + cache.getOrElseUpdate(q, { + // First check if a match, then check early exit, then recurse + if(terminate(q)){ + true + } else if(earlyExit(q)) { + false + } else { + recShouldMatch(q, Set.empty) + } + }) + } + } + + + private val shouldMatchData : Type => Boolean = shouldMatchGen(tq"chisel3.Data") + private val shouldMatchDataOrMem : Type => Boolean = shouldMatchGen(tq"chisel3.Data", tq"chisel3.MemBase[_]") + private val shouldMatchModule : Type => Boolean = shouldMatchGen(tq"chisel3.experimental.BaseModule") + + // Given a type tree, infer the type and return it + private def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe + + // Indicates whether a ValDef is properly formed to get name + private def okVal(dd: ValDef): Boolean = { + + // These were found through trial and error + def okFlags(mods: Modifiers): Boolean = { + val badFlags = Set( + Flag.PARAM, + Flag.SYNTHETIC, + Flag.DEFERRED, + Flags.TRIEDCOOKING, + Flags.CASEACCESSOR, + Flags.PARAMACCESSOR + ) + badFlags.forall{ x => !mods.hasFlag(x)} + } + + // Ensure expression isn't null, as you can't call `null.autoName("myname")` + val isNull = dd.rhs match { + case Literal(Constant(null)) => true + case _ => false + } + + okFlags(dd.mods) && !isNull && dd.rhs != EmptyTree + } + // TODO Unify with okVal + private def okUnapply(dd: ValDef): Boolean = { + + // These were found through trial and error + def okFlags(mods: Modifiers): Boolean = { + val badFlags = Set( + Flag.PARAM, + Flag.DEFERRED, + Flags.TRIEDCOOKING, + Flags.CASEACCESSOR, + Flags.PARAMACCESSOR + ) + val goodFlags = Set( + Flag.SYNTHETIC, + Flag.ARTIFACT + ) + goodFlags.forall(f => mods.hasFlag(f)) && badFlags.forall(f => !mods.hasFlag(f)) + } + + // Ensure expression isn't null, as you can't call `null.autoName("myname")` + val isNull = dd.rhs match { + case Literal(Constant(null)) => true + case _ => false + } + val tpe = inferType(dd.tpt) + definitions.isTupleType(tpe) && okFlags(dd.mods) && !isNull && dd.rhs != EmptyTree + } + + private def findUnapplyNames(tree: Tree): Option[List[String]] = { + val applyArgs: Option[List[Tree]] = tree match { + case Match(_, List(CaseDef(_, _, Apply(_, args)))) => Some(args) + case _ => None + } + applyArgs.flatMap { args => + var ok = true + val result = mutable.ListBuffer[String]() + args.foreach { + case Ident(TermName(name)) => result += name + // Anything unexpected and we abort + case _ => ok = false + } + if (ok) Some(result.toList) else None + } + } + + // Whether this val is directly enclosed by a Bundle type + private def inBundle(dd: ValDef): Boolean = { + dd.symbol.logicallyEnclosingMember.thisType <:< inferType(tq"chisel3.Bundle") + } + + private def stringFromTermName(name: TermName): String = + name.toString.trim() // Remove trailing space (Scalac implementation detail) + + // Method called by the compiler to modify source tree + override def transform(tree: Tree): Tree = tree match { + // Check if a subtree is a candidate + case dd @ ValDef(mods, name, tpt, rhs) if okVal(dd) => + val tpe = inferType(tpt) + // If a Data and in a Bundle, just get the name but not a prefix + if (shouldMatchData(tpe) && inBundle(dd)) { + val str = stringFromTermName(name) + val newRHS = transform(rhs) // chisel3.internal.plugin.autoNameRecursively + val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + } + // If a Data or a Memory, get the name and a prefix + else if (shouldMatchDataOrMem(tpe)) { + val str = stringFromTermName(name) + val newRHS = transform(rhs) + val prefixed = q"chisel3.experimental.prefix.apply[$tpt](name=$str)(f=$newRHS)" + val named = q"chisel3.internal.plugin.autoNameRecursively($str)($prefixed)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + // If an instance, just get a name but no prefix + } else if (shouldMatchModule(tpe)) { + val str = stringFromTermName(name) + val newRHS = transform(rhs) + val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + } else { + // Otherwise, continue + super.transform(tree) + } + case dd @ ValDef(mods, name, tpt, rhs @ Match(_, _)) if okUnapply(dd) => + val tpe = inferType(tpt) + val fieldsOfInterest: List[Boolean] = tpe.typeArgs.map(shouldMatchData) + // Only transform if at least one field is of interest + if (fieldsOfInterest.reduce(_ || _)) { + findUnapplyNames(rhs) match { + case Some(names) => + val onames: List[Option[String]] = fieldsOfInterest.zip(names).map { case (ok, name) => if (ok) Some(name) else None } + val named = q"chisel3.internal.plugin.autoNameRecursivelyProduct($onames)($rhs)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + case None => // It's not clear how this could happen but we don't want to crash + super.transform(tree) + } + } else { + super.transform(tree) + } + // Otherwise, continue + case _ => super.transform(tree) + } + } +} diff --git a/plugin/src/main/scala/chisel3/internal/plugin/ChiselPlugin.scala b/plugin/src/main/scala/chisel3/internal/plugin/ChiselPlugin.scala new file mode 100644 index 00000000..23082329 --- /dev/null +++ b/plugin/src/main/scala/chisel3/internal/plugin/ChiselPlugin.scala @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.internal.plugin + +import scala.tools.nsc +import nsc.Global +import nsc.plugins.{Plugin, PluginComponent} +import scala.reflect.internal.util.NoPosition + +private[plugin] case class ChiselPluginArguments(var useBundlePlugin: Boolean = true) { + def useBundlePluginOpt = "useBundlePlugin" + def useBundlePluginFullOpt = s"-P:chiselplugin:$useBundlePluginOpt" +} + +// The plugin to be run by the Scala compiler during compilation of Chisel code +class ChiselPlugin(val global: Global) extends Plugin { + val name = "chiselplugin" + val description = "Plugin for Chisel 3 Hardware Description Language" + private val arguments = ChiselPluginArguments() + val components: List[PluginComponent] = List[PluginComponent]( + new ChiselComponent(global), + new BundleComponent(global, arguments) + ) + + override def init(options: List[String], error: String => Unit): Boolean = { + for (option <- options) { + if (option == arguments.useBundlePluginOpt) { + val msg = s"'${arguments.useBundlePluginFullOpt}' is now default behavior, you can stop using the scalacOption." + global.reporter.warning(NoPosition, msg) + } else { + error(s"Option not understood: '$option'") + } + } + true + } + + +} + diff --git a/src/main/scala/chisel3/aop/Select.scala b/src/main/scala/chisel3/aop/Select.scala index e2689f39..b9ad808b 100644 --- a/src/main/scala/chisel3/aop/Select.scala +++ b/src/main/scala/chisel3/aop/Select.scala @@ -248,7 +248,7 @@ object Select { case other => } }) - predicatedConnects + predicatedConnects.toSeq } /** Selects all stop statements, and includes the predicates surrounding the stop statement @@ -264,7 +264,7 @@ object Select { case other => } }) - stops + stops.toSeq } /** Selects all printf statements, and includes the predicates surrounding the printf statement @@ -280,7 +280,7 @@ object Select { case other => } }) - printfs + printfs.toSeq } // Checks that a module has finished its construction @@ -321,7 +321,7 @@ object Select { } } catch { case e: ChiselException => i.getOptionRef.get match { - case l: LitArg => l.num.intValue().toString + case l: LitArg => l.num.intValue.toString } } diff --git a/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala b/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala index 768680ed..c540fc83 100644 --- a/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala +++ b/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala @@ -89,7 +89,7 @@ abstract class InjectorAspect[T <: RawModule, M <: RawModule]( Seq(other) } - InjectStatement(ModuleTarget(circuit, module.name), ir.Block(stmts), modules, annotations) + InjectStatement(ModuleTarget(circuit, module.name), ir.Block(stmts.toSeq), modules, annotations) }.toSeq } } diff --git a/src/main/scala/chisel3/compatibility.scala b/src/main/scala/chisel3/compatibility.scala index 3b9a3dcc..dde2321d 100644 --- a/src/main/scala/chisel3/compatibility.scala +++ b/src/main/scala/chisel3/compatibility.scala @@ -398,8 +398,8 @@ package object Chisel { } // Deprecated as of Chsiel3 - @throws(classOf[Exception]) object throwException { + @throws(classOf[Exception]) def apply(s: String, t: Throwable = null): Nothing = { val xcpt = new Exception(s, t) throw xcpt diff --git a/src/main/scala/chisel3/util/MixedVec.scala b/src/main/scala/chisel3/util/MixedVec.scala index a632ec3a..14d6be38 100644 --- a/src/main/scala/chisel3/util/MixedVec.scala +++ b/src/main/scala/chisel3/util/MixedVec.scala @@ -91,6 +91,10 @@ final class MixedVec[T <: Data](private val eltsIn: Seq[T]) extends Record with eltsIn.foreach(e => requireIsChiselType(e)) } + // In Scala 2.13, this is protected in IndexedSeq, must override as public because it's public in + // Record + override def className: String = "MixedVec" + // Clone the inputs so that we have our own references. private val elts: IndexedSeq[T] = eltsIn.map(_.cloneTypeFull).toIndexedSeq diff --git a/src/test/scala/chiselTests/ChiselSpec.scala b/src/test/scala/chiselTests/ChiselSpec.scala index 9503089a..37c4a2b7 100644 --- a/src/test/scala/chiselTests/ChiselSpec.scala +++ b/src/test/scala/chiselTests/ChiselSpec.scala @@ -292,7 +292,7 @@ trait Utils { exceptions.collectFirst{ case a: A => a } match { case Some(a) => throw a case None => exceptions match { - case Nil => Unit + case Nil => () case h :: t => throw h } } diff --git a/src/test/scala/chiselTests/ExtModule.scala b/src/test/scala/chiselTests/ExtModule.scala index 0c3a0633..161b6f5f 100644 --- a/src/test/scala/chiselTests/ExtModule.scala +++ b/src/test/scala/chiselTests/ExtModule.scala @@ -9,7 +9,7 @@ import chisel3.testers.{BasicTester, TesterDriver} // Avoid collisions with regular BlackBox tests by putting ExtModule blackboxes // in their own scope. -package ExtModule { +package extmoduletests { import chisel3.experimental.ExtModule @@ -25,8 +25,8 @@ package ExtModule { } class ExtModuleTester extends BasicTester { - val blackBoxPos = Module(new ExtModule.BlackBoxInverter) - val blackBoxNeg = Module(new ExtModule.BlackBoxInverter) + val blackBoxPos = Module(new extmoduletests.BlackBoxInverter) + val blackBoxNeg = Module(new extmoduletests.BlackBoxInverter) blackBoxPos.in := 1.U blackBoxNeg.in := 0.U @@ -42,10 +42,10 @@ class ExtModuleTester extends BasicTester { */ class MultiExtModuleTester extends BasicTester { - val blackBoxInvPos = Module(new ExtModule.BlackBoxInverter) - val blackBoxInvNeg = Module(new ExtModule.BlackBoxInverter) - val blackBoxPassPos = Module(new ExtModule.BlackBoxPassthrough) - val blackBoxPassNeg = Module(new ExtModule.BlackBoxPassthrough) + val blackBoxInvPos = Module(new extmoduletests.BlackBoxInverter) + val blackBoxInvNeg = Module(new extmoduletests.BlackBoxInverter) + val blackBoxPassPos = Module(new extmoduletests.BlackBoxPassthrough) + val blackBoxPassNeg = Module(new extmoduletests.BlackBoxPassthrough) blackBoxInvPos.in := 1.U blackBoxInvNeg.in := 0.U @@ -71,7 +71,7 @@ class ExtModuleSpec extends ChiselFlatSpec { "DataMirror.modulePorts" should "work with ExtModule" in { ChiselStage.elaborate(new Module { val io = IO(new Bundle { }) - val m = Module(new ExtModule.BlackBoxPassthrough) + val m = Module(new extmoduletests.BlackBoxPassthrough) assert(DataMirror.modulePorts(m) == Seq( "in" -> m.in, "out" -> m.out)) }) diff --git a/src/test/scala/examples/VendingMachineUtils.scala b/src/test/scala/examples/VendingMachineUtils.scala index 131256f8..6847768a 100644 --- a/src/test/scala/examples/VendingMachineUtils.scala +++ b/src/test/scala/examples/VendingMachineUtils.scala @@ -34,6 +34,6 @@ object VendingMachineUtils { value += incValue } } - outputs + outputs.toSeq } } -- cgit v1.2.3 From 7dd2d7db355d8dd9e1fc49ed7cd479ce5273b691 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Fri, 30 Apr 2021 05:12:24 +0000 Subject: add helper function to convert chirrtl to firrtl. (#1854) * add convert(chirrtl: cir.Circuit): fir.Circuit to convert chirrtl to firrtl. * add scaladoc. * add test. Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- src/main/scala/chisel3/stage/ChiselStage.scala | 20 ++++++++++++++++++++ .../scala/chiselTests/stage/ChiselStageSpec.scala | 7 +++++++ 2 files changed, 27 insertions(+) diff --git a/src/main/scala/chisel3/stage/ChiselStage.scala b/src/main/scala/chisel3/stage/ChiselStage.scala index 1bcf5124..989b3a17 100644 --- a/src/main/scala/chisel3/stage/ChiselStage.scala +++ b/src/main/scala/chisel3/stage/ChiselStage.scala @@ -172,6 +172,26 @@ object ChiselStage { .get } + /** Return a [[firrtl.ir.Circuit]] for a [[chisel3.internal.firrtl.Circuit]](aka chirrtl) + * @param chirrtl [[chisel3.internal.firrtl.Circuit]] which need to be converted to [[firrtl.ir.Circuit]] + */ + def convert(chirrtl: cir.Circuit): fir.Circuit = { + val phase = new ChiselPhase { + override val targets = Seq( + Dependency[chisel3.stage.phases.AddImplicitOutputFile], + Dependency[chisel3.stage.phases.AddImplicitOutputAnnotationFile], + Dependency[chisel3.stage.phases.MaybeAspectPhase], + Dependency[chisel3.stage.phases.Convert] ) + } + + phase + .transform(Seq(ChiselCircuitAnnotation(chirrtl))) + .collectFirst { + case FirrtlCircuitAnnotation(a) => a + } + .get + } + /** Return a CHIRRTL string for a Chisel module * @param gen a call-by-name Chisel module */ diff --git a/src/test/scala/chiselTests/stage/ChiselStageSpec.scala b/src/test/scala/chiselTests/stage/ChiselStageSpec.scala index 167e414b..7b6a2d39 100644 --- a/src/test/scala/chiselTests/stage/ChiselStageSpec.scala +++ b/src/test/scala/chiselTests/stage/ChiselStageSpec.scala @@ -88,6 +88,13 @@ class ChiselStageSpec extends AnyFlatSpec with Matchers with Utils { catchWrites { ChiselStage.convert(new Foo) } shouldBe a[Right[_, _]] } + ignore should "generate a FIRRTL circuit from a CHIRRTL circuit" in { + info("no files were written") + catchWrites { + ChiselStage.convert(ChiselStage.elaborate(new Foo)) + } shouldBe a[Right[_, _]] + } + behavior of "ChiselStage$.emitChirrtl" ignore should "generate a CHIRRTL string from a Chisel module" in { -- cgit v1.2.3 From 30b16a56533ce667d1a53eacd0f533fd7fa79981 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Tue, 4 May 2021 18:48:30 +0200 Subject: Update sbt-scoverage to 1.7.3 (#1899) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 9ad3d988..852afd87 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -4,7 +4,7 @@ resolvers += Classpaths.sbtPluginReleases resolvers += "jgit-repo" at "https://download.eclipse.org/jgit/maven" -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.7.0") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.7.3") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.0") -- cgit v1.2.3 From 365a51a8ce692c85df60427e0562e89945d9797d Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Wed, 5 May 2021 13:18:57 -0400 Subject: Remove chisel3.stage.phases.DriverCompatibility (#1772) --- .../chisel3/stage/phases/DriverCompatibility.scala | 152 +-------------------- .../stage/phases/DriverCompatibilitySpec.scala | 71 ---------- 2 files changed, 2 insertions(+), 221 deletions(-) delete mode 100644 src/test/scala/chisel3/stage/phases/DriverCompatibilitySpec.scala diff --git a/src/main/scala/chisel3/stage/phases/DriverCompatibility.scala b/src/main/scala/chisel3/stage/phases/DriverCompatibility.scala index 659914ae..9305c5c9 100644 --- a/src/main/scala/chisel3/stage/phases/DriverCompatibility.scala +++ b/src/main/scala/chisel3/stage/phases/DriverCompatibility.scala @@ -16,153 +16,5 @@ import chisel3.stage.{ChiselStage, NoRunFirrtlCompilerAnnotation, ChiselOutputFi * Primarily, this object includes [[firrtl.options.Phase Phase]]s that generate [[firrtl.annotations.Annotation]]s * derived from the deprecated [[firrtl.stage.phases.DriverCompatibility.TopNameAnnotation]]. */ -object DriverCompatibility { - - /** Adds a [[ChiselOutputFileAnnotation]] derived from a [[TopNameAnnotation]] if no [[ChiselOutputFileAnnotation]] - * already exists. If no [[TopNameAnnotation]] exists, then no [[firrtl.stage.OutputFileAnnotation]] is added. ''This is not a - * replacement for [[chisel3.stage.phases.AddImplicitOutputFile AddImplicitOutputFile]] as this only adds an output - * file based on a discovered top name and not on a discovered elaborated circuit.'' Consequently, this will provide - * the correct behavior before a circuit has been elaborated. - * @note the output suffix is unspecified and will be set by the underlying [[firrtl.EmittedComponent]] - */ - private [chisel3] class AddImplicitOutputFile extends Phase { - - override def prerequisites = Seq.empty - override def optionalPrerequisites = Seq.empty - override def optionalPrerequisiteOf = Seq(Dependency[chisel3.stage.ChiselStage]) - override def invalidates(a: Phase) = false - - def transform(annotations: AnnotationSeq): AnnotationSeq = { - val hasOutputFile = annotations - .collectFirst{ case a: ChiselOutputFileAnnotation => a } - .isDefined - lazy val top = annotations.collectFirst{ case TopNameAnnotation(a) => a } - - if (!hasOutputFile && top.isDefined) { - ChiselOutputFileAnnotation(top.get) +: annotations - } else { - annotations - } - } - } - - /** If a [[firrtl.options.OutputAnnotationFileAnnotation]] does not exist, this adds one derived from a - * [[TopNameAnnotation]]. ''This is not a replacement for [[chisel3.stage.phases.AddImplicitOutputAnnotationFile]] as - * this only adds an output annotation file based on a discovered top name.'' Consequently, this will provide the - * correct behavior before a circuit has been elaborated. - * @note the output suffix is unspecified and will be set by [[firrtl.options.phases.WriteOutputAnnotations]] - */ - private[chisel3] class AddImplicitOutputAnnotationFile extends Phase { - - override def prerequisites = Seq.empty - override def optionalPrerequisites = Seq.empty - override def optionalPrerequisiteOf = Seq(Dependency[chisel3.stage.ChiselStage]) - override def invalidates(a: Phase) = false - - def transform(annotations: AnnotationSeq): AnnotationSeq = - annotations - .collectFirst{ case _: OutputAnnotationFileAnnotation => annotations } - .getOrElse{ - val top = annotations.collectFirst{ case TopNameAnnotation(a) => a } - if (top.isDefined) { - OutputAnnotationFileAnnotation(top.get) +: annotations - } else { - annotations - } - } - } - - private[chisel3] case object RunFirrtlCompilerAnnotation extends NoTargetAnnotation - - /** Disables the execution of [[firrtl.stage.FirrtlStage]]. This can be used to call [[chisel3.stage.ChiselStage]] and - * guarantee that the FIRRTL compiler will not run. This is necessary for certain [[chisel3.Driver]] compatibility - * situations where you need to do something between Chisel compilation and FIRRTL compilations, e.g., update a - * mutable data structure. - */ - private[chisel3] class DisableFirrtlStage extends Phase { - - override def prerequisites = Seq.empty - override def optionalPrerequisites = Seq.empty - override def optionalPrerequisiteOf = Seq(Dependency[ChiselStage]) - override def invalidates(a: Phase) = false - - def transform(annotations: AnnotationSeq): AnnotationSeq = annotations - .collectFirst { case NoRunFirrtlCompilerAnnotation => annotations } - .getOrElse { Seq(RunFirrtlCompilerAnnotation, NoRunFirrtlCompilerAnnotation) ++ annotations } - } - - private[chisel3] class ReEnableFirrtlStage extends Phase { - - override def prerequisites = Seq(Dependency[DisableFirrtlStage], Dependency[ChiselStage]) - override def optionalPrerequisites = Seq.empty - override def optionalPrerequisiteOf = Seq.empty - override def invalidates(a: Phase) = false - - def transform(annotations: AnnotationSeq): AnnotationSeq = annotations - .collectFirst { case RunFirrtlCompilerAnnotation => - val a: AnnotationSeq = annotations.filter { - case NoRunFirrtlCompilerAnnotation | RunFirrtlCompilerAnnotation => false - case _ => true - } - a - } - .getOrElse{ annotations } - - } - - private[chisel3] case class OptionsManagerAnnotation( - manager: ExecutionOptionsManager with HasChiselExecutionOptions with HasFirrtlOptions) - extends NoTargetAnnotation with Unserializable - - /** Mutate an input [[firrtl.ExecutionOptionsManager]] based on information encoded in an [[firrtl.AnnotationSeq]]. - * This is intended to be run between [[chisel3.stage.ChiselStage ChiselStage]] and [[firrtl.stage.FirrtlStage]] if - * you want to have backwards compatibility with an [[firrtl.ExecutionOptionsManager]]. - */ - private[chisel3] class MutateOptionsManager extends Phase { - - override def prerequisites = Seq(Dependency[chisel3.stage.ChiselStage]) - override def optionalPrerequisites = Seq.empty - override def optionalPrerequisiteOf = Seq(Dependency[ReEnableFirrtlStage]) - override def invalidates(a: Phase) = false - - def transform(annotations: AnnotationSeq): AnnotationSeq = { - - val optionsManager = annotations - .collectFirst{ case OptionsManagerAnnotation(a) => a } - .getOrElse{ throw new OptionsException( - "An OptionsManagerException must exist for Chisel Driver compatibility mode") } - - val firrtlCircuit = annotations.collectFirst{ case FirrtlCircuitAnnotation(a) => a } - optionsManager.firrtlOptions = optionsManager.firrtlOptions.copy( - firrtlCircuit = firrtlCircuit, - annotations = optionsManager.firrtlOptions.annotations ++ annotations, - customTransforms = optionsManager.firrtlOptions.customTransforms ++ - annotations.collect{ case RunFirrtlTransformAnnotation(a) => a } ) - - annotations - - } - - } - - /** A [[Phase]] that lets us run - * @todo a better solution than the current state hack below may be needed - */ - private [chisel3] class FirrtlPreprocessing extends Phase { - - override def prerequisites = Seq(Dependency[ChiselStage], Dependency[MutateOptionsManager], Dependency[ReEnableFirrtlStage]) - override def optionalPrerequisites = Seq.empty - override def optionalPrerequisiteOf = Seq(Dependency[MaybeFirrtlStage]) - override def invalidates(a: Phase) = false - - private val phases = - Seq( new firrtl.stage.phases.DriverCompatibility.AddImplicitOutputFile, - new firrtl.stage.phases.DriverCompatibility.AddImplicitEmitter ) - - override def transform(annotations: AnnotationSeq): AnnotationSeq = - phases - .foldLeft(annotations)( (a, p) => p.transform(a) ) - - } - -} +@deprecated("This object contains no public members. This will be removed in Chisel 3.6.", "Chisel 3.5") +object DriverCompatibility diff --git a/src/test/scala/chisel3/stage/phases/DriverCompatibilitySpec.scala b/src/test/scala/chisel3/stage/phases/DriverCompatibilitySpec.scala deleted file mode 100644 index b80d5298..00000000 --- a/src/test/scala/chisel3/stage/phases/DriverCompatibilitySpec.scala +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chisel3.stage.phases - - -import chisel3.stage.{NoRunFirrtlCompilerAnnotation, ChiselOutputFileAnnotation} - -import firrtl.options.{OutputAnnotationFileAnnotation, StageOptions} -import firrtl.options.Viewer.view -import firrtl.stage.phases.DriverCompatibility.TopNameAnnotation -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers - -class DriverCompatibilitySpec extends AnyFlatSpec with Matchers { - - behavior of classOf[DriverCompatibility.AddImplicitOutputFile].toString - - it should "do nothing if a ChiselOutputFileAnnotation is present" in { - val annotations = Seq( - ChiselOutputFileAnnotation("Foo"), - TopNameAnnotation("Bar") ) - (new DriverCompatibility.AddImplicitOutputFile).transform(annotations).toSeq should be (annotations) - } - - it should "add a ChiselOutputFileAnnotation derived from a TopNameAnnotation" in { - val annotations = Seq( TopNameAnnotation("Bar") ) - val expected = ChiselOutputFileAnnotation("Bar") +: annotations - (new DriverCompatibility.AddImplicitOutputFile).transform(annotations).toSeq should be (expected) - } - - behavior of classOf[DriverCompatibility.AddImplicitOutputAnnotationFile].toString - - it should "do nothing if an OutputAnnotationFileAnnotation is present" in { - val annotations = Seq( - OutputAnnotationFileAnnotation("Foo"), - TopNameAnnotation("Bar") ) - (new DriverCompatibility.AddImplicitOutputAnnotationFile).transform(annotations).toSeq should be (annotations) - } - - it should "add an OutputAnnotationFileAnnotation derived from a TopNameAnnotation" in { - val annotations = Seq( TopNameAnnotation("Bar") ) - val expected = OutputAnnotationFileAnnotation("Bar") +: annotations - (new DriverCompatibility.AddImplicitOutputAnnotationFile).transform(annotations).toSeq should be (expected) - } - - behavior of classOf[DriverCompatibility.DisableFirrtlStage].toString - - it should "add a NoRunFirrtlCompilerAnnotation if one does not exist" in { - val annos = Seq(NoRunFirrtlCompilerAnnotation) - val expected = DriverCompatibility.RunFirrtlCompilerAnnotation +: annos - (new DriverCompatibility.DisableFirrtlStage).transform(Seq.empty).toSeq should be (expected) - } - - it should "NOT add a NoRunFirrtlCompilerAnnotation if one already exists" in { - val annos = Seq(NoRunFirrtlCompilerAnnotation) - (new DriverCompatibility.DisableFirrtlStage).transform(annos).toSeq should be (annos) - } - - behavior of classOf[DriverCompatibility.ReEnableFirrtlStage].toString - - it should "NOT strip a NoRunFirrtlCompilerAnnotation if NO RunFirrtlCompilerAnnotation is present" in { - val annos = Seq(NoRunFirrtlCompilerAnnotation, DriverCompatibility.RunFirrtlCompilerAnnotation) - (new DriverCompatibility.ReEnableFirrtlStage).transform(annos).toSeq should be (Seq.empty) - } - - it should "strip a NoRunFirrtlCompilerAnnotation if a RunFirrtlCompilerAnnotation is present" in { - val annos = Seq(NoRunFirrtlCompilerAnnotation) - (new DriverCompatibility.ReEnableFirrtlStage).transform(annos).toSeq should be (annos) - } - -} -- cgit v1.2.3 From 361e4433ac6f0db8564415f07258ae151a48affe Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Thu, 6 May 2021 17:58:35 +0000 Subject: add ShiftRegisters to expose register inside ShiftRegister. (#1723) * add ShiftRegisters to expose register inside ShiftRegister. * use Seq.iter for oneline implementation.--- src/main/scala/chisel3/util/Reg.scala | 43 ++++++++++++++++++++++------------- src/test/scala/chiselTests/Reg.scala | 18 +++++++++++++++ 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/main/scala/chisel3/util/Reg.scala b/src/main/scala/chisel3/util/Reg.scala index 982d80d0..1be6ea85 100644 --- a/src/main/scala/chisel3/util/Reg.scala +++ b/src/main/scala/chisel3/util/Reg.scala @@ -42,14 +42,7 @@ object ShiftRegister * val regDelayTwo = ShiftRegister(nextVal, 2, ena) * }}} */ - def apply[T <: Data](in: T, n: Int, en: Bool = true.B): T = { - // The order of tests reflects the expected use cases. - if (n != 0) { - RegEnable(apply(in, n-1, en), en) - } else { - in - } - } + def apply[T <: Data](in: T, n: Int, en: Bool = true.B): T = ShiftRegisters(in, n, en).last /** Returns the n-cycle delayed version of the input signal with reset initialization. * @@ -62,12 +55,30 @@ object ShiftRegister * val regDelayTwoReset = ShiftRegister(nextVal, 2, 0.U, ena) * }}} */ - def apply[T <: Data](in: T, n: Int, resetData: T, en: Bool): T = { - // The order of tests reflects the expected use cases. - if (n != 0) { - RegEnable(apply(in, n-1, resetData, en), resetData, en) - } else { - in - } - } + def apply[T <: Data](in: T, n: Int, resetData: T, en: Bool): T = ShiftRegisters(in, n, resetData, en).last +} + + +object ShiftRegisters +{ + /** Returns a sequence of delayed input signal registers from 1 to n. + * + * @param in input to delay + * @param n number of cycles to delay + * @param en enable the shift + * + */ + def apply[T <: Data](in: T, n: Int, en: Bool = true.B): Seq[T] = + Seq.iterate(in, n + 1)(util.RegEnable(_, en)).drop(1) + + /** Returns delayed input signal registers with reset initialization from 1 to n. + * + * @param in input to delay + * @param n number of cycles to delay + * @param resetData reset value for each register in the shift + * @param en enable the shift + * + */ + def apply[T <: Data](in: T, n: Int, resetData: T, en: Bool): Seq[T] = + Seq.iterate(in, n + 1)(util.RegEnable(_, resetData, en)).drop(1) } diff --git a/src/test/scala/chiselTests/Reg.scala b/src/test/scala/chiselTests/Reg.scala index d86fe8b4..21334aea 100644 --- a/src/test/scala/chiselTests/Reg.scala +++ b/src/test/scala/chiselTests/Reg.scala @@ -69,3 +69,21 @@ class ShiftRegisterSpec extends ChiselPropSpec { forAll(smallPosInts) { (shift: Int) => assertTesterPasses{ new ShiftResetTester(shift) } } } } + +class ShiftsTester(n: Int) extends BasicTester { + val (cntVal, done) = Counter(true.B, n) + val start = 23.U + val srs = ShiftRegisters(cntVal + start, n) + when(RegNext(done)) { + srs.zipWithIndex.foreach{ case (data, index) => + assert(data === (23 + n - 1 - index).U) + } + stop() + } +} + +class ShiftRegistersSpec extends ChiselPropSpec { + property("ShiftRegisters should shift") { + forAll(smallPosInts) { (shift: Int) => assertTesterPasses{ new ShiftsTester(shift) } } + } +} -- cgit v1.2.3 From 1f798a3a196b190cc5e100734f1f5479729431ac Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Fri, 7 May 2021 04:27:06 +0200 Subject: Update sbt-mima-plugin to 0.9.0 (#1900) Co-authored-by: Jiuyang Liu --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 852afd87..3d6f7902 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -20,7 +20,7 @@ addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.20" ) addSbtPlugin("com.eed3si9n" % "sbt-sriracha" % "0.1.0") -addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.8.1") +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.9.0") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") -- cgit v1.2.3 From c118facb82e43c010a6333c5281b956a7c9c7e20 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Sun, 9 May 2021 13:32:48 +0000 Subject: Fix ShiftRegister with 0 delay. (#1903) * Add test to check ShiftRegister(s) with delay is 0. This should break ShiftRegister(x, 0) since last is not exist in a empty Seq. Originally, test only test 1 to 4, which missed a potential bug from #1723. * Fix ShiftRegister with 0 delay. if ShiftRegisters is empty, java will complain: ``` java.util.NoSuchElementException scala.collection.LinearSeqOptimized.last(LinearSeqOptimized.scala:150) ``` This fix this issue and return `in` directly when ShiftRegister size is 0. Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- src/main/scala/chisel3/util/Reg.scala | 4 ++-- src/test/scala/chiselTests/Reg.scala | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/scala/chisel3/util/Reg.scala b/src/main/scala/chisel3/util/Reg.scala index 1be6ea85..e2b5d172 100644 --- a/src/main/scala/chisel3/util/Reg.scala +++ b/src/main/scala/chisel3/util/Reg.scala @@ -42,7 +42,7 @@ object ShiftRegister * val regDelayTwo = ShiftRegister(nextVal, 2, ena) * }}} */ - def apply[T <: Data](in: T, n: Int, en: Bool = true.B): T = ShiftRegisters(in, n, en).last + def apply[T <: Data](in: T, n: Int, en: Bool = true.B): T = ShiftRegisters(in, n, en).lastOption.getOrElse(in) /** Returns the n-cycle delayed version of the input signal with reset initialization. * @@ -55,7 +55,7 @@ object ShiftRegister * val regDelayTwoReset = ShiftRegister(nextVal, 2, 0.U, ena) * }}} */ - def apply[T <: Data](in: T, n: Int, resetData: T, en: Bool): T = ShiftRegisters(in, n, resetData, en).last + def apply[T <: Data](in: T, n: Int, resetData: T, en: Bool): T = ShiftRegisters(in, n, resetData, en).lastOption.getOrElse(in) } diff --git a/src/test/scala/chiselTests/Reg.scala b/src/test/scala/chiselTests/Reg.scala index 21334aea..a02e6fa5 100644 --- a/src/test/scala/chiselTests/Reg.scala +++ b/src/test/scala/chiselTests/Reg.scala @@ -7,6 +7,7 @@ import chisel3.util._ import chisel3.experimental.DataMirror import chisel3.stage.ChiselStage import chisel3.testers.BasicTester +import org.scalacheck.Gen class RegSpec extends ChiselFlatSpec { "Reg" should "be of the same type and width as t" in { @@ -55,18 +56,18 @@ class ShiftResetTester(n: Int) extends BasicTester { val start = 23.U val sr = ShiftRegister(cntVal + 23.U, n, 1.U, true.B) when(done) { - assert(sr === 1.U) + assert(sr === (if(n == 0) cntVal + 23.U else 1.U)) stop() } } class ShiftRegisterSpec extends ChiselPropSpec { property("ShiftRegister should shift") { - forAll(smallPosInts) { (shift: Int) => assertTesterPasses{ new ShiftTester(shift) } } + forAll(Gen.choose(0, 4)) { (shift: Int) => assertTesterPasses{ new ShiftTester(shift) } } } property("ShiftRegister should reset all values inside") { - forAll(smallPosInts) { (shift: Int) => assertTesterPasses{ new ShiftResetTester(shift) } } + forAll(Gen.choose(0, 4)) { (shift: Int) => assertTesterPasses{ new ShiftResetTester(shift) } } } } @@ -84,6 +85,6 @@ class ShiftsTester(n: Int) extends BasicTester { class ShiftRegistersSpec extends ChiselPropSpec { property("ShiftRegisters should shift") { - forAll(smallPosInts) { (shift: Int) => assertTesterPasses{ new ShiftsTester(shift) } } + forAll(Gen.choose(0, 4)) { (shift: Int) => assertTesterPasses{ new ShiftsTester(shift) } } } } -- cgit v1.2.3 From 994e8780f41cf64c1b9758745c6f81c7e3dd375c Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Mon, 10 May 2021 19:04:23 +0000 Subject: implement equal to BitPat. (#1867) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- src/main/scala/chisel3/util/BitPat.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/scala/chisel3/util/BitPat.scala b/src/main/scala/chisel3/util/BitPat.scala index 5a40d312..88e46e3c 100644 --- a/src/main/scala/chisel3/util/BitPat.scala +++ b/src/main/scala/chisel3/util/BitPat.scala @@ -108,6 +108,13 @@ sealed class BitPat(val value: BigInt, val mask: BigInt, width: Int) extends Sou def === (that: UInt): Bool = macro SourceInfoTransform.thatArg def =/= (that: UInt): Bool = macro SourceInfoTransform.thatArg + override def equals(obj: Any): Boolean = { + obj match { + case y: BitPat => value == y.value && mask == y.mask && getWidth == y.getWidth + case _ => false + } + } + /** @group SourceInfoTransformMacro */ def do_=== (that: UInt) (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = { -- cgit v1.2.3 From 950aae21c363c64e927d03274ece53490b0c6d15 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Tue, 11 May 2021 08:32:57 +0200 Subject: Update sbt-scoverage to 1.8.0 (#1905) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 3d6f7902..cef6f784 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -4,7 +4,7 @@ resolvers += Classpaths.sbtPluginReleases resolvers += "jgit-repo" at "https://download.eclipse.org/jgit/maven" -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.7.3") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.0") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.0") -- cgit v1.2.3 From 987e58f83996241aca6aa5f87395481184950436 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Tue, 11 May 2021 08:57:50 +0200 Subject: Update scalacheck-1-14, ... to 3.1.4.0 (#1575) * Update scalacheck-1-14, ... to 3.1.4.0 * Update scalacheck-1-14, ... to 3.1.4.0 * Update scalacheck-1-14 to 3.1.4.0 Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- build.sbt | 2 +- build.sc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 60b83a3e..ed3a4354 100644 --- a/build.sbt +++ b/build.sbt @@ -71,7 +71,7 @@ lazy val chiselSettings = Seq ( libraryDependencies ++= Seq( "org.scalatest" %% "scalatest" % "3.1.2" % "test", - "org.scalatestplus" %% "scalacheck-1-14" % "3.1.1.1" % "test", + "org.scalatestplus" %% "scalacheck-1-14" % "3.1.4.0" % "test", "com.github.scopt" %% "scopt" % "4.0.1" ), ) ++ ( diff --git a/build.sc b/build.sc index 80a96fa0..7e0a8019 100644 --- a/build.sc +++ b/build.sc @@ -106,7 +106,7 @@ class chisel3CrossModule(val crossScalaVersion: String) extends CommonModule wit override def ivyDeps = m.ivyDeps() ++ Agg( ivy"org.scalatest::scalatest:3.1.2", - ivy"org.scalatestplus::scalacheck-1-14:3.1.1.1", + ivy"org.scalatestplus::scalacheck-1-14:3.1.4.0", ivy"com.github.scopt::scopt:4.0.1" ) ++ m.treadleIvyDeps -- cgit v1.2.3 From fec860d6ae63cb46bf8b290c203aad290985bf3b Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Tue, 11 May 2021 09:20:00 +0200 Subject: Update sbt to 1.3.13 (#1501) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 797e7ccf..0837f7a1 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.3.10 +sbt.version=1.3.13 -- cgit v1.2.3 From 5e941a2300a12790c79dbc343ff41dcf8bfa77e4 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Wed, 12 May 2021 11:20:15 +0200 Subject: Update sbt to 1.5.2 (#1907) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 0837f7a1..19479ba4 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.3.13 +sbt.version=1.5.2 -- cgit v1.2.3 From 46b522df7a9162b073a1fe8453cdce891d86b802 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Wed, 12 May 2021 11:40:20 +0200 Subject: Update scalacheck-1-14 to 3.2.2.0 (#1908) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- build.sbt | 2 +- build.sc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index ed3a4354..b6971b8f 100644 --- a/build.sbt +++ b/build.sbt @@ -71,7 +71,7 @@ lazy val chiselSettings = Seq ( libraryDependencies ++= Seq( "org.scalatest" %% "scalatest" % "3.1.2" % "test", - "org.scalatestplus" %% "scalacheck-1-14" % "3.1.4.0" % "test", + "org.scalatestplus" %% "scalacheck-1-14" % "3.2.2.0" % "test", "com.github.scopt" %% "scopt" % "4.0.1" ), ) ++ ( diff --git a/build.sc b/build.sc index 7e0a8019..7ec6d23b 100644 --- a/build.sc +++ b/build.sc @@ -106,7 +106,7 @@ class chisel3CrossModule(val crossScalaVersion: String) extends CommonModule wit override def ivyDeps = m.ivyDeps() ++ Agg( ivy"org.scalatest::scalatest:3.1.2", - ivy"org.scalatestplus::scalacheck-1-14:3.1.4.0", + ivy"org.scalatestplus::scalacheck-1-14:3.2.2.0", ivy"com.github.scopt::scopt:4.0.1" ) ++ m.treadleIvyDeps -- cgit v1.2.3 From 91d0bb2d801e6649341943ae4fd8ed5ff1b20611 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Sat, 15 May 2021 15:34:53 +0200 Subject: Update sbt-mdoc to 2.2.21 (#1916) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index cef6f784..6021b078 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -16,7 +16,7 @@ addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.15") addSbtPlugin("com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "3.0.0") -addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.20" ) +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.21" ) addSbtPlugin("com.eed3si9n" % "sbt-sriracha" % "0.1.0") -- cgit v1.2.3 From 89dd22b875ae99dc728d33eb28ace05c8dfd9b6c Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Mon, 17 May 2021 05:03:53 +0200 Subject: Update sbt-mima-plugin to 0.9.1 (#1915) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 6021b078..5ee58c07 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -20,7 +20,7 @@ addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.21" ) addSbtPlugin("com.eed3si9n" % "sbt-sriracha" % "0.1.0") -addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.9.0") +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.9.1") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") -- cgit v1.2.3 From 5c35e17b142cd76f3ff333eb0edf05c2e8f79cfd Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Mon, 17 May 2021 17:37:32 +0200 Subject: Update scala-compiler, scala-library, ... to 2.13.6 --- .github/workflows/test.yml | 2 +- build.sbt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5d321bfa..6c7f14cc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - scala: [2.13.5, 2.12.13] + scala: [2.13.6, 2.12.13] container: image: ucbbar/chisel3-tools options: --user github --entrypoint /bin/bash diff --git a/build.sbt b/build.sbt index b6971b8f..d7b68ed9 100644 --- a/build.sbt +++ b/build.sbt @@ -15,8 +15,8 @@ lazy val commonSettings = Seq ( organization := "edu.berkeley.cs", version := "3.5-SNAPSHOT", autoAPIMappings := true, - scalaVersion := "2.13.5", - crossScalaVersions := Seq("2.13.5", "2.12.13"), + scalaVersion := "2.13.6", + crossScalaVersions := Seq("2.13.6", "2.12.13"), scalacOptions := Seq("-deprecation", "-feature"), libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value, // Macros paradise is integrated into 2.13 but requires a scalacOption -- cgit v1.2.3 From 761447a2d88563e2b6c0a5a538abd4a2ebf70620 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Mon, 17 May 2021 18:52:30 +0000 Subject: remove scopt dependency. (#1917) --- build.sbt | 1 - build.sc | 1 - 2 files changed, 2 deletions(-) diff --git a/build.sbt b/build.sbt index d7b68ed9..7e9c55e6 100644 --- a/build.sbt +++ b/build.sbt @@ -72,7 +72,6 @@ lazy val chiselSettings = Seq ( libraryDependencies ++= Seq( "org.scalatest" %% "scalatest" % "3.1.2" % "test", "org.scalatestplus" %% "scalacheck-1-14" % "3.2.2.0" % "test", - "com.github.scopt" %% "scopt" % "4.0.1" ), ) ++ ( // Tests from other projects may still run concurrently diff --git a/build.sc b/build.sc index 7ec6d23b..35921d8a 100644 --- a/build.sc +++ b/build.sc @@ -107,7 +107,6 @@ class chisel3CrossModule(val crossScalaVersion: String) extends CommonModule wit override def ivyDeps = m.ivyDeps() ++ Agg( ivy"org.scalatest::scalatest:3.1.2", ivy"org.scalatestplus::scalacheck-1-14:3.2.2.0", - ivy"com.github.scopt::scopt:4.0.1" ) ++ m.treadleIvyDeps override def moduleDeps = super.moduleDeps ++ treadleModule -- cgit v1.2.3 From 8c9007365d038d23b94bb4d1a6a7f20448f951eb Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Thu, 20 May 2021 03:09:24 +0000 Subject: implement model checking API for chiseltest (#1910) * add os-lib to dependency. * implement EndToEndSMTBaseSpec * rename to SMTModelCheckingSpec * add documentation. * fix for review.--- build.sbt | 1 + build.sc | 4 +- .../scala/chiselTests/SMTModelCheckingSpec.scala | 103 +++++++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 src/test/scala/chiselTests/SMTModelCheckingSpec.scala diff --git a/build.sbt b/build.sbt index 7e9c55e6..d15fc740 100644 --- a/build.sbt +++ b/build.sbt @@ -72,6 +72,7 @@ lazy val chiselSettings = Seq ( libraryDependencies ++= Seq( "org.scalatest" %% "scalatest" % "3.1.2" % "test", "org.scalatestplus" %% "scalacheck-1-14" % "3.2.2.0" % "test", + "com.lihaoyi" %% "os-lib" % "0.7.7", ), ) ++ ( // Tests from other projects may still run concurrently diff --git a/build.sc b/build.sc index 35921d8a..dbfe2438 100644 --- a/build.sc +++ b/build.sc @@ -44,7 +44,9 @@ trait CommonModule extends CrossSbtModule with PublishModule { override def moduleDeps = super.moduleDeps ++ firrtlModule - override def ivyDeps = super.ivyDeps() ++ firrtlIvyDeps + override def ivyDeps = super.ivyDeps() ++ Agg( + ivy"com.lihaoyi::os-lib:0.7.7", + ) ++ firrtlIvyDeps def publishVersion = "3.5-SNAPSHOT" diff --git a/src/test/scala/chiselTests/SMTModelCheckingSpec.scala b/src/test/scala/chiselTests/SMTModelCheckingSpec.scala new file mode 100644 index 00000000..6a24ff10 --- /dev/null +++ b/src/test/scala/chiselTests/SMTModelCheckingSpec.scala @@ -0,0 +1,103 @@ +package chiselTests + +import chisel3.Module +import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage} +import firrtl.annotations.Annotation +import firrtl.options.{OutputAnnotationFileAnnotation, TargetDirAnnotation} +import firrtl.stage.OutputFileAnnotation +import firrtl.util.BackendCompilationUtilities.timeStamp +import logger.{LazyLogging, LogLevel, LogLevelAnnotation} +import org.scalatest.flatspec.AnyFlatSpec +import os._ + +/** [[SMTModelCheckingSpec]] use z3 and [[firrtl.backends.experimental.smt]] library + * to solve `assert/assume` in [[chisel3.experimental.verification]], + * It is a copy&paste version from `firrtl.backends.experimental.smt.end2end.EndToEndSMTBaseSpec` from firrtl + * Useful to check combinational logic and some small test. + */ +abstract class SMTModelCheckingSpec extends AnyFlatSpec { + def success = MCSuccess + + def fail(k: Int) = MCFail(k) + + def test(dut: () => Module, name: String, expected: MCResult, kmax: Int = 0, annos: Seq[Annotation] = Seq()): Unit = { + expected match { + case MCFail(k) => + assert(kmax >= k, s"Please set a kmax that includes the expected failing step! ($kmax < $expected)") + case _ => + } + // @todo rewrite BackendCompilationUtilities + val testBaseDir = os.pwd / "test_run_dir" / name + os.makeDir.all(testBaseDir) + val testDir = os.temp.dir(testBaseDir, timeStamp, deleteOnExit = false) + val res = (new ChiselStage).execute( + Array("-E", "experimental-smt2"), + Seq( + LogLevelAnnotation(LogLevel.Error), // silence warnings for tests + ChiselGeneratorAnnotation(dut), + TargetDirAnnotation(testDir.toString) + ) ++ annos + ) + val top = res.collectFirst{case OutputAnnotationFileAnnotation(top) => top}.get + assert(res.collectFirst { case _: OutputFileAnnotation => true }.isDefined) + val r = Z3ModelChecker.bmc(testDir, top, kmax) + assert(r == expected) + } +} + +private object Z3ModelChecker extends LazyLogging { + def bmc(testDir: Path, main: String, kmax: Int): MCResult = { + assert(kmax >= 0 && kmax < 50, "Trying to keep kmax in a reasonable range.") + Seq.tabulate(kmax + 1) { k => + val stepFile = testDir / s"${main}_step$k.smt2" + os.copy(testDir / s"$main.smt2", stepFile) + os.write.append(stepFile, + s"""${step(main, k)} + |(check-sat) + |""".stripMargin) + val success = executeStep(stepFile) + if (!success) return MCFail(k) + } + MCSuccess + } + + private def executeStep(path: Path): Boolean = { + val (out, ret) = executeCmd(path.toString) + assert(ret == 0, s"expected success (0), not $ret: `$out`\nz3 ${path.toString}") + assert(out == "sat\n" || out == "unsat\n", s"Unexpected output: $out") + out == "unsat\n" + } + + private def executeCmd(cmd: String): (String, Int) = { + val process = os.proc("z3", cmd).call(stderr = ProcessOutput.Readlines(logger.warn(_))) + (process.out.chunks.mkString, process.exitCode) + } + + private def step(main: String, k: Int): String = { + // define all states + (0 to k).map(ii => s"(declare-fun s$ii () $main$StateTpe)") ++ + // assert that init holds in state 0 + List(s"(assert ($main$Init s0))") ++ + // assert transition relation + (0 until k).map(ii => s"(assert ($main$Transition s$ii s${ii + 1}))") ++ + // assert that assumptions hold in all states + (0 to k).map(ii => s"(assert ($main$Assumes s$ii))") ++ + // assert that assertions hold for all but last state + (0 until k).map(ii => s"(assert ($main$Asserts s$ii))") ++ + // check to see if we can violate the assertions in the last state + List(s"(assert (not ($main$Asserts s$k)))") + }.mkString("\n") + + // the following suffixes have to match the ones in [[SMTTransitionSystemEncoder]] + private val Transition = "_t" + private val Init = "_i" + private val Asserts = "_a" + private val Assumes = "_u" + private val StateTpe = "_s" +} +sealed trait MCResult + +case object MCSuccess extends MCResult + +case class MCFail(k: Int) extends MCResult + -- cgit v1.2.3 From 1c1a4d7217574a938ad0ce529803fb991f9903f0 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Thu, 20 May 2021 06:50:10 +0000 Subject: Implement PLA (#1912) * implement pla * implement test for pla * implement inverter matrix of PLA generator * fix for review. Co-authored-by: Boyang Han --- src/main/scala/chisel3/util/pla.scala | 119 +++++++++++++++++++++ .../chiselTests/util/experimental/PlaSpec.scala | 80 ++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 src/main/scala/chisel3/util/pla.scala create mode 100644 src/test/scala/chiselTests/util/experimental/PlaSpec.scala diff --git a/src/main/scala/chisel3/util/pla.scala b/src/main/scala/chisel3/util/pla.scala new file mode 100644 index 00000000..bb2b7996 --- /dev/null +++ b/src/main/scala/chisel3/util/pla.scala @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.util + +import chisel3._ + +object pla { + + /** Construct a [[https://en.wikipedia.org/wiki/Programmable_logic_array]] from specified table. + * + * Each position in the input matrix corresponds to an input variable where + * `0` implies the corresponding input literal appears complemented in the product term. + * `1` implies the input literal appears uncomplemented in the product term + * `?` implies the input literal does not appear in the product term. + * + * For each output + * a `1` means this product term makes the function value to `1` + * and a `0` or `?` means this product term make the function value to `0` + * + * @param table A [[Seq]] of inputs -> outputs mapping + * @param invert A [[BitPat]] specify which bit of the output should be inverted. `1` means the correspond position + * of the output should be inverted in the PLA, a `0` or a `?` means direct output from the OR matrix. + * @return the (input, output) [[Wire]] of [[UInt]] of the constructed pla. + * {{{ + * // A 1-of-8 decoder (like the 74xx138) can be constructed as follow + * val (inputs, outputs) = pla(Seq( + * (BitPat("b000"), BitPat("b00000001")), + * (BitPat("b001"), BitPat("b00000010")), + * (BitPat("b010"), BitPat("b00000100")), + * (BitPat("b011"), BitPat("b00001000")), + * (BitPat("b100"), BitPat("b00010000")), + * (BitPat("b101"), BitPat("b00100000")), + * (BitPat("b110"), BitPat("b01000000")), + * (BitPat("b111"), BitPat("b10000000")), + * )) + * }}} + */ + def apply(table: Seq[(BitPat, BitPat)], invert: BitPat = BitPat("b0")): (UInt, UInt) = { + require(table.nonEmpty, "pla table must not be empty") + + val (inputTerms, outputTerms) = table.unzip + require( + inputTerms.map(_.getWidth).distinct.size == 1, + "all `BitPat`s in the input part of specified PLA table must have the same width" + ) + require( + outputTerms.map(_.getWidth).distinct.size == 1, + "all `BitPat`s in the output part of specified PLA table must have the same width" + ) + + // now all inputs / outputs have the same width + val numberOfInputs = inputTerms.head.getWidth + val numberOfOutputs = outputTerms.head.getWidth + + val inverterMask = invert.value & invert.mask + if (inverterMask.bitCount != 0) + require(invert.getWidth == numberOfOutputs, + "non-zero inverter mask must have the same width as the output part of specified PLA table" + ) + + // input wires of the generated PLA + val inputs = Wire(UInt(numberOfInputs.W)) + val invInputs = ~inputs + + // output wires of the generated PLA + val outputs = Wire(UInt(numberOfOutputs.W)) + + // the AND matrix + // use `term -> AND line` map to reuse AND matrix output lines + val andMatrixOutputs: Map[String, Bool] = inputTerms.map { t => + t.toString -> Cat( + Seq + .tabulate(numberOfInputs) { i => + if (t.mask.testBit(i)) { + Some( + if (t.value.testBit(i)) inputs(i) + else invInputs(i) + ) + } else { + None + } + } + .flatten + ).andR() + }.toMap + + // the OR matrix + val orMatrixOutputs: UInt = Cat( + Seq + .tabulate(numberOfOutputs) { i => + val andMatrixLines = table + // OR matrix composed by input terms which makes this output bit a `1` + .filter { + case (_, or) => or.mask.testBit(i) && or.value.testBit(i) + }.map { + case (inputTerm, _) => + andMatrixOutputs(inputTerm.toString) + } + if (andMatrixLines.isEmpty) false.B + else Cat(andMatrixLines).orR() + } + .reverse + ) + + // the INV matrix, useful for decoders + val invMatrixOutputs: UInt = Cat( + Seq + .tabulate(numberOfOutputs) { i => + if (inverterMask.testBit(i)) ~orMatrixOutputs(i) + else orMatrixOutputs(i) + } + .reverse + ) + + outputs := invMatrixOutputs + + (inputs, outputs) + } +} diff --git a/src/test/scala/chiselTests/util/experimental/PlaSpec.scala b/src/test/scala/chiselTests/util/experimental/PlaSpec.scala new file mode 100644 index 00000000..45ac012e --- /dev/null +++ b/src/test/scala/chiselTests/util/experimental/PlaSpec.scala @@ -0,0 +1,80 @@ +package chiselTests.util.experimental + +import chisel3._ +import chisel3.stage.PrintFullStackTraceAnnotation +import chisel3.testers.BasicTester +import chisel3.util.{BitPat, pla} +import chiselTests.ChiselFlatSpec + +class PlaSpec extends ChiselFlatSpec { + "A 1-of-8 decoder (eg. 74xx138 without enables)" should "be generated correctly" in { + assertTesterPasses(new BasicTester { + val table = Seq( + (BitPat("b000"), BitPat("b00000001")), + (BitPat("b001"), BitPat("b00000010")), + (BitPat("b010"), BitPat("b00000100")), + (BitPat("b011"), BitPat("b00001000")), + (BitPat("b100"), BitPat("b00010000")), + (BitPat("b101"), BitPat("b00100000")), + (BitPat("b110"), BitPat("b01000000")), + (BitPat("b111"), BitPat("b10000000")), + ) + table.foreach { case (i, o) => + val (plaIn, plaOut) = pla(table) + plaIn := WireDefault(i.value.U(3.W)) + chisel3.assert(plaOut === o.value.U(8.W), "Input " + i.toString + " produced incorrect output BitPat(%b)", plaOut) + } + stop() + }) + } + + "An active-low 1-of-8 decoder (eg. inverted 74xx138 without enables)" should "be generated correctly" in { + assertTesterPasses(new BasicTester { + val table = Seq( + (BitPat("b000"), BitPat("b00000001")), + (BitPat("b001"), BitPat("b00000010")), + (BitPat("b010"), BitPat("b00000100")), + (BitPat("b011"), BitPat("b00001000")), + (BitPat("b100"), BitPat("b00010000")), + (BitPat("b101"), BitPat("b00100000")), + (BitPat("b110"), BitPat("b01000000")), + (BitPat("b111"), BitPat("b10000000")), + ) + table.foreach { case (i, o) => + val (plaIn, plaOut) = pla(table, BitPat("b11111111")) + plaIn := WireDefault(i.value.U(3.W)) + chisel3.assert(plaOut === ~o.value.U(8.W), "Input " + i.toString + " produced incorrect output BitPat(%b)", plaOut) + } + stop() + }) + } + + "A simple PLA" should "be generated correctly" in { + assertTesterPasses(new BasicTester { + val table = Seq( + (BitPat("b0000"), BitPat("b1")), + (BitPat("b0001"), BitPat("b1")), + (BitPat("b0010"), BitPat("b0")), + (BitPat("b0011"), BitPat("b1")), + (BitPat("b0100"), BitPat("b1")), + (BitPat("b0101"), BitPat("b0")), + (BitPat("b0110"), BitPat("b0")), + (BitPat("b0111"), BitPat("b0")), + (BitPat("b1000"), BitPat("b0")), + (BitPat("b1001"), BitPat("b0")), + (BitPat("b1010"), BitPat("b1")), + (BitPat("b1011"), BitPat("b0")), + (BitPat("b1100"), BitPat("b0")), + (BitPat("b1101"), BitPat("b1")), + (BitPat("b1110"), BitPat("b1")), + (BitPat("b1111"), BitPat("b1")), + ) + table.foreach { case (i, o) => + val (plaIn, plaOut) = pla(table) + plaIn := WireDefault(i.value.U(4.W)) + chisel3.assert(plaOut === o.value.U(1.W), "Input " + i.toString + " produced incorrect output BitPat(%b)", plaOut) + } + stop() + }) + } +} -- cgit v1.2.3 From 1875d3dae18a518d303472cb0b022c1180797b90 Mon Sep 17 00:00:00 2001 From: Martin Schoeberl Date: Thu, 20 May 2021 22:49:36 +0200 Subject: doc: link to developer style guide (#1929) --- docs/src/developers/developers.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/developers/developers.md b/docs/src/developers/developers.md index b2b5dff8..7072af6b 100644 --- a/docs/src/developers/developers.md +++ b/docs/src/developers/developers.md @@ -10,3 +10,4 @@ Tips and tricks for Chisel developers: * [Embedding Chisel as an sbt subproject](sbt-subproject) * [Test Coverage](test-coverage) +* [Developers Style Guide](style) -- cgit v1.2.3 From 9234ca1ebbf9ac725b58511786b3918bb1769c8b Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Fri, 21 May 2021 06:23:43 +0200 Subject: Update sbt-scalafix to 0.9.28 (#1931) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 5ee58c07..bd554d5a 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -25,4 +25,4 @@ addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.9.1") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") // From FIRRTL for building from source -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.27") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.28") -- cgit v1.2.3 From 48c6ce40a7be09be8192787debde79441957e020 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Fri, 21 May 2021 06:46:07 +0200 Subject: Update sbt-scoverage to 1.8.1 (#1924) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index bd554d5a..17540e98 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -4,7 +4,7 @@ resolvers += Classpaths.sbtPluginReleases resolvers += "jgit-repo" at "https://download.eclipse.org/jgit/maven" -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.0") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.1") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.0") -- cgit v1.2.3 From 3e65fb78769c8e4f7b737b021c6bbb52d1d7b7af Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Wed, 26 May 2021 01:29:25 +0800 Subject: make mill support 2.13. (#1934) --- build.sc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build.sc b/build.sc index dbfe2438..bd5a3944 100644 --- a/build.sc +++ b/build.sc @@ -5,7 +5,7 @@ import coursier.maven.MavenRepository import $ivy.`com.lihaoyi::mill-contrib-buildinfo:$MILL_VERSION` import mill.contrib.buildinfo.BuildInfo -object chisel3 extends mill.Cross[chisel3CrossModule]("2.12.13") +object chisel3 extends mill.Cross[chisel3CrossModule]("2.13.6", "2.12.13") // The following stanza is searched for and used when preparing releases. // Please retain it. @@ -62,14 +62,14 @@ trait CommonModule extends CrossSbtModule with PublishModule { super.scalacOptions() ++ Agg( "-deprecation", "-feature" - ) + ) ++ (if (majorVersion == 13) Agg("-Ymacro-annotations") else Agg.empty[String]) } private val macroParadise = ivy"org.scalamacros:::paradise:2.1.1" - override def compileIvyDeps = Agg(macroParadise) + override def compileIvyDeps = if(majorVersion == 13) super.compileIvyDeps else Agg(macroParadise) - override def scalacPluginIvyDeps = Agg(macroParadise) + override def scalacPluginIvyDeps = if(majorVersion == 13) super.compileIvyDeps else Agg(macroParadise) def pomSettings = PomSettings( description = artifactName(), @@ -179,8 +179,8 @@ class chisel3CrossModule(val crossScalaVersion: String) extends CommonModule wit override def firrtlModule = m.firrtlModule override def ivyDeps = Agg( - ivy"${scalaOrganization()}:scala-library:$crossScalaVersion" - ) + ivy"${scalaOrganization()}:scala-library:$crossScalaVersion", + ) ++ (if (majorVersion == 13) Agg(ivy"${scalaOrganization()}:scala-compiler:$crossScalaVersion") else Agg.empty[Dep]) def scalacOptions = T { Seq( -- cgit v1.2.3 From 6ccff4848fcf55cb1e865738a67f2bec25988ac8 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Wed, 26 May 2021 02:35:34 +0800 Subject: throw exception if BitPat width is 0 (#1920) * spot a bug when BitPat width is 0 * fix #1919 Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- src/main/scala/chisel3/util/BitPat.scala | 1 + src/test/scala/chiselTests/util/BitPatSpec.scala | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/main/scala/chisel3/util/BitPat.scala b/src/main/scala/chisel3/util/BitPat.scala index 88e46e3c..985d22da 100644 --- a/src/main/scala/chisel3/util/BitPat.scala +++ b/src/main/scala/chisel3/util/BitPat.scala @@ -23,6 +23,7 @@ object BitPat { // If ? parsing is to be exposed, the return API needs further scrutiny // (especially with things like mask polarity). require(x.head == 'b', "BitPats must be in binary and be prefixed with 'b'") + require(x.length > 1, "BitPat width cannot be 0.") var bits = BigInt(0) var mask = BigInt(0) var count = 0 diff --git a/src/test/scala/chiselTests/util/BitPatSpec.scala b/src/test/scala/chiselTests/util/BitPatSpec.scala index a6c0acf7..ca8dd85c 100644 --- a/src/test/scala/chiselTests/util/BitPatSpec.scala +++ b/src/test/scala/chiselTests/util/BitPatSpec.scala @@ -14,4 +14,8 @@ class BitPatSpec extends AnyFlatSpec with Matchers { val testPattern = "0" * 32 + "1" * 32 + "?" * 32 + "?01" * 32 BitPat("b" + testPattern).toString should be (s"BitPat($testPattern)") } + + it should "not fail if BitPat width is 0" in { + intercept[IllegalArgumentException]{BitPat("b")} + } } -- cgit v1.2.3 From cc6d925f31ef5253b6af50146d79e20d8e575adb Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Wed, 26 May 2021 12:36:46 +0200 Subject: Update sbt-mima-plugin to 0.9.2 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 17540e98..b4dfb638 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -20,7 +20,7 @@ addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.21" ) addSbtPlugin("com.eed3si9n" % "sbt-sriracha" % "0.1.0") -addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.9.1") +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.9.2") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") -- cgit v1.2.3 From 461691810094f5ca6722f9e5bbd3a1304cec36fd Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Sat, 29 May 2021 06:27:08 +0800 Subject: remove testOnly, since upstream implemented it. (#1946) --- build.sc | 6 ------ 1 file changed, 6 deletions(-) diff --git a/build.sc b/build.sc index bd5a3944..59f30c79 100644 --- a/build.sc +++ b/build.sc @@ -116,12 +116,6 @@ class chisel3CrossModule(val crossScalaVersion: String) extends CommonModule wit def testFrameworks = T { Seq("org.scalatest.tools.Framework") } - - // a sbt-like testOnly command. - // for example, mill -i "chisel3[2.12.12].test.testOnly" "chiselTests.BitwiseOpsSpec" - def testOnly(args: String*) = T.command { - super.runMain("org.scalatest.run", args: _*) - } } override def buildInfoPackageName = Some("chisel3") -- cgit v1.2.3 From ac31c62e4360aedca97605b7fe4471c8611051f3 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Wed, 2 Jun 2021 13:22:57 +0800 Subject: CCC schedule update (#1947) --- README.md | 61 +++++-------------------------------------------------------- 1 file changed, 5 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 1d91840a..91aef6b6 100644 --- a/README.md +++ b/README.md @@ -9,62 +9,11 @@ Chisel/FIRRTL development meetings happen every Monday and Tuesday from 1100--12 Call-in info and meeting notes are available [here](https://docs.google.com/document/d/1BLP2DYt59DqI-FgFCcjw8Ddl4K-WU0nHmQu0sZ_wAGo/). -### Chisel Community Conference 2021, Shanghai, China. 6/24/2021 - -The Chisel Community Conference China 2021 (CCC2021) is planned for June 25, -2021 at the ShanghaiTech University. CCC is an annual gathering of Chisel -community enthusiasts and technical exchange workshop. -With the support of the Chisel development community, this conference will -bring together designers and developers with hands-on experience in Chisel -from home and abroad to share cutting-edge results and experiences from the -open source community and industry. - -Session topics include and are not limited to -* CPU Core (recommended but not restricted to RISC-V) implementations -* SoC implementations -* Verification -* Simulation -* Synthesis -* Education -* Experience sharing - -Types of manuscripts. -* Technical Presentations: case studies or problem-oriented presentations on -original research, breakthrough ideas, or insights into future trends. -Sessions should provide specific examples and include both practical and -theoretical information. The length of time is about 20 minutes. -* Lightning talks: 5 to 10 minutes, either pre-registered or on-site (depending -on the time of the conference), can present and promote a specific Chisel -project. - -The presentation submission language is required to be in English, and both -English and Chinese are acceptable for the presentation language. -Reviewers (subject to change at that time). -* Jack Koenig -* Adam Izraelevitz -* Edward Wang -* Jiuyang Liu - -Key Timeline. -Submission deadline: April 25, 2021 -Manuscript topics and abstracts should be submitted by the submission -deadline, and will be reviewed and selected by Chisel developers. -Notification of acceptance: by May 12, 2021 -Final manuscript deadline: May 30, 2021 -A full version of the manuscript should be submitted by the final deadline, and -Chisel developers will quality review and suggest final changes. - -Mail submission method. -``` -Subject: - [CCC] Your Topic -CC: Jiuyang Liu -CC: Jack Koenig -CC: Adam Izraelevitz -CC: Edward Wang -Body: Abstract of your paper. -Attachment: pdf only slides -All submissions are welcome. -``` +### Chisel Community Conference 2021, Shanghai, China. 6/26/2021(CST) +The Chisel Community Conference China 2021 (CCC2021) is planned for June 26, 2021(CST) at the ShanghaiTech University. The schedule is available [here](https://docs.google.com/spreadsheets/d/1Gb4mMGRhs9exJW-l3NOS0IFi7fDplNQQnkXyAKzHlTg) +CCC is an annual gathering of Chisel community enthusiasts and technical exchange workshop. With the support of the Chisel development community and RISC-V World Conference China 2021 Committee, this conference will bring together designers and developers with hands-on experience in Chisel from home and abroad to share cutting-edge results and experiences from both the open source community as well as industry. +Online signup is not required. Zoom/YouTube/BiliBili streaming links will be added to the schedule before the start of the conference. +Offline signup [link](https://www.bagevent.com/event/registerTicket/7314534), Due to COVID-19 restrictions, offline registration is open only to current residents already present in mainland China. --- -- cgit v1.2.3 From f6ca84405cd146c64f3eb6f29bf7448a0b98f057 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Fri, 4 Jun 2021 12:28:59 +0200 Subject: Update sbt-scoverage to 1.8.2 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index b4dfb638..27eb7c05 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -4,7 +4,7 @@ resolvers += Classpaths.sbtPluginReleases resolvers += "jgit-repo" at "https://download.eclipse.org/jgit/maven" -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.1") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.0") -- cgit v1.2.3 From 682c73d0021c07c62aa4daf6548088e136490204 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Fri, 4 Jun 2021 13:58:20 +0200 Subject: Update os-lib to 0.7.8 (#1949) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- build.sbt | 2 +- build.sc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index d15fc740..2da958bc 100644 --- a/build.sbt +++ b/build.sbt @@ -72,7 +72,7 @@ lazy val chiselSettings = Seq ( libraryDependencies ++= Seq( "org.scalatest" %% "scalatest" % "3.1.2" % "test", "org.scalatestplus" %% "scalacheck-1-14" % "3.2.2.0" % "test", - "com.lihaoyi" %% "os-lib" % "0.7.7", + "com.lihaoyi" %% "os-lib" % "0.7.8", ), ) ++ ( // Tests from other projects may still run concurrently diff --git a/build.sc b/build.sc index 59f30c79..8c5a258d 100644 --- a/build.sc +++ b/build.sc @@ -45,7 +45,7 @@ trait CommonModule extends CrossSbtModule with PublishModule { override def moduleDeps = super.moduleDeps ++ firrtlModule override def ivyDeps = super.ivyDeps() ++ Agg( - ivy"com.lihaoyi::os-lib:0.7.7", + ivy"com.lihaoyi::os-lib:0.7.8", ) ++ firrtlIvyDeps def publishVersion = "3.5-SNAPSHOT" -- cgit v1.2.3 From 81bc972bf3202577b6534b82d96b75f7abfbab5e Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Fri, 4 Jun 2021 14:19:26 +0200 Subject: Update sbt-scalafix to 0.9.29 (#1948) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 27eb7c05..ac844474 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -25,4 +25,4 @@ addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.9.2") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") // From FIRRTL for building from source -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.28") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.29") -- cgit v1.2.3 From 820200b75242dde2a66c8103fd53eb10afc7ff6b Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Thu, 10 Jun 2021 17:32:08 -0400 Subject: Stop Emitting BlackBoxResourceAnno (#1954) * Change HasBlackBoxResource to Resolve Resources Change HasBlackBoxResource to resolve resources immediately and emit BlackBoxInlineAnno instead of a BlackBoxResourceAnno. This removes the need for a FIRRTL compiler to grok the Java Resource API in order to handle BlackBoxResourceAnno. Emit BlackBoxInlineAnno from HasExtModuleResource instead of BlackBoxResourceAnno. Signed-off-by: Schuyler Eldridge --- src/main/scala/chisel3/util/BlackBoxUtils.scala | 33 +++++++++++++++++++++--- src/main/scala/chisel3/util/ExtModuleUtils.scala | 7 +++-- src/test/scala/chiselTests/BlackBoxImpl.scala | 15 +++++++++++ src/test/scala/chiselTests/ExtModuleImpl.scala | 16 ++++++++++++ 4 files changed, 66 insertions(+), 5 deletions(-) diff --git a/src/main/scala/chisel3/util/BlackBoxUtils.scala b/src/main/scala/chisel3/util/BlackBoxUtils.scala index 3cd704b3..443d7f3e 100644 --- a/src/main/scala/chisel3/util/BlackBoxUtils.scala +++ b/src/main/scala/chisel3/util/BlackBoxUtils.scala @@ -4,12 +4,39 @@ package chisel3.util import chisel3._ import chisel3.experimental.{ChiselAnnotation, RunFirrtlTransform} -import firrtl.transforms.{BlackBoxPathAnno, BlackBoxResourceAnno, BlackBoxInlineAnno, BlackBoxSourceHelper} +import firrtl.transforms.{BlackBoxPathAnno, BlackBoxResourceAnno, BlackBoxInlineAnno, BlackBoxSourceHelper, + BlackBoxNotFoundException} +import firrtl.annotations.ModuleName +import logger.LazyLogging + +private[util] object BlackBoxHelpers { + + implicit class BlackBoxInlineAnnoHelpers(anno: BlackBoxInlineAnno.type) extends LazyLogging { + /** Generate a BlackBoxInlineAnno from a Java Resource and a module name. */ + def fromResource(resourceName: String, moduleName: ModuleName) = try { + val blackBoxFile = os.resource / os.RelPath(resourceName.dropWhile(_ == '/')) + val contents = os.read(blackBoxFile) + if (contents.size > BigInt(2).pow(20)) { + val message = + s"Black box resource $resourceName, which will be converted to an inline annotation, is greater than 1 MiB." + + "This may affect compiler performance. Consider including this resource via a black box path." + logger.warn(message) + } + BlackBoxInlineAnno(moduleName, blackBoxFile.last, contents) + } catch { + case e: os.ResourceNotFoundException => + throw new BlackBoxNotFoundException(resourceName, e.getMessage) + } + } +} + +import BlackBoxHelpers._ trait HasBlackBoxResource extends BlackBox { self: BlackBox => - /** Copies a resource file to the target directory + /** Copies a Java resource containing some text into the output directory. This is typically used to copy a Verilog file + * to the final output directory, but may be used to copy any Java resource (e.g., a C++ testbench). * * Resource files are located in project_root/src/main/resources/. * Example of adding the resource file project_root/src/main/resources/blackbox.v: @@ -19,7 +46,7 @@ trait HasBlackBoxResource extends BlackBox { */ def addResource(blackBoxResource: String): Unit = { val anno = new ChiselAnnotation with RunFirrtlTransform { - def toFirrtl = BlackBoxResourceAnno(self.toNamed, blackBoxResource) + def toFirrtl = BlackBoxInlineAnno.fromResource(blackBoxResource, self.toNamed) def transformClass = classOf[BlackBoxSourceHelper] } chisel3.experimental.annotate(anno) diff --git a/src/main/scala/chisel3/util/ExtModuleUtils.scala b/src/main/scala/chisel3/util/ExtModuleUtils.scala index 831639be..62f384bc 100644 --- a/src/main/scala/chisel3/util/ExtModuleUtils.scala +++ b/src/main/scala/chisel3/util/ExtModuleUtils.scala @@ -3,7 +3,10 @@ package chisel3.util import chisel3.experimental.{ChiselAnnotation, ExtModule, RunFirrtlTransform} -import firrtl.transforms.{BlackBoxPathAnno, BlackBoxResourceAnno, BlackBoxInlineAnno, BlackBoxSourceHelper} +import firrtl.transforms.{BlackBoxPathAnno, BlackBoxResourceAnno, BlackBoxInlineAnno, BlackBoxSourceHelper, + BlackBoxNotFoundException} + +import BlackBoxHelpers._ trait HasExtModuleResource extends ExtModule { self: ExtModule => @@ -18,7 +21,7 @@ trait HasExtModuleResource extends ExtModule { */ def addResource(blackBoxResource: String): Unit = { val anno = new ChiselAnnotation with RunFirrtlTransform { - def toFirrtl = BlackBoxResourceAnno(self.toNamed, blackBoxResource) + def toFirrtl = BlackBoxInlineAnno.fromResource(blackBoxResource, self.toNamed) def transformClass = classOf[BlackBoxSourceHelper] } chisel3.experimental.annotate(anno) diff --git a/src/test/scala/chiselTests/BlackBoxImpl.scala b/src/test/scala/chiselTests/BlackBoxImpl.scala index f8e16ad7..a9a6fa29 100644 --- a/src/test/scala/chiselTests/BlackBoxImpl.scala +++ b/src/test/scala/chiselTests/BlackBoxImpl.scala @@ -8,6 +8,7 @@ import chisel3._ import chisel3.util.{HasBlackBoxInline, HasBlackBoxResource, HasBlackBoxPath} import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage} import firrtl.FirrtlExecutionSuccess +import firrtl.transforms.BlackBoxNotFoundException import org.scalacheck.Test.Failed import org.scalatest.Succeeded import org.scalatest.freespec.AnyFreeSpec @@ -88,6 +89,15 @@ class UsesBlackBoxMinusViaPath extends Module { io.out := mod0.io.out } +class BlackBoxResourceNotFound extends HasBlackBoxResource { + val io = IO(new Bundle{}) + addResource("/missing.resource") +} + +class UsesMissingBlackBoxResource extends RawModule { + val foo = Module(new BlackBoxResourceNotFound) +} + class BlackBoxImplSpec extends AnyFreeSpec with Matchers { val targetDir = "test_run_dir" val stage = new ChiselStage @@ -114,5 +124,10 @@ class BlackBoxImplSpec extends AnyFreeSpec with Matchers { verilogOutput.delete() Succeeded } + "Resource files that do not exist produce Chisel errors" in { + assertThrows[BlackBoxNotFoundException]{ + ChiselStage.emitChirrtl(new UsesMissingBlackBoxResource) + } + } } } diff --git a/src/test/scala/chiselTests/ExtModuleImpl.scala b/src/test/scala/chiselTests/ExtModuleImpl.scala index f71a1335..e0a76201 100644 --- a/src/test/scala/chiselTests/ExtModuleImpl.scala +++ b/src/test/scala/chiselTests/ExtModuleImpl.scala @@ -11,6 +11,7 @@ import chisel3.util.{HasExtModuleInline, HasExtModulePath, HasExtModuleResource} import firrtl.FirrtlExecutionSuccess import firrtl.options.TargetDirAnnotation import firrtl.stage.FirrtlCircuitAnnotation +import firrtl.transforms.BlackBoxNotFoundException import org.scalacheck.Test.Failed import org.scalatest.{FreeSpec, Matchers, Succeeded} @@ -92,6 +93,15 @@ class UsesExtModuleMinusViaPath extends Module { io.out := mod0.io.out } +class ExtModuleResourceNotFound extends HasExtModuleResource { + val io = IO(new Bundle{}) + addResource("/missing.resource") +} + +class UsesMissingExtModuleResource extends RawModule { + val foo = Module(new ExtModuleResourceNotFound) +} + class ExtModuleImplSpec extends FreeSpec with Matchers { "ExtModule can have verilator source implementation" - { @@ -137,5 +147,11 @@ class ExtModuleImplSpec extends FreeSpec with Matchers { verilogOutput.exists() should be(true) verilogOutput.delete() } + + "Resource files that do not exist produce Chisel errors" in { + assertThrows[BlackBoxNotFoundException]{ + ChiselStage.emitChirrtl(new UsesMissingExtModuleResource) + } + } } } -- cgit v1.2.3 From ea84a17691c1fbea81644788b78699702c8faaf3 Mon Sep 17 00:00:00 2001 From: Deborah Soung Date: Mon, 14 Jun 2021 15:34:45 -0700 Subject: explain sub-projects in README (#1962) * explain sub-projects * Update README.md Co-authored-by: Megan Wachs Co-authored-by: Megan Wachs --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 91aef6b6..9aba4e8a 100644 --- a/README.md +++ b/README.md @@ -243,6 +243,17 @@ Also included is: - **Chisel Stage**, `chisel3.stage.*`, which contains compilation and test functions that are invoked in the standard Verilog generation and simulation testing infrastructure. These can also be used as part of custom flows. + +### Chisel Sub-Projects + +Chisel consists of 4 Scala projects; each is its own separate compilation unit: + +- [`core`](core) is the bulk of the source code of Chisel, depends on `macros` +- [`src/main`](src/main) is the "main" that brings it all together and includes a [`util`](src/main/scala/chisel3/util) library, which depends on `core` +- [`plugin`](plugin) is the compiler plugin, no internal dependencies +- [`macros`](macros) is most of the macros used in Chisel, no internal dependencies + +Code that touches lots of APIs that are private to the `chisel3` package should belong in `core`, while code that is pure Chisel should belong in `src/main`. ### Which version should I use? -- cgit v1.2.3 From 71575609ae7242585ed1008b8473acae1a42165e Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Thu, 6 May 2021 16:18:58 +0000 Subject: implement TruthTable to represent a decode table. --- .../util/experimental/decode/TruthTable.scala | 102 +++++++++++++++++++++ .../util/experimental/TruthTableSpec.scala | 39 ++++++++ 2 files changed, 141 insertions(+) create mode 100644 src/main/scala/chisel3/util/experimental/decode/TruthTable.scala create mode 100644 src/test/scala/chiselTests/util/experimental/TruthTableSpec.scala diff --git a/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala b/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala new file mode 100644 index 00000000..084403ac --- /dev/null +++ b/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.util.experimental.decode + +import chisel3.util.BitPat + +class TruthTable(val table: Map[BitPat, BitPat], val default: BitPat) { + require(table.map(_._1.getWidth).toSet.size == 1, "input width not equal.") + require(table.map(_._2.getWidth).toSet.size == 1, "output width not equal.") + + def inputWidth = table.head._1.getWidth + + def outputWidth = table.head._2.getWidth + + override def toString: String = { + def writeRow(map: (BitPat, BitPat)): String = + s"${TruthTable.bpStr(map._1)}->${TruthTable.bpStr(map._2)}" + + (table.map(writeRow) ++ Seq(s"${" "*(inputWidth + 2)}${TruthTable.bpStr(default)}")).toSeq.sorted.mkString("\n") + } + + def copy(table: Map[BitPat, BitPat] = this.table, default: BitPat = this.default) = new TruthTable(table, default) + + override def equals(y: Any): Boolean = { + y match { + case y: TruthTable => toString == y.toString + case _ => false + } + } +} + +object TruthTable { + /** Parse TruthTable from its string representation. */ + def apply(tableString: String): TruthTable = { + TruthTable( + tableString + .split("\n") + .filter(_.contains("->")) + .map(_.split("->").map(str => BitPat(s"b$str"))) + .map(bps => bps(0) -> bps(1)) + .toMap, + BitPat(s"b${tableString.split("\n").filterNot(_.contains("->")).head.replace(" ", "")}") + ) + } + + def apply(table: Map[BitPat, BitPat], default: BitPat) = new TruthTable(table, default) + + + /** consume 1 table, split it into up to 3 tables with the same default bits. + * + * @return table and its indexes from original bits. + * @note + * Since most of minimizer(like espresso) cannot handle a multiple default table. + * It is useful to split a table into 3 tables based on the default type. + */ + private[decode] def split( + table: TruthTable + ): Seq[(TruthTable, Seq[Int])] = { + def bpFilter(bitPat: BitPat, indexes: Seq[Int]): BitPat = + BitPat(s"b${bpStr(bitPat).zipWithIndex.filter(b => indexes.contains(b._2)).map(_._1).mkString}") + + def tableFilter(indexes: Seq[Int]): Option[(TruthTable, Seq[Int])] = { + if(indexes.nonEmpty) Some((TruthTable( + table.table.map { case (in, out) => in -> bpFilter(out, indexes) }, + bpFilter(table.default, indexes) + ), indexes)) else None + } + + def index(bitPat: BitPat, bpType: Char): Seq[Int] = + bpStr(bitPat).zipWithIndex.filter(_._1 == bpType).map(_._2) + + Seq('1', '0', '?').flatMap(t => tableFilter(index(table.default, t))) + } + + /** consume tables, merge it into single table with different default bits. + * + * @note + * Since most of minimizer(like espresso) cannot handle a multiple default table. + * It is useful to split a table into 3 tables based on the default type. + */ + private[decode] def merge( + tables: Seq[(TruthTable, Seq[Int])] + ): TruthTable = { + def reIndex(bitPat: BitPat, table: TruthTable, indexes: Seq[Int]): Seq[(Char, Int)] = + bpStr(table.table.getOrElse(bitPat, BitPat.dontCare(indexes.size))).zip(indexes) + def bitPat(indexedChar: Seq[(Char, Int)]) = BitPat(s"b${indexedChar + .sortBy(_._2) + .map(_._1) + .mkString}") + TruthTable( + tables + .flatMap(_._1.table.keys) + .map { key => + key -> bitPat(tables.flatMap { case (table, indexes) => reIndex(key, table, indexes) }) + } + .toMap, + bitPat(tables.flatMap { case (table, indexes) => bpStr(table.default).zip(indexes) }) + ) + } + + private def bpStr(bitPat: BitPat) = bitPat.toString.drop(7).dropRight(1) +} diff --git a/src/test/scala/chiselTests/util/experimental/TruthTableSpec.scala b/src/test/scala/chiselTests/util/experimental/TruthTableSpec.scala new file mode 100644 index 00000000..ed79f7f5 --- /dev/null +++ b/src/test/scala/chiselTests/util/experimental/TruthTableSpec.scala @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.util.experimental + +import chisel3.util.BitPat +import chisel3.util.experimental.decode.TruthTable +import org.scalatest.flatspec.AnyFlatSpec + +class TruthTableSpec extends AnyFlatSpec { + val table = TruthTable( + Map( + // BitPat("b000") -> BitPat("b0"), + BitPat("b001") -> BitPat("b?"), + BitPat("b010") -> BitPat("b?"), + // BitPat("b011") -> BitPat("b0"), + BitPat("b100") -> BitPat("b1"), + BitPat("b101") -> BitPat("b1"), + // BitPat("b110") -> BitPat("b0"), + BitPat("b111") -> BitPat("b1") + ), + BitPat("b0") + ) + val str = """001->? + |010->? + |100->1 + |101->1 + |111->1 + |0""".stripMargin + "TruthTable" should "serialize" in { + assert(table.toString contains "001->?") + assert(table.toString contains "010->?") + assert(table.toString contains "100->1") + assert(table.toString contains "111->1") + assert(table.toString contains " 0") + } + "TruthTable" should "deserialize" in { + assert(TruthTable(str) == table) + } +} -- cgit v1.2.3 From 1c9163bb05ff7e50885d1560a9df088ff1f2b49d Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Thu, 6 May 2021 16:15:14 +0000 Subject: implement DecodeTableAnnotation for decode table caching. --- .../util/experimental/decode/DecodeTableAnnotation.scala | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/main/scala/chisel3/util/experimental/decode/DecodeTableAnnotation.scala diff --git a/src/main/scala/chisel3/util/experimental/decode/DecodeTableAnnotation.scala b/src/main/scala/chisel3/util/experimental/decode/DecodeTableAnnotation.scala new file mode 100644 index 00000000..3a6957e2 --- /dev/null +++ b/src/main/scala/chisel3/util/experimental/decode/DecodeTableAnnotation.scala @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.util.experimental.decode + +import firrtl.annotations.{Annotation, ReferenceTarget, SingleTargetAnnotation} + +case class DecodeTableAnnotation( + target: ReferenceTarget, + truthTable: TruthTable, + minimizedTable: TruthTable) + extends SingleTargetAnnotation[ReferenceTarget] { + override def duplicate(n: ReferenceTarget): Annotation = this.copy(target = n) +} -- cgit v1.2.3 From 28eef17430d8bbca2765b5a2b0ab0337f7484840 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Sun, 23 May 2021 07:55:48 +0000 Subject: TruthTable can merge same inputs now. --- .../util/experimental/decode/TruthTable.scala | 27 ++++++++++++++++++---- .../util/experimental/TruthTableSpec.scala | 24 +++++++++++++++++++ 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala b/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala index 084403ac..37bfec77 100644 --- a/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala +++ b/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala @@ -4,9 +4,7 @@ package chisel3.util.experimental.decode import chisel3.util.BitPat -class TruthTable(val table: Map[BitPat, BitPat], val default: BitPat) { - require(table.map(_._1.getWidth).toSet.size == 1, "input width not equal.") - require(table.map(_._2.getWidth).toSet.size == 1, "output width not equal.") +sealed class TruthTable(val table: Map[BitPat, BitPat], val default: BitPat) { def inputWidth = table.head._1.getWidth @@ -38,12 +36,31 @@ object TruthTable { .filter(_.contains("->")) .map(_.split("->").map(str => BitPat(s"b$str"))) .map(bps => bps(0) -> bps(1)) - .toMap, + .toSeq, BitPat(s"b${tableString.split("\n").filterNot(_.contains("->")).head.replace(" ", "")}") ) } - def apply(table: Map[BitPat, BitPat], default: BitPat) = new TruthTable(table, default) + /** Convert a table and default output into a [[TruthTable]]. */ + def apply(table: Iterable[(BitPat, BitPat)], default: BitPat) = { + require(table.map(_._1.getWidth).toSet.size == 1, "input width not equal.") + require(table.map(_._2.getWidth).toSet.size == 1, "output width not equal.") + val outputWidth = table.map(_._2.getWidth).head + new TruthTable(table.toSeq.groupBy(_._1).map { case (key, values) => + // merge same input inputs. + key -> BitPat(s"b${ + Seq.tabulate(outputWidth) { i => + val outputSet = values.map(_._2) + .map(bpStr) + .map(_ (i)) + .toSet + .filterNot(_ == '?') + require(outputSet.size != 2, s"TruthTable conflict in :\n${values.map { case (i, o) => s"${bpStr(i)}->${bpStr(o)}" }.mkString("\n")}") + outputSet.headOption.getOrElse('?') + }.mkString + }") + }, default) + } /** consume 1 table, split it into up to 3 tables with the same default bits. diff --git a/src/test/scala/chiselTests/util/experimental/TruthTableSpec.scala b/src/test/scala/chiselTests/util/experimental/TruthTableSpec.scala index ed79f7f5..743a3cd8 100644 --- a/src/test/scala/chiselTests/util/experimental/TruthTableSpec.scala +++ b/src/test/scala/chiselTests/util/experimental/TruthTableSpec.scala @@ -36,4 +36,28 @@ class TruthTableSpec extends AnyFlatSpec { "TruthTable" should "deserialize" in { assert(TruthTable(str) == table) } + "TruthTable" should "merge same key" in { + assert( + TruthTable( + """001100->??1 + |001100->1?? + |??? + |""".stripMargin + ) == TruthTable( + """001100->1?1 + |??? + |""".stripMargin + ) + ) + } + "TruthTable" should "crash when merging 0 and 1" in { + intercept[IllegalArgumentException] { + TruthTable( + """0->0 + |0->1 + |??? + |""".stripMargin + ) + } + } } -- cgit v1.2.3 From f3318e52e4338dc558de8a900f93bc5757223642 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Sun, 23 May 2021 13:35:54 +0000 Subject: fix for 2.13 --- src/main/scala/chisel3/util/experimental/decode/TruthTable.scala | 4 ++-- 1 file changed, 2 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 37bfec77..cde9cf14 100644 --- a/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala +++ b/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala @@ -46,9 +46,9 @@ object TruthTable { require(table.map(_._1.getWidth).toSet.size == 1, "input width not equal.") require(table.map(_._2.getWidth).toSet.size == 1, "output width not equal.") val outputWidth = table.map(_._2.getWidth).head - new TruthTable(table.toSeq.groupBy(_._1).map { case (key, values) => + new TruthTable(table.toSeq.groupBy(_._1.toString).map { case (key, values) => // merge same input inputs. - key -> BitPat(s"b${ + values.head._1 -> BitPat(s"b${ Seq.tabulate(outputWidth) { i => val outputSet = values.map(_._2) .map(bpStr) -- cgit v1.2.3 From 819e806664da117172b6d46aa1adf83d39042d48 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Thu, 6 May 2021 16:16:52 +0000 Subject: implement abstract Minimizer as a general API. --- .../util/experimental/decode/Minimizer.scala | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/main/scala/chisel3/util/experimental/decode/Minimizer.scala diff --git a/src/main/scala/chisel3/util/experimental/decode/Minimizer.scala b/src/main/scala/chisel3/util/experimental/decode/Minimizer.scala new file mode 100644 index 00000000..86847710 --- /dev/null +++ b/src/main/scala/chisel3/util/experimental/decode/Minimizer.scala @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.util.experimental.decode + +abstract class Minimizer { + /** Minimize a multi-input multi-output logic function given by the truth table `table`, with function output values + * on unspecified inputs treated as `default`, and return a minimized PLA-like representation of the function. + * + * Each bit of `table[]._1` encodes one 1-bit input variable of the logic function, and each bit of `default` and + * `table[]._2` represents one 1-bit output value of the function. + * + * @param table Truth table, can have don't cares in both inputs and outputs, specified as [(inputs, outputs), ...] + * @return Minimized truth table, [(inputs, outputs), ...] + * + * @example {{{ + * minimize(BitPat("b?"), Seq( + * (BitPat("b000"), BitPat("b0")), + * // (BitPat("b001"), BitPat("b?")), // same as default, can be omitted + * // (BitPat("b010"), BitPat("b?")), // same as default, can be omitted + * (BitPat("b011"), BitPat("b0")), + * (BitPat("b100"), BitPat("b1")), + * (BitPat("b101"), BitPat("b1")), + * (BitPat("b110"), BitPat("b0")), + * (BitPat("b111"), BitPat("b1")), + * )) + * }}} + */ + def minimize(table: TruthTable): TruthTable +} \ No newline at end of file -- cgit v1.2.3 From 3691fe61cc43beb0e7d0f813723d4150daa79372 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Thu, 6 May 2021 16:20:50 +0000 Subject: add a simple decoder API. --- .../decode/DecodeTableAnnotation.scala | 4 +- .../chisel3/util/experimental/decode/decoder.scala | 53 ++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/main/scala/chisel3/util/experimental/decode/decoder.scala diff --git a/src/main/scala/chisel3/util/experimental/decode/DecodeTableAnnotation.scala b/src/main/scala/chisel3/util/experimental/decode/DecodeTableAnnotation.scala index 3a6957e2..cd289a5d 100644 --- a/src/main/scala/chisel3/util/experimental/decode/DecodeTableAnnotation.scala +++ b/src/main/scala/chisel3/util/experimental/decode/DecodeTableAnnotation.scala @@ -6,8 +6,8 @@ import firrtl.annotations.{Annotation, ReferenceTarget, SingleTargetAnnotation} case class DecodeTableAnnotation( target: ReferenceTarget, - truthTable: TruthTable, - minimizedTable: TruthTable) + truthTable: String, + minimizedTable: String) extends SingleTargetAnnotation[ReferenceTarget] { override def duplicate(n: ReferenceTarget): Annotation = this.copy(target = n) } diff --git a/src/main/scala/chisel3/util/experimental/decode/decoder.scala b/src/main/scala/chisel3/util/experimental/decode/decoder.scala new file mode 100644 index 00000000..2ef474e1 --- /dev/null +++ b/src/main/scala/chisel3/util/experimental/decode/decoder.scala @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.util.experimental.decode + +import chisel3._ +import chisel3.experimental.{ChiselAnnotation, annotate} +import chisel3.util.{BitPat, pla} +import chisel3.util.experimental.getAnnotations +import firrtl.annotations.Annotation +import logger.LazyLogging + +object decoder extends LazyLogging { + def apply(minimizer: Minimizer, input: UInt, truthTable: TruthTable): UInt = { + val minimizedTable = getAnnotations().collect { + case DecodeTableAnnotation(_, in, out) => TruthTable(in) -> TruthTable(out) + }.toMap.getOrElse( + { + logger.trace(s"""Decoder Cache Hit! + |${truthTable.table} + |""".stripMargin) + truthTable + }, { + val startTime = System.nanoTime() + val minimizedTable = minimizer.minimize(truthTable) + val totalTime = System.nanoTime() - startTime + val totalTimeInSeconds = totalTime / 1e9 + val info = f"Logic Minimize with $minimizer finished in ${totalTimeInSeconds} second" + if (totalTimeInSeconds > 5) + logger.error( + s"$info spends too long, consider using chisel3.util.experimental.DecodeTableAnnotation to cache decode result or switch to EspressoMinimizer." + ) + else logger.trace(info) + minimizedTable + } + ) + if (minimizedTable.table.isEmpty) { + val outputs = Wire(UInt(minimizedTable.default.getWidth.W)) + outputs := minimizedTable.default.value.U(minimizedTable.default.getWidth.W) + outputs + } else { + val (plaInput, plaOutput) = + pla(minimizedTable.table.toSeq, BitPat(minimizedTable.default.value.U(minimizedTable.default.getWidth.W))) + + annotate(new ChiselAnnotation { + override def toFirrtl: Annotation = + DecodeTableAnnotation(plaOutput.toTarget, truthTable.toString, minimizedTable.toString) + }) + + plaInput := input + plaOutput + } + } +} -- cgit v1.2.3 From d2c71fbf64a4728914414bbf27803d7cf03f94ad Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Tue, 25 May 2021 04:54:14 +0000 Subject: async decoder with 5 seconds timeout. --- .../chisel3/util/experimental/decode/decoder.scala | 25 ++++++++++++++-------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/main/scala/chisel3/util/experimental/decode/decoder.scala b/src/main/scala/chisel3/util/experimental/decode/decoder.scala index 2ef474e1..87e839d3 100644 --- a/src/main/scala/chisel3/util/experimental/decode/decoder.scala +++ b/src/main/scala/chisel3/util/experimental/decode/decoder.scala @@ -9,27 +9,34 @@ import chisel3.util.experimental.getAnnotations import firrtl.annotations.Annotation import logger.LazyLogging +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration.Duration +import scala.concurrent.{Await, Future} + object decoder extends LazyLogging { - def apply(minimizer: Minimizer, input: UInt, truthTable: TruthTable): UInt = { + def apply(minimizer: Minimizer, input: UInt, truthTable: TruthTable, timeout: Int = 5): UInt = { val minimizedTable = getAnnotations().collect { case DecodeTableAnnotation(_, in, out) => TruthTable(in) -> TruthTable(out) }.toMap.getOrElse( { - logger.trace(s"""Decoder Cache Hit! + logger.warn(s"""Decoder Cache Hit! |${truthTable.table} |""".stripMargin) truthTable }, { val startTime = System.nanoTime() - val minimizedTable = minimizer.minimize(truthTable) + val future = Future(minimizer.minimize(truthTable)) + var now = System.nanoTime() + while (!future.isCompleted) { + val elapsed = (System.nanoTime() - now) / 1e9 + now = System.nanoTime() + if(elapsed > timeout) logger.error(s"Minimizer has been executed for ${(System.nanoTime() - startTime) / 1e9} seconds.") + } + val minimizedTable = Await.result(future, Duration.Inf) val totalTime = System.nanoTime() - startTime val totalTimeInSeconds = totalTime / 1e9 - val info = f"Logic Minimize with $minimizer finished in ${totalTimeInSeconds} second" - if (totalTimeInSeconds > 5) - logger.error( - s"$info spends too long, consider using chisel3.util.experimental.DecodeTableAnnotation to cache decode result or switch to EspressoMinimizer." - ) - else logger.trace(info) + val info = f"Logic Minimize with $minimizer finished in $totalTimeInSeconds second" + logger.warn(info) minimizedTable } ) -- cgit v1.2.3 From 88b7c48c18267e55c755f4fe6615c2faf2910906 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Tue, 25 May 2021 07:22:32 +0000 Subject: remove all timeouts by review. --- .../chisel3/util/experimental/decode/decoder.scala | 30 ++-------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/src/main/scala/chisel3/util/experimental/decode/decoder.scala b/src/main/scala/chisel3/util/experimental/decode/decoder.scala index 87e839d3..8168824f 100644 --- a/src/main/scala/chisel3/util/experimental/decode/decoder.scala +++ b/src/main/scala/chisel3/util/experimental/decode/decoder.scala @@ -9,37 +9,11 @@ import chisel3.util.experimental.getAnnotations import firrtl.annotations.Annotation import logger.LazyLogging -import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.duration.Duration -import scala.concurrent.{Await, Future} - object decoder extends LazyLogging { - def apply(minimizer: Minimizer, input: UInt, truthTable: TruthTable, timeout: Int = 5): UInt = { + def apply(minimizer: Minimizer, input: UInt, truthTable: TruthTable): UInt = { val minimizedTable = getAnnotations().collect { case DecodeTableAnnotation(_, in, out) => TruthTable(in) -> TruthTable(out) - }.toMap.getOrElse( - { - logger.warn(s"""Decoder Cache Hit! - |${truthTable.table} - |""".stripMargin) - truthTable - }, { - val startTime = System.nanoTime() - val future = Future(minimizer.minimize(truthTable)) - var now = System.nanoTime() - while (!future.isCompleted) { - val elapsed = (System.nanoTime() - now) / 1e9 - now = System.nanoTime() - if(elapsed > timeout) logger.error(s"Minimizer has been executed for ${(System.nanoTime() - startTime) / 1e9} seconds.") - } - val minimizedTable = Await.result(future, Duration.Inf) - val totalTime = System.nanoTime() - startTime - val totalTimeInSeconds = totalTime / 1e9 - val info = f"Logic Minimize with $minimizer finished in $totalTimeInSeconds second" - logger.warn(info) - minimizedTable - } - ) + }.toMap.getOrElse(truthTable, minimizer.minimize(truthTable)) if (minimizedTable.table.isEmpty) { val outputs = Wire(UInt(minimizedTable.default.getWidth.W)) outputs := minimizedTable.default.value.U(minimizedTable.default.getWidth.W) -- cgit v1.2.3 From b0c76525ed53c20dbbe4bd8eea4a9676d7247ec7 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Tue, 18 May 2021 14:53:59 +0000 Subject: test decode cache. --- .../util/experimental/DecoderSpec.scala | 61 ++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/test/scala/chiselTests/util/experimental/DecoderSpec.scala diff --git a/src/test/scala/chiselTests/util/experimental/DecoderSpec.scala b/src/test/scala/chiselTests/util/experimental/DecoderSpec.scala new file mode 100644 index 00000000..3c9d490d --- /dev/null +++ b/src/test/scala/chiselTests/util/experimental/DecoderSpec.scala @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.util.experimental + +import chisel3.util.experimental.decode.{DecodeTableAnnotation, Minimizer, QMCMinimizer, TruthTable} +import chiselTests.SMTModelCheckingSpec +import chiselTests.util.experimental.minimizer.DecodeTestModule +import firrtl.annotations.ReferenceTarget + +class DecoderSpec extends SMTModelCheckingSpec { + val xor = TruthTable( + """10->1 + |01->1 + | 0""".stripMargin) + + def minimizer: Minimizer = QMCMinimizer + + "decoder" should "pass without DecodeTableAnnotation" in { + test( + () => new DecodeTestModule(minimizer, table = xor), + s"${minimizer.getClass.getSimpleName}.noAnno", + success + ) + } + + "decoder" should "fail with a incorrect DecodeTableAnnotation" in { + test( + () => new DecodeTestModule(minimizer, table = xor), + s"${minimizer.getClass.getSimpleName}.incorrectAnno", + fail(0), + annos = Seq( + DecodeTableAnnotation(ReferenceTarget("", "", Nil, "", Nil), + """10->1 + |01->1 + | 0""".stripMargin, + """10->1 + | 0""".stripMargin + ) + ) + ) + } + + "decoder" should "success with a correct DecodeTableAnnotation" in { + test( + () => new DecodeTestModule(minimizer, table = xor), + s"${minimizer.getClass.getSimpleName}.correctAnno", + success, + annos = Seq( + DecodeTableAnnotation(ReferenceTarget("", "", Nil, "", Nil), + """10->1 + |01->1 + | 0""".stripMargin, + QMCMinimizer.minimize(TruthTable( + """10->1 + |01->1 + | 0""".stripMargin)).toString + ) + ) + ) + } +} -- cgit v1.2.3 From 3a74d433560c8aca8bf9b2734a3ee620a3442117 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Thu, 6 May 2021 16:20:09 +0000 Subject: use z3 formal check minimized circuit and reference model. --- .../experimental/minimizer/MinimizerSpec.scala | 104 +++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala diff --git a/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala b/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala new file mode 100644 index 00000000..9651f241 --- /dev/null +++ b/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.util.experimental.minimizer + +import chisel3._ +import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage} +import chisel3.util._ +import chisel3.util.experimental.decode._ +import chisel3.util.pla +import firrtl.backends.experimental.smt.EmittedSMTModelAnnotation +import firrtl.options.TargetDirAnnotation +import firrtl.util.BackendCompilationUtilities +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class MinimizerSpec extends AnyFlatSpec with Matchers { + class DecodeTestModule(minimizer: Minimizer, table: TruthTable, reference: TruthTable) extends Module { + val i = IO(Input(UInt(table.table.head._1.getWidth.W))) + val (plaI, plaO) = pla(reference.table.toSeq, BitPat(reference.default.value.U)) + plaI := i + chisel3.experimental.verification.assert((decoder(minimizer, i, table) === plaO) | (i === reference.default)) + } + + def test(minimizer: Minimizer, testcase: (TruthTable, TruthTable)) = { + val testDir = os.pwd / "test_run_dir" / "MinimizerSpec" / BackendCompilationUtilities.timeStamp + os.makeDir.all(testDir) + val checkFile = testDir / "check.smt" + os.write(checkFile, + (new ChiselStage).execute( + Array("-E", "experimental-smt2"), + Seq( + ChiselGeneratorAnnotation(() => new DecodeTestModule(minimizer, table = testcase._1, testcase._2)), + TargetDirAnnotation(testDir.toString) + ) + ).collectFirst { + case EmittedSMTModelAnnotation(_, smt, _) => smt + }.get + + """; combinational logic check + |(declare-fun s0 () DecodeTestModule_s) + |(assert (not (DecodeTestModule_a s0))) + |(check-sat) + |""".stripMargin + ) + os.proc("z3", checkFile).call().out.toString + } + + val case0 = ( + TruthTable( + Map( + BitPat("b000") -> BitPat("b0"), + // (BitPat("b001") -> BitPat("b?")), // same as default, can be omitted + // (BitPat("b010") -> BitPat("b?")), // same as default, can be omitted + BitPat("b011") -> BitPat("b0"), + BitPat("b100") -> BitPat("b1"), + BitPat("b101") -> BitPat("b1"), + BitPat("b110") -> BitPat("b0"), + BitPat("b111") -> BitPat("b1") + ), + BitPat("b?") + ), + TruthTable( + Map( + BitPat("b000") -> BitPat("b0"), + // (BitPat("b001") -> BitPat("b?")), // same as default, can be omitted + // (BitPat("b010") -> BitPat("b?")), // same as default, can be omitted + BitPat("b011") -> BitPat("b0"), + BitPat("b100") -> BitPat("b1"), + BitPat("b101") -> BitPat("b1"), + BitPat("b110") -> BitPat("b0"), + BitPat("b111") -> BitPat("b1") + ), + BitPat("b?") + ) + ) + + val case1 = ( + TruthTable( + Map( + BitPat("b000") -> BitPat("b0"), + BitPat("b001") -> BitPat("b0"), + // (BitPat("b010") -> BitPat("b?")), // same as default, can be omitted + (BitPat("b011") -> BitPat("b0")), + // (BitPat("b100") -> BitPat("b?")), // same as default, can be omitted + // (BitPat("b101") -> BitPat("b?")), // same as default, can be omitted + (BitPat("b110") -> BitPat("b1")), + (BitPat("b111") -> BitPat("b0")) + ), + BitPat("b?") + ), + TruthTable( + Map( + BitPat("b000") -> BitPat("b0"), + BitPat("b001") -> BitPat("b0"), + // (BitPat("b010") -> BitPat("b?")), // same as default, can be omitted + (BitPat("b011") -> BitPat("b0")), + // (BitPat("b100") -> BitPat("b?")), // same as default, can be omitted + // (BitPat("b101") -> BitPat("b?")), // same as default, can be omitted + (BitPat("b110") -> BitPat("b1")), + (BitPat("b111") -> BitPat("b0")) + ), + BitPat("b?") + ), + ) +} -- cgit v1.2.3 From 38321a2eecfd7dc463ba640b2e97113043235b88 Mon Sep 17 00:00:00 2001 From: Boyang Han Date: Tue, 11 May 2021 05:55:38 -0700 Subject: Add minimized form of test cases --- .../experimental/minimizer/MinimizerSpec.scala | 34 +++++++--------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala b/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala index 9651f241..0db6c0e2 100644 --- a/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala +++ b/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala @@ -48,8 +48,8 @@ class MinimizerSpec extends AnyFlatSpec with Matchers { TruthTable( Map( BitPat("b000") -> BitPat("b0"), - // (BitPat("b001") -> BitPat("b?")), // same as default, can be omitted - // (BitPat("b010") -> BitPat("b?")), // same as default, can be omitted + // BitPat("b001") -> BitPat("b?"), // same as default, can be omitted + // BitPat("b010") -> BitPat("b?"), // same as default, can be omitted BitPat("b011") -> BitPat("b0"), BitPat("b100") -> BitPat("b1"), BitPat("b101") -> BitPat("b1"), @@ -60,14 +60,8 @@ class MinimizerSpec extends AnyFlatSpec with Matchers { ), TruthTable( Map( - BitPat("b000") -> BitPat("b0"), - // (BitPat("b001") -> BitPat("b?")), // same as default, can be omitted - // (BitPat("b010") -> BitPat("b?")), // same as default, can be omitted - BitPat("b011") -> BitPat("b0"), - BitPat("b100") -> BitPat("b1"), - BitPat("b101") -> BitPat("b1"), - BitPat("b110") -> BitPat("b0"), - BitPat("b111") -> BitPat("b1") + BitPat("b10?") -> BitPat("b1"), + BitPat("b1?1") -> BitPat("b1"), ), BitPat("b?") ) @@ -78,25 +72,19 @@ class MinimizerSpec extends AnyFlatSpec with Matchers { Map( BitPat("b000") -> BitPat("b0"), BitPat("b001") -> BitPat("b0"), - // (BitPat("b010") -> BitPat("b?")), // same as default, can be omitted + // BitPat("b010") -> BitPat("b?"), // same as default, can be omitted (BitPat("b011") -> BitPat("b0")), - // (BitPat("b100") -> BitPat("b?")), // same as default, can be omitted - // (BitPat("b101") -> BitPat("b?")), // same as default, can be omitted - (BitPat("b110") -> BitPat("b1")), - (BitPat("b111") -> BitPat("b0")) + // BitPat("b100") -> BitPat("b?"), // same as default, can be omitted + // BitPat("b101") -> BitPat("b?"), // same as default, can be omitted + BitPat("b110") -> BitPat("b1"), + BitPat("b111") -> BitPat("b0") ), BitPat("b?") ), TruthTable( Map( - BitPat("b000") -> BitPat("b0"), - BitPat("b001") -> BitPat("b0"), - // (BitPat("b010") -> BitPat("b?")), // same as default, can be omitted - (BitPat("b011") -> BitPat("b0")), - // (BitPat("b100") -> BitPat("b?")), // same as default, can be omitted - // (BitPat("b101") -> BitPat("b?")), // same as default, can be omitted - (BitPat("b110") -> BitPat("b1")), - (BitPat("b111") -> BitPat("b0")) + BitPat("b?10") -> BitPat("b1"), + // BitPat("b1?0") -> BitPat("b1"), // both are ok ), BitPat("b?") ), -- cgit v1.2.3 From ce755215d2657c81b4a2a161e0f04b1f6c59d5a1 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Wed, 12 May 2021 13:12:43 +0000 Subject: switch to EndToEndSMTBaseSpec --- .../experimental/minimizer/MinimizerSpec.scala | 78 +++++++--------------- 1 file changed, 24 insertions(+), 54 deletions(-) diff --git a/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala b/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala index 0db6c0e2..6513dc64 100644 --- a/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala +++ b/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala @@ -3,49 +3,36 @@ package chiselTests.util.experimental.minimizer import chisel3._ -import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage} import chisel3.util._ import chisel3.util.experimental.decode._ import chisel3.util.pla -import firrtl.backends.experimental.smt.EmittedSMTModelAnnotation -import firrtl.options.TargetDirAnnotation -import firrtl.util.BackendCompilationUtilities -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers +import chiselTests.SMTModelCheckingSpec -class MinimizerSpec extends AnyFlatSpec with Matchers { - class DecodeTestModule(minimizer: Minimizer, table: TruthTable, reference: TruthTable) extends Module { - val i = IO(Input(UInt(table.table.head._1.getWidth.W))) - val (plaI, plaO) = pla(reference.table.toSeq, BitPat(reference.default.value.U)) - plaI := i - chisel3.experimental.verification.assert((decoder(minimizer, i, table) === plaO) | (i === reference.default)) - } +class DecodeTestModule(minimizer: Minimizer, table: TruthTable) extends Module { + val i = IO(Input(UInt(table.table.head._1.getWidth.W))) + val (unminimizedI, unminimizedO) = pla(table.table.toSeq) + unminimizedI := i + val minimizedO: UInt = decoder(minimizer, i, table) + + chisel3.experimental.verification.assert( + // for each instruction, if input matches, output should match, not no matched, fallback to default + (table.table.map { case (key, value) => (i === key) && (minimizedO === value) } ++ + Seq(table.table.keys.map(i =/= _).reduce(_ && _) && minimizedO === table.default)).reduce(_ || _) + ) +} - def test(minimizer: Minimizer, testcase: (TruthTable, TruthTable)) = { - val testDir = os.pwd / "test_run_dir" / "MinimizerSpec" / BackendCompilationUtilities.timeStamp - os.makeDir.all(testDir) - val checkFile = testDir / "check.smt" - os.write(checkFile, - (new ChiselStage).execute( - Array("-E", "experimental-smt2"), - Seq( - ChiselGeneratorAnnotation(() => new DecodeTestModule(minimizer, table = testcase._1, testcase._2)), - TargetDirAnnotation(testDir.toString) - ) - ).collectFirst { - case EmittedSMTModelAnnotation(_, smt, _) => smt - }.get + - """; combinational logic check - |(declare-fun s0 () DecodeTestModule_s) - |(assert (not (DecodeTestModule_a s0))) - |(check-sat) - |""".stripMargin +trait MinimizerSpec extends SMTModelCheckingSpec { + def minimizer: Minimizer + + def minimizerTest(testcase: TruthTable, caseName: String) = { + test( + () => new DecodeTestModule(minimizer, table = testcase), + s"${minimizer.getClass.getSimpleName}.$caseName", + success ) - os.proc("z3", checkFile).call().out.toString } - val case0 = ( - TruthTable( + val case0 = TruthTable( Map( BitPat("b000") -> BitPat("b0"), // BitPat("b001") -> BitPat("b?"), // same as default, can be omitted @@ -57,18 +44,9 @@ class MinimizerSpec extends AnyFlatSpec with Matchers { BitPat("b111") -> BitPat("b1") ), BitPat("b?") - ), - TruthTable( - Map( - BitPat("b10?") -> BitPat("b1"), - BitPat("b1?1") -> BitPat("b1"), - ), - BitPat("b?") ) - ) - val case1 = ( - TruthTable( + val case1 = TruthTable( Map( BitPat("b000") -> BitPat("b0"), BitPat("b001") -> BitPat("b0"), @@ -80,13 +58,5 @@ class MinimizerSpec extends AnyFlatSpec with Matchers { BitPat("b111") -> BitPat("b0") ), BitPat("b?") - ), - TruthTable( - Map( - BitPat("b?10") -> BitPat("b1"), - // BitPat("b1?0") -> BitPat("b1"), // both are ok - ), - BitPat("b?") - ), - ) + ) } -- cgit v1.2.3 From 37ec9eb3c3008939605bddfad9096d9b2b47ccdc Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Sat, 15 May 2021 15:45:26 +0000 Subject: Add test cases. --- .../experimental/minimizer/MinimizerSpec.scala | 227 +++++++++++++++++++-- 1 file changed, 212 insertions(+), 15 deletions(-) diff --git a/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala b/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala index 6513dc64..a9e56800 100644 --- a/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala +++ b/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala @@ -32,11 +32,47 @@ trait MinimizerSpec extends SMTModelCheckingSpec { ) } - val case0 = TruthTable( + // Term that being commented out is the result of which is same as default, + // making optimization opportunities to decoder algorithms + + "case0" should "pass" in { + minimizerTest(TruthTable( + Map( + // BitPat("b000") -> BitPat("b0"), + BitPat("b001") -> BitPat("b?"), + BitPat("b010") -> BitPat("b?"), + // BitPat("b011") -> BitPat("b0"), + BitPat("b100") -> BitPat("b1"), + BitPat("b101") -> BitPat("b1"), + // BitPat("b110") -> BitPat("b0"), + BitPat("b111") -> BitPat("b1") + ), + BitPat("b0") + ), "case0") + } + + "case1" should "pass" in { + minimizerTest(TruthTable( Map( BitPat("b000") -> BitPat("b0"), - // BitPat("b001") -> BitPat("b?"), // same as default, can be omitted - // BitPat("b010") -> BitPat("b?"), // same as default, can be omitted + BitPat("b001") -> BitPat("b?"), + BitPat("b010") -> BitPat("b?"), + BitPat("b011") -> BitPat("b0"), + // BitPat("b100") -> BitPat("b1"), + // BitPat("b101") -> BitPat("b1"), + BitPat("b110") -> BitPat("b0"), + // BitPat("b111") -> BitPat("b1") + ), + BitPat("b1") + ), "case1") + } + + "caseX" should "pass" in { + minimizerTest(TruthTable( + Map( + BitPat("b000") -> BitPat("b0"), + // BitPat("b001") -> BitPat("b?"), + // BitPat("b010") -> BitPat("b?"), BitPat("b011") -> BitPat("b0"), BitPat("b100") -> BitPat("b1"), BitPat("b101") -> BitPat("b1"), @@ -44,19 +80,180 @@ trait MinimizerSpec extends SMTModelCheckingSpec { BitPat("b111") -> BitPat("b1") ), BitPat("b?") - ) + ), "caseX") + } - val case1 = TruthTable( + "caseMultiDefault" should "pass" in { + minimizerTest(TruthTable( Map( - BitPat("b000") -> BitPat("b0"), - BitPat("b001") -> BitPat("b0"), - // BitPat("b010") -> BitPat("b?"), // same as default, can be omitted - (BitPat("b011") -> BitPat("b0")), - // BitPat("b100") -> BitPat("b?"), // same as default, can be omitted - // BitPat("b101") -> BitPat("b?"), // same as default, can be omitted - BitPat("b110") -> BitPat("b1"), - BitPat("b111") -> BitPat("b0") + BitPat("b000") -> BitPat("b0100"), + BitPat("b001") -> BitPat("b?111"), + BitPat("b010") -> BitPat("b?000"), + BitPat("b011") -> BitPat("b0101"), + BitPat("b111") -> BitPat("b1101") ), - BitPat("b?") - ) + BitPat("b?100") + ), "caseMultiDefault") + } + + // A simple RV32I decode table example + "caseRV32I" should "pass" in { + val BEQ = "?????????????????000?????1100011" + val BNE = "?????????????????001?????1100011" + val BLT = "?????????????????100?????1100011" + val BGE = "?????????????????101?????1100011" + val BLTU = "?????????????????110?????1100011" + val BGEU = "?????????????????111?????1100011" + val JALR = "?????????????????000?????1100111" + val JAL = "?????????????????????????1101111" + val LUI = "?????????????????????????0110111" + val AUIPC = "?????????????????????????0010111" + val ADDI = "?????????????????000?????0010011" + val SLTI = "?????????????????010?????0010011" + val SLTIU = "?????????????????011?????0010011" + val XORI = "?????????????????100?????0010011" + val ORI = "?????????????????110?????0010011" + val ANDI = "?????????????????111?????0010011" + val ADD = "0000000??????????000?????0110011" + val SUB = "0100000??????????000?????0110011" + val SLL = "0000000??????????001?????0110011" + val SLT = "0000000??????????010?????0110011" + val SLTU = "0000000??????????011?????0110011" + val XOR = "0000000??????????100?????0110011" + val SRL = "0000000??????????101?????0110011" + val SRA = "0100000??????????101?????0110011" + val OR = "0000000??????????110?????0110011" + val AND = "0000000??????????111?????0110011" + val LB = "?????????????????000?????0000011" + val LH = "?????????????????001?????0000011" + val LW = "?????????????????010?????0000011" + val LBU = "?????????????????100?????0000011" + val LHU = "?????????????????101?????0000011" + val SB = "?????????????????000?????0100011" + val SH = "?????????????????001?????0100011" + val SW = "?????????????????010?????0100011" + val FENCE = "?????????????????000?????0001111" + val MRET = "00110000001000000000000001110011" + val WFI = "00010000010100000000000001110011" + val CEASE = "00110000010100000000000001110011" + val CSRRW = "?????????????????001?????1110011" + val CSRRS = "?????????????????010?????1110011" + val CSRRC = "?????????????????011?????1110011" + val CSRRWI = "?????????????????101?????1110011" + val CSRRSI = "?????????????????110?????1110011" + val CSRRCI = "?????????????????111?????1110011" + val SCALL = "00000000000000000000000001110011" + val SBREAK = "00000000000100000000000001110011" + val SLLI_RV32 = "0000000??????????001?????0010011" + val SRLI_RV32 = "0000000??????????101?????0010011" + val SRAI_RV32 = "0100000??????????101?????0010011" + + val A1_X = "??" + val A1_ZERO = "00" + val A1_RS1 = "01" + val A1_PC = "10" + + val IMM_X = "???" + val IMM_S = "000" + val IMM_SB = "001" + val IMM_U = "010" + val IMM_UJ = "011" + val IMM_I = "100" + val IMM_Z = "101" + + val A2_X = "??" + val A2_ZERO = "00" + val A2_SIZE = "01" + val A2_RS2 = "10" + val A2_IMM = "11" + + val X = "?" + val N = "0" + val Y = "1" + + val DW_X = X + val DW_XPR = Y + + val M_X = "?????" + val M_XRD = "00000" + val M_XWR = "00001" + + val CSR_X = "???" + val CSR_N = "000" + val CSR_I = "100" + val CSR_W = "101" + val CSR_S = "110" + val CSR_C = "111" + + val FN_X = "????" + val FN_ADD = "0000" + val FN_SL = "0001" + val FN_SEQ = "0010" + val FN_SNE = "0011" + val FN_XOR = "0100" + val FN_SR = "0101" + val FN_OR = "0110" + val FN_AND = "0111" + val FN_SUB = "1010" + val FN_SRA = "1011" + val FN_SLT = "1100" + val FN_SGE = "1101" + val FN_SLTU = "1110" + val FN_SGEU = "1111" + + minimizerTest(TruthTable( + Map( + BNE -> Seq(Y, N, N, Y, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_SB, DW_X, FN_SNE, N, M_X, N, N, N, N, N, N, N, CSR_N, N, N, N, N), + BEQ -> Seq(Y, N, N, Y, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_SB, DW_X, FN_SEQ, N, M_X, N, N, N, N, N, N, N, CSR_N, N, N, N, N), + BLT -> Seq(Y, N, N, Y, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_SB, DW_X, FN_SLT, N, M_X, N, N, N, N, N, N, N, CSR_N, N, N, N, N), + BLTU -> Seq(Y, N, N, Y, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_SB, DW_X, FN_SLTU, N, M_X, N, N, N, N, N, N, N, CSR_N, N, N, N, N), + BGE -> Seq(Y, N, N, Y, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_SB, DW_X, FN_SGE, N, M_X, N, N, N, N, N, N, N, CSR_N, N, N, N, N), + BGEU -> Seq(Y, N, N, Y, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_SB, DW_X, FN_SGEU, N, M_X, N, N, N, N, N, N, N, CSR_N, N, N, N, N), + JAL -> Seq(Y, N, N, N, Y, N, N, N, N, A2_SIZE, A1_PC, IMM_UJ, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + JALR -> Seq(Y, N, N, N, N, Y, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + AUIPC -> Seq(Y, N, N, N, N, N, N, N, N, A2_IMM, A1_PC, IMM_U, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + LB -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, Y, M_XRD, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + LH -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, Y, M_XRD, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + LW -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, Y, M_XRD, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + LBU -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, Y, M_XRD, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + LHU -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, Y, M_XRD, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SB -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_IMM, A1_RS1, IMM_S, DW_XPR, FN_ADD, Y, M_XWR, N, N, N, N, N, N, N, CSR_N, N, N, N, N), + SH -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_IMM, A1_RS1, IMM_S, DW_XPR, FN_ADD, Y, M_XWR, N, N, N, N, N, N, N, CSR_N, N, N, N, N), + SW -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_IMM, A1_RS1, IMM_S, DW_XPR, FN_ADD, Y, M_XWR, N, N, N, N, N, N, N, CSR_N, N, N, N, N), + LUI -> Seq(Y, N, N, N, N, N, N, N, N, A2_IMM, A1_ZERO, IMM_U, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + ADDI -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SLTI -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_SLT, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SLTIU -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_SLTU, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + ANDI -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_AND, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + ORI -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_OR, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + XORI -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_XOR, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + ADD -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SUB -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_SUB, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SLT -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_SLT, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SLTU -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_SLTU, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + AND -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_AND, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + OR -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_OR, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + XOR -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_XOR, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SLL -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_SL, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SRL -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_SR, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SRA -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_SRA, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + FENCE -> Seq(Y, N, N, N, N, N, N, N, N, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, N, N, N, N, N, N, N, CSR_N, N, Y, N, N), + SCALL -> Seq(Y, N, N, N, N, N, N, X, N, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, N, N, N, N, N, N, N, CSR_I, N, N, N, N), + SBREAK -> Seq(Y, N, N, N, N, N, N, X, N, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, N, N, N, N, N, N, N, CSR_I, N, N, N, N), + MRET -> Seq(Y, N, N, N, N, N, N, X, N, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, N, N, N, N, N, N, N, CSR_I, N, N, N, N), + WFI -> Seq(Y, N, N, N, N, N, N, X, N, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, N, N, N, N, N, N, N, CSR_I, N, N, N, N), + CEASE -> Seq(Y, N, N, N, N, N, N, X, N, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, N, N, N, N, N, N, N, CSR_I, N, N, N, N), + CSRRW -> Seq(Y, N, N, N, N, N, N, Y, N, A2_ZERO, A1_RS1, IMM_X, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_W, N, N, N, N), + CSRRS -> Seq(Y, N, N, N, N, N, N, Y, N, A2_ZERO, A1_RS1, IMM_X, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_S, N, N, N, N), + CSRRC -> Seq(Y, N, N, N, N, N, N, Y, N, A2_ZERO, A1_RS1, IMM_X, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_C, N, N, N, N), + CSRRWI -> Seq(Y, N, N, N, N, N, N, N, N, A2_IMM, A1_ZERO, IMM_Z, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_W, N, N, N, N), + CSRRSI -> Seq(Y, N, N, N, N, N, N, N, N, A2_IMM, A1_ZERO, IMM_Z, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_S, N, N, N, N), + CSRRCI -> Seq(Y, N, N, N, N, N, N, N, N, A2_IMM, A1_ZERO, IMM_Z, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_C, N, N, N, N), + SLLI_RV32 -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_SL, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SRLI_RV32 -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_SR, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SRAI_RV32 -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_SRA, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + ).map { case (k, v) => BitPat(s"b$k") -> BitPat(s"b${v.reduce(_ + _)}") }, + BitPat(s"b${Seq(N, X, X, X, X, X, X, X, X, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, X, X, X, X, X, X, X, CSR_X, X, X, X, X).reduce(_ + _)}") + ), "rv32i") + } } -- cgit v1.2.3 From c11fdf33981365927ea085b7aee095933a4a5ddf Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Mon, 14 Jun 2021 14:58:01 +0000 Subject: add documentation for DecodeTableAnnotation. --- .../chisel3/util/experimental/decode/DecodeTableAnnotation.scala | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/scala/chisel3/util/experimental/decode/DecodeTableAnnotation.scala b/src/main/scala/chisel3/util/experimental/decode/DecodeTableAnnotation.scala index cd289a5d..2b50ef90 100644 --- a/src/main/scala/chisel3/util/experimental/decode/DecodeTableAnnotation.scala +++ b/src/main/scala/chisel3/util/experimental/decode/DecodeTableAnnotation.scala @@ -4,6 +4,15 @@ package chisel3.util.experimental.decode import firrtl.annotations.{Annotation, ReferenceTarget, SingleTargetAnnotation} +/** DecodeTableAnnotation used to store a decode result for a specific [[TruthTable]]. + * This is useful for saving large [[TruthTable]] during a elaboration time. + * + * @note user should manage the correctness of [[minimizedTable]]. + * + * @param target output wire of a decoder. + * @param truthTable input [[TruthTable]] encoded in a serialized [[TruthTable]]. + * @param minimizedTable minimized [[truthTable]] encoded in a serialized [[TruthTable]]. + */ case class DecodeTableAnnotation( target: ReferenceTarget, truthTable: String, -- cgit v1.2.3 From 48548463c6c5c6bc9076c80924cb4683fc737f11 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Mon, 14 Jun 2021 22:39:42 +0800 Subject: Apply Jack's Review 1. `TruthTable` is final now. 2. add return type for `TruthTable` Co-authored-by: Jack Koenig --- src/main/scala/chisel3/util/experimental/decode/TruthTable.scala | 4 ++-- 1 file changed, 2 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 cde9cf14..ca0ff8b4 100644 --- a/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala +++ b/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala @@ -4,7 +4,7 @@ package chisel3.util.experimental.decode import chisel3.util.BitPat -sealed class TruthTable(val table: Map[BitPat, BitPat], val default: BitPat) { +final class TruthTable(val table: Map[BitPat, BitPat], val default: BitPat) { def inputWidth = table.head._1.getWidth @@ -42,7 +42,7 @@ object TruthTable { } /** Convert a table and default output into a [[TruthTable]]. */ - def apply(table: Iterable[(BitPat, BitPat)], default: BitPat) = { + def apply(table: Iterable[(BitPat, BitPat)], default: BitPat): TruthTable = { require(table.map(_._1.getWidth).toSet.size == 1, "input width not equal.") require(table.map(_._2.getWidth).toSet.size == 1, "output width not equal.") val outputWidth = table.map(_._2.getWidth).head -- cgit v1.2.3 From f48b4abd9b2e10a5243cadf3803f7f42d4ce25fc Mon Sep 17 00:00:00 2001 From: Boyang Han Date: Thu, 6 May 2021 16:27:11 +0000 Subject: implement QMC. --- .../util/experimental/decode/QMCMinimizer.scala | 282 +++++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala diff --git a/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala b/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala new file mode 100644 index 00000000..bc5ea351 --- /dev/null +++ b/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.util.experimental.decode + +import chisel3.util.BitPat + +import scala.annotation.tailrec +import scala.math.Ordered.orderingToOrdered +import scala.language.implicitConversions + +object QMCMinimizer extends Minimizer { + private implicit def toImplicant(x: BitPat): Implicant = new Implicant(x) + + private class Implicant(val bp: BitPat) { + var isPrime: Boolean = true + + def width = bp.getWidth + + override def equals(that: Any): Boolean = that match { + case x: Implicant => bp.value == x.bp.value && bp.mask == x.bp.mask + case _ => false + } + + override def hashCode = bp.value.toInt + + /** Check whether two implicants have the same value on all of the cared bits (intersection). + * + * {{{ + * value ^^ x.value // bits that are different + * (bits that are different) & x.mask // bits that are different and `this` care + * (bits that are different and `this` care) & y.mask // bits that are different and `both` care + * (bits that are different and both care) == 0 // no (bits that are different and we both care) exists + * no (bits that are different and we both care) exists // all cared bits are the same, two terms intersect + * }}} + * + * @param y Implicant to be checked with + * @return Whether two implicants intersect + */ + def intersects(y: Implicant): Boolean = ((bp.value ^ y.bp.value) & bp.mask & y.bp.mask) == 0 + + /** Check whether two implicants are similar. + * Two implicants are "similar" when they satisfy all the following rules: + * 1. have the same mask ('?'s are at the same positions) + * 1. values only differ by one bit + * 1. the bit at the differed position of this term is '1' (that of the other term is '0') + * + * @example this = 11?0, x = 10?0 -> similar + * @example this = 11??, x = 10?0 -> not similar, violated rule 1 + * @example this = 11?1, x = 10?0 -> not similar, violated rule 2 + * @example this = 10?0, x = 11?0 -> not similar, violated rule 3 + * @param y Implicant to be checked with + * @return Whether this term is similar to the other + */ + def similar(y: Implicant): Boolean = { + val diff = bp.value - y.bp.value + bp.mask == y.bp.mask && bp.value > y.bp.value && (diff & diff - 1) == 0 + } + + /** Merge two similar implicants + * Rule of merging: '0' and '1' merge to '?' + * + * @param y Term to be merged with + * @return A new term representing the merge result + */ + def merge(y: Implicant): Implicant = { + require(similar(y), s"merge is only reasonable when $this is similar to $y") + + // if two term can be merged, then they both are not prime implicants. + isPrime = false + y.isPrime = false + val bit = bp.value - y.bp.value + new BitPat(bp.value &~ bit, bp.mask &~ bit, width) + } + + /** Check all bits in `x` cover the correspond position in `y`. + * + * Rule to define coverage relationship among `0`, `1` and `?`: + * 1. '?' covers '0' and '1', '0' covers '0', '1' covers '1' + * 1. '1' doesn't cover '?', '1' doesn't cover '0' + * 1. '0' doesn't cover '?', '0' doesn't cover '1' + * + * For all bits that `x` don't care, `y` can be `0`, `1`, `?` + * For all bits that `x` care, `y` must be the same value and not masked. + * {{{ + * (~x.mask & -1) | ((x.mask) & ((x.value xnor y.value) & y.mask)) = -1 + * -> ~x.mask | ((x.mask) & ((x.value xnor y.value) & y.mask)) = -1 + * -> ~x.mask | ((x.value xnor y.value) & y.mask) = -1 + * -> x.mask & ~((x.value xnor y.value) & y.mask) = 0 + * -> x.mask & (~(x.value xnor y.value) | ~y.mask) = 0 + * -> x.mask & ((x.value ^ y.value) | ~y.mask) = 0 + * -> ((x.value ^ y.value) & x.mask | ~y.mask & x.mask) = 0 + * }}} + * + * @param y to check is covered by `x` or not. + * @return Whether `x` covers `y` + */ + def covers(y: Implicant): Boolean = ((bp.value ^ y.bp.value) & bp.mask | ~y.bp.mask & bp.mask) == 0 + + override def toString = (if (!isPrime) "Non" else "") + "Prime" + bp.toString.replace("BitPat", "Implicant") + } + + /** + * If two terms have different value, then their order is determined by the value, or by the mask. + */ + private implicit def ordering: Ordering[Implicant] = new Ordering[Implicant] { + override def compare(x: Implicant, y: Implicant): Int = + if (x.bp.value < y.bp.value || x.bp.value == y.bp.value && x.bp.mask > y.bp.mask) -1 else 1 + } + + /** Calculate essential prime implicants based on previously calculated prime implicants and all implicants. + * + * @param primes Prime implicants + * @param minterms All implicants + * @return (a, b, c) + * a: essential prime implicants + * b: nonessential prime implicants + * c: implicants that are not cover by any of the essential prime implicants + */ + private def getEssentialPrimeImplicants(primes: Seq[Implicant], minterms: Seq[Implicant]): (Seq[Implicant], Seq[Implicant], Seq[Implicant]) = { + // primeCovers(i): implicants that `prime(i)` covers + val primeCovers = primes.map(p => minterms.filter(p.covers)) + // eliminate prime implicants that can be covered by other prime implicants + for (((icover, pi), i) <- (primeCovers zip primes).zipWithIndex) { + for (((jcover, pj), _) <- (primeCovers zip primes).zipWithIndex.drop(i + 1)) { + // we prefer prime implicants with wider implicants coverage + if (icover.size > jcover.size && jcover.forall(pi.covers)) { + // calculate essential prime implicants with `pj` eliminated from prime implicants table + return getEssentialPrimeImplicants(primes.filter(_ != pj), minterms) + } + } + } + + // implicants that only one prime implicant covers + val essentiallyCovered = minterms.filter(t => primes.count(_.covers(t)) == 1) + // essential prime implicants, prime implicants that covers only one implicant + val essential = primes.filter(p => essentiallyCovered.exists(p.covers)) + // {nonessential} = {prime implicants} - {essential prime implicants} + val nonessential = primes.filterNot(essential contains _) + // implicants that no essential prime implicants covers + val uncovered = minterms.filterNot(t => essential.exists(_.covers(t))) + if (essential.isEmpty || uncovered.isEmpty) + (essential, nonessential, uncovered) + else { + // now there are implicants (`uncovered`) that are covered by multiple nonessential prime implicants (`nonessential`) + // need to reduce prime implicants + val (a, b, c) = getEssentialPrimeImplicants(nonessential, uncovered) + (essential ++ a, b, c) + } + } + + /** Use [[https://en.wikipedia.org/wiki/Petrick%27s_method]] to select a [[Seq]] of nonessential prime implicants + * that covers all implicants that are not covered by essential prime implicants. + * + * @param implicants Nonessential prime implicants + * @param minterms Implicants that are not covered by essential prime implicants + * @return Selected nonessential prime implicants + */ + private def getCover(implicants: Seq[Implicant], minterms: Seq[Implicant]): Seq[Implicant] = { + /** Calculate the implementation cost (using comparators) of a list of implicants, more don't cares is cheaper + * + * @param cover Implicant list + * @return How many comparators need to implement this list of implicants + */ + def getCost(cover: Seq[Implicant]): Int = cover.map(_.bp.mask.bitCount).sum + + /** Determine if one combination of prime implicants is cheaper when implementing as comparators. + * Shorter term list is cheaper, term list with more don't cares is cheaper (less comparators) + * + * @param a Operand a + * @param b Operand b + * @return `a` < `b` + */ + def cheaper(a: Seq[Implicant], b: Seq[Implicant]): Boolean = { + val ca = getCost(a) + val cb = getCost(b) + + /** If `a` < `b` + * + * Like comparing the dictionary order of two strings. + * Define `a` < `b` if both `a` and `b` are empty. + * + * @param a Operand a + * @param b Operand b + * @return `a` < `b` + */ + @tailrec + def listLess(a: Seq[Implicant], b: Seq[Implicant]): Boolean = b.nonEmpty && (a.isEmpty || a.head < b.head || a.head == b.head && listLess(a.tail, b.tail)) + + ca < cb || ca == cb && listLess(a.sortWith(_ < _), b.sortWith(_ < _)) + } + + // if there are no implicant that is not covered by essential prime implicants, which means all implicants are + // covered by essential prime implicants, there is no need to apply Petrick's method + if (minterms.nonEmpty) { + // cover(i): nonessential prime implicants that covers `minterms(i)` + val cover = minterms.map(m => implicants.filter(_.covers(m))) + val all = cover.tail.foldLeft(cover.head.map(Set(_)))((c0, c1) => c0.flatMap(a => c1.map(a + _))) + all.map(_.toList).reduceLeft((a, b) => if (cheaper(a, b)) a else b) + } else + Seq[Implicant]() + } + + def minimize(table: TruthTable): TruthTable = { + require(table.table.nonEmpty, "Truth table must not be empty") + + // extract decode table to inputs and outputs + val (inputs, outputs) = table.table.unzip + + require(outputs.map(_.getWidth == table.default.getWidth).reduce(_ && _), "All output BitPats and default BitPat must have the same length") + require(if (inputs.toSeq.length > 1) inputs.tail.map(_.width == inputs.head.width).reduce(_ && _) else true, "All input BitPats must have the same length") + + // make sure no two inputs specified in the truth table intersect + for (t <- inputs.tails; if t.nonEmpty) + for (u <- t.tail) + require(!t.head.intersects(u), "truth table entries " + t.head + " and " + u + " overlap") + + // number of inputs + val n = inputs.head.width + // number of outputs + val m = outputs.head.getWidth + + // for all outputs + table.copy(table = (0 until m).flatMap(i => { + val outputBp = BitPat("b" + "?" * (m - i - 1) + "1" + "?" * i) + + // Minterms, implicants that makes the output to be 1 + val mint: Seq[Implicant] = table.table.filter { case (_, t) => t.mask.testBit(i) && t.value.testBit(i) }.map(_._1).map(toImplicant).toSeq + // Maxterms, implicants that makes the output to be 0 + val maxt: Seq[Implicant] = table.table.filter { case (_, t) => t.mask.testBit(i) && !t.value.testBit(i) }.map(_._1).map(toImplicant).toSeq + // Don't cares, implicants that can produce either 0 or 1 as output + val dc: Seq[Implicant] = table.table.filter { case (_, t) => !t.mask.testBit(i) }.map(_._1).map(toImplicant).toSeq + + val (implicants, defaultToDc) = table.default match { + case x if x.mask.testBit(i) && !x.value.testBit(i) => // default to 0 + (mint ++ dc, false) + case x if x.mask.testBit(i) && x.value.testBit(i) => // default to 1 + (maxt ++ dc, false) + case x if !x.mask.testBit(i) => // default to ? + (mint, true) + } + + implicants.foreach(_.isPrime = true) + val cols = (0 to n).reverse.map(b => implicants.filter(b == _.bp.mask.bitCount)) + val mergeTable = cols.map( + c => (0 to n).map( + b => collection.mutable.Set(c.filter(b == _.bp.value.bitCount):_*) + ) + ) + + for (i <- 0 to n) { + for (j <- 0 until n - i) { + mergeTable(i)(j).foreach(a => mergeTable(i + 1)(j) ++= mergeTable(i)(j + 1).filter(_ similar a).map(_ merge a)) + } + if (defaultToDc) { + for (j <- 0 until n - i) { + for (a <- mergeTable(i)(j).filter(_.isPrime)) { + if (a.bp.mask.testBit(i) && !a.bp.value.testBit(i)) { + // this bit is `0` + val t = new BitPat(a.bp.value.setBit(i), a.bp.mask, a.width) + if (!maxt.exists(_.intersects(t))) mergeTable(i + 1)(j) += t merge a + } + } + for (a <- mergeTable(i)(j + 1).filter(_.isPrime)) { + if (a.bp.mask.testBit(i) && a.bp.value.testBit(i)) { + // this bit is `1` + val t = new BitPat(a.bp.value.clearBit(i), a.bp.mask, a.width) + if (!maxt.exists(_.intersects(t))) mergeTable(i + 1)(j) += a merge t + } + } + } + } + } + + val primeImplicants = mergeTable.flatten.flatten.filter(_.isPrime).sortWith(_ < _) + + val (essentialPrimeImplicants, nonessentialPrimeImplicants, uncoveredImplicants) = + getEssentialPrimeImplicants(primeImplicants, implicants) + + (essentialPrimeImplicants ++ getCover(nonessentialPrimeImplicants, uncoveredImplicants)).map(a => (a.bp, outputBp)) + }).toMap) + } +} \ No newline at end of file -- cgit v1.2.3 From 93e9e2ba8058ef89afacc00c16e138b0243587ba Mon Sep 17 00:00:00 2001 From: Boyang Han Date: Sun, 23 May 2021 04:04:42 -0700 Subject: Merge minimized table before return as a TruthTable --- .../chisel3/util/experimental/decode/QMCMinimizer.scala | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala b/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala index bc5ea351..3fa5a70c 100644 --- a/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala +++ b/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala @@ -220,7 +220,7 @@ object QMCMinimizer extends Minimizer { val m = outputs.head.getWidth // for all outputs - table.copy(table = (0 until m).flatMap(i => { + val minimized = (0 until m).flatMap(i => { val outputBp = BitPat("b" + "?" * (m - i - 1) + "1" + "?" * i) // Minterms, implicants that makes the output to be 1 @@ -277,6 +277,19 @@ object QMCMinimizer extends Minimizer { getEssentialPrimeImplicants(primeImplicants, implicants) (essentialPrimeImplicants ++ getCover(nonessentialPrimeImplicants, uncoveredImplicants)).map(a => (a.bp, outputBp)) - }).toMap) + }) + + minimized.tail.foldLeft(table.copy(table = Map(minimized.head))) { case (tb, t) => + if (tb.table.exists(x => x._1 == t._1)) { + tb.copy(table = tb.table.map { case (k, v) => + if (k == t._1) { + def ones(bitPat: BitPat) = bitPat.toString.drop(7).dropRight(1).zipWithIndex.collect{case ('1', x) => x} + (k, BitPat("b" + (0 until v.getWidth).map(i => if ((ones(v) ++ ones(t._2)).contains(i)) "1" else "?").mkString)) + } else (k, v) + }) + } else { + tb.copy(table = tb.table + t) + } + } } } \ No newline at end of file -- cgit v1.2.3 From 6bcedfdc01e51079c258c5cc576356ad561e2555 Mon Sep 17 00:00:00 2001 From: Boyang Han Date: Tue, 11 May 2021 06:01:25 -0700 Subject: Refactor to a more `scala` form --- src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala b/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala index 3fa5a70c..b6b97daf 100644 --- a/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala +++ b/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala @@ -224,11 +224,11 @@ object QMCMinimizer extends Minimizer { val outputBp = BitPat("b" + "?" * (m - i - 1) + "1" + "?" * i) // Minterms, implicants that makes the output to be 1 - val mint: Seq[Implicant] = table.table.filter { case (_, t) => t.mask.testBit(i) && t.value.testBit(i) }.map(_._1).map(toImplicant).toSeq + val mint: Seq[Implicant] = table.table.filter { case (_, t) => t.mask.testBit(i) && t.value.testBit(i) }.keys.map(toImplicant).toSeq // Maxterms, implicants that makes the output to be 0 - val maxt: Seq[Implicant] = table.table.filter { case (_, t) => t.mask.testBit(i) && !t.value.testBit(i) }.map(_._1).map(toImplicant).toSeq + val maxt: Seq[Implicant] = table.table.filter { case (_, t) => t.mask.testBit(i) && !t.value.testBit(i) }.keys.map(toImplicant).toSeq // Don't cares, implicants that can produce either 0 or 1 as output - val dc: Seq[Implicant] = table.table.filter { case (_, t) => !t.mask.testBit(i) }.map(_._1).map(toImplicant).toSeq + val dc: Seq[Implicant] = table.table.filter { case (_, t) => !t.mask.testBit(i) }.keys.map(toImplicant).toSeq val (implicants, defaultToDc) = table.default match { case x if x.mask.testBit(i) && !x.value.testBit(i) => // default to 0 -- cgit v1.2.3 From 31821aabdb8222dfeae76bac24858dd2ec271aa9 Mon Sep 17 00:00:00 2001 From: Boyang Han Date: Tue, 11 May 2021 06:23:30 -0700 Subject: Add computational complexity analysis --- src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala b/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala index b6b97daf..54faa734 100644 --- a/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala +++ b/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala @@ -194,6 +194,7 @@ object QMCMinimizer extends Minimizer { if (minterms.nonEmpty) { // cover(i): nonessential prime implicants that covers `minterms(i)` val cover = minterms.map(m => implicants.filter(_.covers(m))) + // all subsets of `cover`, NP algorithm, O(2 ^ len(cover)) val all = cover.tail.foldLeft(cover.head.map(Set(_)))((c0, c1) => c0.flatMap(a => c1.map(a + _))) all.map(_.toList).reduceLeft((a, b) => if (cheaper(a, b)) a else b) } else @@ -247,6 +248,7 @@ object QMCMinimizer extends Minimizer { ) ) + // O(n ^ 3) for (i <- 0 to n) { for (j <- 0 until n - i) { mergeTable(i)(j).foreach(a => mergeTable(i + 1)(j) ++= mergeTable(i)(j + 1).filter(_ similar a).map(_ merge a)) @@ -273,6 +275,7 @@ object QMCMinimizer extends Minimizer { val primeImplicants = mergeTable.flatten.flatten.filter(_.isPrime).sortWith(_ < _) + // O(len(primeImplicants) ^ 4) val (essentialPrimeImplicants, nonessentialPrimeImplicants, uncoveredImplicants) = getEssentialPrimeImplicants(primeImplicants, implicants) -- cgit v1.2.3 From b860a814c6e5a9333d6c94b6c5fbee6daad7ba5c Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Sat, 15 May 2021 15:46:08 +0000 Subject: implement test for qmc --- .../scala/chiselTests/util/experimental/minimizer/QMCSpec.scala | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/test/scala/chiselTests/util/experimental/minimizer/QMCSpec.scala diff --git a/src/test/scala/chiselTests/util/experimental/minimizer/QMCSpec.scala b/src/test/scala/chiselTests/util/experimental/minimizer/QMCSpec.scala new file mode 100644 index 00000000..fc770202 --- /dev/null +++ b/src/test/scala/chiselTests/util/experimental/minimizer/QMCSpec.scala @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.util.experimental.minimizer +import chisel3.util.experimental.decode.Minimizer +import chisel3.util.experimental.decode.QMCMinimizer + +class QMCSpec extends MinimizerSpec { + override def minimizer: Minimizer = QMCMinimizer +} -- cgit v1.2.3 From a03dea9dfb72efa66132ddbfb385583d60d37fd9 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Wed, 16 Jun 2021 10:32:45 +0200 Subject: Update sbt to 1.5.4 (#1960) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 19479ba4..9edb75b7 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.2 +sbt.version=1.5.4 -- cgit v1.2.3 -- cgit v1.2.3 From a3ddd4b98049b624080422717c6822ec9ab43e07 Mon Sep 17 00:00:00 2001 From: Martin Schoeberl Date: Wed, 16 Jun 2021 19:33:26 +0200 Subject: getVerilog in Chisel3 (#1921) --- src/main/scala/chisel3/verilog.scala | 15 +++++++++++++++ src/test/scala/chiselTests/Module.scala | 13 +++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 src/main/scala/chisel3/verilog.scala diff --git a/src/main/scala/chisel3/verilog.scala b/src/main/scala/chisel3/verilog.scala new file mode 100644 index 00000000..a91444de --- /dev/null +++ b/src/main/scala/chisel3/verilog.scala @@ -0,0 +1,15 @@ +package chisel3 + +import chisel3.stage.ChiselStage +import firrtl.AnnotationSeq + +object getVerilogString { + def apply(gen: => RawModule): String = ChiselStage.emitVerilog(gen) +} + +object emitVerilog { + def apply(gen: => RawModule, args: Array[String] = Array.empty, + annotations: AnnotationSeq = Seq.empty): Unit = { + (new ChiselStage).emitVerilog(gen, args, annotations) + } +} diff --git a/src/test/scala/chiselTests/Module.scala b/src/test/scala/chiselTests/Module.scala index bc9c524a..7703e876 100644 --- a/src/test/scala/chiselTests/Module.scala +++ b/src/test/scala/chiselTests/Module.scala @@ -8,6 +8,8 @@ import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage, NoRunFirrtlCompile import firrtl.annotations.NoTargetAnnotation import firrtl.options.Unserializable +import scala.io.Source + class SimpleIO extends Bundle { val in = Input(UInt(32.W)) val out = Output(UInt(32.W)) @@ -187,4 +189,15 @@ class ModuleSpec extends ChiselPropSpec with Utils { } ChiselStage.elaborate(new RawModule with Foo) } + + 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")) + } + + property("emitVerilog((new PlusOne()..) shall produce a valid Verilog file in a subfolder") { + emitVerilog(new PlusOne(), Array("--target-dir", "generated")) + val s = Source.fromFile("generated/PlusOne.v").mkString("") + assert(s.contains("assign io_out = io_in + 32'h1")) + } } -- cgit v1.2.3 From 44c7fca8a303bc787634014502acd835c6b5d334 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Mon, 21 Jun 2021 13:36:13 -0700 Subject: Switch to Github Actions CI Badge (#1967) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9aba4e8a..8d0ca862 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Offline signup [link](https://www.bagevent.com/event/registerTicket/7314534), Du --- [![Join the chat at https://gitter.im/freechipsproject/chisel3](https://badges.gitter.im/chipsalliance/chisel3.svg)](https://gitter.im/freechipsproject/chisel3?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![CircleCI](https://circleci.com/gh/chipsalliance/chisel3/tree/master.svg?style=shield)](https://circleci.com/gh/chipsalliance/chisel3/tree/master) +![CI](https://github.com/chipsalliance/chisel3/actions/workflows/test.yml/badge.svg) [![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/chipsalliance/chisel3.svg?label=release)](https://github.com/chipsalliance/chisel3/releases/latest) [**Chisel**](https://www.chisel-lang.org) is a hardware design language that facilitates **advanced circuit generation and design reuse for both ASIC and FPGA digital logic designs**. -- cgit v1.2.3 From 942218432a80de1546ed53a4cfb10a8683ff4f27 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Mon, 21 Jun 2021 16:58:15 -0700 Subject: Bump scalatest to 3.2.9 (#1965) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- build.sbt | 2 +- build.sc | 2 +- src/test/scala/chiselTests/ChiselSpec.scala | 5 +++-- src/test/scala/chiselTests/ExtModuleImpl.scala | 7 +++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build.sbt b/build.sbt index 2da958bc..cc4dee41 100644 --- a/build.sbt +++ b/build.sbt @@ -70,7 +70,7 @@ lazy val chiselSettings = Seq ( name := "chisel3", libraryDependencies ++= Seq( - "org.scalatest" %% "scalatest" % "3.1.2" % "test", + "org.scalatest" %% "scalatest" % "3.2.9" % "test", "org.scalatestplus" %% "scalacheck-1-14" % "3.2.2.0" % "test", "com.lihaoyi" %% "os-lib" % "0.7.8", ), diff --git a/build.sc b/build.sc index 8c5a258d..19ddc7ed 100644 --- a/build.sc +++ b/build.sc @@ -107,7 +107,7 @@ class chisel3CrossModule(val crossScalaVersion: String) extends CommonModule wit override def scalacPluginClasspath = m.scalacPluginClasspath override def ivyDeps = m.ivyDeps() ++ Agg( - ivy"org.scalatest::scalatest:3.1.2", + ivy"org.scalatest::scalatest:3.2.9", ivy"org.scalatestplus::scalacheck-1-14:3.2.2.0", ) ++ m.treadleIvyDeps diff --git a/src/test/scala/chiselTests/ChiselSpec.scala b/src/test/scala/chiselTests/ChiselSpec.scala index 37c4a2b7..a4192c5e 100644 --- a/src/test/scala/chiselTests/ChiselSpec.scala +++ b/src/test/scala/chiselTests/ChiselSpec.scala @@ -13,7 +13,8 @@ import org.scalacheck._ import org.scalatest._ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.freespec.AnyFreeSpec -import org.scalatest.matchers.should._ +import org.scalatest.propspec.AnyPropSpec +import org.scalatest.matchers.should.Matchers import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks import java.io.ByteArrayOutputStream @@ -94,7 +95,7 @@ abstract class ChiselFlatSpec extends AnyFlatSpec with ChiselRunners with Matche abstract class ChiselFreeSpec extends AnyFreeSpec with ChiselRunners with Matchers /** Spec base class for property-based testers. */ -abstract class ChiselPropSpec extends PropSpec with ChiselRunners with ScalaCheckPropertyChecks with Matchers { +abstract class ChiselPropSpec extends AnyPropSpec with ChiselRunners with ScalaCheckPropertyChecks with Matchers { // Constrain the default number of instances generated for every use of forAll. implicit override val generatorDrivenConfig: PropertyCheckConfiguration = diff --git a/src/test/scala/chiselTests/ExtModuleImpl.scala b/src/test/scala/chiselTests/ExtModuleImpl.scala index e0a76201..c6cd4a9f 100644 --- a/src/test/scala/chiselTests/ExtModuleImpl.scala +++ b/src/test/scala/chiselTests/ExtModuleImpl.scala @@ -8,12 +8,11 @@ import chisel3._ import chisel3.experimental.ExtModule import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage} import chisel3.util.{HasExtModuleInline, HasExtModulePath, HasExtModuleResource} -import firrtl.FirrtlExecutionSuccess import firrtl.options.TargetDirAnnotation import firrtl.stage.FirrtlCircuitAnnotation import firrtl.transforms.BlackBoxNotFoundException -import org.scalacheck.Test.Failed -import org.scalatest.{FreeSpec, Matchers, Succeeded} +import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.matchers.should.Matchers //scalastyle:off magic.number @@ -102,7 +101,7 @@ class UsesMissingExtModuleResource extends RawModule { val foo = Module(new ExtModuleResourceNotFound) } -class ExtModuleImplSpec extends FreeSpec with Matchers { +class ExtModuleImplSpec extends AnyFreeSpec with Matchers { "ExtModule can have verilator source implementation" - { "Implementations can be contained in-line" in { -- cgit v1.2.3 From aaccf17799fdbbb9810e424ab8b246d9a5ff5c70 Mon Sep 17 00:00:00 2001 From: Boyang Han Date: Wed, 23 Jun 2021 17:14:03 +0800 Subject: Replace hard coded line separators with system specific ones --- src/test/scala/chiselTests/SMTModelCheckingSpec.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/scala/chiselTests/SMTModelCheckingSpec.scala b/src/test/scala/chiselTests/SMTModelCheckingSpec.scala index 6a24ff10..0a752b10 100644 --- a/src/test/scala/chiselTests/SMTModelCheckingSpec.scala +++ b/src/test/scala/chiselTests/SMTModelCheckingSpec.scala @@ -9,6 +9,7 @@ import firrtl.util.BackendCompilationUtilities.timeStamp import logger.{LazyLogging, LogLevel, LogLevelAnnotation} import org.scalatest.flatspec.AnyFlatSpec import os._ +import scala.util.Properties /** [[SMTModelCheckingSpec]] use z3 and [[firrtl.backends.experimental.smt]] library * to solve `assert/assume` in [[chisel3.experimental.verification]], @@ -64,8 +65,8 @@ private object Z3ModelChecker extends LazyLogging { private def executeStep(path: Path): Boolean = { val (out, ret) = executeCmd(path.toString) assert(ret == 0, s"expected success (0), not $ret: `$out`\nz3 ${path.toString}") - assert(out == "sat\n" || out == "unsat\n", s"Unexpected output: $out") - out == "unsat\n" + assert(out == "sat" + Properties.lineSeparator || out == "unsat" + Properties.lineSeparator, s"Unexpected output: $out") + out == "unsat" + Properties.lineSeparator } private def executeCmd(cmd: String): (String, Int) = { -- cgit v1.2.3 From 04de237e91192b884bbc51c78c57932b2ad7754a Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Thu, 24 Jun 2021 00:16:31 +0800 Subject: CCC info update (#1969) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8d0ca862..b2de88de 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,12 @@ Call-in info and meeting notes are available [here](https://docs.google.com/docu ### Chisel Community Conference 2021, Shanghai, China. 6/26/2021(CST) The Chisel Community Conference China 2021 (CCC2021) is planned for June 26, 2021(CST) at the ShanghaiTech University. The schedule is available [here](https://docs.google.com/spreadsheets/d/1Gb4mMGRhs9exJW-l3NOS0IFi7fDplNQQnkXyAKzHlTg) CCC is an annual gathering of Chisel community enthusiasts and technical exchange workshop. With the support of the Chisel development community and RISC-V World Conference China 2021 Committee, this conference will bring together designers and developers with hands-on experience in Chisel from home and abroad to share cutting-edge results and experiences from both the open source community as well as industry. -Online signup is not required. Zoom/YouTube/BiliBili streaming links will be added to the schedule before the start of the conference. -Offline signup [link](https://www.bagevent.com/event/registerTicket/7314534), Due to COVID-19 restrictions, offline registration is open only to current residents already present in mainland China. +You can join the meeting via streaming links below: +- [Zoom](https://sifive.zoom.us/j/93495528878?pwd=L3IxYkdqWURGVk5pR0hMUDBEM0UzUT09) + Meeting ID: 934 9552 8878 + Passcode: 820268 + +- [BiliBili](http://live.bilibili.com/22275404) --- -- cgit v1.2.3 From f8053db3d20b733e0119b77595f0cdfcdab71057 Mon Sep 17 00:00:00 2001 From: Deborah Soung Date: Thu, 24 Jun 2021 14:03:28 -0700 Subject: create and extend annotatable BaseSim class for verification nodes (#1968) * prototype annotating verif constructs * switch to final class * name emissions * moving BaseSim to experimental * adding name tests * fixing quotation escapes * emitting names, but everything has a default name * only name things with provided/suggested names * name every BaseSim node * removing msg, unused imports * fixing file exist calls--- core/src/main/scala/chisel3/RawModule.scala | 4 +- .../main/scala/chisel3/experimental/package.scala | 6 + .../experimental/verification/package.scala | 28 ++++- .../scala/chisel3/internal/firrtl/Converter.scala | 4 +- .../main/scala/chisel3/internal/firrtl/IR.scala | 5 +- .../scala/chisel3/internal/firrtl/Emitter.scala | 4 +- .../verification/VerificationSpec.scala | 134 +++++++++++++++++++-- 7 files changed, 158 insertions(+), 27 deletions(-) diff --git a/core/src/main/scala/chisel3/RawModule.scala b/core/src/main/scala/chisel3/RawModule.scala index f678c587..de93e781 100644 --- a/core/src/main/scala/chisel3/RawModule.scala +++ b/core/src/main/scala/chisel3/RawModule.scala @@ -5,8 +5,7 @@ package chisel3 import scala.collection.mutable.{ArrayBuffer, HashMap} import scala.util.Try import scala.language.experimental.macros - -import chisel3.experimental.BaseModule +import chisel3.experimental.{BaseModule, BaseSim} import chisel3.internal._ import chisel3.internal.Builder._ import chisel3.internal.firrtl._ @@ -77,6 +76,7 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) id match { case id: BaseModule => id.forceName(None, default=id.desiredName, _namespace) case id: MemBase[_] => id.forceName(None, default="MEM", _namespace) + case id: BaseSim => id.forceName(None, default="SIM", _namespace) case id: Data => if (id.isSynthesizable) { id.topBinding match { diff --git a/core/src/main/scala/chisel3/experimental/package.scala b/core/src/main/scala/chisel3/experimental/package.scala index e8360430..8018159f 100644 --- a/core/src/main/scala/chisel3/experimental/package.scala +++ b/core/src/main/scala/chisel3/experimental/package.scala @@ -2,6 +2,7 @@ package chisel3 +import chisel3.internal.NamedComponent import chisel3.internal.sourceinfo.SourceInfo /** Package for experimental features, which may have their API changed, be removed, etc. @@ -165,4 +166,9 @@ package object experimental { val prefix = chisel3.internal.prefix // Use to remove prefixes not in provided scope val noPrefix = chisel3.internal.noPrefix + + /** Base simulation-only component. */ + abstract class BaseSim extends NamedComponent { + _parent.foreach(_.addId(this)) + } } diff --git a/core/src/main/scala/chisel3/experimental/verification/package.scala b/core/src/main/scala/chisel3/experimental/verification/package.scala index 816299a3..ca15a5c4 100644 --- a/core/src/main/scala/chisel3/experimental/verification/package.scala +++ b/core/src/main/scala/chisel3/experimental/verification/package.scala @@ -8,36 +8,52 @@ import chisel3.internal.firrtl.{Formal, Verification} import chisel3.internal.sourceinfo.SourceInfo package object verification { + + /** Named class for assertions. */ + final class Assert(val predicate: Bool) extends BaseSim + + /** Named class for assumes. */ + final class Assume(val predicate: Bool) extends BaseSim + + /** Named class for covers. */ + final class Cover(val predicate: Bool) extends BaseSim + object assert { def apply(predicate: Bool, msg: String = "")( implicit sourceInfo: SourceInfo, - compileOptions: CompileOptions): Unit = { + compileOptions: CompileOptions): Assert = { + val a = new Assert(predicate) when (!Module.reset.asBool) { val clock = Module.clock - Builder.pushCommand(Verification(Formal.Assert, sourceInfo, clock.ref, predicate.ref, msg)) + Builder.pushCommand(Verification(a, Formal.Assert, sourceInfo, clock.ref, predicate.ref, msg)) } + a } } object assume { def apply(predicate: Bool, msg: String = "")( implicit sourceInfo: SourceInfo, - compileOptions: CompileOptions): Unit = { + compileOptions: CompileOptions): Assume = { + val a = new Assume(predicate) when (!Module.reset.asBool) { val clock = Module.clock - Builder.pushCommand(Verification(Formal.Assume, sourceInfo, clock.ref, predicate.ref, msg)) + Builder.pushCommand(Verification(a, Formal.Assume, sourceInfo, clock.ref, predicate.ref, msg)) } + a } } object cover { def apply(predicate: Bool, msg: String = "")( implicit sourceInfo: SourceInfo, - compileOptions: CompileOptions): Unit = { + compileOptions: CompileOptions): Cover = { val clock = Module.clock + val c = new Cover(predicate) when (!Module.reset.asBool) { - Builder.pushCommand(Verification(Formal.Cover, sourceInfo, clock.ref, predicate.ref, msg)) + Builder.pushCommand(Verification(c, Formal.Cover, sourceInfo, clock.ref, predicate.ref, msg)) } + c } } } diff --git a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala index ff0fa770..40d3691c 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -136,14 +136,14 @@ private[chisel3] object Converter { val (fmt, args) = unpack(pable, ctx) Some(fir.Print(convert(info), fir.StringLit(fmt), args.map(a => convert(a, ctx, info)), convert(clock, ctx, info), firrtl.Utils.one)) - case Verification(op, info, clk, pred, msg) => + case e @ Verification(_, op, info, clk, pred, msg) => val firOp = op match { case Formal.Assert => fir.Formal.Assert case Formal.Assume => fir.Formal.Assume case Formal.Cover => fir.Formal.Cover } Some(fir.Verification(firOp, convert(info), convert(clk, ctx, info), - convert(pred, ctx, info), firrtl.Utils.one, fir.StringLit(msg))) + convert(pred, ctx, info), firrtl.Utils.one, fir.StringLit(msg), e.name)) case _ => None } diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index 81b4f7ab..5dc72a43 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -3,7 +3,6 @@ package chisel3.internal.firrtl import firrtl.{ir => fir} - import chisel3._ import chisel3.internal._ import chisel3.internal.sourceinfo.SourceInfo @@ -765,8 +764,8 @@ object Formal extends Enumeration { val Assume = Value("assume") val Cover = Value("cover") } -case class Verification(op: Formal.Value, sourceInfo: SourceInfo, clock: Arg, - predicate: Arg, message: String) extends Command +case class Verification[T <: BaseSim](id: T, op: Formal.Value, sourceInfo: SourceInfo, clock: Arg, + predicate: Arg, message: String) extends Definition abstract class Component extends Arg { def id: BaseModule def name: String diff --git a/src/main/scala/chisel3/internal/firrtl/Emitter.scala b/src/main/scala/chisel3/internal/firrtl/Emitter.scala index ad4df80a..53d5c6ce 100644 --- a/src/main/scala/chisel3/internal/firrtl/Emitter.scala +++ b/src/main/scala/chisel3/internal/firrtl/Emitter.scala @@ -80,8 +80,8 @@ private class Emitter(circuit: Circuit) { val printfArgs = Seq(e.clock.fullName(ctx), "UInt<1>(1)", "\"" + printf.format(fmt) + "\"") ++ args printfArgs mkString ("printf(", ", ", ")") - case e: Verification => s"${e.op}(${e.clock.fullName(ctx)}, ${e.predicate.fullName(ctx)}, " + - s"UInt<1>(1), " + "\"" + s"${printf.format(e.message)}" + "\")" + case e: Verification[_] => + s"""${e.op}(${e.clock.fullName(ctx)}, ${e.predicate.fullName(ctx)}, UInt<1>(1), "${printf.format(e.message)}") : ${e.name}""" case e: DefInvalid => s"${e.arg.fullName(ctx)} is invalid" case e: DefInstance => s"inst ${e.name} of ${e.id.name}" case w: WhenBegin => diff --git a/src/test/scala/chiselTests/experimental/verification/VerificationSpec.scala b/src/test/scala/chiselTests/experimental/verification/VerificationSpec.scala index 86d6418c..a1fc2a1d 100644 --- a/src/test/scala/chiselTests/experimental/verification/VerificationSpec.scala +++ b/src/test/scala/chiselTests/experimental/verification/VerificationSpec.scala @@ -3,11 +3,15 @@ package chiselTests.experimental.verification import chisel3._ -import chisel3.experimental.{verification => formal} +import chisel3.experimental.{ChiselAnnotation, verification => formal} import chisel3.stage.ChiselStage import chiselTests.ChiselPropSpec +import firrtl.annotations.{ReferenceTarget, SingleTargetAnnotation} -class VerificationModule extends Module { +import java.io.File +import org.scalatest.matchers.should.Matchers + +class SimpleTest extends Module { val io = IO(new Bundle{ val in = Input(UInt(8.W)) val out = Output(UInt(8.W)) @@ -20,25 +24,131 @@ class VerificationModule extends Module { } } -class VerificationSpec extends ChiselPropSpec { +/** Dummy verification annotation. + * @param target target of component to be annotated + */ +case class VerifAnnotation(target: ReferenceTarget) extends SingleTargetAnnotation[ReferenceTarget] { + def duplicate(n: ReferenceTarget): VerifAnnotation = this.copy(target = n) +} + +object VerifAnnotation { + /** Create annotation for a given verification component. + * @param c component to be annotated + */ + def annotate(c: experimental.BaseSim): Unit = { + chisel3.experimental.annotate(new ChiselAnnotation { + def toFirrtl: VerifAnnotation = VerifAnnotation(c.toTarget) + }) + } +} + +class VerificationSpec extends ChiselPropSpec with Matchers { - def assertContains[T](s: Seq[T], x: T): Unit = { - val containsLine = s.map(_ == x).reduce(_ || _) + def assertContains(s: Seq[String], x: String): Unit = { + val containsLine = s.map(_.contains(x)).reduce(_ || _) assert(containsLine, s"\n $x\nwas not found in`\n ${s.mkString("\n ")}``") } property("basic equality check should work") { - val fir = ChiselStage.emitChirrtl(new VerificationModule) + val fir = ChiselStage.emitChirrtl(new SimpleTest) val lines = fir.split("\n").map(_.trim) // reset guard around the verification statement - assertContains(lines, "when _T_2 : @[VerificationSpec.scala 16:15]") - assertContains(lines, "cover(clock, _T, UInt<1>(\"h1\"), \"\") @[VerificationSpec.scala 16:15]") + assertContains(lines, "when _T_2 : @[VerificationSpec.scala") + assertContains(lines, "cover(clock, _T, UInt<1>(\"h1\"), \"\")") + + assertContains(lines, "when _T_6 : @[VerificationSpec.scala") + assertContains(lines, "assume(clock, _T_4, UInt<1>(\"h1\"), \"\")") + + assertContains(lines, "when _T_9 : @[VerificationSpec.scala") + assertContains(lines, "assert(clock, _T_7, UInt<1>(\"h1\"), \"\")") + } + + property("annotation of verification constructs should work") { + /** Circuit that contains and annotates verification nodes. */ + class AnnotationTest extends Module { + val io = IO(new Bundle{ + val in = Input(UInt(8.W)) + val out = Output(UInt(8.W)) + }) + io.out := io.in + val cov = formal.cover(io.in === 3.U) + val assm = formal.assume(io.in =/= 2.U) + val asst = formal.assert(io.out === io.in) + VerifAnnotation.annotate(cov) + VerifAnnotation.annotate(assm) + VerifAnnotation.annotate(asst) + } + + // compile circuit + val testDir = new File("test_run_dir", "VerificationAnnotationTests") + (new ChiselStage).emitSystemVerilog( + gen = new AnnotationTest, + args = Array("-td", testDir.getPath) + ) + + // read in annotation file + val annoFile = new File(testDir, "AnnotationTest.anno.json") + annoFile should exist + val annoLines = scala.io.Source.fromFile(annoFile).getLines.toList + + // check for expected verification annotations + exactly(3, annoLines) should include ("chiselTests.experimental.verification.VerifAnnotation") + exactly(1, annoLines) should include ("~AnnotationTest|AnnotationTest>asst") + exactly(1, annoLines) should include ("~AnnotationTest|AnnotationTest>assm") + exactly(1, annoLines) should include ("~AnnotationTest|AnnotationTest>cov") + + // read in FIRRTL file + val firFile = new File(testDir, "AnnotationTest.fir") + firFile should exist + val firLines = scala.io.Source.fromFile(firFile).getLines.toList + + // check that verification components have expected names + exactly(1, firLines) should include ("cover(clock, _T, UInt<1>(1), \"\") : cov") + exactly(1, firLines) should include ("assume(clock, _T_3, UInt<1>(1), \"\") : assm") + exactly(1, firLines) should include ("assert(clock, _T_6, UInt<1>(1), \"\") : asst") + } + + property("annotation of verification constructs with suggested name should work") { + /** Circuit that annotates a renamed verification nodes. */ + class AnnotationRenameTest extends Module { + val io = IO(new Bundle{ + val in = Input(UInt(8.W)) + val out = Output(UInt(8.W)) + }) + io.out := io.in + + val goodbye = formal.assert(io.in === 1.U) + goodbye.suggestName("hello") + VerifAnnotation.annotate(goodbye) + + VerifAnnotation.annotate(formal.assume(io.in =/= 2.U).suggestName("howdy")) + } + + // compile circuit + val testDir = new File("test_run_dir", "VerificationAnnotationRenameTests") + (new ChiselStage).emitSystemVerilog( + gen = new AnnotationRenameTest, + args = Array("-td", testDir.getPath) + ) + + // read in annotation file + val annoFile = new File(testDir, "AnnotationRenameTest.anno.json") + annoFile should exist + val annoLines = scala.io.Source.fromFile(annoFile).getLines.toList + + // check for expected verification annotations + exactly(2, annoLines) should include ("chiselTests.experimental.verification.VerifAnnotation") + exactly(1, annoLines) should include ("~AnnotationRenameTest|AnnotationRenameTest>hello") + exactly(1, annoLines) should include ("~AnnotationRenameTest|AnnotationRenameTest>howdy") - assertContains(lines, "when _T_6 : @[VerificationSpec.scala 18:18]") - assertContains(lines, "assume(clock, _T_4, UInt<1>(\"h1\"), \"\") @[VerificationSpec.scala 18:18]") + // read in FIRRTL file + val firFile = new File(testDir, "AnnotationRenameTest.fir") + firFile should exist + val firLines = scala.io.Source.fromFile(firFile).getLines.toList - assertContains(lines, "when _T_9 : @[VerificationSpec.scala 19:18]") - assertContains(lines, "assert(clock, _T_7, UInt<1>(\"h1\"), \"\") @[VerificationSpec.scala 19:18]") + // check that verification components have expected names + exactly(1, firLines) should include ("assert(clock, _T, UInt<1>(1), \"\") : hello") + exactly(1, firLines) should include ("assume(clock, _T_3, UInt<1>(1), \"\") : howdy") } } -- cgit v1.2.3 From 71fff0795a07cb6b136d202a3b75adfee42be6fe Mon Sep 17 00:00:00 2001 From: Felix Yan Date: Fri, 25 Jun 2021 23:22:34 +0800 Subject: Correct typos in core/src/main/scala/chisel3/Num.scala (#1976) --- core/src/main/scala/chisel3/Num.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/chisel3/Num.scala b/core/src/main/scala/chisel3/Num.scala index 70f2fbf0..6dd299f4 100644 --- a/core/src/main/scala/chisel3/Num.scala +++ b/core/src/main/scala/chisel3/Num.scala @@ -156,7 +156,7 @@ trait Num[T <: Data] { /** Minimum operator * * @param that a hardware $coll - * @return a $numType with a value equal to the mimimum value of this $coll and `that` + * @return a $numType with a value equal to the minimum value of this $coll and `that` * $maxWidth * @group Arithmetic */ @@ -169,7 +169,7 @@ trait Num[T <: Data] { /** Maximum operator * * @param that a $numType - * @return a $numType with a value equal to the mimimum value of this $coll and `that` + * @return a $numType with a value equal to the minimum value of this $coll and `that` * $maxWidth * @group Arithmetic */ @@ -304,4 +304,4 @@ trait NumObject { throw new ChiselException(s"Error converting BigDecimal $value to BigInt, binary point must be known, not $x") } } -} \ No newline at end of file +} -- cgit v1.2.3 From 5e8ef455d8f911c05ae858ef05bb2d16a91d9b0a Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Fri, 25 Jun 2021 13:41:19 -0700 Subject: Update type_hierarchy (#1977) * Add Clock * Add Analog * Add Interval * Add line from User Types (...) to Record--- docs/src/images/type_hierarchy.dot | 18 ++- docs/src/images/type_hierarchy.png | Bin 34477 -> 45913 bytes docs/src/images/type_hierarchy.svg | 272 ++++++++++++++++++++++--------------- 3 files changed, 172 insertions(+), 118 deletions(-) diff --git a/docs/src/images/type_hierarchy.dot b/docs/src/images/type_hierarchy.dot index d3bf6eb4..b98cc269 100644 --- a/docs/src/images/type_hierarchy.dot +++ b/docs/src/images/type_hierarchy.dot @@ -10,7 +10,10 @@ digraph TypeHierarchy { "Chisel Internal" } { node [fillcolor="#e5f5e0"] - Bool UInt SInt FixedPoint + Bool UInt SInt + FixedPoint Interval + Analog + Clock Reset AsyncReset Record @@ -27,8 +30,9 @@ digraph TypeHierarchy { color=transparent Element Bits Num - Reset - Bool UInt SInt FixedPoint + Reset Clock Analog + UInt SInt FixedPoint Interval + Bool color=transparent Aggregate VecLike @@ -37,10 +41,12 @@ digraph TypeHierarchy { Vec {Aggregate Element} -> Data - {Bits Reset} -> Element - {FixedPoint SInt UInt} -> {Bits Num} + {Bits Reset Clock Analog} -> Element + {UInt SInt FixedPoint Interval} -> {Bits Num} Bool -> {UInt Reset} - Ellipsis -> Bundle -> Record -> Aggregate + Ellipsis -> Bundle + Ellipsis -> Record -> Aggregate + Bundle -> Record Vec -> {Aggregate VecLike} AsyncReset -> {Element Reset} } diff --git a/docs/src/images/type_hierarchy.png b/docs/src/images/type_hierarchy.png index f3947975..9c4f74f1 100644 Binary files a/docs/src/images/type_hierarchy.png and b/docs/src/images/type_hierarchy.png differ diff --git a/docs/src/images/type_hierarchy.svg b/docs/src/images/type_hierarchy.svg index c120daba..faf23a1e 100644 --- a/docs/src/images/type_hierarchy.svg +++ b/docs/src/images/type_hierarchy.svg @@ -1,261 +1,309 @@ - - + TypeHierarchy cluster_data_hierarchy - + cluster_legend - -Legend + +Legend Data - -Data + +Data Element - -Element + +Element Element->Data - - + + Bits - -Bits + +Bits Bits->Element - - + + Num - -Num + +Num Aggregate - -Aggregate + +Aggregate Aggregate->Data - - + + VecLike - -VecLike + +VecLike Chisel Internal - -Chisel Internal + +Chisel Internal Bool - -Bool + +Bool UInt - -UInt + +UInt - + Bool->UInt - - + + - + Reset - -Reset + +Reset - + Bool->Reset - - + + - + UInt->Bits - - + + - + UInt->Num - - + + SInt - -SInt + +SInt - + SInt->Bits - - + + - + SInt->Num - - + + FixedPoint - -FixedPoint + +FixedPoint - + FixedPoint->Bits - - + + - + FixedPoint->Num - - + + - + + +Interval + +Interval + + + +Interval->Bits + + + + + +Interval->Num + + + + + +Analog + +Analog + + +Analog->Element + + + + + +Clock + +Clock + + + +Clock->Element + + + + + Reset->Element - - + + - + AsyncReset - -AsyncReset + +AsyncReset - + AsyncReset->Element - - + + - + AsyncReset->Reset - - + + - + Record - -Record + +Record - + Record->Aggregate - - + + - + Bundle - -Bundle + +Bundle - + Bundle->Record - - + + - + Vec - -Vec + +Vec - + Vec->Aggregate - - + + - + Vec->VecLike - - + + - + Chisel Types - -Chisel Types + +Chisel Types - + Chisel Types->Chisel Internal - - + + - + User Types - -User Types + +User Types - + User Types->Chisel Types - - + + - + Ellipsis - -... + +... + + + +Ellipsis->Record + + - + Ellipsis->Bundle - - + + -- cgit v1.2.3 From a5cfd8bf64a2fb261f15dd2d9ab669839a71c7b1 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Mon, 28 Jun 2021 12:49:54 -0700 Subject: Add link to Chisel Breakdown slides to README (#1979) --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index b2de88de..b368e139 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,12 @@ These are the base data types for defining circuit components: ## Contributor Documentation This section describes how to get started contributing to Chisel itself, including how to test your version locally against other projects that pull in Chisel using [sbt's managed dependencies](https://www.scala-sbt.org/1.x/docs/Library-Dependencies.html). +### Useful Resources for Contributors + +The [Useful Resources](#useful-resources) for users are also helpful for contributors. + +- [**Chisel Breakdown Slides**](https://docs.google.com/presentation/d/114YihixFBPCfUnv1inqAL8UjsiWfcNWdPHX7SeqlRQc), an introductory talk about Chisel's internals + ### Compiling and Testing Chisel First, clone and build the master branch of [FIRRTL](https://github.com/chipsalliance/firrtl) and [Treadle](https://github.com/chipsalliance/treadle), as the master branch of Chisel may depend on unreleased changes in those projects: -- cgit v1.2.3 From 6a806918b15d78613638c8d860538adbef9425b1 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Tue, 29 Jun 2021 04:28:20 +0800 Subject: CCC update (#1982) --- README.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b368e139..2cf78760 100644 --- a/README.md +++ b/README.md @@ -9,15 +9,11 @@ Chisel/FIRRTL development meetings happen every Monday and Tuesday from 1100--12 Call-in info and meeting notes are available [here](https://docs.google.com/document/d/1BLP2DYt59DqI-FgFCcjw8Ddl4K-WU0nHmQu0sZ_wAGo/). -### Chisel Community Conference 2021, Shanghai, China. 6/26/2021(CST) -The Chisel Community Conference China 2021 (CCC2021) is planned for June 26, 2021(CST) at the ShanghaiTech University. The schedule is available [here](https://docs.google.com/spreadsheets/d/1Gb4mMGRhs9exJW-l3NOS0IFi7fDplNQQnkXyAKzHlTg) -CCC is an annual gathering of Chisel community enthusiasts and technical exchange workshop. With the support of the Chisel development community and RISC-V World Conference China 2021 Committee, this conference will bring together designers and developers with hands-on experience in Chisel from home and abroad to share cutting-edge results and experiences from both the open source community as well as industry. -You can join the meeting via streaming links below: -- [Zoom](https://sifive.zoom.us/j/93495528878?pwd=L3IxYkdqWURGVk5pR0hMUDBEM0UzUT09) - Meeting ID: 934 9552 8878 - Passcode: 820268 - -- [BiliBili](http://live.bilibili.com/22275404) +### Chisel Community Conference 2021, Shanghai, China. +CCC is an annual gathering of Chisel community enthusiasts and technical exchange workshop. +This year with the support of the Chisel development community and RISC-V World Conference China 2021 Committee, we have brought together designers and developers with hands-on experience in Chisel from home and abroad to share cutting-edge results and experiences from both the open source community as well as industry. +English translated recordings version will be updated soon. +Looking forward to CCC 2022! See you then! --- -- cgit v1.2.3 From d3e13ce24956871d2f0fd01ca3a7d89317e3db68 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Wed, 23 Jun 2021 17:11:22 -0700 Subject: Fix CloneModuleAsRecord support for .toTarget --- core/src/main/scala/chisel3/BlackBox.scala | 8 +- core/src/main/scala/chisel3/Module.scala | 91 ++++++++++++++++++---- core/src/main/scala/chisel3/RawModule.scala | 6 +- core/src/main/scala/chisel3/internal/Builder.scala | 2 +- .../scala/chisel3/internal/firrtl/Converter.scala | 19 ++++- .../main/scala/chisel3/internal/firrtl/IR.scala | 13 +++- src/test/scala/chiselTests/CloneModuleSpec.scala | 66 +++++++++++++++- 7 files changed, 175 insertions(+), 30 deletions(-) diff --git a/core/src/main/scala/chisel3/BlackBox.scala b/core/src/main/scala/chisel3/BlackBox.scala index 8ba4b612..0c42600f 100644 --- a/core/src/main/scala/chisel3/BlackBox.scala +++ b/core/src/main/scala/chisel3/BlackBox.scala @@ -62,7 +62,7 @@ package experimental { * @note The parameters API is experimental and may change */ abstract class ExtModule(val params: Map[String, Param] = Map.empty[String, Param]) extends BaseBlackBox { - private[chisel3] override def generateComponent(): Component = { + private[chisel3] override def generateComponent(): Option[Component] = { require(!_closed, "Can't generate module more than once") _closed = true @@ -86,7 +86,7 @@ package experimental { val firrtlPorts = getModulePorts map {port => Port(port, port.specifiedDirection)} val component = DefBlackBox(this, name, firrtlPorts, SpecifiedDirection.Unspecified, params) _component = Some(component) - component + _component } private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = { @@ -145,7 +145,7 @@ abstract class BlackBox(val params: Map[String, Param] = Map.empty[String, Param // Allow access to bindings from the compatibility package protected def _compatIoPortBound() = portsContains(_io) - private[chisel3] override def generateComponent(): Component = { + private[chisel3] override def generateComponent(): Option[Component] = { _compatAutoWrapPorts() // pre-IO(...) compatibility hack // Restrict IO to just io, clock, and reset @@ -178,7 +178,7 @@ abstract class BlackBox(val params: Map[String, Param] = Map.empty[String, Param val firrtlPorts = namedPorts map {namedPort => Port(namedPort._2, namedPort._2.specifiedDirection)} val component = DefBlackBox(this, name, firrtlPorts, _io.specifiedDirection, params) _component = Some(component) - component + _component } private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = { diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala index b204be8d..77735583 100644 --- a/core/src/main/scala/chisel3/Module.scala +++ b/core/src/main/scala/chisel3/Module.scala @@ -64,13 +64,18 @@ object Module extends SourceInfoDoc { Builder.currentClock = saveClock // Back to clock and reset scope Builder.currentReset = saveReset - val component = module.generateComponent() - Builder.components += component + // Only add the component if the module generates one + val componentOpt = module.generateComponent() + for (component <- componentOpt) { + Builder.components += component + } Builder.setPrefix(savePrefix) // Handle connections at enclosing scope - if(!Builder.currentModule.isEmpty) { + // We use _component because Modules that don't generate them may still have one + if (Builder.currentModule.isDefined && module._component.isDefined) { + val component = module._component.get pushCommand(DefInstance(sourceInfo, module, component.ports)) module.initializeInParent(compileOptions) } @@ -178,20 +183,73 @@ package internal { import chisel3.experimental.BaseModule object BaseModule { - private[chisel3] class ClonePorts (elts: Data*)(implicit compileOptions: CompileOptions) extends Record { + // Private internal class to serve as a _parent for Data in cloned ports + private[chisel3] class ModuleClone(proto: BaseModule) extends BaseModule { + // Don't generate a component, but point to the one for the cloned Module + private[chisel3] def generateComponent(): Option[Component] = { + _component = proto._component + None + } + // This module doesn't acutally exist in the FIRRTL so no initialization to do + private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = () + + override def desiredName: String = proto.name + } + + /** Record type returned by CloneModuleAsRecord + * + * @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. + * @param proto Optional pointer to the Module we are a clone of. Set for first instance, unset + * for clones + */ + private[chisel3] class ClonePorts (proto: Option[BaseModule], elts: Data*)(implicit compileOptions: CompileOptions) extends Record { val elements = ListMap(elts.map(d => d.instanceName -> d.cloneTypeFull): _*) def apply(field: String) = elements(field) - override def cloneType = (new ClonePorts(elts: _*)).asInstanceOf[this.type] + override def cloneType = (new ClonePorts(None, elts: _*)).asInstanceOf[this.type] + + // Because ClonePorts instances are *not* created inside of their parent module, but rather, + // their parent's parent, we have to intercept the standard setRef and replace it with our own + // special Ref type. + // This only applies to ClonePorts created in cloneIORecord, any clones of these Records have + // normal behavior. + // Also, the name of ClonePorts Records needs to be propagated to their parent ModuleClone + // since we have no other way of setting the instance name for those. + private[chisel3] override def setRef(imm: Arg, force: Boolean): Unit = { + val immx = (proto, imm) match { + case (Some(mod), Ref(name)) => + // Our _parent is a ModuleClone that needs its ref to match ours for .toAbsoluteTarget + _parent.foreach(_.setRef(Ref(name), force=true)) + // Return a specialize-ref that will do the right thing + ModuleCloneIO(mod, name) + case _ => imm + } + super.setRef(immx, force) + } + } + + // Recursively set the parent of the start Data and any children (eg. in an Aggregate) + private def setAllParents(start: Data, parent: Option[BaseModule]): Unit = { + def rec(data: Data): Unit = { + data._parent = parent + data match { + case _: Element => + case agg: Aggregate => + agg.getElements.foreach(rec) + } + } + rec(start) } private[chisel3] def cloneIORecord(proto: BaseModule)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): ClonePorts = { require(proto.isClosed, "Can't clone a module before module close") - val clonePorts = new ClonePorts(proto.getModulePorts: _*) - clonePorts.bind(WireBinding(Builder.forcedUserModule, Builder.currentWhen())) - val cloneInstance = new DefInstance(sourceInfo, proto, proto._component.get.ports) { - override def name = clonePorts.getRef.name - } - pushCommand(cloneInstance) + // 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(Some(proto), proto.getModulePorts: _*) + val cloneParent = Module(new ModuleClone(proto)) + clonePorts.bind(PortBinding(cloneParent)) + setAllParents(clonePorts, Some(cloneParent)) + // Normally handled during Module construction but ClonePorts really lives in its parent's parent if (!compileOptions.explicitInvalidate) { pushCommand(DefInvalid(sourceInfo, clonePorts.ref)) } @@ -270,7 +328,7 @@ package experimental { /** Generates the FIRRTL Component (Module or Blackbox) of this Module. * Also closes the module so no more construction can happen inside. */ - private[chisel3] def generateComponent(): Component + private[chisel3] def generateComponent(): Option[Component] /** Sets up this module in the parent context */ @@ -308,9 +366,12 @@ package experimental { /** Legalized name of this module. */ final lazy val name = try { - // If this is a module aspect, it should share the same name as the original module - // Thus, the desired name should be returned without uniquification - if(this.isInstanceOf[ModuleAspect]) desiredName else Builder.globalNamespace.name(desiredName) + // ModuleAspects and ModuleClones are not "true modules" and thus should share + // their original modules names without uniquification + this match { + case (_: ModuleAspect | _: internal.BaseModule.ModuleClone) => desiredName + case _ => Builder.globalNamespace.name(desiredName) + } } catch { case e: NullPointerException => throwException( s"Error: desiredName of ${this.getClass.getName} is null. Did you evaluate 'name' before all values needed by desiredName were available?", e) diff --git a/core/src/main/scala/chisel3/RawModule.scala b/core/src/main/scala/chisel3/RawModule.scala index de93e781..5bcd4dbd 100644 --- a/core/src/main/scala/chisel3/RawModule.scala +++ b/core/src/main/scala/chisel3/RawModule.scala @@ -57,7 +57,7 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) } - private[chisel3] override def generateComponent(): Component = { + private[chisel3] override def generateComponent(): Option[Component] = { require(!_closed, "Can't generate module more than once") _closed = true @@ -130,7 +130,7 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) } val component = DefModule(this, name, firrtlPorts, invalidateCommands ++ getCommands) _component = Some(component) - component + _component } private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = { @@ -221,7 +221,7 @@ package object internal { // Allow access to bindings from the compatibility package protected def _compatIoPortBound() = portsContains(_io) - private[chisel3] override def generateComponent(): Component = { + private[chisel3] override def generateComponent(): Option[Component] = { _compatAutoWrapPorts() // pre-IO(...) compatibility hack // Restrict IO to just io, clock, and reset diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index e1e4d460..35c4bdf9 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -84,7 +84,7 @@ trait InstanceId { private[chisel3] trait HasId extends InstanceId { private[chisel3] def _onModuleClose: Unit = {} - private[chisel3] val _parent: Option[BaseModule] = Builder.currentModule + private[chisel3] var _parent: Option[BaseModule] = Builder.currentModule private[chisel3] val _id: Long = Builder.idGen.next diff --git a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala index 40d3691c..093d4848 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -24,16 +24,24 @@ private[chisel3] object Converter { case Percent => ("%%", List.empty) } + private def reportInternalError(msg: String): Nothing = { + val link = "https://github.com/chipsalliance/chisel3/issues/new" + val fullMsg = s"Internal Error! $msg This is a bug in Chisel, please file an issue at '$link'" + throwException(fullMsg) + } + def getRef(id: HasId, sourceInfo: SourceInfo): Arg = id.getOptionRef.getOrElse { val module = id._parent.map(m => s" '$id' was defined in module '$m'.").getOrElse("") val loc = sourceInfo.makeMessage(" " + _) - val link = "https://github.com/chipsalliance/chisel3/issues/new" - val msg = s"Internal error! Could not get ref for '$id'$loc!$module " + - s"This is a bug in Chisel, please file an issue at '$link'." - throwException(msg) + reportInternalError(s"Could not get ref for '$id'$loc!$module") } + private def clonedModuleIOError(mod: BaseModule, name: String, sourceInfo: SourceInfo): Nothing = { + val loc = sourceInfo.makeMessage(" " + _) + reportInternalError(s"Trying to convert a cloned IO of $mod inside of $mod itself$loc!") + } + def convert(info: SourceInfo): fir.Info = info match { case _: NoSourceInfo => fir.NoInfo case SourceLine(fn, line, col) => fir.FileInfo(fir.StringLit(s"$fn $line:$col")) @@ -65,6 +73,9 @@ private[chisel3] object Converter { case ModuleIO(mod, name) => if (mod eq ctx.id) fir.Reference(name, fir.UnknownType) else fir.SubField(fir.Reference(getRef(mod, info).name, fir.UnknownType), name, fir.UnknownType) + case ModuleCloneIO(mod, name) => + if (mod eq ctx.id) clonedModuleIOError(mod, name, info) + else fir.Reference(name) case u @ ULit(n, UnknownWidth()) => fir.UIntLiteral(n, fir.IntWidth(u.minWidth)) case ULit(n, w) => diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index 5dc72a43..1d77802b 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -165,9 +165,18 @@ case class ModuleIO(mod: BaseModule, name: String) extends Arg { override def fullName(ctx: Component): String = if (mod eq ctx.id) name else s"${mod.getRef.name}.$name" } -case class Slot(imm: Node, name: String) extends Arg { +// For use with CloneModuleAsRecord +// Note that `name` is the name of the module instance whereas in ModuleIO it's the name of the port +// The names of ports inside of a ModuleCloneIO are the names of the Slots +case class ModuleCloneIO(mod: BaseModule, name: String) extends Arg { override def fullName(ctx: Component): String = - if (imm.fullName(ctx).isEmpty) name else s"${imm.fullName(ctx)}.${name}" + if (mod eq ctx.id) "" else name +} +case class Slot(imm: Node, name: String) extends Arg { + override def fullName(ctx: Component): String = { + val immName = imm.fullName(ctx) + if (immName.isEmpty) name else s"$immName.$name" + } } case class Index(imm: Arg, value: Arg) extends Arg { def name: String = s"[$value]" diff --git a/src/test/scala/chiselTests/CloneModuleSpec.scala b/src/test/scala/chiselTests/CloneModuleSpec.scala index e54ef1c2..8359bc28 100644 --- a/src/test/scala/chiselTests/CloneModuleSpec.scala +++ b/src/test/scala/chiselTests/CloneModuleSpec.scala @@ -4,7 +4,7 @@ package chiselTests import chisel3._ import chisel3.stage.ChiselStage -import chisel3.util.{Queue, EnqIO, DeqIO, QueueIO, log2Ceil} +import chisel3.util.{Decoupled, Queue, EnqIO, DeqIO, QueueIO, log2Ceil} import chisel3.experimental.{CloneModuleAsRecord, IO} import chisel3.testers.BasicTester @@ -57,6 +57,26 @@ class QueueCloneTester(x: Int, multiIO: Boolean = false) extends BasicTester { } } +class CloneModuleAsRecordAnnotate extends Module { + override def desiredName = "Top" + val in = IO(Flipped(Decoupled(UInt(8.W)))) + val out = IO(Decoupled(UInt(8.W))) + + val q1 = Module(new Queue(UInt(8.W), 4)) + val q2 = CloneModuleAsRecord(q1) + val q2_io = q2("io").asInstanceOf[q1.io.type] + // Also make a wire to check that cloning works, can be connected to, and annotated + val q2_wire = { + val w = Wire(chiselTypeOf(q2)) + w <> q2 + w + } + // But connect to the original (using last connect semantics to override connects to wire + q1.io.enq <> in + q2_io.enq <> q1.io.deq + out <> q2_io.deq +} + class CloneModuleSpec extends ChiselPropSpec { val xVals = Table( @@ -87,4 +107,48 @@ class CloneModuleSpec extends ChiselPropSpec { assert(c.modules.length == 3) } + property("Cloned Modules should annotate correctly") { + // Hackily get the actually Module object out + var mod: CloneModuleAsRecordAnnotate = null + val res = ChiselStage.convert { + mod = new CloneModuleAsRecordAnnotate + mod + } + // ********** Checking the output of CloneModuleAsRecord ********** + // Note that we overrode desiredName so that Top is named "Top" + mod.q1.io.enq.toTarget.serialize should be ("~Top|Queue>io.enq") + mod.q2_io.deq.toTarget.serialize should be ("~Top|Queue>io.deq") + mod.q1.io.enq.toAbsoluteTarget.serialize should be ("~Top|Top/q1:Queue>io.enq") + mod.q2_io.deq.toAbsoluteTarget.serialize should be ("~Top|Top/q2:Queue>io.deq") + // Legacy APIs that nevertheless were tricky to get right + mod.q1.io.enq.toNamed.serialize should be ("Top.Queue.io.enq") + mod.q2_io.deq.toNamed.serialize should be ("Top.Queue.io.deq") + mod.q1.io.enq.instanceName should be ("io.enq") + mod.q2_io.deq.instanceName should be ("io.deq") + mod.q1.io.enq.pathName should be ("Top.q1.io.enq") + mod.q2_io.deq.pathName should be ("Top.q2.io.deq") + mod.q1.io.enq.parentPathName should be ("Top.q1") + mod.q2_io.deq.parentPathName should be ("Top.q2") + mod.q1.io.enq.parentModName should be ("Queue") + mod.q2_io.deq.parentModName should be ("Queue") + + // ********** Checking the wire cloned from the output of CloneModuleAsRecord ********** + val wire_io = mod.q2_wire("io").asInstanceOf[QueueIO[UInt]] + mod.q2_wire.toTarget.serialize should be ("~Top|Top>q2_wire") + wire_io.enq.toTarget.serialize should be ("~Top|Top>q2_wire.io.enq") + mod.q2_wire.toAbsoluteTarget.serialize should be ("~Top|Top>q2_wire") + wire_io.enq.toAbsoluteTarget.serialize should be ("~Top|Top>q2_wire.io.enq") + // Legacy APIs + mod.q2_wire.toNamed.serialize should be ("Top.Top.q2_wire") + wire_io.enq.toNamed.serialize should be ("Top.Top.q2_wire.io.enq") + mod.q2_wire.instanceName should be ("q2_wire") + wire_io.enq.instanceName should be ("q2_wire.io.enq") + mod.q2_wire.pathName should be ("Top.q2_wire") + wire_io.enq.pathName should be ("Top.q2_wire.io.enq") + mod.q2_wire.parentPathName should be ("Top") + wire_io.enq.parentPathName should be ("Top") + mod.q2_wire.parentModName should be ("Top") + wire_io.enq.parentModName should be ("Top") + } + } -- cgit v1.2.3 From b87107ad41e948de9da9c349505de414b1a9db7f Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Mon, 28 Jun 2021 14:07:03 -0700 Subject: Set refs for ModuleClone and ClonePorts in less hacky way --- core/src/main/scala/chisel3/Module.scala | 56 +++++++++++----------- core/src/main/scala/chisel3/RawModule.scala | 2 + .../main/scala/chisel3/internal/firrtl/IR.scala | 12 +++-- 3 files changed, 39 insertions(+), 31 deletions(-) diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala index 77735583..8a914fbc 100644 --- a/core/src/main/scala/chisel3/Module.scala +++ b/core/src/main/scala/chisel3/Module.scala @@ -184,48 +184,44 @@ package internal { object BaseModule { // Private internal class to serve as a _parent for Data in cloned ports - private[chisel3] class ModuleClone(proto: BaseModule) extends BaseModule { + private[chisel3] class ModuleClone(_proto: BaseModule) extends BaseModule { + // ClonePorts that hold the bound ports for this module + // Used for setting the refs of both this module and the Record + private[BaseModule] var _portsRecord: Record = _ // Don't generate a component, but point to the one for the cloned Module private[chisel3] def generateComponent(): Option[Component] = { - _component = proto._component + _component = _proto._component None } // This module doesn't acutally exist in the FIRRTL so no initialization to do private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = () - override def desiredName: String = proto.name + override def desiredName: String = _proto.name + + private[chisel3] def setRefAndPortsRef(namespace: Namespace): Unit = { + val record = _portsRecord + // Use .forceName to re-use default name resolving behavior + record.forceName(None, default=this.desiredName, namespace) + // Now take the Ref that forceName set and convert it to the correct Arg + val instName = record.getRef match { + case Ref(name) => name + 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(_proto, instName), force=true) // force because we did .forceName first + this.setRef(Ref(instName)) + } } /** Record type returned by CloneModuleAsRecord * * @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. - * @param proto Optional pointer to the Module we are a clone of. Set for first instance, unset - * for clones */ - private[chisel3] class ClonePorts (proto: Option[BaseModule], elts: Data*)(implicit compileOptions: CompileOptions) extends Record { + private[chisel3] class ClonePorts (elts: Data*)(implicit compileOptions: CompileOptions) extends Record { val elements = ListMap(elts.map(d => d.instanceName -> d.cloneTypeFull): _*) def apply(field: String) = elements(field) - override def cloneType = (new ClonePorts(None, elts: _*)).asInstanceOf[this.type] - - // Because ClonePorts instances are *not* created inside of their parent module, but rather, - // their parent's parent, we have to intercept the standard setRef and replace it with our own - // special Ref type. - // This only applies to ClonePorts created in cloneIORecord, any clones of these Records have - // normal behavior. - // Also, the name of ClonePorts Records needs to be propagated to their parent ModuleClone - // since we have no other way of setting the instance name for those. - private[chisel3] override def setRef(imm: Arg, force: Boolean): Unit = { - val immx = (proto, imm) match { - case (Some(mod), Ref(name)) => - // Our _parent is a ModuleClone that needs its ref to match ours for .toAbsoluteTarget - _parent.foreach(_.setRef(Ref(name), force=true)) - // Return a specialize-ref that will do the right thing - ModuleCloneIO(mod, name) - case _ => imm - } - super.setRef(immx, force) - } + override def cloneType = (new ClonePorts(elts: _*)).asInstanceOf[this.type] } // Recursively set the parent of the start Data and any children (eg. in an Aggregate) @@ -243,12 +239,16 @@ package internal { private[chisel3] def cloneIORecord(proto: BaseModule)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): ClonePorts = { require(proto.isClosed, "Can't clone a module before module close") + // Fake Module to serve as the _parent of the cloned ports + // We make this before clonePorts because we want it to come up first in naming in + // currentModule + val cloneParent = Module(new ModuleClone(proto)) // 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(Some(proto), proto.getModulePorts: _*) - val cloneParent = Module(new ModuleClone(proto)) + val clonePorts = new ClonePorts(proto.getModulePorts: _*) clonePorts.bind(PortBinding(cloneParent)) setAllParents(clonePorts, Some(cloneParent)) + cloneParent._portsRecord = clonePorts // Normally handled during Module construction but ClonePorts really lives in its parent's parent if (!compileOptions.explicitInvalidate) { pushCommand(DefInvalid(sourceInfo, clonePorts.ref)) diff --git a/core/src/main/scala/chisel3/RawModule.scala b/core/src/main/scala/chisel3/RawModule.scala index 5bcd4dbd..4a60ca47 100644 --- a/core/src/main/scala/chisel3/RawModule.scala +++ b/core/src/main/scala/chisel3/RawModule.scala @@ -7,6 +7,7 @@ import scala.util.Try import scala.language.experimental.macros import chisel3.experimental.{BaseModule, BaseSim} import chisel3.internal._ +import chisel3.internal.BaseModule.ModuleClone import chisel3.internal.Builder._ import chisel3.internal.firrtl._ import chisel3.internal.sourceinfo.UnlocatableSourceInfo @@ -74,6 +75,7 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) // All suggestions are in, force names to every node. for (id <- getIds) { id match { + case id: ModuleClone => id.setRefAndPortsRef(_namespace) // special handling case id: BaseModule => id.forceName(None, default=id.desiredName, _namespace) case id: MemBase[_] => id.forceName(None, default="MEM", _namespace) case id: BaseSim => id.forceName(None, default="SIM", _namespace) diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index 1d77802b..a4f6d26d 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -161,15 +161,21 @@ case class IntervalLit(n: BigInt, w: Width, binaryPoint: BinaryPoint) extends Li } case class Ref(name: String) extends Arg +/** Arg for ports of Modules + * @param mod the module this port belongs to + * @param name the name of the port + */ case class ModuleIO(mod: BaseModule, name: String) extends Arg { override def fullName(ctx: Component): String = if (mod eq ctx.id) name else s"${mod.getRef.name}.$name" } -// For use with CloneModuleAsRecord -// Note that `name` is the name of the module instance whereas in ModuleIO it's the name of the port -// The names of ports inside of a ModuleCloneIO are the names of the Slots +/** Ports of cloned modules (CloneModuleAsRecord) + * @param mod The original module for which these ports are a clone + * @param name the name of the module instance + */ case class ModuleCloneIO(mod: BaseModule, name: String) extends Arg { override def fullName(ctx: Component): String = + // NOTE: mod eq ctx.id only occurs in Target and Named-related APIs if (mod eq ctx.id) "" else name } case class Slot(imm: Node, name: String) extends Arg { -- cgit v1.2.3 From f210909e11f95cbc033898b22d3498ac7a7a87bc Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Tue, 29 Jun 2021 09:47:43 +0800 Subject: deprecate getPorts with modulePorts. (#1945) * deprecate getPorts with modulePorts. * add doc to fullModulePorts and update deprecation notes. Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- core/src/main/scala/chisel3/Data.scala | 4 +++- core/src/main/scala/chisel3/RawModule.scala | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/chisel3/Data.scala b/core/src/main/scala/chisel3/Data.scala index 0832161e..5513035b 100644 --- a/core/src/main/scala/chisel3/Data.scala +++ b/core/src/main/scala/chisel3/Data.scala @@ -156,7 +156,9 @@ package experimental { // with compiled artifacts (vs. elaboration-time reflection)? def modulePorts(target: BaseModule): Seq[(String, Data)] = target.getChiselPorts - // Returns all module ports with underscore-qualified names + /** Returns all module ports with underscore-qualified names + * return includes [[Module.clock]] and [[Module.reset]] + */ def fullModulePorts(target: BaseModule): Seq[(String, Data)] = { def getPortNames(name: String, data: Data): Seq[(String, Data)] = Seq(name -> data) ++ (data match { case _: Element => Seq() diff --git a/core/src/main/scala/chisel3/RawModule.scala b/core/src/main/scala/chisel3/RawModule.scala index 4a60ca47..fadb8dae 100644 --- a/core/src/main/scala/chisel3/RawModule.scala +++ b/core/src/main/scala/chisel3/RawModule.scala @@ -36,6 +36,7 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) // // For debuggers/testers, TODO: refactor out into proper public API private var _firrtlPorts: Option[Seq[firrtl.Port]] = None + @deprecated("Use DataMirror.fullModulePorts instead. this API will be removed in Chisel 3.6", "Chisel 3.5") lazy val getPorts = _firrtlPorts.get val compileOptions = moduleCompileOptions -- cgit v1.2.3 From 8724cd542df2e907069d9ee3fd7d1675a9ca9287 Mon Sep 17 00:00:00 2001 From: anniej-sifive Date: Mon, 28 Jun 2021 19:06:47 -0700 Subject: updated readme to mention verilator dependency (#1984) * added to readme * Update README.md Co-authored-by: Jack Koenig Co-authored-by: Jack Koenig Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 2cf78760..1f302c53 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,9 @@ cd chisel3 sbt compile ``` +In order to run the following unit tests, make sure you have Verilator installed and on your `PATH` +(you can check this by running `which verilator`). + If the compilation succeeded, you can then run the included unit tests by invoking: ``` -- cgit v1.2.3 From 0531cb53d3cedaff33c2a280e34418f6af5bc6a1 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 29 Jun 2021 15:34:18 -0700 Subject: Restore aop.Select behavior for CloneModuleAsRecord --- core/src/main/scala/chisel3/Module.scala | 4 +++- src/main/scala/chisel3/aop/Select.scala | 6 +++++- src/test/scala/chiselTests/aop/SelectSpec.scala | 25 +++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala index 8a914fbc..f82b6a7b 100644 --- a/core/src/main/scala/chisel3/Module.scala +++ b/core/src/main/scala/chisel3/Module.scala @@ -184,12 +184,14 @@ package internal { object BaseModule { // Private internal class to serve as a _parent for Data in cloned ports - private[chisel3] class ModuleClone(_proto: BaseModule) extends BaseModule { + private[chisel3] class ModuleClone(private[chisel3] val _proto: BaseModule) extends BaseModule { // ClonePorts that hold the bound ports for this module // Used for setting the refs of both this module and the Record private[BaseModule] var _portsRecord: Record = _ // Don't generate a component, but point to the one for the cloned Module private[chisel3] def generateComponent(): Option[Component] = { + require(!_closed, "Can't generate module more than once") + _closed = true _component = _proto._component None } diff --git a/src/main/scala/chisel3/aop/Select.scala b/src/main/scala/chisel3/aop/Select.scala index b9ad808b..08cf40ff 100644 --- a/src/main/scala/chisel3/aop/Select.scala +++ b/src/main/scala/chisel3/aop/Select.scala @@ -6,6 +6,7 @@ import chisel3._ import chisel3.experimental.{BaseModule, FixedPoint} import chisel3.internal.HasId import chisel3.internal.firrtl._ +import chisel3.internal.BaseModule.ModuleClone import firrtl.annotations.ReferenceTarget import scala.collection.mutable @@ -82,7 +83,10 @@ object Select { check(module) module._component.get match { case d: DefModule => d.commands.collect { - case i: DefInstance => i.id + case i: DefInstance => i.id match { + case clone: ModuleClone => clone._proto + case other => other + } } case other => Nil } diff --git a/src/test/scala/chiselTests/aop/SelectSpec.scala b/src/test/scala/chiselTests/aop/SelectSpec.scala index 91353f5a..d34f4391 100644 --- a/src/test/scala/chiselTests/aop/SelectSpec.scala +++ b/src/test/scala/chiselTests/aop/SelectSpec.scala @@ -153,5 +153,30 @@ class SelectSpec extends ChiselFlatSpec { assert(bbs.size == 1) } + "CloneModuleAsRecord" should "show up in Select aspects as duplicates" in { + import chisel3.experimental.CloneModuleAsRecord + class Child extends RawModule { + val in = IO(Input(UInt(8.W))) + val out = IO(Output(UInt(8.W))) + out := in + } + class Top extends MultiIOModule { + val in = IO(Input(UInt(8.W))) + val out = IO(Output(UInt(8.W))) + val inst0 = Module(new Child) + val inst1 = CloneModuleAsRecord(inst0) + inst0.in := in + inst1("in") := inst0.out + out := inst1("out") + } + val top = ChiselGeneratorAnnotation(() => { + new Top() + }).elaborate + .collectFirst { case DesignAnnotation(design: Top) => design } + .get + val mods = Select.collectDeep(top) { case mod => mod } + mods should equal (Seq(top, top.inst0, top.inst0)) + } + } -- cgit v1.2.3 From 25a84b5667614ea3f437b656f1939caba57e6f66 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 29 Jun 2021 16:39:45 -0700 Subject: Change behavior of aop.Select to not include CloneModuleAsRecord Previously, CloneModuleAsRecord clones would result in the same BaseModule object coming up multiple times when using APIs like .instances, .collectDeep, and .getDeep. This was not the intended behavior and can lead to very subtle bugs. --- core/src/main/scala/chisel3/Module.scala | 2 +- src/main/scala/chisel3/aop/Select.scala | 7 ++++--- src/test/scala/chiselTests/aop/SelectSpec.scala | 7 ++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala index f82b6a7b..41fe4554 100644 --- a/core/src/main/scala/chisel3/Module.scala +++ b/core/src/main/scala/chisel3/Module.scala @@ -184,7 +184,7 @@ package internal { object BaseModule { // Private internal class to serve as a _parent for Data in cloned ports - private[chisel3] class ModuleClone(private[chisel3] val _proto: BaseModule) extends BaseModule { + private[chisel3] class ModuleClone(_proto: BaseModule) extends BaseModule { // ClonePorts that hold the bound ports for this module // Used for setting the refs of both this module and the Record private[BaseModule] var _portsRecord: Record = _ diff --git a/src/main/scala/chisel3/aop/Select.scala b/src/main/scala/chisel3/aop/Select.scala index 08cf40ff..a16c415c 100644 --- a/src/main/scala/chisel3/aop/Select.scala +++ b/src/main/scala/chisel3/aop/Select.scala @@ -82,11 +82,12 @@ object Select { def instances(module: BaseModule): Seq[BaseModule] = { check(module) module._component.get match { - case d: DefModule => d.commands.collect { + case d: DefModule => d.commands.flatMap { case i: DefInstance => i.id match { - case clone: ModuleClone => clone._proto - case other => other + case _: ModuleClone => None + case other => Some(other) } + case _ => None } case other => Nil } diff --git a/src/test/scala/chiselTests/aop/SelectSpec.scala b/src/test/scala/chiselTests/aop/SelectSpec.scala index d34f4391..14ae202d 100644 --- a/src/test/scala/chiselTests/aop/SelectSpec.scala +++ b/src/test/scala/chiselTests/aop/SelectSpec.scala @@ -153,7 +153,7 @@ class SelectSpec extends ChiselFlatSpec { assert(bbs.size == 1) } - "CloneModuleAsRecord" should "show up in Select aspects as duplicates" in { + "CloneModuleAsRecord" should "NOT show up in Select aspects" in { import chisel3.experimental.CloneModuleAsRecord class Child extends RawModule { val in = IO(Input(UInt(8.W))) @@ -174,8 +174,9 @@ class SelectSpec extends ChiselFlatSpec { }).elaborate .collectFirst { case DesignAnnotation(design: Top) => design } .get - val mods = Select.collectDeep(top) { case mod => mod } - mods should equal (Seq(top, top.inst0, top.inst0)) + Select.collectDeep(top) { case x => x } should equal (Seq(top, top.inst0)) + Select.getDeep(top)(x => Seq(x)) should equal (Seq(top, top.inst0)) + Select.instances(top) should equal (Seq(top.inst0)) } } -- cgit v1.2.3 From 1e40c7e2a2fd227c752e4e4dc29925004f790f7d Mon Sep 17 00:00:00 2001 From: Boyang Han Date: Wed, 23 Jun 2021 17:37:27 +0800 Subject: Add 7 segment display decoder test case --- .../util/experimental/minimizer/MinimizerSpec.scala | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala b/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala index a9e56800..5e3be9a6 100644 --- a/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala +++ b/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala @@ -96,6 +96,24 @@ trait MinimizerSpec extends SMTModelCheckingSpec { ), "caseMultiDefault") } + "case7SegDecoder" should "pass" in { + minimizerTest(TruthTable( + Map( + BitPat("b0000") -> BitPat("b111111001"), + BitPat("b0001") -> BitPat("b011000001"), + BitPat("b0010") -> BitPat("b110110101"), + BitPat("b0011") -> BitPat("b111100101"), + BitPat("b0100") -> BitPat("b011001101"), + BitPat("b0101") -> BitPat("b101101101"), + BitPat("b0110") -> BitPat("b101111101"), + BitPat("b0111") -> BitPat("b111000001"), + BitPat("b1000") -> BitPat("b111111101"), + BitPat("b1001") -> BitPat("b111101101"), + ), + BitPat("b???????10") + ), "case7SegDecoder") + } + // A simple RV32I decode table example "caseRV32I" should "pass" in { val BEQ = "?????????????????000?????1100011" -- cgit v1.2.3 From 04caf395c737450c26f59d373d76b567a2b80f0f Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 1 Jul 2021 16:33:38 -0700 Subject: Change Chisel warnings to use logger instead of println It also uses the same logger as the Builder so that if we ever refactor that to be passed as an argument, it will be the same logger for both Builder and warning reporting. --- core/src/main/scala/chisel3/internal/Builder.scala | 2 +- core/src/main/scala/chisel3/internal/Error.scala | 25 +++++++++++----------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index 35c4bdf9..fef12093 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -652,7 +652,7 @@ private[chisel3] object Builder extends LazyLogging { logger.warn("Elaborating design...") val mod = f mod.forceName(None, mod.name, globalNamespace) - errors.checkpoint() + errors.checkpoint(logger) logger.warn("Done elaborating.") (Circuit(components.last.name, components.toSeq, annotations.toSeq), mod) diff --git a/core/src/main/scala/chisel3/internal/Error.scala b/core/src/main/scala/chisel3/internal/Error.scala index 454be360..d6e0c0e6 100644 --- a/core/src/main/scala/chisel3/internal/Error.scala +++ b/core/src/main/scala/chisel3/internal/Error.scala @@ -4,6 +4,7 @@ package chisel3.internal import scala.annotation.tailrec import scala.collection.mutable.{ArrayBuffer, LinkedHashMap} +import _root_.logger.Logger object ExceptionHelpers { @@ -184,31 +185,31 @@ private[chisel3] class ErrorLog { } /** Throw an exception if any errors have yet occurred. */ - def checkpoint(): Unit = { + def checkpoint(logger: Logger): Unit = { deprecations.foreach { case ((message, sourceLoc), count) => - println(s"${ErrorLog.depTag} $sourceLoc ($count calls): $message") + logger.warn(s"${ErrorLog.depTag} $sourceLoc ($count calls): $message") } - errors foreach println + errors.foreach(e => logger.error(e.toString)) if (!deprecations.isEmpty) { - println(s"${ErrorLog.warnTag} ${Console.YELLOW}There were ${deprecations.size} deprecated function(s) used." + + logger.warn(s"${ErrorLog.warnTag} ${Console.YELLOW}There were ${deprecations.size} deprecated function(s) used." + s" These may stop compiling in a future release - you are encouraged to fix these issues.${Console.RESET}") - println(s"${ErrorLog.warnTag} Line numbers for deprecations reported by Chisel may be inaccurate; enable scalac compiler deprecation warnings via either of the following methods:") - println(s"${ErrorLog.warnTag} In the sbt interactive console, enter:") - println(s"""${ErrorLog.warnTag} set scalacOptions in ThisBuild ++= Seq("-unchecked", "-deprecation")""") - println(s"${ErrorLog.warnTag} or, in your build.sbt, add the line:") - println(s"""${ErrorLog.warnTag} scalacOptions := Seq("-unchecked", "-deprecation")""") + logger.warn(s"${ErrorLog.warnTag} Line numbers for deprecations reported by Chisel may be inaccurate; enable scalac compiler deprecation warnings via either of the following methods:") + logger.warn(s"${ErrorLog.warnTag} In the sbt interactive console, enter:") + logger.warn(s"""${ErrorLog.warnTag} set scalacOptions in ThisBuild ++= Seq("-unchecked", "-deprecation")""") + logger.warn(s"${ErrorLog.warnTag} or, in your build.sbt, add the line:") + logger.warn(s"""${ErrorLog.warnTag} scalacOptions := Seq("-unchecked", "-deprecation")""") } val allErrors = errors.filter(_.isFatal) val allWarnings = errors.filter(!_.isFatal) if (!allWarnings.isEmpty && !allErrors.isEmpty) { - println(s"${ErrorLog.errTag} There were ${Console.RED}${allErrors.size} error(s)${Console.RESET} and ${Console.YELLOW}${allWarnings.size} warning(s)${Console.RESET} during hardware elaboration.") + logger.warn(s"${ErrorLog.errTag} There were ${Console.RED}${allErrors.size} error(s)${Console.RESET} and ${Console.YELLOW}${allWarnings.size} warning(s)${Console.RESET} during hardware elaboration.") } else if (!allWarnings.isEmpty) { - println(s"${ErrorLog.warnTag} There were ${Console.YELLOW}${allWarnings.size} warning(s)${Console.RESET} during hardware elaboration.") + logger.warn(s"${ErrorLog.warnTag} There were ${Console.YELLOW}${allWarnings.size} warning(s)${Console.RESET} during hardware elaboration.") } else if (!allErrors.isEmpty) { - println(s"${ErrorLog.errTag} There were ${Console.RED}${allErrors.size} error(s)${Console.RESET} during hardware elaboration.") + logger.warn(s"${ErrorLog.errTag} There were ${Console.RED}${allErrors.size} error(s)${Console.RESET} during hardware elaboration.") } if (!allErrors.isEmpty) { -- cgit v1.2.3 From 5fe539c707c88eedbb112f5c6bcea1dfe1d52169 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 1 Jul 2021 16:34:48 -0700 Subject: Add ChiselEnum.safe factory method and avoid warning Previously, ChiselEnum would warn any time a UInt is converted to an Enum. There was no way to suppress this warning. Now there is a factory method (`.safe`) that does not warn and returns (Enum, Bool) where the Bool is the result of calling .isValid on an Enum object. The regular UInt cast is also now smarter and will not warn if all bitvectors of the width of the Enum are legal states. --- core/src/main/scala/chisel3/StrongEnum.scala | 34 ++++++++-- src/test/scala/chiselTests/ChiselSpec.scala | 17 ++++- src/test/scala/chiselTests/StrongEnum.scala | 97 ++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/chisel3/StrongEnum.scala b/core/src/main/scala/chisel3/StrongEnum.scala index 7d328eb7..1d0e04d3 100644 --- a/core/src/main/scala/chisel3/StrongEnum.scala +++ b/core/src/main/scala/chisel3/StrongEnum.scala @@ -131,7 +131,7 @@ abstract class EnumType(private val factory: EnumFactory, selfAnnotating: Boolea if (litOption.isDefined) { true.B } else { - factory.all.map(this === _).reduce(_ || _) + if (factory.isTotal) true.B else factory.all.map(this === _).reduce(_ || _) } } @@ -233,6 +233,12 @@ abstract class EnumFactory { private[chisel3] val enumTypeName = getClass.getName.init + // Do all bitvectors of this Enum's width represent legal states? + private[chisel3] def isTotal: Boolean = { + (this.getWidth < 31) && // guard against Integer overflow + (enumRecords.size == (1 << this.getWidth)) + } + private[chisel3] def globalAnnotation: EnumDefChiselAnnotation = EnumDefChiselAnnotation(enumTypeName, (enumNames, enumValues).zipped.toMap) @@ -277,7 +283,7 @@ abstract class EnumFactory { def apply(): Type = new Type - def apply(n: UInt)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): Type = { + private def castImpl(n: UInt, warn: Boolean)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): Type = { if (n.litOption.isDefined) { enumInstances.find(_.litValue == n.litValue) match { case Some(result) => result @@ -288,8 +294,9 @@ abstract class EnumFactory { } else if (n.getWidth > this.getWidth) { throwException(s"The UInt being cast to $enumTypeName is wider than $enumTypeName's width ($getWidth)") } else { - Builder.warning(s"Casting non-literal UInt to $enumTypeName. You can check that its value is legal by calling isValid") - + if (warn && !this.isTotal) { + Builder.warning(s"Casting non-literal UInt to $enumTypeName. You can use $enumTypeName.safe to cast without this warning.") + } val glue = Wire(new UnsafeEnum(width)) glue := n val result = Wire(new Type) @@ -297,6 +304,25 @@ abstract class EnumFactory { result } } + + /** Cast an [[UInt]] to the type of this Enum + * + * @note will give a Chisel elaboration time warning if the argument could hit invalid states + * @param n the UInt to cast + * @return the equivalent Enum to the value of the cast UInt + */ + def apply(n: UInt)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): Type = castImpl(n, warn = true) + + /** Safely cast an [[UInt]] to the type of this Enum + * + * @param n the UInt to cast + * @return the equivalent Enum to the value of the cast UInt and a Bool indicating if the + * Enum is valid + */ + def safe(n: UInt)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): (Type, Bool) = { + val t = castImpl(n, warn = false) + (t, t.isValid) + } } diff --git a/src/test/scala/chiselTests/ChiselSpec.scala b/src/test/scala/chiselTests/ChiselSpec.scala index a4192c5e..e513189e 100644 --- a/src/test/scala/chiselTests/ChiselSpec.scala +++ b/src/test/scala/chiselTests/ChiselSpec.scala @@ -9,6 +9,7 @@ import chisel3.testers._ import firrtl.annotations.Annotation import firrtl.util.BackendCompilationUtilities import firrtl.{AnnotationSeq, EmittedVerilogCircuitAnnotation} +import _root_.logger.Logger import org.scalacheck._ import org.scalatest._ import org.scalatest.flatspec.AnyFlatSpec @@ -17,7 +18,7 @@ import org.scalatest.propspec.AnyPropSpec import org.scalatest.matchers.should.Matchers import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks -import java.io.ByteArrayOutputStream +import java.io.{ByteArrayOutputStream, PrintStream} import java.security.Permission import scala.reflect.ClassTag @@ -172,6 +173,20 @@ trait Utils { (stdout.toString, stderr.toString, ret) } + /** Run some Scala thunk and return all logged messages as Strings + * @param thunk some Scala code + * @return a tuple containing LOGGED, and what the thunk returns + */ + def grabLog[T](thunk: => T): (String, T) = { + val baos = new ByteArrayOutputStream() + val stream = new PrintStream(baos, true, "utf-8") + val ret = Logger.makeScope(Nil) { + Logger.setOutput(stream) + thunk + } + (baos.toString, ret) + } + /** Encodes a System.exit exit code * @param status the exit code */ diff --git a/src/test/scala/chiselTests/StrongEnum.scala b/src/test/scala/chiselTests/StrongEnum.scala index bf0eb2fe..e59a5398 100644 --- a/src/test/scala/chiselTests/StrongEnum.scala +++ b/src/test/scala/chiselTests/StrongEnum.scala @@ -74,6 +74,18 @@ class CastFromNonLit extends Module { io.valid := io.out.isValid } +class SafeCastFromNonLit extends Module { + val io = IO(new Bundle { + val in = Input(UInt(EnumExample.getWidth.W)) + val out = Output(EnumExample()) + val valid = Output(Bool()) + }) + + val (enum, valid) = EnumExample.safe(io.in) + io.out := enum + io.valid := valid +} + class CastFromNonLitWidth(w: Option[Int] = None) extends Module { val width = if (w.isDefined) w.get.W else UnknownWidth() @@ -191,6 +203,28 @@ class CastFromNonLitTester extends BasicTester { stop() } +class SafeCastFromNonLitTester extends BasicTester { + for ((enum,lit) <- EnumExample.all zip EnumExample.litValues) { + val mod = Module(new SafeCastFromNonLit) + mod.io.in := lit + assert(mod.io.out === enum) + assert(mod.io.valid === true.B) + } + + val invalid_values = (1 until (1 << EnumExample.getWidth)). + filter(!EnumExample.litValues.map(_.litValue).contains(_)). + map(_.U) + + for (invalid_val <- invalid_values) { + val mod = Module(new SafeCastFromNonLit) + mod.io.in := invalid_val + + assert(mod.io.valid === false.B) + } + + stop() +} + class CastToInvalidEnumTester extends BasicTester { val invalid_value: UInt = EnumExample.litValues.last + 1.U Module(new CastFromLit(invalid_value)) @@ -320,6 +354,10 @@ class StrongEnumSpec extends ChiselFlatSpec with Utils { assertTesterPasses(new CastFromNonLitTester) } + it should "safely cast non-literal UInts to enums correctly and detect illegal casts" in { + assertTesterPasses(new SafeCastFromNonLitTester) + } + it should "prevent illegal literal casts to enums" in { a [ChiselException] should be thrownBy extractCause[ChiselException] { ChiselStage.elaborate(new CastToInvalidEnumTester) @@ -377,6 +415,65 @@ class StrongEnumSpec extends ChiselFlatSpec with Utils { "StrongEnum FSM" should "work" in { assertTesterPasses(new StrongEnumFSMTester) } + + "Casting a UInt to an Enum" should "warn if the UInt can express illegal states" in { + object MyEnum extends ChiselEnum { + val e0, e1, e2 = Value + } + + class MyModule extends Module { + val in = IO(Input(UInt(2.W))) + val out = IO(Output(MyEnum())) + out := MyEnum(in) + } + val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) + log should include ("warn") + log should include ("Casting non-literal UInt") + } + + it should "NOT warn if the Enum is total" in { + object TotalEnum extends ChiselEnum { + val e0, e1, e2, e3 = Value + } + + class MyModule extends Module { + val in = IO(Input(UInt(2.W))) + val out = IO(Output(TotalEnum())) + out := TotalEnum(in) + } + val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) + log should not include ("warn") + } + + "Casting a UInt to an Enum with .safe" should "NOT warn" in { + object MyEnum extends ChiselEnum { + val e0, e1, e2 = Value + } + + class MyModule extends Module { + val in = IO(Input(UInt(2.W))) + val out = IO(Output(MyEnum())) + out := MyEnum.safe(in)._1 + } + val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) + log should not include ("warn") + } + + it should "NOT generate any validity logic if the Enum is total" in { + object TotalEnum extends ChiselEnum { + val e0, e1, e2, e3 = Value + } + + class MyModule extends Module { + val in = IO(Input(UInt(2.W))) + val out = IO(Output(TotalEnum())) + val (res, valid) = TotalEnum.safe(in) + assert(valid.litToBoolean, "It should be true.B") + out := res + } + val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) + log should not include ("warn") + } } class StrongEnumAnnotator extends Module { -- cgit v1.2.3 From bfb77d4cc1163e34cc54ce856214a0181b2cb029 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 1 Jul 2021 18:03:55 -0700 Subject: Update docs for ChiselEnum --- docs/src/explanations/chisel-enum.md | 170 ++++++++++++++++++++++++----------- 1 file changed, 118 insertions(+), 52 deletions(-) diff --git a/docs/src/explanations/chisel-enum.md b/docs/src/explanations/chisel-enum.md index cb017239..3f0f3b18 100644 --- a/docs/src/explanations/chisel-enum.md +++ b/docs/src/explanations/chisel-enum.md @@ -1,4 +1,4 @@ ---- +--- layout: docs title: "Enumerations" section: "chisel3" @@ -6,8 +6,8 @@ section: "chisel3" # ChiselEnum -The ChiselEnum type can be used to reduce the chance of error when encoding mux selectors, opcodes, and functional unit operations. In contrast with`Chisel.util.Enum`, `ChiselEnum` are subclasses of `Data`, which means that they can be used to define fields in `Bundle`s, including in `IO`s. - +The ChiselEnum type can be used to reduce the chance of error when encoding mux selectors, opcodes, and functional unit operations. +In contrast with `Chisel.util.Enum`, `ChiselEnum` are subclasses of `Data`, which means that they can be used to define fields in `Bundle`s, including in `IO`s. ## Functionality and Examples @@ -19,57 +19,68 @@ import chisel3.stage.ChiselStage import chisel3.experimental.ChiselEnum ``` -Below we see ChiselEnum being used as mux select for a RISC-V core. While wrapping the object in a package is not required, it is highly recommended as it allows for the type to be used in multiple files more easily. +```scala mdoc:invisible +// Helper to print stdout from Chisel elab +// May be related to: https://github.com/scalameta/mdoc/issues/517 +import java.io._ +import _root_.logger.Logger +def grabLog[T](thunk: => T): (String, T) = { + val baos = new ByteArrayOutputStream() + val stream = new PrintStream(baos, true, "utf-8") + val ret = Logger.makeScope(Nil) { + Logger.setOutput(stream) + thunk + } + (baos.toString, ret) +} +``` + +Below we see ChiselEnum being used as mux select for a RISC-V core. While wrapping the object in a package is not required, it is highly recommended as it allows for the type to be used in multiple files more easily. ```scala mdoc // package CPUTypes { - object AluMux1Sel extends ChiselEnum { - val selectRS1, selectPC = Value - /** How the values will be mapped - "selectRS1" -> 0.U, - "selectPC" -> 1.U - */ - } -// } +object AluMux1Sel extends ChiselEnum { + val selectRS1, selectPC = Value +} +// We can see the mapping by printing each Value +AluMux1Sel.all.foreach(println) ``` -Here we see a mux using the AluMux1Sel to select between different inputs. +Here we see a mux using the AluMux1Sel to select between different inputs. ```scala mdoc import AluMux1Sel._ class AluMux1Bundle extends Bundle { - val aluMux1Sel = Input( AluMux1Sel() ) - val rs1Out = Input(Bits(32.W)) - val pcOut = Input(Bits(32.W)) - val aluMux1Out = Output(Bits(32.W)) + val aluMux1Sel = Input(AluMux1Sel()) + val rs1Out = Input(Bits(32.W)) + val pcOut = Input(Bits(32.W)) + val aluMux1Out = Output(Bits(32.W)) } class AluMux1File extends Module { - val io = IO(new AluMux1Bundle) - - // Default value for aluMux1Out - io.aluMux1Out := 0.U - - switch (io.aluMux1Sel) { - is (selectRS1) { - io.aluMux1Out := io.rs1Out - } - is (selectPC) { - io.aluMux1Out := io.pcOut - } + val io = IO(new AluMux1Bundle) + + // Default value for aluMux1Out + io.aluMux1Out := 0.U + + switch (io.aluMux1Sel) { + is (selectRS1) { + io.aluMux1Out := io.rs1Out } + is (selectPC) { + io.aluMux1Out := io.pcOut + } + } } ``` ```scala mdoc:verilog -ChiselStage.emitVerilog(new AluMux1File ) +ChiselStage.emitVerilog(new AluMux1File) ``` -ChiselEnum also allows for the user to define variables by passing in the value shown below. Note that the value must be increasing or else - - > chisel3.internal.ChiselException: Exception thrown when elaborating ChiselGeneratorAnnotation - -is thrown during Verilog generation. +ChiselEnum also allows for the user to directly set the Values by passing an `UInt` to `Value(...)` +as shown below. Note that the magnitude of each `Value` must be strictly greater than the one before +it. ```scala mdoc object Opcode extends ChiselEnum { @@ -85,27 +96,81 @@ object Opcode extends ChiselEnum { } ``` -The user can 'jump' to a value and continue incrementing by passing a start point then using a regular Value assignment. +The user can 'jump' to a value and continue incrementing by passing a start point then using a regular Value definition. ```scala mdoc object BranchFunct3 extends ChiselEnum { val beq, bne = Value val blt = Value(4.U) val bge, bltu, bgeu = Value - /** How the values will be mapped - "beq" -> 0.U, - "bne" -> 1.U, - "blt" -> 4.U, - "bge" -> 5.U, - "bltu" -> 6.U, - "bgeu" -> 7.U - */ } +// We can see the mapping by printing each Value +BranchFunct3.all.foreach(println) +``` + +## Casting + +You can cast an enum to a `UInt` using `.asUInt`: + +```scala mdoc +class ToUInt extends RawModule { + val in = IO(Input(Opcode())) + val out = IO(Output(UInt())) + out := in.asUInt +} +``` + +```scala mdoc:invisible +// Always need to run Chisel to see if there are elaboration errors +ChiselStage.emitVerilog(new ToUInt) +``` + +You can cast from a `UInt` to an enum by passing the `UInt` to the apply method of the `ChiselEnum` object: + +```scala mdoc +class FromUInt extends Module { + val in = IO(Input(UInt(7.W))) + val out = IO(Output(Opcode())) + out := Opcode(in) +} +``` + +However, if you cast from a `UInt` to an Enum type when there are undefined states in the Enum values +that the `UInt` could hit, you will see a warning like the following: + +```scala mdoc:passthrough +val (log, _) = grabLog(ChiselStage.emitChirrtl(new FromUInt)) +println(s"```$log```") +``` +(Note that the name of the Enum is ugly as an artifact of our documentation generation flow, it will +be cleaner in normal use). + +You can avoid this warning by using the `.safe` factory method which returns the cast Enum in addition +to a `Bool` indicating if the Enum is in a valid state: + +```scala mdoc +class SafeFromUInt extends Module { + val in = IO(Input(UInt(7.W))) + val out = IO(Output(Opcode())) + val (value, valid) = Opcode.safe(in) + assert(valid, "Enum state must be valid, got %d!", in) + out := value +} +``` + +Now there will be no warning: + +```scala mdoc:passthrough +val (log2, _) = grabLog(ChiselStage.emitChirrtl(new SafeFromUInt)) +println(s"```$log2```") ``` ## Testing -When testing your modules, the `.Type` and `.litValue` attributes allow for the the objects to be passed as parameters and for the value to be converted to BigInt type. Note that BigInts cannot be casted to Int with `.asInstanceOf[Int]`, they use their own methods like `toInt`. Please review the [scala.math.BigInt](https://www.scala-lang.org/api/2.12.5/scala/math/BigInt.html) page for more details! +The _Type_ of the enums values is `.Type` which can be useful for passing the values +as parameters to a function (or any other time a type annotation is needed). +Calling `.litValue` on an enum value will return the integer value of that object as a +[`BigInt`](https://www.scala-lang.org/api/2.12.13/scala/math/BigInt.html). ```scala mdoc def expectedSel(sel: AluMux1Sel.Type): Boolean = sel match { @@ -115,22 +180,23 @@ def expectedSel(sel: AluMux1Sel.Type): Boolean = sel match { } ``` -The ChiselEnum type also has methods `.all` and `.getWidth` where `all` returns all of the enum instances and `getWidth` returns the width of the hardware type. +Some additional useful methods defined on the `ChiselEnum` object are: +* `.all`: returns the enum values within the enumeration +* `.getWidth`: returns the width of the hardware type ## Workarounds -As of 2/26/2021, the width of the values is always inferred. To work around this, you can add an extra `Value` that forces the width that is desired. This is shown in the example below, where we add a field `ukn` to force the width to be 3 bits wide: +As of Chisel v3.4.3 (1 July 2020), the width of the values is always inferred. +To work around this, you can add an extra `Value` that forces the width that is desired. +This is shown in the example below, where we add a field `ukn` to force the width to be 3 bits wide: ```scala mdoc object StoreFunct3 extends ChiselEnum { val sb, sh, sw = Value val ukn = Value(7.U) - /** How the values will be mapped - "sb" -> 0.U, - "sh" -> 1.U, - "sw" -> 2.U - */ } +// We can see the mapping by printing each Value +StoreFunct3.all.foreach(println) ``` Signed values are not supported so if you want the value signed, you must cast the UInt with `.asSInt`. -- cgit v1.2.3 From 503ae520e7f997bcbc639b79869c9a4214d402ed Mon Sep 17 00:00:00 2001 From: Deborah Soung Date: Tue, 6 Jul 2021 14:40:59 -0700 Subject: Make printf return BaseSim subclass so it can be named/annotated (#1992) --- core/src/main/scala/chisel3/Printf.scala | 22 +++++--- .../experimental/verification/package.scala | 17 +++--- .../scala/chisel3/internal/firrtl/Converter.scala | 4 +- .../main/scala/chisel3/internal/firrtl/IR.scala | 2 +- src/main/scala/chisel3/aop/Select.scala | 4 +- .../scala/chisel3/internal/firrtl/Emitter.scala | 4 +- src/test/scala/chiselTests/PrintableSpec.scala | 66 ++++++++++++++++++++++ src/test/scala/chiselTests/aop/SelectSpec.scala | 11 ++-- 8 files changed, 103 insertions(+), 27 deletions(-) diff --git a/core/src/main/scala/chisel3/Printf.scala b/core/src/main/scala/chisel3/Printf.scala index 7cbd1918..cf7821b8 100644 --- a/core/src/main/scala/chisel3/Printf.scala +++ b/core/src/main/scala/chisel3/Printf.scala @@ -3,11 +3,10 @@ package chisel3 import scala.language.experimental.macros - import chisel3.internal._ import chisel3.internal.Builder.pushCommand -import chisel3.internal.firrtl._ import chisel3.internal.sourceinfo.SourceInfo +import chisel3.experimental.BaseSim /** Prints a message in simulation * @@ -34,6 +33,9 @@ object printf { formatIn map escaped mkString "" } + /** Named class for [[printf]]s. */ + final class Printf(val pable: Printable) extends BaseSim + /** Prints a message in simulation * * Prints a message every cycle. If defined within the scope of a [[when]] block, the message @@ -71,7 +73,7 @@ object printf { * @param fmt printf format string * @param data format string varargs containing data to print */ - def apply(fmt: String, data: Bits*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Unit = + def apply(fmt: String, data: Bits*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Printf = apply(Printable.pack(fmt, data:_*)) /** Prints a message in simulation * @@ -87,16 +89,20 @@ object printf { * @see [[Printable]] documentation * @param pable [[Printable]] to print */ - def apply(pable: Printable)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Unit = { + def apply(pable: Printable)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Printf = { + var printfId: Printf = null when (!Module.reset.asBool) { - printfWithoutReset(pable) + printfId = printfWithoutReset(pable) } + printfId } - private[chisel3] def printfWithoutReset(pable: Printable)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Unit = { + private[chisel3] def printfWithoutReset(pable: Printable)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Printf = { val clock = Builder.forcedClock - pushCommand(Printf(sourceInfo, clock.ref, pable)) + val printfId = new Printf(pable) + pushCommand(chisel3.internal.firrtl.Printf(printfId, sourceInfo, clock.ref, pable)) + printfId } - private[chisel3] def printfWithoutReset(fmt: String, data: Bits*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Unit = + private[chisel3] def printfWithoutReset(fmt: String, data: Bits*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Printf = printfWithoutReset(Printable.pack(fmt, data:_*)) } diff --git a/core/src/main/scala/chisel3/experimental/verification/package.scala b/core/src/main/scala/chisel3/experimental/verification/package.scala index ca15a5c4..190083fd 100644 --- a/core/src/main/scala/chisel3/experimental/verification/package.scala +++ b/core/src/main/scala/chisel3/experimental/verification/package.scala @@ -9,16 +9,11 @@ import chisel3.internal.sourceinfo.SourceInfo package object verification { - /** Named class for assertions. */ - final class Assert(val predicate: Bool) extends BaseSim - - /** Named class for assumes. */ - final class Assume(val predicate: Bool) extends BaseSim + object assert { + /** Named class for assertions. */ + final class Assert(private[chisel3] val predicate: Bool) extends BaseSim - /** Named class for covers. */ - final class Cover(val predicate: Bool) extends BaseSim - object assert { def apply(predicate: Bool, msg: String = "")( implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Assert = { @@ -32,6 +27,9 @@ package object verification { } object assume { + /** Named class for assumes. */ + final class Assume(private[chisel3] val predicate: Bool) extends BaseSim + def apply(predicate: Bool, msg: String = "")( implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Assume = { @@ -45,6 +43,9 @@ package object verification { } object cover { + /** Named class for covers. */ + final class Cover(private[chisel3] val predicate: Bool) extends BaseSim + def apply(predicate: Bool, msg: String = "")( implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Cover = { diff --git a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala index 093d4848..8efb2abc 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -143,10 +143,10 @@ private[chisel3] object Converter { Some(fir.DefInstance(convert(info), e.name, id.name)) case Stop(info, clock, ret) => Some(fir.Stop(convert(info), ret, convert(clock, ctx, info), firrtl.Utils.one)) - case Printf(info, clock, pable) => + case e @ Printf(_, info, clock, pable) => val (fmt, args) = unpack(pable, ctx) Some(fir.Print(convert(info), fir.StringLit(fmt), - args.map(a => convert(a, ctx, info)), convert(clock, ctx, info), firrtl.Utils.one)) + args.map(a => convert(a, ctx, info)), convert(clock, ctx, info), firrtl.Utils.one, e.name)) case e @ Verification(_, op, info, clk, pred, msg) => val firOp = op match { case Formal.Assert => fir.Formal.Assert diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index a4f6d26d..5796522c 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -773,7 +773,7 @@ case class Attach(sourceInfo: SourceInfo, locs: Seq[Node]) extends Command case class ConnectInit(sourceInfo: SourceInfo, loc: Node, exp: Arg) extends Command case class Stop(sourceInfo: SourceInfo, clock: Arg, ret: Int) extends Command case class Port(id: Data, dir: SpecifiedDirection) -case class Printf(sourceInfo: SourceInfo, clock: Arg, pable: Printable) extends Command +case class Printf(id: printf.Printf, sourceInfo: SourceInfo, clock: Arg, pable: Printable) extends Definition object Formal extends Enumeration { val Assert = Value("assert") val Assume = Value("assume") diff --git a/src/main/scala/chisel3/aop/Select.scala b/src/main/scala/chisel3/aop/Select.scala index a16c415c..078422bb 100644 --- a/src/main/scala/chisel3/aop/Select.scala +++ b/src/main/scala/chisel3/aop/Select.scala @@ -281,7 +281,7 @@ object Select { val printfs = mutable.ArrayBuffer[Printf]() searchWhens(module, (cmd: Command, preds: Seq[Predicate]) => { cmd match { - case chisel3.internal.firrtl.Printf(_, clock, pable) => printfs += Printf(preds, pable, getId(clock).asInstanceOf[Clock]) + case chisel3.internal.firrtl.Printf(id, _, clock, pable) => printfs += Printf(id, preds, pable, getId(clock).asInstanceOf[Clock]) case other => } }) @@ -418,7 +418,7 @@ object Select { * @param pable * @param clock */ - case class Printf(preds: Seq[Predicate], pable: Printable, clock: Clock) extends Serializeable { + case class Printf(id: printf.Printf, preds: Seq[Predicate], pable: Printable, clock: Clock) extends Serializeable { def serialize: String = { s"printf when(${preds.map(_.serialize).mkString(" & ")}) on ${getName(clock)}: $pable" } diff --git a/src/main/scala/chisel3/internal/firrtl/Emitter.scala b/src/main/scala/chisel3/internal/firrtl/Emitter.scala index 53d5c6ce..47849d91 100644 --- a/src/main/scala/chisel3/internal/firrtl/Emitter.scala +++ b/src/main/scala/chisel3/internal/firrtl/Emitter.scala @@ -75,11 +75,11 @@ private class Emitter(circuit: Circuit) { case e: BulkConnect => s"${e.loc1.fullName(ctx)} <- ${e.loc2.fullName(ctx)}" case e: Attach => e.locs.map(_.fullName(ctx)).mkString("attach (", ", ", ")") case e: Stop => s"stop(${e.clock.fullName(ctx)}, UInt<1>(1), ${e.ret})" - case e: Printf => + case e: chisel3.internal.firrtl.Printf => val (fmt, args) = e.pable.unpack(ctx) val printfArgs = Seq(e.clock.fullName(ctx), "UInt<1>(1)", "\"" + printf.format(fmt) + "\"") ++ args - printfArgs mkString ("printf(", ", ", ")") + (printfArgs mkString ("printf(", ", ", ")")) + s": ${e.name}" case e: Verification[_] => s"""${e.op}(${e.clock.fullName(ctx)}, ${e.predicate.fullName(ctx)}, UInt<1>(1), "${printf.format(e.message)}") : ${e.name}""" case e: DefInvalid => s"${e.arg.fullName(ctx)} is invalid" diff --git a/src/test/scala/chiselTests/PrintableSpec.scala b/src/test/scala/chiselTests/PrintableSpec.scala index c76b26de..0325d3bc 100644 --- a/src/test/scala/chiselTests/PrintableSpec.scala +++ b/src/test/scala/chiselTests/PrintableSpec.scala @@ -3,11 +3,33 @@ package chiselTests import chisel3._ +import chisel3.experimental.{BaseSim, ChiselAnnotation} import chisel3.stage.ChiselStage import chisel3.testers.BasicTester +import firrtl.annotations.{ReferenceTarget, SingleTargetAnnotation} import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers +import java.io.File + +/** Dummy [[printf]] annotation. + * @param target target of component to be annotated + */ +case class PrintfAnnotation(target: ReferenceTarget) extends SingleTargetAnnotation[ReferenceTarget] { + def duplicate(n: ReferenceTarget): PrintfAnnotation = this.copy(target = n) +} + +object PrintfAnnotation { + /** Create annotation for a given [[printf]]. + * @param c component to be annotated + */ + def annotate(c: BaseSim): Unit = { + chisel3.experimental.annotate(new ChiselAnnotation { + def toFirrtl: PrintfAnnotation = PrintfAnnotation(c.toTarget) + }) + } +} + /* Printable Tests */ class PrintableSpec extends AnyFlatSpec with Matchers { // This regex is brittle, it specifically finds the clock and enable signals followed by commas @@ -194,4 +216,48 @@ class PrintableSpec extends AnyFlatSpec with Matchers { case e => fail() } } + it should "get emitted with a name and annotated" in { + + /** Test circuit containing annotated and renamed [[printf]]s. */ + class PrintfAnnotationTest extends Module { + val myBun = Wire(new Bundle { + val foo = UInt(32.W) + val bar = UInt(32.W) + }) + myBun.foo := 0.U + myBun.bar := 0.U + val howdy = printf(p"hello ${myBun}") + PrintfAnnotation.annotate(howdy) + PrintfAnnotation.annotate(printf(p"goodbye $myBun")) + PrintfAnnotation.annotate(printf(p"adieu $myBun").suggestName("farewell")) + } + + // compile circuit + val testDir = new File("test_run_dir", "PrintfAnnotationTest") + (new ChiselStage).emitSystemVerilog( + gen = new PrintfAnnotationTest, + args = Array("-td", testDir.getPath) + ) + + // read in annotation file + val annoFile = new File(testDir, "PrintfAnnotationTest.anno.json") + annoFile should exist + val annoLines = scala.io.Source.fromFile(annoFile).getLines.toList + + // check for expected annotations + exactly(3, annoLines) should include ("chiselTests.PrintfAnnotation") + exactly(1, annoLines) should include ("~PrintfAnnotationTest|PrintfAnnotationTest>farewell") + exactly(1, annoLines) should include ("~PrintfAnnotationTest|PrintfAnnotationTest>SIM") + exactly(1, annoLines) should include ("~PrintfAnnotationTest|PrintfAnnotationTest>howdy") + + // read in FIRRTL file + val firFile = new File(testDir, "PrintfAnnotationTest.fir") + firFile should exist + val firLines = scala.io.Source.fromFile(firFile).getLines.toList + + // check that verification components have expected names + exactly(1, firLines) should include ("""printf(clock, UInt<1>(1), "hello AnonymousBundle(foo -> %d, bar -> %d)", myBun.foo, myBun.bar): howdy""") + exactly(1, firLines) should include ("""printf(clock, UInt<1>(1), "goodbye AnonymousBundle(foo -> %d, bar -> %d)", myBun.foo, myBun.bar): SIM""") + exactly(1, firLines) should include ("""printf(clock, UInt<1>(1), "adieu AnonymousBundle(foo -> %d, bar -> %d)", myBun.foo, myBun.bar): farewell""") + } } diff --git a/src/test/scala/chiselTests/aop/SelectSpec.scala b/src/test/scala/chiselTests/aop/SelectSpec.scala index 14ae202d..46c62d67 100644 --- a/src/test/scala/chiselTests/aop/SelectSpec.scala +++ b/src/test/scala/chiselTests/aop/SelectSpec.scala @@ -22,14 +22,16 @@ class SelectTester(results: Seq[Int]) extends BasicTester { val nreset = reset.asBool() === false.B val selected = values(counter) val zero = 0.U + 0.U + var p: printf.Printf = null when(overflow) { counter := zero stop() }.otherwise { when(nreset) { assert(counter === values(counter)) - printf("values(%d) = %d\n", counter, selected) + p = printf("values(%d) = %d\n", counter, selected) } + } } @@ -81,17 +83,18 @@ class SelectSpec extends ChiselFlatSpec { "Test" should "pass if selecting correct printfs" in { execute( () => new SelectTester(Seq(0, 1, 2)), - { dut: SelectTester => Seq(Select.printfs(dut).last) }, + { dut: SelectTester => Seq(Select.printfs(dut).last.toString) }, { dut: SelectTester => Seq(Select.Printf( + dut.p, Seq( When(Select.ops("eq")(dut).last.asInstanceOf[Bool]), When(dut.nreset), WhenNot(dut.overflow) ), - Printable.pack("values(%d) = %d\n", dut.counter, dut.selected), + dut.p.pable, dut.clock - )) + ).toString) } ) } -- cgit v1.2.3 From 558df41db062a14f52617fc44edd0aff569afa67 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Wed, 7 Jul 2021 16:56:35 -0700 Subject: Fix ChiselEnum docs (#2016) Also add newline to end of `verilog` modifier code blocks so that there is always a newline between code blocks and following material.--- docs-target/src/main/scala/chisel3/docs/VerilogMdocModifier.scala | 2 +- docs/src/explanations/chisel-enum.md | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs-target/src/main/scala/chisel3/docs/VerilogMdocModifier.scala b/docs-target/src/main/scala/chisel3/docs/VerilogMdocModifier.scala index a76e412a..f41fff73 100644 --- a/docs-target/src/main/scala/chisel3/docs/VerilogMdocModifier.scala +++ b/docs-target/src/main/scala/chisel3/docs/VerilogMdocModifier.scala @@ -27,7 +27,7 @@ class VerilogMdocModifier extends PostModifier { case (None, _) => None } result match { - case Some(content) => s"```verilog\n$content```" + case Some(content) => s"```verilog\n$content```\n" case None => "" } } diff --git a/docs/src/explanations/chisel-enum.md b/docs/src/explanations/chisel-enum.md index 3f0f3b18..96fc9e8a 100644 --- a/docs/src/explanations/chisel-enum.md +++ b/docs/src/explanations/chisel-enum.md @@ -74,6 +74,7 @@ class AluMux1File extends Module { } } ``` + ```scala mdoc:verilog ChiselStage.emitVerilog(new AluMux1File) ``` @@ -140,8 +141,9 @@ that the `UInt` could hit, you will see a warning like the following: ```scala mdoc:passthrough val (log, _) = grabLog(ChiselStage.emitChirrtl(new FromUInt)) -println(s"```$log```") +println(s"```\n$log```") ``` + (Note that the name of the Enum is ugly as an artifact of our documentation generation flow, it will be cleaner in normal use). @@ -162,7 +164,7 @@ Now there will be no warning: ```scala mdoc:passthrough val (log2, _) = grabLog(ChiselStage.emitChirrtl(new SafeFromUInt)) -println(s"```$log2```") +println(s"```\n$log2```") ``` ## Testing @@ -181,6 +183,7 @@ def expectedSel(sel: AluMux1Sel.Type): Boolean = sel match { ``` Some additional useful methods defined on the `ChiselEnum` object are: + * `.all`: returns the enum values within the enumeration * `.getWidth`: returns the width of the hardware type -- cgit v1.2.3 From f1e37900170124254f3cf4599a45e7a485c17a91 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Wed, 7 Jul 2021 21:05:33 -0400 Subject: Update docs on dontTouch as optimization barrier (#2015) Change the Scaladoc for the dontTouch utility. Indicate that this is an _optimization barrier_ and not just a guarantee that the signal won't be removed. The optimization barrier interpretation is the current implementation in the Scala FIRRTL Compiler. Signed-off-by: Schuyler Eldridge Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- core/src/main/scala/chisel3/dontTouch.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/chisel3/dontTouch.scala b/core/src/main/scala/chisel3/dontTouch.scala index 0487a567..e679f1a4 100644 --- a/core/src/main/scala/chisel3/dontTouch.scala +++ b/core/src/main/scala/chisel3/dontTouch.scala @@ -5,7 +5,8 @@ package chisel3 import chisel3.experimental.{ChiselAnnotation, annotate, requireIsHardware} import firrtl.transforms.DontTouchAnnotation -/** Marks that a signal should not be removed by Chisel and Firrtl optimization passes +/** Marks that a signal is an optimization barrier to Chisel and the FIRRTL compiler. This has the effect of + * guaranteeing that a signal will not be removed. * * @example {{{ * class MyModule extends Module { @@ -21,9 +22,11 @@ import firrtl.transforms.DontTouchAnnotation * @note Calling this on [[Data]] creates an annotation that Chisel emits to a separate annotations * file. This file must be passed to FIRRTL independently of the `.fir` file. The execute methods * in [[chisel3.Driver]] will pass the annotations to FIRRTL automatically. + * @note Because this is an optimization barrier, constants will not be propagated through a signal marked as + * dontTouch. */ object dontTouch { - /** Marks a signal to be preserved in Chisel and Firrtl + /** Mark a signal as an optimization barrier to Chisel and FIRRTL. * * @note Requires the argument to be bound to hardware * @param data The signal to be marked -- cgit v1.2.3 From bb520b8573328fda5f7b3c3892e6995fbe1b4239 Mon Sep 17 00:00:00 2001 From: Verneri Hirvonen Date: Thu, 8 Jul 2021 18:59:12 +0300 Subject: Add `isOneOf` method to `ChiselEnum` (#1966) * Add @ekiwi's code as a starting point * Add test for ChiselEnum isOneOf method * Make isOneOfTester naming consistent with other testers * Add scaladoc comments for isOneOf * Add isOneOf tests that use the method that takes variable number of args * Add guide level documentation example for isOneOf--- core/src/main/scala/chisel3/StrongEnum.scala | 18 +++++++++++++ docs/src/explanations/chisel-enum.md | 19 ++++++++++++++ src/test/scala/chiselTests/StrongEnum.scala | 39 ++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/core/src/main/scala/chisel3/StrongEnum.scala b/core/src/main/scala/chisel3/StrongEnum.scala index 1d0e04d3..b3d7cf7d 100644 --- a/core/src/main/scala/chisel3/StrongEnum.scala +++ b/core/src/main/scala/chisel3/StrongEnum.scala @@ -135,6 +135,24 @@ abstract class EnumType(private val factory: EnumFactory, selfAnnotating: Boolea } } + /** Test if this enumeration is equal to any of the values in a given sequence + * + * @param s a [[scala.collection.Seq$ Seq]] of enumeration values to look for + * @return a hardware [[Bool]] that indicates if this value matches any of the given values + */ + final def isOneOf(s: Seq[EnumType])(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = { + VecInit(s.map(this === _)).asUInt().orR() + } + + /** Test if this enumeration is equal to any of the values given as arguments + * + * @param u1 the first value to look for + * @param u2 zero or more additional values to look for + * @return a hardware [[Bool]] that indicates if this value matches any of the given values + */ + final def isOneOf(u1: EnumType, u2: EnumType*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool + = isOneOf(u1 +: u2.toSeq) + def next(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): this.type = { if (litOption.isDefined) { val index = factory.all.indexOf(this) diff --git a/docs/src/explanations/chisel-enum.md b/docs/src/explanations/chisel-enum.md index 96fc9e8a..a390aea4 100644 --- a/docs/src/explanations/chisel-enum.md +++ b/docs/src/explanations/chisel-enum.md @@ -182,6 +182,25 @@ def expectedSel(sel: AluMux1Sel.Type): Boolean = sel match { } ``` +The enum value type also defines some convenience methods for working with `ChiselEnum` values. For example, continuing with the RISC-V opcode +example, one could easily create hardware signal that is only asserted on LOAD/STORE operations (when the enum value is equal to `Opcode.load` +or `Opcode.store`) using the `.isOneOf` method: + +```scala mdoc +class LoadStoreExample extends Module { + val io = IO(new Bundle { + val opcode = Input(Opcode()) + val load_or_store = Output(Bool()) + }) + io.load_or_store := io.opcode.isOneOf(Opcode.load, Opcode.store) +} +``` + +```scala mdoc:invisible +// Always need to run Chisel to see if there are elaboration errors +ChiselStage.emitVerilog(new LoadStoreExample) +``` + Some additional useful methods defined on the `ChiselEnum` object are: * `.all`: returns the enum values within the enumeration diff --git a/src/test/scala/chiselTests/StrongEnum.scala b/src/test/scala/chiselTests/StrongEnum.scala index e59a5398..d7dea571 100644 --- a/src/test/scala/chiselTests/StrongEnum.scala +++ b/src/test/scala/chiselTests/StrongEnum.scala @@ -304,6 +304,41 @@ class StrongEnumFSMTester extends BasicTester { } } +class IsOneOfTester extends BasicTester { + import EnumExample._ + + // is one of itself + assert(e0.isOneOf(e0)) + + // is one of Seq of itself + assert(e0.isOneOf(Seq(e0))) + assert(e0.isOneOf(Seq(e0, e0, e0, e0))) + assert(e0.isOneOf(e0, e0, e0, e0)) + + // is one of Seq of multiple elements + val subset = Seq(e0, e1, e2) + assert(e0.isOneOf(subset)) + assert(e1.isOneOf(subset)) + assert(e2.isOneOf(subset)) + + // is not element not in subset + assert(!e100.isOneOf(subset)) + assert(!e101.isOneOf(subset)) + + // test multiple elements with variable number of arguments + assert(e0.isOneOf(e0, e1, e2)) + assert(e1.isOneOf(e0, e1, e2)) + assert(e2.isOneOf(e0, e1, e2)) + assert(!e100.isOneOf(e0, e1, e2)) + assert(!e101.isOneOf(e0, e1, e2)) + + // is not another value + assert(!e0.isOneOf(e1)) + assert(!e2.isOneOf(e101)) + + stop() +} + class StrongEnumSpec extends ChiselFlatSpec with Utils { import chisel3.internal.ChiselException @@ -474,6 +509,10 @@ class StrongEnumSpec extends ChiselFlatSpec with Utils { val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) log should not include ("warn") } + + it should "correctly check if the enumeration is one of the values in a given sequence" in { + assertTesterPasses(new IsOneOfTester) + } } class StrongEnumAnnotator extends Module { -- cgit v1.2.3 From 16c0b53e04f3a78ddaaa382936cd660523a57199 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 8 Jul 2021 15:30:28 -0700 Subject: Fix chisel3 <> for Bundles that contain compatibility Bundles (#2023) BiConnect in chisel3 delegates to FIRRTL <- semantics whenever it hits a Bundle defined in `import Chisel._`. Because chisel3 <> is commutative it needs to be mindful of flippedness when emitting a FIRRTL <- (which is *not* commutative).--- .../main/scala/chisel3/internal/BiConnect.scala | 25 +++++++++----- .../CompatibilityInteroperabilitySpec.scala | 38 ++++++++++++++++++++++ 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/chisel3/internal/BiConnect.scala b/core/src/main/scala/chisel3/internal/BiConnect.scala index 1ee149ee..fcea4fe2 100644 --- a/core/src/main/scala/chisel3/internal/BiConnect.scala +++ b/core/src/main/scala/chisel3/internal/BiConnect.scala @@ -113,14 +113,23 @@ private[chisel3] object BiConnect { } } } - // Handle Records defined in Chisel._ code (change to NotStrict) - case (left_r: Record, right_r: Record) => (left_r.compileOptions, right_r.compileOptions) match { - case (ExplicitCompileOptions.NotStrict, _) => - left_r.bulkConnect(right_r)(sourceInfo, ExplicitCompileOptions.NotStrict) - case (_, ExplicitCompileOptions.NotStrict) => - left_r.bulkConnect(right_r)(sourceInfo, ExplicitCompileOptions.NotStrict) - case _ => recordConnect(sourceInfo, connectCompileOptions, left_r, right_r, context_mod) - } + // Handle Records defined in Chisel._ code by emitting a FIRRTL partial connect + case pair @ (left_r: Record, right_r: Record) => + val notStrict = + Seq(left_r.compileOptions, right_r.compileOptions).contains(ExplicitCompileOptions.NotStrict) + if (notStrict) { + // chisel3 <> is commutative but FIRRTL <- is not + val flipped = { + // Everything is flipped when it's the port of a child + val childPort = left_r._parent.get != context_mod + val isFlipped = left_r.direction == ActualDirection.Bidirectional(ActualDirection.Flipped) + isFlipped ^ childPort + } + val (newLeft, newRight) = if (flipped) pair.swap else pair + newLeft.bulkConnect(newRight)(sourceInfo, ExplicitCompileOptions.NotStrict) + } else { + recordConnect(sourceInfo, connectCompileOptions, left_r, right_r, context_mod) + } // Handle Records connected to DontCare (change to NotStrict) case (left_r: Record, DontCare) => diff --git a/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala b/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala index cfcc4608..8e9f9e7e 100644 --- a/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala +++ b/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala @@ -289,5 +289,43 @@ class CompatibiltyInteroperabilitySpec extends ChiselFlatSpec { } } } + + "A chisel3 Bundle that instantiates a Chisel Bundle" should "bulk connect correctly" in { + compile { + object Compat { + import Chisel._ + class Foo extends Bundle { + val a = Input(UInt(8.W)) + val b = Output(UInt(8.W)) + } + } + import chisel3._ + import Compat._ + class Bar extends Bundle { + val foo1 = new Foo + val foo2 = Flipped(new Foo) + } + // Check every connection both ways to see that chisel3 <>'s commutativity holds + class Child extends RawModule { + val deq = IO(new Bar) + val enq = IO(Flipped(new Bar)) + enq <> deq + deq <> enq + } + new RawModule { + val deq = IO(new Bar) + val enq = IO(Flipped(new Bar)) + // Also important to check connections to child ports + val c1 = Module(new Child) + val c2 = Module(new Child) + c1.enq <> enq + enq <> c1.enq + c2.enq <> c1.deq + c1.deq <> c2.enq + deq <> c2.deq + c2.deq <> deq + } + } + } } -- cgit v1.2.3 From 4b7b771eeced366345779a75987ce552558a1c7e Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 8 Jul 2021 17:07:24 -0700 Subject: Make it legal for concrete resets to drive abstract reset (#2018) This has been legal in FIRRTL since v1.2.3 (when reset inference started using a unification-style algorithm) but was never exposed in the Chisel API. Also delete the overridden connects in AsyncReset and ResetType which just duplicate logic from MonoConnect.--- core/src/main/scala/chisel3/Bits.scala | 10 ---------- .../main/scala/chisel3/internal/MonoConnect.scala | 2 ++ src/test/scala/chiselTests/ResetSpec.scala | 20 ++++++++++++++++++++ 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/core/src/main/scala/chisel3/Bits.scala b/core/src/main/scala/chisel3/Bits.scala index aa5b1abb..670f6e7a 100644 --- a/core/src/main/scala/chisel3/Bits.scala +++ b/core/src/main/scala/chisel3/Bits.scala @@ -961,11 +961,6 @@ final class ResetType(private[chisel3] val width: Width = Width(1)) extends Elem private[chisel3] def typeEquivalent(that: Data): Boolean = this.getClass == that.getClass - override def connect(that: Data)(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions): Unit = that match { - case _: Reset | DontCare => super.connect(that)(sourceInfo, connectCompileOptions) - case _ => super.badConnect(that)(sourceInfo) - } - override def litOption = None /** Not really supported */ @@ -1008,11 +1003,6 @@ sealed class AsyncReset(private[chisel3] val width: Width = Width(1)) extends El private[chisel3] def typeEquivalent(that: Data): Boolean = this.getClass == that.getClass - override def connect(that: Data)(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions): Unit = that match { - case _: AsyncReset | DontCare => super.connect(that)(sourceInfo, connectCompileOptions) - case _ => super.badConnect(that)(sourceInfo) - } - override def litOption = None /** Not really supported */ diff --git a/core/src/main/scala/chisel3/internal/MonoConnect.scala b/core/src/main/scala/chisel3/internal/MonoConnect.scala index 2155894a..b979ebae 100644 --- a/core/src/main/scala/chisel3/internal/MonoConnect.scala +++ b/core/src/main/scala/chisel3/internal/MonoConnect.scala @@ -103,6 +103,8 @@ private[chisel3] object MonoConnect { elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) case (sink_e: ResetType, source_e: Reset) => elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: Reset, source_e: ResetType) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) case (sink_e: EnumType, source_e: UnsafeEnum) => elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) case (sink_e: EnumType, source_e: EnumType) if sink_e.typeEquivalent(source_e) => diff --git a/src/test/scala/chiselTests/ResetSpec.scala b/src/test/scala/chiselTests/ResetSpec.scala index 0e535964..7a5d444d 100644 --- a/src/test/scala/chiselTests/ResetSpec.scala +++ b/src/test/scala/chiselTests/ResetSpec.scala @@ -44,6 +44,26 @@ class ResetSpec extends ChiselFlatSpec with Utils { ChiselStage.elaborate(new AbstractResetDontCareModule) } + it should "be able to drive Bool" in { + ChiselStage.emitVerilog(new RawModule { + val in = IO(Input(Bool())) + val out = IO(Output(Bool())) + val w = Wire(Reset()) + w := in + out := w + }) + } + + it should "be able to drive AsyncReset" in { + ChiselStage.emitVerilog(new RawModule { + val in = IO(Input(AsyncReset())) + val out = IO(Output(AsyncReset())) + val w = Wire(Reset()) + w := in + out := w + }) + } + it should "allow writing modules that are reset agnostic" in { val sync = compile(new Module { val io = IO(new Bundle { -- cgit v1.2.3 From 5183ef888274c1d9cc2e22aef95c0e90d86e5122 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Fri, 9 Jul 2021 14:29:45 -0700 Subject: Fix chisel3 <> for Bundles that contain compatibility Bundles (Take 2) (#2031) PR #2023 fixed a composition issue for chisel3 biconnects delegating to FIRRTL partial connect when compatibility mode Bundles are elements of chisel3 Bundles. It missed an important case though that caused previously working code to break. The bug is fixed by doing the automatic flipping for compatibility mode Bundles that have "Input" as a direction in addition to those that are "Flipped".--- core/src/main/scala/chisel3/internal/BiConnect.scala | 3 ++- .../scala/chiselTests/CompatibilityInteroperabilitySpec.scala | 11 ++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/chisel3/internal/BiConnect.scala b/core/src/main/scala/chisel3/internal/BiConnect.scala index fcea4fe2..aa7d7ac3 100644 --- a/core/src/main/scala/chisel3/internal/BiConnect.scala +++ b/core/src/main/scala/chisel3/internal/BiConnect.scala @@ -120,9 +120,10 @@ private[chisel3] object BiConnect { if (notStrict) { // chisel3 <> is commutative but FIRRTL <- is not val flipped = { + import ActualDirection._ // Everything is flipped when it's the port of a child val childPort = left_r._parent.get != context_mod - val isFlipped = left_r.direction == ActualDirection.Bidirectional(ActualDirection.Flipped) + val isFlipped = Seq(Bidirectional(Flipped), Input).contains(left_r.direction) isFlipped ^ childPort } val (newLeft, newRight) = if (flipped) pair.swap else pair diff --git a/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala b/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala index 8e9f9e7e..28b8bc80 100644 --- a/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala +++ b/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala @@ -294,16 +294,21 @@ class CompatibiltyInteroperabilitySpec extends ChiselFlatSpec { compile { object Compat { import Chisel._ - class Foo extends Bundle { + class BiDir extends Bundle { val a = Input(UInt(8.W)) val b = Output(UInt(8.W)) } + class Struct extends Bundle { + val a = UInt(8.W) + } } import chisel3._ import Compat._ class Bar extends Bundle { - val foo1 = new Foo - val foo2 = Flipped(new Foo) + val bidir1 = new BiDir + val bidir2 = Flipped(new BiDir) + val struct1 = Output(new Struct) + val struct2 = Input(new Struct) } // Check every connection both ways to see that chisel3 <>'s commutativity holds class Child extends RawModule { -- cgit v1.2.3 From abbb58efc5609e2aa57dba21e0151335f043869d Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Mon, 12 Jul 2021 10:51:34 +0200 Subject: Update sbt to 1.5.5 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 9edb75b7..10fd9eee 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.4 +sbt.version=1.5.5 -- cgit v1.2.3 From ea7630bf12f8da0a4150386b95539a177940bd68 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Sun, 4 Jul 2021 12:51:25 +0800 Subject: refactor github action --- .github/workflows/test.yml | 63 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6c7f14cc..b365776f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,17 +12,62 @@ on: jobs: ci: name: ci - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 strategy: matrix: - scala: [2.13.6, 2.12.13] - container: - image: ucbbar/chisel3-tools - options: --user github --entrypoint /bin/bash - env: - CONTAINER_HOME: /home/github + scala: ["2.13.6", "2.12.13"] + verilator: ["4.204"] + z3: ["4.8.10"] steps: + - name: Install Z3 Build Dependencies + run: sudo apt-get install -y git make autoconf g++ flex bison libfl2 libfl-dev + + - name: Cache Z3 ${{ matrix.z3 }} + uses: actions/cache@v2 + id: cache-z3 + with: + path: z3-z3-${{ matrix.z3 }} + key: ${{ runner.os }}-z3-${{ matrix.z3 }} + - name: Compile Z3 + if: steps.cache-z3.outputs.cache-hit != 'true' + run: | + wget https://github.com/Z3Prover/z3/archive/refs/tags/z3-${{ matrix.z3 }}.tar.gz + tar xvf z3-${{ matrix.z3 }}.tar.gz + cd z3-z3-${{ matrix.z3 }} + mkdir -p build + cd build + cmake .. \ + -DCMAKE_BUILD_TYPE=Release \ + -DZ3_LINK_TIME_OPTIMIZATION=1 + make + - name: Install Z3 ${{ matrix.z3 }} + run: | + cd z3-z3-${{ matrix.z3 }}/build + sudo make install + z3 --version + + - name: Cache Verilator ${{ matrix.verilator }} + uses: actions/cache@v2 + id: cache-verilator + with: + path: verilator-${{ matrix.verilator }} + key: ${{ runner.os }}-verilator-${{ matrix.verilator }} + - name: Compile Verilator ${{ matrix.verilator }} + if: steps.cache-verilator.outputs.cache-hit != 'true' + run: | + wget https://github.com/verilator/verilator/archive/refs/tags/v${{ matrix.verilator }}.tar.gz + tar xvf v${{ matrix.verilator }}.tar.gz + cd verilator-${{ matrix.verilator }} + autoconf + ./configure + make + - name: Install Verilator ${{ matrix.verilator }} + run: | + cd verilator-${{ matrix.verilator }} + sudo make install + verilator --version + - name: Checkout uses: actions/checkout@v2 - name: Setup Scala @@ -46,7 +91,7 @@ jobs: all_tests_passed: name: "all tests passed" needs: [ci] - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - run: echo Success! @@ -54,7 +99,7 @@ jobs: # separate from a Scala versions build matrix to avoid duplicate publishing publish: needs: [all_tests_passed] - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 if: github.event_name == 'push' steps: -- cgit v1.2.3 From 0d4bfbccd8b31230d16d3039429df7358970bf99 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Sun, 11 Jul 2021 01:59:41 +0800 Subject: fix for GitHub Action missing ivy FIRRTL. --- build.sbt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index cc4dee41..1a47d7c1 100644 --- a/build.sbt +++ b/build.sbt @@ -219,7 +219,11 @@ lazy val chisel = (project in file(".")). lazy val noPluginTests = (project in file ("no-plugin-tests")). dependsOn(chisel). settings(commonSettings: _*). - settings(chiselSettings: _*) + settings(chiselSettings: _*). + settings(Seq( + // Totally don't know why GitHub Action won't introduce FIRRTL to dependency. + libraryDependencies += defaultVersions("firrtl"), + )) lazy val docs = project // new documentation project .in(file("docs-target")) // important: it must not be docs/ -- cgit v1.2.3 From fabbd209a964d1573bf9371adbaa8373cba8e55e Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Tue, 13 Jul 2021 10:51:48 +0800 Subject: fix ci, add more matrix. --- .github/workflows/test.yml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b365776f..6d68ad1a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,23 +12,29 @@ on: jobs: ci: name: ci - runs-on: ubuntu-20.04 strategy: matrix: + system: ["ubuntu-20.04"] + jvm: ["adopt@1.8"] scala: ["2.13.6", "2.12.13"] verilator: ["4.204"] z3: ["4.8.10"] + runs-on: ${{ matrix.system }} steps: - - name: Install Z3 Build Dependencies - run: sudo apt-get install -y git make autoconf g++ flex bison libfl2 libfl-dev + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Z3 Build Dependencies(Ubuntu) + if: matrix.system == 'ubuntu-20.04' + run: sudo apt-get install -y libfl2 libfl-dev - name: Cache Z3 ${{ matrix.z3 }} uses: actions/cache@v2 id: cache-z3 with: path: z3-z3-${{ matrix.z3 }} - key: ${{ runner.os }}-z3-${{ matrix.z3 }} + key: ${{ matrix.system }}-z3-${{ matrix.z3 }} - name: Compile Z3 if: steps.cache-z3.outputs.cache-hit != 'true' run: | @@ -52,7 +58,7 @@ jobs: id: cache-verilator with: path: verilator-${{ matrix.verilator }} - key: ${{ runner.os }}-verilator-${{ matrix.verilator }} + key: ${{ matrix.system }}-verilator-${{ matrix.verilator }} - name: Compile Verilator ${{ matrix.verilator }} if: steps.cache-verilator.outputs.cache-hit != 'true' run: | @@ -68,12 +74,10 @@ jobs: sudo make install verilator --version - - name: Checkout - uses: actions/checkout@v2 - name: Setup Scala uses: olafurpg/setup-scala@v10 with: - java-version: adopt@1.8 + java-version: ${{ matrix.jvm }} - name: Cache Scala uses: coursier/cache-action@v5 - name: Documentation (Scala 2.12 only) -- cgit v1.2.3 From 695864f5716626a15a7798dae048d8301940a2db Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Thu, 15 Jul 2021 02:32:06 +0800 Subject: Espresso Decoder (#1964) Co-authored-by: Haoran Yuan Co-authored-by: Boyang Han --- .github/workflows/test.yml | 23 +++++++ .../experimental/decode/EspressoMinimizer.scala | 75 ++++++++++++++++++++++ .../util/experimental/decode/TruthTable.scala | 2 +- .../chisel3/util/experimental/decode/decoder.scala | 49 ++++++++++++++ .../util/experimental/minimizer/EspressoSpec.scala | 9 +++ 5 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala create mode 100644 src/test/scala/chiselTests/util/experimental/minimizer/EspressoSpec.scala diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6d68ad1a..b2620d2f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,6 +19,7 @@ jobs: scala: ["2.13.6", "2.12.13"] verilator: ["4.204"] z3: ["4.8.10"] + espresso: ["2.4"] runs-on: ${{ matrix.system }} steps: @@ -74,6 +75,28 @@ jobs: sudo make install verilator --version + - name: Cache Espresso ${{ matrix.espresso }} + uses: actions/cache@v2 + id: cache-espresso + with: + path: espresso-${{ matrix.espresso }} + key: ${{ matrix.system }}-espresso-${{ matrix.espresso }} + - name: Compile Espresso ${{ matrix.espresso }} + if: steps.cache-espresso.outputs.cache-hit != 'true' + run: | + wget https://github.com/chipsalliance/espresso/archive/refs/tags/v${{ matrix.espresso }}.tar.gz + tar xvf v${{ matrix.espresso }}.tar.gz + cd espresso-${{ matrix.espresso }} + mkdir -p build + cd build + cmake .. + make + - name: Install Espresso ${{ matrix.espresso }} + run: | + cd espresso-${{ matrix.espresso }}/build + sudo make install + + - name: Setup Scala uses: olafurpg/setup-scala@v10 with: diff --git a/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala b/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala new file mode 100644 index 00000000..0351a46a --- /dev/null +++ b/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.util.experimental.decode + +import chisel3.util.BitPat +import logger.LazyLogging + +case object EspressoNotFoundException extends Exception + +object EspressoMinimizer extends Minimizer with LazyLogging { + def minimize(table: TruthTable): TruthTable = + TruthTable.merge(TruthTable.split(table).map{case (table, indexes) => (espresso(table), indexes)}) + + def espresso(table: TruthTable): TruthTable = { + def writeTable(table: TruthTable): String = { + def invert(string: String) = string + .replace('0', 't') + .replace('1', '0') + .replace('t', '1') + val defaultType: Char = { + val t = table.default.toString.drop(7).dropRight(1).toCharArray.distinct + require(t.length == 1, "Internal Error: espresso only accept unified default type.") + t.head + } + val tableType: String = defaultType match { + case '?' => "fr" + case _ => "fd" + } + val rawTable = table + .toString + .split("\n") + .filter(_.contains("->")) + .mkString("\n") + .replace("->", " ") + .replace('?', '-') + // invert all output, since espresso cannot handle default is on. + val invertRawTable = rawTable + .split("\n") + .map(_.split(" ")) + .map(row => s"${row(0)} ${invert(row(1))}") + .mkString("\n") + s""".i ${table.inputWidth} + |.o ${table.outputWidth} + |.type ${tableType} + |""".stripMargin ++ (if (defaultType == '1') invertRawTable else rawTable) + } + + def readTable(espressoTable: String): Map[BitPat, BitPat] = { + def bitPat(espresso: String): BitPat = BitPat("b" + espresso.replace('-', '?')) + + espressoTable + .split("\n") + .filterNot(_.startsWith(".")) + .map(_.split(' ')) + .map(row => bitPat(row(0)) -> bitPat(row(1))) + .toMap + } + + // Since Espresso don't implements pipe, we use a temp file to do so. + val input = writeTable(table) + logger.trace(s"""espresso input table: + |$input + |""".stripMargin) + val f = os.temp(input) + val o = try { + os.proc("espresso", f).call().out.chunks.mkString + } catch { + case e: java.io.IOException if e.getMessage.contains("error=2, No such file or directory") => throw EspressoNotFoundException + } + logger.trace(s"""espresso output table: + |$o + |""".stripMargin) + TruthTable(readTable(o), table.default) + } +} diff --git a/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala b/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala index ca0ff8b4..f4f200ce 100644 --- a/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala +++ b/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala @@ -99,7 +99,7 @@ object TruthTable { tables: Seq[(TruthTable, Seq[Int])] ): TruthTable = { def reIndex(bitPat: BitPat, table: TruthTable, indexes: Seq[Int]): Seq[(Char, Int)] = - bpStr(table.table.getOrElse(bitPat, BitPat.dontCare(indexes.size))).zip(indexes) + bpStr(table.table.map(a => a._1.toString -> a._2).getOrElse(bitPat.toString, BitPat.dontCare(indexes.size))).zip(indexes) def bitPat(indexedChar: Seq[(Char, Int)]) = BitPat(s"b${indexedChar .sortBy(_._2) .map(_._1) diff --git a/src/main/scala/chisel3/util/experimental/decode/decoder.scala b/src/main/scala/chisel3/util/experimental/decode/decoder.scala index 8168824f..42e374d1 100644 --- a/src/main/scala/chisel3/util/experimental/decode/decoder.scala +++ b/src/main/scala/chisel3/util/experimental/decode/decoder.scala @@ -10,6 +10,13 @@ import firrtl.annotations.Annotation import logger.LazyLogging object decoder extends LazyLogging { + /** Use a specific [[Minimizer]] to generated decoded signals. + * + * @param minimizer specific [[Minimizer]], can be [[QMCMinimizer]] or [[EspressoMinimizer]]. + * @param input input signal that contains decode table input + * @param truthTable [[TruthTable]] to decode user input. + * @return decode table output. + */ def apply(minimizer: Minimizer, input: UInt, truthTable: TruthTable): UInt = { val minimizedTable = getAnnotations().collect { case DecodeTableAnnotation(_, in, out) => TruthTable(in) -> TruthTable(out) @@ -31,4 +38,46 @@ object decoder extends LazyLogging { plaOutput } } + + /** Use [[EspressoMinimizer]] to generated decoded signals. + * + * @param input input signal that contains decode table input + * @param truthTable [[TruthTable]] to decode user input. + * @return decode table output. + */ + def espresso(input: UInt, truthTable: TruthTable): UInt = apply(EspressoMinimizer, input, truthTable) + + /** Use [[QMCMinimizer]] to generated decoded signals. + * + * @param input input signal that contains decode table input + * @param truthTable [[TruthTable]] to decode user input. + * @return decode table output. + */ + def qmc(input: UInt, truthTable: TruthTable): UInt = apply(QMCMinimizer, input, truthTable) + + /** try to use [[EspressoMinimizer]] to decode `input` by `truthTable` + * if `espresso` not exist in your PATH environment it will fall back to [[QMCMinimizer]], and print a warning. + * + * @param input input signal that contains decode table input + * @param truthTable [[TruthTable]] to decode user input. + * @return decode table output. + */ + def apply(input: UInt, truthTable: TruthTable): UInt = { + def qmcFallBack(input: UInt, truthTable: TruthTable) = { + """fall back to QMC. + |Quine-McCluskey is a NP complete algorithm, may run forever or run out of memory in large decode tables. + |To get rid of this warning, please use `decoder.qmc` directly, or add espresso to your PATH. + |""".stripMargin + qmc(input, truthTable) + } + + try espresso(input, truthTable) catch { + case EspressoNotFoundException => + logger.error(s"espresso is not found in your PATH:\n${sys.env("PATH").split(":").mkString("\n")}".stripMargin) + qmcFallBack(input, truthTable) + case e: java.io.IOException => + logger.error(s"espresso failed to run with ${e.getMessage}") + qmcFallBack(input, truthTable) + } + } } diff --git a/src/test/scala/chiselTests/util/experimental/minimizer/EspressoSpec.scala b/src/test/scala/chiselTests/util/experimental/minimizer/EspressoSpec.scala new file mode 100644 index 00000000..f3270cae --- /dev/null +++ b/src/test/scala/chiselTests/util/experimental/minimizer/EspressoSpec.scala @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.util.experimental.minimizer +import chisel3.util.experimental.decode.EspressoMinimizer +import chisel3.util.experimental.decode.Minimizer + +class EspressoSpec extends MinimizerSpec { + override def minimizer: Minimizer = EspressoMinimizer +} -- cgit v1.2.3 From e6c902eaf6f63413ff8f3b12ad8993cf34447413 Mon Sep 17 00:00:00 2001 From: Leway Colin Date: Thu, 15 Jul 2021 07:22:20 +0800 Subject: Fix Cat rename signal (#2011) Co-authored-by: Jack Koenig --- core/src/main/scala/chisel3/SeqUtils.scala | 10 ++++---- .../test/scala/chiselTests/BetterNamingTests.scala | 1 + .../src/test/scala/chiselTests/CatSpec.scala | 1 + .../src/test/scala/chiselTests/CustomBundle.scala | 1 + .../scala/chiselTests/NamingAnnotationTest.scala | 1 + src/test/scala/chiselTests/CustomBundle.scala | 24 +++++++++++++++++ src/test/scala/chiselTests/RecordSpec.scala | 19 +------------- src/test/scala/chiselTests/util/CatSpec.scala | 30 ++++++++++++++++++++++ 8 files changed, 64 insertions(+), 23 deletions(-) create mode 120000 no-plugin-tests/src/test/scala/chiselTests/BetterNamingTests.scala create mode 120000 no-plugin-tests/src/test/scala/chiselTests/CatSpec.scala create mode 120000 no-plugin-tests/src/test/scala/chiselTests/CustomBundle.scala create mode 120000 no-plugin-tests/src/test/scala/chiselTests/NamingAnnotationTest.scala create mode 100644 src/test/scala/chiselTests/CustomBundle.scala diff --git a/core/src/main/scala/chisel3/SeqUtils.scala b/core/src/main/scala/chisel3/SeqUtils.scala index f6642bcb..da6fc802 100644 --- a/core/src/main/scala/chisel3/SeqUtils.scala +++ b/core/src/main/scala/chisel3/SeqUtils.scala @@ -7,7 +7,7 @@ import chisel3.internal.{prefix, throwException} import scala.language.experimental.macros import chisel3.internal.sourceinfo._ - +import chisel3.internal.plugin.autoNameRecursively private[chisel3] object SeqUtils { /** Concatenates the data elements of the input sequence, in sequence order, together. @@ -26,12 +26,12 @@ private[chisel3] object SeqUtils { } else if (in.tail.isEmpty) { in.head.asUInt } else { - val lo = prefix("lo") { + val lo = autoNameRecursively("lo")(prefix("lo") { asUInt(in.slice(0, in.length/2)) - }.autoSeed("lo") - val hi = prefix("hi") { + }) + val hi = autoNameRecursively("hi")(prefix("hi") { asUInt(in.slice(in.length/2, in.length)) - }.autoSeed("hi") + }) hi ## lo } } diff --git a/no-plugin-tests/src/test/scala/chiselTests/BetterNamingTests.scala b/no-plugin-tests/src/test/scala/chiselTests/BetterNamingTests.scala new file mode 120000 index 00000000..0d362f3c --- /dev/null +++ b/no-plugin-tests/src/test/scala/chiselTests/BetterNamingTests.scala @@ -0,0 +1 @@ +../../../../../src/test/scala/chiselTests/BetterNamingTests.scala \ No newline at end of file diff --git a/no-plugin-tests/src/test/scala/chiselTests/CatSpec.scala b/no-plugin-tests/src/test/scala/chiselTests/CatSpec.scala new file mode 120000 index 00000000..2fb2d6a7 --- /dev/null +++ b/no-plugin-tests/src/test/scala/chiselTests/CatSpec.scala @@ -0,0 +1 @@ +../../../../../src/test/scala/chiselTests/util/CatSpec.scala \ No newline at end of file diff --git a/no-plugin-tests/src/test/scala/chiselTests/CustomBundle.scala b/no-plugin-tests/src/test/scala/chiselTests/CustomBundle.scala new file mode 120000 index 00000000..006a2dbd --- /dev/null +++ b/no-plugin-tests/src/test/scala/chiselTests/CustomBundle.scala @@ -0,0 +1 @@ +../../../../../src/test/scala/chiselTests/CustomBundle.scala \ No newline at end of file diff --git a/no-plugin-tests/src/test/scala/chiselTests/NamingAnnotationTest.scala b/no-plugin-tests/src/test/scala/chiselTests/NamingAnnotationTest.scala new file mode 120000 index 00000000..6933a290 --- /dev/null +++ b/no-plugin-tests/src/test/scala/chiselTests/NamingAnnotationTest.scala @@ -0,0 +1 @@ +../../../../../src/test/scala/chiselTests/NamingAnnotationTest.scala \ No newline at end of file diff --git a/src/test/scala/chiselTests/CustomBundle.scala b/src/test/scala/chiselTests/CustomBundle.scala new file mode 100644 index 00000000..b04dcc59 --- /dev/null +++ b/src/test/scala/chiselTests/CustomBundle.scala @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests + +import chisel3._ +import chisel3.experimental.{DataMirror, requireIsChiselType} +import scala.collection.immutable.ListMap + +// An example of how Record might be extended +// In this case, CustomBundle is a Record constructed from a Tuple of (String, Data) +// it is a possible implementation of a programmatic "Bundle" +// (and can by connected to MyBundle below) +final class CustomBundle(elts: (String, Data)*) extends Record { + val elements = ListMap(elts map { case (field, elt) => + requireIsChiselType(elt) + field -> elt + }: _*) + def apply(elt: String): Data = elements(elt) + override def cloneType: this.type = { + val cloned = elts.map { case (n, d) => n -> DataMirror.internal.chiselTypeClone(d) } + (new CustomBundle(cloned: _*)).asInstanceOf[this.type] + } +} + diff --git a/src/test/scala/chiselTests/RecordSpec.scala b/src/test/scala/chiselTests/RecordSpec.scala index c34c2bf4..c21d455c 100644 --- a/src/test/scala/chiselTests/RecordSpec.scala +++ b/src/test/scala/chiselTests/RecordSpec.scala @@ -6,24 +6,7 @@ import chisel3._ import chisel3.stage.ChiselStage import chisel3.testers.BasicTester import chisel3.util.{Counter, Queue} -import chisel3.experimental.{DataMirror, requireIsChiselType} -import scala.collection.immutable.ListMap - -// An example of how Record might be extended -// In this case, CustomBundle is a Record constructed from a Tuple of (String, Data) -// it is a possible implementation of a programmatic "Bundle" -// (and can by connected to MyBundle below) -final class CustomBundle(elts: (String, Data)*) extends Record { - val elements = ListMap(elts map { case (field, elt) => - requireIsChiselType(elt) - field -> elt - }: _*) - def apply(elt: String): Data = elements(elt) - override def cloneType: this.type = { - val cloned = elts.map { case (n, d) => n -> DataMirror.internal.chiselTypeClone(d) } - (new CustomBundle(cloned: _*)).asInstanceOf[this.type] - } -} +import chisel3.experimental.DataMirror trait RecordSpecUtils { class MyBundle extends Bundle { diff --git a/src/test/scala/chiselTests/util/CatSpec.scala b/src/test/scala/chiselTests/util/CatSpec.scala index 5565ca51..79d2c027 100644 --- a/src/test/scala/chiselTests/util/CatSpec.scala +++ b/src/test/scala/chiselTests/util/CatSpec.scala @@ -5,6 +5,7 @@ package chiselTests.util import chisel3._ import chisel3.stage.ChiselStage import chisel3.util.Cat +import chisel3.experimental.noPrefix import chiselTests.ChiselFlatSpec @@ -31,4 +32,33 @@ class CatSpec extends ChiselFlatSpec { } + it should "not override the names of its arguments" in { + class MyModule extends RawModule { + val a, b, c, d = IO(Input(UInt(8.W))) + val out = IO(Output(UInt())) + + out := Cat(a, b, c, d) + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + for (name <- Seq("a", "b", "c", "d")) { + chirrtl should include (s"input $name : UInt<8>") + } + } + + it should "have prefixed naming" in { + class MyModule extends RawModule { + val in = IO(Input(Vec(8, UInt(8.W)))) + val out = IO(Output(UInt())) + + // noPrefix to avoid `out` as prefix + out := noPrefix(Cat(in)) + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include ("node lo_lo = cat(in[6], in[7])") + chirrtl should include ("node lo_hi = cat(in[4], in[5])") + chirrtl should include ("node hi_lo = cat(in[2], in[3])") + chirrtl should include ("node hi_hi = cat(in[0], in[1])") + } + + } -- cgit v1.2.3 From 3f007becb8093cbeda66ec0db9f2876553bc0257 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 22 Jul 2021 12:50:50 -0700 Subject: MovingAverage3 => MovingSum3 (#2050) The example in the README is a sum, not an average.--- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1f302c53..1b36d91d 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,8 @@ Consider an FIR filter that implements a convolution operation, as depicted in t While Chisel provides similar base primitives as synthesizable Verilog, and *could* be used as such: ```scala -// 3-point moving average implemented in the style of a FIR filter -class MovingAverage3(bitWidth: Int) extends Module { +// 3-point moving sum implemented in the style of a FIR filter +class MovingSum3(bitWidth: Int) extends Module { val io = IO(new Bundle { val in = Input(UInt(bitWidth.W)) val out = Output(UInt(bitWidth.W)) @@ -52,7 +52,7 @@ class MovingAverage3(bitWidth: Int) extends Module { } ``` -the power of Chisel comes from the ability to create generators, such as n FIR filter that is defined by the list of coefficients: +the power of Chisel comes from the ability to create generators, such as an FIR filter that is defined by the list of coefficients: ```scala // Generalized FIR filter parameterized by the convolution coefficients class FirFilter(bitWidth: Int, coeffs: Seq[UInt]) extends Module { @@ -77,7 +77,7 @@ class FirFilter(bitWidth: Int, coeffs: Seq[UInt]) extends Module { and use and re-use them across designs: ```scala -val movingAverage3Filter = Module(new FirFilter(8, Seq(1.U, 1.U, 1.U))) // same 3-point moving average filter as before +val movingSum3Filter = Module(new FirFilter(8, Seq(1.U, 1.U, 1.U))) // same 3-point moving sum filter as before val delayFilter = Module(new FirFilter(8, Seq(0.U, 1.U))) // 1-cycle delay as a FIR filter val triangleFilter = Module(new FirFilter(8, Seq(1.U, 2.U, 3.U, 2.U, 1.U))) // 5-point FIR filter with a triangle impulse response ``` -- cgit v1.2.3 From 3c7e47633b3caed5d0e85984174405ba1591fa5b Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Fri, 23 Jul 2021 04:29:16 +0200 Subject: Update sbt-mdoc to 2.2.22 (#2047) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index ac844474..1eca3137 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -16,7 +16,7 @@ addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.15") addSbtPlugin("com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "3.0.0") -addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.21" ) +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.22" ) addSbtPlugin("com.eed3si9n" % "sbt-sriracha" % "0.1.0") -- cgit v1.2.3 From e3d245e0bfaf164996fac655b2da0818e1ad37d0 Mon Sep 17 00:00:00 2001 From: Megan Wachs Date: Tue, 27 Jul 2021 13:54:44 -0700 Subject: Clarify test dependencies in README, add noplugin (#2053) There are now more tool dependecies than just `verilator` to get the full test suite to pass. Also add instructions for running the tests without the compiler plugin,--- README.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1b36d91d..d5ddb0fb 100644 --- a/README.md +++ b/README.md @@ -172,15 +172,27 @@ cd chisel3 sbt compile ``` -In order to run the following unit tests, make sure you have Verilator installed and on your `PATH` -(you can check this by running `which verilator`). +In order to run the following unit tests, you will need several tools on your `PATH`, namely +[verilator](https://www.veripool.org/verilator/), +[yosys](http://www.clifford.at/yosys/), +[espresso](https://github.com/chipsalliance/espresso), +and [z3](https://github.com/Z3Prover/z3). +Check that each is installed on your `PATH` by running `which verilator` and so on. -If the compilation succeeded, you can then run the included unit tests by invoking: +If the compilation succeeded and the dependencies noted above are installed, you can then run the included unit tests by invoking: ``` sbt test ``` + + +If you would like to run the tests without the compiler plugin (less common), you can do so by first launching `sbt`, +then running `noPluginTests / test`: +``` +sbt +> noPluginTests / test +``` ### Running Projects Against Local Chisel To use the development version of Chisel (`master` branch), you will need to build from source and `publishLocal`. -- cgit v1.2.3 From 0666933b3e902192ef57723a92b57d41d50b3f8e Mon Sep 17 00:00:00 2001 From: Jared Barocsi Date: Wed, 28 Jul 2021 14:53:04 -0700 Subject: Bundles can no longer be instantiated with bound hardware (#2046) Co-authored-by: Jack Koenig --- core/src/main/scala/chisel3/Aggregate.scala | 2 ++ src/test/scala/chiselTests/BundleSpec.scala | 34 ++++++++++++++++++----------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/core/src/main/scala/chisel3/Aggregate.scala b/core/src/main/scala/chisel3/Aggregate.scala index 0031e53b..45ecec36 100644 --- a/core/src/main/scala/chisel3/Aggregate.scala +++ b/core/src/main/scala/chisel3/Aggregate.scala @@ -963,6 +963,8 @@ abstract class Bundle(implicit compileOptions: CompileOptions) extends Record { for (m <- getPublicFields(classOf[Bundle])) { getBundleField(m) match { case Some(d: Data) => + requireIsChiselType(d) + if (nameMap contains m.getName) { require(nameMap(m.getName) eq d) } else { diff --git a/src/test/scala/chiselTests/BundleSpec.scala b/src/test/scala/chiselTests/BundleSpec.scala index 5d3f23ec..1d392f5c 100644 --- a/src/test/scala/chiselTests/BundleSpec.scala +++ b/src/test/scala/chiselTests/BundleSpec.scala @@ -129,6 +129,27 @@ class BundleSpec extends ChiselFlatSpec with BundleSpecUtils with Utils { }).getMessage should include("aliased fields") } + "Bundles" should "not have bound hardware" in { + (the[ChiselException] thrownBy extractCause[ChiselException] { + ChiselStage.elaborate { new Module { + class MyBundle(val foo: UInt) extends Bundle + val in = IO(Input(new MyBundle(123.U))) // This should error: value passed in instead of type + val out = IO(Output(new MyBundle(UInt(8.W)))) + + out := in + } } + }).getMessage should include("must be a Chisel type, not hardware") + } + "Bundles" should "not recursively contain aggregates with bound hardware" in { + (the[ChiselException] thrownBy extractCause[ChiselException] { + ChiselStage.elaborate { new Module { + class MyBundle(val foo: UInt) extends Bundle + val out = IO(Output(Vec(2, UInt(8.W)))) + val in = IO(Input(new MyBundle(out(0)))) // This should error: Bound aggregate passed + out := in + } } + }).getMessage should include("must be a Chisel type, not hardware") + } "Unbound bundles sharing a field" should "not error" in { ChiselStage.elaborate { new RawModule { @@ -141,17 +162,4 @@ class BundleSpec extends ChiselFlatSpec with BundleSpecUtils with Utils { } } } - - "Bound Data" should "have priority in setting ref over unbound Data" in { - class MyModule extends RawModule { - val foo = IO(new Bundle { - val x = Output(UInt(8.W)) - }) - foo.x := 0.U // getRef on foo.x is None.get without fix - val bar = new Bundle { - val y = foo.x - } - } - ChiselStage.emitChirrtl(new MyModule) - } } -- cgit v1.2.3 From 1e7829eb674eed85a4cd537896d9fd9ee0bc5ff4 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Wed, 28 Jul 2021 19:35:27 -0700 Subject: Just install z3 from apt-get in CI (#2056) --- .github/workflows/test.yml | 27 ++------------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b2620d2f..f5a1f811 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,6 @@ jobs: jvm: ["adopt@1.8"] scala: ["2.13.6", "2.12.13"] verilator: ["4.204"] - z3: ["4.8.10"] espresso: ["2.4"] runs-on: ${{ matrix.system }} @@ -26,32 +25,10 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Install Z3 Build Dependencies(Ubuntu) + - name: Install Z3 if: matrix.system == 'ubuntu-20.04' - run: sudo apt-get install -y libfl2 libfl-dev - - - name: Cache Z3 ${{ matrix.z3 }} - uses: actions/cache@v2 - id: cache-z3 - with: - path: z3-z3-${{ matrix.z3 }} - key: ${{ matrix.system }}-z3-${{ matrix.z3 }} - - name: Compile Z3 - if: steps.cache-z3.outputs.cache-hit != 'true' - run: | - wget https://github.com/Z3Prover/z3/archive/refs/tags/z3-${{ matrix.z3 }}.tar.gz - tar xvf z3-${{ matrix.z3 }}.tar.gz - cd z3-z3-${{ matrix.z3 }} - mkdir -p build - cd build - cmake .. \ - -DCMAKE_BUILD_TYPE=Release \ - -DZ3_LINK_TIME_OPTIMIZATION=1 - make - - name: Install Z3 ${{ matrix.z3 }} run: | - cd z3-z3-${{ matrix.z3 }}/build - sudo make install + sudo apt-get install -y z3 z3 --version - name: Cache Verilator ${{ matrix.verilator }} -- cgit v1.2.3 From da923f317ff325a93cee6289552ccfa413c35f98 Mon Sep 17 00:00:00 2001 From: anniej-sifive Date: Tue, 3 Aug 2021 16:09:19 -0700 Subject: Added flush capability to Queue (#2030) Co-authored-by: Megan Wachs Co-authored-by: Deborah Soung --- src/main/scala/chisel3/util/Decoupled.scala | 29 ++- src/test/scala/chiselTests/QueueFlushSpec.scala | 259 ++++++++++++++++++++++++ src/test/scala/chiselTests/QueueSpec.scala | 8 +- 3 files changed, 284 insertions(+), 12 deletions(-) create mode 100644 src/test/scala/chiselTests/QueueFlushSpec.scala diff --git a/src/main/scala/chisel3/util/Decoupled.scala b/src/main/scala/chisel3/util/Decoupled.scala index 33682020..8909ffe3 100644 --- a/src/main/scala/chisel3/util/Decoupled.scala +++ b/src/main/scala/chisel3/util/Decoupled.scala @@ -163,8 +163,9 @@ object DeqIO { /** An I/O Bundle for Queues * @param gen The type of data to queue * @param entries The max number of entries in the queue. + * @param hasFlush A boolean for whether the generated Queue is flushable */ -class QueueIO[T <: Data](private val gen: T, val entries: Int) extends Bundle +class QueueIO[T <: Data](private val gen: T, val entries: Int, val hasFlush: Boolean = false) extends Bundle { // See github.com/freechipsproject/chisel3/issues/765 for why gen is a private val and proposed replacement APIs. /* These may look inverted, because the names (enq/deq) are from the perspective of the client, @@ -177,6 +178,9 @@ class QueueIO[T <: Data](private val gen: T, val entries: Int) extends Bundle val deq = Flipped(DeqIO(gen)) /** The current amount of data in the queue */ val count = Output(UInt(log2Ceil(entries + 1).W)) + /** When asserted, reset the enqueue and dequeue pointers, effectively flushing the queue (Optional IO for a flushable Queue)*/ + val flush = if (hasFlush) Some(Input(Bool())) else None + } /** A hardware module implementing a Queue @@ -187,7 +191,7 @@ class QueueIO[T <: Data](private val gen: T, val entries: Int) extends Bundle * @param flow True if the inputs can be consumed on the same cycle (the inputs "flow" through the queue immediately). * The ''valid'' signals are coupled. * @param useSyncReadMem True uses SyncReadMem instead of Mem as an internal memory element. - * + * @param hasFlush True if generated queue requires a flush feature * @example {{{ * val q = Module(new Queue(UInt(), 16)) * q.io.enq <> producer.io.out @@ -199,7 +203,8 @@ class Queue[T <: Data](val gen: T, val entries: Int, val pipe: Boolean = false, val flow: Boolean = false, - val useSyncReadMem: Boolean = false) + val useSyncReadMem: Boolean = false, + val hasFlush: Boolean = false) (implicit compileOptions: chisel3.CompileOptions) extends Module() { require(entries > -1, "Queue must have non-negative number of entries") @@ -215,19 +220,20 @@ class Queue[T <: Data](val gen: T, } } - val io = IO(new QueueIO(genType, entries)) - + val io = IO(new QueueIO(genType, entries, hasFlush)) val ram = if (useSyncReadMem) SyncReadMem(entries, genType, SyncReadMem.WriteFirst) else Mem(entries, genType) val enq_ptr = Counter(entries) val deq_ptr = Counter(entries) val maybe_full = RegInit(false.B) - val ptr_match = enq_ptr.value === deq_ptr.value val empty = ptr_match && !maybe_full val full = ptr_match && maybe_full val do_enq = WireDefault(io.enq.fire()) val do_deq = WireDefault(io.deq.fire()) + val flush = io.flush.getOrElse(false.B) + // when flush is high, empty the queue + // Semantically, any enqueues happen before the flush. when (do_enq) { ram(enq_ptr.value) := io.enq.bits enq_ptr.inc() @@ -238,6 +244,11 @@ class Queue[T <: Data](val gen: T, when (do_enq =/= do_deq) { maybe_full := do_enq } + when(flush) { + enq_ptr.reset() + deq_ptr.reset() + maybe_full := false.B + } io.deq.valid := !empty io.enq.ready := !full @@ -265,6 +276,7 @@ class Queue[T <: Data](val gen: T, } val ptr_diff = enq_ptr.value - deq_ptr.value + if (isPow2(entries)) { io.count := Mux(maybe_full && ptr_match, entries.U, 0.U) | ptr_diff } else { @@ -296,7 +308,8 @@ object Queue entries: Int = 2, pipe: Boolean = false, flow: Boolean = false, - useSyncReadMem: Boolean = false): DecoupledIO[T] = { + useSyncReadMem: Boolean = false, + hasFlush: Boolean = false): DecoupledIO[T] = { if (entries == 0) { val deq = Wire(new DecoupledIO(chiselTypeOf(enq.bits))) deq.valid := enq.valid @@ -304,7 +317,7 @@ object Queue enq.ready := deq.ready deq } else { - val q = Module(new Queue(chiselTypeOf(enq.bits), entries, pipe, flow, useSyncReadMem)) + val q = Module(new Queue(chiselTypeOf(enq.bits), entries, pipe, flow, useSyncReadMem, hasFlush)) q.io.enq.valid := enq.valid // not using <> so that override is allowed q.io.enq.bits := enq.bits enq.ready := q.io.enq.ready diff --git a/src/test/scala/chiselTests/QueueFlushSpec.scala b/src/test/scala/chiselTests/QueueFlushSpec.scala new file mode 100644 index 00000000..11a411a8 --- /dev/null +++ b/src/test/scala/chiselTests/QueueFlushSpec.scala @@ -0,0 +1,259 @@ +package chiselTests + +import org.scalacheck._ + +import chisel3._ +import chisel3.testers.{BasicTester, TesterDriver} +import chisel3.util._ +import chisel3.util.random.LFSR +import treadle.WriteVcdAnnotation + +/** Test elements can be enqueued and dequeued when flush is tied to false + * + * @param elements The sequence of elements used in the queue + * @param queueDepth The max number of entries in the queue + * @param bitWidth Integer size of the data type used in the queue + * @param tap Integer tap('seed') for the LFSR + * @param useSyncReadMem True uses SyncReadMem instead of Mem as an internal memory element + */ +class ThingsPassThroughFlushQueueTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends ThingsPassThroughTester(elements, queueDepth, bitWidth, tap, useSyncReadMem, hasFlush = true) + +/** Generic flush queue tester base class + * + * @param elements The sequence of elements used in the queue + * @param queueDepth The max number of entries in the queue + * @param bitWidth Integer size of the data type used in the queue + * @param tap Integer tap('seed') for the LFSR + * @param useSyncReadMem True uses SyncReadMem instead of Mem as an internal memory element + */ +abstract class FlushQueueTesterBase(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends BasicTester { + val q = Module(new Queue(UInt(bitWidth.W), queueDepth, hasFlush = true)) + val elems = VecInit(elements.map(_.U)) + val inCnt = Counter(elements.length + 1) + val outCnt = RegInit(0.U(log2Ceil(elements.length).W)) + val currQCnt = RegInit(0.U(log2Ceil(5).W)) + + val flush: Bool = WireInit(false.B) + val flushRegister = RegNext(flush, init = false.B) + q.io.flush.get := flush + q.io.enq.valid := (inCnt.value < elements.length.U) + q.io.deq.ready := LFSR(16)(tap) + + q.io.enq.bits := elems(inCnt.value) + when(q.io.enq.fire()) { + inCnt.inc() + currQCnt := currQCnt + 1.U //counts how many items have been enqueued + } + when(q.io.deq.fire()) { + assert(flushRegister === false.B) //check queue isn't flushed (can't dequeue an empty queue) + } + when(flushRegister) { //Internal signal maybe_full is a register so some signals update on the next cycle + //check that queue gets flushed when queue is full + assert(q.io.count === 0.U) + assert(!q.io.deq.valid, "Expected to not be able to dequeue when flush is asserted the previous cycle") + assert(q.io.enq.ready, "Expected enqueue to be ready when flush was asserted the previous cycle because queue should be empty") + } + when(inCnt.value === elements.length.U) { //stop when all entries are enqueued + stop() + } +} + +/** Test queue can flush at random times + * + * @param elements The sequence of elements used in the queue + * @param queueDepth The max number of entries in the queue + * @param bitWidth Integer size of the data type used in the queue + * @param tap Integer tap('seed') for the LFSR + * @param useSyncReadMem True uses SyncReadMem instead of Mem as an internal memory element + */ +class QueueGetsFlushedTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends FlushQueueTesterBase(elements, queueDepth, bitWidth, tap, useSyncReadMem) { + flush := LFSR(16)((tap + 3) % 16) //testing a flush when flush is called randomly + val halfCnt = (queueDepth + 1)/2 + + when(q.io.deq.fire()) { + //ensure that what comes out is what comes in + assert(currQCnt <= queueDepth.U) + assert(elems(outCnt) === q.io.deq.bits) + outCnt := outCnt + 1.U + when (currQCnt > 0.U) { + currQCnt := Mux(q.io.enq.fire(), currQCnt, (currQCnt - 1.U)) + } + } + when(flush) { + assert(currQCnt === 0.U || q.io.deq.valid) + outCnt := outCnt + Mux(q.io.enq.fire(), (currQCnt + 1.U), currQCnt) + currQCnt := 0.U //resets the number of items currently inside queue + } +} + +/** Test queue can flush when empty + * + * @param elements The sequence of elements used in the queue + * @param queueDepth The max number of entries in the queue + * @param bitWidth Integer size of the data type used in the queue + * @param tap Integer tap('seed') for the LFSR + * @param useSyncReadMem True uses SyncReadMem instead of Mem as an internal memory element + */ +class EmptyFlushEdgecaseTester (elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends FlushQueueTesterBase(elements, queueDepth, bitWidth, tap, useSyncReadMem) { + val cycleCounter = Counter(elements.length + 1) + cycleCounter.inc() //counts every cycle + + //testing a flush when queue is empty + flush := (cycleCounter.value === 0.U && inCnt.value === 0.U) //flushed only before anything is enqueued + q.io.enq.valid := (inCnt.value < elements.length.U) && !flush + + when(q.io.deq.fire()) { + assert(elems(outCnt) === q.io.deq.bits) + outCnt := outCnt + 1.U + } +} + +/** Test queue can enqueue during a flush + * + * @param elements The sequence of elements used in the queue + * @param queueDepth The max number of entries in the queue + * @param bitWidth Integer size of the data type used in the queue + * @param tap Integer tap('seed') for the LFSR + * @param useSyncReadMem True uses SyncReadMem instead of Mem as an internal memory element + */ +class EnqueueEmptyFlushEdgecaseTester (elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends FlushQueueTesterBase(elements, queueDepth, bitWidth, tap, useSyncReadMem) { + val cycleCounter = Counter(elements.length + 1) + val outCounter = Counter(elements.length + 1) + + //testing an enqueue during a flush + flush := (cycleCounter.value === 0.U && inCnt.value === 0.U) //flushed only before anything is enqueued + cycleCounter.inc() //counts every cycle + + when(q.io.deq.fire()) { + //flush and enqueue were both active on the first cycle, + //so that element is flushed immediately which makes outCnt off by one + assert(elems(outCounter.value + 1.U) === q.io.deq.bits) //ensure that what comes out is what comes in + outCounter.inc() + } +} + +/** Test queue can flush when full + * + * @param elements The sequence of elements used in the queue + * @param queueDepth The max number of entries in the queue + * @param bitWidth Integer size of the data type used in the queue + * @param tap Integer tap('seed') for the LFSR + * @param useSyncReadMem True uses SyncReadMem instead of Mem as an internal memory element + */ +class FullQueueFlushEdgecaseTester (elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends FlushQueueTesterBase(elements, queueDepth, bitWidth, tap, useSyncReadMem) { + + //testing a flush when queue is full + flush := (currQCnt === queueDepth.U) + + when(q.io.deq.fire()) { + //ensure that what comes out is what comes in + assert(currQCnt <= queueDepth.U) + assert(elems(outCnt) === q.io.deq.bits) + outCnt := outCnt + 1.U + when (currQCnt > 0.U) { + currQCnt := currQCnt - 1.U + } + } + when(flush) { + outCnt := outCnt + currQCnt + currQCnt := 0.U //resets the number of items currently inside queue + assert(currQCnt === 0.U || q.io.deq.valid) + } +} + +/** Test queue can dequeue on the same cycle as a flush + * + * @param elements The sequence of elements used in the queue + * @param queueDepth The max number of entries in the queue + * @param bitWidth Integer size of the data type used in the queue + * @param tap Integer tap('seed') for the LFSR + * @param useSyncReadMem True uses SyncReadMem instead of Mem as an internal memory element + */ +class DequeueFullQueueEdgecaseTester (elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends FlushQueueTesterBase(elements, queueDepth, bitWidth, tap, useSyncReadMem) { + //Queue should be able to dequeue when queue is not empty and flush is high + + //testing a flush when dequeue is called + flush := currQCnt === (queueDepth/2).U + q.io.enq.valid := !flushRegister + q.io.deq.ready := flush + + when(q.io.deq.fire()) { + //ensure that what comes out is what comes in + assert(currQCnt <= queueDepth.U) + assert(elems(outCnt) === q.io.deq.bits) + assert(currQCnt > 0.U) + } + when(flush) { + //The outcount register is one count behind because the dequeue happens at the same time as the flush + outCnt := outCnt + currQCnt + 1.U + currQCnt := 0.U //resets the number of items currently inside queue + assert(currQCnt === 0.U || q.io.deq.valid) + } + when(flushRegister) { + //check that queue gets flushed when queue is full + assert(q.io.deq.fire() === false.B) + } + +} + +class QueueFlushSpec extends ChiselPropSpec { + // Disable shrinking on error. + implicit val noShrinkListVal = Shrink[List[Int]](_ => Stream.empty) + implicit val noShrinkInt = Shrink[Int](_ => Stream.empty) + + property("Queue should have things pass through") { + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => + whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { + assertTesterPasses { + new ThingsPassThroughFlushQueueTester(se._2, depth, se._1, tap, isSync) + } + } + } + } + property("Queue should flush when requested") { + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => + whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { + assertTesterPasses { + new QueueGetsFlushedTester(se._2, depth, se._1, tap, isSync) + } + } + } + } + property("Queue flush when queue is empty") { + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => + whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { + assertTesterPasses { + new EmptyFlushEdgecaseTester(se._2, depth, se._1, tap, isSync) + } + } + } + } + property("Test queue can enqueue during a flush") { + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => + whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { + assertTesterPasses { + new EnqueueEmptyFlushEdgecaseTester(se._2, depth, se._1, tap, isSync) + } + } + } + } + property("Queue flush when queue is full") { + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => + whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { + assertTesterPasses { + new FullQueueFlushEdgecaseTester(se._2, depth, se._1, tap, isSync) + } + } + } + } + property("Queue should be able to dequeue when flush is high") { + forAll(Gen.choose(3, 5), safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => + whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { + assertTesterPasses ( + new DequeueFullQueueEdgecaseTester(se._2, depth, se._1, tap, isSync), + annotations = Seq(WriteVcdAnnotation) + ) + } + } + } +} diff --git a/src/test/scala/chiselTests/QueueSpec.scala b/src/test/scala/chiselTests/QueueSpec.scala index 3555a13c..51b899cb 100644 --- a/src/test/scala/chiselTests/QueueSpec.scala +++ b/src/test/scala/chiselTests/QueueSpec.scala @@ -9,8 +9,8 @@ import chisel3.testers.BasicTester import chisel3.util._ import chisel3.util.random.LFSR -class ThingsPassThroughTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends BasicTester { - val q = Module(new Queue(UInt(bitWidth.W), queueDepth, useSyncReadMem = useSyncReadMem)) +class ThingsPassThroughTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean, hasFlush: Boolean) extends BasicTester { + val q = Module(new Queue(UInt(bitWidth.W), queueDepth, useSyncReadMem = useSyncReadMem, hasFlush = hasFlush)) val elems = VecInit(elements.map { _.asUInt() }) @@ -19,7 +19,7 @@ class ThingsPassThroughTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int q.io.enq.valid := (inCnt.value < elements.length.U) q.io.deq.ready := LFSR(16)(tap) - + q.io.flush.foreach { _ := false.B } //Flush behavior is tested in QueueFlushSpec q.io.enq.bits := elems(inCnt.value) when(q.io.enq.fire()) { inCnt.inc() @@ -205,7 +205,7 @@ class QueueSpec extends ChiselPropSpec { forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { assertTesterPasses { - new ThingsPassThroughTester(se._2, depth, se._1, tap, isSync) + new ThingsPassThroughTester(se._2, depth, se._1, tap, isSync, false) } } } -- cgit v1.2.3 From 5a9b6487fbc5ccf0243f9755ee3cd88ec6036e2c Mon Sep 17 00:00:00 2001 From: anniej-sifive Date: Wed, 4 Aug 2021 12:11:58 -0700 Subject: Added VecInit factory methods (fill,iterate) (#2059) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- core/src/main/scala/chisel3/Aggregate.scala | 30 +++++++++++++++++++++- .../internal/sourceinfo/SourceInfoTransform.scala | 3 +++ src/test/scala/chiselTests/Vec.scala | 28 ++++++++++++++++++-- 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/chisel3/Aggregate.scala b/core/src/main/scala/chisel3/Aggregate.scala index 45ecec36..3a766628 100644 --- a/core/src/main/scala/chisel3/Aggregate.scala +++ b/core/src/main/scala/chisel3/Aggregate.scala @@ -590,6 +590,33 @@ object VecInit extends SourceInfoDoc { /** @group SourceInfoTransformMacro */ def do_tabulate[T <: Data](n: Int)(gen: (Int) => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[T] = apply((0 until n).map(i => gen(i))) + + /** Creates a new [[Vec]] of length `n` composed of the result of the given + * function applied to an element of data type T. + * + * @param n number of elements in the vector + * @param gen function that takes in an element T and returns an output + * element of the same type + */ + def fill[T <: Data](n: Int)(gen: => T): Vec[T] = macro VecTransform.fill + + /** @group SourceInfoTransformMacro */ + def do_fill[T <: Data](n: Int)(gen: => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[T] = + apply(Seq.fill(n)(gen)) + + /** Creates a new [[Vec]] of length `n` composed of the result of the given + * function applied to an element of data type T. + * + * @param start First element in the Vec + * @param len Lenth of elements in the Vec + * @param f Function that applies the element T from previous index and returns the output + * element to the next index + */ + def iterate[T <: Data](start: T, len: Int)(f: (T) => T): Vec[T] = macro VecTransform.iterate + + /** @group SourceInfoTransformMacro */ + def do_iterate[T <: Data](start: T, len: Int)(f: (T) => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[T] = + apply(Seq.iterate(start, len)(f)) } /** A trait for [[Vec]]s containing common hardware generators for collection @@ -964,7 +991,7 @@ abstract class Bundle(implicit compileOptions: CompileOptions) extends Record { getBundleField(m) match { case Some(d: Data) => requireIsChiselType(d) - + if (nameMap contains m.getName) { require(nameMap(m.getName) eq d) } else { @@ -1268,3 +1295,4 @@ abstract class Bundle(implicit compileOptions: CompileOptions) extends Record { */ override def toPrintable: Printable = toPrintableHelper(elements.toList.reverse) } + diff --git a/macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala b/macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala index 4533aa39..d7c301e9 100644 --- a/macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala +++ b/macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala @@ -84,6 +84,9 @@ class VecTransform(val c: Context) extends SourceInfoTransformMacro { def fill(n: c.Tree)(gen: c.Tree): c.Tree = { q"$thisObj.do_fill($n)($gen)($implicitSourceInfo, $implicitCompileOptions)" } + def iterate(start: c.Tree, len: c.Tree)(f: c.Tree): c.Tree = { + q"$thisObj.do_iterate($start,$len)($f)($implicitSourceInfo, $implicitCompileOptions)" + } def contains(x: c.Tree)(ev: c.Tree): c.Tree = { q"$thisObj.do_contains($x)($implicitSourceInfo, $ev, $implicitCompileOptions)" } diff --git a/src/test/scala/chiselTests/Vec.scala b/src/test/scala/chiselTests/Vec.scala index 1327913d..f3160c2e 100644 --- a/src/test/scala/chiselTests/Vec.scala +++ b/src/test/scala/chiselTests/Vec.scala @@ -2,6 +2,8 @@ package chiselTests +import org.scalacheck._ + import chisel3._ import chisel3.stage.ChiselStage import chisel3.testers.BasicTester @@ -104,6 +106,21 @@ class TabulateTester(n: Int) extends BasicTester { stop() } +class FillTester(n: Int, value: Int) extends BasicTester { + val x = VecInit(Array.fill(n)(value.U)) + val u = VecInit.fill(n)(value.U) + + assert(x.asUInt() === u.asUInt(), s"Expected Vec to be filled like $x, instead VecInit.fill created $u") + stop() +} + +class IterateTester(start: Int, len: Int)(f: UInt => UInt) extends BasicTester { + val controlVec = VecInit(Seq.iterate(start.U, len)(f)) + val testVec = VecInit.iterate(start.U, len)(f) + assert(controlVec.asUInt() === testVec.asUInt(), s"Expected Vec to be filled like $controlVec, instead creaeted $testVec\n") + stop() +} + class ShiftRegisterTester(n: Int) extends BasicTester { val (cnt, wrap) = Counter(true.B, n*2) val shifter = Reg(Vec(n, UInt((log2Ceil(n) max 1).W))) @@ -160,7 +177,6 @@ class PassthroughModuleTester extends Module { assert(io.out === 123.U) } - class ModuleIODynamicIndexTester(n: Int) extends BasicTester { val duts = VecInit(Seq.fill(n)(Module(new PassthroughModule).io)) val tester = Module(new PassthroughModuleTester) @@ -219,10 +235,18 @@ class VecSpec extends ChiselPropSpec with Utils { } } - property("Vecs should tabulate correctly") { + property("VecInit should tabulate correctly") { forAll(smallPosInts) { (n: Int) => assertTesterPasses{ new TabulateTester(n) } } } + property("VecInit should fill correctly") { + forAll(smallPosInts, Gen.choose(0, 50)) { (n: Int, value: Int) => assertTesterPasses{ new FillTester(n, value) } } + } + + property("VecInit should iterate correctly") { + forAll(Gen.choose(1, 10), smallPosInts) { (start: Int, len: Int) => assertTesterPasses{ new IterateTester(start, len)(x => x + 50.U)}} + } + property("Regs of vecs should be usable as shift registers") { forAll(smallPosInts) { (n: Int) => assertTesterPasses{ new ShiftRegisterTester(n) } } } -- cgit v1.2.3 From 75c00606df6a2ba48d5354f32d5dbe327552ad45 Mon Sep 17 00:00:00 2001 From: Chick Markley Date: Thu, 5 Aug 2021 09:36:24 -0700 Subject: Small changes to memory doc (#2062) * Small changes to memory doc - Fixed typo "except" => "accept" - Use `Counter` explicitly in ROM section example. * Fix counter doc compile error * remove invisible doc block in memory example * more small fixes to make mem example pass doc compile * Get rid of sine wave iterator in memory doc * get rid of tabs on VecInit example * get rid of tabs on VecInit example--- docs/src/explanations/memories.md | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/docs/src/explanations/memories.md b/docs/src/explanations/memories.md index 1f2f83be..10759f25 100644 --- a/docs/src/explanations/memories.md +++ b/docs/src/explanations/memories.md @@ -11,35 +11,32 @@ Chisel provides facilities for creating both read only and read/write memories. ## ROM Users can define read-only memories by constructing a `Vec` with `VecInit`. -`VecInit` can except either a variable-argument number of `Data` literals or a `Seq[Data]` literals that initialize the ROM. - -```scala mdoc:invisible -import chisel3._ -// This is bad, don't do this, use a val -def counter = util.Counter(true.B, 4)._1 -val Pi = 3.14 -def sin(t: Double): Double = t // What should this be? -``` +`VecInit` can accept either a variable-argument number of `Data` literals or a `Seq[Data]` literals that initialize the ROM. For example, users can create a small ROM initialized to 1, 2, 4, 8 and loop through all values using a counter as an address generator as follows: ```scala mdoc:compile-only +import chisel3._ +import chisel3.util.Counter val m = VecInit(1.U, 2.U, 4.U, 8.U) -val r = m(counter(m.length.U)) +val c = Counter(m.length) +c.inc() +val r = m(c.value) ``` -We can create an *n* value sine lookup table using a ROM initialized as follows: +We can create an *n* value sine lookup table generator using a ROM initialized as follows: ```scala mdoc:compile-only +import chisel3._ + +val Pi = math.Pi def sinTable(amp: Double, n: Int) = { val times = (0 until n).map(i => (i*2*Pi)/(n.toDouble-1) - Pi) val inits = - times.map(t => Math.round(amp * sin(t)).asSInt(32.W)) + times.map(t => Math.round(amp * math.sin(t)).asSInt(32.W)) VecInit(inits) } -def sinWave(amp: Double, n: Int) = - sinTable(amp, n)(counter(n.U)) ``` where `amp` is used to scale the fixpoint values stored in the ROM. -- cgit v1.2.3 From aae627919336f0efff98dc47b2ea5694728d5350 Mon Sep 17 00:00:00 2001 From: Boyang Han Date: Thu, 12 Aug 2021 01:36:03 -0700 Subject: Pass truth table to espresso using stdin instead of temp file --- .../chisel3/util/experimental/decode/EspressoMinimizer.scala | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala b/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala index 0351a46a..3ecec048 100644 --- a/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala +++ b/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala @@ -56,20 +56,18 @@ object EspressoMinimizer extends Minimizer with LazyLogging { .toMap } - // Since Espresso don't implements pipe, we use a temp file to do so. val input = writeTable(table) logger.trace(s"""espresso input table: |$input |""".stripMargin) - val f = os.temp(input) - val o = try { - os.proc("espresso", f).call().out.chunks.mkString + val output = try { + os.proc("espresso").call(stdin = input).out.chunks.mkString } catch { case e: java.io.IOException if e.getMessage.contains("error=2, No such file or directory") => throw EspressoNotFoundException } logger.trace(s"""espresso output table: - |$o + |$output |""".stripMargin) - TruthTable(readTable(o), table.default) + TruthTable(readTable(output), table.default) } } -- cgit v1.2.3 From 1ceb974c55c6785c21ab3934fa750ade0702e276 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 12 Aug 2021 17:04:11 -0700 Subject: Add DataView (#1955) DataView is a mechanism for "viewing" Scala objects as a subtype of `Data`. Often, this is useful for viewing one subtype of `Data`, as another. One can think about a DataView as a cross between a customizable cast and an untagged union. A DataView has a Target type `T`, and a View type `V`. DataView requires that an implementation of `DataProduct` is available for Target types. DataProduct is a type class that provides a way to iterate on `Data` children of objects of implementing types. If a DataView is provided for a type T to a type V, then the function .viewAs[V] (of type T => V) is available. The object (of type T) returned by .viewAs is called a "View" and can be used as both an rvalue and an lvalue. Unlike when using an .asTypeOf cast, connecting to a "View" will connect to the associated field or fields of the underlying Target. DataView also enables .viewAsSupertype which is available for viewing Bundles as a parent Bundle type. It is similar to .viewAs but requires a prototype object of the Target type which will be cloned in order to create the returned View. .viewAsSupertype maps between the corresponding fields of the parent and child Bundle types.--- build.sbt | 3 +- .../scala/collection/immutable/package.scala | 3 + core/src/main/scala/chisel3/Aggregate.scala | 42 +- core/src/main/scala/chisel3/Data.scala | 83 +++- core/src/main/scala/chisel3/Element.scala | 4 + core/src/main/scala/chisel3/Module.scala | 42 +- core/src/main/scala/chisel3/ModuleAspect.scala | 4 +- core/src/main/scala/chisel3/RawModule.scala | 31 ++ .../experimental/dataview/DataProduct.scala | 72 +++ .../chisel3/experimental/dataview/DataView.scala | 154 ++++++ .../chisel3/experimental/dataview/package.scala | 251 ++++++++++ .../main/scala/chisel3/internal/BiConnect.scala | 6 +- core/src/main/scala/chisel3/internal/Binding.scala | 9 + core/src/main/scala/chisel3/internal/Builder.scala | 66 ++- .../main/scala/chisel3/internal/MonoConnect.scala | 6 +- .../main/scala/chisel3/internal/firrtl/IR.scala | 7 +- docs/src/cookbooks/cookbook.md | 9 +- docs/src/cookbooks/dataview.md | 179 +++++++ docs/src/explanations/dataview.md | 520 ++++++++++++++++++++ src/main/scala/chisel3/stage/phases/Convert.scala | 3 +- src/test/scala/chiselTests/ChiselSpec.scala | 29 ++ .../scala/chiselTests/experimental/DataView.scala | 542 +++++++++++++++++++++ .../experimental/DataViewIntegrationSpec.scala | 57 +++ .../experimental/DataViewTargetSpec.scala | 169 +++++++ .../experimental/ModuleDataProductSpec.scala | 91 ++++ .../chiselTests/stage/ChiselOptionsViewSpec.scala | 3 +- 26 files changed, 2307 insertions(+), 78 deletions(-) create mode 100644 core/src/main/scala/chisel3/experimental/dataview/DataProduct.scala create mode 100644 core/src/main/scala/chisel3/experimental/dataview/DataView.scala create mode 100644 core/src/main/scala/chisel3/experimental/dataview/package.scala create mode 100644 docs/src/cookbooks/dataview.md create mode 100644 docs/src/explanations/dataview.md create mode 100644 src/test/scala/chiselTests/experimental/DataView.scala create mode 100644 src/test/scala/chiselTests/experimental/DataViewIntegrationSpec.scala create mode 100644 src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala create mode 100644 src/test/scala/chiselTests/experimental/ModuleDataProductSpec.scala diff --git a/build.sbt b/build.sbt index 1a47d7c1..5d7fad2d 100644 --- a/build.sbt +++ b/build.sbt @@ -235,7 +235,8 @@ lazy val docs = project // new documentation project scalacOptions += "-language:reflectiveCalls", mdocIn := file("docs/src"), mdocOut := file("docs/generated"), - mdocExtraArguments := Seq("--cwd", "docs"), + // None of our links are hygienic because they're primarily used on the website with .html + mdocExtraArguments := Seq("--cwd", "docs", "--no-link-hygiene"), mdocVariables := Map( "BUILD_DIR" -> "docs-target" // build dir for mdoc programs to dump temp files ) diff --git a/core/src/main/scala-2.12/scala/collection/immutable/package.scala b/core/src/main/scala-2.12/scala/collection/immutable/package.scala index 7ae87d9b..c06935d0 100644 --- a/core/src/main/scala-2.12/scala/collection/immutable/package.scala +++ b/core/src/main/scala-2.12/scala/collection/immutable/package.scala @@ -10,4 +10,7 @@ package object immutable { val VectorMap = ListMap type VectorMap[K, +V] = ListMap[K, V] + + val LazyList = Stream + type LazyList[+A] = Stream[A] } diff --git a/core/src/main/scala/chisel3/Aggregate.scala b/core/src/main/scala/chisel3/Aggregate.scala index 3a766628..58bc5ccb 100644 --- a/core/src/main/scala/chisel3/Aggregate.scala +++ b/core/src/main/scala/chisel3/Aggregate.scala @@ -3,6 +3,7 @@ package chisel3 import chisel3.experimental.VecLiterals.AddVecLiteralConstructor +import chisel3.experimental.dataview.{InvalidViewException, isView} import scala.collection.immutable.{SeqMap, VectorMap} import scala.collection.mutable.{HashSet, LinkedHashMap} @@ -88,44 +89,6 @@ sealed abstract class Aggregate extends Data { } } - // Returns pairs of all fields, element-level and containers, in a Record and their path names - private[chisel3] def getRecursiveFields(data: Data, path: String): Seq[(Data, String)] = data match { - case data: Record => - data.elements.map { case (fieldName, fieldData) => - getRecursiveFields(fieldData, s"$path.$fieldName") - }.fold(Seq(data -> path)) { - _ ++ _ - } - case data: Vec[_] => - data.getElements.zipWithIndex.map { case (fieldData, fieldIndex) => - getRecursiveFields(fieldData, path = s"$path($fieldIndex)") - }.fold(Seq(data -> path)) { - _ ++ _ - } - case data => Seq(data -> path) // we don't support or recurse into other Aggregate types here - } - - - // Returns pairs of corresponding fields between two Records of the same type - private[chisel3] def getMatchedFields(x: Data, y: Data): Seq[(Data, Data)] = (x, y) match { - case (x: Element, y: Element) => - require(x typeEquivalent y) - Seq(x -> y) - case (x: Record, y: Record) => - (x.elements zip y.elements).map { case ((xName, xElt), (yName, yElt)) => - require(xName == yName) // assume fields returned in same, deterministic order - getMatchedFields(xElt, yElt) - }.fold(Seq(x -> y)) { - _ ++ _ - } - case (x: Vec[_], y: Vec[_]) => - (x.getElements zip y.getElements).map { case (xElt, yElt) => - getMatchedFields(xElt, yElt) - }.fold(Seq(x -> y)) { - _ ++ _ - } - } - override def do_asUInt(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = { SeqUtils.do_asUInt(flatten.map(_.asUInt())) } @@ -297,6 +260,9 @@ sealed class Vec[T <: Data] private[chisel3] (gen: => T, val length: Int) def do_apply(p: UInt)(implicit compileOptions: CompileOptions): T = { requireIsHardware(this, "vec") requireIsHardware(p, "vec index") + if (isView(this)) { + throw InvalidViewException("Dynamic indexing of Views is not yet supported") + } val port = gen // Reconstruct the resolvedDirection (in Aggregate.bind), since it's not stored. diff --git a/core/src/main/scala/chisel3/Data.scala b/core/src/main/scala/chisel3/Data.scala index 5513035b..32d83008 100644 --- a/core/src/main/scala/chisel3/Data.scala +++ b/core/src/main/scala/chisel3/Data.scala @@ -2,13 +2,16 @@ package chisel3 +import chisel3.experimental.dataview.reify + import scala.language.experimental.macros -import chisel3.experimental.{Analog, DataMirror, FixedPoint, Interval} +import chisel3.experimental.{Analog, BaseModule, DataMirror, FixedPoint, Interval} import chisel3.internal.Builder.pushCommand import chisel3.internal._ import chisel3.internal.firrtl._ import chisel3.internal.sourceinfo.{DeprecatedSourceInfo, SourceInfo, SourceInfoTransform, UnlocatableSourceInfo} +import scala.collection.immutable.LazyList // Needed for 2.12 alias import scala.util.Try /** User-specified directions. @@ -236,6 +239,62 @@ private[chisel3] object cloneSupertype { } } +// Returns pairs of all fields, element-level and containers, in a Record and their path names +private[chisel3] object getRecursiveFields { + def apply(data: Data, path: String): Seq[(Data, String)] = data match { + case data: Record => + data.elements.map { case (fieldName, fieldData) => + getRecursiveFields(fieldData, s"$path.$fieldName") + }.fold(Seq(data -> path)) { + _ ++ _ + } + case data: Vec[_] => + data.getElements.zipWithIndex.map { case (fieldData, fieldIndex) => + getRecursiveFields(fieldData, path = s"$path($fieldIndex)") + }.fold(Seq(data -> path)) { + _ ++ _ + } + case data: Element => Seq(data -> path) + } + + def lazily(data: Data, path: String): Seq[(Data, String)] = data match { + case data: Record => + LazyList(data -> path) ++ + data.elements.view.flatMap { case (fieldName, fieldData) => + getRecursiveFields(fieldData, s"$path.$fieldName") + } + case data: Vec[_] => + LazyList(data -> path) ++ + data.getElements.view.zipWithIndex.flatMap { case (fieldData, fieldIndex) => + getRecursiveFields(fieldData, path = s"$path($fieldIndex)") + } + case data: Element => LazyList(data -> path) + } +} + +// Returns pairs of corresponding fields between two Records of the same type +// TODO it seems wrong that Elements are checked for typeEquivalence in Bundle and Vec lit creation +private[chisel3] object getMatchedFields { + def apply(x: Data, y: Data): Seq[(Data, Data)] = (x, y) match { + case (x: Element, y: Element) => + require(x typeEquivalent y) + Seq(x -> y) + case (x: Record, y: Record) => + (x.elements zip y.elements).map { case ((xName, xElt), (yName, yElt)) => + require(xName == yName) // assume fields returned in same, deterministic order + getMatchedFields(xElt, yElt) + }.fold(Seq(x -> y)) { + _ ++ _ + } + case (x: Vec[_], y: Vec[_]) => + (x.getElements zip y.getElements).map { case (xElt, yElt) => + getMatchedFields(xElt, yElt) + }.fold(Seq(x -> y)) { + _ ++ _ + } + } +} + /** Returns the chisel type of a hardware object, allowing other hardware to be constructed from it. */ object chiselTypeOf { @@ -488,6 +547,14 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { } requireIsHardware(this) topBindingOpt match { + // DataView + case Some(ViewBinding(target)) => reify(target).ref + 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 + case x: Some[_] => throwException(s"Internal Error: In .ref for $this got '$topBindingOpt' and '$x'") + } // Literals case Some(ElementLitBinding(litArg)) => litArg case Some(BundleLitBinding(litMap)) => @@ -514,6 +581,20 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { } } + + // Recursively set the parent of the start Data and any children (eg. in an Aggregate) + private[chisel3] def setAllParents(parent: Option[BaseModule]): Unit = { + def rec(data: Data): Unit = { + data._parent = parent + data match { + case _: Element => + case agg: Aggregate => + agg.getElements.foreach(rec) + } + } + rec(this) + } + private[chisel3] def width: Width private[chisel3] def legacyConnect(that: Data)(implicit sourceInfo: SourceInfo): Unit diff --git a/core/src/main/scala/chisel3/Element.scala b/core/src/main/scala/chisel3/Element.scala index 40291b12..bc006922 100644 --- a/core/src/main/scala/chisel3/Element.scala +++ b/core/src/main/scala/chisel3/Element.scala @@ -34,6 +34,10 @@ abstract class Element extends Data { case Some(litArg) => Some(ElementLitBinding(litArg)) case _ => Some(DontCareBinding()) } + 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 topBindingOpt => topBindingOpt } diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala index 41fe4554..64065ec9 100644 --- a/core/src/main/scala/chisel3/Module.scala +++ b/core/src/main/scala/chisel3/Module.scala @@ -184,7 +184,7 @@ package internal { object BaseModule { // Private internal class to serve as a _parent for Data in cloned ports - private[chisel3] class ModuleClone(_proto: BaseModule) extends BaseModule { + private[chisel3] class ModuleClone(_proto: BaseModule) extends PseudoModule { // ClonePorts that hold the bound ports for this module // Used for setting the refs of both this module and the Record private[BaseModule] var _portsRecord: Record = _ @@ -195,7 +195,7 @@ package internal { _component = _proto._component None } - // This module doesn't acutally exist in the FIRRTL so no initialization to do + // This module doesn't actually exist in the FIRRTL so no initialization to do private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = () override def desiredName: String = _proto.name @@ -226,19 +226,6 @@ package internal { override def cloneType = (new ClonePorts(elts: _*)).asInstanceOf[this.type] } - // Recursively set the parent of the start Data and any children (eg. in an Aggregate) - private def setAllParents(start: Data, parent: Option[BaseModule]): Unit = { - def rec(data: Data): Unit = { - data._parent = parent - data match { - case _: Element => - case agg: Aggregate => - agg.getElements.foreach(rec) - } - } - rec(start) - } - private[chisel3] def cloneIORecord(proto: BaseModule)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): ClonePorts = { require(proto.isClosed, "Can't clone a module before module close") // Fake Module to serve as the _parent of the cloned ports @@ -249,7 +236,7 @@ package internal { // currentModule (and not clonePorts) val clonePorts = new ClonePorts(proto.getModulePorts: _*) clonePorts.bind(PortBinding(cloneParent)) - setAllParents(clonePorts, Some(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) { @@ -303,7 +290,15 @@ package experimental { } } - protected def getIds = { + // Returns the last id contained within a Module + private[chisel3] def _lastId: Long = _ids.last match { + case mod: BaseModule => mod._lastId + case _ => + // Ideally we could just take last._id, but Records store and thus bind their Data in reverse order + _ids.maxBy(_._id)._id + } + + private[chisel3] def getIds = { require(_closed, "Can't get ids before module close") _ids.toSeq } @@ -368,10 +363,10 @@ package experimental { /** Legalized name of this module. */ final lazy val name = try { - // ModuleAspects and ModuleClones are not "true modules" and thus should share + // PseudoModules are not "true modules" and thus should share // their original modules names without uniquification this match { - case (_: ModuleAspect | _: internal.BaseModule.ModuleClone) => desiredName + case _: PseudoModule => desiredName case _ => Builder.globalNamespace.name(desiredName) } } catch { @@ -399,7 +394,14 @@ package experimental { final def toAbsoluteTarget: IsModule = { _parent match { case Some(parent) => parent.toAbsoluteTarget.instOf(this.instanceName, toTarget.module) - case None => toTarget + case None => + // FIXME Special handling for Views - evidence of "weirdness" of .toAbsoluteTarget + // In theory, .toAbsoluteTarget should not be necessary, .toTarget combined with the + // target disambiguation in FIRRTL's deduplication transform should ensure that .toTarget + // is always unambigous. However, legacy workarounds for Chisel's lack of an instance API + // have lead some to use .toAbsoluteTarget as a workaround. A proper instance API will make + // it possible to deprecate and remove .toAbsoluteTarget + if (this == ViewParent) ViewParent.absoluteTarget else toTarget } } diff --git a/core/src/main/scala/chisel3/ModuleAspect.scala b/core/src/main/scala/chisel3/ModuleAspect.scala index 4e37ab35..a528fa72 100644 --- a/core/src/main/scala/chisel3/ModuleAspect.scala +++ b/core/src/main/scala/chisel3/ModuleAspect.scala @@ -2,7 +2,7 @@ package chisel3 -import chisel3.internal.Builder +import chisel3.internal.{Builder, PseudoModule} /** Used by Chisel Aspects to inject Chisel code into modules, after they have been elaborated. * This is an internal API - don't use! @@ -13,7 +13,7 @@ import chisel3.internal.Builder * @param moduleCompileOptions */ abstract class ModuleAspect private[chisel3] (module: RawModule) - (implicit moduleCompileOptions: CompileOptions) extends RawModule { + (implicit moduleCompileOptions: CompileOptions) extends RawModule with PseudoModule { Builder.addAspect(module, this) diff --git a/core/src/main/scala/chisel3/RawModule.scala b/core/src/main/scala/chisel3/RawModule.scala index fadb8dae..74e9db6c 100644 --- a/core/src/main/scala/chisel3/RawModule.scala +++ b/core/src/main/scala/chisel3/RawModule.scala @@ -11,6 +11,7 @@ import chisel3.internal.BaseModule.ModuleClone import chisel3.internal.Builder._ import chisel3.internal.firrtl._ import chisel3.internal.sourceinfo.UnlocatableSourceInfo +import _root_.firrtl.annotations.{IsModule, ModuleTarget} /** Abstract base class for Modules that contain Chisel RTL. * This abstract base class is a user-defined module which does not include implicit clock and reset and supports @@ -157,6 +158,9 @@ trait RequireSyncReset extends Module { package object internal { + /** Marker trait for modules that are not true modules */ + private[chisel3] trait PseudoModule extends BaseModule + // Private reflective version of "val io" to maintain Chisel.Module semantics without having // io as a virtual method. See https://github.com/freechipsproject/chisel3/pull/1550 for more // information about the removal of "val io" @@ -264,4 +268,31 @@ package object internal { } } } + + /** Internal API for [[ViewParent]] */ + sealed private[chisel3] class ViewParentAPI extends RawModule()(ExplicitCompileOptions.Strict) with PseudoModule { + // We must provide `absoluteTarget` but not `toTarget` because otherwise they would be exactly + // the same and we'd have no way to distinguish the kind of target when renaming view targets in + // the Converter + // Note that this is not overriding .toAbsoluteTarget, that is a final def in BaseModule that delegates + // to this method + private[chisel3] val absoluteTarget: IsModule = ModuleTarget(this.circuitName, "_$$AbsoluteView$$_") + + // This module is not instantiable + override private[chisel3] def generateComponent(): Option[Component] = None + override private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = () + // This module is not really part of the circuit + _parent = None + + // Sigil to mark views, starts with '_' to make it a legal FIRRTL target + override def desiredName = "_$$View$$_" + + private[chisel3] val fakeComponent: Component = DefModule(this, desiredName, Nil, Nil) + } + + /** Special internal object representing the parent of all views + * + * @note this is a val instead of an object because of the need to wrap in Module(...) + */ + private[chisel3] val ViewParent = Module.do_apply(new ViewParentAPI)(UnlocatableSourceInfo, ExplicitCompileOptions.Strict) } diff --git a/core/src/main/scala/chisel3/experimental/dataview/DataProduct.scala b/core/src/main/scala/chisel3/experimental/dataview/DataProduct.scala new file mode 100644 index 00000000..55dd8505 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/dataview/DataProduct.scala @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.dataview + +import chisel3.experimental.BaseModule +import chisel3.{Data, getRecursiveFields} + +import scala.annotation.implicitNotFound + +/** Typeclass interface for getting elements of type [[Data]] + * + * This is needed for validating [[DataView]]s targeting type `A`. + * Can be thought of as "can be the Target of a DataView". + * + * Chisel provides some implementations in [[DataProduct$ object DataProduct]] that are available + * by default in the implicit scope. + * + * @tparam A Type that has elements of type [[Data]] + * @see [[https://www.chisel-lang.org/chisel3/docs/explanations/dataview#dataproduct Detailed Documentation]] + */ +@implicitNotFound("Could not find implicit value for DataProduct[${A}].\n" + + "Please see https://www.chisel-lang.org/chisel3/docs/explanations/dataview#dataproduct") +trait DataProduct[-A] { + /** Provides [[Data]] elements within some containing object + * + * @param a Containing object + * @param path Hierarchical path to current signal (for error reporting) + * @return Data elements and associated String paths (Strings for error reporting only!) + */ + def dataIterator(a: A, path: String): Iterator[(Data, String)] + + /** Returns a checker to test if the containing object contains a `Data` object + * @note Implementers may want to override if iterating on all `Data` is expensive for `A` and `A` + * will primarily be used in `PartialDataViews` + * @note The returned value is a function, not a true Set, but is describing the functionality of + * Set containment + * @param a Containing object + * @return A checker that itself returns True if a given `Data` is contained in `a` + * as determined by an `==` test + */ + def dataSet(a: A): Data => Boolean = dataIterator(a, "").map(_._1).toSet +} + +/** Encapsulating object for automatically provided implementations of [[DataProduct]] + * + * @note DataProduct implementations provided in this object are available in the implicit scope + */ +object DataProduct { + /** [[DataProduct]] implementation for [[Data]] */ + implicit val dataDataProduct: DataProduct[Data] = new DataProduct[Data] { + def dataIterator(a: Data, path: String): Iterator[(Data, String)] = + getRecursiveFields.lazily(a, path).iterator + } + + /** [[DataProduct]] implementation for [[BaseModule]] */ + implicit val userModuleDataProduct: DataProduct[BaseModule] = new DataProduct[BaseModule] { + def dataIterator(a: BaseModule, path: String): Iterator[(Data, String)] = { + a.getIds.iterator.flatMap { + case d: Data if d.getOptionRef.isDefined => // Using ref to decide if it's truly hardware in the module + Seq(d -> s"${path}.${d.instanceName}") + case b: BaseModule => dataIterator(b, s"$path.${b.instanceName}") + case _ => Seq.empty + } + } + // Overridden for performance + override def dataSet(a: BaseModule): Data => Boolean = { + val lastId = a._lastId // Not cheap to compute + // Return a function + e => e._id > a._id && e._id <= lastId + } + } +} diff --git a/core/src/main/scala/chisel3/experimental/dataview/DataView.scala b/core/src/main/scala/chisel3/experimental/dataview/DataView.scala new file mode 100644 index 00000000..caf004c2 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/dataview/DataView.scala @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.dataview + +import chisel3._ +import chisel3.internal.sourceinfo.SourceInfo +import scala.reflect.runtime.universe.WeakTypeTag + +import annotation.implicitNotFound + + +/** Mapping between a target type `T` and a view type `V` + * + * Enables calling `.viewAs[T]` on instances of the target type. + * + * ==Detailed documentation== + * - [[https://www.chisel-lang.org/chisel3/docs/explanations/dataview Explanation]] + * - [[https://www.chisel-lang.org/chisel3/docs/cookbooks/dataview Cookbook]] + * + * @example {{{ + * class Foo(val w: Int) extends Bundle { + * val a = UInt(w.W) + * } + * class Bar(val w: Int) extends Bundle { + * val b = UInt(w.W) + * } + * // DataViews are created using factory methods in the companion object + * implicit val view = DataView[Foo, Bar]( + * // The first argument is a function constructing a Foo from a Bar + * foo => new Bar(foo.w) + * // The remaining arguments are a variable number of field pairings + * _.a -> _.b + * ) + * }}} + * + * @tparam T Target type (must have an implementation of [[DataProduct]]) + * @tparam V View type + * @see [[DataView$ object DataView]] for factory methods + * @see [[PartialDataView object PartialDataView]] for defining non-total `DataViews` + */ +@implicitNotFound("Could not find implicit value for DataView[${T}, ${V}].\n" + + "Please see https://www.chisel-lang.org/chisel3/docs/explanations/dataview") +sealed class DataView[T : DataProduct, V <: Data] private[chisel3] ( + /** Function constructing an object of the View type from an object of the Target type */ + private[chisel3] val mkView: T => V, + /** Function that returns corresponding fields of the target and view */ + private[chisel3] val mapping: (T, V) => Iterable[(Data, Data)], + // Aliasing this with a def below to make the ScalaDoc show up for the field + _total: Boolean +)( + implicit private[chisel3] val sourceInfo: SourceInfo +) { + /** Indicates if the mapping contains every field of the target */ + def total: Boolean = _total + + override def toString: String = { + val base = sourceInfo.makeMessage(x => x) + val loc = if (base.nonEmpty) base else "@unknown" + val name = if (total) "DataView" else "PartialDataView" + s"$name(defined $loc)" + } + + /** Compose two `DataViews` together to construct a view from the target of this `DataView` to the + * view type of the second `DataView` + * + * @param g a DataView from `V` to new view-type `V2` + * @tparam V2 View type of `DataView` `g` + * @return a new `DataView` from the original `T` to new view-type `V2` + */ + def andThen[V2 <: Data](g: DataView[V, V2])(implicit sourceInfo: SourceInfo): DataView[T, V2] = { + val self = this + // We have to pass the DataProducts and DataViews manually to .viewAs below + val tdp = implicitly[DataProduct[T]] + val vdp = implicitly[DataProduct[V]] + new DataView[T, V2]( + t => g.mkView(mkView(t)), + { case (t, v2) => List(t.viewAs[V](tdp, self).viewAs[V2](vdp, g) -> v2) }, + this.total && g.total + ) { + override def toString: String = s"$self andThen $g" + } + } +} + +/** Factory methods for constructing [[DataView]]s, see class for example use */ +object DataView { + + /** Default factory method, alias for [[pairs]] */ + def apply[T : DataProduct, V <: Data](mkView: T => V, pairs: ((T, V) => (Data, Data))*)(implicit sourceInfo: SourceInfo): DataView[T, V] = + DataView.pairs(mkView, pairs: _*) + + /** Construct [[DataView]]s with pairs of functions from the target and view to corresponding fields */ + def pairs[T : DataProduct, V <: Data](mkView: T => V, pairs: ((T, V) => (Data, Data))*)(implicit sourceInfo: SourceInfo): DataView[T, V] = + mapping(mkView: T => V, swizzle(pairs)) + + /** More general factory method for complex mappings */ + def mapping[T : DataProduct, V <: Data](mkView: T => V, mapping: (T, V) => Iterable[(Data, Data)])(implicit sourceInfo: SourceInfo): DataView[T, V] = + new DataView[T, V](mkView, mapping, _total = true) + + /** Provides `invert` for invertible [[DataView]]s + * + * This must be done as an extension method because it applies an addition constraint on the `Target` + * type parameter, namely that it must be a subtype of [[Data]]. + * + * @note [[PartialDataView]]s are **not** invertible and will result in an elaboration time exception + */ + implicit class InvertibleDataView[T <: Data : WeakTypeTag, V <: Data : WeakTypeTag](view: DataView[T, V]) { + def invert(mkView: V => T): DataView[V, T] = { + // It would've been nice to make this a compiler error, but it's unclear how to make that work. + // We tried having separate TotalDataView and PartialDataView and only defining inversion for + // TotalDataView. For some reason, implicit resolution wouldn't invert TotalDataViews. This is + // probably because it was looking for the super-type DataView and since invertDataView was + // only defined on TotalDataView, it wasn't included in implicit resolution. Thus we end up + // with a runtime check. + if (!view.total) { + val tt = implicitly[WeakTypeTag[T]].tpe + val vv = implicitly[WeakTypeTag[V]].tpe + val msg = s"Cannot invert '$view' as it is non-total.\n Try providing a DataView[$vv, $tt]." + + s"\n Please see https://www.chisel-lang.org/chisel3/docs/explanations/dataview." + throw InvalidViewException(msg) + } + implicit val sourceInfo = view.sourceInfo + new DataView[V, T](mkView, swapArgs(view.mapping), view.total) + } + } + + private[dataview] def swizzle[A, B, C, D](fs: Iterable[(A, B) => (C, D)]): (A, B) => Iterable[(C, D)] = { + case (a, b) => fs.map(f => f(a, b)) + } + + private def swapArgs[A, B, C, D](f: (A, B) => Iterable[(C, D)]): (B, A) => Iterable[(D, C)] = { + case (b, a) => f(a, b).map(_.swap) + } + + /** All Chisel Data are viewable as their own type */ + implicit def identityView[A <: Data](implicit sourceInfo: SourceInfo): DataView[A, A] = + DataView[A, A](chiselTypeOf.apply, { case (x, y) => (x, y) }) +} + +/** Factory methods for constructing non-total [[DataView]]s */ +object PartialDataView { + + /** Default factory method, alias for [[pairs]] */ + def apply[T: DataProduct, V <: Data](mkView: T => V, pairs: ((T, V) => (Data, Data))*)(implicit sourceInfo: SourceInfo): DataView[T, V] = + PartialDataView.pairs(mkView, pairs: _*) + + /** Construct [[DataView]]s with pairs of functions from the target and view to corresponding fields */ + def pairs[T: DataProduct, V <: Data](mkView: T => V, pairs: ((T, V) => (Data, Data))*)(implicit sourceInfo: SourceInfo): DataView[T, V] = + mapping(mkView, DataView.swizzle(pairs)) + + /** More general factory method for complex mappings */ + def mapping[T: DataProduct, V <: Data](mkView: T => V, mapping: (T, V) => Iterable[(Data, Data)])(implicit sourceInfo: SourceInfo): DataView[T, V] = + new DataView[T, V](mkView, mapping, _total = false) +} diff --git a/core/src/main/scala/chisel3/experimental/dataview/package.scala b/core/src/main/scala/chisel3/experimental/dataview/package.scala new file mode 100644 index 00000000..1acf43e1 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/dataview/package.scala @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental + +import chisel3._ +import chisel3.internal._ +import chisel3.internal.sourceinfo.SourceInfo + +import scala.annotation.{implicitNotFound, tailrec} +import scala.collection.mutable +import scala.collection.immutable.LazyList // Needed for 2.12 alias + +package object dataview { + case class InvalidViewException(message: String) extends ChiselException(message) + + /** Provides `viewAs` for types that have an implementation of [[DataProduct]] + * + * Calling `viewAs` also requires an implementation of [[DataView]] for the target type + */ + implicit class DataViewable[T](target: T) { + def viewAs[V <: Data](implicit dataproduct: DataProduct[T], dataView: DataView[T, V]): V = { + // TODO put a try catch here for ExpectedHardwareException and perhaps others + // It's likely users will accidentally use chiselTypeOf or something that may error, + // The right thing to use is DataMirror...chiselTypeClone because of composition with DataView.andThen + // Another option is that .andThen could give a fake binding making chiselTypeOfs in the user code safe + val result: V = dataView.mkView(target) + requireIsChiselType(result, "viewAs") + + doBind(target, result, dataView) + + // Setting the parent marks these Data as Views + result.setAllParents(Some(ViewParent)) + // The names of views do not matter except for when a view is annotated. For Views that correspond + // To a single Data, we just forward the name of the Target. For Views that correspond to more + // than one Data, we return this assigned name but rename it in the Convert stage + result.forceName(None, "view", Builder.viewNamespace) + result + } + } + + // This private type alias lets us provide a custom error message for misuing the .viewAs for upcasting Bundles + @implicitNotFound("${A} is not a subtype of ${B}! Did you mean .viewAs[${B}]? " + + "Please see https://www.chisel-lang.org/chisel3/docs/cookbooks/dataview") + private type SubTypeOf[A, B] = A <:< B + + /** Provides `viewAsSupertype` for subclasses of [[Bundle]] */ + implicit class BundleUpcastable[T <: Bundle](target: T) { + /** View a [[Bundle]] or [[Record]] as a parent type (upcast) */ + def viewAsSupertype[V <: Bundle](proto: V)(implicit ev: SubTypeOf[T, V], sourceInfo: SourceInfo): V = { + implicit val dataView = PartialDataView.mapping[T, V](_ => proto, { + case (a, b) => + val aElts = a.elements + val bElts = b.elements + val bKeys = bElts.keySet + val keys = aElts.keysIterator.filter(bKeys.contains) + keys.map(k => aElts(k) -> bElts(k)).toSeq + }) + target.viewAs[V] + } + } + + private def nonTotalViewException(dataView: DataView[_, _], target: Any, view: Data, targetFields: Seq[String], viewFields: Seq[String]) = { + def missingMsg(name: String, fields: Seq[String]): Option[String] = { + val str = fields.mkString(", ") + fields.size match { + case 0 => None + case 1 => Some(s"$name field '$str' is missing") + case _ => Some(s"$name fields '$str' are missing") + } + } + val vs = missingMsg("view", viewFields) + val ts = missingMsg("target", targetFields) + val reasons = (ts ++ vs).mkString(" and ").capitalize + val suggestion = if (ts.nonEmpty) "\n If the view *should* be non-total, try a 'PartialDataView'." else "" + val msg = s"Viewing $target as $view is non-Total!\n $reasons.\n DataView used is $dataView.$suggestion" + throw InvalidViewException(msg) + } + + // TODO should this be moved to class Aggregate / can it be unified with Aggregate.bind? + private def doBind[T : DataProduct, V <: Data](target: T, view: V, dataView: DataView[T, V]): Unit = { + val mapping = dataView.mapping(target, view) + val total = dataView.total + // Lookups to check the mapping results + val viewFieldLookup: Map[Data, String] = getRecursiveFields(view, "_").toMap + val targetContains: Data => Boolean = implicitly[DataProduct[T]].dataSet(target) + + // Resulting bindings for each Element of the View + val childBindings = + new mutable.HashMap[Data, mutable.ListBuffer[Element]] ++ + viewFieldLookup.view + .collect { case (elt: Element, _) => elt } + .map(_ -> new mutable.ListBuffer[Element]) + + def viewFieldName(d: Data): String = + viewFieldLookup.get(d).map(_ + " ").getOrElse("") + d.toString + + // Helper for recording the binding of each + def onElt(te: Element, ve: Element): Unit = { + // TODO can/should we aggregate these errors? + def err(name: String, arg: Data) = + throw InvalidViewException(s"View mapping must only contain Elements within the $name, got $arg") + + // The elements may themselves be views, look through the potential chain of views for the Elements + // that are actually members of the target or view + val tex = unfoldView(te).find(targetContains).getOrElse(err("Target", te)) + val vex = unfoldView(ve).find(viewFieldLookup.contains).getOrElse(err("View", ve)) + + if (tex.getClass != vex.getClass) { + val fieldName = viewFieldName(vex) + throw InvalidViewException(s"Field $fieldName specified as view of non-type-equivalent value $tex") + } + // View width must be unknown or match target width + if (vex.widthKnown && vex.width != tex.width) { + def widthAsString(x: Element) = x.widthOption.map("<" + _ + ">").getOrElse("") + val fieldName = viewFieldName(vex) + val vwidth = widthAsString(vex) + val twidth = widthAsString(tex) + throw InvalidViewException(s"View field $fieldName has width ${vwidth} that is incompatible with target value $tex's width ${twidth}") + } + childBindings(vex) += tex + } + + mapping.foreach { + // Special cased because getMatchedFields checks typeEquivalence on Elements (and is used in Aggregate path) + // Also saves object allocations on common case of Elements + case (ae: Element, be: Element) => onElt(ae, be) + + case (aa: Aggregate, ba: Aggregate) => + if (!ba.typeEquivalent(aa)) { + val fieldName = viewFieldLookup(ba) + throw InvalidViewException(s"field $fieldName specified as view of non-type-equivalent value $aa") + } + getMatchedFields(aa, ba).foreach { + case (aelt: Element, belt: Element) => onElt(aelt, belt) + case _ => // Ignore matching of Aggregates + } + } + + // Errors in totality of the View, use var List to keep fast path cheap (no allocation) + var viewNonTotalErrors: List[Data] = Nil + var targetNonTotalErrors: List[String] = Nil + + val targetSeen: Option[mutable.Set[Data]] = if (total) Some(mutable.Set.empty[Data]) else None + + val resultBindings = childBindings.map { case (data, targets) => + val targetsx = targets match { + case collection.Seq(target: Element) => target + case collection.Seq() => + viewNonTotalErrors = data :: viewNonTotalErrors + data.asInstanceOf[Element] // Return the Data itself, will error after this map, cast is safe + case x => + throw InvalidViewException(s"Got $x, expected Seq(_: Direct)") + } + // TODO record and report aliasing errors + targetSeen.foreach(_ += targetsx) + data -> targetsx + }.toMap + + // Check for totality of Target + targetSeen.foreach { seen => + val lookup = implicitly[DataProduct[T]].dataIterator(target, "_") + for (missed <- lookup.collect { case (d: Element, name) if !seen(d) => name }) { + targetNonTotalErrors = missed :: targetNonTotalErrors + } + } + if (viewNonTotalErrors != Nil || targetNonTotalErrors != Nil) { + val viewErrors = viewNonTotalErrors.map(f => viewFieldLookup.getOrElse(f, f.toString)) + nonTotalViewException(dataView, target, view, targetNonTotalErrors, viewErrors) + } + + view match { + case elt: Element => view.bind(ViewBinding(resultBindings(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) + 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 + getRecursiveFields.lazily(view, "_").foreach { + case (agg: Aggregate, _) if agg != view => + Builder.unnamedViews += agg + case _ => // Do nothing + } + agg.bind(AggregateViewBinding(resultBindings, topt)) + } + } + + // Traces an Element that may (or may not) be a view until it no longer maps + // Inclusive of the argument + 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) + target #:: rec(target) + case Some(_) | None => LazyList.empty + } + elt #:: rec(elt) + } + + // Safe for all Data + private[chisel3] def isView(d: Data): Boolean = d._parent.contains(ViewParent) + + /** Turn any [[Element]] that could be a View into a concrete Element + * + * This is the fundamental "unwrapping" or "tracing" primitive operation for handling Views within + * Chisel. + */ + private[chisel3] def reify(elt: Element): Element = + reify(elt, elt.topBinding) + + /** Turn any [[Element]] that could be a View into a concrete Element + * + * This is the fundamental "unwrapping" or "tracing" primitive operation for handling Views within + * Chisel. + */ + @tailrec private[chisel3] def reify(elt: Element, topBinding: TopBinding): Element = + topBinding match { + case ViewBinding(target) => reify(target, elt.topBinding) + case _ => elt + } + + /** 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 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 + } + } + candidate.flatMap { d => + // Candidate may itself be a view, keep tracing in those cases + if (isView(d)) reifySingleData(d) else Some(d) + } + } + +} diff --git a/core/src/main/scala/chisel3/internal/BiConnect.scala b/core/src/main/scala/chisel3/internal/BiConnect.scala index aa7d7ac3..4a9bb4f5 100644 --- a/core/src/main/scala/chisel3/internal/BiConnect.scala +++ b/core/src/main/scala/chisel3/internal/BiConnect.scala @@ -3,9 +3,11 @@ package chisel3.internal import chisel3._ +import chisel3.experimental.dataview.reify import chisel3.experimental.{Analog, BaseModule, attach} import chisel3.internal.Builder.pushCommand import chisel3.internal.firrtl.{Connect, DefInvalid} + import scala.language.experimental.macros import chisel3.internal.sourceinfo._ @@ -225,8 +227,10 @@ private[chisel3] object BiConnect { // This function checks if element-level connection operation allowed. // Then it either issues it or throws the appropriate exception. - def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, left: Element, right: Element, context_mod: RawModule): Unit = { + def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, _left: Element, _right: Element, context_mod: RawModule): Unit = { import BindingDirection.{Internal, Input, Output} // Using extensively so import these + val left = reify(_left) + val right = reify(_right) // If left or right have no location, assume in context module // This can occur if one of them is a literal, unbound will error previously val left_mod: BaseModule = left.topBinding.location.getOrElse(context_mod) diff --git a/core/src/main/scala/chisel3/internal/Binding.scala b/core/src/main/scala/chisel3/internal/Binding.scala index 300803ce..6f4ab4b0 100644 --- a/core/src/main/scala/chisel3/internal/Binding.scala +++ b/core/src/main/scala/chisel3/internal/Binding.scala @@ -120,6 +120,15 @@ case class MemTypeBinding[T <: Data](parent: MemBase[T]) extends Binding { // It is a source (RHS). It may only be connected/applied to sinks. case class DontCareBinding() extends UnconstrainedBinding +// Views currently only support 1:1 Element-level mappings +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 target Optional Data this Aggregate views if the view is total and the target is a Data + */ +private[chisel3] case class AggregateViewBinding(childMap: Map[Data, Element], target: Option[Data]) extends UnconstrainedBinding + + sealed trait LitBinding extends UnconstrainedBinding with ReadOnlyBinding // Literal binding attached to a element that is not part of a Bundle. case class ElementLitBinding(litArg: LitArg) extends LitBinding diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index fef12093..ddaedd2e 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -10,7 +10,8 @@ import chisel3.internal.firrtl._ import chisel3.internal.naming._ import _root_.firrtl.annotations.{CircuitName, ComponentName, IsMember, ModuleName, Named, ReferenceTarget} import _root_.firrtl.annotations.AnnotationUtils.validComponentName -import _root_.firrtl.AnnotationSeq +import _root_.firrtl.{AnnotationSeq, RenameMap} +import chisel3.experimental.dataview.{reify, reifySingleData} import chisel3.internal.Builder.Prefix import logger.LazyLogging @@ -215,32 +216,48 @@ private[chisel3] trait HasId extends InstanceId { private[chisel3] def getRef: Arg = _ref.get private[chisel3] def getOptionRef: Option[Arg] = _ref + private def localName(c: Component): String = _ref match { + case Some(arg) => arg fullName c + case None => computeName(None, None).get + } + + // Helper for reifying views if they map to a single Target + private[chisel3] def reifyTarget: Option[Data] = this match { + case d: Data => reifySingleData(d) // Only Data can be views + case bad => throwException(s"This shouldn't be possible - got $bad with ${_parent}") + } + + // Helper for reifying the parent of a view if the view maps to a single Target + private[chisel3] def reifyParent: BaseModule = reifyTarget.flatMap(_._parent).getOrElse(ViewParent) + // Implementation of public methods. def instanceName: String = _parent match { + case Some(ViewParent) => reifyTarget.map(_.instanceName).getOrElse(this.localName(ViewParent.fakeComponent)) case Some(p) => p._component match { - case Some(c) => _ref match { - case Some(arg) => arg fullName c - case None => computeName(None, None).get - } + case Some(c) => localName(c) case None => throwException("signalName/pathName should be called after circuit elaboration") } case None => throwException("this cannot happen") } def pathName: String = _parent match { case None => instanceName + case Some(ViewParent) => s"${reifyParent.pathName}.$instanceName" case Some(p) => s"${p.pathName}.$instanceName" } def parentPathName: String = _parent match { + case Some(ViewParent) => reifyParent.pathName case Some(p) => p.pathName case None => throwException(s"$instanceName doesn't have a parent") } def parentModName: String = _parent match { + case Some(ViewParent) => reifyParent.name case Some(p) => p.name case None => throwException(s"$instanceName doesn't have a parent") } // TODO Should this be public? protected def circuitName: String = _parent match { case None => instanceName + case Some(ViewParent) => reifyParent.circuitName case Some(p) => p.circuitName } @@ -288,8 +305,10 @@ private[chisel3] trait NamedComponent extends HasId { final def toAbsoluteTarget: ReferenceTarget = { val localTarget = toTarget + def makeTarget(p: BaseModule) = p.toAbsoluteTarget.ref(localTarget.ref).copy(component = localTarget.component) _parent match { - case Some(parent) => parent.toAbsoluteTarget.ref(localTarget.ref).copy(component = localTarget.component) + case Some(ViewParent) => makeTarget(reifyParent) + case Some(parent) => makeTarget(parent) case None => localTarget } } @@ -304,6 +323,11 @@ private[chisel3] class ChiselContext() { // Records the different prefixes which have been scoped at this point in time var prefixStack: Prefix = Nil + + // Views belong to a separate namespace (for renaming) + // The namespace outside of Builder context is useless, but it ensures that views can still be created + // and the resulting .toTarget is very clearly useless (_$$View$$_...) + val viewNamespace = Namespace.empty } private[chisel3] class DynamicContext(val annotationSeq: AnnotationSeq) { @@ -319,6 +343,9 @@ private[chisel3] class DynamicContext(val annotationSeq: AnnotationSeq) { */ val aspectModule: mutable.HashMap[BaseModule, BaseModule] = mutable.HashMap.empty[BaseModule, BaseModule] + // Views that do not correspond to a single ReferenceTarget and thus require renaming + val unnamedViews: ArrayBuffer[Data] = ArrayBuffer.empty + // Set by object Module.apply before calling class Module constructor // Used to distinguish between no Module() wrapping, multiple wrappings, and rewrapping var readyForModuleConstr: Boolean = false @@ -370,6 +397,9 @@ private[chisel3] object Builder extends LazyLogging { def annotationSeq: AnnotationSeq = dynamicContext.annotationSeq def namingStack: NamingStack = dynamicContext.namingStack + def unnamedViews: ArrayBuffer[Data] = dynamicContext.unnamedViews + def viewNamespace: Namespace = chiselContext.get.viewNamespace + // Puts a prefix string onto the prefix stack def pushPrefix(d: String): Unit = { val context = chiselContext.get() @@ -403,6 +433,7 @@ private[chisel3] object Builder extends LazyLogging { case PortBinding(mod) if Builder.currentModule.contains(mod) => data.seedOpt case PortBinding(mod) => map2(mod.seedOpt, data.seedOpt)(_ + "_" + _) case (_: LitBinding | _: DontCareBinding) => None + case _ => Some("view_") // TODO implement } id match { case d: Data => recData(d) @@ -646,8 +677,29 @@ private[chisel3] object Builder extends LazyLogging { } } + // Builds a RenameMap for all Views that do not correspond to a single Data + // These Data give a fake ReferenceTarget for .toTarget and .toReferenceTarget that the returned + // RenameMap can split into the constituent parts + private[chisel3] def makeViewRenameMap: RenameMap = { + val renames = RenameMap() + for (view <- unnamedViews) { + val localTarget = view.toTarget + val absTarget = view.toAbsoluteTarget + val elts = getRecursiveFields.lazily(view, "") + .collect { case (elt: Element, _) => elt } + for (elt <- elts) { + val targetOfView = reify(elt) + renames.record(localTarget, targetOfView.toTarget) + renames.record(absTarget, targetOfView.toAbsoluteTarget) + } + } + renames + } + private [chisel3] def build[T <: RawModule](f: => T, dynamicContext: DynamicContext): (Circuit, T) = { dynamicContextVar.withValue(Some(dynamicContext)) { + ViewParent // Must initialize the singleton in a Builder context or weird things can happen + // in tiny designs/testcases that never access anything in chisel3.internal checkScalaVersion() logger.warn("Elaborating design...") val mod = f @@ -655,7 +707,7 @@ private[chisel3] object Builder extends LazyLogging { errors.checkpoint(logger) logger.warn("Done elaborating.") - (Circuit(components.last.name, components.toSeq, annotations.toSeq), mod) + (Circuit(components.last.name, components.toSeq, annotations.toSeq, makeViewRenameMap), mod) } } initializeSingletons() diff --git a/core/src/main/scala/chisel3/internal/MonoConnect.scala b/core/src/main/scala/chisel3/internal/MonoConnect.scala index b979ebae..5cbab329 100644 --- a/core/src/main/scala/chisel3/internal/MonoConnect.scala +++ b/core/src/main/scala/chisel3/internal/MonoConnect.scala @@ -5,7 +5,9 @@ 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 scala.language.experimental.macros import chisel3.internal.sourceinfo.SourceInfo @@ -188,8 +190,10 @@ private[chisel3] object MonoConnect { // This function checks if element-level connection operation allowed. // Then it either issues it or throws the appropriate exception. - def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, sink: Element, source: Element, context_mod: RawModule): Unit = { + def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, _sink: Element, _source: Element, context_mod: RawModule): Unit = { import BindingDirection.{Internal, Input, Output} // Using extensively so import these + val sink = reify(_sink) + val source = reify(_source) // 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) diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index 5796522c..f8a3cf7f 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -8,7 +8,8 @@ import chisel3.internal._ import chisel3.internal.sourceinfo.SourceInfo import chisel3.experimental._ import _root_.firrtl.{ir => firrtlir} -import _root_.firrtl.PrimOps +import _root_.firrtl.{PrimOps, RenameMap} +import _root_.firrtl.annotations.Annotation import scala.collection.immutable.NumericRange import scala.math.BigDecimal.RoundingMode @@ -789,4 +790,6 @@ abstract class Component extends Arg { case class DefModule(id: RawModule, name: String, ports: Seq[Port], commands: Seq[Command]) extends Component case class DefBlackBox(id: BaseBlackBox, name: String, ports: Seq[Port], topDir: SpecifiedDirection, params: Map[String, Param]) extends Component -case class Circuit(name: String, components: Seq[Component], annotations: Seq[ChiselAnnotation] = Seq.empty) +case class Circuit(name: String, components: Seq[Component], annotations: Seq[ChiselAnnotation], renames: RenameMap) { + def firrtlAnnotations: Iterable[Annotation] = annotations.flatMap(_.toFirrtl.update(renames)) +} diff --git a/docs/src/cookbooks/cookbook.md b/docs/src/cookbooks/cookbook.md index 1b47ad14..f033c285 100644 --- a/docs/src/cookbooks/cookbook.md +++ b/docs/src/cookbooks/cookbook.md @@ -9,12 +9,13 @@ section: "chisel3" Please note that these examples make use of [Chisel's scala-style printing](../explanations/printing#scala-style). -* Converting Chisel Types to/from UInt +* Type Conversions * [How do I create a UInt from an instance of a Bundle?](#how-do-i-create-a-uint-from-an-instance-of-a-bundle) * [How do I create a Bundle from a UInt?](#how-do-i-create-a-bundle-from-a-uint) * [How can I tieoff a Bundle/Vec to 0?](#how-can-i-tieoff-a-bundlevec-to-0) * [How do I create a Vec of Bools from a UInt?](#how-do-i-create-a-vec-of-bools-from-a-uint) * [How do I create a UInt from a Vec of Bool?](#how-do-i-create-a-uint-from-a-vec-of-bool) + * [How do I connect a subset of Bundle fields?](#how-do-i-connect-a-subset-of-bundle-fields) * Vectors and Registers * [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) @@ -27,7 +28,7 @@ Please note that these examples make use of [Chisel's scala-style printing](../e * [How do I get Chisel to name the results of vector reads properly?](#how-do-i-get-chisel-to-name-the-results-of-vector-reads-properly) * [How can I dynamically set/parametrize the name of a module?](#how-can-i-dynamically-setparametrize-the-name-of-a-module) -## Converting Chisel Types to/from UInt +## Type Conversions ### How do I create a UInt from an instance of a Bundle? @@ -148,6 +149,10 @@ class Foo extends RawModule { } ``` +### How do I connect a subset of Bundle fields? + +See the [DataView cookbook](dataview#how-do-i-connect-a-subset-of-bundle-fields). + ## Vectors and Registers ### How do I create a Vector of Registers? diff --git a/docs/src/cookbooks/dataview.md b/docs/src/cookbooks/dataview.md new file mode 100644 index 00000000..ed969ca1 --- /dev/null +++ b/docs/src/cookbooks/dataview.md @@ -0,0 +1,179 @@ +--- +layout: docs +title: "DataView Cookbook" +section: "chisel3" +--- + +# DataView Cookbook + +* [How do I view a Data as a UInt or vice versa?](#how-do-i-view-a-data-as-a-uint-or-vice-versa) +* [How do I create a DataView for a Bundle has a type parameter?](#how-do-i-create-a-dataview-for-a-bundle-has-a-type-parameter) +* [How do I create a DataView for a Bundle with optional fields?](#how-do-i-create-a-dataview-for-a-bundle-with-optional-fields) +* [How do I connect a subset of Bundle fields?](#how-do-i-connect-a-subset-of-bundle-fields) + * [How do I view a Bundle as a parent type (superclass)?](#how-do-i-view-a-bundle-as-a-parent-type-superclass) + * [How do I view a Bundle as a parent type when the parent type is abstract (like a trait)?](#how-do-i-view-a-bundle-as-a-parent-type-when-the-parent-type-is-abstract-like-a-trait) + +## How do I view a Data as a UInt or vice versa? + +Subword viewing (using concatenations or bit extractions in `DataViews`) is not yet supported. +We intend to implement this in the future, but for the time being, use regular casts +(`.asUInt` and `.asTypeOf`). + +## How do I create a DataView for a Bundle has a type parameter? + +Instead of using a `val`, use a `def` which can have type parameters: + +```scala mdoc:silent:reset +import chisel3._ +import chisel3.experimental.dataview._ + +class Foo[T <: Data](val foo: T) extends Bundle +class Bar[T <: Data](val bar: T) extends Bundle + +object Foo { + implicit def view[T <: Data]: DataView[Foo[T], Bar[T]] = { + DataView(f => new Bar(f.foo.cloneType), _.foo -> _.bar) + // .cloneType is necessary because the f passed to this function will be bound hardware + } +} +``` + +```scala mdoc:invisible +// Make sure this works during elaboration, not part of doc +class MyModule extends RawModule { + val in = IO(Input(new Foo(UInt(8.W)))) + val out = IO(Output(new Bar(UInt(8.W)))) + out := in.viewAs[Bar[UInt]] +} +chisel3.stage.ChiselStage.emitVerilog(new MyModule) +``` +If you think about type parameterized classes as really being a family of different classes +(one for each type parameter), you can think about the `implicit def` as a generator of `DataViews` +for each type parameter. + +## How do I create a DataView for a Bundle with optional fields? + +Instead of using the default `DataView` apply method, use `DataView.mapping`: + +```scala mdoc:silent:reset +import chisel3._ +import chisel3.experimental.dataview._ + +class Foo(val w: Option[Int]) extends Bundle { + val foo = UInt(8.W) + val opt = w.map(x => UInt(x.W)) +} +class Bar(val w: Option[Int]) extends Bundle { + val bar = UInt(8.W) + val opt = w.map(x => UInt(x.W)) +} + +object Foo { + implicit val view: DataView[Foo, Bar] = + DataView.mapping( + // First argument is always the function to make the view from the target + f => new Bar(f.w), + // Now instead of a varargs of tuples of individual mappings, we have a single function that + // takes a target and a view and returns an Iterable of tuple + (f, b) => List(f.foo -> b.bar) ++ f.opt.map(_ -> b.opt.get) + // ^ Note that we can append options since they are Iterable! + + ) +} +``` + +```scala mdoc:invisible +// Make sure this works during elaboration, not part of doc +class MyModule extends RawModule { + val in = IO(Input(new Foo(Some(8)))) + val out = IO(Output(new Bar(Some(8)))) + out := in.viewAs[Bar] +} +chisel3.stage.ChiselStage.emitVerilog(new MyModule) +``` + +## How do I connect a subset of Bundle fields? + +Chisel 3 requires types to match exactly for connections. +DataView provides a mechanism for "viewing" one `Bundle` object as if it were the type of another, +which allows them to be connected. + +### How do I view a Bundle as a parent type (superclass)? + +For viewing `Bundles` as the type of the parent, it is as simple as using `viewAsSupertype` and providing a +template object of the parent type: + +```scala mdoc:silent:reset +import chisel3._ +import chisel3.experimental.dataview._ + +class Foo extends Bundle { + val foo = UInt(8.W) +} +class Bar extends Foo { + val bar = UInt(8.W) +} +class MyModule extends Module { + val foo = IO(Input(new Foo)) + val bar = IO(Output(new Bar)) + bar.viewAsSupertype(new Foo) := foo // bar.foo := foo.foo + bar.bar := 123.U // all fields need to be connected +} +``` +```scala mdoc:verilog +chisel3.stage.ChiselStage.emitVerilog(new MyModule) +``` + +### How do I view a Bundle as a parent type when the parent type is abstract (like a trait)? + +Given the following `Bundles` that share a common `trait`: + +```scala mdoc:silent:reset +import chisel3._ +import chisel3.experimental.dataview._ + +trait Super extends Bundle { + def bitwidth: Int + val a = UInt(bitwidth.W) +} +class Foo(val bitwidth: Int) extends Super { + val foo = UInt(8.W) +} +class Bar(val bitwidth: Int) extends Super { + val bar = UInt(8.W) +} +``` + +`Foo` and `Bar` cannot be connected directly, but they could be connected by viewing them both as if +they were instances of their common supertype, `Super`. +A straightforward approach might run into an issue like the following: + +```scala mdoc:fail +class MyModule extends Module { + val foo = IO(Input(new Foo(8))) + val bar = IO(Output(new Bar(8))) + bar.viewAsSupertype(new Super) := foo.viewAsSupertype(new Super) +} +``` + +The problem is that `viewAs` requires an object to use as a type template (so that it can be cloned), +but `traits` are abstract and cannot be instantiated. +The solution is to create an instance of an _anonymous class_ and use that object as the argument to `viewAs`. +We can do this like so: + +```scala mdoc:silent +class MyModule extends Module { + val foo = IO(Input(new Foo(8))) + val bar = IO(Output(new Bar(8))) + val tpe = new Super { // Adding curly braces creates an anonymous class + def bitwidth = 8 // We must implement any abstract methods + } + bar.viewAsSupertype(tpe) := foo.viewAsSupertype(tpe) +} +``` +By adding curly braces after the name of the trait, we're telling Scala to create a new concrete +subclass of the trait, and create an instance of it. +As indicated in the comment, abstract methods must still be implemented. +This is the same that happens when one writes `new Bundle {}`, +the curly braces create a new concrete subclass; however, because `Bundle` has no abstract methods, +the contents of the body can be empty. diff --git a/docs/src/explanations/dataview.md b/docs/src/explanations/dataview.md new file mode 100644 index 00000000..2f229bfc --- /dev/null +++ b/docs/src/explanations/dataview.md @@ -0,0 +1,520 @@ +--- +layout: docs +title: "DataView" +section: "chisel3" +--- + +# DataView + +_New in Chisel 3.5_ + +```scala mdoc:invisible +import chisel3._ +import chisel3.stage.ChiselStage.emitVerilog +``` + +## Introduction + +DataView is a mechanism for "viewing" Scala objects as a subtype of `chisel3.Data`. +Often, this is useful for viewing one subtype of `chisel3.Data`, as another. +One can think about a `DataView` as a mapping from a _Target_ type `T` to a _View_ type `V`. +This is similar to a cast (eg. `.asTypeOf`) with a few differences: +1. Views are _connectable_—connections to the view will occur on the target +2. Whereas casts are _structural_ (a reinterpretation of the underlying bits), a DataView is a customizable mapping +3. Views can be _partial_—not every field in the target must be included in the mapping + +## A Motivating Example (AXI4) + +[AXI4](https://en.wikipedia.org/wiki/Advanced_eXtensible_Interface) is a common interface in digital +design. +A typical Verilog peripheral using AXI4 will define a write channel as something like: +```verilog +module my_module( + // Write Channel + input AXI_AWVALID, + output AXI_AWREADY, + input [3:0] AXI_AWID, + input [19:0] AXI_AWADDR, + input [1:0] AXI_AWLEN, + input [1:0] AXI_AWSIZE, + // ... +); +``` + +This would correspond to the following Chisel Bundle: + +```scala mdoc +class VerilogAXIBundle(val addrWidth: Int) extends Bundle { + val AWVALID = Output(Bool()) + val AWREADY = Input(Bool()) + val AWID = Output(UInt(4.W)) + val AWADDR = Output(UInt(addrWidth.W)) + val AWLEN = Output(UInt(2.W)) + val AWSIZE = Output(UInt(2.W)) + // The rest of AW and other AXI channels here +} + +// Instantiated as +class my_module extends RawModule { + val AXI = IO(new VerilogAXIBundle(20)) +} +``` + +Expressing something that matches a standard Verilog interface is important when instantiating Verilog +modules in a Chisel design as `BlackBoxes`. +Generally though, Chisel developers prefer to use composition via utilities like `Decoupled` rather +than a flat handling of `ready` and `valid` as in the above. +A more "Chisel-y" implementation of this interface might look like: + +```scala mdoc +// Note that both the AW and AR channels look similar and could use the same Bundle definition +class AXIAddressChannel(val addrWidth: Int) extends Bundle { + val id = UInt(4.W) + val addr = UInt(addrWidth.W) + val len = UInt(2.W) + val size = UInt(2.W) + // ... +} +import chisel3.util.Decoupled +// We can compose the various AXI channels together +class AXIBundle(val addrWidth: Int) extends Bundle { + val aw = Decoupled(new AXIAddressChannel(addrWidth)) + // val ar = new AXIAddressChannel + // ... Other channels here ... +} +// Instantiated as +class MyModule extends RawModule { + val axi = IO(new AXIBundle(20)) +} +``` + +Of course, this would result in very different looking Verilog: + +```scala mdoc:verilog +emitVerilog(new MyModule { + override def desiredName = "MyModule" + axi := DontCare // Just to generate Verilog in this stub +}) +``` + +So how can we use our more structured types while maintaining expected Verilog interfaces? +Meet DataView: + +```scala mdoc +import chisel3.experimental.dataview._ + +// We recommend putting DataViews in a companion object of one of the involved types +object AXIBundle { + // Don't be afraid of the use of implicits, we will discuss this pattern in more detail later + implicit val axiView = DataView[VerilogAXIBundle, AXIBundle]( + // The first argument is a function constructing an object of View type (AXIBundle) + // from an object of the Target type (VerilogAXIBundle) + vab => new AXIBundle(vab.addrWidth), + // The remaining arguments are a mapping of the corresponding fields of the two types + _.AWVALID -> _.aw.valid, + _.AWREADY -> _.aw.ready, + _.AWID -> _.aw.bits.id, + _.AWADDR -> _.aw.bits.addr, + _.AWLEN -> _.aw.bits.len, + _.AWSIZE -> _.aw.bits.size, + // ... + ) +} +``` + +This `DataView` is a mapping between our flat, Verilog-style AXI Bundle to our more compositional, +Chisel-style AXI Bundle. +It allows us to define our ports to match the expected Verilog interface, while manipulating it as if +it were the more structured type: + +```scala mdoc +class AXIStub extends RawModule { + val AXI = IO(new VerilogAXIBundle(20)) + val view = AXI.viewAs[AXIBundle] + + // We can now manipulate `AXI` via `view` + view.aw.bits := 0.U.asTypeOf(new AXIAddressChannel(20)) // zero everything out by default + view.aw.valid := true.B + when (view.aw.ready) { + view.aw.bits.id := 5.U + view.aw.bits.addr := 1234.U + // We can still manipulate AXI as well + AXI.AWLEN := 1.U + } +} +``` + +This will generate Verilog that matches the standard naming convention: + +```scala mdoc:verilog +emitVerilog(new AXIStub) +``` + +Note that if both the _Target_ and the _View_ types are subtypes of `Data` (as they are in this example), +the `DataView` is _invertible_. +This means that we can easily create a `DataView[AXIBundle, VerilogAXIBundle]` from our existing +`DataView[VerilogAXIBundle, AXIBundle]`, all we need to do is provide a function to construct +a `VerilogAXIBundle` from an instance of an `AXIBundle`: + +```scala mdoc:silent +// Note that typically you should define these together (eg. inside object AXIBundle) +implicit val axiView2 = AXIBundle.axiView.invert(ab => new VerilogAXIBundle(ab.addrWidth)) +``` + +The following example shows this and illustrates another use case of `DataView`—connecting unrelated +types: + +```scala mdoc +class ConnectionExample extends RawModule { + val in = IO(new AXIBundle(20)) + val out = IO(Flipped(new VerilogAXIBundle(20))) + out.viewAs[AXIBundle] <> in +} +``` + +This results in the corresponding fields being connected in the emitted Verilog: + +```scala mdoc:verilog +emitVerilog(new ConnectionExample) +``` + +## Other Use Cases + +While the ability to map between `Bundle` types as in the AXI4 example is pretty compelling, +DataView has many other applications. +Importantly, because the _Target_ of the `DataView` need not be a `Data`, it provides a way to use +`non-Data` objects with APIs that require `Data`. + +### Tuples + +Perhaps the most helpful use of `DataView` for a non-`Data` type is viewing Scala tuples as `Bundles`. +For example, in Chisel prior to the introduction of `DataView`, one might try to `Mux` tuples and +see an error like the following: + + + +```scala mdoc:fail +class TupleExample extends RawModule { + val a, b, c, d = IO(Input(UInt(8.W))) + val cond = IO(Input(Bool())) + val x, y = IO(Output(UInt(8.W))) + (x, y) := Mux(cond, (a, b), (c, d)) +} +``` + +The issue, is that Chisel primitives like `Mux` and `:=` only operate on subtypes of `Data` and +Tuples (as members of the Scala standard library), are not subclasses of `Data`. +`DataView` provides a mechanism to _view_ a `Tuple` as if it were a `Data`: + + + +```scala mdoc:invisible +// ProductDataProduct +implicit val productDataProduct: DataProduct[Product] = new DataProduct[Product] { + def dataIterator(a: Product, path: String): Iterator[(Data, String)] = { + a.productIterator.zipWithIndex.collect { case (d: Data, i) => d -> s"$path._$i" } + } +} +``` + +```scala mdoc +// We need a type to represent the Tuple +class HWTuple2[A <: Data, B <: Data](val _1: A, val _2: B) extends Bundle + +// Provide DataView between Tuple and HWTuple +implicit def view[A <: Data, B <: Data]: DataView[(A, B), HWTuple2[A, B]] = + DataView(tup => new HWTuple2(tup._1.cloneType, tup._2.cloneType), + _._1 -> _._1, _._2 -> _._2) +``` + +Now, we can use `.viewAs` to view Tuples as if they were subtypes of `Data`: + +```scala mdoc +class TupleVerboseExample extends RawModule { + val a, b, c, d = IO(Input(UInt(8.W))) + val cond = IO(Input(Bool())) + val x, y = IO(Output(UInt(8.W))) + (x, y).viewAs[HWTuple2[UInt, UInt]] := Mux(cond, (a, b).viewAs[HWTuple2[UInt, UInt]], (c, d).viewAs[HWTuple2[UInt, UInt]]) +} +``` + +This is much more verbose than the original idea of just using the Tuples directly as if they were `Data`. +We can make this better by providing an implicit conversion that views a `Tuple` as a `HWTuple2`: + +```scala mdoc +implicit def tuple2hwtuple[A <: Data, B <: Data](tup: (A, B)): HWTuple2[A, B] = + tup.viewAs[HWTuple2[A, B]] +``` + +Now, the original code just works! + +```scala mdoc +class TupleExample extends RawModule { + val a, b, c, d = IO(Input(UInt(8.W))) + val cond = IO(Input(Bool())) + val x, y = IO(Output(UInt(8.W))) + (x, y) := Mux(cond, (a, b), (c, d)) +} +``` + +```scala mdoc:invisible +// Always emit Verilog to make sure it actually works +emitVerilog(new TupleExample) +``` + +Note that this example ignored `DataProduct` which is another required piece (see [the documentation +about it below](#dataproduct)). + +All of this is slated to be included the Chisel standard library. + +## Totality and PartialDataView + +A `DataView` is _total_ if all fields of the _Target_ type and all fields of the _View_ type are +included in the mapping. +Chisel will error if a field is accidentally left out from a `DataView`. +For example: + +```scala mdoc +class BundleA extends Bundle { + val foo = UInt(8.W) + val bar = UInt(8.W) +} +class BundleB extends Bundle { + val fizz = UInt(8.W) +} +``` + +```scala mdoc:crash +{ // Using an extra scope here to avoid a bug in mdoc (documentation generation) +// We forgot BundleA.foo in the mapping! +implicit val myView = DataView[BundleA, BundleB](_ => new BundleB, _.bar -> _.fizz) +class BadMapping extends Module { + val in = IO(Input(new BundleA)) + val out = IO(Output(new BundleB)) + out := in.viewAs[BundleB] +} +// We must run Chisel to see the error +emitVerilog(new BadMapping) +} +``` + +As that error suggests, if we *want* the view to be non-total, we can use a `PartialDataView`: + +```scala mdoc +// A PartialDataView does not have to be total for the Target +implicit val myView = PartialDataView[BundleA, BundleB](_ => new BundleB, _.bar -> _.fizz) +class PartialDataViewModule extends Module { + val in = IO(Input(new BundleA)) + val out = IO(Output(new BundleB)) + out := in.viewAs[BundleB] +} +``` + +```scala mdoc:verilog +emitVerilog(new PartialDataViewModule) +``` + +While `PartialDataViews` need not be total for the _Target_, both `PartialDataViews` and `DataViews` +must always be total for the _View_. +This has the consequence that `PartialDataViews` are **not** invertible in the same way as `DataViews`. + +For example: + +```scala mdoc:crash +{ // Using an extra scope here to avoid a bug in mdoc (documentation generation) +implicit val myView2 = myView.invert(_ => new BundleA) +class PartialDataViewModule2 extends Module { + val in = IO(Input(new BundleA)) + val out = IO(Output(new BundleB)) + // Using the inverted version of the mapping + out.viewAs[BundleA] := in +} +// We must run Chisel to see the error +emitVerilog(new PartialDataViewModule2) +} +``` + +As noted, the mapping must **always** be total for the `View`. + +## Advanced Details + +`DataView` takes advantage of features of Scala that may be new to many users of Chisel—in particular +[Type Classes](#type-classes). + +### Type Classes + +[Type classes](https://en.wikipedia.org/wiki/Type_class) are powerful language feature for writing +polymorphic code. +They are a common feature in "modern programming languages" like +Scala, +Swift (see [protocols](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html)), +and Rust (see [traits](https://doc.rust-lang.org/book/ch10-02-traits.html)). +Type classes may appear similar to inheritance in object-oriented programming but there are some +important differences: + +1. You can provide a type class for a type you don't own (eg. one defined in a 3rd party library, + the Scala standard library, or Chisel itself) +2. You can write a single type class for many types that do not have a sub-typing relationship +3. You can provide multiple different type classes for the same type + +For `DataView`, (1) is crucial because we want to be able to implement `DataViews` of built-in Scala +types like tuples and `Seqs`. Furthermore, `DataView` has two type parameters (the _Target_ and the +_View_ types) so inheritance does not really make sense—which type would `extend` `DataView`? + +In Scala 2, type classes are not a built-in language feature, but rather are implemented using implicits. +There are great resources out there for interested readers: +* [Basic Tutorial](https://scalac.io/blog/typeclasses-in-scala/) +* [Fantastic Explanation on StackOverflow](https://stackoverflow.com/a/5598107/2483329) + +Note that Scala 3 has added built-in syntax for type classes that does not apply to Chisel 3 which +currently only supports Scala 2. + +### Implicit Resolution + +Given that `DataView` is implemented using implicits, it is important to understand implicit +resolution. +Whenever the compiler sees an implicit argument is required, it first looks in _current scope_ +before looking in the _implicit scope_. + +1. Current scope + * Values defined in the current scope + * Explicit imports + * Wildcard imports +2. Implicit scope + * Companion object of a type + * Implicit scope of an argument's type + * Implicit scope of type parameters + +If at either stage, multiple implicits are found, then the static overloading rule is used to resolve +it. +Put simply, if one implicit applies to a more-specific type than the other, the more-specific one +will be selected. +If multiple implicits apply within a given stage, then the compiler throws an ambiguous implicit +resolution error. + + +This section draws heavily from [[1]](https://stackoverflow.com/a/5598107/2483329) and +[[2]](https://stackoverflow.com/a/5598107/2483329). +In particular, see [1] for examples. + +#### Implicit Resolution Example + +To help clarify a bit, let us consider how implicit resolution works for `DataView`. +Consider the definition of `viewAs`: + +```scala +def viewAs[V <: Data](implicit dataView: DataView[T, V]): V +``` + +Armed with the knowledge from the previous section, we know that whenever we call `.viewAs`, the +Scala compiler will first look for a `DataView[T, V]` in the current scope (defined in, or imported), +then it will look in the companion objects of `DataView`, `T`, and `V`. +This enables a fairly powerful pattern, namely that default or typical implementations of a `DataView` +should be defined in the companion object for one of the two types. +We can think about `DataViews` defined in this way as "low priority defaults". +They can then be overruled by a specific import if a given user ever wants different behavior. +For example: + +Given the following types: + +```scala mdoc +class Foo extends Bundle { + val a = UInt(8.W) + val b = UInt(8.W) +} +class Bar extends Bundle { + val c = UInt(8.W) + val d = UInt(8.W) +} +object Foo { + implicit val f2b = DataView[Foo, Bar](_ => new Bar, _.a -> _.c, _.b -> _.d) + implicit val b2f = f2b.invert(_ => new Foo) +} +``` + +This provides an implementation of `DataView` in the _implicit scope_ as a "default" mapping between +`Foo` and `Bar` (and it doesn't even require an import!): + +```scala mdoc +class FooToBar extends Module { + val foo = IO(Input(new Foo)) + val bar = IO(Output(new Bar)) + bar := foo.viewAs[Bar] +} +``` + +```scala mdoc:verilog +emitVerilog(new FooToBar) +``` + +However, it's possible that some user of `Foo` and `Bar` wants different behavior, +perhaps they would prefer more of "swizzling" behavior rather than a direct mapping: + +```scala mdoc +object Swizzle { + implicit val swizzle = DataView[Foo, Bar](_ => new Bar, _.a -> _.d, _.b -> _.c) +} +// Current scope always wins over implicit scope +import Swizzle._ +class FooToBarSwizzled extends Module { + val foo = IO(Input(new Foo)) + val bar = IO(Output(new Bar)) + bar := foo.viewAs[Bar] +} +``` + +```scala mdoc:verilog +emitVerilog(new FooToBarSwizzled) +``` + +### DataProduct + +`DataProduct` is a type class used by `DataView` to validate the correctness of a user-provided mapping. +In order for a type to be "viewable" (ie. the `Target` type of a `DataView`), it must have an +implementation of `DataProduct`. + +For example, say we have some non-Bundle type: +```scala mdoc +// Loosely based on chisel3.util.Counter +class MyCounter(val width: Int) { + /** Indicates if the Counter is incrementing this cycle */ + val active = WireDefault(false.B) + val value = RegInit(0.U(width.W)) + def inc(): Unit = { + active := true.B + value := value + 1.U + } + def reset(): Unit = { + value := 0.U + } +} +``` + +Say we want to view `MyCounter` as a `Valid[UInt]`: + +```scala mdoc:fail +import chisel3.util.Valid +implicit val counterView = DataView[MyCounter, Valid[UInt]](c => Valid(UInt(c.width.W)), _.value -> _.bits, _.active -> _.valid) +``` + +As you can see, this fails Scala compliation. +We need to provide an implementation of `DataProduct[MyCounter]` which provides Chisel a way to access +the objects of type `Data` within `MyCounter`: + +```scala mdoc:silent +import chisel3.util.Valid +implicit val counterProduct = new DataProduct[MyCounter] { + // The String part of the tuple is a String path to the object to help in debugging + def dataIterator(a: MyCounter, path: String): Iterator[(Data, String)] = + List(a.value -> s"$path.value", a.active -> s"$path.active").iterator +} +// Now this works +implicit val counterView = DataView[MyCounter, Valid[UInt]](c => Valid(UInt(c.width.W)), _.value -> _.bits, _.active -> _.valid) +``` + +Why is this useful? +This is how Chisel is able to check for totality as [described above](#totality-and-partialdataview). +In addition to checking if a user has left a field out of the mapping, it also allows Chisel to check +if the user has included a `Data` in the mapping that isn't actually a part of the _target_ nor the +_view_. + diff --git a/src/main/scala/chisel3/stage/phases/Convert.scala b/src/main/scala/chisel3/stage/phases/Convert.scala index bf42b58a..b5b01b8d 100644 --- a/src/main/scala/chisel3/stage/phases/Convert.scala +++ b/src/main/scala/chisel3/stage/phases/Convert.scala @@ -29,8 +29,7 @@ class Convert extends Phase { /* Convert all Chisel Annotations to FIRRTL Annotations */ a .circuit - .annotations - .map(_.toFirrtl) ++ + .firrtlAnnotations ++ a .circuit .annotations diff --git a/src/test/scala/chiselTests/ChiselSpec.scala b/src/test/scala/chiselTests/ChiselSpec.scala index e513189e..8e35273d 100644 --- a/src/test/scala/chiselTests/ChiselSpec.scala +++ b/src/test/scala/chiselTests/ChiselSpec.scala @@ -7,9 +7,11 @@ import chisel3.aop.Aspect import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage, NoRunFirrtlCompilerAnnotation, PrintFullStackTraceAnnotation} import chisel3.testers._ import firrtl.annotations.Annotation +import firrtl.ir.Circuit import firrtl.util.BackendCompilationUtilities import firrtl.{AnnotationSeq, EmittedVerilogCircuitAnnotation} import _root_.logger.Logger +import firrtl.stage.FirrtlCircuitAnnotation import org.scalacheck._ import org.scalatest._ import org.scalatest.flatspec.AnyFlatSpec @@ -87,6 +89,33 @@ trait ChiselRunners extends Assertions with BackendCompilationUtilities { case EmittedVerilogCircuitAnnotation(a) => a.value }.getOrElse(fail("No Verilog circuit was emitted by the FIRRTL compiler!")) } + + def elaborateAndGetModule[A <: RawModule](t: => A): A = { + var res: Any = null + ChiselStage.elaborate { + res = t + res.asInstanceOf[A] + } + res.asInstanceOf[A] + } + + /** Compiles a Chisel Module to FIRRTL + * NOTE: This uses the "test_run_dir" as the default directory for generated code. + * @param t the generator for the module + * @return The FIRRTL Circuit and Annotations _before_ FIRRTL compilation + */ + def getFirrtlAndAnnos(t: => RawModule): (Circuit, Seq[Annotation]) = { + val args = Array( + "--target-dir", + createTestDirectory(this.getClass.getSimpleName).toString, + "--no-run-firrtl" + ) + val annos = (new ChiselStage).execute(args, Seq(ChiselGeneratorAnnotation(() => t))) + val circuit = annos.collectFirst { + case FirrtlCircuitAnnotation(c) => c + }.getOrElse(fail("No FIRRTL Circuit found!!")) + (circuit, annos) + } } /** Spec base class for BDD-style testers. */ diff --git a/src/test/scala/chiselTests/experimental/DataView.scala b/src/test/scala/chiselTests/experimental/DataView.scala new file mode 100644 index 00000000..381cfeb5 --- /dev/null +++ b/src/test/scala/chiselTests/experimental/DataView.scala @@ -0,0 +1,542 @@ +// See LICENSE for license details. + +package chiselTests.experimental + +import chiselTests.ChiselFlatSpec +import chisel3._ +import chisel3.experimental.dataview._ +import chisel3.experimental.DataMirror.internal.chiselTypeClone +import chisel3.stage.ChiselStage +import chisel3.util.{Decoupled, DecoupledIO} + +object SimpleBundleDataView { + class BundleA(val w: Int) extends Bundle { + val foo = UInt(w.W) + } + class BundleB(val w: Int) extends Bundle { + val bar = UInt(w.W) + } + implicit val v1 = DataView[BundleA, BundleB](a => new BundleB(a.w), _.foo -> _.bar) + implicit val v2 = v1.invert(b => new BundleA(b.w)) +} + +object VecBundleDataView { + class MyBundle extends Bundle { + val foo = UInt(8.W) + val bar = UInt(8.W) + } + implicit val v1: DataView[MyBundle, Vec[UInt]] = DataView(_ => Vec(2, UInt(8.W)), _.foo -> _(1), _.bar -> _(0)) + implicit val v2 = v1.invert(_ => new MyBundle) +} + +// This should become part of Chisel in a later PR +object Tuple2DataProduct { + implicit def tuple2DataProduct[A : DataProduct, B : DataProduct] = new DataProduct[(A, B)] { + def dataIterator(tup: (A, B), path: String): Iterator[(Data, String)] = { + val dpa = implicitly[DataProduct[A]] + val dpb = implicitly[DataProduct[B]] + val (a, b) = tup + dpa.dataIterator(a, s"$path._1") ++ dpb.dataIterator(b, s"$path._2") + } + } +} + +// This should become part of Chisel in a later PR +object HWTuple { + import Tuple2DataProduct._ + + class HWTuple2[A <: Data, B <: Data](val _1: A, val _2: B) extends Bundle + + implicit def view[T1 : DataProduct, T2 : DataProduct, V1 <: Data, V2 <: Data]( + implicit v1: DataView[T1, V1], v2: DataView[T2, V2] + ): DataView[(T1, T2), HWTuple2[V1, V2]] = + DataView.mapping( + { case (a, b) => new HWTuple2(a.viewAs[V1].cloneType, b.viewAs[V2].cloneType)}, + { case ((a, b), hwt) => + Seq(a.viewAs[V1] -> hwt._1, + b.viewAs[V2] -> hwt._2) + } + ) + + implicit def tuple2hwtuple[T1 : DataProduct, T2 : DataProduct, V1 <: Data, V2 <: Data]( + tup: (T1, T2))(implicit v1: DataView[T1, V1], v2: DataView[T2, V2] + ): HWTuple2[V1, V2] = tup.viewAs[HWTuple2[V1, V2]] +} + +// This should become part of Chisel in a later PR +object SeqDataProduct { + // Should we special case Seq[Data]? + implicit def seqDataProduct[A : DataProduct]: DataProduct[Seq[A]] = new DataProduct[Seq[A]] { + def dataIterator(a: Seq[A], path: String): Iterator[(Data, String)] = { + val dpa = implicitly[DataProduct[A]] + a.iterator + .zipWithIndex + .flatMap { case (elt, idx) => + dpa.dataIterator(elt, s"$path[$idx]") + } + } + } +} + +object SeqToVec { + import SeqDataProduct._ + + // TODO this would need a better way to determine the prototype for the Vec + implicit def seqVec[A : DataProduct, B <: Data](implicit dv: DataView[A, B]): DataView[Seq[A], Vec[B]] = + DataView.mapping[Seq[A], Vec[B]]( + xs => Vec(xs.size, chiselTypeClone(xs.head.viewAs[B])), // xs.head is not correct in general + { case (s, v) => s.zip(v).map { case (a, b) => a.viewAs[B] -> b } } + ) + + implicit def seq2Vec[A : DataProduct, B <: Data](xs: Seq[A])(implicit dv: DataView[A, B]): Vec[B] = + xs.viewAs[Vec[B]] +} + +class DataViewSpec extends ChiselFlatSpec { + + behavior of "DataView" + + it should "support simple Bundle viewing" in { + import SimpleBundleDataView._ + class MyModule extends Module { + val in = IO(Input(new BundleA(8))) + val out = IO(Output(new BundleB(8))) + out := in.viewAs[BundleB] + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include("out.bar <= in.foo") + } + + it should "be a bidirectional mapping" in { + import SimpleBundleDataView._ + class MyModule extends Module { + val in = IO(Input(new BundleA(8))) + val out = IO(Output(new BundleB(8))) + out.viewAs[BundleA] := in + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include("out.bar <= in.foo") + } + + it should "handle viewing UInts as UInts" in { + class MyModule extends Module { + val in = IO(Input(UInt(8.W))) + val foo = IO(Output(UInt(8.W))) + val bar = IO(Output(UInt(8.W))) + foo := in.viewAs[UInt] + bar.viewAs[UInt] := in + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include("foo <= in") + chirrtl should include("bar <= in") + } + + it should "handle viewing Bundles as their same concrete type" in { + class MyBundle extends Bundle { + val foo = UInt(8.W) + } + class MyModule extends Module { + val in = IO(Input(new MyBundle)) + val fizz = IO(Output(new MyBundle)) + val buzz = IO(Output(new MyBundle)) + fizz := in.viewAs[MyBundle] + buzz.viewAs[MyBundle] := in + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include("fizz.foo <= in.foo") + chirrtl should include("buzz.foo <= in.foo") + } + + it should "handle viewing Vecs as their same concrete type" in { + class MyModule extends Module { + val in = IO(Input(Vec(1, UInt(8.W)))) + val fizz = IO(Output(Vec(1, UInt(8.W)))) + val buzz = IO(Output(Vec(1, UInt(8.W)))) + fizz := in.viewAs[Vec[UInt]] + 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]") + } + + it should "handle viewing Vecs as Bundles and vice versa" in { + import VecBundleDataView._ + class MyModule extends Module { + val in = IO(Input(new MyBundle)) + val out = IO(Output(Vec(2, UInt(8.W)))) + val out2 = IO(Output(Vec(2, UInt(8.W)))) + out := in.viewAs[Vec[UInt]] + out2.viewAs[MyBundle] := in + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include("out[0] <= in.bar") + chirrtl should include("out[1] <= in.foo") + chirrtl should include("out2[0] <= in.bar") + chirrtl should include("out2[1] <= in.foo") + } + + it should "work with bidirectional connections for nested types" in { + class FizzBuzz extends Bundle { + val fizz = UInt(8.W) + val buzz = UInt(8.W) + } + class FlatDecoupled extends Bundle { + val valid = Output(Bool()) + val ready = Input(Bool()) + val fizz = Output(UInt(8.W)) + val buzz = Output(UInt(8.W)) + } + implicit val view = DataView[FlatDecoupled, DecoupledIO[FizzBuzz]]( + _ => Decoupled(new FizzBuzz), + _.valid -> _.valid, + _.ready -> _.ready, + _.fizz -> _.bits.fizz, + _.buzz -> _.bits.buzz + ) + implicit val view2 = view.invert(_ => new FlatDecoupled) + class MyModule extends Module { + val enq = IO(Flipped(Decoupled(new FizzBuzz))) + val deq = IO(new FlatDecoupled) + val deq2 = IO(new FlatDecoupled) + deq <> enq.viewAs[FlatDecoupled] + deq2.viewAs[DecoupledIO[FizzBuzz]] <> enq + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include("deq.valid <= enq.valid") + chirrtl should include("enq.ready <= deq.ready") + chirrtl should include("deq.fizz <= enq.bits.fizz") + chirrtl should include("deq.buzz <= enq.bits.buzz") + chirrtl should include("deq2.valid <= enq.valid") + chirrtl should include("enq.ready <= deq2.ready") + chirrtl should include("deq2.fizz <= enq.bits.fizz") + chirrtl should include("deq2.buzz <= enq.bits.buzz") + } + + it should "support viewing a Bundle as a Parent Bundle type" in { + class Foo extends Bundle { + val foo = UInt(8.W) + } + class Bar extends Foo { + val bar = UInt(8.W) + } + class MyModule extends Module { + val fooIn = IO(Input(new Foo)) + val barOut = IO(Output(new Bar)) + barOut.viewAsSupertype(new Foo) := fooIn + + val barIn = IO(Input(new Bar)) + val fooOut = IO(Output(new Foo)) + fooOut := barIn.viewAsSupertype(new Foo) + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include("barOut.foo <= fooIn.foo") + chirrtl should include("fooOut.foo <= barIn.foo") + } + + it should "error if viewing a parent Bundle as a child Bundle type" in { + assertTypeError(""" + class Foo extends Bundle { + val foo = UInt(8.W) + } + class Bar extends Foo { + val bar = UInt(8.W) + } + class MyModule extends Module { + val barIn = IO(Input(new Bar)) + val fooOut = IO(Output(new Foo)) + fooOut.viewAs(new Bar) := barIn + } + """) + } + + it should "work in UInt operations" in { + class MyBundle extends Bundle { + val value = UInt(8.W) + } + class MyModule extends Module { + val a = IO(Input(UInt(8.W))) + val b = IO(Input(new MyBundle)) + val cond = IO(Input(Bool())) + val and, mux, bitsCat = IO(Output(UInt(8.W))) + // Chisel unconditionally emits a node, so name it at least + val x = a.viewAs[UInt] & b.viewAs[MyBundle].value + and.viewAs[UInt] := x + + val y = Mux(cond.viewAs[Bool], a.viewAs[UInt], b.value.viewAs[UInt]) + mux.viewAs[UInt] := y + + // TODO should we have a macro so that we don't need .apply? + val aBits = a.viewAs[UInt].apply(3, 0) + val bBits = b.viewAs[MyBundle].value(3, 0) + val abCat = aBits.viewAs[UInt] ## bBits.viewAs[UInt] + bitsCat := abCat + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + val expected = List( + "node x = and(a, b.value)", + "and <= x", + "node y = mux(cond, a, b.value)", + "mux <= y", + "node aBits = bits(a, 3, 0)", + "node bBits = bits(b.value, 3, 0)", + "node abCat = cat(aBits, bBits)", + "bitsCat <= abCat" + ) + for (line <- expected) { + chirrtl should include(line) + } + } + + it should "support .asUInt of Views" in { + import VecBundleDataView._ + class MyModule extends Module { + val barIn = IO(Input(new MyBundle)) + val fooOut = IO(Output(UInt())) + val cat = barIn.viewAs[Vec[UInt]].asUInt + fooOut := cat + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include ("node cat = cat(barIn.foo, barIn.bar)") + chirrtl should include ("fooOut <= cat") + } + + it should "be composable" in { + // Given DataView[A, B] and DataView[B, C], derive DataView[A, C] + class Foo(val foo: UInt) extends Bundle + class Bar(val bar: UInt) extends Bundle + class Fizz(val fizz: UInt) extends Bundle + + implicit val foo2bar = DataView[Foo, Bar](f => new Bar(chiselTypeClone(f.foo)), _.foo -> _.bar) + implicit val bar2fizz = DataView[Bar, Fizz](b => new Fizz(chiselTypeClone(b.bar)), _.bar -> _.fizz) + + implicit val foo2fizz: DataView[Foo, Fizz] = foo2bar.andThen(bar2fizz) + + class MyModule extends Module { + val a, b = IO(Input(new Foo(UInt(8.W)))) + val y, z = IO(Output(new Fizz(UInt(8.W)))) + y := a.viewAs[Fizz] + z := b.viewAs[Bar].viewAs[Fizz] + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include ("y.fizz <= a.foo") + chirrtl should include ("z.fizz <= b.foo") + } + + // This example should be turned into a built-in feature + it should "enable implementing \"HardwareTuple\"" in { + import HWTuple._ + + class MyModule extends Module { + val a, b, c, d = IO(Input(UInt(8.W))) + val sel = IO(Input(Bool())) + val y, z = IO(Output(UInt(8.W))) + (y, z) := Mux(sel, (a, b), (c, d)) + } + // Verilog instead of CHIRRTL because the optimizations make it much prettier + val verilog = ChiselStage.emitVerilog(new MyModule) + verilog should include ("assign y = sel ? a : c;") + verilog should include ("assign z = sel ? b : d;") + } + + it should "support nesting of tuples" in { + import Tuple2DataProduct._ + import HWTuple._ + + class MyModule extends Module { + val a, b, c, d = IO(Input(UInt(8.W))) + val w, x, y, z = IO(Output(UInt(8.W))) + ((w, x), (y, z)) := ((a, b), (c, d)) + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include ("w <= a") + chirrtl should include ("x <= b") + chirrtl should include ("y <= c") + chirrtl should include ("z <= d") + } + + // This example should be turned into a built-in feature + it should "enable viewing Seqs as Vecs" in { + import SeqToVec._ + + class MyModule extends Module { + val a, b, c = IO(Input(UInt(8.W))) + val x, y, z = IO(Output(UInt(8.W))) + Seq(x, y, z) := VecInit(a, b, c) + } + // Verilog instead of CHIRRTL because the optimizations make it much prettier + val verilog = ChiselStage.emitVerilog(new MyModule) + verilog should include ("assign x = a;") + verilog should include ("assign y = b;") + verilog should include ("assign z = c;") + } + + it should "support recursive composition of views" in { + import Tuple2DataProduct._ + import SeqDataProduct._ + import SeqToVec._ + import HWTuple._ + + class MyModule extends Module { + val a, b, c, d = IO(Input(UInt(8.W))) + val w, x, y, z = IO(Output(UInt(8.W))) + + // A little annoying that we need the type annotation on VecInit to get the implicit conversion to work + // Note that one can just use the Seq on the RHS so there is an alternative (may lack discoverability) + // We could also overload `VecInit` instead of relying on the implicit conversion + Seq((w, x), (y, z)) := VecInit[HWTuple2[UInt, UInt]]((a, b), (c, d)) + } + val verilog = ChiselStage.emitVerilog(new MyModule) + verilog should include ("assign w = a;") + verilog should include ("assign x = b;") + verilog should include ("assign y = c;") + verilog should include ("assign z = d;") + } + + it should "error if you try to dynamically index a Vec view" in { + import SeqDataProduct._ + import SeqToVec._ + import Tuple2DataProduct._ + import HWTuple._ + + class MyModule extends Module { + val inA, inB = IO(Input(UInt(8.W))) + val outA, outB = IO(Output(UInt(8.W))) + val idx = IO(Input(UInt(1.W))) + + val a, b, c, d = RegInit(0.U) + + // Dynamic indexing is more of a "generator" in Chisel3 than an individual node + val selected = Seq((a, b), (c, d)).apply(idx) + selected := (inA, inB) + (outA, outB) := selected + } + (the [InvalidViewException] thrownBy { + ChiselStage.emitChirrtl(new MyModule) + }).getMessage should include ("Dynamic indexing of Views is not yet supported") + } + + it should "error if the mapping is non-total in the view" in { + class MyBundle(val foo: UInt, val bar: UInt) extends Bundle + implicit val dv = DataView[UInt, MyBundle](_ => new MyBundle(UInt(), UInt()), _ -> _.bar) + class MyModule extends Module { + val tpe = new MyBundle(UInt(8.W), UInt(8.W)) + val in = IO(Input(UInt(8.W))) + val out = IO(Output(tpe)) + out := in.viewAs[MyBundle] + } + val err = the [InvalidViewException] thrownBy (ChiselStage.emitVerilog(new MyModule)) + err.toString should include ("View field '_.foo' is missing") + } + + it should "error if the mapping is non-total in the target" in { + import Tuple2DataProduct._ + implicit val dv = DataView[(UInt, UInt), UInt](_ => UInt(), _._1 -> _) + class MyModule extends Module { + val a, b = IO(Input(UInt(8.W))) + val out = IO(Output(UInt(8.W))) + out := (a, b).viewAs[UInt] + } + val err = the [InvalidViewException] thrownBy (ChiselStage.emitVerilog(new MyModule)) + err.toString should include ("Target field '_._2' is missing") + } + + it should "error if the mapping contains Data that are not part of the Target" in { + class BundleA extends Bundle { + val foo = UInt(8.W) + } + class BundleB extends Bundle { + val fizz = UInt(8.W) + val buzz = UInt(8.W) + } + implicit val dv = DataView[BundleA, BundleB](_ => new BundleB, _.foo -> _.fizz, (_, b) => (3.U, b.buzz)) + class MyModule extends Module { + val in = IO(Input(new BundleA)) + val out = IO(Output(new BundleB)) + out := in.viewAs[BundleB] + } + val err = the [InvalidViewException] thrownBy (ChiselStage.emitVerilog(new MyModule)) + err.toString should include ("View mapping must only contain Elements within the Target") + } + + it should "error if the mapping contains Data that are not part of the View" in { + class BundleA extends Bundle { + val foo = UInt(8.W) + } + class BundleB extends Bundle { + val fizz = UInt(8.W) + val buzz = UInt(8.W) + } + implicit val dv = DataView[BundleA, BundleB](_ => new BundleB, _.foo -> _.fizz, (_, b) => (3.U, b.buzz)) + implicit val dv2 = dv.invert(_ => new BundleA) + class MyModule extends Module { + val in = IO(Input(new BundleA)) + val out = IO(Output(new BundleB)) + out.viewAs[BundleA] := in + } + val err = the [InvalidViewException] thrownBy (ChiselStage.emitVerilog(new MyModule)) + err.toString should include ("View mapping must only contain Elements within the View") + } + + it should "error if a view has a width that does not match the target" in { + class BundleA extends Bundle { + val foo = UInt(8.W) + } + class BundleB extends Bundle { + val bar = UInt(4.W) + } + implicit val dv = DataView[BundleA, BundleB](_ => new BundleB, _.foo -> _.bar) + class MyModule extends Module { + val in = IO(Input(new BundleA)) + val out = IO(Output(new BundleB)) + out := in.viewAs[BundleB] + } + val err = the [InvalidViewException] thrownBy ChiselStage.emitChirrtl(new MyModule) + val expected = """View field _\.bar UInt<4> has width <4> that is incompatible with target value .+'s width <8>""".r + err.getMessage should fullyMatch regex expected + } + + it should "error if a view has a known width when the target width is unknown" in { + class BundleA extends Bundle { + val foo = UInt() + } + class BundleB extends Bundle { + val bar = UInt(4.W) + } + implicit val dv = DataView[BundleA, BundleB](_ => new BundleB, _.foo -> _.bar) + class MyModule extends Module { + val in = IO(Input(new BundleA)) + val out = IO(Output(new BundleB)) + out := in.viewAs[BundleB] + } + val err = the [InvalidViewException] thrownBy ChiselStage.emitChirrtl(new MyModule) + val expected = """View field _\.bar UInt<4> has width <4> that is incompatible with target value .+'s width """.r + err.getMessage should fullyMatch regex expected + } + + behavior of "PartialDataView" + + it should "still error if the mapping is non-total in the view" in { + class MyBundle(val foo: UInt, val bar: UInt) extends Bundle + implicit val dv = PartialDataView[UInt, MyBundle](_ => new MyBundle(UInt(), UInt()), _ -> _.bar) + class MyModule extends Module { + val in = IO(Input(UInt(8.W))) + val out = IO(Output(new MyBundle(UInt(8.W), UInt(8.W)))) + out := in.viewAs[MyBundle] + } + val err = the [InvalidViewException] thrownBy (ChiselStage.emitVerilog(new MyModule)) + err.toString should include ("View field '_.foo' is missing") + } + + it should "NOT error if the mapping is non-total in the target" in { + import Tuple2DataProduct._ + implicit val dv = PartialDataView[(UInt, UInt), UInt](_ => UInt(), _._2 -> _) + class MyModule extends Module { + val a, b = IO(Input(UInt(8.W))) + val out = IO(Output(UInt(8.W))) + out := (a, b).viewAs[UInt] + } + val verilog = ChiselStage.emitVerilog(new MyModule) + verilog should include ("assign out = b;") + } +} diff --git a/src/test/scala/chiselTests/experimental/DataViewIntegrationSpec.scala b/src/test/scala/chiselTests/experimental/DataViewIntegrationSpec.scala new file mode 100644 index 00000000..3f149f75 --- /dev/null +++ b/src/test/scala/chiselTests/experimental/DataViewIntegrationSpec.scala @@ -0,0 +1,57 @@ +// See LICENSE for license details. + +package chiselTests.experimental + +import chisel3._ +import chisel3.experimental.{BaseModule, ExtModule} +import chisel3.experimental.dataview._ +import chisel3.util.{Decoupled, DecoupledIO, Queue, QueueIO, log2Ceil} +import chiselTests.ChiselFlatSpec +import firrtl.transforms.DontTouchAnnotation + +// Let's put it all together! +object DataViewIntegrationSpec { + + class QueueIntf[T <: Data](gen: T, entries: Int) extends Bundle { + val ports = new QueueIO(gen, entries) + // Let's grab a reference to something internal too + // Output because can't have directioned and undirectioned stuff + val enq_ptr = Output(UInt(log2Ceil(entries).W)) + } + + // It's not clear if a view of a Module ever _can_ be total since internal nodes are part of the Module + implicit def queueView[T <: Data] = PartialDataView[Queue[T], QueueIntf[T]]( + q => new QueueIntf(q.gen, q.entries), + _.io -> _.ports, + // Some token internal signal + _.enq_ptr.value -> _.enq_ptr + ) + + object MyQueue { + def apply[T <: Data](enq: DecoupledIO[T], n: Int): QueueIntf[T] = { + val queue = Module(new Queue[T](enq.bits.cloneType, n)) + val view = queue.viewAs[QueueIntf[T]] + view.ports.enq <> enq + view + } + } + + class MyModule extends Module { + val enq = IO(Flipped(Decoupled(UInt(8.W)))) + val deq = IO(Decoupled(UInt(8.W))) + + val queue = MyQueue(enq, 4) + deq <> queue.ports.deq + dontTouch(queue.enq_ptr) + } +} + +class DataViewIntegrationSpec extends ChiselFlatSpec { + import DataViewIntegrationSpec.MyModule + + "Users" should "be able to view and annotate Modules" in { + val (_, annos) = getFirrtlAndAnnos(new MyModule) + val ts = annos.collect { case DontTouchAnnotation(t) => t.serialize } + ts should equal (Seq("~MyModule|Queue>enq_ptr_value")) + } +} diff --git a/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala b/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala new file mode 100644 index 00000000..41636da7 --- /dev/null +++ b/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala @@ -0,0 +1,169 @@ +// See LICENSE for license details. + +package chiselTests.experimental + +import chisel3._ +import chisel3.experimental.dataview._ +import chisel3.experimental.{ChiselAnnotation, annotate} +import chisel3.stage.ChiselStage +import chiselTests.ChiselFlatSpec + +object DataViewTargetSpec { + import firrtl.annotations._ + private case class DummyAnno(target: ReferenceTarget, id: Int) extends SingleTargetAnnotation[ReferenceTarget] { + override def duplicate(n: ReferenceTarget) = this.copy(target = n) + } + private def mark(d: Data, id: Int) = annotate(new ChiselAnnotation { + override def toFirrtl: Annotation = DummyAnno(d.toTarget, id) + }) + private def markAbs(d: Data, id: Int) = annotate(new ChiselAnnotation { + override def toFirrtl: Annotation = DummyAnno(d.toAbsoluteTarget, id) + }) +} + +class DataViewTargetSpec extends ChiselFlatSpec { + import DataViewTargetSpec._ + private val checks: Seq[Data => String] = Seq( + _.toTarget.toString, + _.toAbsoluteTarget.toString, + _.instanceName, + _.pathName, + _.parentPathName, + _.parentModName, + ) + + // Check helpers + private def checkAll(impl: Data, refs: String*): Unit = { + refs.size should be (checks.size) + for ((check, value) <- checks.zip(refs)) { + check(impl) should be (value) + } + } + private def checkSameAs(impl: Data, refs: Data*): Unit = + for (ref <- refs) { + checkAll(impl, checks.map(_(ref)):_*) + } + + behavior of "DataView Naming" + + it should "support views of Elements" in { + class MyChild extends Module { + val out = IO(Output(UInt(8.W))) + val insideView = out.viewAs[UInt] + out := 0.U + } + class MyParent extends Module { + val out = IO(Output(UInt(8.W))) + val inst = Module(new MyChild) + out := inst.out + } + val m = elaborateAndGetModule(new MyParent) + val outsideView = m.inst.out.viewAs[UInt] + checkSameAs(m.inst.out, m.inst.insideView, outsideView) + } + + it should "support 1:1 mappings of Aggregates and their children" in { + class MyBundle extends Bundle { + val foo = UInt(8.W) + val bars = Vec(2, UInt(8.W)) + } + implicit val dv = DataView[MyBundle, Vec[UInt]](_ => Vec(3, UInt(8.W)), _.foo -> _(0), _.bars(0) -> _(1), _.bars(1) -> _(2)) + class MyChild extends Module { + val out = IO(Output(new MyBundle)) + val outView = out.viewAs[Vec[UInt]] // Note different type + val outFooView = out.foo.viewAs[UInt] + val outBarsView = out.bars.viewAs[Vec[UInt]] + val outBars0View = out.bars(0).viewAs[UInt] + out := 0.U.asTypeOf(new MyBundle) + } + class MyParent extends Module { + val out = IO(Output(new MyBundle)) + val inst = Module(new MyChild) + out := inst.out + } + val m = elaborateAndGetModule(new MyParent) + val outView = m.inst.out.viewAs[Vec[UInt]]// Note different type + val outFooView = m.inst.out.foo.viewAs[UInt] + val outBarsView = m.inst.out.bars.viewAs[Vec[UInt]] + val outBars0View = m.inst.out.bars(0).viewAs[UInt] + + checkSameAs(m.inst.out, m.inst.outView, outView) + checkSameAs(m.inst.out.foo, m.inst.outFooView, m.inst.outView(0), outFooView, outView(0)) + checkSameAs(m.inst.out.bars, m.inst.outBarsView, outBarsView) + checkSameAs(m.inst.out.bars(0), m.inst.outBars0View, outBars0View, m.inst.outView(1), outView(1), + m.inst.outBarsView(0), outBarsView(0)) + } + + // Ideally this would work 1:1 but that requires changing the binding + it should "support annotation renaming of Aggregate children of Aggregate views" in { + class MyBundle extends Bundle { + val foo = Vec(2, UInt(8.W)) + } + class MyChild extends Module { + val out = IO(Output(new MyBundle)) + val outView = out.viewAs[MyBundle] + mark(out.foo, 0) + mark(outView.foo, 1) + markAbs(out.foo, 2) + markAbs(outView, 3) + out := 0.U.asTypeOf(new MyBundle) + } + class MyParent extends Module { + val out = IO(Output(new MyBundle)) + val inst = Module(new MyChild) + out := inst.out + } + val (_, annos) = getFirrtlAndAnnos(new MyParent) + 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]", + 2 -> "~MyParent|MyParent/inst:MyChild>out.foo", + 3 -> "~MyParent|MyParent/inst:MyChild>out" + ) + pairs should equal (expected) + } + + it should "support annotating views that cannot be mapped to a single ReferenceTarget" in { + import HWTuple._ + class MyBundle extends Bundle { + val a, b = Input(UInt(8.W)) + val c, d = Output(UInt(8.W)) + } + // Note that each use of a Tuple as Data causes an implicit conversion creating a View + class MyChild extends Module { + val io = IO(new MyBundle) + (io.c, io.d) := (io.a, io.b) + // The type annotations create the views via the implicit conversion + val view1: Data = (io.a, io.b) + val view2: Data = (io.c, io.d) + mark(view1, 0) + mark(view2, 1) + markAbs(view1, 2) + markAbs(view2, 3) + mark((io.b, io.d), 4) // Mix it up for fun + } + class MyParent extends Module { + val io = IO(new MyBundle) + val inst = Module(new MyChild) + io <> inst.io + } + val (_, annos) = getFirrtlAndAnnos(new MyParent) + val pairs = annos.collect { case DummyAnno(t, idx) => (idx, t.toString) }.sorted + val expected = Seq( + 0 -> "~MyParent|MyChild>io.a", + 0 -> "~MyParent|MyChild>io.b", + 1 -> "~MyParent|MyChild>io.c", + 1 -> "~MyParent|MyChild>io.d", + 2 -> "~MyParent|MyParent/inst:MyChild>io.a", + 2 -> "~MyParent|MyParent/inst:MyChild>io.b", + 3 -> "~MyParent|MyParent/inst:MyChild>io.c", + 3 -> "~MyParent|MyParent/inst:MyChild>io.d", + 4 -> "~MyParent|MyChild>io.b", + 4 -> "~MyParent|MyChild>io.d", + ) + pairs should equal (expected) + } +} diff --git a/src/test/scala/chiselTests/experimental/ModuleDataProductSpec.scala b/src/test/scala/chiselTests/experimental/ModuleDataProductSpec.scala new file mode 100644 index 00000000..78986517 --- /dev/null +++ b/src/test/scala/chiselTests/experimental/ModuleDataProductSpec.scala @@ -0,0 +1,91 @@ +// See LICENSE for license details. + +package chiselTests.experimental + +import chisel3._ +import chisel3.experimental.{BaseModule, ExtModule} +import chisel3.experimental.dataview.DataProduct +import chiselTests.ChiselFlatSpec + +object ModuleDataProductSpec { + class MyBundle extends Bundle { + val foo = UInt(8.W) + val bar = UInt(8.W) + } + trait MyIntf extends BaseModule { + val in = IO(Input(new MyBundle)) + val out = IO(Output(new MyBundle)) + } + class Passthrough extends RawModule { + val in = IO(Input(UInt(8.W))) + val out = IO(Output(UInt(8.W))) + out := in + } + class MyUserModule extends Module with MyIntf { + val inst = Module(new Passthrough) + inst.in := in.foo + val r = RegNext(in) + out := r + } + + class MyExtModule extends ExtModule with MyIntf + class MyExtModuleWrapper extends RawModule with MyIntf { + val inst = Module(new MyExtModule) + inst.in := in + out := inst.out + } +} + +class ModuleDataProductSpec extends ChiselFlatSpec { + import ModuleDataProductSpec._ + + behavior of "DataProduct" + + it should "work for UserModules (recursively)" in { + val m = elaborateAndGetModule(new MyUserModule) + val expected = Seq( + m.clock -> "m.clock", + m.reset -> "m.reset", + m.in -> "m.in", + m.in.foo -> "m.in.foo", + m.in.bar -> "m.in.bar", + m.out -> "m.out", + m.out.foo -> "m.out.foo", + m.out.bar -> "m.out.bar", + m.r -> "m.r", + m.r.foo -> "m.r.foo", + m.r.bar -> "m.r.bar", + m.inst.in -> "m.inst.in", + m.inst.out -> "m.inst.out" + ) + + val impl = implicitly[DataProduct[MyUserModule]] + val set = impl.dataSet(m) + for ((d, _) <- expected) { + set(d) should be (true) + } + val it = impl.dataIterator(m, "m") + it.toList should contain theSameElementsAs (expected) + } + + it should "work for (wrapped) ExtModules" in { + val m = elaborateAndGetModule(new MyExtModuleWrapper).inst + val expected = Seq( + m.in -> "m.in", + m.in.foo -> "m.in.foo", + m.in.bar -> "m.in.bar", + m.out -> "m.out", + m.out.foo -> "m.out.foo", + m.out.bar -> "m.out.bar" + ) + + val impl = implicitly[DataProduct[MyExtModule]] + val set = impl.dataSet(m) + for ((d, _) <- expected) { + set(d) should be (true) + } + val it = impl.dataIterator(m, "m") + it.toList should contain theSameElementsAs (expected) + } + +} diff --git a/src/test/scala/chiselTests/stage/ChiselOptionsViewSpec.scala b/src/test/scala/chiselTests/stage/ChiselOptionsViewSpec.scala index 35e354a6..99c0f7c0 100644 --- a/src/test/scala/chiselTests/stage/ChiselOptionsViewSpec.scala +++ b/src/test/scala/chiselTests/stage/ChiselOptionsViewSpec.scala @@ -4,6 +4,7 @@ package chiselTests.stage import firrtl.options.Viewer.view +import firrtl.RenameMap import chisel3.stage._ import chisel3.internal.firrtl.Circuit @@ -15,7 +16,7 @@ class ChiselOptionsViewSpec extends AnyFlatSpec with Matchers { behavior of ChiselOptionsView.getClass.getName it should "construct a view from an AnnotationSeq" in { - val bar = Circuit("bar", Seq.empty, Seq.empty) + val bar = Circuit("bar", Seq.empty, Seq.empty, RenameMap()) val annotations = Seq( NoRunFirrtlCompilerAnnotation, PrintFullStackTraceAnnotation, -- cgit v1.2.3 From d3f10eddd25a9b1b211464a43635683c783e4bbe Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Mon, 16 Aug 2021 18:30:53 +0200 Subject: Update sbt-scalafix to 0.9.30 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 1eca3137..b534ec90 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -25,4 +25,4 @@ addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.9.2") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") // From FIRRTL for building from source -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.29") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.30") -- cgit v1.2.3 From ed894c61474c8bc73761a6c360ef9d14505d853b Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Wed, 18 Aug 2021 03:44:17 +0800 Subject: remove DefRegInit, change DefReg API with option definition. (#1944) * remove DefRegInit, change DefReg API with option defination. * add error message * use Option[RegInitIR]. Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- core/src/main/scala/chisel3/Reg.scala | 4 ++-- core/src/main/scala/chisel3/internal/firrtl/Converter.scala | 4 ++-- core/src/main/scala/chisel3/internal/firrtl/IR.scala | 4 ++-- src/main/scala/chisel3/aop/Select.scala | 1 - src/main/scala/chisel3/internal/firrtl/Emitter.scala | 3 +-- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/core/src/main/scala/chisel3/Reg.scala b/core/src/main/scala/chisel3/Reg.scala index bd9e5311..9cdbf6a1 100644 --- a/core/src/main/scala/chisel3/Reg.scala +++ b/core/src/main/scala/chisel3/Reg.scala @@ -42,7 +42,7 @@ object Reg { val clock = Node(Builder.forcedClock) reg.bind(RegBinding(Builder.forcedUserModule, Builder.currentWhen())) - pushCommand(DefReg(sourceInfo, reg, clock)) + pushCommand(DefReg(sourceInfo, reg, clock, None)) reg } @@ -176,7 +176,7 @@ object RegInit { reg.bind(RegBinding(Builder.forcedUserModule, Builder.currentWhen())) requireIsHardware(init, "reg initializer") - pushCommand(DefRegInit(sourceInfo, reg, clock.ref, reset.ref, init.ref)) + pushCommand(DefReg(sourceInfo, reg, clock.ref, Some(RegInitIR(reset.ref, init.ref)))) reg } diff --git a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala index 8efb2abc..e8fb197c 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -117,10 +117,10 @@ private[chisel3] object Converter { Some(fir.DefNode(convert(e.sourceInfo), e.name, expr)) case e @ DefWire(info, id) => Some(fir.DefWire(convert(info), e.name, extractType(id, info))) - case e @ DefReg(info, id, clock) => + case e @ DefReg(info, id, clock, None) => Some(fir.DefRegister(convert(info), e.name, extractType(id, info), convert(clock, ctx, info), firrtl.Utils.zero, convert(getRef(id, info), ctx, info))) - case e @ DefRegInit(info, id, clock, reset, init) => + case e @ DefReg(info, id, clock, Some(RegInitIR(reset, init))) => Some(fir.DefRegister(convert(info), e.name, extractType(id, info), convert(clock, ctx, info), convert(reset, ctx, info), convert(init, ctx, info))) case e @ DefMemory(info, id, t, size) => diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index f8a3cf7f..a45ae3c2 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -758,8 +758,8 @@ abstract class Definition extends Command { case class DefPrim[T <: Data](sourceInfo: SourceInfo, id: T, op: PrimOp, args: Arg*) extends Definition case class DefInvalid(sourceInfo: SourceInfo, arg: Arg) extends Command case class DefWire(sourceInfo: SourceInfo, id: Data) extends Definition -case class DefReg(sourceInfo: SourceInfo, id: Data, clock: Arg) extends Definition -case class DefRegInit(sourceInfo: SourceInfo, id: Data, clock: Arg, reset: Arg, init: Arg) extends Definition +case class RegInitIR(reset: Arg, init: Arg) +case class DefReg(sourceInfo: SourceInfo, id: Data, clock: Arg, regInit: Option[RegInitIR]) extends Definition case class DefMemory(sourceInfo: SourceInfo, id: HasId, t: Data, size: BigInt) extends Definition case class DefSeqMemory(sourceInfo: SourceInfo, id: HasId, t: Data, size: BigInt, readUnderWrite: fir.ReadUnderWrite.Value) extends Definition case class DefMemPort[T <: Data](sourceInfo: SourceInfo, id: T, source: Node, dir: MemPortDirection, index: Arg, clock: Arg) extends Definition diff --git a/src/main/scala/chisel3/aop/Select.scala b/src/main/scala/chisel3/aop/Select.scala index 078422bb..81b4cdab 100644 --- a/src/main/scala/chisel3/aop/Select.scala +++ b/src/main/scala/chisel3/aop/Select.scala @@ -101,7 +101,6 @@ object Select { check(module) module._component.get.asInstanceOf[DefModule].commands.collect { case r: DefReg => r.id - case r: DefRegInit => r.id } } diff --git a/src/main/scala/chisel3/internal/firrtl/Emitter.scala b/src/main/scala/chisel3/internal/firrtl/Emitter.scala index 47849d91..daa83db0 100644 --- a/src/main/scala/chisel3/internal/firrtl/Emitter.scala +++ b/src/main/scala/chisel3/internal/firrtl/Emitter.scala @@ -66,8 +66,7 @@ private class Emitter(circuit: Circuit) { val firrtlLine = e match { case e: DefPrim[_] => s"node ${e.name} = ${e.op.name}(${e.args.map(_.fullName(ctx)).mkString(", ")})" case e: DefWire => s"wire ${e.name} : ${emitType(e.id)}" - case e: DefReg => s"reg ${e.name} : ${emitType(e.id)}, ${e.clock.fullName(ctx)}" - case e: DefRegInit => s"reg ${e.name} : ${emitType(e.id)}, ${e.clock.fullName(ctx)} with : (reset => (${e.reset.fullName(ctx)}, ${e.init.fullName(ctx)}))" + case e: DefReg => s"reg ${e.name} : ${emitType(e.id)}, ${e.clock.fullName(ctx)}${ if (e.regInit.isDefined) "with : (reset => (${e.reset.fullName(ctx)}, ${e.init.fullName(ctx)}))" else ""}" case e: DefMemory => s"cmem ${e.name} : ${emitType(e.t)}[${e.size}]" case e: DefSeqMemory => s"smem ${e.name} : ${emitType(e.t)}[${e.size}], ${e.readUnderWrite}" case e: DefMemPort[_] => s"${e.dir} mport ${e.name} = ${e.source.fullName(ctx)}[${e.index.fullName(ctx)}], ${e.clock.fullName(ctx)}" -- cgit v1.2.3 From e14bcb145860207a825f780e3d0984e869f605c9 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 17 Aug 2021 14:24:20 -0700 Subject: [docs] Add example of stripping directions from type (#2074) * [docs] Add example of stripping directions from type * Apply suggestions from code review Co-authored-by: Megan Wachs * Improve := comment Co-authored-by: Megan Wachs Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- docs/src/cookbooks/cookbook.md | 54 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/docs/src/cookbooks/cookbook.md b/docs/src/cookbooks/cookbook.md index f033c285..cff7a5b2 100644 --- a/docs/src/cookbooks/cookbook.md +++ b/docs/src/cookbooks/cookbook.md @@ -27,6 +27,8 @@ Please note that these examples make use of [Chisel's scala-style printing](../e * [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) * [How do I get Chisel to name the results of vector reads properly?](#how-do-i-get-chisel-to-name-the-results-of-vector-reads-properly) * [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 @@ -462,3 +464,55 @@ ChiselStage.emitVerilog(new Salt) ```scala mdoc:verilog ChiselStage.emitVerilog(new Salt) ``` + +## Directionality + +### How do I strip directions from a bidirectional Bundle (or other Data)? + +Given a bidirectional port like a `Decoupled`, you will get an error if you try to connect it directly +to a register: + +```scala mdoc:silent +import chisel3.util.Decoupled +class BadRegConnect extends Module { + val io = IO(new Bundle { + val enq = Decoupled(UInt(8.W)) + }) + + val monitor = Reg(chiselTypeOf(io.enq)) + monitor := io.enq +} +``` + +```scala mdoc:crash +ChiselStage.emitVerilog(new BadRegConnect) +``` + +While there is no construct to "strip direction" in Chisel3, wrapping a type in `Output(...)` +(the default direction in Chisel3) will +set all of the individual elements to output direction. +This will have the desired result when used to construct a Register: + +```scala mdoc:silent +import chisel3.util.Decoupled +class CoercedRegConnect extends Module { + val io = IO(new Bundle { + val enq = Flipped(Decoupled(UInt(8.W))) + }) + + // Make a Reg which contains all of the bundle's signals, regardless of their directionality + val monitor = Reg(Output(chiselTypeOf(io.enq))) + // Even though io.enq is bidirectional, := will drive all fields of monitor with the fields of io.enq + monitor := io.enq +} +``` + + +```scala mdoc:invisible +ChiselStage.emitVerilog(new CoercedRegConnect { + // Provide default connections that would just muddy the example + io.enq.ready := true.B + // dontTouch so that it shows up in the Verilog + dontTouch(monitor) +}) +``` -- cgit v1.2.3 From 7c8a032e7e23902283035d93579b8dc477b32f6a Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 17 Aug 2021 18:22:16 -0700 Subject: Revert "remove DefRegInit, change DefReg API with option definition. (#1944)" (#2080) This reverts commit ed894c61474c8bc73761a6c360ef9d14505d853b.--- core/src/main/scala/chisel3/Reg.scala | 4 ++-- core/src/main/scala/chisel3/internal/firrtl/Converter.scala | 4 ++-- core/src/main/scala/chisel3/internal/firrtl/IR.scala | 4 ++-- src/main/scala/chisel3/aop/Select.scala | 1 + src/main/scala/chisel3/internal/firrtl/Emitter.scala | 3 ++- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/core/src/main/scala/chisel3/Reg.scala b/core/src/main/scala/chisel3/Reg.scala index 9cdbf6a1..bd9e5311 100644 --- a/core/src/main/scala/chisel3/Reg.scala +++ b/core/src/main/scala/chisel3/Reg.scala @@ -42,7 +42,7 @@ object Reg { val clock = Node(Builder.forcedClock) reg.bind(RegBinding(Builder.forcedUserModule, Builder.currentWhen())) - pushCommand(DefReg(sourceInfo, reg, clock, None)) + pushCommand(DefReg(sourceInfo, reg, clock)) reg } @@ -176,7 +176,7 @@ object RegInit { reg.bind(RegBinding(Builder.forcedUserModule, Builder.currentWhen())) requireIsHardware(init, "reg initializer") - pushCommand(DefReg(sourceInfo, reg, clock.ref, Some(RegInitIR(reset.ref, init.ref)))) + pushCommand(DefRegInit(sourceInfo, reg, clock.ref, reset.ref, init.ref)) reg } diff --git a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala index e8fb197c..8efb2abc 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -117,10 +117,10 @@ private[chisel3] object Converter { Some(fir.DefNode(convert(e.sourceInfo), e.name, expr)) case e @ DefWire(info, id) => Some(fir.DefWire(convert(info), e.name, extractType(id, info))) - case e @ DefReg(info, id, clock, None) => + case e @ DefReg(info, id, clock) => Some(fir.DefRegister(convert(info), e.name, extractType(id, info), convert(clock, ctx, info), firrtl.Utils.zero, convert(getRef(id, info), ctx, info))) - case e @ DefReg(info, id, clock, Some(RegInitIR(reset, init))) => + case e @ DefRegInit(info, id, clock, reset, init) => Some(fir.DefRegister(convert(info), e.name, extractType(id, info), convert(clock, ctx, info), convert(reset, ctx, info), convert(init, ctx, info))) case e @ DefMemory(info, id, t, size) => diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index a45ae3c2..f8a3cf7f 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -758,8 +758,8 @@ abstract class Definition extends Command { case class DefPrim[T <: Data](sourceInfo: SourceInfo, id: T, op: PrimOp, args: Arg*) extends Definition case class DefInvalid(sourceInfo: SourceInfo, arg: Arg) extends Command case class DefWire(sourceInfo: SourceInfo, id: Data) extends Definition -case class RegInitIR(reset: Arg, init: Arg) -case class DefReg(sourceInfo: SourceInfo, id: Data, clock: Arg, regInit: Option[RegInitIR]) extends Definition +case class DefReg(sourceInfo: SourceInfo, id: Data, clock: Arg) extends Definition +case class DefRegInit(sourceInfo: SourceInfo, id: Data, clock: Arg, reset: Arg, init: Arg) extends Definition case class DefMemory(sourceInfo: SourceInfo, id: HasId, t: Data, size: BigInt) extends Definition case class DefSeqMemory(sourceInfo: SourceInfo, id: HasId, t: Data, size: BigInt, readUnderWrite: fir.ReadUnderWrite.Value) extends Definition case class DefMemPort[T <: Data](sourceInfo: SourceInfo, id: T, source: Node, dir: MemPortDirection, index: Arg, clock: Arg) extends Definition diff --git a/src/main/scala/chisel3/aop/Select.scala b/src/main/scala/chisel3/aop/Select.scala index 81b4cdab..078422bb 100644 --- a/src/main/scala/chisel3/aop/Select.scala +++ b/src/main/scala/chisel3/aop/Select.scala @@ -101,6 +101,7 @@ object Select { check(module) module._component.get.asInstanceOf[DefModule].commands.collect { case r: DefReg => r.id + case r: DefRegInit => r.id } } diff --git a/src/main/scala/chisel3/internal/firrtl/Emitter.scala b/src/main/scala/chisel3/internal/firrtl/Emitter.scala index daa83db0..47849d91 100644 --- a/src/main/scala/chisel3/internal/firrtl/Emitter.scala +++ b/src/main/scala/chisel3/internal/firrtl/Emitter.scala @@ -66,7 +66,8 @@ private class Emitter(circuit: Circuit) { val firrtlLine = e match { case e: DefPrim[_] => s"node ${e.name} = ${e.op.name}(${e.args.map(_.fullName(ctx)).mkString(", ")})" case e: DefWire => s"wire ${e.name} : ${emitType(e.id)}" - case e: DefReg => s"reg ${e.name} : ${emitType(e.id)}, ${e.clock.fullName(ctx)}${ if (e.regInit.isDefined) "with : (reset => (${e.reset.fullName(ctx)}, ${e.init.fullName(ctx)}))" else ""}" + case e: DefReg => s"reg ${e.name} : ${emitType(e.id)}, ${e.clock.fullName(ctx)}" + case e: DefRegInit => s"reg ${e.name} : ${emitType(e.id)}, ${e.clock.fullName(ctx)} with : (reset => (${e.reset.fullName(ctx)}, ${e.init.fullName(ctx)}))" case e: DefMemory => s"cmem ${e.name} : ${emitType(e.t)}[${e.size}]" case e: DefSeqMemory => s"smem ${e.name} : ${emitType(e.t)}[${e.size}], ${e.readUnderWrite}" case e: DefMemPort[_] => s"${e.dir} mport ${e.name} = ${e.source.fullName(ctx)}[${e.index.fullName(ctx)}], ${e.clock.fullName(ctx)}" -- cgit v1.2.3 From 5f550b606d47d04b7ee60976cdb18a51c391992c Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Thu, 29 Jul 2021 10:55:52 +0800 Subject: Revert "Just install z3 from apt-get in CI (#2056)" This reverts commit 1e7829eb674eed85a4cd537896d9fd9ee0bc5ff4. --- .github/workflows/test.yml | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f5a1f811..b2620d2f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,6 +18,7 @@ jobs: jvm: ["adopt@1.8"] scala: ["2.13.6", "2.12.13"] verilator: ["4.204"] + z3: ["4.8.10"] espresso: ["2.4"] runs-on: ${{ matrix.system }} @@ -25,10 +26,32 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Install Z3 + - name: Install Z3 Build Dependencies(Ubuntu) if: matrix.system == 'ubuntu-20.04' + run: sudo apt-get install -y libfl2 libfl-dev + + - name: Cache Z3 ${{ matrix.z3 }} + uses: actions/cache@v2 + id: cache-z3 + with: + path: z3-z3-${{ matrix.z3 }} + key: ${{ matrix.system }}-z3-${{ matrix.z3 }} + - name: Compile Z3 + if: steps.cache-z3.outputs.cache-hit != 'true' + run: | + wget https://github.com/Z3Prover/z3/archive/refs/tags/z3-${{ matrix.z3 }}.tar.gz + tar xvf z3-${{ matrix.z3 }}.tar.gz + cd z3-z3-${{ matrix.z3 }} + mkdir -p build + cd build + cmake .. \ + -DCMAKE_BUILD_TYPE=Release \ + -DZ3_LINK_TIME_OPTIMIZATION=1 + make + - name: Install Z3 ${{ matrix.z3 }} run: | - sudo apt-get install -y z3 + cd z3-z3-${{ matrix.z3 }}/build + sudo make install z3 --version - name: Cache Verilator ${{ matrix.verilator }} -- cgit v1.2.3 From 886d60ca6412a017ad345e3ce346eefe383c4a7e Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Thu, 29 Jul 2021 11:06:15 +0800 Subject: switch to ninja --- .github/workflows/test.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b2620d2f..94a30f12 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -45,13 +45,14 @@ jobs: mkdir -p build cd build cmake .. \ + -GNinja \ -DCMAKE_BUILD_TYPE=Release \ -DZ3_LINK_TIME_OPTIMIZATION=1 - make + ninja - name: Install Z3 ${{ matrix.z3 }} run: | cd z3-z3-${{ matrix.z3 }}/build - sudo make install + sudo ninja install z3 --version - name: Cache Verilator ${{ matrix.verilator }} -- cgit v1.2.3 From b940d9f83f87567fe69c068a1faf63fd8d3bf091 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Thu, 29 Jul 2021 11:08:02 +0800 Subject: apt install ninja --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 94a30f12..10742b96 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,7 +28,7 @@ jobs: - name: Install Z3 Build Dependencies(Ubuntu) if: matrix.system == 'ubuntu-20.04' - run: sudo apt-get install -y libfl2 libfl-dev + run: sudo apt-get install -y libfl2 libfl-dev ninja-build - name: Cache Z3 ${{ matrix.z3 }} uses: actions/cache@v2 -- cgit v1.2.3 From 0952a6ecf0811663f21379371707abc679a9cea4 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Thu, 29 Jul 2021 11:16:55 +0800 Subject: don't hit cache --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 10742b96..dca07648 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,7 +35,7 @@ jobs: id: cache-z3 with: path: z3-z3-${{ matrix.z3 }} - key: ${{ matrix.system }}-z3-${{ matrix.z3 }} + key: ${{ matrix.system }}-z3-ninja-${{ matrix.z3 }} - name: Compile Z3 if: steps.cache-z3.outputs.cache-hit != 'true' run: | -- cgit v1.2.3 From bc963e741df2f949c236db65aa51b686abdd41a1 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Thu, 29 Jul 2021 12:14:27 +0800 Subject: timestamp seems to be fixed in actions/cache#397 --- .github/workflows/test.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dca07648..22a04c35 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,7 +31,7 @@ jobs: run: sudo apt-get install -y libfl2 libfl-dev ninja-build - name: Cache Z3 ${{ matrix.z3 }} - uses: actions/cache@v2 + uses: actions/cache@v2.1.6 id: cache-z3 with: path: z3-z3-${{ matrix.z3 }} @@ -56,7 +56,7 @@ jobs: z3 --version - name: Cache Verilator ${{ matrix.verilator }} - uses: actions/cache@v2 + uses: actions/cache@v2.1.6 id: cache-verilator with: path: verilator-${{ matrix.verilator }} @@ -77,7 +77,7 @@ jobs: verilator --version - name: Cache Espresso ${{ matrix.espresso }} - uses: actions/cache@v2 + uses: actions/cache@v2.1.6 id: cache-espresso with: path: espresso-${{ matrix.espresso }} @@ -97,7 +97,6 @@ jobs: cd espresso-${{ matrix.espresso }}/build sudo make install - - name: Setup Scala uses: olafurpg/setup-scala@v10 with: -- cgit v1.2.3 From 42f1363c1956976cad62a68c3f34f57941b08003 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Wed, 18 Aug 2021 12:59:59 +0800 Subject: switch espresso to ninja. --- .github/workflows/test.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 22a04c35..3438dcf9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,7 +48,7 @@ jobs: -GNinja \ -DCMAKE_BUILD_TYPE=Release \ -DZ3_LINK_TIME_OPTIMIZATION=1 - ninja + ninja - name: Install Z3 ${{ matrix.z3 }} run: | cd z3-z3-${{ matrix.z3 }}/build @@ -81,7 +81,7 @@ jobs: id: cache-espresso with: path: espresso-${{ matrix.espresso }} - key: ${{ matrix.system }}-espresso-${{ matrix.espresso }} + key: ${{ matrix.system }}-espresso-ninja-${{ matrix.espresso }} - name: Compile Espresso ${{ matrix.espresso }} if: steps.cache-espresso.outputs.cache-hit != 'true' run: | @@ -90,12 +90,14 @@ jobs: cd espresso-${{ matrix.espresso }} mkdir -p build cd build - cmake .. - make + cmake .. \ + -GNinja \ + -DCMAKE_BUILD_TYPE=Release + ninja - name: Install Espresso ${{ matrix.espresso }} run: | cd espresso-${{ matrix.espresso }}/build - sudo make install + sudo ninja install - name: Setup Scala uses: olafurpg/setup-scala@v10 -- cgit v1.2.3 From efe448c5ca907b3d7a40407f720aa6d4ff2a1a4e Mon Sep 17 00:00:00 2001 From: Kevin Laeufer Date: Wed, 18 Aug 2021 16:46:03 -0700 Subject: Builder: downgrade "Elaborating design" message to info (#2081) --- core/src/main/scala/chisel3/internal/Builder.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index ddaedd2e..f7306d5d 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -701,11 +701,11 @@ private[chisel3] object Builder extends LazyLogging { ViewParent // Must initialize the singleton in a Builder context or weird things can happen // in tiny designs/testcases that never access anything in chisel3.internal checkScalaVersion() - logger.warn("Elaborating design...") + logger.info("Elaborating design...") val mod = f mod.forceName(None, mod.name, globalNamespace) errors.checkpoint(logger) - logger.warn("Done elaborating.") + logger.info("Done elaborating.") (Circuit(components.last.name, components.toSeq, annotations.toSeq, makeViewRenameMap), mod) } -- cgit v1.2.3 From a3d51e4c91059362b20296eaa00f06f96ec7a4e1 Mon Sep 17 00:00:00 2001 From: Ruige Lee Date: Sun, 22 Aug 2021 00:08:54 +0800 Subject: Update ChiselStage.scala (#2082) There might be some "@"?--- src/main/scala/chisel3/stage/ChiselStage.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/scala/chisel3/stage/ChiselStage.scala b/src/main/scala/chisel3/stage/ChiselStage.scala index 989b3a17..0c76f411 100644 --- a/src/main/scala/chisel3/stage/ChiselStage.scala +++ b/src/main/scala/chisel3/stage/ChiselStage.scala @@ -47,7 +47,7 @@ class ChiselStage extends Stage { /** Convert a Chisel module to a CHIRRTL string * @param gen a call-by-name Chisel module * @param args additional command line arguments to pass to Chisel - * param annotations additional annotations to pass to Chisel + * @param annotations additional annotations to pass to Chisel * @return a string containing the Verilog output */ final def emitChirrtl( @@ -70,7 +70,7 @@ class ChiselStage extends Stage { /** Convert a Chisel module to a FIRRTL string * @param gen a call-by-name Chisel module * @param args additional command line arguments to pass to Chisel - * param annotations additional annotations to pass to Chisel + * @param annotations additional annotations to pass to Chisel * @return a string containing the FIRRTL output */ final def emitFirrtl( @@ -90,7 +90,7 @@ class ChiselStage extends Stage { /** Convert a Chisel module to Verilog * @param gen a call-by-name Chisel module * @param args additional command line arguments to pass to Chisel - * param annotations additional annotations to pass to Chisel + * @param annotations additional annotations to pass to Chisel * @return a string containing the Verilog output */ final def emitVerilog( @@ -110,7 +110,7 @@ class ChiselStage extends Stage { /** Convert a Chisel module to SystemVerilog * @param gen a call-by-name Chisel module * @param args additional command line arguments to pass to Chisel - * param annotations additional annotations to pass to Chisel + * @param annotations additional annotations to pass to Chisel * @return a string containing the SystemVerilog output */ final def emitSystemVerilog( -- cgit v1.2.3 From f50ce19406e45982390162777fb62c8563c962c7 Mon Sep 17 00:00:00 2001 From: anniej-sifive Date: Mon, 23 Aug 2021 14:37:09 -0700 Subject: Add multiple dimensions to VecInit fill and iterate (#2065) Co-authored-by: Jack Koenig --- core/src/main/scala/chisel3/Aggregate.scala | 129 ++++++++++++++++-- docs/src/cookbooks/cookbook.md | 46 +++++++ .../internal/sourceinfo/SourceInfoTransform.scala | 15 +++ src/test/scala/chiselTests/Vec.scala | 150 ++++++++++++++++++++- 4 files changed, 327 insertions(+), 13 deletions(-) diff --git a/core/src/main/scala/chisel3/Aggregate.scala b/core/src/main/scala/chisel3/Aggregate.scala index 58bc5ccb..17e46cb3 100644 --- a/core/src/main/scala/chisel3/Aggregate.scala +++ b/core/src/main/scala/chisel3/Aggregate.scala @@ -488,6 +488,21 @@ sealed class Vec[T <: Data] private[chisel3] (gen: => T, val length: Int) } object VecInit extends SourceInfoDoc { + + /** Gets the correct connect operation (directed hardware assign or bulk connect) for element in Vec. + */ + private def getConnectOpFromDirectionality[T <: Data](proto: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): (T, T) => Unit = proto.direction match { + case ActualDirection.Input | ActualDirection.Output | ActualDirection.Unspecified => + // When internal wires are involved, driver / sink must be specified explicitly, otherwise + // the system is unable to infer which is driver / sink + (x, y) => x := y + case ActualDirection.Bidirectional(_) => + // For bidirectional, must issue a bulk connect so subelements are resolved correctly. + // Bulk connecting two wires may not succeed because Chisel frontend does not infer + // directions. + (x, y) => x <> y + } + /** Creates a new [[Vec]] composed of elements of the input Seq of [[Data]] * nodes. * @@ -513,18 +528,10 @@ object VecInit extends SourceInfoDoc { elts.foreach(requireIsHardware(_, "vec element")) val vec = Wire(Vec(elts.length, cloneSupertype(elts, "Vec"))) - - // TODO: try to remove the logic for this mess - elts.head.direction match { - case ActualDirection.Input | ActualDirection.Output | ActualDirection.Unspecified => - // When internal wires are involved, driver / sink must be specified explicitly, otherwise - // the system is unable to infer which is driver / sink - (vec zip elts).foreach(x => x._1 := x._2) - case ActualDirection.Bidirectional(_) => - // For bidirectional, must issue a bulk connect so subelements are resolved correctly. - // Bulk connecting two wires may not succeed because Chisel frontend does not infer - // directions. - (vec zip elts).foreach(x => x._1 <> x._2) + val op = getConnectOpFromDirectionality(vec.head) + + (vec zip elts).foreach{ x => + op(x._1, x._2) } vec } @@ -557,6 +564,73 @@ object VecInit extends SourceInfoDoc { def do_tabulate[T <: Data](n: Int)(gen: (Int) => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[T] = apply((0 until n).map(i => gen(i))) + /** Creates a new 2D [[Vec]] of length `n by m` composed of the results of the given + * function applied over a range of integer values starting from 0. + * + * @param n number of 1D vectors inside outer vector + * @param m number of elements in each 1D vector (the function is applied from + * 0 to `n-1`) + * @param gen function that takes in an Int (the index) and returns a + * [[Data]] that becomes the output element + */ + def tabulate[T <: Data](n: Int, m: Int)(gen: (Int, Int) => T): Vec[Vec[T]] = macro VecTransform.tabulate2D + + /** @group SourceInfoTransformMacro */ + def do_tabulate[T <: Data](n: Int, m: Int)(gen: (Int, Int) => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[Vec[T]] = { + // TODO make this lazy (requires LazyList and cross compilation, beyond the scope of this PR) + val elts = Seq.tabulate(n, m)(gen) + val flatElts = elts.flatten + + require(flatElts.nonEmpty, "Vec hardware values are not allowed to be empty") + flatElts.foreach(requireIsHardware(_, "vec element")) + + val tpe = cloneSupertype(flatElts, "Vec.tabulate") + val myVec = Wire(Vec(n, Vec(m, tpe))) + val op = getConnectOpFromDirectionality(myVec.head.head) + for ( + (xs1D, ys1D) <- myVec zip elts; + (x, y) <- xs1D zip ys1D + ) { + op(x, y) + } + myVec + } + + /** Creates a new 3D [[Vec]] of length `n by m by p` composed of the results of the given + * function applied over a range of integer values starting from 0. + * + * @param n number of 2D vectors inside outer vector + * @param m number of 1D vectors in each 2D vector + * @param p number of elements in each 1D vector + * @param gen function that takes in an Int (the index) and returns a + * [[Data]] that becomes the output element + */ + def tabulate[T <: Data](n: Int, m: Int, p: Int)(gen: (Int, Int, Int) => T): Vec[Vec[Vec[T]]] = macro VecTransform.tabulate3D + + /** @group SourceInfoTransformMacro */ + def do_tabulate[T <: Data](n: Int, m: Int, p: Int)(gen: (Int, Int, Int) => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[Vec[Vec[T]]] = { + // TODO make this lazy (requires LazyList and cross compilation, beyond the scope of this PR) + val elts = Seq.tabulate(n, m, p)(gen) + val flatElts = elts.flatten.flatten + + require(flatElts.nonEmpty, "Vec hardware values are not allowed to be empty") + flatElts.foreach(requireIsHardware(_, "vec element")) + + val tpe = cloneSupertype(flatElts, "Vec.tabulate") + val myVec = Wire(Vec(n, Vec(m, Vec(p, tpe)))) + val op = getConnectOpFromDirectionality(myVec.head.head.head) + + for ( + (xs2D, ys2D) <- myVec zip elts; + (xs1D, ys1D) <- xs2D zip ys2D; + (x, y) <- xs1D zip ys1D + ) { + op(x, y) + } + + myVec + } + /** Creates a new [[Vec]] of length `n` composed of the result of the given * function applied to an element of data type T. * @@ -570,6 +644,37 @@ object VecInit extends SourceInfoDoc { def do_fill[T <: Data](n: Int)(gen: => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[T] = apply(Seq.fill(n)(gen)) + /** Creates a new 2D [[Vec]] of length `n by m` composed of the result of the given + * function applied to an element of data type T. + * + * @param n number of inner vectors (rows) in the outer vector + * @param m number of elements in each inner vector (column) + * @param gen function that takes in an element T and returns an output + * element of the same type + */ + def fill[T <: Data](n: Int, m: Int)(gen: => T): Vec[Vec[T]] = macro VecTransform.fill2D + + /** @group SourceInfoTransformMacro */ + def do_fill[T <: Data](n: Int, m: Int)(gen: => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[Vec[T]] = { + do_tabulate(n, m)((_, _) => gen) + } + + /** Creates a new 3D [[Vec]] of length `n by m by p` composed of the result of the given + * function applied to an element of data type T. + * + * @param n number of 2D vectors inside outer vector + * @param m number of 1D vectors in each 2D vector + * @param p number of elements in each 1D vector + * @param gen function that takes in an element T and returns an output + * element of the same type + */ + def fill[T <: Data](n: Int, m: Int, p: Int)(gen: => T): Vec[Vec[Vec[T]]] = macro VecTransform.fill3D + + /** @group SourceInfoTransformMacro */ + def do_fill[T <: Data](n: Int, m: Int, p: Int)(gen: => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[Vec[Vec[T]]] = { + do_tabulate(n, m, p)((_, _, _) => gen) + } + /** Creates a new [[Vec]] of length `n` composed of the result of the given * function applied to an element of data type T. * diff --git a/docs/src/cookbooks/cookbook.md b/docs/src/cookbooks/cookbook.md index cff7a5b2..ce49b668 100644 --- a/docs/src/cookbooks/cookbook.md +++ b/docs/src/cookbooks/cookbook.md @@ -17,6 +17,7 @@ Please note that these examples make use of [Chisel's scala-style printing](../e * [How do I create a UInt from a Vec of Bool?](#how-do-i-create-a-uint-from-a-vec-of-bool) * [How do I connect a subset of Bundle fields?](#how-do-i-connect-a-subset-of-bundle-fields) * Vectors and Registers + * [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) * [How do I create a finite state machine?](#how-do-i-create-a-finite-state-machine-fsm) @@ -157,6 +158,51 @@ See the [DataView cookbook](dataview#how-do-i-connect-a-subset-of-bundle-fields) ## Vectors and Registers +### Can I make a 2D or 3D Vector? + +Yes. Using `VecInit` you can make Vectors that hold Vectors of Chisel types. Methods `fill` and `tabulate` make these multi-dimensional Vectors. + +```scala mdoc:silent:reset +import chisel3._ + +class MyBundle extends Bundle { + val foo = UInt(4.W) + val bar = UInt(4.W) +} + +class Foo extends Module { + //2D Fill + val twoDVec = VecInit.fill(2, 3)(5.U) + //3D Fill + val myBundle = Wire(new MyBundle) + myBundle.foo := 0xc.U + myBundle.bar := 0x3.U + val threeDVec = VecInit.fill(1, 2, 3)(myBundle) + assert(threeDVec(0)(0)(0).foo === 0xc.U && threeDVec(0)(0)(0).bar === 0x3.U) + + //2D Tabulate + val indexTiedVec = VecInit.tabulate(2, 2){ (x, y) => (x + y).U } + assert(indexTiedVec(0)(0) === 0.U) + assert(indexTiedVec(0)(1) === 1.U) + assert(indexTiedVec(1)(0) === 1.U) + assert(indexTiedVec(1)(1) === 2.U) + //3D Tabulate + val indexTiedVec3D = VecInit.tabulate(2, 3, 4){ (x, y, z) => (x + y * z).U } + assert(indexTiedVec3D(0)(0)(0) === 0.U) + assert(indexTiedVec3D(1)(1)(1) === 2.U) + assert(indexTiedVec3D(1)(1)(2) === 3.U) + assert(indexTiedVec3D(1)(1)(3) === 4.U) + assert(indexTiedVec3D(1)(2)(3) === 7.U) +} +``` +```scala mdoc:invisible +// Hidden but will make sure this actually compiles +import chisel3.stage.ChiselStage + +ChiselStage.emitVerilog(new Foo) +``` + + ### How do I create a Vector of Registers? **Rule! Use Reg of Vec not Vec of Reg!** diff --git a/macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala b/macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala index d7c301e9..6121bc1e 100644 --- a/macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala +++ b/macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala @@ -81,9 +81,24 @@ class VecTransform(val c: Context) extends SourceInfoTransformMacro { def tabulate(n: c.Tree)(gen: c.Tree): c.Tree = { q"$thisObj.do_tabulate($n)($gen)($implicitSourceInfo, $implicitCompileOptions)" } + def tabulate2D(n: c.Tree, m: c.Tree)(gen: c.Tree): c.Tree = { + q"$thisObj.do_tabulate($n,$m)($gen)($implicitSourceInfo, $implicitCompileOptions)" + } + def tabulate3D(n: c.Tree, m: c.Tree, p: c.Tree)(gen: c.Tree): c.Tree = { + q"$thisObj.do_tabulate($n,$m,$p)($gen)($implicitSourceInfo, $implicitCompileOptions)" + } def fill(n: c.Tree)(gen: c.Tree): c.Tree = { q"$thisObj.do_fill($n)($gen)($implicitSourceInfo, $implicitCompileOptions)" } + def fill2D(n: c.Tree, m: c.Tree)(gen: c.Tree): c.Tree = { + q"$thisObj.do_fill($n,$m)($gen)($implicitSourceInfo, $implicitCompileOptions)" + } + def fill3D(n: c.Tree, m: c.Tree, p: c.Tree)(gen: c.Tree): c.Tree = { + q"$thisObj.do_fill($n,$m,$p)($gen)($implicitSourceInfo, $implicitCompileOptions)" + } + def fill4D(n: c.Tree, m: c.Tree, p: c.Tree, q: c.Tree)(gen: c.Tree): c.Tree = { + q"$thisObj.do_fill($n,$m,$p,$q)($gen)($implicitSourceInfo, $implicitCompileOptions)" + } def iterate(start: c.Tree, len: c.Tree)(f: c.Tree): c.Tree = { q"$thisObj.do_iterate($start,$len)($f)($implicitSourceInfo, $implicitCompileOptions)" } diff --git a/src/test/scala/chiselTests/Vec.scala b/src/test/scala/chiselTests/Vec.scala index f3160c2e..97aea909 100644 --- a/src/test/scala/chiselTests/Vec.scala +++ b/src/test/scala/chiselTests/Vec.scala @@ -9,6 +9,7 @@ import chisel3.stage.ChiselStage import chisel3.testers.BasicTester import chisel3.util._ import org.scalacheck.Shrink +import scala.annotation.tailrec class LitTesterMod(vecSize: Int) extends Module { val io = IO(new Bundle { @@ -114,6 +115,121 @@ class FillTester(n: Int, value: Int) extends BasicTester { stop() } +object VecMultiDimTester { + + @tailrec + private def assert2DIsCorrect(n: Int, arr: Vec[Vec[UInt]], compArr: Seq[Seq[Int]]): Unit = { + val compareRow = arr(n) zip compArr(n) + compareRow.foreach (x => assert(x._1 === x._2.U)) + if (n != 0) assert2DIsCorrect(n-1, arr, compArr) + } + + @tailrec + private def assert3DIsCorrect(n: Int, m: Int, arr: Vec[Vec[Vec[UInt]]], compArr: Seq[Seq[Seq[Int]]]): Unit = { + assert2DIsCorrect(m-1, arr(n), compArr(n)) + if (n != 0) assert3DIsCorrect(n-1, m, arr, compArr) + } + + class TabulateTester2D(n: Int, m: Int) extends BasicTester { + def gen(x: Int, y: Int): UInt = (x+y).asUInt + def genCompVec(x: Int, y:Int): Int = x+y + val vec = VecInit.tabulate(n, m){ gen } + val compArr = Seq.tabulate(n,m){ genCompVec } + + assert2DIsCorrect(n-1, vec, compArr) + stop() + } + + class TabulateTester3D(n: Int, m: Int, p: Int) extends BasicTester { + def gen(x: Int, y: Int, z: Int): UInt = (x+y+z).asUInt + def genCompVec(x: Int, y:Int, z: Int): Int = x+y+z + val vec = VecInit.tabulate(n, m, p){ gen } + val compArr = Seq.tabulate(n, m, p){ genCompVec } + + assert3DIsCorrect(n-1, m, vec, compArr) + stop() + } + + class Fill2DTester(n: Int, m: Int, value: Int) extends BasicTester { + val u = VecInit.fill(n,m)(value.U) + val compareArr = Seq.fill(n,m)(value) + + assert2DIsCorrect(n-1, u, compareArr) + stop() + } + + class Fill3DTester(n: Int, m: Int, p: Int, value: Int) extends BasicTester { + val u = VecInit.fill(n,m,p)(value.U) + val compareArr = Seq.fill(n,m,p)(value) + + assert3DIsCorrect(n-1, m, u, compareArr) + stop() + } + + class BidirectionalTester2DFill(n: Int, m: Int) extends BasicTester { + val mod = Module(new PassthroughModule) + val vec2D = VecInit.fill(n, m)(mod.io) + for { + vec1D <- vec2D + module <- vec1D + } yield { + module <> Module(new PassthroughModuleTester).io + } + stop() + } + + class BidirectionalTester3DFill(n: Int, m: Int, p: Int) extends BasicTester { + val mod = Module(new PassthroughModule) + val vec3D = VecInit.fill(n, m, p)(mod.io) + + for { + vec2D <- vec3D + vec1D <- vec2D + module <- vec1D + } yield { + module <> (Module(new PassthroughModuleTester).io) + } + stop() + } + + class TabulateModuleTester(value: Int) extends Module { + val io = IO(Flipped(new PassthroughModuleIO)) + // This drives the input of a PassthroughModule + io.in := value.U + } + + class BidirectionalTester2DTabulate(n: Int, m: Int) extends BasicTester { + val vec2D = VecInit.tabulate(n, m) { (x, y) => Module(new TabulateModuleTester(x + y + 1)).io} + + for { + x <- 0 until n + y <- 0 until m + } yield { + val value = x + y + 1 + val receiveMod = Module(new PassthroughModule).io + vec2D(x)(y) <> receiveMod + assert(receiveMod.out === value.U) + } + stop() + } + + class BidirectionalTester3DTabulate(n: Int, m: Int, p: Int) extends BasicTester { + val vec3D = VecInit.tabulate(n, m, p) { (x, y, z) => Module(new TabulateModuleTester(x + y + z + 1)).io } + + for { + x <- 0 until n + y <- 0 until m + z <- 0 until p + } yield { + val value = x + y + z + 1 + val receiveMod = Module(new PassthroughModule).io + vec3D(x)(y)(z) <> receiveMod + assert(receiveMod.out === value.U) + } + stop() + } +} + class IterateTester(start: Int, len: Int)(f: UInt => UInt) extends BasicTester { val controlVec = VecInit(Seq.iterate(start.U, len)(f)) val testVec = VecInit.iterate(start.U, len)(f) @@ -178,7 +294,7 @@ class PassthroughModuleTester extends Module { } class ModuleIODynamicIndexTester(n: Int) extends BasicTester { - val duts = VecInit(Seq.fill(n)(Module(new PassthroughModule).io)) + val duts = VecInit.fill(n)(Module(new PassthroughModule).io) val tester = Module(new PassthroughModuleTester) val (cycle, done) = Counter(true.B, n) @@ -239,10 +355,42 @@ class VecSpec extends ChiselPropSpec with Utils { forAll(smallPosInts) { (n: Int) => assertTesterPasses{ new TabulateTester(n) } } } + property("VecInit should tabulate 2D vec correctly") { + forAll(smallPosInts, smallPosInts) { (n: Int, m: Int) => assertTesterPasses { new VecMultiDimTester.TabulateTester2D(n, m) } } + } + + property("VecInit should tabulate 3D vec correctly") { + forAll(smallPosInts, smallPosInts, smallPosInts) { (n: Int, m: Int, p: Int) => assertTesterPasses{ new VecMultiDimTester.TabulateTester3D(n, m, p) } } + } + property("VecInit should fill correctly") { forAll(smallPosInts, Gen.choose(0, 50)) { (n: Int, value: Int) => assertTesterPasses{ new FillTester(n, value) } } } + property("VecInit should fill 2D vec correctly") { + forAll(smallPosInts, smallPosInts, Gen.choose(0, 50)) { (n: Int, m: Int, value: Int) => assertTesterPasses{ new VecMultiDimTester.Fill2DTester(n, m, value) } } + } + + property("VecInit should fill 3D vec correctly") { + forAll(smallPosInts, smallPosInts, smallPosInts, Gen.choose(0, 50)) { (n: Int, m: Int, p: Int, value: Int) => assertTesterPasses{ new VecMultiDimTester.Fill3DTester(n, m, p, value) } } + } + + property("VecInit should support 2D fill bidirectional wire connection") { + forAll(smallPosInts, smallPosInts) { (n: Int, m: Int) => assertTesterPasses{ new VecMultiDimTester.BidirectionalTester2DFill(n, m) }} + } + + property("VecInit should support 3D fill bidirectional wire connection") { + forAll(smallPosInts, smallPosInts, smallPosInts) { (n: Int, m: Int, p: Int) => assertTesterPasses{ new VecMultiDimTester.BidirectionalTester3DFill(n, m, p) }} + } + + property("VecInit should support 2D tabulate bidirectional wire connection") { + forAll(smallPosInts, smallPosInts) { (n: Int, m: Int) => assertTesterPasses{ new VecMultiDimTester.BidirectionalTester2DTabulate(n, m) }} + } + + property("VecInit should support 3D tabulate bidirectional wire connection") { + forAll(smallPosInts, smallPosInts, smallPosInts) { (n: Int, m: Int, p: Int) => assertTesterPasses{ new VecMultiDimTester.BidirectionalTester3DTabulate(n, m, p) }} + } + property("VecInit should iterate correctly") { forAll(Gen.choose(1, 10), smallPosInts) { (start: Int, len: Int) => assertTesterPasses{ new IterateTester(start, len)(x => x + 50.U)}} } -- cgit v1.2.3 From 73bd4ee6b9b510725b692c33e075362a19512d2c Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Fri, 20 Aug 2021 11:30:27 -0700 Subject: Remove chisel3's own firrtl Emitter, use firrtl Serializer This will be slightly slower as it involves converting from Chisel modules to FIRRTL modules before turning them into Strings. This cost is somewhat mitigated by doing that conversion lazily such that we never materialize the entire firrtl Circuit in memory, only 1 module at a time. --- .../scala/chisel3/internal/firrtl/Converter.scala | 7 + .../scala/chisel3/internal/firrtl/Emitter.scala | 192 +-------------------- src/test/scala/chiselTests/PrintableSpec.scala | 7 +- src/test/scala/chiselTests/VecLiteralSpec.scala | 8 +- .../verification/VerificationSpec.scala | 10 +- 5 files changed, 24 insertions(+), 200 deletions(-) diff --git a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala index 8efb2abc..f56c3b15 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -9,6 +9,7 @@ import chisel3.internal.{HasId, castToInt, throwException} import scala.annotation.tailrec import scala.collection.immutable.Queue +import scala.collection.immutable.LazyList // Needed for 2.12 alias private[chisel3] object Converter { // TODO modeled on unpack method on Printable, refactor? @@ -301,5 +302,11 @@ private[chisel3] object Converter { def convert(circuit: Circuit): fir.Circuit = fir.Circuit(fir.NoInfo, circuit.components.map(convert), circuit.name) + + // TODO Unclear if this should just be the default + def convertLazily(circuit: Circuit): fir.Circuit = { + val lazyModules = LazyList() ++ circuit.components + fir.Circuit(fir.NoInfo, lazyModules.map(convert), circuit.name) + } } diff --git a/src/main/scala/chisel3/internal/firrtl/Emitter.scala b/src/main/scala/chisel3/internal/firrtl/Emitter.scala index 47849d91..53329908 100644 --- a/src/main/scala/chisel3/internal/firrtl/Emitter.scala +++ b/src/main/scala/chisel3/internal/firrtl/Emitter.scala @@ -1,194 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 package chisel3.internal.firrtl -import chisel3._ -import chisel3.experimental.{Interval, _} -import chisel3.internal.BaseBlackBox +import firrtl.{ir => fir} private[chisel3] object Emitter { - def emit(circuit: Circuit): String = new Emitter(circuit).toString -} - -private class Emitter(circuit: Circuit) { - override def toString: String = res.toString - - private def emitPort(e: Port, topDir: SpecifiedDirection=SpecifiedDirection.Unspecified): String = { - val resolvedDir = SpecifiedDirection.fromParent(topDir, e.dir) - val dirString = resolvedDir match { - case SpecifiedDirection.Unspecified | SpecifiedDirection.Output => "output" - case SpecifiedDirection.Flip | SpecifiedDirection.Input => "input" - } - val clearDir = resolvedDir match { - case SpecifiedDirection.Input | SpecifiedDirection.Output => true - case SpecifiedDirection.Unspecified | SpecifiedDirection.Flip => false - } - s"$dirString ${e.id.getRef.name} : ${emitType(e.id, clearDir)}" - } - - private def emitType(d: Data, clearDir: Boolean = false): String = d match { - case d: Clock => "Clock" - case _: AsyncReset => "AsyncReset" - case _: ResetType => "Reset" - case d: chisel3.experimental.EnumType => s"UInt${d.width}" - case d: UInt => s"UInt${d.width}" - case d: SInt => s"SInt${d.width}" - case d: FixedPoint => s"Fixed${d.width}${d.binaryPoint}" - case d: Interval => - val binaryPointString = d.binaryPoint match { - case UnknownBinaryPoint => "" - case KnownBinaryPoint(value) => s".$value" - } - d.toType - case d: Analog => s"Analog${d.width}" - case d: Vec[_] => s"${emitType(d.sample_element, clearDir)}[${d.length}]" - case d: Record => { - val childClearDir = clearDir || - d.specifiedDirection == SpecifiedDirection.Input || d.specifiedDirection == SpecifiedDirection.Output - def eltPort(elt: Data): String = (childClearDir, firrtlUserDirOf(elt)) match { - case (true, _) => - s"${elt.getRef.name} : ${emitType(elt, true)}" - case (false, SpecifiedDirection.Unspecified | SpecifiedDirection.Output) => - s"${elt.getRef.name} : ${emitType(elt, false)}" - case (false, SpecifiedDirection.Flip | SpecifiedDirection.Input) => - s"flip ${elt.getRef.name} : ${emitType(elt, false)}" - } - d.elements.toIndexedSeq.reverse.map(e => eltPort(e._2)).mkString("{", ", ", "}") - } - } - - private def firrtlUserDirOf(d: Data): SpecifiedDirection = d match { - case d: Vec[_] => - SpecifiedDirection.fromParent(d.specifiedDirection, firrtlUserDirOf(d.sample_element)) - case d => d.specifiedDirection - } - - private def emit(e: Command, ctx: Component): String = { - val firrtlLine = e match { - case e: DefPrim[_] => s"node ${e.name} = ${e.op.name}(${e.args.map(_.fullName(ctx)).mkString(", ")})" - case e: DefWire => s"wire ${e.name} : ${emitType(e.id)}" - case e: DefReg => s"reg ${e.name} : ${emitType(e.id)}, ${e.clock.fullName(ctx)}" - case e: DefRegInit => s"reg ${e.name} : ${emitType(e.id)}, ${e.clock.fullName(ctx)} with : (reset => (${e.reset.fullName(ctx)}, ${e.init.fullName(ctx)}))" - case e: DefMemory => s"cmem ${e.name} : ${emitType(e.t)}[${e.size}]" - case e: DefSeqMemory => s"smem ${e.name} : ${emitType(e.t)}[${e.size}], ${e.readUnderWrite}" - case e: DefMemPort[_] => s"${e.dir} mport ${e.name} = ${e.source.fullName(ctx)}[${e.index.fullName(ctx)}], ${e.clock.fullName(ctx)}" - case e: Connect => s"${e.loc.fullName(ctx)} <= ${e.exp.fullName(ctx)}" - case e: BulkConnect => s"${e.loc1.fullName(ctx)} <- ${e.loc2.fullName(ctx)}" - case e: Attach => e.locs.map(_.fullName(ctx)).mkString("attach (", ", ", ")") - case e: Stop => s"stop(${e.clock.fullName(ctx)}, UInt<1>(1), ${e.ret})" - case e: chisel3.internal.firrtl.Printf => - val (fmt, args) = e.pable.unpack(ctx) - val printfArgs = Seq(e.clock.fullName(ctx), "UInt<1>(1)", - "\"" + printf.format(fmt) + "\"") ++ args - (printfArgs mkString ("printf(", ", ", ")")) + s": ${e.name}" - case e: Verification[_] => - s"""${e.op}(${e.clock.fullName(ctx)}, ${e.predicate.fullName(ctx)}, UInt<1>(1), "${printf.format(e.message)}") : ${e.name}""" - case e: DefInvalid => s"${e.arg.fullName(ctx)} is invalid" - case e: DefInstance => s"inst ${e.name} of ${e.id.name}" - case w: WhenBegin => - // When consequences are always indented - indent() - s"when ${w.pred.fullName(ctx)} :" - case w: WhenEnd => - // If a when has no else, the indent level must be reset to the enclosing block - unindent() - if (!w.hasAlt) { for (i <- 0 until w.firrtlDepth) { unindent() } } - s"skip" - case a: AltBegin => - // Else blocks are always indented - indent() - s"else :" - case o: OtherwiseEnd => - // Chisel otherwise: ends all FIRRTL associated a Chisel when, resetting indent level - for (i <- 0 until o.firrtlDepth) { unindent() } - s"skip" - } - firrtlLine + e.sourceInfo.makeMessage(" " + _) + def emit(circuit: Circuit): String = { + val fcircuit = Converter.convertLazily(circuit) + fir.Serializer.serialize(fcircuit) } - - private def emitParam(name: String, p: Param): String = { - val str = p match { - case IntParam(value) => value.toString - case DoubleParam(value) => value.toString - case StringParam(str) => "\"" + str + "\"" - case RawParam(str) => "'" + str + "'" - } - s"parameter $name = $str" - } - - /** Generates the FIRRTL module declaration. - */ - private def moduleDecl(m: Component): String = m.id match { - case _: BaseBlackBox => newline + s"extmodule ${m.name} : " - case _: RawModule => newline + s"module ${m.name} : " - } - - /** Generates the FIRRTL module definition. - */ - private def moduleDefn(m: Component): String = { - val body = new StringBuilder - withIndent { - for (p <- m.ports) { - val portDef = m match { - case bb: DefBlackBox => emitPort(p, bb.topDir) - case mod: DefModule => emitPort(p) - } - body ++= newline + portDef - } - body ++= newline - - m match { - case bb: DefBlackBox => - // Firrtl extmodule can overrule name - body ++= newline + s"defname = ${bb.id.desiredName}" - body ++= newline + (bb.params map { case (n, p) => emitParam(n, p) } mkString newline) - case mod: DefModule => { - // Preprocess whens & elsewhens, marking those that have no alternative - val procMod = mod.copy(commands = processWhens(mod.commands)) - for (cmd <- procMod.commands) { body ++= newline + emit(cmd, procMod)} - } - } - body ++= newline - } - body.toString() - } - - /** Returns the FIRRTL declaration and body of a module, or nothing if it's a - * duplicate of something already emitted (on the basis of simple string - * matching). - */ - private def emit(m: Component): String = { - // Generate the body. - val sb = new StringBuilder - sb.append(moduleDecl(m)) - sb.append(moduleDefn(m)) - sb.result - } - - /** Preprocess the command queue, marking when/elsewhen statements - * that have no alternatives (elsewhens or otherwise). These - * alternative-free statements reset the indent level to the - * enclosing block upon emission. - */ - private def processWhens(cmds: Seq[Command]): Seq[Command] = { - if (cmds.isEmpty) { - Seq.empty - } else { - cmds.zip(cmds.tail).map{ - case (a: WhenEnd, b: AltBegin) => a.copy(hasAlt = true) - case (a, b) => a - } ++ cmds.lastOption - } - } - - private var indentLevel = 0 - private def newline = "\n" + (" " * indentLevel) - private def indent(): Unit = indentLevel += 1 - private def unindent() { require(indentLevel > 0); indentLevel -= 1 } - private def withIndent(f: => Unit) { indent(); f; unindent() } - - private val res = new StringBuilder() - res ++= s";${BuildInfo.toString}\n" - res ++= s"circuit ${circuit.name} : " - withIndent { circuit.components.foreach(c => res ++= emit(c)) } - res ++= newline } + diff --git a/src/test/scala/chiselTests/PrintableSpec.scala b/src/test/scala/chiselTests/PrintableSpec.scala index 0325d3bc..25b54966 100644 --- a/src/test/scala/chiselTests/PrintableSpec.scala +++ b/src/test/scala/chiselTests/PrintableSpec.scala @@ -150,7 +150,6 @@ class PrintableSpec extends AnyFlatSpec with Matchers { printf(p"${FullName(myInst.io.fizz)}") } val firrtl = ChiselStage.emitChirrtl(new MyModule) - println(firrtl) getPrintfs(firrtl) match { case Seq(Printf("foo", Seq()), Printf("myWire.foo", Seq()), @@ -256,8 +255,8 @@ class PrintableSpec extends AnyFlatSpec with Matchers { val firLines = scala.io.Source.fromFile(firFile).getLines.toList // check that verification components have expected names - exactly(1, firLines) should include ("""printf(clock, UInt<1>(1), "hello AnonymousBundle(foo -> %d, bar -> %d)", myBun.foo, myBun.bar): howdy""") - exactly(1, firLines) should include ("""printf(clock, UInt<1>(1), "goodbye AnonymousBundle(foo -> %d, bar -> %d)", myBun.foo, myBun.bar): SIM""") - exactly(1, firLines) should include ("""printf(clock, UInt<1>(1), "adieu AnonymousBundle(foo -> %d, bar -> %d)", myBun.foo, myBun.bar): farewell""") + exactly(1, firLines) should include ("""printf(clock, UInt<1>("h1"), "hello AnonymousBundle(foo -> %d, bar -> %d)", myBun.foo, myBun.bar) : howdy""") + exactly(1, firLines) should include ("""printf(clock, UInt<1>("h1"), "goodbye AnonymousBundle(foo -> %d, bar -> %d)", myBun.foo, myBun.bar) : SIM""") + exactly(1, firLines) should include ("""printf(clock, UInt<1>("h1"), "adieu AnonymousBundle(foo -> %d, bar -> %d)", myBun.foo, myBun.bar) : farewell""") } } diff --git a/src/test/scala/chiselTests/VecLiteralSpec.scala b/src/test/scala/chiselTests/VecLiteralSpec.scala index d11289e1..d91cd2f4 100644 --- a/src/test/scala/chiselTests/VecLiteralSpec.scala +++ b/src/test/scala/chiselTests/VecLiteralSpec.scala @@ -461,10 +461,10 @@ class VecLiteralSpec extends ChiselFreeSpec with Utils { "vec literals can contain bundles" in { val chirrtl = (new chisel3.stage.ChiselStage).emitChirrtl(new VecExample, args = Array("--full-stacktrace")) - chirrtl should include("""out[0].bar <= UInt<5>("h016")""") - chirrtl should include("""out[0].foo <= UInt<6>("h02a")""") - chirrtl should include("""out[1].bar <= UInt<2>("h03")""") - chirrtl should include("""out[1].foo <= UInt<3>("h07")""") + 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")""") } diff --git a/src/test/scala/chiselTests/experimental/verification/VerificationSpec.scala b/src/test/scala/chiselTests/experimental/verification/VerificationSpec.scala index a1fc2a1d..1e080739 100644 --- a/src/test/scala/chiselTests/experimental/verification/VerificationSpec.scala +++ b/src/test/scala/chiselTests/experimental/verification/VerificationSpec.scala @@ -104,9 +104,9 @@ class VerificationSpec extends ChiselPropSpec with Matchers { val firLines = scala.io.Source.fromFile(firFile).getLines.toList // check that verification components have expected names - exactly(1, firLines) should include ("cover(clock, _T, UInt<1>(1), \"\") : cov") - exactly(1, firLines) should include ("assume(clock, _T_3, UInt<1>(1), \"\") : assm") - exactly(1, firLines) should include ("assert(clock, _T_6, UInt<1>(1), \"\") : asst") + exactly(1, firLines) should include ("cover(clock, _T, UInt<1>(\"h1\"), \"\") : cov") + exactly(1, firLines) should include ("assume(clock, _T_3, UInt<1>(\"h1\"), \"\") : assm") + exactly(1, firLines) should include ("assert(clock, _T_6, UInt<1>(\"h1\"), \"\") : asst") } property("annotation of verification constructs with suggested name should work") { @@ -148,7 +148,7 @@ class VerificationSpec extends ChiselPropSpec with Matchers { val firLines = scala.io.Source.fromFile(firFile).getLines.toList // check that verification components have expected names - exactly(1, firLines) should include ("assert(clock, _T, UInt<1>(1), \"\") : hello") - exactly(1, firLines) should include ("assume(clock, _T_3, UInt<1>(1), \"\") : howdy") + exactly(1, firLines) should include ("assert(clock, _T, UInt<1>(\"h1\"), \"\") : hello") + exactly(1, firLines) should include ("assume(clock, _T_3, UInt<1>(\"h1\"), \"\") : howdy") } } -- cgit v1.2.3 From d9c30ea0fdd90f3e47cae161f4a7224a861a6bf0 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Fri, 20 Aug 2021 12:00:08 -0700 Subject: Emit .fir lazily, overcomes JVM 2 GiB String limit --- src/main/scala/chisel3/internal/firrtl/Emitter.scala | 15 +++++++++++++-- src/main/scala/chisel3/stage/ChiselAnnotations.scala | 7 +++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/main/scala/chisel3/internal/firrtl/Emitter.scala b/src/main/scala/chisel3/internal/firrtl/Emitter.scala index 53329908..a94558ce 100644 --- a/src/main/scala/chisel3/internal/firrtl/Emitter.scala +++ b/src/main/scala/chisel3/internal/firrtl/Emitter.scala @@ -1,12 +1,23 @@ // SPDX-License-Identifier: Apache-2.0 package chisel3.internal.firrtl -import firrtl.{ir => fir} + +import scala.collection.immutable.LazyList // Needed for 2.12 alias +import firrtl.ir.Serializer private[chisel3] object Emitter { def emit(circuit: Circuit): String = { val fcircuit = Converter.convertLazily(circuit) - fir.Serializer.serialize(fcircuit) + Serializer.serialize(fcircuit) + } + + def emitLazily(circuit: Circuit): Iterable[String] = { + val result = LazyList(s"circuit ${circuit.name} :\n") + val modules = circuit.components.view.map(Converter.convert) + val moduleStrings = modules.flatMap { m => + Array(Serializer.serialize(m, 1), "\n\n") + } + result ++ moduleStrings } } diff --git a/src/main/scala/chisel3/stage/ChiselAnnotations.scala b/src/main/scala/chisel3/stage/ChiselAnnotations.scala index b071c60f..6cace05f 100644 --- a/src/main/scala/chisel3/stage/ChiselAnnotations.scala +++ b/src/main/scala/chisel3/stage/ChiselAnnotations.scala @@ -133,9 +133,12 @@ case class CircuitSerializationAnnotation(circuit: Circuit, filename: String, fo protected def suffix: Option[String] = Some(format.extension) - // TODO Use lazy Iterables so that we don't have to materialize full intermediate data structures override def getBytes: Iterable[Byte] = format match { - case FirrtlFileFormat => OldEmitter.emit(circuit).getBytes + case FirrtlFileFormat => + OldEmitter.emitLazily(circuit) + .map(_.getBytes) + .flatten + // TODO Use lazy Iterables so that we don't have to materialize full intermediate data structures case ProtoBufFileFormat => val ostream = new java.io.ByteArrayOutputStream val modules = circuit.components.map(m => () => chisel3.internal.firrtl.Converter.convert(m)) -- cgit v1.2.3 From a6eb2ad8b6ff50bf245d610891808e436b19ed01 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Fri, 20 Aug 2021 17:34:17 -0700 Subject: Use BufferedCustomFileEmission in CircuitSerializationAnnotation --- src/main/scala/chisel3/stage/ChiselAnnotations.scala | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/scala/chisel3/stage/ChiselAnnotations.scala b/src/main/scala/chisel3/stage/ChiselAnnotations.scala index 6cace05f..de47ef36 100644 --- a/src/main/scala/chisel3/stage/ChiselAnnotations.scala +++ b/src/main/scala/chisel3/stage/ChiselAnnotations.scala @@ -3,7 +3,7 @@ package chisel3.stage import firrtl.annotations.{Annotation, NoTargetAnnotation} -import firrtl.options.{CustomFileEmission, HasShellOptions, OptionsException, ShellOption, StageOptions, Unserializable} +import firrtl.options.{BufferedCustomFileEmission, CustomFileEmission, HasShellOptions, OptionsException, ShellOption, StageOptions, Unserializable} import firrtl.options.Viewer.view import chisel3.{ChiselException, Module} import chisel3.RawModule @@ -123,7 +123,7 @@ import CircuitSerializationAnnotation._ */ case class CircuitSerializationAnnotation(circuit: Circuit, filename: String, format: Format) extends NoTargetAnnotation - with CustomFileEmission { + with BufferedCustomFileEmission { /* Caching the hashCode for a large circuit is necessary due to repeated queries. * Not caching the hashCode will cause severe performance degredations for large [[Circuit]]s. */ @@ -133,17 +133,16 @@ case class CircuitSerializationAnnotation(circuit: Circuit, filename: String, fo protected def suffix: Option[String] = Some(format.extension) - override def getBytes: Iterable[Byte] = format match { + override def getBytesBuffered: Iterable[Array[Byte]] = format match { case FirrtlFileFormat => OldEmitter.emitLazily(circuit) .map(_.getBytes) - .flatten // TODO Use lazy Iterables so that we don't have to materialize full intermediate data structures case ProtoBufFileFormat => val ostream = new java.io.ByteArrayOutputStream val modules = circuit.components.map(m => () => chisel3.internal.firrtl.Converter.convert(m)) firrtl.proto.ToProto.writeToStreamFast(ostream, firrtl.ir.NoInfo, modules, circuit.name) - ostream.toByteArray + List(ostream.toByteArray) } } -- cgit v1.2.3 From bf46afcebcb13e51d1e8c96ea2755fdcb352db4c Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 24 Aug 2021 13:18:23 -0700 Subject: Add backport policy to PR Template (#2085) --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index bcc4d289..1077ec29 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -44,7 +44,7 @@ Text from here to the end of the body will be considered for inclusion in the re ### Reviewer Checklist (only modified by reviewer) - [ ] Did you add the appropriate labels? -- [ ] Did you mark the proper milestone (3.2.x, 3.3.x, 3.4.x, 3.5.0) ? +- [ ] Did you mark the proper milestone (Bug fix: `3.3.x`, [small] API extension: `3.4.x`, API modification or big change: `3.5.0`)? - [ ] Did you review? - [ ] Did you check whether all relevant Contributor checkboxes have been checked? - [ ] Did you mark as `Please Merge`? -- cgit v1.2.3 From 3840fec3d918f23df07a18311136ac6a1bc365e1 Mon Sep 17 00:00:00 2001 From: Kevin Laeufer Date: Wed, 25 Aug 2021 12:38:56 -0700 Subject: replace custom model checker with chiseltest formal verify command (#2075) * replace custom model checker with chiseltest formal verify command * integration-tests can make use of chiseltest This is a compromise solution to avoid issues with binary compatibility breaking changes in chisel3. * ci: move integration tests into separate job * run integration tests only for one scala version * ci: install espresso for integration tests * Update build.sbt Co-authored-by: Jack Koenig Co-authored-by: Jack Koenig --- .github/workflows/test.yml | 29 ++- build.sbt | 13 +- .../util/experimental/DecoderSpec.scala | 53 ++++ .../util/experimental/minimizer/EspressoSpec.scala | 9 + .../experimental/minimizer/MinimizerSpec.scala | 275 ++++++++++++++++++++ .../util/experimental/minimizer/QMCSpec.scala | 9 + .../scala/chiselTests/SMTModelCheckingSpec.scala | 104 -------- .../util/experimental/DecoderSpec.scala | 61 ----- .../util/experimental/minimizer/EspressoSpec.scala | 9 - .../experimental/minimizer/MinimizerSpec.scala | 277 --------------------- .../util/experimental/minimizer/QMCSpec.scala | 9 - 11 files changed, 386 insertions(+), 462 deletions(-) create mode 100644 integration-tests/src/test/scala/chiselTests/util/experimental/DecoderSpec.scala create mode 100644 integration-tests/src/test/scala/chiselTests/util/experimental/minimizer/EspressoSpec.scala create mode 100644 integration-tests/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala create mode 100644 integration-tests/src/test/scala/chiselTests/util/experimental/minimizer/QMCSpec.scala delete mode 100644 src/test/scala/chiselTests/SMTModelCheckingSpec.scala delete mode 100644 src/test/scala/chiselTests/util/experimental/DecoderSpec.scala delete mode 100644 src/test/scala/chiselTests/util/experimental/minimizer/EspressoSpec.scala delete mode 100644 src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala delete mode 100644 src/test/scala/chiselTests/util/experimental/minimizer/QMCSpec.scala diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3438dcf9..a2240ff8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -113,13 +113,40 @@ jobs: - name: Binary compatibility run: sbt ++${{ matrix.scala }} mimaReportBinaryIssues + integration: + name: Integration Tests (w/ chiseltest) + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Install Verilator and Z3 + run: | + sudo apt-get install -y verilator z3 + verilator --version + z3 --version + - name: Install Espresso + run: | + cd /tmp + wget https://github.com/chipsalliance/espresso/releases/download/v2.4/x86_64-linux-gnu-espresso + chmod +x x86_64-linux-gnu-espresso + sudo mv x86_64-linux-gnu-espresso /usr/local/bin/espresso + espresso || true + - name: Setup Scala + uses: olafurpg/setup-scala@v10 + with: + java-version: "adopt@1.11" + - name: Cache Scala + uses: coursier/cache-action@v5 + - name: Integration Tests + run: sbt integrationTests/test + # Sentinel job to simplify how we specify which checks need to pass in branch # protection and in Mergify # # When adding new jobs, please add them to `needs` below all_tests_passed: name: "all tests passed" - needs: [ci] + needs: [ci, integration] runs-on: ubuntu-20.04 steps: - run: echo Success! diff --git a/build.sbt b/build.sbt index 5d7fad2d..54bf87f0 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,8 @@ enablePlugins(SiteScaladocPlugin) val defaultVersions = Map( "firrtl" -> "edu.berkeley.cs" %% "firrtl" % "1.5-SNAPSHOT", - "treadle" -> "edu.berkeley.cs" %% "treadle" % "1.5-SNAPSHOT" + "treadle" -> "edu.berkeley.cs" %% "treadle" % "1.5-SNAPSHOT", + "chiseltest" -> "edu.berkeley.cs" %% "chiseltest" % "0.5-SNAPSHOT", ) lazy val commonSettings = Seq ( @@ -225,6 +226,16 @@ lazy val noPluginTests = (project in file ("no-plugin-tests")). libraryDependencies += defaultVersions("firrtl"), )) +// tests elaborating and executing/formally verifying a Chisel circuit with chiseltest +lazy val integrationTests = (project in file ("integration-tests")). + dependsOn(chisel). + settings(commonSettings: _*). + settings(chiselSettings: _*). + settings(usePluginSettings: _*). + settings(Seq( + libraryDependencies += defaultVersions("chiseltest") % "test", + )) + lazy val docs = project // new documentation project .in(file("docs-target")) // important: it must not be docs/ .dependsOn(chisel) diff --git a/integration-tests/src/test/scala/chiselTests/util/experimental/DecoderSpec.scala b/integration-tests/src/test/scala/chiselTests/util/experimental/DecoderSpec.scala new file mode 100644 index 00000000..c31fdee0 --- /dev/null +++ b/integration-tests/src/test/scala/chiselTests/util/experimental/DecoderSpec.scala @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.util.experimental + +import chisel3.util.experimental.decode.{DecodeTableAnnotation, Minimizer, QMCMinimizer, TruthTable} +import chiselTests.util.experimental.minimizer.DecodeTestModule +import firrtl.annotations.ReferenceTarget +import org.scalatest.flatspec.AnyFlatSpec +import chiseltest._ +import chiseltest.formal._ + +class DecoderSpec extends AnyFlatSpec with ChiselScalatestTester with Formal { + val xor = TruthTable( + """10->1 + |01->1 + | 0""".stripMargin) + + def minimizer: Minimizer = QMCMinimizer + + "decoder" should "pass without DecodeTableAnnotation" in { + verify(new DecodeTestModule(minimizer, table = xor), Seq(BoundedCheck(1))) + } + + "decoder" should "fail with a incorrect DecodeTableAnnotation" in { + val annos = Seq( + DecodeTableAnnotation(ReferenceTarget("", "", Nil, "", Nil), + """10->1 + |01->1 + | 0""".stripMargin, + """10->1 + | 0""".stripMargin + ) + ) + assertThrows[FailedBoundedCheckException] { + verify(new DecodeTestModule(minimizer, table = xor), BoundedCheck(1) +: annos) + } + } + + "decoder" should "success with a correct DecodeTableAnnotation" in { + val annos = Seq( + DecodeTableAnnotation(ReferenceTarget("", "", Nil, "", Nil), + """10->1 + |01->1 + | 0""".stripMargin, + QMCMinimizer.minimize(TruthTable( + """10->1 + |01->1 + | 0""".stripMargin)).toString + ) + ) + verify(new DecodeTestModule(minimizer, table = xor), BoundedCheck(1) +: annos) + } +} diff --git a/integration-tests/src/test/scala/chiselTests/util/experimental/minimizer/EspressoSpec.scala b/integration-tests/src/test/scala/chiselTests/util/experimental/minimizer/EspressoSpec.scala new file mode 100644 index 00000000..f3270cae --- /dev/null +++ b/integration-tests/src/test/scala/chiselTests/util/experimental/minimizer/EspressoSpec.scala @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.util.experimental.minimizer +import chisel3.util.experimental.decode.EspressoMinimizer +import chisel3.util.experimental.decode.Minimizer + +class EspressoSpec extends MinimizerSpec { + override def minimizer: Minimizer = EspressoMinimizer +} diff --git a/integration-tests/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala b/integration-tests/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala new file mode 100644 index 00000000..a932eb77 --- /dev/null +++ b/integration-tests/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.util.experimental.minimizer + +import chisel3._ +import chisel3.util._ +import chisel3.util.experimental.decode._ +import chisel3.util.pla +import chiseltest._ +import chiseltest.formal._ +import org.scalatest.flatspec.AnyFlatSpec + +class DecodeTestModule(minimizer: Minimizer, table: TruthTable) extends Module { + val i = IO(Input(UInt(table.table.head._1.getWidth.W))) + val (unminimizedI, unminimizedO) = pla(table.table.toSeq) + unminimizedI := i + val minimizedO: UInt = decoder(minimizer, i, table) + + chisel3.experimental.verification.assert( + // for each instruction, if input matches, output should match, not no matched, fallback to default + (table.table.map { case (key, value) => (i === key) && (minimizedO === value) } ++ + Seq(table.table.keys.map(i =/= _).reduce(_ && _) && minimizedO === table.default)).reduce(_ || _) + ) +} + +trait MinimizerSpec extends AnyFlatSpec with ChiselScalatestTester with Formal { + def minimizer: Minimizer + + def minimizerTest(testcase: TruthTable) = { + verify(new DecodeTestModule(minimizer, table = testcase), Seq(BoundedCheck(1))) + } + + // Term that being commented out is the result of which is same as default, + // making optimization opportunities to decoder algorithms + + "case0" should "pass" in { + minimizerTest(TruthTable( + Map( + // BitPat("b000") -> BitPat("b0"), + BitPat("b001") -> BitPat("b?"), + BitPat("b010") -> BitPat("b?"), + // BitPat("b011") -> BitPat("b0"), + BitPat("b100") -> BitPat("b1"), + BitPat("b101") -> BitPat("b1"), + // BitPat("b110") -> BitPat("b0"), + BitPat("b111") -> BitPat("b1") + ), + BitPat("b0") + )) + } + + "case1" should "pass" in { + minimizerTest(TruthTable( + Map( + BitPat("b000") -> BitPat("b0"), + BitPat("b001") -> BitPat("b?"), + BitPat("b010") -> BitPat("b?"), + BitPat("b011") -> BitPat("b0"), + // BitPat("b100") -> BitPat("b1"), + // BitPat("b101") -> BitPat("b1"), + BitPat("b110") -> BitPat("b0"), + // BitPat("b111") -> BitPat("b1") + ), + BitPat("b1") + )) + } + + "caseX" should "pass" in { + minimizerTest(TruthTable( + Map( + BitPat("b000") -> BitPat("b0"), + // BitPat("b001") -> BitPat("b?"), + // BitPat("b010") -> BitPat("b?"), + BitPat("b011") -> BitPat("b0"), + BitPat("b100") -> BitPat("b1"), + BitPat("b101") -> BitPat("b1"), + BitPat("b110") -> BitPat("b0"), + BitPat("b111") -> BitPat("b1") + ), + BitPat("b?") + )) + } + + "caseMultiDefault" should "pass" in { + minimizerTest(TruthTable( + Map( + BitPat("b000") -> BitPat("b0100"), + BitPat("b001") -> BitPat("b?111"), + BitPat("b010") -> BitPat("b?000"), + BitPat("b011") -> BitPat("b0101"), + BitPat("b111") -> BitPat("b1101") + ), + BitPat("b?100") + )) + } + + "case7SegDecoder" should "pass" in { + minimizerTest(TruthTable( + Map( + BitPat("b0000") -> BitPat("b111111001"), + BitPat("b0001") -> BitPat("b011000001"), + BitPat("b0010") -> BitPat("b110110101"), + BitPat("b0011") -> BitPat("b111100101"), + BitPat("b0100") -> BitPat("b011001101"), + BitPat("b0101") -> BitPat("b101101101"), + BitPat("b0110") -> BitPat("b101111101"), + BitPat("b0111") -> BitPat("b111000001"), + BitPat("b1000") -> BitPat("b111111101"), + BitPat("b1001") -> BitPat("b111101101"), + ), + BitPat("b???????10") + )) + } + + // A simple RV32I decode table example + "caseRV32I" should "pass" in { + val BEQ = "?????????????????000?????1100011" + val BNE = "?????????????????001?????1100011" + val BLT = "?????????????????100?????1100011" + val BGE = "?????????????????101?????1100011" + val BLTU = "?????????????????110?????1100011" + val BGEU = "?????????????????111?????1100011" + val JALR = "?????????????????000?????1100111" + val JAL = "?????????????????????????1101111" + val LUI = "?????????????????????????0110111" + val AUIPC = "?????????????????????????0010111" + val ADDI = "?????????????????000?????0010011" + val SLTI = "?????????????????010?????0010011" + val SLTIU = "?????????????????011?????0010011" + val XORI = "?????????????????100?????0010011" + val ORI = "?????????????????110?????0010011" + val ANDI = "?????????????????111?????0010011" + val ADD = "0000000??????????000?????0110011" + val SUB = "0100000??????????000?????0110011" + val SLL = "0000000??????????001?????0110011" + val SLT = "0000000??????????010?????0110011" + val SLTU = "0000000??????????011?????0110011" + val XOR = "0000000??????????100?????0110011" + val SRL = "0000000??????????101?????0110011" + val SRA = "0100000??????????101?????0110011" + val OR = "0000000??????????110?????0110011" + val AND = "0000000??????????111?????0110011" + val LB = "?????????????????000?????0000011" + val LH = "?????????????????001?????0000011" + val LW = "?????????????????010?????0000011" + val LBU = "?????????????????100?????0000011" + val LHU = "?????????????????101?????0000011" + val SB = "?????????????????000?????0100011" + val SH = "?????????????????001?????0100011" + val SW = "?????????????????010?????0100011" + val FENCE = "?????????????????000?????0001111" + val MRET = "00110000001000000000000001110011" + val WFI = "00010000010100000000000001110011" + val CEASE = "00110000010100000000000001110011" + val CSRRW = "?????????????????001?????1110011" + val CSRRS = "?????????????????010?????1110011" + val CSRRC = "?????????????????011?????1110011" + val CSRRWI = "?????????????????101?????1110011" + val CSRRSI = "?????????????????110?????1110011" + val CSRRCI = "?????????????????111?????1110011" + val SCALL = "00000000000000000000000001110011" + val SBREAK = "00000000000100000000000001110011" + val SLLI_RV32 = "0000000??????????001?????0010011" + val SRLI_RV32 = "0000000??????????101?????0010011" + val SRAI_RV32 = "0100000??????????101?????0010011" + + val A1_X = "??" + val A1_ZERO = "00" + val A1_RS1 = "01" + val A1_PC = "10" + + val IMM_X = "???" + val IMM_S = "000" + val IMM_SB = "001" + val IMM_U = "010" + val IMM_UJ = "011" + val IMM_I = "100" + val IMM_Z = "101" + + val A2_X = "??" + val A2_ZERO = "00" + val A2_SIZE = "01" + val A2_RS2 = "10" + val A2_IMM = "11" + + val X = "?" + val N = "0" + val Y = "1" + + val DW_X = X + val DW_XPR = Y + + val M_X = "?????" + val M_XRD = "00000" + val M_XWR = "00001" + + val CSR_X = "???" + val CSR_N = "000" + val CSR_I = "100" + val CSR_W = "101" + val CSR_S = "110" + val CSR_C = "111" + + val FN_X = "????" + val FN_ADD = "0000" + val FN_SL = "0001" + val FN_SEQ = "0010" + val FN_SNE = "0011" + val FN_XOR = "0100" + val FN_SR = "0101" + val FN_OR = "0110" + val FN_AND = "0111" + val FN_SUB = "1010" + val FN_SRA = "1011" + val FN_SLT = "1100" + val FN_SGE = "1101" + val FN_SLTU = "1110" + val FN_SGEU = "1111" + + minimizerTest(TruthTable( + Map( + BNE -> Seq(Y, N, N, Y, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_SB, DW_X, FN_SNE, N, M_X, N, N, N, N, N, N, N, CSR_N, N, N, N, N), + BEQ -> Seq(Y, N, N, Y, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_SB, DW_X, FN_SEQ, N, M_X, N, N, N, N, N, N, N, CSR_N, N, N, N, N), + BLT -> Seq(Y, N, N, Y, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_SB, DW_X, FN_SLT, N, M_X, N, N, N, N, N, N, N, CSR_N, N, N, N, N), + BLTU -> Seq(Y, N, N, Y, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_SB, DW_X, FN_SLTU, N, M_X, N, N, N, N, N, N, N, CSR_N, N, N, N, N), + BGE -> Seq(Y, N, N, Y, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_SB, DW_X, FN_SGE, N, M_X, N, N, N, N, N, N, N, CSR_N, N, N, N, N), + BGEU -> Seq(Y, N, N, Y, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_SB, DW_X, FN_SGEU, N, M_X, N, N, N, N, N, N, N, CSR_N, N, N, N, N), + JAL -> Seq(Y, N, N, N, Y, N, N, N, N, A2_SIZE, A1_PC, IMM_UJ, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + JALR -> Seq(Y, N, N, N, N, Y, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + AUIPC -> Seq(Y, N, N, N, N, N, N, N, N, A2_IMM, A1_PC, IMM_U, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + LB -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, Y, M_XRD, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + LH -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, Y, M_XRD, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + LW -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, Y, M_XRD, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + LBU -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, Y, M_XRD, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + LHU -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, Y, M_XRD, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SB -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_IMM, A1_RS1, IMM_S, DW_XPR, FN_ADD, Y, M_XWR, N, N, N, N, N, N, N, CSR_N, N, N, N, N), + SH -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_IMM, A1_RS1, IMM_S, DW_XPR, FN_ADD, Y, M_XWR, N, N, N, N, N, N, N, CSR_N, N, N, N, N), + SW -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_IMM, A1_RS1, IMM_S, DW_XPR, FN_ADD, Y, M_XWR, N, N, N, N, N, N, N, CSR_N, N, N, N, N), + LUI -> Seq(Y, N, N, N, N, N, N, N, N, A2_IMM, A1_ZERO, IMM_U, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + ADDI -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SLTI -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_SLT, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SLTIU -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_SLTU, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + ANDI -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_AND, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + ORI -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_OR, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + XORI -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_XOR, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + ADD -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SUB -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_SUB, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SLT -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_SLT, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SLTU -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_SLTU, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + AND -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_AND, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + OR -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_OR, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + XOR -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_XOR, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SLL -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_SL, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SRL -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_SR, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SRA -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_SRA, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + FENCE -> Seq(Y, N, N, N, N, N, N, N, N, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, N, N, N, N, N, N, N, CSR_N, N, Y, N, N), + SCALL -> Seq(Y, N, N, N, N, N, N, X, N, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, N, N, N, N, N, N, N, CSR_I, N, N, N, N), + SBREAK -> Seq(Y, N, N, N, N, N, N, X, N, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, N, N, N, N, N, N, N, CSR_I, N, N, N, N), + MRET -> Seq(Y, N, N, N, N, N, N, X, N, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, N, N, N, N, N, N, N, CSR_I, N, N, N, N), + WFI -> Seq(Y, N, N, N, N, N, N, X, N, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, N, N, N, N, N, N, N, CSR_I, N, N, N, N), + CEASE -> Seq(Y, N, N, N, N, N, N, X, N, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, N, N, N, N, N, N, N, CSR_I, N, N, N, N), + CSRRW -> Seq(Y, N, N, N, N, N, N, Y, N, A2_ZERO, A1_RS1, IMM_X, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_W, N, N, N, N), + CSRRS -> Seq(Y, N, N, N, N, N, N, Y, N, A2_ZERO, A1_RS1, IMM_X, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_S, N, N, N, N), + CSRRC -> Seq(Y, N, N, N, N, N, N, Y, N, A2_ZERO, A1_RS1, IMM_X, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_C, N, N, N, N), + CSRRWI -> Seq(Y, N, N, N, N, N, N, N, N, A2_IMM, A1_ZERO, IMM_Z, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_W, N, N, N, N), + CSRRSI -> Seq(Y, N, N, N, N, N, N, N, N, A2_IMM, A1_ZERO, IMM_Z, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_S, N, N, N, N), + CSRRCI -> Seq(Y, N, N, N, N, N, N, N, N, A2_IMM, A1_ZERO, IMM_Z, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_C, N, N, N, N), + SLLI_RV32 -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_SL, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SRLI_RV32 -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_SR, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + SRAI_RV32 -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_SRA, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), + ).map { case (k, v) => BitPat(s"b$k") -> BitPat(s"b${v.reduce(_ + _)}") }, + BitPat(s"b${Seq(N, X, X, X, X, X, X, X, X, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, X, X, X, X, X, X, X, CSR_X, X, X, X, X).reduce(_ + _)}") + )) + } +} diff --git a/integration-tests/src/test/scala/chiselTests/util/experimental/minimizer/QMCSpec.scala b/integration-tests/src/test/scala/chiselTests/util/experimental/minimizer/QMCSpec.scala new file mode 100644 index 00000000..fc770202 --- /dev/null +++ b/integration-tests/src/test/scala/chiselTests/util/experimental/minimizer/QMCSpec.scala @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.util.experimental.minimizer +import chisel3.util.experimental.decode.Minimizer +import chisel3.util.experimental.decode.QMCMinimizer + +class QMCSpec extends MinimizerSpec { + override def minimizer: Minimizer = QMCMinimizer +} diff --git a/src/test/scala/chiselTests/SMTModelCheckingSpec.scala b/src/test/scala/chiselTests/SMTModelCheckingSpec.scala deleted file mode 100644 index 0a752b10..00000000 --- a/src/test/scala/chiselTests/SMTModelCheckingSpec.scala +++ /dev/null @@ -1,104 +0,0 @@ -package chiselTests - -import chisel3.Module -import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage} -import firrtl.annotations.Annotation -import firrtl.options.{OutputAnnotationFileAnnotation, TargetDirAnnotation} -import firrtl.stage.OutputFileAnnotation -import firrtl.util.BackendCompilationUtilities.timeStamp -import logger.{LazyLogging, LogLevel, LogLevelAnnotation} -import org.scalatest.flatspec.AnyFlatSpec -import os._ -import scala.util.Properties - -/** [[SMTModelCheckingSpec]] use z3 and [[firrtl.backends.experimental.smt]] library - * to solve `assert/assume` in [[chisel3.experimental.verification]], - * It is a copy&paste version from `firrtl.backends.experimental.smt.end2end.EndToEndSMTBaseSpec` from firrtl - * Useful to check combinational logic and some small test. - */ -abstract class SMTModelCheckingSpec extends AnyFlatSpec { - def success = MCSuccess - - def fail(k: Int) = MCFail(k) - - def test(dut: () => Module, name: String, expected: MCResult, kmax: Int = 0, annos: Seq[Annotation] = Seq()): Unit = { - expected match { - case MCFail(k) => - assert(kmax >= k, s"Please set a kmax that includes the expected failing step! ($kmax < $expected)") - case _ => - } - // @todo rewrite BackendCompilationUtilities - val testBaseDir = os.pwd / "test_run_dir" / name - os.makeDir.all(testBaseDir) - val testDir = os.temp.dir(testBaseDir, timeStamp, deleteOnExit = false) - val res = (new ChiselStage).execute( - Array("-E", "experimental-smt2"), - Seq( - LogLevelAnnotation(LogLevel.Error), // silence warnings for tests - ChiselGeneratorAnnotation(dut), - TargetDirAnnotation(testDir.toString) - ) ++ annos - ) - val top = res.collectFirst{case OutputAnnotationFileAnnotation(top) => top}.get - assert(res.collectFirst { case _: OutputFileAnnotation => true }.isDefined) - val r = Z3ModelChecker.bmc(testDir, top, kmax) - assert(r == expected) - } -} - -private object Z3ModelChecker extends LazyLogging { - def bmc(testDir: Path, main: String, kmax: Int): MCResult = { - assert(kmax >= 0 && kmax < 50, "Trying to keep kmax in a reasonable range.") - Seq.tabulate(kmax + 1) { k => - val stepFile = testDir / s"${main}_step$k.smt2" - os.copy(testDir / s"$main.smt2", stepFile) - os.write.append(stepFile, - s"""${step(main, k)} - |(check-sat) - |""".stripMargin) - val success = executeStep(stepFile) - if (!success) return MCFail(k) - } - MCSuccess - } - - private def executeStep(path: Path): Boolean = { - val (out, ret) = executeCmd(path.toString) - assert(ret == 0, s"expected success (0), not $ret: `$out`\nz3 ${path.toString}") - assert(out == "sat" + Properties.lineSeparator || out == "unsat" + Properties.lineSeparator, s"Unexpected output: $out") - out == "unsat" + Properties.lineSeparator - } - - private def executeCmd(cmd: String): (String, Int) = { - val process = os.proc("z3", cmd).call(stderr = ProcessOutput.Readlines(logger.warn(_))) - (process.out.chunks.mkString, process.exitCode) - } - - private def step(main: String, k: Int): String = { - // define all states - (0 to k).map(ii => s"(declare-fun s$ii () $main$StateTpe)") ++ - // assert that init holds in state 0 - List(s"(assert ($main$Init s0))") ++ - // assert transition relation - (0 until k).map(ii => s"(assert ($main$Transition s$ii s${ii + 1}))") ++ - // assert that assumptions hold in all states - (0 to k).map(ii => s"(assert ($main$Assumes s$ii))") ++ - // assert that assertions hold for all but last state - (0 until k).map(ii => s"(assert ($main$Asserts s$ii))") ++ - // check to see if we can violate the assertions in the last state - List(s"(assert (not ($main$Asserts s$k)))") - }.mkString("\n") - - // the following suffixes have to match the ones in [[SMTTransitionSystemEncoder]] - private val Transition = "_t" - private val Init = "_i" - private val Asserts = "_a" - private val Assumes = "_u" - private val StateTpe = "_s" -} -sealed trait MCResult - -case object MCSuccess extends MCResult - -case class MCFail(k: Int) extends MCResult - diff --git a/src/test/scala/chiselTests/util/experimental/DecoderSpec.scala b/src/test/scala/chiselTests/util/experimental/DecoderSpec.scala deleted file mode 100644 index 3c9d490d..00000000 --- a/src/test/scala/chiselTests/util/experimental/DecoderSpec.scala +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chiselTests.util.experimental - -import chisel3.util.experimental.decode.{DecodeTableAnnotation, Minimizer, QMCMinimizer, TruthTable} -import chiselTests.SMTModelCheckingSpec -import chiselTests.util.experimental.minimizer.DecodeTestModule -import firrtl.annotations.ReferenceTarget - -class DecoderSpec extends SMTModelCheckingSpec { - val xor = TruthTable( - """10->1 - |01->1 - | 0""".stripMargin) - - def minimizer: Minimizer = QMCMinimizer - - "decoder" should "pass without DecodeTableAnnotation" in { - test( - () => new DecodeTestModule(minimizer, table = xor), - s"${minimizer.getClass.getSimpleName}.noAnno", - success - ) - } - - "decoder" should "fail with a incorrect DecodeTableAnnotation" in { - test( - () => new DecodeTestModule(minimizer, table = xor), - s"${minimizer.getClass.getSimpleName}.incorrectAnno", - fail(0), - annos = Seq( - DecodeTableAnnotation(ReferenceTarget("", "", Nil, "", Nil), - """10->1 - |01->1 - | 0""".stripMargin, - """10->1 - | 0""".stripMargin - ) - ) - ) - } - - "decoder" should "success with a correct DecodeTableAnnotation" in { - test( - () => new DecodeTestModule(minimizer, table = xor), - s"${minimizer.getClass.getSimpleName}.correctAnno", - success, - annos = Seq( - DecodeTableAnnotation(ReferenceTarget("", "", Nil, "", Nil), - """10->1 - |01->1 - | 0""".stripMargin, - QMCMinimizer.minimize(TruthTable( - """10->1 - |01->1 - | 0""".stripMargin)).toString - ) - ) - ) - } -} diff --git a/src/test/scala/chiselTests/util/experimental/minimizer/EspressoSpec.scala b/src/test/scala/chiselTests/util/experimental/minimizer/EspressoSpec.scala deleted file mode 100644 index f3270cae..00000000 --- a/src/test/scala/chiselTests/util/experimental/minimizer/EspressoSpec.scala +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chiselTests.util.experimental.minimizer -import chisel3.util.experimental.decode.EspressoMinimizer -import chisel3.util.experimental.decode.Minimizer - -class EspressoSpec extends MinimizerSpec { - override def minimizer: Minimizer = EspressoMinimizer -} diff --git a/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala b/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala deleted file mode 100644 index 5e3be9a6..00000000 --- a/src/test/scala/chiselTests/util/experimental/minimizer/MinimizerSpec.scala +++ /dev/null @@ -1,277 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chiselTests.util.experimental.minimizer - -import chisel3._ -import chisel3.util._ -import chisel3.util.experimental.decode._ -import chisel3.util.pla -import chiselTests.SMTModelCheckingSpec - -class DecodeTestModule(minimizer: Minimizer, table: TruthTable) extends Module { - val i = IO(Input(UInt(table.table.head._1.getWidth.W))) - val (unminimizedI, unminimizedO) = pla(table.table.toSeq) - unminimizedI := i - val minimizedO: UInt = decoder(minimizer, i, table) - - chisel3.experimental.verification.assert( - // for each instruction, if input matches, output should match, not no matched, fallback to default - (table.table.map { case (key, value) => (i === key) && (minimizedO === value) } ++ - Seq(table.table.keys.map(i =/= _).reduce(_ && _) && minimizedO === table.default)).reduce(_ || _) - ) -} - -trait MinimizerSpec extends SMTModelCheckingSpec { - def minimizer: Minimizer - - def minimizerTest(testcase: TruthTable, caseName: String) = { - test( - () => new DecodeTestModule(minimizer, table = testcase), - s"${minimizer.getClass.getSimpleName}.$caseName", - success - ) - } - - // Term that being commented out is the result of which is same as default, - // making optimization opportunities to decoder algorithms - - "case0" should "pass" in { - minimizerTest(TruthTable( - Map( - // BitPat("b000") -> BitPat("b0"), - BitPat("b001") -> BitPat("b?"), - BitPat("b010") -> BitPat("b?"), - // BitPat("b011") -> BitPat("b0"), - BitPat("b100") -> BitPat("b1"), - BitPat("b101") -> BitPat("b1"), - // BitPat("b110") -> BitPat("b0"), - BitPat("b111") -> BitPat("b1") - ), - BitPat("b0") - ), "case0") - } - - "case1" should "pass" in { - minimizerTest(TruthTable( - Map( - BitPat("b000") -> BitPat("b0"), - BitPat("b001") -> BitPat("b?"), - BitPat("b010") -> BitPat("b?"), - BitPat("b011") -> BitPat("b0"), - // BitPat("b100") -> BitPat("b1"), - // BitPat("b101") -> BitPat("b1"), - BitPat("b110") -> BitPat("b0"), - // BitPat("b111") -> BitPat("b1") - ), - BitPat("b1") - ), "case1") - } - - "caseX" should "pass" in { - minimizerTest(TruthTable( - Map( - BitPat("b000") -> BitPat("b0"), - // BitPat("b001") -> BitPat("b?"), - // BitPat("b010") -> BitPat("b?"), - BitPat("b011") -> BitPat("b0"), - BitPat("b100") -> BitPat("b1"), - BitPat("b101") -> BitPat("b1"), - BitPat("b110") -> BitPat("b0"), - BitPat("b111") -> BitPat("b1") - ), - BitPat("b?") - ), "caseX") - } - - "caseMultiDefault" should "pass" in { - minimizerTest(TruthTable( - Map( - BitPat("b000") -> BitPat("b0100"), - BitPat("b001") -> BitPat("b?111"), - BitPat("b010") -> BitPat("b?000"), - BitPat("b011") -> BitPat("b0101"), - BitPat("b111") -> BitPat("b1101") - ), - BitPat("b?100") - ), "caseMultiDefault") - } - - "case7SegDecoder" should "pass" in { - minimizerTest(TruthTable( - Map( - BitPat("b0000") -> BitPat("b111111001"), - BitPat("b0001") -> BitPat("b011000001"), - BitPat("b0010") -> BitPat("b110110101"), - BitPat("b0011") -> BitPat("b111100101"), - BitPat("b0100") -> BitPat("b011001101"), - BitPat("b0101") -> BitPat("b101101101"), - BitPat("b0110") -> BitPat("b101111101"), - BitPat("b0111") -> BitPat("b111000001"), - BitPat("b1000") -> BitPat("b111111101"), - BitPat("b1001") -> BitPat("b111101101"), - ), - BitPat("b???????10") - ), "case7SegDecoder") - } - - // A simple RV32I decode table example - "caseRV32I" should "pass" in { - val BEQ = "?????????????????000?????1100011" - val BNE = "?????????????????001?????1100011" - val BLT = "?????????????????100?????1100011" - val BGE = "?????????????????101?????1100011" - val BLTU = "?????????????????110?????1100011" - val BGEU = "?????????????????111?????1100011" - val JALR = "?????????????????000?????1100111" - val JAL = "?????????????????????????1101111" - val LUI = "?????????????????????????0110111" - val AUIPC = "?????????????????????????0010111" - val ADDI = "?????????????????000?????0010011" - val SLTI = "?????????????????010?????0010011" - val SLTIU = "?????????????????011?????0010011" - val XORI = "?????????????????100?????0010011" - val ORI = "?????????????????110?????0010011" - val ANDI = "?????????????????111?????0010011" - val ADD = "0000000??????????000?????0110011" - val SUB = "0100000??????????000?????0110011" - val SLL = "0000000??????????001?????0110011" - val SLT = "0000000??????????010?????0110011" - val SLTU = "0000000??????????011?????0110011" - val XOR = "0000000??????????100?????0110011" - val SRL = "0000000??????????101?????0110011" - val SRA = "0100000??????????101?????0110011" - val OR = "0000000??????????110?????0110011" - val AND = "0000000??????????111?????0110011" - val LB = "?????????????????000?????0000011" - val LH = "?????????????????001?????0000011" - val LW = "?????????????????010?????0000011" - val LBU = "?????????????????100?????0000011" - val LHU = "?????????????????101?????0000011" - val SB = "?????????????????000?????0100011" - val SH = "?????????????????001?????0100011" - val SW = "?????????????????010?????0100011" - val FENCE = "?????????????????000?????0001111" - val MRET = "00110000001000000000000001110011" - val WFI = "00010000010100000000000001110011" - val CEASE = "00110000010100000000000001110011" - val CSRRW = "?????????????????001?????1110011" - val CSRRS = "?????????????????010?????1110011" - val CSRRC = "?????????????????011?????1110011" - val CSRRWI = "?????????????????101?????1110011" - val CSRRSI = "?????????????????110?????1110011" - val CSRRCI = "?????????????????111?????1110011" - val SCALL = "00000000000000000000000001110011" - val SBREAK = "00000000000100000000000001110011" - val SLLI_RV32 = "0000000??????????001?????0010011" - val SRLI_RV32 = "0000000??????????101?????0010011" - val SRAI_RV32 = "0100000??????????101?????0010011" - - val A1_X = "??" - val A1_ZERO = "00" - val A1_RS1 = "01" - val A1_PC = "10" - - val IMM_X = "???" - val IMM_S = "000" - val IMM_SB = "001" - val IMM_U = "010" - val IMM_UJ = "011" - val IMM_I = "100" - val IMM_Z = "101" - - val A2_X = "??" - val A2_ZERO = "00" - val A2_SIZE = "01" - val A2_RS2 = "10" - val A2_IMM = "11" - - val X = "?" - val N = "0" - val Y = "1" - - val DW_X = X - val DW_XPR = Y - - val M_X = "?????" - val M_XRD = "00000" - val M_XWR = "00001" - - val CSR_X = "???" - val CSR_N = "000" - val CSR_I = "100" - val CSR_W = "101" - val CSR_S = "110" - val CSR_C = "111" - - val FN_X = "????" - val FN_ADD = "0000" - val FN_SL = "0001" - val FN_SEQ = "0010" - val FN_SNE = "0011" - val FN_XOR = "0100" - val FN_SR = "0101" - val FN_OR = "0110" - val FN_AND = "0111" - val FN_SUB = "1010" - val FN_SRA = "1011" - val FN_SLT = "1100" - val FN_SGE = "1101" - val FN_SLTU = "1110" - val FN_SGEU = "1111" - - minimizerTest(TruthTable( - Map( - BNE -> Seq(Y, N, N, Y, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_SB, DW_X, FN_SNE, N, M_X, N, N, N, N, N, N, N, CSR_N, N, N, N, N), - BEQ -> Seq(Y, N, N, Y, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_SB, DW_X, FN_SEQ, N, M_X, N, N, N, N, N, N, N, CSR_N, N, N, N, N), - BLT -> Seq(Y, N, N, Y, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_SB, DW_X, FN_SLT, N, M_X, N, N, N, N, N, N, N, CSR_N, N, N, N, N), - BLTU -> Seq(Y, N, N, Y, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_SB, DW_X, FN_SLTU, N, M_X, N, N, N, N, N, N, N, CSR_N, N, N, N, N), - BGE -> Seq(Y, N, N, Y, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_SB, DW_X, FN_SGE, N, M_X, N, N, N, N, N, N, N, CSR_N, N, N, N, N), - BGEU -> Seq(Y, N, N, Y, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_SB, DW_X, FN_SGEU, N, M_X, N, N, N, N, N, N, N, CSR_N, N, N, N, N), - JAL -> Seq(Y, N, N, N, Y, N, N, N, N, A2_SIZE, A1_PC, IMM_UJ, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - JALR -> Seq(Y, N, N, N, N, Y, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - AUIPC -> Seq(Y, N, N, N, N, N, N, N, N, A2_IMM, A1_PC, IMM_U, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - LB -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, Y, M_XRD, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - LH -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, Y, M_XRD, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - LW -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, Y, M_XRD, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - LBU -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, Y, M_XRD, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - LHU -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, Y, M_XRD, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - SB -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_IMM, A1_RS1, IMM_S, DW_XPR, FN_ADD, Y, M_XWR, N, N, N, N, N, N, N, CSR_N, N, N, N, N), - SH -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_IMM, A1_RS1, IMM_S, DW_XPR, FN_ADD, Y, M_XWR, N, N, N, N, N, N, N, CSR_N, N, N, N, N), - SW -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_IMM, A1_RS1, IMM_S, DW_XPR, FN_ADD, Y, M_XWR, N, N, N, N, N, N, N, CSR_N, N, N, N, N), - LUI -> Seq(Y, N, N, N, N, N, N, N, N, A2_IMM, A1_ZERO, IMM_U, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - ADDI -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - SLTI -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_SLT, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - SLTIU -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_SLTU, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - ANDI -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_AND, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - ORI -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_OR, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - XORI -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_XOR, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - ADD -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - SUB -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_SUB, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - SLT -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_SLT, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - SLTU -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_SLTU, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - AND -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_AND, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - OR -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_OR, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - XOR -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_XOR, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - SLL -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_SL, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - SRL -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_SR, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - SRA -> Seq(Y, N, N, N, N, N, Y, Y, N, A2_RS2, A1_RS1, IMM_X, DW_XPR, FN_SRA, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - FENCE -> Seq(Y, N, N, N, N, N, N, N, N, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, N, N, N, N, N, N, N, CSR_N, N, Y, N, N), - SCALL -> Seq(Y, N, N, N, N, N, N, X, N, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, N, N, N, N, N, N, N, CSR_I, N, N, N, N), - SBREAK -> Seq(Y, N, N, N, N, N, N, X, N, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, N, N, N, N, N, N, N, CSR_I, N, N, N, N), - MRET -> Seq(Y, N, N, N, N, N, N, X, N, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, N, N, N, N, N, N, N, CSR_I, N, N, N, N), - WFI -> Seq(Y, N, N, N, N, N, N, X, N, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, N, N, N, N, N, N, N, CSR_I, N, N, N, N), - CEASE -> Seq(Y, N, N, N, N, N, N, X, N, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, N, N, N, N, N, N, N, CSR_I, N, N, N, N), - CSRRW -> Seq(Y, N, N, N, N, N, N, Y, N, A2_ZERO, A1_RS1, IMM_X, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_W, N, N, N, N), - CSRRS -> Seq(Y, N, N, N, N, N, N, Y, N, A2_ZERO, A1_RS1, IMM_X, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_S, N, N, N, N), - CSRRC -> Seq(Y, N, N, N, N, N, N, Y, N, A2_ZERO, A1_RS1, IMM_X, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_C, N, N, N, N), - CSRRWI -> Seq(Y, N, N, N, N, N, N, N, N, A2_IMM, A1_ZERO, IMM_Z, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_W, N, N, N, N), - CSRRSI -> Seq(Y, N, N, N, N, N, N, N, N, A2_IMM, A1_ZERO, IMM_Z, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_S, N, N, N, N), - CSRRCI -> Seq(Y, N, N, N, N, N, N, N, N, A2_IMM, A1_ZERO, IMM_Z, DW_XPR, FN_ADD, N, M_X, N, N, N, N, N, N, Y, CSR_C, N, N, N, N), - SLLI_RV32 -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_SL, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - SRLI_RV32 -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_SR, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - SRAI_RV32 -> Seq(Y, N, N, N, N, N, N, Y, N, A2_IMM, A1_RS1, IMM_I, DW_XPR, FN_SRA, N, M_X, N, N, N, N, N, N, Y, CSR_N, N, N, N, N), - ).map { case (k, v) => BitPat(s"b$k") -> BitPat(s"b${v.reduce(_ + _)}") }, - BitPat(s"b${Seq(N, X, X, X, X, X, X, X, X, A2_X, A1_X, IMM_X, DW_X, FN_X, N, M_X, X, X, X, X, X, X, X, CSR_X, X, X, X, X).reduce(_ + _)}") - ), "rv32i") - } -} diff --git a/src/test/scala/chiselTests/util/experimental/minimizer/QMCSpec.scala b/src/test/scala/chiselTests/util/experimental/minimizer/QMCSpec.scala deleted file mode 100644 index fc770202..00000000 --- a/src/test/scala/chiselTests/util/experimental/minimizer/QMCSpec.scala +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chiselTests.util.experimental.minimizer -import chisel3.util.experimental.decode.Minimizer -import chisel3.util.experimental.decode.QMCMinimizer - -class QMCSpec extends MinimizerSpec { - override def minimizer: Minimizer = QMCMinimizer -} -- cgit v1.2.3 From e74b978d5188d9cd28e3520912d858d228136e75 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Fri, 27 Aug 2021 04:37:34 +0800 Subject: add new APIs to BitPat (#2076) * add Y and N to BitPat. * add ## for BitPat. * add rawString API. * use rawString in decoder * add select and slice to BitPat.--- src/main/scala/chisel3/util/BitPat.scala | 53 ++++++++++++++++++---- .../experimental/decode/EspressoMinimizer.scala | 2 +- .../util/experimental/decode/QMCMinimizer.scala | 2 +- .../util/experimental/decode/TruthTable.scala | 18 ++++---- src/test/scala/chiselTests/util/BitPatSpec.scala | 23 ++++++++++ 5 files changed, 78 insertions(+), 20 deletions(-) diff --git a/src/main/scala/chisel3/util/BitPat.scala b/src/main/scala/chisel3/util/BitPat.scala index 985d22da..0dcb2466 100644 --- a/src/main/scala/chisel3/util/BitPat.scala +++ b/src/main/scala/chisel3/util/BitPat.scala @@ -58,6 +58,22 @@ object BitPat { */ def dontCare(width: Int): BitPat = BitPat("b" + ("?" * width)) + /** Creates a [[BitPat]] of all 1 of the specified bitwidth. + * + * @example {{{ + * val myY = BitPat.Y(4) // equivalent to BitPat("b1111") + * }}} + */ + def Y(width: Int = 1): BitPat = BitPat("b" + ("1" * width)) + + /** Creates a [[BitPat]] of all 0 of the specified bitwidth. + * + * @example {{{ + * val myN = BitPat.N(4) // equivalent to BitPat("b0000") + * }}} + */ + def N(width: Int = 1): BitPat = BitPat("b" + ("0" * width)) + /** Allows BitPats to be used where a UInt is expected. * * @note the BitPat must not have don't care bits (will error out otherwise) @@ -106,9 +122,11 @@ object BitPat { */ sealed class BitPat(val value: BigInt, val mask: BigInt, width: Int) extends SourceInfoDoc { def getWidth: Int = width + def apply(x: Int): BitPat = macro SourceInfoTransform.xArg + def apply(x: Int, y: Int): BitPat = macro SourceInfoTransform.xyArg def === (that: UInt): Bool = macro SourceInfoTransform.thatArg def =/= (that: UInt): Bool = macro SourceInfoTransform.thatArg - + def ## (that: BitPat): BitPat = macro SourceInfoTransform.thatArg override def equals(obj: Any): Boolean = { obj match { case y: BitPat => value == y.value && mask == y.mask && getWidth == y.getWidth @@ -116,6 +134,18 @@ sealed class BitPat(val value: BigInt, val mask: BigInt, width: Int) extends Sou } } + /** @group SourceInfoTransformMacro */ + def do_apply(x: Int)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): BitPat = { + do_apply(x, x) + } + + /** @group SourceInfoTransformMacro */ + def do_apply(x: Int, y: Int)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): BitPat = { + require(width > x && y >= 0, s"Invalid bit range ($x, $y), index should be bounded by (${width - 1}, 0)") + require(x >= y, s"Invalid bit range ($x, $y), x should be greater or equal to y.") + BitPat(s"b${rawString.slice(width - x - 1, width - y)}") + } + /** @group SourceInfoTransformMacro */ def do_=== (that: UInt) (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = { @@ -126,12 +156,19 @@ sealed class BitPat(val value: BigInt, val mask: BigInt, width: Int) extends Sou (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = { !(this === that) } - - override def toString = { - "BitPat(" + - (0 until width).map(i => - if (((mask >> i) & 1) == 1) if (((value >> i) & 1) == 1) "1" else "0" else "?" - ).reverse.reduce(_ + _) + - ")" + /** @group SourceInfoTransformMacro */ + def do_##(that: BitPat)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): BitPat = { + new BitPat((value << that.getWidth) + that.value, (mask << that.getWidth) + that.mask, this.width + that.getWidth) } + + /** Generate raw string of a BitPat. */ + def rawString: String = Seq.tabulate(width) { i => + (value.testBit(width - i - 1), mask.testBit(width - i - 1)) match { + case (true, true) => "1" + case (false, true) => "0" + case (_, false) => "?" + } + }.mkString + + override def toString = s"BitPat($rawString)" } diff --git a/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala b/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala index 3ecec048..1d725875 100644 --- a/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala +++ b/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala @@ -18,7 +18,7 @@ object EspressoMinimizer extends Minimizer with LazyLogging { .replace('1', '0') .replace('t', '1') val defaultType: Char = { - val t = table.default.toString.drop(7).dropRight(1).toCharArray.distinct + val t = table.default.rawString.toCharArray.distinct require(t.length == 1, "Internal Error: espresso only accept unified default type.") t.head } diff --git a/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala b/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala index 54faa734..c1533f44 100644 --- a/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala +++ b/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala @@ -286,7 +286,7 @@ object QMCMinimizer extends Minimizer { if (tb.table.exists(x => x._1 == t._1)) { tb.copy(table = tb.table.map { case (k, v) => if (k == t._1) { - def ones(bitPat: BitPat) = bitPat.toString.drop(7).dropRight(1).zipWithIndex.collect{case ('1', x) => x} + def ones(bitPat: BitPat) = bitPat.rawString.zipWithIndex.collect{case ('1', x) => x} (k, BitPat("b" + (0 until v.getWidth).map(i => if ((ones(v) ++ ones(t._2)).contains(i)) "1" else "?").mkString)) } else (k, v) }) diff --git a/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala b/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala index f4f200ce..683de16b 100644 --- a/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala +++ b/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala @@ -12,9 +12,9 @@ final class TruthTable(val table: Map[BitPat, BitPat], val default: BitPat) { override def toString: String = { def writeRow(map: (BitPat, BitPat)): String = - s"${TruthTable.bpStr(map._1)}->${TruthTable.bpStr(map._2)}" + s"${map._1.rawString}->${map._2.rawString}" - (table.map(writeRow) ++ Seq(s"${" "*(inputWidth + 2)}${TruthTable.bpStr(default)}")).toSeq.sorted.mkString("\n") + (table.map(writeRow) ++ Seq(s"${" "*(inputWidth + 2)}${default.rawString}")).toSeq.sorted.mkString("\n") } def copy(table: Map[BitPat, BitPat] = this.table, default: BitPat = this.default) = new TruthTable(table, default) @@ -51,11 +51,11 @@ object TruthTable { values.head._1 -> BitPat(s"b${ Seq.tabulate(outputWidth) { i => val outputSet = values.map(_._2) - .map(bpStr) + .map(_.rawString) .map(_ (i)) .toSet .filterNot(_ == '?') - require(outputSet.size != 2, s"TruthTable conflict in :\n${values.map { case (i, o) => s"${bpStr(i)}->${bpStr(o)}" }.mkString("\n")}") + require(outputSet.size != 2, s"TruthTable conflict in :\n${values.map { case (i, o) => s"${i.rawString}->${o.rawString}" }.mkString("\n")}") outputSet.headOption.getOrElse('?') }.mkString }") @@ -74,7 +74,7 @@ object TruthTable { table: TruthTable ): Seq[(TruthTable, Seq[Int])] = { def bpFilter(bitPat: BitPat, indexes: Seq[Int]): BitPat = - BitPat(s"b${bpStr(bitPat).zipWithIndex.filter(b => indexes.contains(b._2)).map(_._1).mkString}") + BitPat(s"b${bitPat.rawString.zipWithIndex.filter(b => indexes.contains(b._2)).map(_._1).mkString}") def tableFilter(indexes: Seq[Int]): Option[(TruthTable, Seq[Int])] = { if(indexes.nonEmpty) Some((TruthTable( @@ -84,7 +84,7 @@ object TruthTable { } def index(bitPat: BitPat, bpType: Char): Seq[Int] = - bpStr(bitPat).zipWithIndex.filter(_._1 == bpType).map(_._2) + bitPat.rawString.zipWithIndex.filter(_._1 == bpType).map(_._2) Seq('1', '0', '?').flatMap(t => tableFilter(index(table.default, t))) } @@ -99,7 +99,7 @@ object TruthTable { tables: Seq[(TruthTable, Seq[Int])] ): TruthTable = { def reIndex(bitPat: BitPat, table: TruthTable, indexes: Seq[Int]): Seq[(Char, Int)] = - bpStr(table.table.map(a => a._1.toString -> a._2).getOrElse(bitPat.toString, BitPat.dontCare(indexes.size))).zip(indexes) + (table.table.map(a => a._1.toString -> a._2).getOrElse(bitPat.toString, BitPat.dontCare(indexes.size))).rawString.zip(indexes) def bitPat(indexedChar: Seq[(Char, Int)]) = BitPat(s"b${indexedChar .sortBy(_._2) .map(_._1) @@ -111,9 +111,7 @@ object TruthTable { key -> bitPat(tables.flatMap { case (table, indexes) => reIndex(key, table, indexes) }) } .toMap, - bitPat(tables.flatMap { case (table, indexes) => bpStr(table.default).zip(indexes) }) + bitPat(tables.flatMap { case (table, indexes) => table.default.rawString.zip(indexes) }) ) } - - private def bpStr(bitPat: BitPat) = bitPat.toString.drop(7).dropRight(1) } diff --git a/src/test/scala/chiselTests/util/BitPatSpec.scala b/src/test/scala/chiselTests/util/BitPatSpec.scala index ca8dd85c..0c83493f 100644 --- a/src/test/scala/chiselTests/util/BitPatSpec.scala +++ b/src/test/scala/chiselTests/util/BitPatSpec.scala @@ -15,7 +15,30 @@ class BitPatSpec extends AnyFlatSpec with Matchers { BitPat("b" + testPattern).toString should be (s"BitPat($testPattern)") } + it should "convert a BitPat to raw form" in { + val testPattern = "0" * 32 + "1" * 32 + "?" * 32 + "?01" * 32 + BitPat("b" + testPattern).rawString should be(testPattern) + } + it should "not fail if BitPat width is 0" in { intercept[IllegalArgumentException]{BitPat("b")} } + + it should "contact BitPat via ##" in { + (BitPat.Y(4) ## BitPat.dontCare(3) ## BitPat.N(2)).toString should be (s"BitPat(1111???00)") + } + + it should "index and return new BitPat" in { + val b = BitPat("b1001???") + b(0) should be(BitPat.dontCare(1)) + b(6) should be(BitPat.Y()) + b(5) should be(BitPat.N()) + } + + it should "slice and return new BitPat" in { + val b = BitPat("b1001???") + b(2, 0) should be(BitPat("b???")) + b(4, 3) should be(BitPat("b01")) + b(6, 6) should be(BitPat("b1")) + } } -- cgit v1.2.3 From 0a985f7d82d33b45bb35237bf3ca85d6f3936600 Mon Sep 17 00:00:00 2001 From: Megan Wachs Date: Mon, 30 Aug 2021 07:36:32 -0700 Subject: FIx firrtl typo in README versioning section (#2097) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d5ddb0fb..60d1dae9 100644 --- a/README.md +++ b/README.md @@ -278,7 +278,7 @@ Code that touches lots of APIs that are private to the `chisel3` package should ### Which version should I use? -The chisel eco-system (`chisel3`, `firttl`, `dsptools`, `firrtl-interpreter`, `treadle`, `diagrammer`) use a form of semantic versioning: +The chisel eco-system (`chisel3`, `firrtl`, `dsptools`, `firrtl-interpreter`, `treadle`, `diagrammer`) use a form of semantic versioning: major versions are identified by two leading numbers, separated by a dot (i.e., `3.2`), minor versions by a single number following the major version, separated by a dot. We maintain API compatibility within a major version (i.e., `3.2.12` should be API-compatible with `3.2.0`), but do not guarantee API compatibility between major versions (i.e., APIs may change between `3.1.8` and `3.2.0`). -- cgit v1.2.3 From 29665743acff120bc87ee997890d7f952317144e Mon Sep 17 00:00:00 2001 From: Kevin Laeufer Date: Mon, 30 Aug 2021 16:06:59 -0700 Subject: SyncReadMem: fix bug with read(addr) and add some formal tests (#2092) --- core/src/main/scala/chisel3/Mem.scala | 8 ++- .../src/test/scala/chiselTest/MemFormalSpec.scala | 81 ++++++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 integration-tests/src/test/scala/chiselTest/MemFormalSpec.scala diff --git a/core/src/main/scala/chisel3/Mem.scala b/core/src/main/scala/chisel3/Mem.scala index 90525bfa..183620b6 100644 --- a/core/src/main/scala/chisel3/Mem.scala +++ b/core/src/main/scala/chisel3/Mem.scala @@ -213,8 +213,14 @@ sealed class SyncReadMem[T <: Data] private (t: T, n: BigInt, val readUnderWrite var port: Option[T] = None when (enable) { a := addr - port = Some(read(a)) + port = Some(super.do_read(a)) } port.get } + + /** @group SourceInfoTransformMacro*/ + override def do_read(idx: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = + do_read(addr = idx, enable = true.B) + // note: we implement do_read(addr) for SyncReadMem in terms of do_read(addr, en) in order to ensure that + // `mem.read(addr)` will always behave the same as `mem.read(addr, true.B)` } diff --git a/integration-tests/src/test/scala/chiselTest/MemFormalSpec.scala b/integration-tests/src/test/scala/chiselTest/MemFormalSpec.scala new file mode 100644 index 00000000..35d1a299 --- /dev/null +++ b/integration-tests/src/test/scala/chiselTest/MemFormalSpec.scala @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTest + +import chisel3._ +import chisel3.experimental._ +import chiseltest._ +import chiseltest.formal._ +import firrtl.annotations.MemoryArrayInitAnnotation +import org.scalatest.flatspec.AnyFlatSpec + +class MemFormalSpec extends AnyFlatSpec with ChiselScalatestTester with Formal { + behavior of "SyncReadMem read enable" + + private def check(mod: Boolean => ReadEnTestModule, alwaysEnabeld: Boolean = false): Unit = { + // we first check that the read is enabled when it should be + verify(mod(true), Seq(BoundedCheck(4))) + if(!alwaysEnabeld) { + // now we check that it is disabled, when it should be + // however, note that this check is not exhaustive/complete! + assertThrows[FailedBoundedCheckException] { + verify(mod(false), Seq(BoundedCheck(4))) + } + } + } + + it should "always be true when calling read(addr)" in { + check(new ReadEnTestModule(_) { out := mem.read(addr) }, true) + } + + it should "always be true when calling read(addr, true.B)" in { + check(new ReadEnTestModule(_) { out := mem.read(addr, true.B) }, true) + } + + it should "always be false when calling read(addr, false.B)" in { + check(new ReadEnTestModule(_) { + out := mem.read(addr, false.B) + shouldRead := false.B + shouldNotRead := true.B + }) + } + + it should "be enabled by iff en=1 when calling read(addr, en)" in { + check(new ReadEnTestModule(_) { + val en = IO(Input(Bool())) + out := mem.read(addr, en) + shouldRead := en + shouldNotRead := !en + }) + } +} + +abstract class ReadEnTestModule(testShouldRead: Boolean) extends Module { + val addr = IO(Input(UInt(5.W))) + val out = IO(Output(UInt(8.W))) + out := DontCare + // these can be overwritten by the concrete test + val shouldRead = WireInit(true.B) + val shouldNotRead = WireInit(false.B) + + // we initialize the memory, so that the output should always equivalent to the read address + val mem = SyncReadMem(32, chiselTypeOf(out)) + annotate(new ChiselAnnotation { + override def toFirrtl = MemoryArrayInitAnnotation(mem.toTarget, values = Seq.tabulate(32)(BigInt(_))) + }) + + // the first cycle after reset, the data will be arbitrary + val firstCycle = RegNext(false.B, init=true.B) + + if(testShouldRead) { + when(!firstCycle && RegNext(shouldRead)) { + verification.assert(out === RegNext(addr)) + } + } else { + when(!firstCycle && RegNext(shouldNotRead)) { + // this can only fail if the read enable is false and an arbitrary value is provided + // note that this test is not complete!! + verification.assert(out === 200.U) + } + } +} -- cgit v1.2.3 From 7fb2c1ebc23ca07e5de6416a284e1be1b62a48ac Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Mon, 30 Aug 2021 18:56:33 -0700 Subject: Fix chisel3 <> for compatibility Bundles (Take 3) (#2093) Previous incomplete fixes in #2023 and #2031. The legality of a FIRRTL connection is determined by type and flow. Chisel does not have access to true flow information. Previous fix attempts tried to use ActualDirection as a stand-in for flow, but it is incorrect in many cases. This new approach checks the flows of the lvalue and rvalues in the connect and flips the connection if either the lvalue cannot be a sink or the rvalue cannot be a source.--- .../main/scala/chisel3/internal/BiConnect.scala | 27 +++++++++++++++------- .../CompatibilityInteroperabilitySpec.scala | 27 ++++++++++++++++++++++ 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/chisel3/internal/BiConnect.scala b/core/src/main/scala/chisel3/internal/BiConnect.scala index 4a9bb4f5..aa58cb95 100644 --- a/core/src/main/scala/chisel3/internal/BiConnect.scala +++ b/core/src/main/scala/chisel3/internal/BiConnect.scala @@ -11,6 +11,8 @@ import chisel3.internal.firrtl.{Connect, DefInvalid} import scala.language.experimental.macros import chisel3.internal.sourceinfo._ +import scala.annotation.tailrec + /** * BiConnect.connect executes a bidirectional connection element-wise. * @@ -120,15 +122,24 @@ private[chisel3] object BiConnect { val notStrict = Seq(left_r.compileOptions, right_r.compileOptions).contains(ExplicitCompileOptions.NotStrict) if (notStrict) { - // chisel3 <> is commutative but FIRRTL <- is not - val flipped = { - import ActualDirection._ - // Everything is flipped when it's the port of a child - val childPort = left_r._parent.get != context_mod - val isFlipped = Seq(Bidirectional(Flipped), Input).contains(left_r.direction) - isFlipped ^ childPort + // 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 + } } - val (newLeft, newRight) = if (flipped) pair.swap else pair + 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 newLeft.bulkConnect(newRight)(sourceInfo, ExplicitCompileOptions.NotStrict) } else { recordConnect(sourceInfo, connectCompileOptions, left_r, right_r, context_mod) diff --git a/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala b/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala index 28b8bc80..1795cc1f 100644 --- a/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala +++ b/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala @@ -332,5 +332,32 @@ class CompatibiltyInteroperabilitySpec extends ChiselFlatSpec { } } } + + "A unidirectional but flipped Bundle" should "bulk connect in import chisel3._ code correctly" in { + object Compat { + import Chisel._ + class MyBundle(extraFlip: Boolean) extends Bundle { + private def maybeFlip[T <: Data](t: T): T = if (extraFlip) t.flip else t + val foo = maybeFlip(new Bundle { + val bar = UInt(INPUT, width = 8) + }) + override def cloneType = (new MyBundle(extraFlip)).asInstanceOf[this.type] + } + } + import chisel3._ + import Compat._ + class Top(extraFlip: Boolean) extends RawModule { + val port = IO(new MyBundle(extraFlip)) + val wire = Wire(new MyBundle(extraFlip)) + port <> DontCare + wire <> DontCare + port <> wire + wire <> port + port.foo <> wire.foo + wire.foo <> port.foo + } + compile(new Top(true)) + compile(new Top(false)) + } } -- cgit v1.2.3 From 9fa8da227569455a77596355aeb114f9c164510a Mon Sep 17 00:00:00 2001 From: Adam Izraelevitz Date: Sun, 5 Sep 2021 12:11:32 -0700 Subject: Add Definition and Instance API (#2045) This introduces a new experimental API for module instantiation that disentagles elaborating the definition (or implementation) from instantiation of a given module. This solves Chisel's longstanding reliance on "Deduplication" for generating Verilog with multiple instances of the same module. The new API resides in package chisel3.experimental.hierarchy. Please see the hierarchy ScalaDoc, documentation, and tests for examples of use. Co-authored-by: Jack Koenig Co-authored-by: Megan Wachs Co-authored-by: Schuyler Eldridge --- core/src/main/scala/chisel3/BlackBox.scala | 3 +- core/src/main/scala/chisel3/Module.scala | 107 +++- core/src/main/scala/chisel3/RawModule.scala | 9 +- .../experimental/hierarchy/Definition.scala | 99 +++ .../chisel3/experimental/hierarchy/Instance.scala | 111 ++++ .../experimental/hierarchy/IsInstantiable.scala | 17 + .../experimental/hierarchy/IsLookupable.scala | 25 + .../experimental/hierarchy/Lookupable.scala | 368 +++++++++++ .../chisel3/experimental/hierarchy/package.scala | 48 ++ core/src/main/scala/chisel3/internal/Binding.scala | 5 + core/src/main/scala/chisel3/internal/Builder.scala | 49 +- .../main/scala/chisel3/internal/firrtl/IR.scala | 33 +- docs/src/cookbooks/hierarchy.md | 204 ++++++ .../scala/chisel3/internal/InstantiableMacro.scala | 77 +++ .../internal/sourceinfo/SourceInfoTransform.scala | 30 + .../chisel3/internal/plugin/ChiselComponent.scala | 6 + src/main/scala/chisel3/aop/Select.scala | 10 +- .../chisel3/aop/injecting/InjectingAspect.scala | 1 + src/test/scala/chiselTests/ChiselSpec.scala | 11 +- src/test/scala/chiselTests/aop/SelectSpec.scala | 31 +- .../scala/chiselTests/experimental/DataView.scala | 40 +- .../experimental/DataViewTargetSpec.scala | 4 +- .../experimental/hierarchy/Annotations.scala | 28 + .../experimental/hierarchy/DefinitionSpec.scala | 493 ++++++++++++++ .../experimental/hierarchy/Examples.scala | 186 ++++++ .../experimental/hierarchy/InstanceSpec.scala | 709 +++++++++++++++++++++ .../chiselTests/experimental/hierarchy/Utils.scala | 21 + 27 files changed, 2671 insertions(+), 54 deletions(-) create mode 100644 core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala create mode 100644 core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala create mode 100644 core/src/main/scala/chisel3/experimental/hierarchy/IsInstantiable.scala create mode 100644 core/src/main/scala/chisel3/experimental/hierarchy/IsLookupable.scala create mode 100644 core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala create mode 100644 core/src/main/scala/chisel3/experimental/hierarchy/package.scala create mode 100644 docs/src/cookbooks/hierarchy.md create mode 100644 macros/src/main/scala/chisel3/internal/InstantiableMacro.scala create mode 100644 src/test/scala/chiselTests/experimental/hierarchy/Annotations.scala create mode 100644 src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala create mode 100644 src/test/scala/chiselTests/experimental/hierarchy/Examples.scala create mode 100644 src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala create mode 100644 src/test/scala/chiselTests/experimental/hierarchy/Utils.scala diff --git a/core/src/main/scala/chisel3/BlackBox.scala b/core/src/main/scala/chisel3/BlackBox.scala index 0c42600f..38b08193 100644 --- a/core/src/main/scala/chisel3/BlackBox.scala +++ b/core/src/main/scala/chisel3/BlackBox.scala @@ -158,11 +158,12 @@ abstract class BlackBox(val params: Map[String, Param] = Map.empty[String, Param val namedPorts = _io.elements.toSeq.reverse // ListMaps are stored in reverse order - // setRef is not called on the actual io. // There is a risk of user improperly attempting to connect directly with io // Long term solution will be to define BlackBox IO differently as part of // it not descending from the (current) Module for ((name, port) <- namedPorts) { + // We are setting a 'fake' ref for io, so that cloneType works but if a user connects to io, it still fails. + this.findPort("io").get.setRef(ModuleIO(internal.ViewParent, ""), force = true) // We have to force override the _ref because it was set during IO binding port.setRef(ModuleIO(this, _namespace.name(name)), force = true) } diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala index 64065ec9..d0171693 100644 --- a/core/src/main/scala/chisel3/Module.scala +++ b/core/src/main/scala/chisel3/Module.scala @@ -121,7 +121,8 @@ abstract class Module(implicit moduleCompileOptions: CompileOptions) extends Raw private[chisel3] def mkReset: Reset = { // Top module and compatibility mode use Bool for reset - val inferReset = _parent.isDefined && moduleCompileOptions.inferModuleReset + // Note that a Definition elaboration will lack a parent, but still not be a Top module + val inferReset = (_parent.isDefined || Builder.inDefinition) && moduleCompileOptions.inferModuleReset if (inferReset) Reset() else Bool() } @@ -181,13 +182,31 @@ package experimental { package internal { import chisel3.experimental.BaseModule + import chisel3.experimental.hierarchy.IsInstantiable object BaseModule { + /** Represents a clone of an underlying object. This is used to support CloneModuleAsRecord and Instance/Definition. + * + * @note We don't actually "clone" anything in the traditional sense but is a placeholder so we lazily clone internal state + */ + private [chisel3] trait IsClone[+T] { + // Underlying object of which this is a clone of + val _proto: T + def getProto: T = _proto + def isACloneOf(a: Any): Boolean = this == a || _proto == a + } + // Private internal class to serve as a _parent for Data in cloned ports - private[chisel3] class ModuleClone(_proto: BaseModule) extends PseudoModule { + private[chisel3] class ModuleClone[T <: BaseModule] (val _proto: T) extends PseudoModule with IsClone[T] { + override def toString = s"ModuleClone(${_proto})" + def getPorts = _portsRecord // ClonePorts that hold the bound ports for this module // Used for setting the refs of both this module and the Record private[BaseModule] var _portsRecord: Record = _ + // This is necessary for correctly supporting .toTarget on a Module Clone. If it is made from the + // Instance/Definition API, it should return an instanceTarget. If made from CMAR, it should return a + // ModuleTarget. + private[chisel3] var _madeFromDefinition: Boolean = false // Don't generate a component, but point to the one for the cloned Module private[chisel3] def generateComponent(): Option[Component] = { require(!_closed, "Can't generate module more than once") @@ -195,9 +214,15 @@ package internal { _component = _proto._component None } + // Maps proto ports to module clone's ports + private[chisel3] lazy val ioMap: Map[Data, Data] = { + val name2Port = getPorts.elements + _proto.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 = () + // Name of this instance's module is the same as the proto's name override def desiredName: String = _proto.name private[chisel3] def setRefAndPortsRef(namespace: Namespace): Unit = { @@ -215,6 +240,53 @@ package internal { } } + /** Represents a module viewed from a different instance context. + * + * @note Why do we need both ModuleClone and InstanceClone? If we are annotating a reference in a module-clone, + * all submodules must be also be 'cloned' so the toTarget can be computed properly. However, we don't need separate + * connectable ports for this instance; all that's different from the proto is the parent. + * + * @note In addition, the instance name of an InstanceClone is going to be the SAME as the proto, but this is not true + * for ModuleClone. + */ + private[chisel3] final class InstanceClone[T <: BaseModule] (val _proto: T, val instName: () => String) extends PseudoModule with IsClone[T] { + override def toString = s"InstanceClone(${_proto})" + // No addition components are generated + private[chisel3] def generateComponent(): Option[Component] = None + // Necessary for toTarget to work + private[chisel3] def setAsInstanceRef(): Unit = { this.setRef(Ref(instName())) } + // This module doesn't acutally exist in the FIRRTL so no initialization to do + private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = () + // Instance name is the same as proto's instance name + override def instanceName = instName() + // Module name is the same as proto's module name + override def desiredName: String = _proto.name + } + + /** Represents a Definition root module, when accessing something from a definition + * + * @note This is necessary to distinguish between the toTarget behavior for a Module returned from a Definition, + * versus a normal Module. A normal Module.toTarget will always return a local target. If calling toTarget + * on a Module returned from a Definition (and thus wrapped in an Instance), we need to return the non-local + * target whose root is the Definition. This DefinitionClone is used to represent the root parent of the + * InstanceClone (which represents the returned module). + */ + private[chisel3] class DefinitionClone[T <: BaseModule] (val _proto: T) extends PseudoModule with IsClone[T] { + override def toString = s"DefinitionClone(${_proto})" + // No addition components are generated + private[chisel3] def generateComponent(): Option[Component] = None + // Necessary for toTarget to work + private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = () + // Module name is the same as proto's module name + override def desiredName: String = _proto.name + } + + /** @note If we are cloning a non-module, we need another object which has the proper _parent set! + */ + private[chisel3] final class InstantiableClone[T <: IsInstantiable] (val _proto: T) extends IsClone[T] { + private[chisel3] var _parent: Option[BaseModule] = internal.Builder.currentModule + } + /** Record type returned by CloneModuleAsRecord * * @note These are not true Data (the Record doesn't correspond to anything in the emitted @@ -232,6 +304,9 @@ package internal { // We make this before clonePorts because we want it to come up first in naming in // currentModule val cloneParent = Module(new ModuleClone(proto)) + require(proto.isClosed, "Can't clone a module before module close") + require(cloneParent.getOptionRef.isEmpty, "Can't have ref set already!") + // 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: _*) @@ -253,10 +328,19 @@ package internal { package experimental { + import chisel3.experimental.hierarchy.IsInstantiable + + object BaseModule { + implicit class BaseModuleExtensions[T <: BaseModule](b: T) { + import chisel3.experimental.hierarchy.{Instance, Definition} + def toInstance: Instance[T] = new Instance(Left(b)) + def toDefinition: Definition[T] = new Definition(Left(b)) + } + } /** Abstract base class for Modules, an instantiable organizational unit for RTL. */ // TODO: seal this? - abstract class BaseModule extends HasId { + abstract class BaseModule extends HasId with IsInstantiable { _parent.foreach(_.addId(this)) // @@ -379,13 +463,24 @@ package experimental { * * @note Should not be called until circuit elaboration is complete */ - final def toNamed: ModuleName = toTarget.toNamed + final def toNamed: ModuleName = ModuleTarget(this.circuitName, this.name).toNamed /** Returns a FIRRTL ModuleTarget that references this object * * @note Should not be called until circuit elaboration is complete */ - final def toTarget: ModuleTarget = ModuleTarget(this.circuitName, this.name) + final def toTarget: IsModule = { + this match { + case m: internal.BaseModule.InstanceClone[_] if m._parent.nonEmpty => m._parent.get.toTarget.instOf(instanceName, name) + case m: internal.BaseModule.InstanceClone[_] => ModuleTarget(this.circuitName, this.name) + case m: internal.BaseModule.ModuleClone[_] if m._madeFromDefinition => m._parent.get.toTarget.instOf(instanceName, name) + case m: internal.BaseModule.ModuleClone[_] => ModuleTarget(this.circuitName, this.name) + // Without this, we get the wrong CircuitName for the Definition + case m: internal.BaseModule.DefinitionClone[_] if m._circuit.nonEmpty => ModuleTarget(this._circuit.get.circuitName, this.name) + case m: internal.BaseModule.DefinitionClone[_] => ModuleTarget(this.circuitName, this.name) + case m => ModuleTarget(this.circuitName, this.name) + } + } /** Returns a FIRRTL ModuleTarget that references this object * @@ -393,7 +488,7 @@ package experimental { */ final def toAbsoluteTarget: IsModule = { _parent match { - case Some(parent) => parent.toAbsoluteTarget.instOf(this.instanceName, toTarget.module) + case Some(parent) => parent.toAbsoluteTarget.instOf(this.instanceName, name) case None => // FIXME Special handling for Views - evidence of "weirdness" of .toAbsoluteTarget // In theory, .toAbsoluteTarget should not be necessary, .toTarget combined with the diff --git a/core/src/main/scala/chisel3/RawModule.scala b/core/src/main/scala/chisel3/RawModule.scala index 74e9db6c..27f16ad4 100644 --- a/core/src/main/scala/chisel3/RawModule.scala +++ b/core/src/main/scala/chisel3/RawModule.scala @@ -7,7 +7,7 @@ import scala.util.Try import scala.language.experimental.macros import chisel3.experimental.{BaseModule, BaseSim} import chisel3.internal._ -import chisel3.internal.BaseModule.ModuleClone +import chisel3.internal.BaseModule.{ModuleClone, InstanceClone} import chisel3.internal.Builder._ import chisel3.internal.firrtl._ import chisel3.internal.sourceinfo.UnlocatableSourceInfo @@ -77,7 +77,8 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) // All suggestions are in, force names to every node. for (id <- getIds) { id match { - case id: ModuleClone => id.setRefAndPortsRef(_namespace) // special handling + case id: ModuleClone[_] => id.setRefAndPortsRef(_namespace) // special handling + case id: InstanceClone[_] => id.setAsInstanceRef() case id: BaseModule => id.forceName(None, default=id.desiredName, _namespace) case id: MemBase[_] => id.forceName(None, default="MEM", _namespace) case id: BaseSim => id.forceName(None, default="SIM", _namespace) @@ -158,6 +159,10 @@ trait RequireSyncReset extends Module { package object internal { + import scala.annotation.implicitNotFound + @implicitNotFound("You are trying to access a macro-only API. Please use the @public annotation instead.") + trait MacroGenerated + /** Marker trait for modules that are not true modules */ private[chisel3] trait PseudoModule extends BaseModule diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala new file mode 100644 index 00000000..2e917dfa --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +import scala.language.experimental.macros + +import chisel3._ +import scala.collection.mutable.HashMap +import chisel3.internal.{Builder, DynamicContext} +import chisel3.internal.sourceinfo.{DefinitionTransform, DefinitionWrapTransform, SourceInfo} +import chisel3.experimental.BaseModule +import chisel3.internal.BaseModule.IsClone + +/** User-facing Definition type. + * Represents a definition of an object of type [[A]] which are marked as @instantiable + * Can be created using Definition.apply method. + * + * These definitions are then used to create multiple [[Instance]]s. + * + * @param cloned The internal representation of the instance, which may be either be directly the object, or a clone of an object + */ +case class Definition[+A] private[chisel3] (private[chisel3] cloned: Either[A, IsClone[A]]) extends IsLookupable { + private[chisel3] def proto: A = cloned match { + case Left(value: A) => value + case Right(i: IsClone[A]) => i._proto + } + /** Used by Chisel's internal macros. DO NOT USE in your normal Chisel code!!! + * Instead, mark the field you are accessing with [[@public]] + * + * Given a selector function (that) which selects a member from the original, return the + * corresponding member from the instance. + * + * Our @instantiable and @public macros generate the calls to this apply method + * + * By calling this function, we summon the proper Lookupable typeclass from our implicit scope. + * + * @param that a user-specified lookup function + * @param lookup typeclass which contains the correct lookup function, based on the types of A and B + * @param macroGenerated a value created in the macro, to make it harder for users to use this API + */ + def _lookup[B, C](that: A => B)(implicit lookup: Lookupable[B], macroGenerated: chisel3.internal.MacroGenerated): lookup.C = { + lookup.definitionLookup(that, this) + } + + /** Updated by calls to [[apply]], to avoid recloning returned Data's */ + private [chisel3] val cache = HashMap[Data, Data]() + + + /** @return the context of any Data's return from inside the instance */ + private[chisel3] def getInnerDataContext: Option[BaseModule] = proto match { + case value: BaseModule => + val newChild = Module.do_apply(new internal.BaseModule.DefinitionClone(value))(chisel3.internal.sourceinfo.UnlocatableSourceInfo, chisel3.ExplicitCompileOptions.Strict) + newChild._circuit = value._circuit.orElse(Some(value)) + newChild._parent = None + Some(newChild) + case value: IsInstantiable => None + } + +} + +/** Factory methods for constructing [[Definition]]s */ +object Definition extends SourceInfoDoc { + implicit class DefinitionBaseModuleExtensions[T <: BaseModule](d: Definition[T]) { + /** If this is an instance of a Module, returns the toTarget of this instance + * @return target of this instance + */ + def toTarget = d.proto.toTarget + + /** If this is an instance of a Module, returns the toAbsoluteTarget of this instance + * @return absoluteTarget of this instance + */ + def toAbsoluteTarget = d.proto.toAbsoluteTarget + } + /** A construction method to build a Definition of a Module + * + * @param proto the Module being defined + * + * @return the input module as a Definition + */ + def apply[T <: BaseModule with IsInstantiable](proto: => T): Definition[T] = macro DefinitionTransform.apply[T] + + /** A construction method to build a Definition of a Module + * + * @param bc the Module being defined + * + * @return the input module as a Definition + */ + def do_apply[T <: BaseModule with IsInstantiable](proto: => T) (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Definition[T] = { + val dynamicContext = new DynamicContext(Nil) + Builder.globalNamespace.copyTo(dynamicContext.globalNamespace) + dynamicContext.inDefinition = true + val (ir, module) = Builder.build(Module(proto), dynamicContext) + Builder.components ++= ir.components + Builder.annotations ++= ir.annotations + module._circuit = Builder.currentModule + dynamicContext.globalNamespace.copyTo(Builder.globalNamespace) + new Definition(Left(module)) + } +} diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala new file mode 100644 index 00000000..aec42da3 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +import scala.collection.mutable.{ArrayBuffer, HashMap} +import scala.language.experimental.macros + +import chisel3._ +import chisel3.internal.BaseModule.{ModuleClone, IsClone, InstantiableClone} +import chisel3.internal.sourceinfo.{InstanceTransform, SourceInfo} +import chisel3.experimental.BaseModule + +/** User-facing Instance type. + * Represents a unique instance of type [[A]] which are marked as @instantiable + * Can be created using Instance.apply method. + * + * @param cloned The internal representation of the instance, which may be either be directly the object, or a clone of an object + */ +case class Instance[+A] private [chisel3] (private[chisel3] cloned: Either[A, IsClone[A]]) { + + /** Returns the original object which is instantiated here. + * If this is an instance of a clone, return that clone's original proto + * + * @return the original object which was instantiated + */ + private[chisel3] def proto: A = cloned match { + case Left(value: A) => value + case Right(i: IsClone[A]) => i._proto + } + + /** @return the context of any Data's return from inside the instance */ + private[chisel3] def getInnerDataContext: Option[BaseModule] = cloned match { + case Left(value: BaseModule) => Some(value) + case Left(value: IsInstantiable) => None + case Right(i: BaseModule) => Some(i) + case Right(i: InstantiableClone[_]) => i._parent + } + + /** @return the context this instance. Note that for non-module clones, getInnerDataContext will be the same as getClonedParent */ + private[chisel3] def getClonedParent: Option[BaseModule] = cloned match { + case Left(value: BaseModule) => value._parent + case Right(i: BaseModule) => i._parent + case Right(i: InstantiableClone[_]) => i._parent + } + + /** Updated by calls to [[apply]], to avoid recloning returned Data's */ + private [chisel3] val cache = HashMap[Data, Data]() + + /** Used by Chisel's internal macros. DO NOT USE in your normal Chisel code!!! + * Instead, mark the field you are accessing with [[@public]] + * + * Given a selector function (that) which selects a member from the original, return the + * corresponding member from the instance. + * + * Our @instantiable and @public macros generate the calls to this apply method + * + * By calling this function, we summon the proper Lookupable typeclass from our implicit scope. + * + * @param that a user-specified lookup function + * @param lookup typeclass which contains the correct lookup function, based on the types of A and B + * @param macroGenerated a value created in the macro, to make it harder for users to use this API + */ + def _lookup[B, C](that: A => B)(implicit lookup: Lookupable[B], macroGenerated: chisel3.internal.MacroGenerated): lookup.C = { + lookup.instanceLookup(that, this) + } + + /** Returns the definition of this Instance */ + def toDefinition: Definition[A] = new Definition(Left(proto)) + +} + +/** Factory methods for constructing [[Instance]]s */ +object Instance extends SourceInfoDoc { + implicit class InstanceBaseModuleExtensions[T <: BaseModule](i: Instance[T]) { + /** If this is an instance of a Module, returns the toTarget of this instance + * @return target of this instance + */ + def toTarget = i.cloned match { + case Left(x: BaseModule) => x.toTarget + case Right(x: IsClone[_] with BaseModule) => x.toTarget + } + + /** If this is an instance of a Module, returns the toAbsoluteTarget of this instance + * @return absoluteTarget of this instance + */ + def toAbsoluteTarget = i.cloned match { + case Left(x) => x.toAbsoluteTarget + case Right(x: IsClone[_] with BaseModule) => x.toAbsoluteTarget + } + + } + /** A constructs an [[Instance]] from a [[Definition]] + * + * @param definition the Module being created + * @return an instance of the module definition + */ + def apply[T <: BaseModule with IsInstantiable](definition: Definition[T]): Instance[T] = macro InstanceTransform.apply[T] + + /** A constructs an [[Instance]] from a [[Definition]] + * + * @param definition the Module being created + * @return an instance of the module definition + */ + def do_apply[T <: BaseModule with IsInstantiable](definition: Definition[T])(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Instance[T] = { + val ports = experimental.CloneModuleAsRecord(definition.proto) + val clone = ports._parent.get.asInstanceOf[ModuleClone[T]] + clone._madeFromDefinition = true + new Instance(Right(clone)) + } + +} diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/IsInstantiable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/IsInstantiable.scala new file mode 100644 index 00000000..26ba0286 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/IsInstantiable.scala @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +/** While this is public, it is not recommended for users to extend directly. + * Instead, use the [[@instantiable]] annotation on your trait or class. + * + * This trait indicates whether a class can be returned from an Instance. + * + */ +trait IsInstantiable + +object IsInstantiable { + implicit class IsInstantiableExtensions[T <: IsInstantiable](i: T) { + def toInstance: Instance[T] = new Instance(Left(i)) + } +} diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/IsLookupable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/IsLookupable.scala new file mode 100644 index 00000000..37d29a43 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/IsLookupable.scala @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +/** A User-extendable trait to mark metadata-containers, e.g. parameter case classes, as valid to return unchanged + * from an instance. + * + * This should only be true of the metadata returned is identical for ALL instances! + * + * @example For instances of the same proto, metadata or other construction parameters + * may be useful to access outside of the instance construction. For parameters that are + * the same for all instances, we should mark it as IsLookupable + * {{{ + * case class Params(debugMessage: String) extends IsLookupable + * class MyModule(p: Params) extends MultiIOModule { + * printf(p.debugMessage) + * } + * val myParams = Params("Hello World") + * val definition = Definition(new MyModule(myParams)) + * val i0 = Instance(definition) + * val i1 = Instance(definition) + * require(i0.p == i1.p) // p is only accessable because it extends IsLookupable + * }}} + */ +trait IsLookupable diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala new file mode 100644 index 00000000..b9617723 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +import chisel3.experimental.BaseModule +import chisel3.internal.sourceinfo.SourceInfo +import chisel3.internal.BaseModule.{InstanceClone, InstantiableClone, IsClone, ModuleClone} + +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.{AggregateViewBinding, Builder, ChildBinding, ViewBinding, ViewParent, throwException} + +/** Represents lookup typeclass to determine how a value accessed from an original IsInstantiable + * should be tweaked to return the Instance's version + * Sealed. + */ +@implicitNotFound("@public is only legal within a class marked @instantiable and only on vals of type" + + " Data, BaseModule, IsInstantiable, IsLookupable, or Instance[_], or in an Iterable or Option") +sealed trait Lookupable[-B] { + type C // Return type of the lookup + /** Function called to modify the returned value of type B from A, into C + * + * @param that function that selects B from A + * @param instance Instance of A, used to determine C's context + * @return + */ + def instanceLookup[A](that: A => B, instance: Instance[A]): C + + /** Function called to modify the returned value of type B from A, into C + * + * @param that function that selects B from A + * @param definition Definition of A, used to determine C's context + * @return + */ + def definitionLookup[A](that: A => B, definition: Definition[A]): C +} + +private[chisel3] object Lookupable { + + /** Clones a data and sets its internal references to its parent module to be in a new context. + * + * @param data data to be cloned + * @param context new context + * @return + */ + private[chisel3] def cloneDataToContext[T <: Data](data: T, context: BaseModule) + (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { + internal.requireIsHardware(data, "cross module reference type") + data._parent match { + case None => data + case Some(parent) => + val newParent = cloneModuleToContext(Left(parent), context) + newParent match { + case Left(p) if p == parent => data + case Right(m: BaseModule) => + val newChild = data.cloneTypeFull + newChild.setRef(data.getRef, true) + newChild.bind(internal.CrossModuleBinding) + newChild.setAllParents(Some(m)) + newChild + } + } + } + // The business logic of lookupData + // Also called by cloneViewToContext which potentially needs to lookup stuff from ioMap or the cache + private[chisel3] def doLookupData[A, B <: Data](data: B, cache: HashMap[Data, Data], ioMap: Option[Map[Data, Data]], context: Option[BaseModule]) + (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): B = { + def impl[C <: Data](d: C): C = d match { + case x: Data if ioMap.nonEmpty && ioMap.get.contains(x) => ioMap.get(x).asInstanceOf[C] + case x: Data if cache.contains(x) => cache(x).asInstanceOf[C] + case _ => + assert(context.nonEmpty) // TODO is this even possible? Better error message here + val ret = cloneDataToContext(d, context.get) + cache(d) = ret + ret + } + data.binding match { + case Some(_: ChildBinding) => mapRootAndExtractSubField(data, impl) + case _ => impl(data) + } + } + + // Helper for co-iterating on Elements of aggregates, they must be the same type but that is unchecked + private def coiterate(a: Data, b: Data): Iterable[(Element, Element)] = { + val as = getRecursiveFields.lazily(a, "_") + val bs = getRecursiveFields.lazily(b, "_") + as.zip(bs).collect { case ((ae: Element, _), (be: Element, _)) => (ae, be) } + } + + /** Given a Data, find the root of its binding, apply a function to the root to get a "new root", + * and find the equivalent child Data in the "new root" + * + * @example {{{ + * Given `arg = a.b[2].c` and some `f`: + * 1. a = root(arg) = root(a.b[2].c) + * 2. newRoot = f(root(arg)) = f(a) + * 3. return newRoot.b[2].c + * }}} + * + * Invariants that elt is a Child of something of the type of data is dynamically checked as we traverse + */ + private def mapRootAndExtractSubField[A <: Data](arg: A, f: Data => Data): A = { + def err(msg: String) = throwException(s"Internal Error! $msg") + 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 _ => (res, d) + } + def applyCoordinates(fullCoor: List[Arg], start: Data): Data = { + def rec(coor: List[Arg], d: Data): Data = { + if (coor.isEmpty) d + else { + 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 (arg, _) => err(s"Unexpected Arg '$arg' applied to '$d'! Root was '$start'.") + } + applyCoordinates(coor.tail, next) + } + } + rec(fullCoor, start) + } + val (coor, root) = unrollCoordinates(Nil, arg) + val newRoot = f(root) + val result = applyCoordinates(coor, newRoot) + try { + result.asInstanceOf[A] + } catch { + case _: ClassCastException => err(s"Applying '$coor' to '$newRoot' somehow resulted in '$result'") + } + } + + // TODO this logic is complicated, can any of it be unified with viewAs? + // If `.viewAs` would capture its arguments, we could potentially use it + // TODO Describe what this is doing at a high level + private[chisel3] def cloneViewToContext[A, B <: Data](data: B, cache: HashMap[Data, Data], ioMap: Option[Map[Data, Data]], context: Option[BaseModule]) + (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): B = { + // alias to shorten lookups + def lookupData[C <: Data](d: C) = doLookupData(d, cache, ioMap, context) + + val result = data.cloneTypeFull + + // We have to lookup the target(s) of the view since they may need to be cloned into the current context + val newBinding = data.topBinding match { + case ViewBinding(target) => ViewBinding(lookupData(reify(target))) + case avb @ AggregateViewBinding(map, targetOpt) => data match { + case _: Element => ViewBinding(lookupData(reify(map(data)))) + case _: Aggregate => + // Provide a 1:1 mapping if possible + val singleTargetOpt = targetOpt.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 + val newTarget = lookupData(singleTarget) + val newMap = coiterate(result, data).map { case (res, from) => + (res: Data) -> mapRootAndExtractSubField(map(from), _ => newTarget) + }.toMap + AggregateViewBinding(newMap, Some(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))) + }.toMap + AggregateViewBinding(newMap, None) + } + } + } + + // TODO Unify the following with `.viewAs` + // 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) { + Builder.unnamedViews += result + } + // Binding does not capture 1:1 for child aggregates views + getRecursiveFields.lazily(result, "_").foreach { + case (agg: Aggregate, _) if agg != result => + Builder.unnamedViews += agg + case _ => // Do nothing + } + } + + result.bind(newBinding) + result.setAllParents(Some(ViewParent)) + result.forceName(None, "view", Builder.viewNamespace) + result + } + /** Given a module (either original or a clone), clone it to a new context + * + * This function effectively recurses up the parents of module to find whether: + * (1) A parent is already in the context; then we do nothing and return module + * (2) A parent is in a different clone of the context; then we clone all the parents up + * to that parent and set their parents to be in this cloned context + * (3) A parent has no root; in that case, we do nothing and return the module. + * + * @param module original or clone to be cloned into a new context + * @param context new context + * @return original or clone in the new context + */ + private[chisel3] def cloneModuleToContext[T <: BaseModule](module: Either[T, IsClone[T]], context: BaseModule) + (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Either[T, IsClone[T]] = { + // Recursive call + def rec[A <: BaseModule](m: A): Either[A, IsClone[A]] = { + def clone(x: A, p: Option[BaseModule], name: () => String): Either[A, IsClone[A]] = { + val newChild = Module.do_apply(new internal.BaseModule.InstanceClone(x, name)) + newChild._parent = p + Right(newChild) + } + (m, context) match { + case (c, ctx) if ctx == c => Left(c) + case (c, ctx: IsClone[_]) if ctx.isACloneOf(c) => Right(ctx.asInstanceOf[IsClone[A]]) + case (c, ctx) if c._parent.isEmpty => Left(c) + case (_, _) => + cloneModuleToContext(Left(m._parent.get), context) match { + case Left(p) => Left(m) + case Right(p: BaseModule) => + clone(m, Some(p), () => m.instanceName) + } + } + } + module match { + case Left(m) => rec(m) + case Right(m: ModuleClone[_]) => + rec(m) match { + case Left(mx) => Right(mx) + case Right(i: InstanceClone[_]) => + val newChild = Module.do_apply(new InstanceClone(m._proto, () => m.instanceName)) + newChild._parent = i._parent + Right(newChild) + } + case Right(m: InstanceClone[_]) => + rec(m) match { + case Left(mx) => Right(mx) + case Right(i: InstanceClone[_]) => + val newChild = Module.do_apply(new InstanceClone(m._proto, () => m.instanceName)) + newChild._parent = i._parent + Right(newChild) + } + } + } + + class SimpleLookupable[X] extends Lookupable[X] { + type B = X + type C = X + def definitionLookup[A](that: A => B, definition: Definition[A]): C = that(definition.proto) + def instanceLookup[A](that: A => B, instance: Instance[A]): C = that(instance.proto) + } + + implicit def lookupInstance[B <: BaseModule](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = new Lookupable[Instance[B]] { + type C = Instance[B] + def definitionLookup[A](that: A => Instance[B], definition: Definition[A]): C = { + val ret = that(definition.proto) + new Instance(cloneModuleToContext(ret.cloned, definition.getInnerDataContext.get)) + } + def instanceLookup[A](that: A => Instance[B], instance: Instance[A]): C = { + val ret = that(instance.proto) + instance.cloned match { + // If instance is just a normal module, no changing of context is necessary + case Left(_) => new Instance(ret.cloned) + case Right(_) => new Instance(cloneModuleToContext(ret.cloned, instance.getInnerDataContext.get)) + } + } + } + + implicit def lookupModule[B <: BaseModule](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = new Lookupable[B] { + type C = Instance[B] + def definitionLookup[A](that: A => B, definition: Definition[A]): C = { + val ret = that(definition.proto) + new Instance(cloneModuleToContext(Left(ret), definition.getInnerDataContext.get)) + } + def instanceLookup[A](that: A => B, instance: Instance[A]): C = { + val ret = that(instance.proto) + instance.cloned match { + // If instance is just a normal module, no changing of context is necessary + case Left(_) => new Instance(Left(ret)) + case Right(_) => new Instance(cloneModuleToContext(Left(ret), instance.getInnerDataContext.get)) + } + } + } + + implicit def lookupData[B <: Data](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = new Lookupable[B] { + type C = B + def definitionLookup[A](that: A => B, definition: Definition[A]): C = { + val ret = that(definition.proto) + if (isView(ret)) { + ??? // TODO!!!!!! cloneViewToContext(ret, instance, ioMap, instance.getInnerDataContext) + } else { + doLookupData(ret, definition.cache, None, definition.getInnerDataContext) + } + } + def instanceLookup[A](that: A => B, instance: Instance[A]): C = { + val ret = that(instance.proto) + val ioMap: Option[Map[Data, Data]] = instance.cloned match { + case Right(x: ModuleClone[_]) => Some(x.ioMap) + case Left(x: BaseModule) => Some(x.getChiselPorts.map { case (_, data) => data -> data }.toMap) + case _ => None + } + if (isView(ret)) { + cloneViewToContext(ret, instance.cache, ioMap, instance.getInnerDataContext) + } else { + doLookupData(ret, instance.cache, ioMap, instance.getInnerDataContext) + } + + } + } + + import scala.language.higherKinds // Required to avoid warning for lookupIterable type parameter + implicit def lookupIterable[B, F[_] <: Iterable[_]](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions, lookupable: Lookupable[B]) = new Lookupable[F[B]] { + type C = F[lookupable.C] + def definitionLookup[A](that: A => F[B], definition: Definition[A]): C = { + val ret = that(definition.proto).asInstanceOf[Iterable[B]] + ret.map{ x: B => lookupable.definitionLookup[A](_ => x, definition) }.asInstanceOf[C] + } + def instanceLookup[A](that: A => F[B], instance: Instance[A]): C = { + import instance._ + val ret = that(proto).asInstanceOf[Iterable[B]] + ret.map{ x: B => lookupable.instanceLookup[A](_ => x, instance) }.asInstanceOf[C] + } + } + implicit def lookupOption[B](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions, lookupable: Lookupable[B]) = new Lookupable[Option[B]] { + type C = Option[lookupable.C] + def definitionLookup[A](that: A => Option[B], definition: Definition[A]): C = { + val ret = that(definition.proto) + ret.map{ x: B => lookupable.definitionLookup[A](_ => x, definition) } + } + def instanceLookup[A](that: A => Option[B], instance: Instance[A]): C = { + import instance._ + val ret = that(proto) + ret.map{ x: B => lookupable.instanceLookup[A](_ => x, instance) } + } + } + implicit def lookupIsInstantiable[B <: IsInstantiable](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = new Lookupable[B] { + type C = Instance[B] + def definitionLookup[A](that: A => B, definition: Definition[A]): C = { + val ret = that(definition.proto) + val cloned = new InstantiableClone(ret) + cloned._parent = definition.getInnerDataContext + new Instance(Right(cloned)) + } + def instanceLookup[A](that: A => B, instance: Instance[A]): C = { + val ret = that(instance.proto) + val cloned = new InstantiableClone(ret) + cloned._parent = instance.getInnerDataContext + new Instance(Right(cloned)) + } + } + + implicit def lookupIsLookupable[B <: IsLookupable](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = new SimpleLookupable[B]() + + implicit val lookupInt = new SimpleLookupable[Int]() + implicit val lookupByte = new SimpleLookupable[Byte]() + implicit val lookupShort = new SimpleLookupable[Short]() + implicit val lookupLong = new SimpleLookupable[Long]() + implicit val lookupFloat = new SimpleLookupable[Float]() + implicit val lookupChar = new SimpleLookupable[Char]() + implicit val lookupString = new SimpleLookupable[String]() + implicit val lookupBoolean = new SimpleLookupable[Boolean]() + implicit val lookupBigInt = new SimpleLookupable[BigInt]() +} diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/package.scala b/core/src/main/scala/chisel3/experimental/hierarchy/package.scala new file mode 100644 index 00000000..c309ab52 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/package.scala @@ -0,0 +1,48 @@ +package chisel3.experimental + +package object hierarchy { + + /** Classes or traits which will be used with the [[Definition]] + [[Instance]] api should be marked + * with the [[@instantiable]] annotation at the class/trait definition. + * + * @example {{{ + * @instantiable + * class MyModule extends Module { + * ... + * } + * + * val d = Definition(new MyModule) + * val i0 = Instance(d) + * val i1 = Instance(d) + * }}} + */ + class instantiable extends chisel3.internal.instantiable + + /** Classes marked with [[@instantiable]] can have their vals marked with the [[@public]] annotation to + * enable accessing these values from a [[Definition]] or [[Instance]] of the class. + * + * Only vals of the the following types can be marked [[@public]]: + * 1. IsInstantiable + * 2. IsLookupable + * 3. Data + * 4. BaseModule + * 5. Iterable/Option containing a type that meets these requirements + * 6. Basic type like String, Int, BigInt etc. + * + * @example {{{ + * @instantiable + * class MyModule extends Module { + * @public val in = IO(Input(UInt(3.W))) + * @public val out = IO(Output(UInt(3.W))) + * .. + * } + * + * val d = Definition(new MyModule) + * val i0 = Instance(d) + * val i1 = Instance(d) + * + * i1.in := i0.out + * }}} + */ + class public extends chisel3.internal.public +} diff --git a/core/src/main/scala/chisel3/internal/Binding.scala b/core/src/main/scala/chisel3/internal/Binding.scala index 6f4ab4b0..a0dcc20c 100644 --- a/core/src/main/scala/chisel3/internal/Binding.scala +++ b/core/src/main/scala/chisel3/internal/Binding.scala @@ -129,6 +129,11 @@ private[chisel3] case class ViewBinding(target: Element) extends UnconstrainedBi private[chisel3] case class AggregateViewBinding(childMap: Map[Data, Element], target: Option[Data]) extends UnconstrainedBinding +/** Binding for Data's returned from accessing an Instance/Definition members, if not readable/writable port */ +private[chisel3] case object CrossModuleBinding extends TopBinding { + def location = None +} + sealed trait LitBinding extends UnconstrainedBinding with ReadOnlyBinding // Literal binding attached to a element that is not part of a Bundle. case class ElementLitBinding(litArg: LitArg) extends LitBinding diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index f7306d5d..1d15247d 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -6,6 +6,7 @@ import scala.util.DynamicVariable import scala.collection.mutable.ArrayBuffer import chisel3._ import chisel3.experimental._ +import chisel3.experimental.hierarchy.Instance import chisel3.internal.firrtl._ import chisel3.internal.naming._ import _root_.firrtl.annotations.{CircuitName, ComponentName, IsMember, ModuleName, Named, ReferenceTarget} @@ -19,6 +20,7 @@ import scala.collection.mutable private[chisel3] class Namespace(keywords: Set[String]) { private val names = collection.mutable.HashMap[String, Long]() + def copyTo(other: Namespace): Unit = names.foreach { case (s: String, l: Long) => other.names(s) = l } for (keyword <- keywords) names(keyword) = 1 @@ -87,6 +89,9 @@ private[chisel3] trait HasId extends InstanceId { private[chisel3] def _onModuleClose: Unit = {} private[chisel3] var _parent: Option[BaseModule] = Builder.currentModule + // Set if the returned top-level module of a nested call to the Chisel Builder, see Definition.apply + private[chisel3] var _circuit: Option[BaseModule] = None + private[chisel3] val _id: Long = Builder.idGen.next // TODO: remove this, but its removal seems to cause a nasty Scala compiler crash. @@ -216,7 +221,7 @@ private[chisel3] trait HasId extends InstanceId { private[chisel3] def getRef: Arg = _ref.get private[chisel3] def getOptionRef: Option[Arg] = _ref - private def localName(c: Component): String = _ref match { + private def refName(c: Component): String = _ref match { case Some(arg) => arg fullName c case None => computeName(None, None).get } @@ -232,11 +237,13 @@ private[chisel3] trait HasId extends InstanceId { // Implementation of public methods. def instanceName: String = _parent match { - case Some(ViewParent) => reifyTarget.map(_.instanceName).getOrElse(this.localName(ViewParent.fakeComponent)) - case Some(p) => p._component match { - case Some(c) => localName(c) - case None => throwException("signalName/pathName should be called after circuit elaboration") - } + case Some(ViewParent) => reifyTarget.map(_.instanceName).getOrElse(this.refName(ViewParent.fakeComponent)) + case Some(p) => + (p._component, this) match { + case (Some(c), _) => refName(c) + case (None, d: Data) if d.topBindingOpt == Some(CrossModuleBinding) => _ref.get.localName + case (None, _) => throwException(s"signalName/pathName should be called after circuit elaboration: $this, ${_parent}") + } case None => throwException("this cannot happen") } def pathName: String = _parent match { @@ -256,7 +263,10 @@ private[chisel3] trait HasId extends InstanceId { } // TODO Should this be public? protected def circuitName: String = _parent match { - case None => instanceName + case None => _circuit match { + case None => instanceName + case Some(o) => o.circuitName + } case Some(ViewParent) => reifyParent.circuitName case Some(p) => p.circuitName } @@ -296,8 +306,12 @@ private[chisel3] trait NamedComponent extends HasId { val name = this.instanceName if (!validComponentName(name)) throwException(s"Illegal component name: $name (note: literals are illegal)") import _root_.firrtl.annotations.{Target, TargetToken} + val root = _parent.map { + case ViewParent => reifyParent + case other => other + }.get.toTarget // All NamedComponents will have a parent, only the top module can have None here Target.toTargetTokens(name).toList match { - case TargetToken.Ref(r) :: components => ReferenceTarget(this.circuitName, this.parentModName, Nil, r, components) + case TargetToken.Ref(r) :: components => root.ref(r).copy(component = components) case other => throw _root_.firrtl.annotations.Target.NamedException(s"Cannot convert $name into [[ReferenceTarget]]: $other") } @@ -354,6 +368,8 @@ private[chisel3] class DynamicContext(val annotationSeq: AnnotationSeq) { var currentReset: Option[Reset] = None val errors = new ErrorLog val namingStack = new NamingStack + // Used to indicate if this is the top-level module of full elaboration, or from a Definition + var inDefinition: Boolean = false } private[chisel3] object Builder extends LazyLogging { @@ -368,6 +384,11 @@ private[chisel3] object Builder extends LazyLogging { dynamicContextVar.value.get } + // Returns the current dynamic context + def captureContext(): DynamicContext = dynamicContext + // Sets the current dynamic contents + def restoreContext(dc: DynamicContext) = dynamicContextVar.value = Some(dc) + // Ensure we have a thread-specific ChiselContext private val chiselContext = new ThreadLocal[ChiselContext]{ override def initialValue: ChiselContext = { @@ -563,6 +584,12 @@ private[chisel3] object Builder extends LazyLogging { dynamicContext.currentReset = newReset } + def inDefinition: Boolean = { + dynamicContextVar.value + .map(_.inDefinition) + .getOrElse(false) + } + // This should only be used for testing, must be true outside of Builder context def allowReflectiveAutoCloneType: Boolean = { dynamicContextVar.value @@ -632,6 +659,10 @@ private[chisel3] object Builder extends LazyLogging { * (Note: Map is Iterable[Tuple2[_,_]] and thus excluded) */ def nameRecursively(prefix: String, nameMe: Any, namer: (HasId, String) => Unit): Unit = nameMe match { + case (id: Instance[_]) => id.cloned match { + case Right(m: internal.BaseModule.ModuleClone[_]) => namer(m.getPorts, prefix) + case _ => + } case (id: HasId) => namer(id, prefix) case Some(elt) => nameRecursively(prefix, elt, namer) case (iter: Iterable[_]) if iter.hasDefiniteSize => @@ -696,7 +727,7 @@ private[chisel3] object Builder extends LazyLogging { renames } - private [chisel3] def build[T <: RawModule](f: => T, dynamicContext: DynamicContext): (Circuit, T) = { + private [chisel3] def build[T <: BaseModule](f: => T, dynamicContext: DynamicContext): (Circuit, T) = { dynamicContextVar.withValue(Some(dynamicContext)) { ViewParent // Must initialize the singleton in a Builder context or weird things can happen // in tiny designs/testcases that never access anything in chisel3.internal diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index f8a3cf7f..0b568548 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -65,13 +65,19 @@ object PrimOp { } abstract class Arg { - def fullName(ctx: Component): String = name + def localName: String = name + def contextualName(ctx: Component): String = name + def fullName(ctx: Component): String = contextualName(ctx) def name: String } case class Node(id: HasId) extends Arg { - override def fullName(ctx: Component): String = id.getOptionRef match { - case Some(arg) => arg.fullName(ctx) + override def contextualName(ctx: Component): String = id.getOptionRef match { + case Some(arg) => arg.contextualName(ctx) + case None => id.instanceName + } + override def localName: String = id.getOptionRef match { + case Some(arg) => arg.localName case None => id.instanceName } def name: String = id.getOptionRef match { @@ -83,7 +89,7 @@ case class Node(id: HasId) extends Arg { abstract class LitArg(val num: BigInt, widthArg: Width) extends Arg { private[chisel3] def forcedWidth = widthArg.known private[chisel3] def width: Width = if (forcedWidth) widthArg else Width(minWidth) - override def fullName(ctx: Component): String = name + override def contextualName(ctx: Component): String = name // Ensure the node representing this LitArg has a ref to it and a literal binding. def bindLitArg[T <: Element](elem: T): T = { elem.bind(ElementLitBinding(this)) @@ -167,7 +173,7 @@ case class Ref(name: String) extends Arg * @param name the name of the port */ case class ModuleIO(mod: BaseModule, name: String) extends Arg { - override def fullName(ctx: Component): String = + override def contextualName(ctx: Component): String = if (mod eq ctx.id) name else s"${mod.getRef.name}.$name" } /** Ports of cloned modules (CloneModuleAsRecord) @@ -175,19 +181,25 @@ case class ModuleIO(mod: BaseModule, name: String) extends Arg { * @param name the name of the module instance */ case class ModuleCloneIO(mod: BaseModule, name: String) extends Arg { - override def fullName(ctx: Component): String = + override def localName = "" + override def contextualName(ctx: Component): String = // NOTE: mod eq ctx.id only occurs in Target and Named-related APIs - if (mod eq ctx.id) "" else name + if (mod eq ctx.id) localName else name } case class Slot(imm: Node, name: String) extends Arg { - override def fullName(ctx: Component): String = { - val immName = imm.fullName(ctx) + override def contextualName(ctx: Component): String = { + val immName = imm.contextualName(ctx) + if (immName.isEmpty) name else s"$immName.$name" + } + override def localName: String = { + val immName = imm.localName if (immName.isEmpty) name else s"$immName.$name" } } case class Index(imm: Arg, value: Arg) extends Arg { def name: String = s"[$value]" - override def fullName(ctx: Component): String = s"${imm.fullName(ctx)}[${value.fullName(ctx)}]" + override def contextualName(ctx: Component): String = s"${imm.contextualName(ctx)}[${value.contextualName(ctx)}]" + override def localName: String = s"${imm.localName}[${value.localName}]" } object Width { @@ -792,4 +804,5 @@ case class DefBlackBox(id: BaseBlackBox, name: String, ports: Seq[Port], topDir: case class Circuit(name: String, components: Seq[Component], annotations: Seq[ChiselAnnotation], renames: RenameMap) { def firrtlAnnotations: Iterable[Annotation] = annotations.flatMap(_.toFirrtl.update(renames)) + } diff --git a/docs/src/cookbooks/hierarchy.md b/docs/src/cookbooks/hierarchy.md new file mode 100644 index 00000000..91d99aa6 --- /dev/null +++ b/docs/src/cookbooks/hierarchy.md @@ -0,0 +1,204 @@ +--- +layout: docs +title: "Hierarchy Cookbook" +section: "chisel3" +--- + +# Hierarchy Cookbook + +* [How do I instantiate multiple instances with the same module parameterization, but avoid re-elaboration?](#how-do-i-instantiate-multiple-instances-with-the-same-module-parameterization) +* [How do I access internal fields of an instance?](#how-do-i-access-internal-fields-of-an-instance) +* [How do I make my parameters accessable from an instance?](#how-do-i-make-my-parameters-accessable-from-an-instance) +* [How do I reuse a previously elaborated module, if my new module has the same parameterization?](#how-do-i-reuse-a-previously-elaborated-module-if-my-new-module-has-the-same-parameterization) + +## How do I instantiate multiple instances with the same module parameterization? + +Prior to this package, Chisel users relied on deduplication in a FIRRTL compiler to combine +structurally equivalent modules into one module (aka "deduplication"). +This package introduces the following new APIs to enable multiply-instantiated modules directly in Chisel. + +`Definition(...)` enables elaborating a module, but does not actually instantiate that module. +Instead, it returns a `Definition` class which represents that module's definition. + +`Instance(...)` takes a `Definition` and instantiates it, returning an `Instance` object. + +Modules (classes or traits) which will be used with the `Definition`/`Instance` api should be marked +with the `@instantiable` annotation at the class/trait definition. + +To make a Module's members variables accessible from an `Instance` object, they must be annotated +with the `@public` annotation. Note that this is only accessible from a Scala sense—this is not +in and of itself a mechanism for cross-module references. + +In the following example, use `Definition`, `Instance`, `@instantiable` and `@public` to create +multiple instances of one specific parameterization of a module, `AddOne`. + +```scala mdoc:silent +import chisel3._ +import chisel3.experimental.hierarchy.{Definition, Instance, instantiable, public} + +@instantiable +class AddOne(width: Int) extends Module { + @public val in = IO(Input(UInt(width.W))) + @public val out = IO(Output(UInt(width.W))) + out := in + 1.U +} + +class AddTwo(width: Int) extends Module { + val in = IO(Input(UInt(width.W))) + val out = IO(Output(UInt(width.W))) + val addOneDef = Definition(new AddOne(width)) + val i0 = Instance(addOneDef) + val i1 = Instance(addOneDef) + i0.in := in + i1.in := i0.out + out := i1.out +} +``` +```scala mdoc:verilog +chisel3.stage.ChiselStage.emitVerilog(new AddTwo(10)) +``` + +## How do I access internal fields of an instance? + +You can mark internal members of a class or trait marked with `@instantiable` with the `@public` annotation. +The requirements are that the field is publicly accessible, is a `val` or `lazy val`, and is a valid type. +The list of valid types are: + +1. `IsInstantiable` +2. `IsLookupable` +3. `Data` +4. `BaseModule` +5. `Iterable`/`Option `containing a type that meets these requirements +6. Basic type like `String`, `Int`, `BigInt` etc. + +To mark a superclass's member as `@public`, use the following pattern (shown with `val clock`). + +```scala mdoc:silent:reset +import chisel3._ +import chisel3.experimental.hierarchy.{instantiable, public} + +@instantiable +class MyModule extends Module { + @public val clock = clock +} +``` + +You'll get the following error message for improperly marking something as `@public`: + +```scala mdoc:reset:fail +import chisel3._ +import chisel3.experimental.hierarchy.{instantiable, public} + +object NotValidType + +@instantiable +class MyModule extends Module { + @public val x = NotValidType +} +``` + +## How do I make my parameters accessible from an instance? + +If an instance's parameters are simple (e.g. `Int`, `String` etc.) they can be marked directly with `@public`. + +Often, parameters are more complicated and are contained in case classes. +In such cases, mark the case class with the `IsLookupable` trait. +This indicates to Chisel that instances of the `IsLookupable` class may be accessed from within instances. + +However, ensure that these parameters are true for **all** instances of a definition. +For example, if our parameters contained an id field which was instance-specific but defaulted to zero, +then the definition's id would be returned for all instances. +This change in behavior could lead to bugs if other code presumed the id field was correct. + +Thus, it is important that when converting normal modules to use this package, +you are careful about what you mark as `IsLookupable`. + +In the following example, we added the trait `IsLookupable` to allow the member to be marked `@public`. + +```scala mdoc:reset:silent +import chisel3._ +import chisel3.experimental.hierarchy.{Definition, Instance, instantiable, IsLookupable, public} + +case class MyCaseClass(width: Int) extends IsLookupable + +@instantiable +class MyModule extends Module { + @public val x = MyCaseClass(10) +} + +class Top extends Module { + val inst = Instance(Definition(new MyModule)) + println(s"Width is ${inst.x.width}") +} +``` +```scala mdoc:passthrough +println("```") +chisel3.stage.ChiselStage.elaborate(new Top) +println("```") +``` + +## How do I look up parameters from a Definition, if I don't want to instantiate it? + +Just like `Instance`s, `Definition`'s also contain accessors for `@public` members. +As such, you can directly access them: + +```scala mdoc:reset:silent +import chisel3._ +import chisel3.experimental.hierarchy.{Definition, instantiable, public} + +@instantiable +class AddOne(val width: Int) extends Module { + @public val width = width + @public val in = IO(Input(UInt(width.W))) + @public val out = IO(Output(UInt(width.W))) + out := in + 1.U +} + +class Top extends Module { + val definition = Definition(new AddOne(10)) + println(s"Width is: ${definition.width}") +} +``` +```scala mdoc:verilog +chisel3.stage.ChiselStage.emitVerilog(new Top()) +``` + +## How do I parameterize a module by its children instances? + +Prior to the introduction of this package, a parent module would have to pass all necessary parameters +when instantiating a child module. +This had the unfortunate consequence of requiring a parent's parameters to always contain the child's +parameters, which was an unnecessary coupling which lead to some anti-patterns. + +Now, a parent can take a child `Definition` as an argument, and instantiate it directly. +In addition, it can analyze the parameters used in the definition to parameterize itself. +In a sense, now the child can actually parameterize the parent. + +In the following example, we create a definition of `AddOne`, and pass the definition to `AddTwo`. +The width of the `AddTwo` ports are now derived from the parameterization of the `AddOne` instance. + +```scala mdoc:reset +import chisel3._ +import chisel3.experimental.hierarchy.{Definition, Instance, instantiable, public} + +@instantiable +class AddOne(val width: Int) extends Module { + @public val width = width + @public val in = IO(Input(UInt(width.W))) + @public val out = IO(Output(UInt(width.W))) + out := in + 1.U +} + +class AddTwo(addOneDef: Definition[AddOne]) extends Module { + val i0 = Instance(addOneDef) + val i1 = Instance(addOneDef) + val in = IO(Input(UInt(addOneDef.width.W))) + val out = IO(Output(UInt(addOneDef.width.W))) + i0.in := in + i1.in := i0.out + out := i1.out +} +``` +```scala mdoc:verilog +chisel3.stage.ChiselStage.emitVerilog(new AddTwo(Definition(new AddOne(10)))) +``` diff --git a/macros/src/main/scala/chisel3/internal/InstantiableMacro.scala b/macros/src/main/scala/chisel3/internal/InstantiableMacro.scala new file mode 100644 index 00000000..1d374198 --- /dev/null +++ b/macros/src/main/scala/chisel3/internal/InstantiableMacro.scala @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.internal + +import scala.language.experimental.macros +import scala.annotation.StaticAnnotation +import scala.reflect.macros.whitebox + + +private[chisel3] object instantiableMacro { + + def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { + import c.universe._ + def processBody(stats: Seq[Tree]): (Seq[Tree], Iterable[Tree]) = { + val extensions = scala.collection.mutable.ArrayBuffer.empty[Tree] + extensions += q"implicit val mg = new chisel3.internal.MacroGenerated{}" + val resultStats = stats.flatMap { + case x @ q"@public val $tpname: $tpe = $name" if tpname.toString() == name.toString() => + extensions += atPos(x.pos)(q"def $tpname = module._lookup(_.$tpname)") + Nil + case x @ q"@public val $tpname: $tpe = $_" => + extensions += atPos(x.pos)(q"def $tpname = module._lookup(_.$tpname)") + Seq(x) + case x @ q"@public lazy val $tpname: $tpe = $_" => + extensions += atPos(x.pos)(q"def $tpname = module._lookup(_.$tpname)") + Seq(x) + case other => Seq(other) + } + (resultStats, extensions) + } + val result = { + val (clz, objOpt) = annottees.map(_.tree).toList match { + case Seq(c, o) => (c, Some(o)) + case Seq(c) => (c, None) + } + val (newClz, implicitClzs, tpname) = clz match { + case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" => + val defname = TypeName(tpname + c.freshName()) + val instname = TypeName(tpname + c.freshName()) + val (newStats, extensions) = processBody(stats) + (q""" $mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents with chisel3.experimental.hierarchy.IsInstantiable { $self => ..$newStats } """, + Seq(q"""implicit class $defname(module: chisel3.experimental.hierarchy.Definition[$tpname]) { ..$extensions }""", + q"""implicit class $instname(module: chisel3.experimental.hierarchy.Instance[$tpname]) { ..$extensions } """), + tpname) + case q"$mods trait $tpname[..$tparams] extends { ..$earlydefns } with ..$parents { $self => ..$stats }" => + val defname = TypeName(tpname + c.freshName()) + val instname = TypeName(tpname + c.freshName()) + val (newStats, extensions) = processBody(stats) + (q"$mods trait $tpname[..$tparams] extends { ..$earlydefns } with ..$parents with chisel3.experimental.hierarchy.IsInstantiable { $self => ..$newStats }", + Seq(q"""implicit class $defname(module: chisel3.experimental.hierarchy.Definition[$tpname]) { ..$extensions }""", + q"""implicit class $instname(module: chisel3.experimental.hierarchy.Instance[$tpname]) { ..$extensions } """), + tpname) + } + val newObj = objOpt match { + case None => q"""object ${tpname.toTermName} { ..$implicitClzs } """ + case Some(obj @ q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }") => + q""" + $mods object $tname extends { ..$earlydefns } with ..$parents { $self => + ..$implicitClzs + ..$body + } + """ + } + q""" + $newClz + + $newObj + """ + } + c.Expr[Any](result) + } +} + +private[chisel3] class instantiable extends StaticAnnotation { + def macroTransform(annottees: Any*): Any = macro instantiableMacro.impl +} +private[chisel3] class public extends StaticAnnotation diff --git a/macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala b/macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala index 6121bc1e..a8a7e8d5 100644 --- a/macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala +++ b/macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala @@ -46,6 +46,36 @@ class InstTransform(val c: Context) extends SourceInfoTransformMacro { } } +// Workaround for https://github.com/sbt/sbt/issues/3966 +object DefinitionTransform +// Module instantiation transform +class DefinitionTransform(val c: Context) extends SourceInfoTransformMacro { + import c.universe._ + def apply[T: c.WeakTypeTag](proto: c.Tree): c.Tree = { + q"$thisObj.do_apply($proto)($implicitSourceInfo, $implicitCompileOptions)" + } +} + +object DefinitionWrapTransform +// Module instantiation transform +class DefinitionWrapTransform(val c: Context) extends SourceInfoTransformMacro { + import c.universe._ + def wrap[T: c.WeakTypeTag](proto: c.Tree): c.Tree = { + q"$thisObj.do_wrap($proto)($implicitSourceInfo)" + } +} + + +// Workaround for https://github.com/sbt/sbt/issues/3966 +object InstanceTransform +// Module instantiation transform +class InstanceTransform(val c: Context) extends SourceInfoTransformMacro { + import c.universe._ + def apply[T: c.WeakTypeTag](definition: c.Tree): c.Tree = { + q"$thisObj.do_apply($definition)($implicitSourceInfo, $implicitCompileOptions)" + } +} + // Workaround for https://github.com/sbt/sbt/issues/3966 object MemTransform class MemTransform(val c: Context) extends SourceInfoTransformMacro { diff --git a/plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala b/plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala index b1302ba3..af22e6a7 100644 --- a/plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala +++ b/plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala @@ -82,6 +82,7 @@ class ChiselComponent(val global: Global) extends PluginComponent with TypingTra private val shouldMatchData : Type => Boolean = shouldMatchGen(tq"chisel3.Data") private val shouldMatchDataOrMem : Type => Boolean = shouldMatchGen(tq"chisel3.Data", tq"chisel3.MemBase[_]") private val shouldMatchModule : Type => Boolean = shouldMatchGen(tq"chisel3.experimental.BaseModule") + private val shouldMatchInstance : Type => Boolean = shouldMatchGen(tq"chisel3.experimental.hierarchy.Instance[_]") // Given a type tree, infer the type and return it private def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe @@ -188,6 +189,11 @@ class ChiselComponent(val global: Global) extends PluginComponent with TypingTra val newRHS = transform(rhs) val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + } else if (shouldMatchInstance(tpe)) { + val str = stringFromTermName(name) + val newRHS = transform(rhs) + val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) } else { // Otherwise, continue super.transform(tree) diff --git a/src/main/scala/chisel3/aop/Select.scala b/src/main/scala/chisel3/aop/Select.scala index 078422bb..2384c4d3 100644 --- a/src/main/scala/chisel3/aop/Select.scala +++ b/src/main/scala/chisel3/aop/Select.scala @@ -3,13 +3,16 @@ package chisel3.aop import chisel3._ -import chisel3.experimental.{BaseModule, FixedPoint} -import chisel3.internal.HasId +import chisel3.internal.{HasId} +import chisel3.experimental.BaseModule +import chisel3.experimental.FixedPoint import chisel3.internal.firrtl._ +import chisel3.internal.PseudoModule import chisel3.internal.BaseModule.ModuleClone import firrtl.annotations.ReferenceTarget import scala.collection.mutable +import chisel3.internal.naming.chiselName /** Use to select Chisel components in a module, after that module has been constructed * Useful for adding additional Chisel annotations or for use within an [[Aspect]] @@ -84,7 +87,8 @@ object Select { module._component.get match { case d: DefModule => d.commands.flatMap { case i: DefInstance => i.id match { - case _: ModuleClone => None + case m: ModuleClone[_] if !m._madeFromDefinition => None + case _: PseudoModule => throw new Exception("Aspect APIs are currently incompatible with Definition/Instance") case other => Some(other) } case _ => None diff --git a/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala b/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala index c540fc83..1a476f61 100644 --- a/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala +++ b/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala @@ -4,6 +4,7 @@ package chisel3.aop.injecting import chisel3.{Module, ModuleAspect, RawModule, withClockAndReset} import chisel3.aop._ +import chisel3.experimental.hierarchy.IsInstantiable import chisel3.internal.{Builder, DynamicContext} import chisel3.internal.firrtl.DefModule import chisel3.stage.DesignAnnotation diff --git a/src/test/scala/chiselTests/ChiselSpec.scala b/src/test/scala/chiselTests/ChiselSpec.scala index 8e35273d..8647d903 100644 --- a/src/test/scala/chiselTests/ChiselSpec.scala +++ b/src/test/scala/chiselTests/ChiselSpec.scala @@ -16,6 +16,7 @@ import org.scalacheck._ import org.scalatest._ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.funspec.AnyFunSpec import org.scalatest.propspec.AnyPropSpec import org.scalatest.matchers.should.Matchers import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks @@ -104,13 +105,14 @@ trait ChiselRunners extends Assertions with BackendCompilationUtilities { * @param t the generator for the module * @return The FIRRTL Circuit and Annotations _before_ FIRRTL compilation */ - def getFirrtlAndAnnos(t: => RawModule): (Circuit, Seq[Annotation]) = { + def getFirrtlAndAnnos(t: => RawModule, providedAnnotations: Seq[Annotation] = Nil): (Circuit, Seq[Annotation]) = { val args = Array( "--target-dir", createTestDirectory(this.getClass.getSimpleName).toString, - "--no-run-firrtl" + "--no-run-firrtl", + "--full-stacktrace" ) - val annos = (new ChiselStage).execute(args, Seq(ChiselGeneratorAnnotation(() => t))) + val annos = (new ChiselStage).execute(args, Seq(ChiselGeneratorAnnotation(() => t)) ++ providedAnnotations) val circuit = annos.collectFirst { case FirrtlCircuitAnnotation(c) => c }.getOrElse(fail("No FIRRTL Circuit found!!")) @@ -124,6 +126,9 @@ abstract class ChiselFlatSpec extends AnyFlatSpec with ChiselRunners with Matche /** Spec base class for BDD-style testers. */ abstract class ChiselFreeSpec extends AnyFreeSpec with ChiselRunners with Matchers +/** Spec base class for BDD-style testers. */ +abstract class ChiselFunSpec extends AnyFunSpec with ChiselRunners with Matchers + /** Spec base class for property-based testers. */ abstract class ChiselPropSpec extends AnyPropSpec with ChiselRunners with ScalaCheckPropertyChecks with Matchers { diff --git a/src/test/scala/chiselTests/aop/SelectSpec.scala b/src/test/scala/chiselTests/aop/SelectSpec.scala index 46c62d67..e09e78c8 100644 --- a/src/test/scala/chiselTests/aop/SelectSpec.scala +++ b/src/test/scala/chiselTests/aop/SelectSpec.scala @@ -163,7 +163,7 @@ class SelectSpec extends ChiselFlatSpec { val out = IO(Output(UInt(8.W))) out := in } - class Top extends MultiIOModule { + class Top extends Module { val in = IO(Input(UInt(8.W))) val out = IO(Output(UInt(8.W))) val inst0 = Module(new Child) @@ -182,5 +182,34 @@ class SelectSpec extends ChiselFlatSpec { Select.instances(top) should equal (Seq(top.inst0)) } + "Using Definition/Instance with Injecting Aspects" should "throw an error" in { + import chisel3.experimental.CloneModuleAsRecord + import chisel3.experimental.hierarchy._ + @instantiable + class Child extends RawModule { + @public val in = IO(Input(UInt(8.W))) + @public val out = IO(Output(UInt(8.W))) + out := in + } + class Top extends Module { + val in = IO(Input(UInt(8.W))) + val out = IO(Output(UInt(8.W))) + val definition = Definition(new Child) + val inst0 = Instance(definition) + val inst1 = Instance(definition) + inst0.in := in + inst1.in := inst0.out + out := inst1.out + } + val top = ChiselGeneratorAnnotation(() => { + new Top() + }).elaborate + .collectFirst { case DesignAnnotation(design: Top) => design } + .get + intercept[Exception] { Select.collectDeep(top) { case x => x } } + intercept[Exception] { Select.getDeep(top)(x => Seq(x)) } + intercept[Exception] { Select.instances(top) } + } + } diff --git a/src/test/scala/chiselTests/experimental/DataView.scala b/src/test/scala/chiselTests/experimental/DataView.scala index 381cfeb5..d1620e88 100644 --- a/src/test/scala/chiselTests/experimental/DataView.scala +++ b/src/test/scala/chiselTests/experimental/DataView.scala @@ -29,6 +29,27 @@ object VecBundleDataView { implicit val v2 = v1.invert(_ => new MyBundle) } +object FlatDecoupledDataView { + class FizzBuzz extends Bundle { + val fizz = UInt(8.W) + val buzz = UInt(8.W) + } + class FlatDecoupled extends Bundle { + val valid = Output(Bool()) + val ready = Input(Bool()) + val fizz = Output(UInt(8.W)) + val buzz = Output(UInt(8.W)) + } + implicit val view = DataView[FlatDecoupled, DecoupledIO[FizzBuzz]]( + _ => Decoupled(new FizzBuzz), + _.valid -> _.valid, + _.ready -> _.ready, + _.fizz -> _.bits.fizz, + _.buzz -> _.bits.buzz + ) + implicit val view2 = view.invert(_ => new FlatDecoupled) +} + // This should become part of Chisel in a later PR object Tuple2DataProduct { implicit def tuple2DataProduct[A : DataProduct, B : DataProduct] = new DataProduct[(A, B)] { @@ -177,24 +198,7 @@ class DataViewSpec extends ChiselFlatSpec { } it should "work with bidirectional connections for nested types" in { - class FizzBuzz extends Bundle { - val fizz = UInt(8.W) - val buzz = UInt(8.W) - } - class FlatDecoupled extends Bundle { - val valid = Output(Bool()) - val ready = Input(Bool()) - val fizz = Output(UInt(8.W)) - val buzz = Output(UInt(8.W)) - } - implicit val view = DataView[FlatDecoupled, DecoupledIO[FizzBuzz]]( - _ => Decoupled(new FizzBuzz), - _.valid -> _.valid, - _.ready -> _.ready, - _.fizz -> _.bits.fizz, - _.buzz -> _.bits.buzz - ) - implicit val view2 = view.invert(_ => new FlatDecoupled) + import FlatDecoupledDataView._ class MyModule extends Module { val enq = IO(Flipped(Decoupled(new FizzBuzz))) val deq = IO(new FlatDecoupled) diff --git a/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala b/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala index 41636da7..92091631 100644 --- a/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala +++ b/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala @@ -1,4 +1,4 @@ -// See LICENSE for license details. +// SPDX-License-Identifier: Apache-2.0 package chiselTests.experimental @@ -166,4 +166,6 @@ class DataViewTargetSpec extends ChiselFlatSpec { ) pairs should equal (expected) } + + // TODO check these properties when using @instance API (especially preservation of totality) } diff --git a/src/test/scala/chiselTests/experimental/hierarchy/Annotations.scala b/src/test/scala/chiselTests/experimental/hierarchy/Annotations.scala new file mode 100644 index 00000000..43111fdd --- /dev/null +++ b/src/test/scala/chiselTests/experimental/hierarchy/Annotations.scala @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.experimental.hierarchy + +import _root_.firrtl.annotations._ +import chisel3.experimental.{annotate, BaseModule} +import chisel3.Data +import chisel3.experimental.hierarchy.{Instance, Definition} + +object Annotations { + case class MarkAnnotation(target: IsMember, tag: String) extends SingleTargetAnnotation[IsMember] { + def duplicate(n: IsMember): Annotation = this.copy(target = n) + } + case class MarkChiselInstanceAnnotation[B <: BaseModule](d: Instance[B], tag: String, isAbsolute: Boolean) extends chisel3.experimental.ChiselAnnotation { + def toFirrtl = MarkAnnotation(d.toTarget, tag) + } + case class MarkChiselDefinitionAnnotation[B <: BaseModule](d: Definition[B], tag: String, isAbsolute: Boolean) extends chisel3.experimental.ChiselAnnotation { + def toFirrtl = MarkAnnotation(d.toTarget, tag) + } + case class MarkChiselAnnotation(d: Data, tag: String, isAbsolute: Boolean) extends chisel3.experimental.ChiselAnnotation { + def toFirrtl = if(isAbsolute) MarkAnnotation(d.toAbsoluteTarget, tag) else MarkAnnotation(d.toTarget, tag) + } + def mark(d: Data, tag: String): Unit = annotate(MarkChiselAnnotation(d, tag, false)) + def mark[B <: BaseModule](d: Instance[B], tag: String): Unit = annotate(MarkChiselInstanceAnnotation(d, tag, false)) + def mark[B <: BaseModule](d: Definition[B], tag: String): Unit = annotate(MarkChiselDefinitionAnnotation(d, tag, false)) + def amark(d: Data, tag: String): Unit = annotate(MarkChiselAnnotation(d, tag, true)) + def amark[B <: BaseModule](d: Instance[B], tag: String): Unit = annotate(MarkChiselInstanceAnnotation(d, tag, true)) +} diff --git a/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala b/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala new file mode 100644 index 00000000..19261c36 --- /dev/null +++ b/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests +package experimental.hierarchy + +import chisel3._ +import chisel3.experimental.BaseModule +import chisel3.experimental.hierarchy.{Definition, Instance, instantiable, public} + +// TODO/Notes +// - In backport, clock/reset are not automatically assigned. I think this is fixed in 3.5 +// - CircuitTarget for annotations on the definition are wrong - needs to be fixed. +class DefinitionSpec extends ChiselFunSpec with Utils { + import Annotations._ + import Examples._ + describe("0: Definition instantiation") { + it("0.0: module name of a definition should be correct") { + class Top extends Module { + val definition = Definition(new AddOne) + } + val (chirrtl, _) = getFirrtlAndAnnos(new Top) + chirrtl.serialize should include ("module AddOne :") + } + it("0.2: accessing internal fields through non-generated means is hard to do") { + class Top extends Module { + val definition = Definition(new AddOne) + //definition.lookup(_.in) // Uncommenting this line will give the following error: + //"You are trying to access a macro-only API. Please use the @public annotation instead." + definition.in + } + val (chirrtl, _) = getFirrtlAndAnnos(new Top) + chirrtl.serialize should include ("module AddOne :") + } + it("0.2: reset inference is not defaulted to Bool for definitions") { + class Top extends Module with RequireAsyncReset { + val definition = Definition(new HasUninferredReset) + val i0 = Instance(definition) + i0.in := 0.U + } + val (chirrtl, _) = getFirrtlAndAnnos(new Top) + chirrtl.serialize should include ("inst i0 of HasUninferredReset") + } + } + describe("1: Annotations on definitions in same chisel compilation") { + it("1.0: should work on a single definition, annotating the definition") { + class Top extends Module { + val definition: Definition[AddOne] = Definition(new AddOne) + mark(definition, "mark") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOne".mt, "mark")) + } + it("1.1: should work on a single definition, annotating an inner wire") { + class Top extends Module { + val definition: Definition[AddOne] = Definition(new AddOne) + mark(definition.innerWire, "i0.innerWire") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOne>innerWire".rt, "i0.innerWire")) + } + it("1.2: should work on a two nested definitions, annotating the definition") { + class Top extends Module { + val definition: Definition[AddTwo] = Definition(new AddTwo) + mark(definition.definition, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOne".mt, "i0.i0")) + } + it("1.2: should work on an instance in a definition, annotating the instance") { + class Top extends Module { + val definition: Definition[AddTwo] = Definition(new AddTwo) + mark(definition.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne".it, "i0.i0")) + } + it("1.2: should work on a definition in an instance, annotating the definition") { + class Top extends Module { + val definition: Definition[AddTwo] = Definition(new AddTwo) + val i0 = Instance(definition) + mark(i0.definition, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOne".mt, "i0.i0")) + } + it("1.3: should work on a wire in an instance in a definition") { + class Top extends Module { + val definition: Definition[AddTwo] = Definition(new AddTwo) + mark(definition.i0.innerWire, "i0.i0.innerWire") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "i0.i0.innerWire")) + } + it("1.4: should work on a nested module in a definition, annotating the module") { + class Top extends Module { + val definition: Definition[AddTwoMixedModules] = Definition(new AddTwoMixedModules) + mark(definition.i1, "i0.i1") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwoMixedModules/i1:AddOne_2".it, "i0.i1")) + } + // Can you define an instantiable container? I think not. + // Instead, we can test the instantiable container in a definition + it("1.5: should work on an instantiable container, annotating a wire in the defintion") { + class Top extends Module { + val definition: Definition[AddOneWithInstantiableWire] = Definition(new AddOneWithInstantiableWire) + mark(definition.wireContainer.innerWire, "i0.innerWire") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithInstantiableWire>innerWire".rt, "i0.innerWire")) + } + it("1.6: should work on an instantiable container, annotating a module") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableModule) + mark(definition.moduleContainer.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithInstantiableModule/i0:AddOne".it, "i0.i0")) + } + it("1.7: should work on an instantiable container, annotating an instance") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableInstance) + mark(definition.instanceContainer.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithInstantiableInstance/i0:AddOne".it, "i0.i0")) + } + it("1.8: should work on an instantiable container, annotating an instantiable container's module") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableInstantiable) + mark(definition.containerContainer.container.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithInstantiableInstantiable/i0:AddOne".it, "i0.i0")) + } + it("1.9: should work on public member which references public member of another instance") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableInstantiable) + mark(definition.containerContainer.container.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithInstantiableInstantiable/i0:AddOne".it, "i0.i0")) + } + it("1.10: should work for targets on definition to have correct circuit name"){ + class Top extends Module { + val definition = Definition(new AddOneWithAnnotation) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithAnnotation>innerWire".rt, "innerWire")) + } + } + describe("2: Annotations on designs not in the same chisel compilation") { + it("2.0: should work on an innerWire, marked in a different compilation") { + val first = elaborateAndGetModule(new AddTwo) + class Top(x: AddTwo) extends Module { + val parent = Definition(new ViewerParent(x, false, true)) + } + val (_, annos) = getFirrtlAndAnnos(new Top(first)) + annos should contain(MarkAnnotation("~AddTwo|AddTwo/i0:AddOne>innerWire".rt, "first")) + } + it("2.1: should work on an innerWire, marked in a different compilation, in instanced instantiable") { + val first = elaborateAndGetModule(new AddTwo) + class Top(x: AddTwo) extends Module { + val parent = Definition(new ViewerParent(x, true, false)) + } + val (_, annos) = getFirrtlAndAnnos(new Top(first)) + annos should contain(MarkAnnotation("~AddTwo|AddTwo/i0:AddOne>innerWire".rt, "second")) + } + it("2.2: should work on an innerWire, marked in a different compilation, in instanced module") { + val first = elaborateAndGetModule(new AddTwo) + class Top(x: AddTwo) extends Module { + val parent = Definition(new ViewerParent(x, false, false)) + mark(parent.viewer.x.i0.innerWire, "third") + } + val (_, annos) = getFirrtlAndAnnos(new Top(first)) + annos should contain(MarkAnnotation("~AddTwo|AddTwo/i0:AddOne>innerWire".rt, "third")) + } + } + describe("3: @public") { + it("3.0: should work on multi-vals") { + class Top() extends Module { + val mv = Definition(new MultiVal()) + mark(mv.x, "mv.x") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|MultiVal>x".rt, "mv.x")) + } + it("3.1: should work on lazy vals") { + class Top() extends Module { + val lv = Definition(new LazyVal()) + mark(lv.x, lv.y) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|LazyVal>x".rt, "Hi")) + } + it("3.2: should work on islookupables") { + class Top() extends Module { + val p = Parameters("hi", 0) + val up = Definition(new UsesParameters(p)) + mark(up.x, up.y.string + up.y.int) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|UsesParameters>x".rt, "hi0")) + } + it("3.3: should work on lists") { + class Top() extends Module { + val i = Definition(new HasList()) + mark(i.x(1), i.y(1).toString) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasList>x_1".rt, "2")) + } + it("3.4: should work on seqs") { + class Top() extends Module { + val i = Definition(new HasSeq()) + mark(i.x(1), i.y(1).toString) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasSeq>x_1".rt, "2")) + } + it("3.5: should work on options") { + class Top() extends Module { + val i = Definition(new HasOption()) + i.x.map(x => mark(x, "x")) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasOption>x".rt, "x")) + } + it("3.6: should work on vecs") { + class Top() extends Module { + val i = Definition(new HasVec()) + mark(i.x, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasVec>x".rt, "blah")) + } + it("3.7: should work on statically indexed vectors external to module") { + class Top() extends Module { + val i = Definition(new HasVec()) + mark(i.x(1), "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasVec>x[1]".rt, "blah")) + } + it("3.8: should work on statically indexed vectors internal to module") { + class Top() extends Module { + val i = Definition(new HasIndexedVec()) + mark(i.y, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasIndexedVec>x[1]".rt, "blah")) + } + ignore("3.9: should work on vals in constructor arguments") { + class Top() extends Module { + val i = Definition(new HasPublicConstructorArgs(10)) + //mark(i.x, i.int.toString) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasPublicConstructorArgs>x".rt, "10")) + } + } + describe("4: toDefinition") { + it("4.0: should work on modules") { + class Top() extends Module { + val i = Module(new AddOne()) + f(i.toDefinition) + } + def f(i: Definition[AddOne]): Unit = mark(i.innerWire, "blah") + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOne>innerWire".rt, "blah")) + } + it("4.2: should work on seqs of modules") { + class Top() extends Module { + val is = Seq(Module(new AddTwo()), Module(new AddTwo())).map(_.toDefinition) + mark(f(is), "blah") + } + def f(i: Seq[Definition[AddTwo]]): Data = i.head.i0.innerWire + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + it("4.2: should work on options of modules") { + class Top() extends Module { + val is: Option[Definition[AddTwo]] = Some(Module(new AddTwo())).map(_.toDefinition) + mark(f(is), "blah") + } + def f(i: Option[Definition[AddTwo]]): Data = i.get.i0.innerWire + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + } + describe("5: Absolute Targets should work as expected") { + it("5.0: toAbsoluteTarget on a port of a definition") { + class Top() extends Module { + val i = Definition(new AddTwo()) + amark(i.in, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo>in".rt, "blah")) + } + it("5.1: toAbsoluteTarget on a subinstance's data within a definition") { + class Top() extends Module { + val i = Definition(new AddTwo()) + amark(i.i0.innerWire, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + it("5.2: toAbsoluteTarget on a submodule's data within a definition") { + class Top() extends Module { + val i = Definition(new AddTwoMixedModules()) + amark(i.i1.in, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwoMixedModules/i1:AddOne_2>in".rt, "blah")) + } + it("5.3: toAbsoluteTarget on a submodule's data, in an aggregate, within a definition") { + class Top() extends Module { + val i = Definition(new InstantiatesHasVec()) + amark(i.i1.x.head, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|InstantiatesHasVec/i1:HasVec_2>x[0]".rt, "blah")) + } + } + describe("6: @instantiable traits should work as expected") { + class MyBundle extends Bundle { + val in = Input(UInt(8.W)) + val out = Output(UInt(8.W)) + } + @instantiable + trait ModuleIntf extends BaseModule { + @public val io = IO(new MyBundle) + } + @instantiable + class ModuleWithCommonIntf(suffix: String = "") extends Module with ModuleIntf { + override def desiredName: String = super.desiredName + suffix + @public val sum = io.in + 1.U + + io.out := sum + } + class BlackBoxWithCommonIntf extends BlackBox with ModuleIntf + + it("6.0: A Module that implements an @instantiable trait should be definable as that trait") { + class Top extends Module { + val i: Definition[ModuleIntf] = Definition(new ModuleWithCommonIntf) + mark(i.io.in, "gotcha") + mark(i, "inst") + } + val expected = List( + "~Top|ModuleWithCommonIntf>io.in".rt -> "gotcha", + "~Top|ModuleWithCommonIntf".mt -> "inst" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("6.1 An @instantiable Module that implements an @instantiable trait should be able to use extension methods from both") { + class Top extends Module { + val i: Definition[ModuleWithCommonIntf] = Definition(new ModuleWithCommonIntf) + mark(i.io.in, "gotcha") + mark(i.sum, "also this") + mark(i, "inst") + } + val expected = List( + "~Top|ModuleWithCommonIntf>io.in".rt -> "gotcha", + "~Top|ModuleWithCommonIntf>sum".rt -> "also this", + "~Top|ModuleWithCommonIntf".mt -> "inst" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("6.2 A BlackBox that implements an @instantiable trait should be instantiable as that trait") { + class Top extends Module { + val m: ModuleIntf = Module(new BlackBoxWithCommonIntf) + val d: Definition[ModuleIntf] = m.toDefinition + mark(d.io.in, "gotcha") + mark(d, "module") + } + val expected = List( + "~Top|BlackBoxWithCommonIntf>in".rt -> "gotcha", + "~Top|BlackBoxWithCommonIntf".mt -> "module" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("6.3 It should be possible to have Vectors of @instantiable traits mixing concrete subclasses") { + class Top extends Module { + val definition = Definition(new ModuleWithCommonIntf("X")) + val insts: Seq[Definition[ModuleIntf]] = Vector( + Module(new ModuleWithCommonIntf("Y")).toDefinition, + Module(new BlackBoxWithCommonIntf).toDefinition, + definition + ) + mark(insts(0).io.in, "foo") + mark(insts(1).io.in, "bar") + mark(insts(2).io.in, "fizz") + } + val expected = List( + "~Top|ModuleWithCommonIntfY>io.in".rt -> "foo", + "~Top|BlackBoxWithCommonIntf>in".rt -> "bar", + "~Top|ModuleWithCommonIntfX>io.in".rt -> "fizz" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + } + describe("7: @instantiable and @public should compose with DataView") { + import chisel3.experimental.dataview._ + ignore("7.0: should work on simple Views") { + @instantiable + class MyModule extends RawModule { + val in = IO(Input(UInt(8.W))) + @public val out = IO(Output(UInt(8.W))) + val sum = in + 1.U + out := sum + 1.U + @public val foo = in.viewAs[UInt] + @public val bar = sum.viewAs[UInt] + } + class Top extends RawModule { + val foo = IO(Input(UInt(8.W))) + val bar = IO(Output(UInt(8.W))) + val d = Definition(new MyModule) + val i = Instance(d) + i.foo := foo + bar := i.out + mark(d.out, "out") + mark(d.foo, "foo") + mark(d.bar, "bar") + } + val expectedAnnos = List( + "~Top|MyModule>out".rt -> "out", + "~Top|MyModule>in".rt -> "foo", + "~Top|MyModule>sum".rt -> "bar" + ) + 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) + } + } + ignore("7.1: should work on Aggregate Views that are mapped 1:1") { + import chiselTests.experimental.SimpleBundleDataView._ + @instantiable + class MyModule extends RawModule { + private val a = IO(Input(new BundleA(8))) + private val b = IO(Output(new BundleA(8))) + @public val in = a.viewAs[BundleB] + @public val out = b.viewAs[BundleB] + out := in + } + class Top extends RawModule { + val foo = IO(Input(new BundleB(8))) + val bar = IO(Output(new BundleB(8))) + val d = Definition(new MyModule) + val i = Instance(d) + i.in := foo + bar.bar := i.out.bar + mark(d.in, "in") + mark(d.in.bar, "in_bar") + } + val expectedAnnos = List( + "~Top|MyModule>a".rt -> "in", + "~Top|MyModule>a.foo".rt -> "in_bar", + ) + val expectedLines = List( + "i.a <= foo", + "bar <= i.b.foo" + ) + 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) + } + } + } +} diff --git a/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala b/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala new file mode 100644 index 00000000..23b8c9c0 --- /dev/null +++ b/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.experimental.hierarchy + +import chisel3._ +import chisel3.util.Valid +import chisel3.experimental.hierarchy._ +import chisel3.experimental.BaseModule + +object Examples { + import Annotations._ + @instantiable + class AddOne extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val innerWire = Wire(UInt(32.W)) + innerWire := in + 1.U + out := innerWire + } + @instantiable + class AddOneWithAnnotation extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val innerWire = Wire(UInt(32.W)) + mark(innerWire, "innerWire") + innerWire := in + 1.U + out := innerWire + } + @instantiable + class AddOneWithAbsoluteAnnotation extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val innerWire = Wire(UInt(32.W)) + amark(innerWire, "innerWire") + innerWire := in + 1.U + out := innerWire + } + @instantiable + class AddTwo extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val definition = Definition(new AddOne) + @public val i0: Instance[AddOne] = Instance(definition) + @public val i1: Instance[AddOne] = Instance(definition) + i0.in := in + i1.in := i0.out + out := i1.out + } + @instantiable + class AddTwoMixedModules extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + val definition = Definition(new AddOne) + @public val i0: Instance[AddOne] = Instance(definition) + @public val i1 = Module(new AddOne) + i0.in := in + i1.in := i0.out + out := i1.out + } + @instantiable + class AggregatePortModule extends Module { + @public val io = IO(new Bundle { + val in = Input(UInt(32.W)) + val out = Output(UInt(32.W)) + }) + io.out := io.in + } + @instantiable + class WireContainer { + @public val innerWire = Wire(UInt(32.W)) + } + @instantiable + class AddOneWithInstantiableWire extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val wireContainer = new WireContainer() + wireContainer.innerWire := in + 1.U + out := wireContainer.innerWire + } + @instantiable + class AddOneContainer { + @public val i0 = Module(new AddOne) + } + @instantiable + class AddOneWithInstantiableModule extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val moduleContainer = new AddOneContainer() + moduleContainer.i0.in := in + out := moduleContainer.i0.out + } + @instantiable + class AddOneInstanceContainer { + val definition = Definition(new AddOne) + @public val i0 = Instance(definition) + } + @instantiable + class AddOneWithInstantiableInstance extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val instanceContainer = new AddOneInstanceContainer() + instanceContainer.i0.in := in + out := instanceContainer.i0.out + } + @instantiable + class AddOneContainerContainer { + @public val container = new AddOneContainer + } + @instantiable + class AddOneWithInstantiableInstantiable extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val containerContainer = new AddOneContainerContainer() + containerContainer.container.i0.in := in + out := containerContainer.container.i0.out + } + @instantiable + class Viewer(val y: AddTwo, markPlease: Boolean) { + @public val x = y + if(markPlease) mark(x.i0.innerWire, "first") + } + @instantiable + class ViewerParent(val x: AddTwo, markHere: Boolean, markThere: Boolean) extends Module { + @public val viewer = new Viewer(x, markThere) + if(markHere) mark(viewer.x.i0.innerWire, "second") + } + @instantiable + class MultiVal() extends Module { + @public val (x, y) = (Wire(UInt(3.W)), Wire(UInt(3.W))) + } + @instantiable + class LazyVal() extends Module { + @public val x = Wire(UInt(3.W)) + @public lazy val y = "Hi" + } + case class Parameters(string: String, int: Int) extends IsLookupable + @instantiable + class UsesParameters(p: Parameters) extends Module { + @public val y = p + @public val x = Wire(UInt(3.W)) + } + @instantiable + class HasList() extends Module { + @public val y = List(1, 2, 3) + @public val x = List.fill(3)(Wire(UInt(3.W))) + } + @instantiable + class HasSeq() extends Module { + @public val y = Seq(1, 2, 3) + @public val x = Seq.fill(3)(Wire(UInt(3.W))) + } + @instantiable + class HasOption() extends Module { + @public val x: Option[UInt] = Some(Wire(UInt(3.W))) + } + @instantiable + class HasVec() extends Module { + @public val x = VecInit(1.U, 2.U, 3.U) + } + @instantiable + class HasIndexedVec() extends Module { + val x = VecInit(1.U, 2.U, 3.U) + @public val y = x(1) + } + @instantiable + class HasSubFieldAccess extends Module { + val in = IO(Input(Valid(UInt(8.W)))) + @public val valid = in.valid + @public val bits = in.bits + } + @instantiable + class HasPublicConstructorArgs(@public val int: Int) extends Module { + @public val x = Wire(UInt(3.W)) + } + @instantiable + class InstantiatesHasVec() extends Module { + @public val i0 = Instance(Definition(new HasVec())) + @public val i1 = Module(new HasVec()) + } + @instantiable + class HasUninferredReset() extends Module { + @public val in = IO(Input(UInt(3.W))) + @public val out = IO(Output(UInt(3.W))) + out := RegNext(in) + } +} diff --git a/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala b/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala new file mode 100644 index 00000000..3866bf87 --- /dev/null +++ b/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala @@ -0,0 +1,709 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests +package experimental.hierarchy + +import chisel3._ +import chisel3.experimental.BaseModule +import chisel3.experimental.hierarchy.{Definition, Instance, instantiable, public} +import chisel3.util.{DecoupledIO, Valid} + + +// TODO/Notes +// - In backport, clock/reset are not automatically assigned. I think this is fixed in 3.5 +// - CircuitTarget for annotations on the definition are wrong - needs to be fixed. +class InstanceSpec extends ChiselFunSpec with Utils { + import Annotations._ + import Examples._ + describe("0: Instance instantiation") { + it("0.0: name of an instance should be correct") { + class Top extends Module { + val definition = Definition(new AddOne) + val i0 = Instance(definition) + } + val (chirrtl, _) = getFirrtlAndAnnos(new Top) + chirrtl.serialize should include ("inst i0 of AddOne") + } + it("0.1: name of an instanceclone should not error") { + class Top extends Module { + val definition = Definition(new AddTwo) + val i0 = Instance(definition) + val i = i0.i0 // This should not error + } + val (chirrtl, _) = getFirrtlAndAnnos(new Top) + chirrtl.serialize should include ("inst i0 of AddTwo") + } + it("0.2: accessing internal fields through non-generated means is hard to do") { + class Top extends Module { + val definition = Definition(new AddOne) + val i0 = Instance(definition) + //i0.lookup(_.in) // Uncommenting this line will give the following error: + //"You are trying to access a macro-only API. Please use the @public annotation instead." + i0.in + } + val (chirrtl, _) = getFirrtlAndAnnos(new Top) + chirrtl.serialize should include ("inst i0 of AddOne") + } + } + describe("1: Annotations on instances in same chisel compilation") { + it("1.0: should work on a single instance, annotating the instance") { + class Top extends Module { + val definition: Definition[AddOne] = Definition(new AddOne) + val i0: Instance[AddOne] = Instance(definition) + mark(i0, "i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOne".it, "i0")) + } + it("1.1: should work on a single instance, annotating an inner wire") { + class Top extends Module { + val definition: Definition[AddOne] = Definition(new AddOne) + val i0: Instance[AddOne] = Instance(definition) + mark(i0.innerWire, "i0.innerWire") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOne>innerWire".rt, "i0.innerWire")) + } + it("1.2: should work on a two nested instances, annotating the instance") { + class Top extends Module { + val definition: Definition[AddTwo] = Definition(new AddTwo) + val i0: Instance[AddTwo] = Instance(definition) + mark(i0.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddTwo/i0:AddOne".it, "i0.i0")) + } + it("1.3: should work on a two nested instances, annotating the inner wire") { + class Top extends Module { + val definition: Definition[AddTwo] = Definition(new AddTwo) + val i0: Instance[AddTwo] = Instance(definition) + mark(i0.i0.innerWire, "i0.i0.innerWire") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddTwo/i0:AddOne>innerWire".rt, "i0.i0.innerWire")) + } + it("1.4: should work on a nested module in an instance, annotating the module") { + class Top extends Module { + val definition: Definition[AddTwoMixedModules] = Definition(new AddTwoMixedModules) + val i0: Instance[AddTwoMixedModules] = Instance(definition) + mark(i0.i1, "i0.i1") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddTwoMixedModules/i1:AddOne_2".it, "i0.i1")) + } + it("1.5: should work on an instantiable container, annotating a wire") { + class Top extends Module { + val definition: Definition[AddOneWithInstantiableWire] = Definition(new AddOneWithInstantiableWire) + val i0: Instance[AddOneWithInstantiableWire] = Instance(definition) + mark(i0.wireContainer.innerWire, "i0.innerWire") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOneWithInstantiableWire>innerWire".rt, "i0.innerWire")) + } + it("1.6: should work on an instantiable container, annotating a module") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableModule) + val i0 = Instance(definition) + mark(i0.moduleContainer.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOneWithInstantiableModule/i0:AddOne".it, "i0.i0")) + } + it("1.7: should work on an instantiable container, annotating an instance") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableInstance) + val i0 = Instance(definition) + mark(i0.instanceContainer.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOneWithInstantiableInstance/i0:AddOne".it, "i0.i0")) + } + it("1.8: should work on an instantiable container, annotating an instantiable container's module") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableInstantiable) + val i0 = Instance(definition) + mark(i0.containerContainer.container.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOneWithInstantiableInstantiable/i0:AddOne".it, "i0.i0")) + } + it("1.9: should work on public member which references public member of another instance") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableInstantiable) + val i0 = Instance(definition) + mark(i0.containerContainer.container.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOneWithInstantiableInstantiable/i0:AddOne".it, "i0.i0")) + } + it("1.10: should work for targets on definition to have correct circuit name"){ + class Top extends Module { + val definition = Definition(new AddOneWithAnnotation) + val i0 = Instance(definition) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|AddOneWithAnnotation>innerWire".rt, "innerWire")) + } + } + describe("2: Annotations on designs not in the same chisel compilation") { + it("2.0: should work on an innerWire, marked in a different compilation") { + val first = elaborateAndGetModule(new AddTwo) + class Top(x: AddTwo) extends Module { + val parent = Instance(Definition(new ViewerParent(x, false, true))) + } + val (_, annos) = getFirrtlAndAnnos(new Top(first)) + annos should contain (MarkAnnotation("~AddTwo|AddTwo/i0:AddOne>innerWire".rt, "first")) + } + it("2.1: should work on an innerWire, marked in a different compilation, in instanced instantiable") { + val first = elaborateAndGetModule(new AddTwo) + class Top(x: AddTwo) extends Module { + val parent = Instance(Definition(new ViewerParent(x, true, false))) + } + val (_, annos) = getFirrtlAndAnnos(new Top(first)) + annos should contain (MarkAnnotation("~AddTwo|AddTwo/i0:AddOne>innerWire".rt, "second")) + } + it("2.2: should work on an innerWire, marked in a different compilation, in instanced module") { + val first = elaborateAndGetModule(new AddTwo) + class Top(x: AddTwo) extends Module { + val parent = Instance(Definition(new ViewerParent(x, false, false))) + mark(parent.viewer.x.i0.innerWire, "third") + } + val (_, annos) = getFirrtlAndAnnos(new Top(first)) + annos should contain (MarkAnnotation("~AddTwo|AddTwo/i0:AddOne>innerWire".rt, "third")) + } + } + describe("3: @public") { + it("3.0: should work on multi-vals") { + class Top() extends Module { + val mv = Instance(Definition(new MultiVal())) + mark(mv.x, "mv.x") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/mv:MultiVal>x".rt, "mv.x")) + } + it("3.1: should work on lazy vals") { + class Top() extends Module { + val lv = Instance(Definition(new LazyVal())) + mark(lv.x, lv.y) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/lv:LazyVal>x".rt, "Hi")) + } + it("3.2: should work on islookupables") { + class Top() extends Module { + val p = Parameters("hi", 0) + val up = Instance(Definition(new UsesParameters(p))) + mark(up.x, up.y.string + up.y.int) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/up:UsesParameters>x".rt, "hi0")) + } + it("3.3: should work on lists") { + class Top() extends Module { + val i = Instance(Definition(new HasList())) + mark(i.x(1), i.y(1).toString) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasList>x_1".rt, "2")) + } + it("3.4: should work on seqs") { + class Top() extends Module { + val i = Instance(Definition(new HasSeq())) + mark(i.x(1), i.y(1).toString) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasSeq>x_1".rt, "2")) + } + it("3.5: should work on options") { + class Top() extends Module { + val i = Instance(Definition(new HasOption())) + i.x.map(x => mark(x, "x")) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasOption>x".rt, "x")) + } + it("3.6: should work on vecs") { + class Top() extends Module { + val i = Instance(Definition(new HasVec())) + mark(i.x, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasVec>x".rt, "blah")) + } + it("3.7: should work on statically indexed vectors external to module") { + class Top() extends Module { + val i = Instance(Definition(new HasVec())) + mark(i.x(1), "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasVec>x[1]".rt, "blah")) + } + it("3.8: should work on statically indexed vectors internal to module") { + class Top() extends Module { + val i = Instance(Definition(new HasIndexedVec())) + mark(i.y, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasIndexedVec>x[1]".rt, "blah")) + } + it("3.9: should work on accessed subfields of aggregate ports") { + class Top extends Module { + val input = IO(Input(Valid(UInt(8.W)))) + val i = Instance(Definition(new HasSubFieldAccess)) + i.valid := input.valid + i.bits := input.bits + mark(i.valid, "valid") + mark(i.bits, "bits") + } + val expected = List( + "~Top|Top/i:HasSubFieldAccess>in.valid".rt -> "valid", + "~Top|Top/i:HasSubFieldAccess>in.bits".rt -> "bits" + ) + val lines = List( + "i.in.valid <= input.valid", + "i.in.bits <= input.bits" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + val text = chirrtl.serialize + for (line <- lines) { + text should include (line) + } + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + ignore("3.10: should work on vals in constructor arguments") { + class Top() extends Module { + val i = Instance(Definition(new HasPublicConstructorArgs(10))) + //mark(i.x, i.int.toString) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasPublicConstructorArgs>x".rt, "10")) + } + } + describe("4: toInstance") { + it("4.0: should work on modules") { + class Top() extends Module { + val i = Module(new AddOne()) + f(i.toInstance) + } + def f(i: Instance[AddOne]): Unit = mark(i.innerWire, "blah") + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOne>innerWire".rt, "blah")) + } + it("4.1: should work on isinstantiables") { + class Top() extends Module { + val i = Module(new AddTwo()) + val v = new Viewer(i, false) + mark(f(v.toInstance), "blah") + } + def f(i: Instance[Viewer]): Data = i.x.i0.innerWire + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + it("4.2: should work on seqs of modules") { + class Top() extends Module { + val is = Seq(Module(new AddTwo()), Module(new AddTwo())).map(_.toInstance) + mark(f(is), "blah") + } + def f(i: Seq[Instance[AddTwo]]): Data = i.head.i0.innerWire + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + it("4.3: should work on seqs of isInstantiables") { + class Top() extends Module { + val i = Module(new AddTwo()) + val vs = Seq(new Viewer(i, false), new Viewer(i, false)).map(_.toInstance) + mark(f(vs), "blah") + } + def f(i: Seq[Instance[Viewer]]): Data = i.head.x.i0.innerWire + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + it("4.2: should work on options of modules") { + class Top() extends Module { + val is: Option[Instance[AddTwo]] = Some(Module(new AddTwo())).map(_.toInstance) + mark(f(is), "blah") + } + def f(i: Option[Instance[AddTwo]]): Data = i.get.i0.innerWire + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + } + describe("5: Absolute Targets should work as expected") { + it("5.0: toAbsoluteTarget on a port of an instance") { + class Top() extends Module { + val i = Instance(Definition(new AddTwo())) + amark(i.in, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:AddTwo>in".rt, "blah")) + } + it("5.1: toAbsoluteTarget on a subinstance's data within an instance") { + class Top() extends Module { + val i = Instance(Definition(new AddTwo())) + amark(i.i0.innerWire, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + it("5.2: toAbsoluteTarget on a submodule's data within an instance") { + class Top() extends Module { + val i = Instance(Definition(new AddTwoMixedModules())) + amark(i.i1.in, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:AddTwoMixedModules/i1:AddOne_2>in".rt, "blah")) + } + it("5.3: toAbsoluteTarget on a submodule's data, in an aggregate, within an instance") { + class Top() extends Module { + val i = Instance(Definition(new InstantiatesHasVec())) + amark(i.i1.x.head, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:InstantiatesHasVec/i1:HasVec_2>x[0]".rt, "blah")) + } + it("5.4: toAbsoluteTarget on a submodule's data, in an aggregate, within an instance, ILit") { + class MyBundle extends Bundle { val x = UInt(3.W) } + @instantiable + class HasVec() extends Module { + @public val x = Wire(Vec(3, new MyBundle())) + } + @instantiable + class InstantiatesHasVec() extends Module { + @public val i0 = Instance(Definition(new HasVec())) + @public val i1 = Module(new HasVec()) + } + class Top() extends Module { + val i = Instance(Definition(new InstantiatesHasVec())) + amark(i.i1.x.head.x, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:InstantiatesHasVec/i1:HasVec_2>x[0].x".rt, "blah")) + } + it("5.5: toAbsoluteTarget on a subinstance") { + class Top() extends Module { + val i = Instance(Definition(new AddTwo())) + amark(i.i1, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:AddTwo/i1:AddOne".it, "blah")) + } + it("5.6: should work for absolute targets on definition to have correct circuit name"){ + class Top extends Module { + val definition = Definition(new AddOneWithAbsoluteAnnotation) + val i0 = Instance(definition) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithAbsoluteAnnotation>innerWire".rt, "innerWire")) + } + } + describe("6: @instantiable traits should work as expected") { + class MyBundle extends Bundle { + val in = Input(UInt(8.W)) + val out = Output(UInt(8.W)) + } + @instantiable + trait ModuleIntf extends BaseModule { + @public val io = IO(new MyBundle) + } + @instantiable + class ModuleWithCommonIntf(suffix: String = "") extends Module with ModuleIntf { + override def desiredName: String = super.desiredName + suffix + @public val sum = io.in + 1.U + + io.out := sum + } + class BlackBoxWithCommonIntf extends BlackBox with ModuleIntf + + it("6.0: A Module that implements an @instantiable trait should be instantiable as that trait") { + class Top extends Module { + val i: Instance[ModuleIntf] = Instance(Definition(new ModuleWithCommonIntf)) + mark(i.io.in, "gotcha") + mark(i, "inst") + } + val expected = List( + "~Top|Top/i:ModuleWithCommonIntf>io.in".rt -> "gotcha", + "~Top|Top/i:ModuleWithCommonIntf".it -> "inst" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("6.1 An @instantiable Module that implements an @instantiable trait should be able to use extension methods from both") { + class Top extends Module { + val i: Instance[ModuleWithCommonIntf] = Instance(Definition(new ModuleWithCommonIntf)) + mark(i.io.in, "gotcha") + mark(i.sum, "also this") + mark(i, "inst") + } + val expected = List( + "~Top|Top/i:ModuleWithCommonIntf>io.in".rt -> "gotcha", + "~Top|Top/i:ModuleWithCommonIntf>sum".rt -> "also this", + "~Top|Top/i:ModuleWithCommonIntf".it -> "inst" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("6.2 A BlackBox that implements an @instantiable trait should be instantiable as that trait") { + class Top extends Module { + val i: Instance[ModuleIntf] = Module(new BlackBoxWithCommonIntf).toInstance + mark(i.io.in, "gotcha") + mark(i, "module") + } + val expected = List( + "~Top|BlackBoxWithCommonIntf>in".rt -> "gotcha", + "~Top|BlackBoxWithCommonIntf".mt -> "module" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("6.3 It should be possible to have Vectors of @instantiable traits mixing concrete subclasses") { + class Top extends Module { + val proto = Definition(new ModuleWithCommonIntf("X")) + val insts: Seq[Instance[ModuleIntf]] = Vector( + Module(new ModuleWithCommonIntf("Y")).toInstance, + Module(new BlackBoxWithCommonIntf).toInstance, + Instance(proto) + ) + mark(insts(0).io.in, "foo") + mark(insts(1).io.in, "bar") + mark(insts(2).io.in, "fizz") + } + val expected = List( + "~Top|ModuleWithCommonIntfY>io.in".rt -> "foo", + "~Top|BlackBoxWithCommonIntf>in".rt -> "bar", + "~Top|Top/insts_2:ModuleWithCommonIntfX>io.in".rt -> "fizz" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + } + // TODO don't forget to test this with heterogeneous Views (eg. viewing a tuple of a port and non-port as a single Bundle) + describe("7: @instantiable and @public should compose with DataView") { + import chisel3.experimental.dataview._ + it("7.0: should work on simple Views") { + @instantiable + class MyModule extends RawModule { + val in = IO(Input(UInt(8.W))) + @public val out = IO(Output(UInt(8.W))) + val sum = in + 1.U + out := sum + 1.U + @public val foo = in.viewAs[UInt] + @public val bar = sum.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 MyModule)) + i.foo := foo + bar := i.out + mark(i.out, "out") + mark(i.foo, "foo") + mark(i.bar, "bar") + } + val expectedAnnos = List( + "~Top|Top/i:MyModule>out".rt -> "out", + "~Top|Top/i:MyModule>in".rt -> "foo", + "~Top|Top/i:MyModule>sum".rt -> "bar" + ) + 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) + } + } + + ignore("7.1: should work on Aggregate Views") { + import chiselTests.experimental.FlatDecoupledDataView._ + type RegDecoupled = DecoupledIO[FizzBuzz] + @instantiable + class MyModule extends RawModule { + private val a = IO(Flipped(new FlatDecoupled)) + private val b = IO(new FlatDecoupled) + @public val enq = a.viewAs[RegDecoupled] + @public val deq = b.viewAs[RegDecoupled] + @public val enq_valid = enq.valid // Also return a subset of the view + deq <> enq + } + class Top extends RawModule { + val foo = IO(Flipped(new RegDecoupled(new FizzBuzz))) + val bar = IO(new RegDecoupled(new FizzBuzz)) + val i = Instance(Definition(new MyModule)) + i.enq <> foo + i.enq_valid := foo.valid // Make sure connections also work for @public on elements of a larger Aggregate + i.deq.ready := bar.ready + bar.valid := i.deq.valid + bar.bits := i.deq.bits + mark(i.enq, "enq") + mark(i.enq.bits, "enq.bits") + mark(i.deq.bits.fizz, "deq.bits.fizz") + mark(i.enq_valid, "enq_valid") + } + val expectedAnnos = List( + "~Top|Top/i:MyModule>a".rt -> "enq", // Not split, checks 1:1 + "~Top|Top/i:MyModule>a.fizz".rt -> "enq.bits", // Split, checks non-1:1 inner Aggregate + "~Top|Top/i:MyModule>a.buzz".rt -> "enq.bits", + "~Top|Top/i:MyModule>b.fizz".rt -> "deq.bits.fizz", // Checks 1 inner Element + "~Top|Top/i:MyModule>a.valid".rt -> "enq_valid" + ) + val expectedLines = List( + "i.a.valid <= foo.valid", + "foo.ready <= i.a.ready", + "i.a.fizz <= foo.bits.fizz", + "i.a.buzz <= foo.bits.buzz", + "bar.valid <= i.b.valid", + "i.b.ready <= bar.ready", + "bar.bits.fizz <= i.b.fizz", + "bar.bits.buzz <= i.b.buzz", + ) + 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) + } + } + + it("7.2: should work on views of views") { + import chiselTests.experimental.SimpleBundleDataView._ + @instantiable + class MyModule extends RawModule { + private val a = IO(Input(UInt(8.W))) + private val b = IO(Output(new BundleA(8))) + @public val in = a.viewAs[UInt].viewAs[UInt] + @public val out = b.viewAs[BundleB].viewAs[BundleA].viewAs[BundleB] + out.bar := in + } + class Top extends RawModule { + val foo = IO(Input(UInt(8.W))) + val bar = IO(Output(new BundleB(8))) + val i = Instance(Definition(new MyModule)) + i.in := foo + bar := i.out + bar.bar := i.out.bar + mark(i.in, "in") + mark(i.out.bar, "out_bar") + } + val expected = List( + "~Top|Top/i:MyModule>a".rt -> "in", + "~Top|Top/i:MyModule>b.foo".rt -> "out_bar", + ) + val lines = List( + "i.a <= foo", + "bar.bar <= i.b.foo" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + val text = chirrtl.serialize + for (line <- lines) { + text should include (line) + } + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + + it("7.3: should work with DataView + implicit conversion") { + import chiselTests.experimental.SeqToVec._ + @instantiable + class MyModule extends RawModule { + private val a = IO(Input(UInt(8.W))) + private val b = IO(Output(UInt(8.W))) + @public val ports = Seq(a, b) + b := a + } + class Top extends RawModule { + val foo = IO(Input(UInt(8.W))) + val bar = IO(Output(UInt(8.W))) + val i = Instance(Definition(new MyModule)) + i.ports <> Seq(foo, bar) + mark(i.ports, "i.ports") + } + val expected = List( + // Not 1:1 so will get split out + "~Top|Top/i:MyModule>a".rt -> "i.ports", + "~Top|Top/i:MyModule>b".rt -> "i.ports", + ) + val lines = List( + "i.a <= foo", + "bar <= i.b" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + val text = chirrtl.serialize + for (line <- lines) { + text should include (line) + } + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + } + + describe("8: @instantiable and @public should compose with CloneModuleAsRecord") { + it("8.0: it should support @public on a CMAR Record in Definitions") { + @instantiable + class HasCMAR extends Module { + @public val in = IO(Input(UInt(8.W))) + @public val out = IO(Output(UInt(8.W))) + @public val m = Module(new AggregatePortModule) + @public val c = experimental.CloneModuleAsRecord(m) + } + class Top extends Module { + val d = Definition(new HasCMAR) + mark(d.c("io"), "c.io") + val bun = d.c("io").asInstanceOf[Record] + mark(bun.elements("out"), "c.io.out") + } + val expected = List( + "~Top|HasCMAR/c:AggregatePortModule>io".rt -> "c.io", + "~Top|HasCMAR/c:AggregatePortModule>io.out".rt -> "c.io.out" + + ) + val (_, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("8.1: it should support @public on a CMAR Record in Instances") { + @instantiable + class HasCMAR extends Module { + @public val in = IO(Input(UInt(8.W))) + @public val out = IO(Output(UInt(8.W))) + @public val m = Module(new AggregatePortModule) + @public val c = experimental.CloneModuleAsRecord(m) + } + class Top extends Module { + val i = Instance(Definition(new HasCMAR)) + mark(i.c("io"), "i.c.io") + val bun = i.c("io").asInstanceOf[Record] + mark(bun.elements("out"), "i.c.io.out") + } + val expected = List( + "~Top|Top/i:HasCMAR/c:AggregatePortModule>io".rt -> "i.c.io", + "~Top|Top/i:HasCMAR/c:AggregatePortModule>io.out".rt -> "i.c.io.out" + + ) + val (_, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + } +} + diff --git a/src/test/scala/chiselTests/experimental/hierarchy/Utils.scala b/src/test/scala/chiselTests/experimental/hierarchy/Utils.scala new file mode 100644 index 00000000..a2e51765 --- /dev/null +++ b/src/test/scala/chiselTests/experimental/hierarchy/Utils.scala @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.experimental.hierarchy + +import chisel3._ +import _root_.firrtl.annotations._ +import chisel3.stage.{ChiselCircuitAnnotation, CircuitSerializationAnnotation, DesignAnnotation} +import chiselTests.ChiselRunners +import firrtl.stage.FirrtlCircuitAnnotation +import org.scalatest.matchers.should.Matchers + +trait Utils extends ChiselRunners with chiselTests.Utils with Matchers { + import Annotations._ + // TODO promote to standard API (in FIRRTL) and perhaps even implement with a macro + implicit class Str2RefTarget(str: String) { + def rt: ReferenceTarget = Target.deserialize(str).asInstanceOf[ReferenceTarget] + def it: InstanceTarget = Target.deserialize(str).asInstanceOf[InstanceTarget] + def mt: ModuleTarget = Target.deserialize(str).asInstanceOf[ModuleTarget] + def ct: CircuitTarget = Target.deserialize(str).asInstanceOf[CircuitTarget] + } +} -- cgit v1.2.3 From 7cd821f5a975ff97694d39893af1d89952d37c69 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Mon, 6 Sep 2021 01:49:09 +0200 Subject: Update sbt-mdoc to 2.2.23 (#2091) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index b534ec90..25e4d0c1 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -16,7 +16,7 @@ addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.15") addSbtPlugin("com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "3.0.0") -addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.22" ) +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.23" ) addSbtPlugin("com.eed3si9n" % "sbt-sriracha" % "0.1.0") -- cgit v1.2.3 From 89679a3c7b42f34d3b9e93dfb6972bc36b6af297 Mon Sep 17 00:00:00 2001 From: Boyang Han Date: Mon, 6 Sep 2021 07:35:46 -0700 Subject: Add a test case to demonstrate the bug found in #2112 --- .../scala/chiselTests/util/experimental/PlaSpec.scala | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/test/scala/chiselTests/util/experimental/PlaSpec.scala b/src/test/scala/chiselTests/util/experimental/PlaSpec.scala index 45ac012e..ced4f9dd 100644 --- a/src/test/scala/chiselTests/util/experimental/PlaSpec.scala +++ b/src/test/scala/chiselTests/util/experimental/PlaSpec.scala @@ -49,6 +49,22 @@ class PlaSpec extends ChiselFlatSpec { }) } + "#2112" should "be generated correctly" in { + assertTesterPasses(new BasicTester { + val table = Seq( + (BitPat("b0"), BitPat("b?0")), + (BitPat("b1"), BitPat("b?1")), + (BitPat("b?"), BitPat("b1?")), + ) + table.foreach { case (i, o) => + val (plaIn, plaOut) = pla(table) + plaIn := WireDefault(i.value.U(3.W)) + chisel3.assert(plaOut === o.value.U(8.W), "Input " + i.toString + " produced incorrect output BitPat(%b)", plaOut) + } + stop() + }) + } + "A simple PLA" should "be generated correctly" in { assertTesterPasses(new BasicTester { val table = Seq( -- cgit v1.2.3 From 0ca4ce23400d3624f8f617aa8ae21880d8430c2e Mon Sep 17 00:00:00 2001 From: Boyang Han Date: Mon, 6 Sep 2021 21:27:51 -0700 Subject: Test case rework --- src/test/scala/chiselTests/util/experimental/PlaSpec.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/test/scala/chiselTests/util/experimental/PlaSpec.scala b/src/test/scala/chiselTests/util/experimental/PlaSpec.scala index ced4f9dd..8af5c936 100644 --- a/src/test/scala/chiselTests/util/experimental/PlaSpec.scala +++ b/src/test/scala/chiselTests/util/experimental/PlaSpec.scala @@ -52,14 +52,13 @@ class PlaSpec extends ChiselFlatSpec { "#2112" should "be generated correctly" in { assertTesterPasses(new BasicTester { val table = Seq( - (BitPat("b0"), BitPat("b?0")), - (BitPat("b1"), BitPat("b?1")), - (BitPat("b?"), BitPat("b1?")), + (BitPat("b000"), BitPat("b?01")), + (BitPat("b111"), BitPat("b?01")), ) table.foreach { case (i, o) => val (plaIn, plaOut) = pla(table) plaIn := WireDefault(i.value.U(3.W)) - chisel3.assert(plaOut === o.value.U(8.W), "Input " + i.toString + " produced incorrect output BitPat(%b)", plaOut) + chisel3.assert(o === plaOut, "Input " + i.toString + " produced incorrect output BitPat(%b)", plaOut) } stop() }) -- cgit v1.2.3 From 189da9828b1990e360fe521598dc340a260d05d5 Mon Sep 17 00:00:00 2001 From: Boyang Han Date: Mon, 6 Sep 2021 21:07:47 -0700 Subject: Fix #2112, handle `?->1` case --- src/main/scala/chisel3/util/pla.scala | 39 +++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/main/scala/chisel3/util/pla.scala b/src/main/scala/chisel3/util/pla.scala index bb2b7996..c57ca962 100644 --- a/src/main/scala/chisel3/util/pla.scala +++ b/src/main/scala/chisel3/util/pla.scala @@ -17,6 +17,20 @@ object pla { * a `1` means this product term makes the function value to `1` * and a `0` or `?` means this product term make the function value to `0` * + * @note There is one special case which we call it `? -> 1`. In this scenario, for some of the output functions (bits), + * all input terms that make this function value to `1` is purely composed by `?`. In a real pla, this will result in + * no connection to the gates in the AND Plane (verilog `z` on gate inputs), which in turn makes the outputs of the + * AND Plane undetermined (verilog `x` on outputs). This is not desired behavior in most cases, for example the + * minimization result of following truth table: + * 0 -> 1 + * 1 -> 1 + * which is: + * ? -> 1 + * actually means something other than a verilog `x`. To ease the generation of minimized truth tables, this pla + * generation api will hard wire outputs to a `1` on this special case. + * This behavior is formally described as: if product terms that make one function value to `1` is solely consisted + * of don't-cares (`?`s), then this function is implemented as a constant `1`. + * * @param table A [[Seq]] of inputs -> outputs mapping * @param invert A [[BitPat]] specify which bit of the output should be inverted. `1` means the correspond position * of the output should be inverted in the PLA, a `0` or a `?` means direct output from the OR matrix. @@ -68,20 +82,19 @@ object pla { // the AND matrix // use `term -> AND line` map to reuse AND matrix output lines val andMatrixOutputs: Map[String, Bool] = inputTerms.map { t => - t.toString -> Cat( - Seq - .tabulate(numberOfInputs) { i => - if (t.mask.testBit(i)) { - Some( - if (t.value.testBit(i)) inputs(i) - else invInputs(i) - ) - } else { - None - } + val andMatrixInput = Seq + .tabulate(numberOfInputs) { i => + if (t.mask.testBit(i)) { + Some( + if (t.value.testBit(i)) inputs(i) + else invInputs(i) + ) + } else { + None } - .flatten - ).andR() + } + .flatten + if (andMatrixInput.nonEmpty) t.toString -> Cat(andMatrixInput).andR() else t.toString -> true.B }.toMap // the OR matrix -- cgit v1.2.3 From ff1bcb09d9bd416fcba68496b7cef1d0e454dfd5 Mon Sep 17 00:00:00 2001 From: Martin Schoeberl Date: Wed, 8 Sep 2021 18:52:23 +0200 Subject: Update sbt installation instructions (#2115) * Update sbt installation instructions The bintray reference is dead. * fix grammar--- SETUP.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/SETUP.md b/SETUP.md index 5f5e6f4f..27e07960 100644 --- a/SETUP.md +++ b/SETUP.md @@ -13,14 +13,7 @@ Note that both [PeekPokeTester](https://github.com/freechipsproject/chisel-teste sudo apt-get install default-jdk ``` -1. [Install sbt](http://www.scala-sbt.org/release/docs/Installing-sbt-on-Linux.html), - which isn't available by default in the system package manager: - ``` - echo "deb https://dl.bintray.com/sbt/debian /" | sudo tee -a /etc/apt/sources.list.d/sbt.list - sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 642AC823 - sudo apt-get update - sudo apt-get install sbt - ``` +1. Install sbt according to the instructions from [sbt download](https://www.scala-sbt.org/download.html). 1. Install Verilator. We currently recommend Verilator version 4.016. -- cgit v1.2.3 From 2b51053fe7744d6860416c7de8bcb99d4aa9e532 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Wed, 8 Sep 2021 14:31:10 -0700 Subject: Restore return type of BaseModule.toTarget to ModuleTarget (#2117) Definition/Instance introduced the need for representing the targets of instances as InstanceTargets. This original implementation changed the return type of BaseModule.toTarget to express this need. This is a backwards incompatible change that is actually unnecessary because it is impossible for users to get references to the internal InstanceClone objects, instead only accessing such modules via Instance[_] wrappers and cloned Data. We restored the old API by adding a new internal method "getTarget" which will give the correct targets for InstanceClones while maintaining the API of BaseModule.toTarget.--- core/src/main/scala/chisel3/Module.scala | 36 ++++++++++++++-------- .../experimental/hierarchy/Definition.scala | 7 +++-- .../chisel3/experimental/hierarchy/Instance.scala | 12 ++++---- core/src/main/scala/chisel3/internal/Builder.scala | 2 +- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala index d0171693..3ae48821 100644 --- a/core/src/main/scala/chisel3/Module.scala +++ b/core/src/main/scala/chisel3/Module.scala @@ -469,17 +469,29 @@ package experimental { * * @note Should not be called until circuit elaboration is complete */ - final def toTarget: IsModule = { - this match { - case m: internal.BaseModule.InstanceClone[_] if m._parent.nonEmpty => m._parent.get.toTarget.instOf(instanceName, name) - case m: internal.BaseModule.InstanceClone[_] => ModuleTarget(this.circuitName, this.name) - case m: internal.BaseModule.ModuleClone[_] if m._madeFromDefinition => m._parent.get.toTarget.instOf(instanceName, name) - case m: internal.BaseModule.ModuleClone[_] => ModuleTarget(this.circuitName, this.name) - // Without this, we get the wrong CircuitName for the Definition - case m: internal.BaseModule.DefinitionClone[_] if m._circuit.nonEmpty => ModuleTarget(this._circuit.get.circuitName, this.name) - case m: internal.BaseModule.DefinitionClone[_] => ModuleTarget(this.circuitName, this.name) - case m => ModuleTarget(this.circuitName, this.name) - } + final def toTarget: ModuleTarget = this match { + case m: internal.BaseModule.InstanceClone[_] => throwException(s"Internal Error! It's not legal to call .toTarget on an InstanceClone. $m") + case m: internal.BaseModule.DefinitionClone[_] => throwException(s"Internal Error! It's not legal to call .toTarget on an DefinitionClone. $m") + case _ => ModuleTarget(this.circuitName, this.name) + } + + /** Returns the real target of a Module which may be an [[InstanceTarget]] + * + * BaseModule.toTarget returns a ModuleTarget because the classic Module(new MyModule) API elaborates + * Modules in a way that there is a 1:1 relationship between instances and elaborated definitions + * + * Instance/Definition introduced special internal modules [[InstanceClone]] and [[ModuleClone]] that + * do not have this 1:1 relationship so need the ability to return [[InstanceTarget]]s. + * Because users can never actually get references to these underlying objects, we can maintain + * BaseModule.toTarget's API returning [[ModuleTarget]] while providing an internal API for getting + * the correct [[InstanceTarget]]s whenever using the Definition/Instance API. + */ + private[chisel3] def getTarget: IsModule = this match { + case m: internal.BaseModule.InstanceClone[_] if m._parent.nonEmpty => m._parent.get.getTarget.instOf(instanceName, name) + case m: internal.BaseModule.ModuleClone[_] if m._madeFromDefinition => m._parent.get.getTarget.instOf(instanceName, name) + // Without this, we get the wrong CircuitName for the Definition + case m: internal.BaseModule.DefinitionClone[_] if m._circuit.nonEmpty => ModuleTarget(this._circuit.get.circuitName, this.name) + case _ => this.toTarget } /** Returns a FIRRTL ModuleTarget that references this object @@ -496,7 +508,7 @@ package experimental { // is always unambigous. However, legacy workarounds for Chisel's lack of an instance API // have lead some to use .toAbsoluteTarget as a workaround. A proper instance API will make // it possible to deprecate and remove .toAbsoluteTarget - if (this == ViewParent) ViewParent.absoluteTarget else toTarget + if (this == ViewParent) ViewParent.absoluteTarget else getTarget } } diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala index 2e917dfa..0cc3d131 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala @@ -3,13 +3,14 @@ package chisel3.experimental.hierarchy import scala.language.experimental.macros - import chisel3._ + import scala.collection.mutable.HashMap import chisel3.internal.{Builder, DynamicContext} import chisel3.internal.sourceinfo.{DefinitionTransform, DefinitionWrapTransform, SourceInfo} import chisel3.experimental.BaseModule import chisel3.internal.BaseModule.IsClone +import firrtl.annotations.{IsModule, ModuleTarget} /** User-facing Definition type. * Represents a definition of an object of type [[A]] which are marked as @instantiable @@ -64,12 +65,12 @@ object Definition extends SourceInfoDoc { /** If this is an instance of a Module, returns the toTarget of this instance * @return target of this instance */ - def toTarget = d.proto.toTarget + def toTarget: ModuleTarget = d.proto.toTarget /** If this is an instance of a Module, returns the toAbsoluteTarget of this instance * @return absoluteTarget of this instance */ - def toAbsoluteTarget = d.proto.toAbsoluteTarget + def toAbsoluteTarget: IsModule = d.proto.toAbsoluteTarget } /** A construction method to build a Definition of a Module * diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala index aec42da3..9b17bfce 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala @@ -4,11 +4,11 @@ package chisel3.experimental.hierarchy import scala.collection.mutable.{ArrayBuffer, HashMap} import scala.language.experimental.macros - import chisel3._ -import chisel3.internal.BaseModule.{ModuleClone, IsClone, InstantiableClone} +import chisel3.internal.BaseModule.{InstantiableClone, IsClone, ModuleClone} import chisel3.internal.sourceinfo.{InstanceTransform, SourceInfo} import chisel3.experimental.BaseModule +import firrtl.annotations.IsModule /** User-facing Instance type. * Represents a unique instance of type [[A]] which are marked as @instantiable @@ -75,15 +75,15 @@ object Instance extends SourceInfoDoc { /** If this is an instance of a Module, returns the toTarget of this instance * @return target of this instance */ - def toTarget = i.cloned match { - case Left(x: BaseModule) => x.toTarget - case Right(x: IsClone[_] with BaseModule) => x.toTarget + def toTarget: IsModule = i.cloned match { + case Left(x: BaseModule) => x.getTarget + case Right(x: IsClone[_] with BaseModule) => x.getTarget } /** If this is an instance of a Module, returns the toAbsoluteTarget of this instance * @return absoluteTarget of this instance */ - def toAbsoluteTarget = i.cloned match { + def toAbsoluteTarget: IsModule = i.cloned match { case Left(x) => x.toAbsoluteTarget case Right(x: IsClone[_] with BaseModule) => x.toAbsoluteTarget } diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index 1d15247d..441abc92 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -309,7 +309,7 @@ private[chisel3] trait NamedComponent extends HasId { val root = _parent.map { case ViewParent => reifyParent case other => other - }.get.toTarget // All NamedComponents will have a parent, only the top module can have None here + }.get.getTarget // All NamedComponents will have a parent, only the top module can have None here Target.toTargetTokens(name).toList match { case TargetToken.Ref(r) :: components => root.ref(r).copy(component = components) case other => -- cgit v1.2.3 From e27de290cd649581957958ab212be8a115a29177 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Mon, 13 Sep 2021 20:42:56 +0200 Subject: Update sbt-mima-plugin to 1.0.0 (#2086) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 25e4d0c1..e97d436f 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -20,7 +20,7 @@ addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.23" ) addSbtPlugin("com.eed3si9n" % "sbt-sriracha" % "0.1.0") -addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.9.2") +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.0") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") -- cgit v1.2.3 From c66f0ed24366f8ce0e20a5a420589b8ae01aba77 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Tue, 14 Sep 2021 00:50:37 +0200 Subject: Update scala-compiler, scala-library, ... to 2.12.14 (#1950) --- .github/workflows/test.yml | 4 ++-- build.sbt | 2 +- build.sc | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a2240ff8..c6ca69fd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: matrix: system: ["ubuntu-20.04"] jvm: ["adopt@1.8"] - scala: ["2.13.6", "2.12.13"] + scala: ["2.13.6", "2.12.14"] verilator: ["4.204"] z3: ["4.8.10"] espresso: ["2.4"] @@ -106,7 +106,7 @@ jobs: - name: Cache Scala uses: coursier/cache-action@v5 - name: Documentation (Scala 2.12 only) - if: matrix.scala == '2.12.13' + if: matrix.scala == '2.12.14' run: sbt ++${{ matrix.scala }} docs/mdoc - name: Test run: sbt ++${{ matrix.scala }} test noPluginTests/test diff --git a/build.sbt b/build.sbt index 54bf87f0..4084eecd 100644 --- a/build.sbt +++ b/build.sbt @@ -17,7 +17,7 @@ lazy val commonSettings = Seq ( version := "3.5-SNAPSHOT", autoAPIMappings := true, scalaVersion := "2.13.6", - crossScalaVersions := Seq("2.13.6", "2.12.13"), + crossScalaVersions := Seq("2.13.6", "2.12.14"), scalacOptions := Seq("-deprecation", "-feature"), libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value, // Macros paradise is integrated into 2.13 but requires a scalacOption diff --git a/build.sc b/build.sc index 19ddc7ed..b83f4bdd 100644 --- a/build.sc +++ b/build.sc @@ -5,7 +5,7 @@ import coursier.maven.MavenRepository import $ivy.`com.lihaoyi::mill-contrib-buildinfo:$MILL_VERSION` import mill.contrib.buildinfo.BuildInfo -object chisel3 extends mill.Cross[chisel3CrossModule]("2.13.6", "2.12.13") +object chisel3 extends mill.Cross[chisel3CrossModule]("2.13.6", "2.12.14") // The following stanza is searched for and used when preparing releases. // Please retain it. -- cgit v1.2.3 From 1ae18008e18dfe20983a6ba3b2b950f66245e69b Mon Sep 17 00:00:00 2001 From: Adam Izraelevitz Date: Wed, 15 Sep 2021 11:49:29 -0700 Subject: Fix higher-kinded types for autoclonetype (#2121) --- .../scala/chisel3/internal/plugin/BundleComponent.scala | 6 ++++-- src/test/scala/chiselTests/AutoClonetypeSpec.scala | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala b/plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala index 96851e95..5fe63991 100644 --- a/plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala +++ b/plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala @@ -108,8 +108,10 @@ private[plugin] class BundleComponent(val global: Global, arguments: ChiselPlugi if (isData(vp.symbol)) cloneTypeFull(select) else select }) - val ttpe = Ident(bundle.symbol) - val neww = localTyper.typed(New(ttpe, conArgs)) + val tparamList = bundle.tparams.map{ t => Ident(t.symbol) } + val ttpe = if(tparamList.nonEmpty) AppliedTypeTree(Ident(bundle.symbol), tparamList) else Ident(bundle.symbol) + val newUntyped = New(ttpe, conArgs) + val neww = localTyper.typed(newUntyped) // Create the symbol for the method and have it be associated with the Bundle class val cloneTypeSym = bundle.symbol.newMethod(TermName("_cloneTypeImpl"), bundle.symbol.pos.focus, Flag.OVERRIDE | Flag.PROTECTED) diff --git a/src/test/scala/chiselTests/AutoClonetypeSpec.scala b/src/test/scala/chiselTests/AutoClonetypeSpec.scala index 57e00e99..fcbc4785 100644 --- a/src/test/scala/chiselTests/AutoClonetypeSpec.scala +++ b/src/test/scala/chiselTests/AutoClonetypeSpec.scala @@ -348,5 +348,18 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { elaborate(new MyModule(UInt(8.W))) } + it should "work for higher-kinded types" in { + class DataGen[T <: Data](gen: T) { + def newType: T = gen.cloneType + } + class MyBundle[A <: Data, B <: DataGen[A]](gen: B) extends Bundle { + val foo = gen.newType + } + class MyModule extends MultiIOModule { + val io = IO(Output(new MyBundle[UInt, DataGen[UInt]](new DataGen(UInt(3.W))))) + io.foo := 0.U + } + elaborate(new MyModule) + } } } -- cgit v1.2.3 From 6aca90241550e57f7752d2bdb572e3c47c40e565 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Thu, 16 Sep 2021 13:20:19 +0200 Subject: Update sbt-scoverage to 1.9.0 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index e97d436f..f81a7854 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -4,7 +4,7 @@ resolvers += Classpaths.sbtPluginReleases resolvers += "jgit-repo" at "https://download.eclipse.org/jgit/maven" -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.2") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.0") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.0") -- cgit v1.2.3 From a692b6d230189191defff45c85bc15ce705c471c Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Fri, 17 Sep 2021 12:18:11 -0700 Subject: Just install Z3 from apt-get in CI (#2131) Also install Espresso from pre-built binary--- .github/workflows/test.yml | 55 +++++++--------------------------------------- 1 file changed, 8 insertions(+), 47 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c6ca69fd..48300189 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,6 @@ jobs: jvm: ["adopt@1.8"] scala: ["2.13.6", "2.12.14"] verilator: ["4.204"] - z3: ["4.8.10"] espresso: ["2.4"] runs-on: ${{ matrix.system }} @@ -26,33 +25,10 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Install Z3 Build Dependencies(Ubuntu) + - name: Install Z3 if: matrix.system == 'ubuntu-20.04' - run: sudo apt-get install -y libfl2 libfl-dev ninja-build - - - name: Cache Z3 ${{ matrix.z3 }} - uses: actions/cache@v2.1.6 - id: cache-z3 - with: - path: z3-z3-${{ matrix.z3 }} - key: ${{ matrix.system }}-z3-ninja-${{ matrix.z3 }} - - name: Compile Z3 - if: steps.cache-z3.outputs.cache-hit != 'true' run: | - wget https://github.com/Z3Prover/z3/archive/refs/tags/z3-${{ matrix.z3 }}.tar.gz - tar xvf z3-${{ matrix.z3 }}.tar.gz - cd z3-z3-${{ matrix.z3 }} - mkdir -p build - cd build - cmake .. \ - -GNinja \ - -DCMAKE_BUILD_TYPE=Release \ - -DZ3_LINK_TIME_OPTIMIZATION=1 - ninja - - name: Install Z3 ${{ matrix.z3 }} - run: | - cd z3-z3-${{ matrix.z3 }}/build - sudo ninja install + sudo apt-get install -y z3 z3 --version - name: Cache Verilator ${{ matrix.verilator }} @@ -76,28 +52,13 @@ jobs: sudo make install verilator --version - - name: Cache Espresso ${{ matrix.espresso }} - uses: actions/cache@v2.1.6 - id: cache-espresso - with: - path: espresso-${{ matrix.espresso }} - key: ${{ matrix.system }}-espresso-ninja-${{ matrix.espresso }} - - name: Compile Espresso ${{ matrix.espresso }} - if: steps.cache-espresso.outputs.cache-hit != 'true' - run: | - wget https://github.com/chipsalliance/espresso/archive/refs/tags/v${{ matrix.espresso }}.tar.gz - tar xvf v${{ matrix.espresso }}.tar.gz - cd espresso-${{ matrix.espresso }} - mkdir -p build - cd build - cmake .. \ - -GNinja \ - -DCMAKE_BUILD_TYPE=Release - ninja - - name: Install Espresso ${{ matrix.espresso }} + - name: Install Espresso run: | - cd espresso-${{ matrix.espresso }}/build - sudo ninja install + cd /tmp + wget https://github.com/chipsalliance/espresso/releases/download/v${{ matrix.espresso }}/x86_64-linux-gnu-espresso + chmod +x x86_64-linux-gnu-espresso + sudo mv x86_64-linux-gnu-espresso /usr/local/bin/espresso + espresso || true - name: Setup Scala uses: olafurpg/setup-scala@v10 -- cgit v1.2.3 From 958904cb2f2f65d02b2ab3ec6d9ec2e06d04e482 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Fri, 17 Sep 2021 13:15:34 -0700 Subject: Update Scala and publish plugin for more minor versions (#2130) --- .github/workflows/test.yml | 4 ++-- build.sbt | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 48300189..abce6d13 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: matrix: system: ["ubuntu-20.04"] jvm: ["adopt@1.8"] - scala: ["2.13.6", "2.12.14"] + scala: ["2.13.6", "2.12.15"] verilator: ["4.204"] espresso: ["2.4"] runs-on: ${{ matrix.system }} @@ -67,7 +67,7 @@ jobs: - name: Cache Scala uses: coursier/cache-action@v5 - name: Documentation (Scala 2.12 only) - if: matrix.scala == '2.12.14' + if: startsWith(matrix.scala, '2.12') run: sbt ++${{ matrix.scala }} docs/mdoc - name: Test run: sbt ++${{ matrix.scala }} test noPluginTests/test diff --git a/build.sbt b/build.sbt index 4084eecd..ec4cda13 100644 --- a/build.sbt +++ b/build.sbt @@ -17,7 +17,7 @@ lazy val commonSettings = Seq ( version := "3.5-SNAPSHOT", autoAPIMappings := true, scalaVersion := "2.13.6", - crossScalaVersions := Seq("2.13.6", "2.12.14"), + crossScalaVersions := Seq("2.13.6", "2.12.15"), scalacOptions := Seq("-deprecation", "-feature"), libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value, // Macros paradise is integrated into 2.13 but requires a scalacOption @@ -105,12 +105,15 @@ lazy val pluginScalaVersions = Seq( "2.12.11", "2.12.12", "2.12.13", + "2.12.14", + "2.12.15", "2.13.0", "2.13.1", "2.13.2", "2.13.3", "2.13.4", - "2.13.5" + "2.13.5", + "2.13.6" ) lazy val plugin = (project in file("plugin")). -- cgit v1.2.3