From 2e1429598159b766dee8df0b223fc4f01eb7c0f7 Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Thu, 1 Feb 2024 17:37:08 -0800 Subject: [PATCH 01/11] Add basic support for Property expression trees. --- .../main/scala/chisel3/internal/Binding.scala | 19 ++++++++++++-- .../chisel3/internal/firrtl/Converter.scala | 2 ++ .../scala/chisel3/internal/firrtl/IR.scala | 9 +++++++ .../scala/chisel3/properties/Property.scala | 25 +++++++++++++++++++ firrtl/src/main/scala/firrtl/ir/IR.scala | 19 ++++++++++++++ 5 files changed, 72 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/chisel3/internal/Binding.scala b/core/src/main/scala/chisel3/internal/Binding.scala index f6837fba379..15966872984 100644 --- a/core/src/main/scala/chisel3/internal/Binding.scala +++ b/core/src/main/scala/chisel3/internal/Binding.scala @@ -3,10 +3,12 @@ package chisel3.internal import chisel3._ -import chisel3.experimental.BaseModule -import chisel3.internal.firrtl.ir.{LitArg, PropertyLit} +import chisel3.experimental.{BaseModule, SourceInfo} +import chisel3.internal.firrtl.ir.{Arg, LitArg, PropertyLit} import chisel3.properties.Class +import _root_.firrtl.ir.{PropPrimOp, PropertyType} + import scala.collection.immutable.VectorMap // Element only direction used for the Binding system only. @@ -176,3 +178,16 @@ private[chisel3] case class BundleLitBinding(litMap: Map[Data, LitArg]) extends private[chisel3] case class VecLitBinding(litMap: VectorMap[Data, LitArg]) extends LitBinding // Literal binding attached to a Property. private[chisel3] case object PropertyValueBinding extends UnconstrainedBinding with ReadOnlyBinding + +/** Binding for Property expressions. + * + * This binding is a little different, because Property expressions don't emit Nodes, so we sore all the information we + * need to create an in-memory Arg that captures the operation and operands. + * + * @param sourceInfo Source location where the expression was created + * @param enclosure BaseModule where the expression was created + * @param op Primitive operation for this expression + * @param args Arguments to this expression + */ +private[chisel3] case class PropExprBinding(sourceInfo: SourceInfo, enclosure: BaseModule, op: PropPrimOp, args: Arg*) + extends ConstrainedBinding diff --git a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala index b170053f5e2..a50433eb4e7 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -100,6 +100,8 @@ private[chisel3] object Converter { fir.RWProbeExpr(convert(probe, ctx, info)) case e @ ProbeRead(probe) => fir.ProbeRead(convert(probe, ctx, info)) + case PropExpr(info, tpe, op, args @ _*) => + fir.PropExpr(convert(info), tpe, op, args.map(convert(_, ctx, info))) case other => throw new InternalErrorException(s"Unexpected type in convert $other") } diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index def9cf6e0c1..e262967877a 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -186,6 +186,15 @@ private[chisel3] object ir { } } + /** Property expressions. + * + * Property expressions are conceptually similar to Nodes, but only exist as a tree of Args in-memory. + */ + case class PropExpr(sourceInfo: SourceInfo, tpe: firrtlir.PropertyType, op: firrtlir.PropPrimOp, args: Arg*) + extends Arg { + def name: String = throwException("Internal Error! PropExpr has no name") + } + case class Ref(name: String) extends Arg /** Arg for ports of Modules diff --git a/core/src/main/scala/chisel3/properties/Property.scala b/core/src/main/scala/chisel3/properties/Property.scala index b3a1b6f0b14..ca3894dab7a 100644 --- a/core/src/main/scala/chisel3/properties/Property.scala +++ b/core/src/main/scala/chisel3/properties/Property.scala @@ -7,6 +7,7 @@ import firrtl.{ir => fir} import firrtl.annotations.{InstanceTarget, IsMember, ModuleTarget, ReferenceTarget, Target} import chisel3.internal._ import chisel3.internal.firrtl.{ir, Converter} +import chisel3.internal.sourceinfo.SourceInfoTransform import chisel3.experimental.{prefix, requireIsHardware, Analog, SourceInfo} import chisel3.experimental.hierarchy.Instance import scala.reflect.runtime.universe.{typeOf, TypeTag} @@ -315,6 +316,9 @@ sealed trait Property[T] extends Element { self => requireIsHardware(this) requireVisible() topBindingOpt match { + case Some(PropExprBinding(sourceInfo, _, op, lhs, rhs)) => + // For Property expressions, we create a special kind of Arg that builds in-memory trees without creating nodes. + ir.PropExpr(sourceInfo, tpe.getPropertyType(), op, lhs, rhs) case Some(binding: TopBinding) => ir.Node(this) case opt => throwException(s"internal error: unknown binding $opt in generating RHS ref") } @@ -335,6 +339,27 @@ private[chisel3] object ClassTypeProvider { } } +/** Typeclass for Property arithmetic. + */ +trait PropertyArithmeticOps[T] {} + +object PropertyArithmeticOps { + // Helper function to create Property expression bindings. + private def binOp[T: PropertyType]( + sourceInfo: SourceInfo, + op: fir.PropPrimOp, + lhs: Property[T], + rhs: Property[T] + ): Property[T] = { + val result = Property[T]() + val enclosure = Builder.referenceUserContainer + result.bind( + PropExprBinding(sourceInfo, enclosure, op, lhs.ref, rhs.ref) + ) + result.asInstanceOf[Property[T]] + } +} + /** Companion object for Property. */ object Property { diff --git a/firrtl/src/main/scala/firrtl/ir/IR.scala b/firrtl/src/main/scala/firrtl/ir/IR.scala index 0b6923a4c0c..f7e0e7b527a 100644 --- a/firrtl/src/main/scala/firrtl/ir/IR.scala +++ b/firrtl/src/main/scala/firrtl/ir/IR.scala @@ -257,6 +257,25 @@ case class PathPropertyLiteral(value: String) extends Expression with UseSeriali case class SequencePropertyValue(tpe: Type, values: Seq[Expression]) extends Expression with UseSerializer +/** Property primitive operations. + */ +sealed trait PropPrimOp +object PropPrimOp {} + +/** Property expressions. + * + * Unlike other primitives, Property expressions serialize as a tree directly in their rvalue context. + */ +case class PropExpr(info: Info, tpe: Type, op: PropPrimOp, args: Seq[Expression]) + extends Expression + with UseSerializer { + override def serialize: String = { + val serializedOp = op.toString() + val serializedArgs = args.map(_.serialize).mkString("(", ", ", ")") + serializedOp + serializedArgs + } +} + case class DoPrim(op: PrimOp, args: Seq[Expression], consts: Seq[BigInt], tpe: Type) extends Expression with UseSerializer From 14d8485c77d6c74e12958652905b948131539b33 Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Thu, 1 Feb 2024 17:44:16 -0800 Subject: [PATCH 02/11] Support addition in PropertyArithmeticOps on Int, Long, and BigInt. --- .../scala/chisel3/properties/Property.scala | 25 +++++++++++- firrtl/src/main/scala/firrtl/ir/IR.scala | 6 ++- .../chiselTests/properties/PropertySpec.scala | 40 +++++++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/chisel3/properties/Property.scala b/core/src/main/scala/chisel3/properties/Property.scala index ca3894dab7a..384efa8effb 100644 --- a/core/src/main/scala/chisel3/properties/Property.scala +++ b/core/src/main/scala/chisel3/properties/Property.scala @@ -324,6 +324,8 @@ sealed trait Property[T] extends Element { self => } } + final def +(that: Property[T])(implicit ev: PropertyArithmeticOps[Property[T]], sourceInfo: SourceInfo): Property[T] = + ev.add(this, that) } private[chisel3] sealed trait ClassTypeProvider[A] { @@ -341,9 +343,30 @@ private[chisel3] object ClassTypeProvider { /** Typeclass for Property arithmetic. */ -trait PropertyArithmeticOps[T] {} +trait PropertyArithmeticOps[T] { + def add(lhs: T, rhs: T)(implicit sourceInfo: SourceInfo): T +} object PropertyArithmeticOps { + // Type class instances for Property arithmetic. + implicit val intArithmeticOps: PropertyArithmeticOps[Property[Int]] = + new PropertyArithmeticOps[Property[Int]] { + def add(lhs: Property[Int], rhs: Property[Int])(implicit sourceInfo: SourceInfo) = + binOp(sourceInfo, fir.PropPrimOp.AddOp, lhs, rhs) + } + + implicit val longArithmeticOps: PropertyArithmeticOps[Property[Long]] = + new PropertyArithmeticOps[Property[Long]] { + def add(lhs: Property[Long], rhs: Property[Long])(implicit sourceInfo: SourceInfo) = + binOp(sourceInfo, fir.PropPrimOp.AddOp, lhs, rhs) + } + + implicit val bigIntArithmeticOps: PropertyArithmeticOps[Property[BigInt]] = + new PropertyArithmeticOps[Property[BigInt]] { + def add(lhs: Property[BigInt], rhs: Property[BigInt])(implicit sourceInfo: SourceInfo) = + binOp(sourceInfo, fir.PropPrimOp.AddOp, lhs, rhs) + } + // Helper function to create Property expression bindings. private def binOp[T: PropertyType]( sourceInfo: SourceInfo, diff --git a/firrtl/src/main/scala/firrtl/ir/IR.scala b/firrtl/src/main/scala/firrtl/ir/IR.scala index f7e0e7b527a..2b0e58f168e 100644 --- a/firrtl/src/main/scala/firrtl/ir/IR.scala +++ b/firrtl/src/main/scala/firrtl/ir/IR.scala @@ -260,7 +260,11 @@ case class SequencePropertyValue(tpe: Type, values: Seq[Expression]) extends Exp /** Property primitive operations. */ sealed trait PropPrimOp -object PropPrimOp {} +object PropPrimOp { + case object AddOp extends PropPrimOp { + override def toString(): String = "integer_add" + } +} /** Property expressions. * diff --git a/src/test/scala/chiselTests/properties/PropertySpec.scala b/src/test/scala/chiselTests/properties/PropertySpec.scala index ccd90815df0..48f53c24919 100644 --- a/src/test/scala/chiselTests/properties/PropertySpec.scala +++ b/src/test/scala/chiselTests/properties/PropertySpec.scala @@ -617,4 +617,44 @@ class PropertySpec extends ChiselFlatSpec with MatchesAndOmits { lit.isLit shouldBe true }) } + + behavior.of("PropertyArithmeticOps") + + it should "support expressions in temporaries, wires, and ports" in { + val chirrtl = ChiselStage.emitCHIRRTL(new RawModule { + val a = IO(Input(Property[Int]())) + val b = IO(Input(Property[Int]())) + val c = IO(Output(Property[Int]())) + val d = IO(Output(Property[Int]())) + val e = IO(Output(Property[Int]())) + + val t = a + b + + val w = WireInit(t) + + c := t + d := t + a + e := w + (a + b) + }) + + matchesAndOmits(chirrtl)( + "propassign w, integer_add(a, b)", + "propassign c, integer_add(a, b)", + "propassign d, integer_add(integer_add(a, b), a)", + "propassign e, integer_add(w, integer_add(a, b))" + )() + } + + it should "support addition" in { + val chirrtl = ChiselStage.emitCHIRRTL(new RawModule { + val a = IO(Input(Property[BigInt]())) + val b = IO(Input(Property[BigInt]())) + val c = IO(Output(Property[BigInt]())) + c := a + b + }) + + matchesAndOmits(chirrtl)( + "propassign c, integer_add(a, b)" + )() + } } From c3a35eda8a95577a6783b465d8536dab26c64ebf Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Tue, 6 Feb 2024 12:28:07 -0800 Subject: [PATCH 03/11] Put down temp wires for all Property expressions; add boring test. --- .../scala/chisel3/properties/Property.scala | 7 ++- .../chiselTests/properties/PropertySpec.scala | 46 +++++++++++++++++-- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/chisel3/properties/Property.scala b/core/src/main/scala/chisel3/properties/Property.scala index 384efa8effb..e10c151b1be 100644 --- a/core/src/main/scala/chisel3/properties/Property.scala +++ b/core/src/main/scala/chisel3/properties/Property.scala @@ -374,12 +374,17 @@ object PropertyArithmeticOps { lhs: Property[T], rhs: Property[T] ): Property[T] = { + implicit val info = sourceInfo + val result = Property[T]() val enclosure = Builder.referenceUserContainer result.bind( PropExprBinding(sourceInfo, enclosure, op, lhs.ref, rhs.ref) ) - result.asInstanceOf[Property[T]] + + val _wire = Wire(chiselTypeOf(result)) + _wire := result + _wire.asInstanceOf[Property[T]] } } diff --git a/src/test/scala/chiselTests/properties/PropertySpec.scala b/src/test/scala/chiselTests/properties/PropertySpec.scala index 48f53c24919..2911ef41baa 100644 --- a/src/test/scala/chiselTests/properties/PropertySpec.scala +++ b/src/test/scala/chiselTests/properties/PropertySpec.scala @@ -8,6 +8,7 @@ import chiselTests.{ChiselFlatSpec, MatchesAndOmits} import circt.stage.ChiselStage import chisel3.properties.ClassType import chisel3.properties.AnyClassType +import chisel3.util.experimental.BoringUtils class PropertySpec extends ChiselFlatSpec with MatchesAndOmits { behavior.of("Property") @@ -638,10 +639,45 @@ class PropertySpec extends ChiselFlatSpec with MatchesAndOmits { }) matchesAndOmits(chirrtl)( - "propassign w, integer_add(a, b)", + "wire t : Integer", + "propassign t, integer_add(a, b)", + "wire w : Integer", + "propassign w, t", + "propassign c, t", + "wire _d_WIRE : Integer", + "propassign _d_WIRE, integer_add(t, a)", + "propassign d, _d_WIRE", + "wire _e_WIRE", + "propassign _e_WIRE, integer_add(a, b)", + "wire _e_WIRE_1", + "propassign _e_WIRE_1, integer_add(w, _e_WIRE)", + "propassign e, _e_WIRE_1" + )() + } + + it should "support boring from expressions" in { + val chirrtl = ChiselStage.emitCHIRRTL(new RawModule { + val child = Module(new RawModule { + val a = IO(Input(Property[Int]())) + val b = IO(Input(Property[Int]())) + val c = a + b + }) + + val a = IO(Input(Property[Int]())) + val b = IO(Input(Property[Int]())) + val c = IO(Output(Property[Int]())) + + child.a := a + child.b := a + c := BoringUtils.bore(child.c) + }) + + matchesAndOmits(chirrtl)( + "output c_bore : Integer", + "wire c : Integer", "propassign c, integer_add(a, b)", - "propassign d, integer_add(integer_add(a, b), a)", - "propassign e, integer_add(w, integer_add(a, b))" + "propassign c_bore, c", + "propassign c, child.c_bore" )() } @@ -654,7 +690,9 @@ class PropertySpec extends ChiselFlatSpec with MatchesAndOmits { }) matchesAndOmits(chirrtl)( - "propassign c, integer_add(a, b)" + "wire _c_WIRE : Integer", + "propassign _c_WIRE, integer_add(a, b)", + "propassign c, _c_WIRE" )() } } From 520fc1426bc264480d30f8ec01e912bd2d6c4d18 Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Tue, 20 Feb 2024 16:10:37 -0800 Subject: [PATCH 04/11] Add docs explanation about Property expressions and arithmetic. --- docs/src/explanations/properties.md | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/src/explanations/properties.md b/docs/src/explanations/properties.md index dcd289f9e7f..d76b0bee76b 100644 --- a/docs/src/explanations/properties.md +++ b/docs/src/explanations/properties.md @@ -114,3 +114,35 @@ class SequenceExample extends RawModule { outPort2 := Property(Seq(inPort, Property(789))) } ``` + +### Property Expressions + +Expressions can be built out of `Property` values for certain `Property` types. +This is useful for expressing design intent that is parameterized by input +`Property` values. + +#### Integer Arithmetic + +The integral `Property` types, like `Property[Int]`, `Property[Long]` and +`Property[BigInt]`, can be used to build arithmetic expressions in terms of +`Property` values. + +In the following example, an output `address` port of `Property[Int]` type is +computed as the addition of an `offset` `Property[Int]` value relative to an +input `base` `Property[Int]` value. + +```scala mdoc:silent +class IntegerArithmeticExample extends RawModule { + val base = IO(Input(Property[Int]())) + val address = IO(Output(Property[Int]())) + val offset = Property(1024) + address := base + offset +} +``` + +The following table lists the possible arithmetic operators that are supported +on integral `Property` typed values. + +| Operation | Description | +| --------- | ----------- | +| `+` | Perform addition as defined by FIRRTL spec section 25.1.1. | From fe3224b1165ea683c2c1fbc2f215eb2b049b0ae3 Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Wed, 21 Feb 2024 07:09:13 -0800 Subject: [PATCH 05/11] Remove PropExprBinding and create PropExpr directly. --- .../main/scala/chisel3/internal/Binding.scala | 19 ++----------------- .../scala/chisel3/properties/Property.scala | 15 +++++++-------- 2 files changed, 9 insertions(+), 25 deletions(-) diff --git a/core/src/main/scala/chisel3/internal/Binding.scala b/core/src/main/scala/chisel3/internal/Binding.scala index 15966872984..f6837fba379 100644 --- a/core/src/main/scala/chisel3/internal/Binding.scala +++ b/core/src/main/scala/chisel3/internal/Binding.scala @@ -3,12 +3,10 @@ package chisel3.internal import chisel3._ -import chisel3.experimental.{BaseModule, SourceInfo} -import chisel3.internal.firrtl.ir.{Arg, LitArg, PropertyLit} +import chisel3.experimental.BaseModule +import chisel3.internal.firrtl.ir.{LitArg, PropertyLit} import chisel3.properties.Class -import _root_.firrtl.ir.{PropPrimOp, PropertyType} - import scala.collection.immutable.VectorMap // Element only direction used for the Binding system only. @@ -178,16 +176,3 @@ private[chisel3] case class BundleLitBinding(litMap: Map[Data, LitArg]) extends private[chisel3] case class VecLitBinding(litMap: VectorMap[Data, LitArg]) extends LitBinding // Literal binding attached to a Property. private[chisel3] case object PropertyValueBinding extends UnconstrainedBinding with ReadOnlyBinding - -/** Binding for Property expressions. - * - * This binding is a little different, because Property expressions don't emit Nodes, so we sore all the information we - * need to create an in-memory Arg that captures the operation and operands. - * - * @param sourceInfo Source location where the expression was created - * @param enclosure BaseModule where the expression was created - * @param op Primitive operation for this expression - * @param args Arguments to this expression - */ -private[chisel3] case class PropExprBinding(sourceInfo: SourceInfo, enclosure: BaseModule, op: PropPrimOp, args: Arg*) - extends ConstrainedBinding diff --git a/core/src/main/scala/chisel3/properties/Property.scala b/core/src/main/scala/chisel3/properties/Property.scala index e10c151b1be..5ccc7b54c3e 100644 --- a/core/src/main/scala/chisel3/properties/Property.scala +++ b/core/src/main/scala/chisel3/properties/Property.scala @@ -229,7 +229,7 @@ sealed trait Property[T] extends Element { self => } } - protected val tpe: PropertyType[_] + protected[properties] val tpe: PropertyType[_] private[chisel3] def _asUIntImpl(first: Boolean)(implicit sourceInfo: SourceInfo): chisel3.UInt = { Builder.error(s"${this._localErrorContext} does not support .asUInt.") @@ -316,9 +316,6 @@ sealed trait Property[T] extends Element { self => requireIsHardware(this) requireVisible() topBindingOpt match { - case Some(PropExprBinding(sourceInfo, _, op, lhs, rhs)) => - // For Property expressions, we create a special kind of Arg that builds in-memory trees without creating nodes. - ir.PropExpr(sourceInfo, tpe.getPropertyType(), op, lhs, rhs) case Some(binding: TopBinding) => ir.Node(this) case opt => throwException(s"internal error: unknown binding $opt in generating RHS ref") } @@ -377,10 +374,12 @@ object PropertyArithmeticOps { implicit val info = sourceInfo val result = Property[T]() - val enclosure = Builder.referenceUserContainer - result.bind( - PropExprBinding(sourceInfo, enclosure, op, lhs.ref, rhs.ref) - ) + Builder.referenceUserContainer match { + case mod: RawModule => result.bind(OpBinding(mod, Builder.currentWhen)) + case cls: Class => result.bind(ClassBinding(cls)) + case _ => throwException("Internal Error! Property expression can only occur within RawModule or Class.") + } + result.setRef(ir.PropExpr(sourceInfo, result.tpe.getPropertyType(), op, lhs.ref, rhs.ref)) val _wire = Wire(chiselTypeOf(result)) _wire := result From 1e04e279dd575470dfc9b44c9667fdb947709922 Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Wed, 21 Feb 2024 07:16:39 -0800 Subject: [PATCH 06/11] Don't use varargs for PropExpr args. --- core/src/main/scala/chisel3/internal/firrtl/Converter.scala | 2 +- core/src/main/scala/chisel3/internal/firrtl/IR.scala | 2 +- core/src/main/scala/chisel3/properties/Property.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala index a50433eb4e7..5dbfbac5bef 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -100,7 +100,7 @@ private[chisel3] object Converter { fir.RWProbeExpr(convert(probe, ctx, info)) case e @ ProbeRead(probe) => fir.ProbeRead(convert(probe, ctx, info)) - case PropExpr(info, tpe, op, args @ _*) => + case PropExpr(info, tpe, op, args) => fir.PropExpr(convert(info), tpe, op, args.map(convert(_, ctx, info))) case other => throw new InternalErrorException(s"Unexpected type in convert $other") diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index e262967877a..998d67d5438 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -190,7 +190,7 @@ private[chisel3] object ir { * * Property expressions are conceptually similar to Nodes, but only exist as a tree of Args in-memory. */ - case class PropExpr(sourceInfo: SourceInfo, tpe: firrtlir.PropertyType, op: firrtlir.PropPrimOp, args: Arg*) + case class PropExpr(sourceInfo: SourceInfo, tpe: firrtlir.PropertyType, op: firrtlir.PropPrimOp, args: List[Arg]) extends Arg { def name: String = throwException("Internal Error! PropExpr has no name") } diff --git a/core/src/main/scala/chisel3/properties/Property.scala b/core/src/main/scala/chisel3/properties/Property.scala index 5ccc7b54c3e..e7db6e77cfd 100644 --- a/core/src/main/scala/chisel3/properties/Property.scala +++ b/core/src/main/scala/chisel3/properties/Property.scala @@ -379,7 +379,7 @@ object PropertyArithmeticOps { case cls: Class => result.bind(ClassBinding(cls)) case _ => throwException("Internal Error! Property expression can only occur within RawModule or Class.") } - result.setRef(ir.PropExpr(sourceInfo, result.tpe.getPropertyType(), op, lhs.ref, rhs.ref)) + result.setRef(ir.PropExpr(sourceInfo, result.tpe.getPropertyType(), op, List(lhs.ref, rhs.ref))) val _wire = Wire(chiselTypeOf(result)) _wire := result From 41e3582f0bf7b1c80649f2e0e8815ba49afa0b69 Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Wed, 21 Feb 2024 15:09:12 -0800 Subject: [PATCH 07/11] Add a comment about PropExpr's intended usage internally. --- core/src/main/scala/chisel3/internal/firrtl/IR.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index 998d67d5438..5e99ed31524 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -192,6 +192,9 @@ private[chisel3] object ir { */ case class PropExpr(sourceInfo: SourceInfo, tpe: firrtlir.PropertyType, op: firrtlir.PropPrimOp, args: List[Arg]) extends Arg { + // PropExpr is different from other Args, because this is only used as an internal data structure, and we never name + // the Arg or use the name in textual FIRRTL. This is always expected to be the exp of a PropAssign, and it would be + // an internal error to request the name. def name: String = throwException("Internal Error! PropExpr has no name") } From f6dd39996d5d1ff67840efa1e480dedd913b04cc Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Wed, 21 Feb 2024 15:09:51 -0800 Subject: [PATCH 08/11] Add more tests, and a nicer error message with implicitNotFound. --- .../scala/chisel3/properties/Property.scala | 1 + .../chiselTests/properties/PropertySpec.scala | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/core/src/main/scala/chisel3/properties/Property.scala b/core/src/main/scala/chisel3/properties/Property.scala index e7db6e77cfd..7ad0f23a396 100644 --- a/core/src/main/scala/chisel3/properties/Property.scala +++ b/core/src/main/scala/chisel3/properties/Property.scala @@ -340,6 +340,7 @@ private[chisel3] object ClassTypeProvider { /** Typeclass for Property arithmetic. */ +@implicitNotFound("arithmetic operations are not supported on Property type ${T}") trait PropertyArithmeticOps[T] { def add(lhs: T, rhs: T)(implicit sourceInfo: SourceInfo): T } diff --git a/src/test/scala/chiselTests/properties/PropertySpec.scala b/src/test/scala/chiselTests/properties/PropertySpec.scala index 2911ef41baa..3128746b31e 100644 --- a/src/test/scala/chiselTests/properties/PropertySpec.scala +++ b/src/test/scala/chiselTests/properties/PropertySpec.scala @@ -681,6 +681,34 @@ class PropertySpec extends ChiselFlatSpec with MatchesAndOmits { )() } + it should "support targeting the result of expressions" in { + val chirrtl = ChiselStage.emitCHIRRTL(new RawModule { + override def desiredName = "Top" + + val mod = Module(new RawModule { + override def desiredName = "Foo" + val a = IO(Input(Property[Int]())) + val b = IO(Input(Property[Int]())) + val c = a + b + }) + + mod.c.toTarget.toString should equal("~Top|Foo>c") + }) + + matchesAndOmits(chirrtl)( + "wire c : Integer", + "propassign c, integer_add(a, b)" + )() + } + + it should "not support expressions involving Property types that don't provide a typeclass instance" in { + assertTypeError(""" + val a = Property[String]() + val b = Property[String]() + a + b + """) + } + it should "support addition" in { val chirrtl = ChiselStage.emitCHIRRTL(new RawModule { val a = IO(Input(Property[BigInt]())) From cdd58fb53bcf19276465e131eac0c7d3ced867e7 Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Wed, 21 Feb 2024 15:40:08 -0800 Subject: [PATCH 09/11] Directly create the PropExpr and PropAssign IR nodes. --- .../scala/chisel3/properties/Property.scala | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/core/src/main/scala/chisel3/properties/Property.scala b/core/src/main/scala/chisel3/properties/Property.scala index 7ad0f23a396..5b6f1720511 100644 --- a/core/src/main/scala/chisel3/properties/Property.scala +++ b/core/src/main/scala/chisel3/properties/Property.scala @@ -365,7 +365,7 @@ object PropertyArithmeticOps { binOp(sourceInfo, fir.PropPrimOp.AddOp, lhs, rhs) } - // Helper function to create Property expression bindings. + // Helper function to create Property expression IR. private def binOp[T: PropertyType]( sourceInfo: SourceInfo, op: fir.PropPrimOp, @@ -374,16 +374,26 @@ object PropertyArithmeticOps { ): Property[T] = { implicit val info = sourceInfo - val result = Property[T]() - Builder.referenceUserContainer match { - case mod: RawModule => result.bind(OpBinding(mod, Builder.currentWhen)) - case cls: Class => result.bind(ClassBinding(cls)) - case _ => throwException("Internal Error! Property expression can only occur within RawModule or Class.") + // Get the containing RawModule, or throw an error. We can only use the temporary Wire approach in RawModule, so at + // least give a decent error explaining this current shortcoming. + val currentModule = Builder.referenceUserContainer match { + case mod: RawModule => mod + case other => + throwException( + sourceInfo.makeMessage(s => s"Property arithmetic is currently only supported in RawModules ${s}") + ) } - result.setRef(ir.PropExpr(sourceInfo, result.tpe.getPropertyType(), op, List(lhs.ref, rhs.ref))) - val _wire = Wire(chiselTypeOf(result)) - _wire := result + // Create a temporary Wire to assign the expression to. We currently don't support Nodes for Property types. + val _wire = Wire(chiselTypeOf(lhs)) + + // Create a PropExpr with the correct type, operation, and operands. + val propExpr = ir.PropExpr(sourceInfo, lhs.tpe.getPropertyType(), op, List(lhs.ref, rhs.ref)) + + // Directly add a PropAssign command assigning the PropExpr to the Wire. + currentModule.addCommand(ir.PropAssign(sourceInfo, _wire.lref, propExpr)) + + // Return the temporary Wire as the result. _wire.asInstanceOf[Property[T]] } } From 14b2ef56c5d8c7b7ead129fdd26e8b6881a5a6af Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Wed, 21 Feb 2024 15:40:33 -0800 Subject: [PATCH 10/11] Add a test for error message for using arithmetic in a Class. --- .../chiselTests/properties/PropertySpec.scala | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/test/scala/chiselTests/properties/PropertySpec.scala b/src/test/scala/chiselTests/properties/PropertySpec.scala index 3128746b31e..e0c3a354632 100644 --- a/src/test/scala/chiselTests/properties/PropertySpec.scala +++ b/src/test/scala/chiselTests/properties/PropertySpec.scala @@ -3,7 +3,7 @@ package chiselTests.properties import chisel3._ -import chisel3.properties.{Class, Path, Property, PropertyType} +import chisel3.properties.{Class, DynamicObject, Path, Property, PropertyType} import chiselTests.{ChiselFlatSpec, MatchesAndOmits} import circt.stage.ChiselStage import chisel3.properties.ClassType @@ -709,6 +709,21 @@ class PropertySpec extends ChiselFlatSpec with MatchesAndOmits { """) } + it should "not support expressions in Classes, and give a nice error" in { + val e = the[ChiselException] thrownBy (ChiselStage.emitCHIRRTL(new RawModule { + DynamicObject(new Class { + val a = IO(Input(Property[BigInt]())) + val b = IO(Input(Property[BigInt]())) + val c = IO(Output(Property[BigInt]())) + c := a + b + }) + })) + + e.getMessage should include( + "Property arithmetic is currently only supported in RawModules @[src/test/scala/chiselTests/properties/PropertySpec.scala" + ) + } + it should "support addition" in { val chirrtl = ChiselStage.emitCHIRRTL(new RawModule { val a = IO(Input(Property[BigInt]())) From 7fc10717ff7c677a31898109e271881cfaaecec9 Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Wed, 21 Feb 2024 17:13:34 -0800 Subject: [PATCH 11/11] Updates after PR review. --- .../scala/chisel3/properties/Property.scala | 15 +++++++------ firrtl/src/main/scala/firrtl/ir/IR.scala | 8 +++---- .../chiselTests/properties/PropertySpec.scala | 22 +++++++++---------- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/core/src/main/scala/chisel3/properties/Property.scala b/core/src/main/scala/chisel3/properties/Property.scala index 5b6f1720511..ece3cca0b25 100644 --- a/core/src/main/scala/chisel3/properties/Property.scala +++ b/core/src/main/scala/chisel3/properties/Property.scala @@ -341,7 +341,7 @@ private[chisel3] object ClassTypeProvider { /** Typeclass for Property arithmetic. */ @implicitNotFound("arithmetic operations are not supported on Property type ${T}") -trait PropertyArithmeticOps[T] { +sealed trait PropertyArithmeticOps[T] { def add(lhs: T, rhs: T)(implicit sourceInfo: SourceInfo): T } @@ -350,19 +350,19 @@ object PropertyArithmeticOps { implicit val intArithmeticOps: PropertyArithmeticOps[Property[Int]] = new PropertyArithmeticOps[Property[Int]] { def add(lhs: Property[Int], rhs: Property[Int])(implicit sourceInfo: SourceInfo) = - binOp(sourceInfo, fir.PropPrimOp.AddOp, lhs, rhs) + binOp(sourceInfo, fir.IntegerAddOp, lhs, rhs) } implicit val longArithmeticOps: PropertyArithmeticOps[Property[Long]] = new PropertyArithmeticOps[Property[Long]] { def add(lhs: Property[Long], rhs: Property[Long])(implicit sourceInfo: SourceInfo) = - binOp(sourceInfo, fir.PropPrimOp.AddOp, lhs, rhs) + binOp(sourceInfo, fir.IntegerAddOp, lhs, rhs) } implicit val bigIntArithmeticOps: PropertyArithmeticOps[Property[BigInt]] = new PropertyArithmeticOps[Property[BigInt]] { def add(lhs: Property[BigInt], rhs: Property[BigInt])(implicit sourceInfo: SourceInfo) = - binOp(sourceInfo, fir.PropPrimOp.AddOp, lhs, rhs) + binOp(sourceInfo, fir.IntegerAddOp, lhs, rhs) } // Helper function to create Property expression IR. @@ -385,16 +385,17 @@ object PropertyArithmeticOps { } // Create a temporary Wire to assign the expression to. We currently don't support Nodes for Property types. - val _wire = Wire(chiselTypeOf(lhs)) + val wire = Wire(chiselTypeOf(lhs)) + wire.autoSeed("_propExpr") // Create a PropExpr with the correct type, operation, and operands. val propExpr = ir.PropExpr(sourceInfo, lhs.tpe.getPropertyType(), op, List(lhs.ref, rhs.ref)) // Directly add a PropAssign command assigning the PropExpr to the Wire. - currentModule.addCommand(ir.PropAssign(sourceInfo, _wire.lref, propExpr)) + currentModule.addCommand(ir.PropAssign(sourceInfo, wire.lref, propExpr)) // Return the temporary Wire as the result. - _wire.asInstanceOf[Property[T]] + wire.asInstanceOf[Property[T]] } } diff --git a/firrtl/src/main/scala/firrtl/ir/IR.scala b/firrtl/src/main/scala/firrtl/ir/IR.scala index 2b0e58f168e..edb81707359 100644 --- a/firrtl/src/main/scala/firrtl/ir/IR.scala +++ b/firrtl/src/main/scala/firrtl/ir/IR.scala @@ -259,12 +259,10 @@ case class SequencePropertyValue(tpe: Type, values: Seq[Expression]) extends Exp /** Property primitive operations. */ -sealed trait PropPrimOp -object PropPrimOp { - case object AddOp extends PropPrimOp { - override def toString(): String = "integer_add" - } +sealed abstract class PropPrimOp(name: String) { + override def toString: String = name } +case object IntegerAddOp extends PropPrimOp("integer_add") /** Property expressions. * diff --git a/src/test/scala/chiselTests/properties/PropertySpec.scala b/src/test/scala/chiselTests/properties/PropertySpec.scala index e0c3a354632..f6f0a1048d7 100644 --- a/src/test/scala/chiselTests/properties/PropertySpec.scala +++ b/src/test/scala/chiselTests/properties/PropertySpec.scala @@ -644,14 +644,14 @@ class PropertySpec extends ChiselFlatSpec with MatchesAndOmits { "wire w : Integer", "propassign w, t", "propassign c, t", - "wire _d_WIRE : Integer", - "propassign _d_WIRE, integer_add(t, a)", - "propassign d, _d_WIRE", - "wire _e_WIRE", - "propassign _e_WIRE, integer_add(a, b)", - "wire _e_WIRE_1", - "propassign _e_WIRE_1, integer_add(w, _e_WIRE)", - "propassign e, _e_WIRE_1" + "wire _d_propExpr : Integer", + "propassign _d_propExpr, integer_add(t, a)", + "propassign d, _d_propExpr", + "wire _e_propExpr", + "propassign _e_propExpr, integer_add(a, b)", + "wire _e_propExpr_1", + "propassign _e_propExpr_1, integer_add(w, _e_propExpr)", + "propassign e, _e_propExpr_1" )() } @@ -733,9 +733,9 @@ class PropertySpec extends ChiselFlatSpec with MatchesAndOmits { }) matchesAndOmits(chirrtl)( - "wire _c_WIRE : Integer", - "propassign _c_WIRE, integer_add(a, b)", - "propassign c, _c_WIRE" + "wire _c_propExpr : Integer", + "propassign _c_propExpr, integer_add(a, b)", + "propassign c, _c_propExpr" )() } }