Skip to content

Commit

Permalink
Support connecting Properties.
Browse files Browse the repository at this point in the history
  • Loading branch information
mikeurbach committed Aug 10, 2023
1 parent 71209b4 commit d156c1d
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 21 deletions.
32 changes: 16 additions & 16 deletions core/src/main/scala/chisel3/Data.scala
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,22 @@ trait BaseType extends HasId with NamedComponent {
private[chisel3] def earlyName: String = Arg.earlyLocalName(this)

private[chisel3] def parentNameOpt: Option[String] = this._parent.map(_.name)

private[chisel3] def requireVisible(): Unit = {
val mod = topBindingOpt.flatMap(_.location)
topBindingOpt match {
case Some(tb: TopBinding) if (mod == Builder.currentModule) =>
case Some(pb: PortBinding)
if (mod.flatMap(Builder.retrieveParent(_, Builder.currentModule.get)) == Builder.currentModule) =>
case Some(pb: SecretPortBinding) => // Ignore secret to not require visibility
case Some(_: UnconstrainedBinding) =>
case _ =>
throwException(s"operand '$this' is not visible from the current module ${Builder.currentModule.get.name}")
}
if (!MonoConnect.checkWhenVisibility(this)) {
throwException(s"operand has escaped the scope of the when in which it was constructed")
}
}
}

/** This forms the root of the type system for wire data types. The data value
Expand Down Expand Up @@ -579,22 +595,6 @@ abstract class Data extends BaseType with SourceInfoDoc {
rec(leftType, rightType)
}

private[chisel3] def requireVisible(): Unit = {
val mod = topBindingOpt.flatMap(_.location)
topBindingOpt match {
case Some(tb: TopBinding) if (mod == Builder.currentModule) =>
case Some(pb: PortBinding)
if (mod.flatMap(Builder.retrieveParent(_, Builder.currentModule.get)) == Builder.currentModule) =>
case Some(pb: SecretPortBinding) => // Ignore secret to not require visibility
case Some(_: UnconstrainedBinding) =>
case _ =>
throwException(s"operand '$this' is not visible from the current module ${Builder.currentModule.get.name}")
}
if (!MonoConnect.checkWhenVisibility(this)) {
throwException(s"operand has escaped the scope of the when in which it was constructed")
}
}

// Internal API: returns a ref that can be assigned to, if consistent with the binding
private[chisel3] def lref: Node = {
requireIsHardware(this)
Expand Down
10 changes: 10 additions & 0 deletions core/src/main/scala/chisel3/internal/MonoConnect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import chisel3.internal.containsProbe
import chisel3.internal.Builder.pushCommand
import chisel3.internal.firrtl.{Connect, Converter, DefInvalid}
import chisel3.experimental.dataview.{isView, reify, reifyToAggregate}
import chisel3.properties.Property

import scala.language.experimental.macros
import scala.annotation.tailrec
Expand Down Expand Up @@ -412,6 +413,15 @@ private[chisel3] object checkConnect {
checkConnection(sourceInfo, sink, source, context_mod)
}

def apply[T](
sourceInfo: SourceInfo,
sink: Property[T],
source: Property[T],
context_mod: RawModule
): Unit = {
checkConnection(sourceInfo, sink, source, context_mod)
}

private def checkConnection(
sourceInfo: SourceInfo,
sink: BaseType,
Expand Down
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 @@ -159,6 +159,8 @@ private[chisel3] object Converter {
)
case Connect(info, loc, exp) =>
Some(fir.Connect(convert(info), convert(loc, ctx, info), convert(exp, ctx, info)))
case PropAssign(info, loc, exp) =>
Some(fir.PropAssign(convert(info), convert(loc, ctx, info), convert(exp, ctx, info)))
case Attach(info, locs) =>
Some(fir.Attach(convert(info), locs.map(l => convert(l, ctx, info))))
case DefInvalid(info, arg) =>
Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala/chisel3/internal/firrtl/IR.scala
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ case class AltBegin(sourceInfo: SourceInfo) extends Command
case class OtherwiseEnd(sourceInfo: SourceInfo, firrtlDepth: Int) extends Command
@deprecated(deprecatedPublicAPIMsg, "Chisel 3.6")
case class Connect(sourceInfo: SourceInfo, loc: Node, exp: Arg) extends Command
private[chisel3 ]case class PropAssign(sourceInfo: SourceInfo, loc: Node, exp: Arg) extends Command
@deprecated(deprecatedPublicAPIMsg, "Chisel 3.6")
case class Attach(sourceInfo: SourceInfo, locs: Seq[Node]) extends Command
@deprecated(deprecatedPublicAPIMsg, "Chisel 3.6")
Expand Down
60 changes: 58 additions & 2 deletions core/src/main/scala/chisel3/properties/Property.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

package chisel3.properties

import chisel3.{ActualDirection, BaseType, SpecifiedDirection}
import chisel3.internal.Binding
import chisel3.{ActualDirection, BaseType, MonoConnectException, SpecifiedDirection}
import chisel3.internal.{checkConnect, throwException, Binding, Builder, MonoConnect, ReadOnlyBinding, TopBinding}
import chisel3.internal.{firrtl => ir}
import chisel3.experimental.{prefix, requireIsHardware, SourceInfo}
import scala.reflect.runtime.universe.{typeOf, TypeTag}
import scala.annotation.implicitNotFound

Expand Down Expand Up @@ -80,6 +81,61 @@ class Property[T: PropertyType] extends BaseType {
private[chisel3] def getPropertyType: ir.PropertyType = {
implicitly[PropertyType[T]].getPropertyType
}

/** Connect a source Property[T] to this sink Property[T]
*/
def :=(source: => Property[T])(implicit sourceInfo: SourceInfo): Unit = {
prefix(this) {
this.connect(source)(sourceInfo)
}
}

/** Internal implementation of connecting a source Property[T] to this sink Property[T].
*/
private def connect(source: Property[T])(implicit sourceInfo: SourceInfo): Unit = {
requireIsHardware(this, "property to be connected to")
requireIsHardware(source, "property to be connected from")
this.topBinding match {
case _: ReadOnlyBinding => throwException(s"Cannot reassign to read-only $this")
case _ => // fine
}

try {
checkConnect(sourceInfo, this, source, Builder.referenceUserModule)
} catch {
case MonoConnectException(message) =>
throwException(
s"Connection between sink ($this) and source ($source) failed @: $message"
)
}

Builder.pushCommand(ir.PropAssign(sourceInfo, this.lref, source.ref))
}

/** Internal API: returns a ref that can be assigned to, if consistent with the binding.
*/
private[chisel3] def lref: ir.Node = {
requireIsHardware(this)
requireVisible()
topBindingOpt match {
case Some(binding: ReadOnlyBinding) =>
throwException(s"internal error: attempted to generate LHS ref to ReadOnlyBinding $binding")
case Some(binding: TopBinding) => ir.Node(this)
case opt => throwException(s"internal error: unknown binding $opt in generating LHS ref")
}
}

/** Internal API: returns a ref, if bound.
*/
private[chisel3] final def ref: ir.Arg = {
requireIsHardware(this)
requireVisible()
topBindingOpt match {
case Some(binding: TopBinding) => ir.Node(this)
case opt => throwException(s"internal error: unknown binding $opt in generating RHS ref")
}
}

}

/** Companion object for Property.
Expand Down
20 changes: 19 additions & 1 deletion docs/src/explanations/properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,26 @@ constructs.
The legal `Property` types may be used in ports. For example:

```scala mdoc:silent
class Example extends RawModule {
class PortsExample extends RawModule {
// An Int Property type port.
val myPort = IO(Input(Property[Int]()))
}
```

### Property Connections

The legal `Property` types may be connected using the `:=` operator. For
example, an input `Property` type port may be connected to an output `Property`
type port:

```scala mdoc:silent
class ConnectExample extends RawModule {
val inPort = IO(Input(Property[Int]()))
val outPort = IO(Output(Property[Int]()))
outPort := inPort
}
```

Connections are only supported between the same `Property` type. For example, a
`Property[Int]` may only be connected to a `Property[Int]`. This is enforced by
the Scala compiler.
1 change: 1 addition & 0 deletions firrtl/src/main/scala/firrtl/ir/IR.scala
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ object Block {

case class Block(stmts: Seq[Statement]) extends Statement with UseSerializer
case class Connect(info: Info, loc: Expression, expr: Expression) extends Statement with HasInfo with UseSerializer
case class PropAssign(info: Info, loc: Expression, expr: Expression) extends Statement with HasInfo with UseSerializer
case class IsInvalid(info: Info, expr: Expression) extends Statement with HasInfo with UseSerializer
case class Attach(info: Info, exprs: Seq[Expression]) extends Statement with HasInfo with UseSerializer

Expand Down
5 changes: 3 additions & 2 deletions firrtl/src/main/scala/firrtl/ir/Serializer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,9 @@ object Serializer {
}

private def s(node: Statement)(implicit b: StringBuilder, indent: Int): Unit = node match {
case DefNode(info, name, value) => b ++= "node "; b ++= legalize(name); b ++= " = "; s(value); s(info)
case Connect(info, loc, expr) => b ++= "connect "; s(loc); b ++= ", "; s(expr); s(info)
case DefNode(info, name, value) => b ++= "node "; b ++= legalize(name); b ++= " = "; s(value); s(info)
case Connect(info, loc, expr) => b ++= "connect "; s(loc); b ++= ", "; s(expr); s(info)
case PropAssign(info, loc, expr) => b ++= "propassign "; s(loc); b ++= ", "; s(expr); s(info)
case c: Conditionally => b ++= sIt(c).mkString
case EmptyStmt => b ++= "skip"
case bb: Block => b ++= sIt(bb).mkString
Expand Down
22 changes: 22 additions & 0 deletions src/test/scala/chiselTests/properties/PropertySpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,26 @@ class PropertySpec extends ChiselFlatSpec with MatchesAndOmits {
"input bigIntProp : Integer"
)()
}

it should "support connecting Property types of the same type" in {
val chirrtl = ChiselStage.emitCHIRRTL(new RawModule {
val propIn = IO(Input(Property[Int]()))
val propOut = IO(Output(Property[Int]()))
propOut := propIn
})

matchesAndOmits(chirrtl)(
"propassign propOut, propIn"
)()
}

it should "fail to compile when connecting Property types of different types" in {
assertTypeError("""
new RawModule {
val propIn = IO(Input(Property[Int]()))
val propOut = IO(Output(Property[BigInt]()))
propOut := propIn
}
""")
}
}

0 comments on commit d156c1d

Please sign in to comment.