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

Support connecting Properties. #3481

Merged
merged 1 commit into from
Aug 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
""")
}
}
Loading