Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Property expressions, starting with addition. #3810

Merged
merged 11 commits into from
Feb 22, 2024
Merged
2 changes: 2 additions & 0 deletions core/src/main/scala/chisel3/internal/firrtl/Converter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
12 changes: 12 additions & 0 deletions core/src/main/scala/chisel3/internal/firrtl/IR.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
66 changes: 65 additions & 1 deletion core/src/main/scala/chisel3/properties/Property.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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.")
Expand Down Expand Up @@ -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] {
Expand All @@ -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 {
Expand Down
32 changes: 32 additions & 0 deletions docs/src/explanations/properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
21 changes: 21 additions & 0 deletions firrtl/src/main/scala/firrtl/ir/IR.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
123 changes: 122 additions & 1 deletion src/test/scala/chiselTests/properties/PropertySpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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"
)()
}
}
Loading