Skip to content

Commit

Permalink
Add Property expressions, starting with addition. (#3810)
Browse files Browse the repository at this point in the history
This allows Properties to be used to build up expressions in terms of input Properties and literals.
  • Loading branch information
mikeurbach authored Feb 22, 2024
1 parent ff3766b commit 11844a4
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 2 deletions.
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"
)()
}
}

0 comments on commit 11844a4

Please sign in to comment.