diff --git a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala index b170053f5e2..5dbfbac5bef 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..5e99ed31524 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -186,6 +186,18 @@ 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: 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") + } + 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..ece3cca0b25 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} @@ -228,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.") @@ -320,6 +321,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] { @@ -335,6 +338,67 @@ private[chisel3] object ClassTypeProvider { } } +/** Typeclass for Property arithmetic. + */ +@implicitNotFound("arithmetic operations are not supported on Property type ${T}") +sealed 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.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.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.IntegerAddOp, lhs, rhs) + } + + // Helper function to create Property expression IR. + private def binOp[T: PropertyType]( + sourceInfo: SourceInfo, + op: fir.PropPrimOp, + lhs: Property[T], + rhs: Property[T] + ): Property[T] = { + implicit val info = sourceInfo + + // 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}") + ) + } + + // Create a temporary Wire to assign the expression to. We currently don't support Nodes for Property types. + 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)) + + // Return the temporary Wire as the result. + wire.asInstanceOf[Property[T]] + } +} + /** Companion object for Property. */ object Property { 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. | diff --git a/firrtl/src/main/scala/firrtl/ir/IR.scala b/firrtl/src/main/scala/firrtl/ir/IR.scala index 0b6923a4c0c..edb81707359 100644 --- a/firrtl/src/main/scala/firrtl/ir/IR.scala +++ b/firrtl/src/main/scala/firrtl/ir/IR.scala @@ -257,6 +257,27 @@ 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 abstract class PropPrimOp(name: String) { + override def toString: String = name +} +case object IntegerAddOp extends PropPrimOp("integer_add") + +/** 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 diff --git a/src/test/scala/chiselTests/properties/PropertySpec.scala b/src/test/scala/chiselTests/properties/PropertySpec.scala index ccd90815df0..f6f0a1048d7 100644 --- a/src/test/scala/chiselTests/properties/PropertySpec.scala +++ b/src/test/scala/chiselTests/properties/PropertySpec.scala @@ -3,11 +3,12 @@ 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 import chisel3.properties.AnyClassType +import chisel3.util.experimental.BoringUtils class PropertySpec extends ChiselFlatSpec with MatchesAndOmits { behavior.of("Property") @@ -617,4 +618,124 @@ 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)( + "wire t : Integer", + "propassign t, integer_add(a, b)", + "wire w : Integer", + "propassign w, t", + "propassign c, t", + "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" + )() + } + + 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 c_bore, c", + "propassign c, child.c_bore" + )() + } + + 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 "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]())) + val b = IO(Input(Property[BigInt]())) + val c = IO(Output(Property[BigInt]())) + c := a + b + }) + + matchesAndOmits(chirrtl)( + "wire _c_propExpr : Integer", + "propassign _c_propExpr, integer_add(a, b)", + "propassign c, _c_propExpr" + )() + } }