diff options
| -rw-r--r-- | chiselFrontend/src/main/scala/chisel3/core/Data.scala | 127 | ||||
| -rw-r--r-- | chiselFrontend/src/main/scala/chisel3/core/Reg.scala | 117 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/ChiselSpec.scala | 81 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/WidthSpec.scala | 171 |
4 files changed, 465 insertions, 31 deletions
diff --git a/chiselFrontend/src/main/scala/chisel3/core/Data.scala b/chiselFrontend/src/main/scala/chisel3/core/Data.scala index f292d3c6..e56412e6 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Data.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Data.scala @@ -429,9 +429,9 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { */ def litValue(): BigInt = litOption.get - /** Returns the width, in bits, if currently known. - * @throws java.util.NoSuchElementException if the width is not known. */ - final def getWidth: Int = width.get + /** Returns the width, in bits, if currently known. */ + final def getWidth: Int = + if (isWidthKnown) width.get else throwException(s"Width of $this is unknown!") /** Returns whether the width is currently known. */ final def isWidthKnown: Boolean = width.known /** Returns Some(width) if the width is known, else None. */ @@ -484,6 +484,10 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { } trait WireFactory { + /** @usecase def apply[T <: Data](t: T): T + * Construct a [[Wire]] from a type template + * @param t The template from which to construct this wire + */ def apply[T <: Data](t: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { if (compileOptions.declaredTypeMustBeUnbound) { requireIsChiselType(t, "wire type") @@ -502,17 +506,88 @@ trait WireFactory { } } +/** Utility for constructing hardware wires + * + * The width of a `Wire` (inferred or not) is copied from the type template + * {{{ + * val w0 = Wire(UInt()) // width is inferred + * val w1 = Wire(UInt(8.W)) // width is set to 8 + * + * val w2 = Wire(Vec(4, UInt())) // width is inferred + * val w3 = Wire(Vec(4, UInt(8.W))) // width of each element is set to 8 + * + * class MyBundle { + * val unknown = UInt() + * val known = UInt(8.W) + * } + * val w4 = Wire(new MyBundle) + * // Width of w4.unknown is inferred + * // Width of w4.known is set to 8 + * }}} + * + */ object Wire extends WireFactory +/** Utility for constructing hardware wires with a default connection + * + * The two forms of `WireInit` differ in how the type and width of the resulting [[Wire]] are + * specified. + * + * ==Single Argument== + * The single argument form uses the argument to specify both the type and default connection. For + * non-literal [[Bits]], the width of the [[Wire]] will be inferred. For literal [[Bits]] and all + * non-Bits arguments, the type will be copied from the argument. See the following examples for + * more details: + * + * 1. Literal [[Bits]] initializer: width will be set to match + * {{{ + * val w1 = WireInit(1.U) // width will be inferred to be 1 + * val w2 = WireInit(1.U(8.W)) // width is set to 8 + * }}} + * + * 2. Non-Literal [[Element]] initializer - width will be inferred + * {{{ + * val x = Wire(UInt()) + * val y = Wire(UInt(8.W)) + * val w1 = WireInit(x) // width will be inferred + * val w2 = WireInit(y) // width will be inferred + * }}} + * + * 3. [[Aggregate]] initializer - width will be set to match the aggregate + * + * {{{ + * class MyBundle { + * val unknown = UInt() + * val known = UInt(8.W) + * } + * val w1 = Wire(new MyBundle) + * val w2 = WireInit(w1) + * // Width of w2.unknown is inferred + * // Width of w2.known is set to 8 + * }}} + * + * ==Double Argument== + * The double argument form allows the type of the [[Wire]] and the default connection to be + * specified independently. + * + * The width inference semantics for `WireInit` with two arguments match those of [[Wire]]. The + * first argument to `WireInit` is the type template which defines the width of the `Wire` in + * exactly the same way as the only argument to [[Wire]]. + * + * More explicitly, you can reason about `WireInit` with multiple arguments as if it were defined + * as: + * {{{ + * def WireInit[T <: Data](t: T, init: T): T = { + * val x = Wire(t) + * x := init + * x + * } + * }}} + * + * @note The `Init` in `WireInit` refers to a `default` connection. This is in contrast to + * [[RegInit]] where the `Init` refers to a value on reset. + */ object WireInit { - def apply[T <: Data](init: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { - val model = (init match { - // For e.g. Wire(init=0.U(k.W)), fix the Reg's width to k - case init: Bits if init.litIsForcedWidth == Some(false) => init.cloneTypeWidth(Width()) - case _ => init.cloneTypeFull - }).asInstanceOf[T] - apply(model, init) - } private def applyImpl[T <: Data](t: T, init: Data)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { implicit val noSourceInfo = UnlocatableSourceInfo @@ -522,12 +597,38 @@ object WireInit { x } - def apply[T <: Data](t: T, init: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { + /** @usecase def apply[T <: Data](t: T, init: DontCare.type): T + * Construct a [[Wire]] with a type template and a [[DontCare]] default + * @param t The type template used to construct this [[Wire]] + * @param init The default connection to this [[Wire]], can only be [[DontCare]] + * @note This is really just a specialized form of `apply[T <: Data](t: T, init: T): T` with [[DontCare]] + * as `init` + */ + def apply[T <: Data](t: T, init: DontCare.type)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { applyImpl(t, init) } - def apply[T <: Data](t: T, init: DontCare.type)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { + + /** @usecase def apply[T <: Data](t: T, init: T): T + * Construct a [[Wire]] with a type template and a default connection + * @param t The type template used to construct this [[Wire]] + * @param init The hardware value that will serve as the default value + */ + def apply[T <: Data](t: T, init: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { applyImpl(t, init) } + + /** @usecase def apply[T <: Data](init: T): T + * Construct a [[Wire]] with a default connection + * @param init The hardware value that will serve as a type template and default value + */ + def apply[T <: Data](init: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { + val model = (init match { + // If init is a literal without forced width OR any non-literal, let width be inferred + case init: Bits if !init.litIsForcedWidth.getOrElse(false) => init.cloneTypeWidth(Width()) + case _ => init.cloneTypeFull + }).asInstanceOf[T] + apply(model, init) + } } /** RHS (source) for Invalidate API. diff --git a/chiselFrontend/src/main/scala/chisel3/core/Reg.scala b/chiselFrontend/src/main/scala/chisel3/core/Reg.scala index 11611c82..27156f8b 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Reg.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Reg.scala @@ -9,11 +9,31 @@ import chisel3.internal.Builder.pushCommand import chisel3.internal.firrtl._ import chisel3.internal.sourceinfo.{SourceInfo} +/** Utility for constructing hardware registers + * + * The width of a `Reg` (inferred or not) is copied from the type template + * {{{ + * val r0 = Reg(UInt()) // width is inferred + * val r1 = Reg(UInt(8.W)) // width is set to 8 + * + * val r2 = Reg(Vec(4, UInt())) // width is inferred + * val r3 = Reg(Vec(4, UInt(8.W))) // width of each element is set to 8 + * + * class MyBundle { + * val unknown = UInt() + * val known = UInt(8.W) + * } + * val r4 = Reg(new MyBundle) + * // Width of r4.unknown is inferred + * // Width of r4.known is set to 8 + * }}} + * + */ object Reg { - /** Creates a register without initialization (reset is ignored). Value does - * not change unless assigned to (using the := operator). - * - * @param t: data type for the register + /** @usecase def apply[T <: Data](t: T): T + * Construct a [[Reg]] from a type template with no initialization value (reset is ignored). + * Value will not change unless the [[Reg]] is given a connection. + * @param t The template from which to construct this wire */ def apply[T <: Data](t: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { if (compileOptions.declaredTypeMustBeUnbound) { @@ -64,20 +84,69 @@ object RegNext { } } +/** Utility for constructing hardware registers with an initialization value. + * + * The register is set to the initialization value when the current implicit `reset` is high + * + * The two forms of `RegInit` differ in how the type and width of the resulting [[Reg]] are + * specified. + * + * ==Single Argument== + * The single argument form uses the argument to specify both the type and reset value. For + * non-literal [[Bits]], the width of the [[Reg]] will be inferred. For literal [[Bits]] and all + * non-Bits arguments, the type will be copied from the argument. See the following examples for + * more details: + * + * 1. Literal [[Bits]] initializer: width will be set to match + * {{{ + * val r1 = RegInit(1.U) // width will be inferred to be 1 + * val r2 = RegInit(1.U(8.W)) // width is set to 8 + * }}} + * + * 2. Non-Literal [[Element]] initializer - width will be inferred + * {{{ + * val x = Wire(UInt()) + * val y = Wire(UInt(8.W)) + * val r1 = RegInit(x) // width will be inferred + * val r2 = RegInit(y) // width will be inferred + * }}} + * + * 3. [[Aggregate]] initializer - width will be set to match the aggregate + * + * {{{ + * class MyBundle { + * val unknown = UInt() + * val known = UInt(8.W) + * } + * val w1 = Reg(new MyBundle) + * val w2 = RegInit(w1) + * // Width of w2.unknown is inferred + * // Width of w2.known is set to 8 + * }}} + * + * ==Double Argument== + * The double argument form allows the type of the [[Reg]] and the default connection to be + * specified independently. + * + * The width inference semantics for `RegInit` with two arguments match those of [[Reg]]. The + * first argument to `RegInit` is the type template which defines the width of the `Reg` in + * exactly the same way as the only argument to [[Wire]]. + * + * More explicitly, you can reason about `RegInit` with multiple arguments as if it were defined + * as: + * {{{ + * def RegInit[T <: Data](t: T, init: T): T = { + * val x = Reg(t) + * x := init + * x + * } + * }}} + */ object RegInit { - /** Returns a register pre-initialized (on reset) to the specified value. - * Register type is inferred from the initializer. - */ - def apply[T <: Data](init: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { - val model = (init match { - // For e.g. Reg(init=UInt(0, k)), fix the Reg's width to k - case init: Bits if init.litIsForcedWidth == Some(false) => init.cloneTypeWidth(Width()) - case init => init.cloneTypeFull - }).asInstanceOf[T] - RegInit(model, init) - } - - /** Creates a register given an explicit type and an initialization (reset) value. + /** @usecase def apply[T <: Data](t: T, init: T): T + * Construct a [[Reg]] from a type template initialized to the specified value on reset + * @param t The type template used to construct this [[Reg]] + * @param init The value the [[Reg]] is initialized to on reset */ def apply[T <: Data](t: T, init: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { if (compileOptions.declaredTypeMustBeUnbound) { @@ -92,4 +161,18 @@ object RegInit { pushCommand(DefRegInit(sourceInfo, reg, clock, reset, init.ref)) reg } + + /** @usecase def apply[T <: Data](init: T): T + * Construct a [[Reg]] initialized on reset to the specified value. + * @param init Initial value that serves as a type template and reset value + */ + def apply[T <: Data](init: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { + val model = (init match { + // If init is a literal without forced width OR any non-literal, let width be inferred + case init: Bits if !init.litIsForcedWidth.getOrElse(false) => init.cloneTypeWidth(Width()) + case init => init.cloneTypeFull + }).asInstanceOf[T] + RegInit(model, init) + } + } diff --git a/src/test/scala/chiselTests/ChiselSpec.scala b/src/test/scala/chiselTests/ChiselSpec.scala index 661cf00e..ff75c1a3 100644 --- a/src/test/scala/chiselTests/ChiselSpec.scala +++ b/src/test/scala/chiselTests/ChiselSpec.scala @@ -31,6 +31,33 @@ trait ChiselRunners extends Assertions with BackendCompilationUtilities { } def elaborate(t: => RawModule): Unit = Driver.elaborate(() => t) + def assertKnownWidth(expected: Int)(gen: => Data): Unit = { + assertTesterPasses(new BasicTester { + val x = gen + assert(x.getWidth === expected) + // Sanity check that firrtl doesn't change the width + x := 0.U.asTypeOf(chiselTypeOf(x)) + val (_, done) = chisel3.util.Counter(true.B, 2) + when (done) { + chisel3.assert(~(x.asUInt) === -1.S(expected.W).asUInt) + stop() + } + }) + } + + def assertInferredWidth(expected: Int)(gen: => Data): Unit = { + assertTesterPasses(new BasicTester { + val x = gen + assert(!x.isWidthKnown, s"Asserting that width should be inferred yet width is known to Chisel!") + x := 0.U.asTypeOf(chiselTypeOf(x)) + val (_, done) = chisel3.util.Counter(true.B, 2) + when (done) { + chisel3.assert(~(x.asUInt) === -1.S(expected.W).asUInt) + stop() + } + }) + } + /** Given a generator, return the Firrtl that it generates. * * @param t Module generator @@ -63,7 +90,59 @@ trait ChiselRunners extends Assertions with BackendCompilationUtilities { } /** Spec base class for BDD-style testers. */ -class ChiselFlatSpec extends FlatSpec with ChiselRunners with Matchers +abstract class ChiselFlatSpec extends FlatSpec 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 { + a [TestFailedException] shouldBe thrownBy { + assertKnownWidth(7) { + Wire(UInt(8.W)) + } + } + } + + 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 { + a [TestFailedException] shouldBe thrownBy { + assertInferredWidth(8) { + Wire(UInt(8.W)) + } + } + } + + 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 PropertyChecks with Matchers { diff --git a/src/test/scala/chiselTests/WidthSpec.scala b/src/test/scala/chiselTests/WidthSpec.scala index 9a5b1860..0011c532 100644 --- a/src/test/scala/chiselTests/WidthSpec.scala +++ b/src/test/scala/chiselTests/WidthSpec.scala @@ -4,6 +4,19 @@ package chiselTests import chisel3._ +class SimpleBundle extends Bundle { + val x = UInt(4.W) + val y = UInt() +} +object SimpleBundle { + def intoWire(): SimpleBundle = { + val w = Wire(new SimpleBundle) + w.x := 0.U(4.W) + w.y := 0.U(4.W) + w + } +} + class WidthSpec extends ChiselFlatSpec { "Literals without specified widths" should "get the minimum legal width" in { "hdeadbeef".U.getWidth should be (32) @@ -15,3 +28,161 @@ class WidthSpec extends ChiselFlatSpec { 1.S.getWidth should be (2) } } + +abstract class WireRegWidthSpecImpl extends ChiselFlatSpec { + def name: String + def builder[T <: Data](x: T): T + + behavior of name + + it should "set the width if the template type has a set width" in { + assertKnownWidth(4) { + builder(UInt(4.W)) + } + assertKnownWidth(4) { + val w = builder(new SimpleBundle) + w := SimpleBundle.intoWire() + w.x + } + assertKnownWidth(4) { + val x = builder(Vec(1, UInt(4.W))) + x(0) + } + } + + it should "infer the width if the template type has no width" in { + assertInferredWidth(4) { + val w = builder(UInt()) + w := 0.U(4.W) + w + } + assertInferredWidth(4) { + val w = builder(new SimpleBundle) + w := SimpleBundle.intoWire() + w.y + } + assertInferredWidth(4) { + val w = builder(Vec(1, UInt())) + w(0) := 0.U(4.W) + w(0) + } + } +} + +class WireWidthSpec extends WireRegWidthSpecImpl { + def name = "Wire" + def builder[T <: Data](x: T): T = Wire(x) +} +class RegWidthSpec extends WireRegWidthSpecImpl { + def name = "Reg" + def builder[T <: Data](x: T): T = Reg(x) +} + +abstract class WireInitRegInitSpecImpl extends ChiselFlatSpec { + def name: String + def builder1[T <: Data](x: T): T + def builder2[T <: Data](x: T, y: T): T + + behavior of s"$name (Single Argument)" + + it should "set width if passed a literal with forced width" in { + assertKnownWidth(4) { + builder1(3.U(4.W)) + } + } + + it should "NOT set width if passed a literal without a forced width" in { + assertInferredWidth(4) { + val w = builder1(3.U) + w := 3.U(4.W) + w + } + } + + it should "NOT set width if passed a non-literal" in { + assertInferredWidth(4) { + val w = WireInit(3.U(4.W)) + builder1(w) + } + } + + it should "copy the widths of aggregates" in { + assertInferredWidth(4) { + val w = builder1(SimpleBundle.intoWire()) + w.y + } + assertKnownWidth(4) { + val w = builder1(SimpleBundle.intoWire()) + w.x + } + assertInferredWidth(4) { + val x = Wire(Vec(1, UInt())) + x(0) := 0.U(4.W) + val w = builder1(x) + w(0) + } + assertKnownWidth(4) { + val x = Wire(Vec(1, UInt(4.W))) + x(0) := 0.U(4.W) + val w = builder1(x) + w(0) + } + } + + behavior of s"$name (Double Argument)" + + it should "set the width if the template type has a set width" in { + assertKnownWidth(4) { + WireInit(UInt(4.W), 0.U) + } + assertKnownWidth(4) { + WireInit(UInt(4.W), 0.U(2.W)) + } + assertKnownWidth(4) { + val w = WireInit(new SimpleBundle, SimpleBundle.intoWire()) + w.x + } + assertKnownWidth(4) { + val x = Wire(Vec(1, UInt())) + x(0) := 0.U(4.W) + val w = WireInit(Vec(1, UInt(4.W)), x) + w(0) + } + } + + it should "infer the width if the template type has no width" in { + val templates = Seq( + () => 0.U, () => 0.U(2.W), () => WireInit(0.U), () => WireInit(0.U(2.W)) + ) + for (gen <- templates) { + assertInferredWidth(4) { + val w = WireInit(UInt(), gen()) + w := 0.U(4.W) + w + } + } + assertInferredWidth(4) { + val w = WireInit(new SimpleBundle, SimpleBundle.intoWire()) + w.y + } + assertInferredWidth(4) { + val x = Wire(Vec(1, UInt())) + x(0) := 0.U(4.W) + val w = WireInit(Vec(1, UInt()), x) + w(0) + } + } +} + +class WireInitWidthSpec extends WireInitRegInitSpecImpl { + def name = "WireInit" + def builder1[T <: Data](x: T): T = WireInit(x) + def builder2[T <: Data](x: T, y: T): T = WireInit(x, y) +} + +class RegInitWidthSpec extends WireInitRegInitSpecImpl { + def name = "RegInit" + def builder1[T <: Data](x: T): T = RegInit(x) + def builder2[T <: Data](x: T, y: T): T = RegInit(x, y) +} + |
