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

WIP: adding scaladoc comments for Diplomacy (Nodes.scala). #2308

Closed
wants to merge 2 commits into from
Closed
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
209 changes: 203 additions & 6 deletions src/main/scala/diplomacy/Nodes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,90 @@ import freechips.rocketchip.util.HeterogeneousBag
import scala.collection.mutable.ListBuffer
import scala.util.matching._

/**
* Concepts, metaphors, and mnemonics to help with understanding Diplomacy code.
*
*
* # Abstract types
*
* Diplomacy is a set of abstractions for describing directed, acyclic graphs
* where parameters may be negotiated between nodes. These abstractions are
* expressed in the form of abstract classes, traits, and type parameters, which
* comprise nearly all of the types defined in this file.
*
* The NodeImp ("node implementation") is the main abstract type that determines
* the type parameters of all other abstract types. Defining a concrete
* implementation of NodeImp will therefore determine concrete types for all
* type parameters. For example, passing in a concrete instance of NodeImp to a
* SourceNode will fully determine concrete types for all of a SourceNode's type
* parameters.
*
* Specific applications of Diplomacy are expected to either extend these types
* or to specify concrete types for the type parameters. This allows for
* creating and associating application-specific node, edge, parameter, and bundle types.
*
* Is an "edge" and a binding the same thing?
*
*
* # Inward/Outward vs. Upward/Downward
*
* Diplomacy defines two dimensions: inward/outward and upward/downward.
*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, is it correct to say that inward/outward refer to the way edges move, and upward/downward refer to the way parameters move? Is this a helpful summary?

* Inward/outward refer to the direction of the directed graph itself. For a
* given node:
* - Inward refers to edges that point into a node.
* - Outward refers to edges that point out of a node.
*
* A useful mnemonic for distinguishing between inward and outward is to always
* view it from the perspective of a particular node. Because Diplomacy
* describes directed, acyclic graphs, this direction will always be consistent
* across the entire graph.
*
* Upward/downward refer to the direction of the parameter negotiation, where
* the direction is relative to the inward/outward direction. For a given edge:
* - Upward refers to a flow of parameters in the outward direction.
* - Downward refers to a flow of parameters in the inward direction.
*
* A useful mnemonic for distinguishing between upward and downward is to imagine
* the diplomatic graph as a literal network of rivers where upward refers to
* parameters that move in the upstream direction while downward refers to
* parameters that move in the downstream direction.
*
*
* # Handles
*
* Two Diplomatic nodes can be bound together using the := operator or one of
* its sibling operators. Binding is asymmetric, and the binding operation will
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you decide to put so much here about the := operator, but not go into :=* and friends? Did you just run out of time or was that a concious division? Later we talk about "ostars" without any intro

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was sort of thinking of mentioning the other operators, but I think that probably goes into a different section that describes the operators in more detail. My primary goal for writing this section was to understand what a Handle is, why it exists, and something to help with why it's specifically named a "handle". I'd also want to run everything I've written here by @terpstra, since everything I wrote is just me trying to "create a theory of handles" from reading the code, and I'm not sure if the design principles I post hoc derived are actually what @terpstra's goals were.

This Handle stuff is only really a concept that you'd need to understand if you were trying to deeply understand the code here, but it's not necessarily something you'd need to know as a user of a library implemented in Diplomacy. For example, anyone using the Diplomatic TileLink framework needs to understand :=, :=*, etc., but they wouldn't need to know anything about a Handle.

For what it's worth, the last part of the code that I got to on the plane while trying to was the four different NodeBinding types and their associated operators, but I had not gotten to the point of actually comprehending the code yet, since the code is recursive, meaning that in order to understand resolveStar(), you need to understand how resolveStar() works on the neighboring nodes. This is a bit challenging when you don't have internet or any prior knowledge on using any of the operators besides the plain "bind-once" := operator.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My primary goal for writing this section was to understand what a Handle is, why it exists, and something to help with why it's specifically named a "handle".

I think handle is implemented as its name.

nodeA := nodeB

will return a new handle with nodeA as outwardNode and nodeB as inwardNode.
If nodeB doesn't have a inwardNode, bind will just return a OutwardNodeHandle which only have a outwardNode nodeA.

* connect the outer side of one node to the inner side of the other.
*
* For example, the expression a := b will connect the outer side of b to the
* inner side of a.
*
* We would like the := operator to have additional properties which make it
* intuitive to use:
*
* 1. It should be chainable, so that a := b := c will have the intuitive effect
* of binding c to b and b to a. This requires that return type of := be the
* same as its arguments, because the result of one := operation must be
* valid as an argument to the other := operation.
*
* 2. It should be associative, so that (a := b) := c is equivalent to a := (b
* := c). This means that the order in which the bind operations execute does
* not matter, even if split across multiple files.
*
* 3. a := b should be allowed if and only if b allows outward edges and a
* allows inward edges. This should be preserved even when chaining
* operations, and it should ideally be enforced at compile-time.
*
* Handles are a way of satisfying all of these properties. A Handle represents
* the aggregation of a chain of Nodes, and it preserves information about
* the connectability of the innermost and the outermost sides of the chain.
*
* If b supports inward edges, then a := b returns a Handle that supports inward
* edges that go into b. If a supports outward edges, then a := b returns a
* Handle that supports outward edges coming out of a.
*/

case object MonitorsEnabled extends Field[Boolean](true)
case object RenderFlipped extends Field[Boolean](false)

Expand Down Expand Up @@ -49,10 +133,21 @@ trait OutwardNodeImp[DO, UO, EO, BO <: Data]
def getI(pd: DO): Option[BaseNode] = None // most-inward common node
}

/**
* A node implementation.
*
* This class has no members and is solely used for holding type information.
* Applications of Diplomacy should extend NodeImp with a case object that sets
* concrete type arguments.
*/
abstract class NodeImp[D, U, EO, EI, B <: Data]
extends Object with InwardNodeImp[D, U, EI, B] with OutwardNodeImp[D, U, EO, B]

// If your edges have the same direction, using this saves you some typing
/**
* A NodeImp where the inward and outward edges are of the same type.
*
* If your edges have the same type in both directions, using this saves you some typing.
*/
abstract class SimpleNodeImp[D, U, E, B <: Data]
extends NodeImp[D, U, E, E, B]
{
Expand All @@ -64,6 +159,9 @@ abstract class SimpleNodeImp[D, U, E, B <: Data]
def bundleI(e: E) = bundle(e)
}

/**
* The base Node type at the top of the Node type hierarchy.
*/
abstract class BaseNode(implicit val valName: ValName)
{
val scope = LazyModule.scope
Expand Down Expand Up @@ -125,18 +223,48 @@ trait FormatNode[I <: FormatEdge, O <: FormatEdge] extends BaseNode {
}
}

/**
* A Handle with no explicitly defined binding functionality.
*
* A NoHandle is at the top of the Handle type hierarchy, but it does not define
* any binding operators, so by itself a NoHandle cannot be used on either side
* of a bind operator.
*
* The other Handle types extend this type and bestow actual binding semantics.
* They can always be used in whenever a NoHandle is expected because a NoHandle
* doesn't provide any guaranteed behavior.
*
* Handle algebra:
*
* x---x = NoHandle
* x---< = InwardNodeHandle
* <---x = OutwardNodeHandle
* <---< = (Full) NodeHandle
*
* < = can be bound to (arrow points in the direction of binding)
* x = cannot be bound to
*
* left side is outer, right side is inner
*
* Two Handles can be bound if their adjacent ends are both <.
*/
trait NoHandle
case object NoHandleObject extends NoHandle

/**
* A Handle that can be used on either side of a bind operator.
*/
trait NodeHandle[DI, UI, EI, BI <: Data, DO, UO, EO, BO <: Data]
extends InwardNodeHandle[DI, UI, EI, BI] with OutwardNodeHandle[DO, UO, EO, BO]
{
// connecting two full nodes => full node
// connecting two full nodes handles => full node handle
// <---< := <---< == <---<
override def := [DX, UX, EX, BX <: Data, EY](h: NodeHandle[DX, UX, EX, BX, DI, UI, EY, BI])(implicit p: Parameters, sourceInfo: SourceInfo): NodeHandle[DX, UX, EX, BX, DO, UO, EO, BO] = { bind(h, BIND_ONCE); NodeHandle(h, this) }
override def :*= [DX, UX, EX, BX <: Data, EY](h: NodeHandle[DX, UX, EX, BX, DI, UI, EY, BI])(implicit p: Parameters, sourceInfo: SourceInfo): NodeHandle[DX, UX, EX, BX, DO, UO, EO, BO] = { bind(h, BIND_STAR); NodeHandle(h, this) }
override def :=* [DX, UX, EX, BX <: Data, EY](h: NodeHandle[DX, UX, EX, BX, DI, UI, EY, BI])(implicit p: Parameters, sourceInfo: SourceInfo): NodeHandle[DX, UX, EX, BX, DO, UO, EO, BO] = { bind(h, BIND_QUERY); NodeHandle(h, this) }
override def :*=*[DX, UX, EX, BX <: Data, EY](h: NodeHandle[DX, UX, EX, BX, DI, UI, EY, BI])(implicit p: Parameters, sourceInfo: SourceInfo): NodeHandle[DX, UX, EX, BX, DO, UO, EO, BO] = { bind(h, BIND_FLEX); NodeHandle(h, this) }
// connecting a full node with an output => an output
// <---< := <---x == <---x
override def := [EY](h: OutwardNodeHandle[DI, UI, EY, BI])(implicit p: Parameters, sourceInfo: SourceInfo): OutwardNodeHandle[DO, UO, EO, BO] = { bind(h, BIND_ONCE); this }
override def :*= [EY](h: OutwardNodeHandle[DI, UI, EY, BI])(implicit p: Parameters, sourceInfo: SourceInfo): OutwardNodeHandle[DO, UO, EO, BO] = { bind(h, BIND_STAR); this }
override def :=* [EY](h: OutwardNodeHandle[DI, UI, EY, BI])(implicit p: Parameters, sourceInfo: SourceInfo): OutwardNodeHandle[DO, UO, EO, BO] = { bind(h, BIND_QUERY); this }
Expand All @@ -148,6 +276,10 @@ object NodeHandle
def apply[DI, UI, EI, BI <: Data, DO, UO, EO, BO <: Data](i: InwardNodeHandle[DI, UI, EI, BI], o: OutwardNodeHandle[DO, UO, EO, BO]) = new NodeHandlePair(i, o)
}

/**
* A data structure that preserves information about the innermost and outermost
* Nodes in a NodeHandle.
*/
class NodeHandlePair[DI, UI, EI, BI <: Data, DO, UO, EO, BO <: Data]
(inwardHandle: InwardNodeHandle[DI, UI, EI, BI], outwardHandle: OutwardNodeHandle[DO, UO, EO, BO])
extends NodeHandle[DI, UI, EI, BI, DO, UO, EO, BO]
Expand All @@ -158,6 +290,10 @@ class NodeHandlePair[DI, UI, EI, BI <: Data, DO, UO, EO, BO <: Data]
def outer = outwardHandle.outer
}

/**
* A Handle for an InwardNode, which may appear on the left side of a bind
* operator.
*/
trait InwardNodeHandle[DI, UI, EI, BI <: Data] extends NoHandle
{
def inward: InwardNode[DI, UI, BI]
Expand All @@ -166,11 +302,13 @@ trait InwardNodeHandle[DI, UI, EI, BI <: Data] extends NoHandle
protected def bind[EY](h: OutwardNodeHandle[DI, UI, EY, BI], binding: NodeBinding)(implicit p: Parameters, sourceInfo: SourceInfo): Unit = inward.bind(h.outward, binding)

// connecting an input node with a full nodes => an input node
// x---< := <---< == x---<
def := [DX, UX, EX, BX <: Data, EY](h: NodeHandle[DX, UX, EX, BX, DI, UI, EY, BI])(implicit p: Parameters, sourceInfo: SourceInfo): InwardNodeHandle[DX, UX, EX, BX] = { bind(h, BIND_ONCE); h }
def :*= [DX, UX, EX, BX <: Data, EY](h: NodeHandle[DX, UX, EX, BX, DI, UI, EY, BI])(implicit p: Parameters, sourceInfo: SourceInfo): InwardNodeHandle[DX, UX, EX, BX] = { bind(h, BIND_STAR); h }
def :=* [DX, UX, EX, BX <: Data, EY](h: NodeHandle[DX, UX, EX, BX, DI, UI, EY, BI])(implicit p: Parameters, sourceInfo: SourceInfo): InwardNodeHandle[DX, UX, EX, BX] = { bind(h, BIND_QUERY); h }
def :*=*[DX, UX, EX, BX <: Data, EY](h: NodeHandle[DX, UX, EX, BX, DI, UI, EY, BI])(implicit p: Parameters, sourceInfo: SourceInfo): InwardNodeHandle[DX, UX, EX, BX] = { bind(h, BIND_FLEX); h }
// connecting input node with output node => no node
// x---< := <---x == x---x
def := [EY](h: OutwardNodeHandle[DI, UI, EY, BI])(implicit p: Parameters, sourceInfo: SourceInfo): NoHandle = { bind(h, BIND_ONCE); NoHandleObject }
def :*= [EY](h: OutwardNodeHandle[DI, UI, EY, BI])(implicit p: Parameters, sourceInfo: SourceInfo): NoHandle = { bind(h, BIND_STAR); NoHandleObject }
def :=* [EY](h: OutwardNodeHandle[DI, UI, EY, BI])(implicit p: Parameters, sourceInfo: SourceInfo): NoHandle = { bind(h, BIND_QUERY); NoHandleObject }
Expand All @@ -183,6 +321,10 @@ case object BIND_QUERY extends NodeBinding
case object BIND_STAR extends NodeBinding
case object BIND_FLEX extends NodeBinding

/**
* A Node that defines inward behavior, meaning that it can have edges coming
* into it.
*/
trait InwardNode[DI, UI, BI <: Data] extends BaseNode
{
private val accPI = ListBuffer[(Int, OutwardNode[DI, UI, BI], NodeBinding, Parameters, SourceInfo)]()
Expand All @@ -206,12 +348,20 @@ trait InwardNode[DI, UI, BI <: Data] extends BaseNode
protected[diplomacy] def bind(h: OutwardNode[DI, UI, BI], binding: NodeBinding)(implicit p: Parameters, sourceInfo: SourceInfo): Unit
}

/**
* A Handle for OutwardNodes, which may appear on the right side of a bind
* operator.
*/
trait OutwardNodeHandle[DO, UO, EO, BO <: Data] extends NoHandle
{
def outward: OutwardNode[DO, UO, BO]
def outer: OutwardNodeImp[DO, UO, EO, BO]
}

/**
* A Node that defines outward behavior, meaning that it can have edges coming
* out of it.
*/
trait OutwardNode[DO, UO, BO <: Data] extends BaseNode
{
private val accPO = ListBuffer[(Int, InwardNode [DO, UO, BO], NodeBinding, Parameters, SourceInfo)]()
Expand All @@ -224,6 +374,19 @@ trait OutwardNode[DO, UO, BO <: Data] extends BaseNode
accPO += ((index, node, binding, p, sourceInfo))
}

/**
* The frozen list of outward bindings on this Node.
*
* Evaluating this lazy val will mark the outward bindings as frozen,
* preventing subsequent bindings from being created.
*
* The bindings are each a tuple of:
* - numeric index of this binding
* - InwardNode on the other end of this binding
* - NodeBinding describing the type of binding
* - a Parameters instance
* - SourceInfo for source-level error reporting
*/
protected[diplomacy] lazy val oBindings = { oRealized = true; accPO.result() }

protected[diplomacy] val oStar: Int
Expand All @@ -239,6 +402,10 @@ case class DownwardCycleException(loop: Seq[String] = Nil) extends CycleExceptio
case class UpwardCycleException(loop: Seq[String] = Nil) extends CycleException("upward", loop)

case class Edges[EI, EO](in: Seq[EI], out: Seq[EO])

/**
* A Node that may be a mix of different NodeImps between inward and outward directions.
*/
sealed abstract class MixedNode[DI, UI, EI, BI <: Data, DO, UO, EO, BO <: Data](
val inner: InwardNodeImp [DI, UI, EI, BI],
val outer: OutwardNodeImp[DO, UO, EO, BO])(
Expand All @@ -248,6 +415,13 @@ sealed abstract class MixedNode[DI, UI, EI, BI <: Data, DO, UO, EO, BO <: Data](
val inward = this
val outward = this

/**
* Given counts of known inward and outward binding and inward and outward
* star bindings, return the resolved inward stars and outward stars.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is a "star binding"? We never talked about it. Not sure if that was intentional vs running out of time to type :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a running out of time (or really, the passing out due to being awake for 20 hours) thing. This is where I got to when I hit the "Understanding NodeBindings and running into a recursive block" described above.

*
* This method will also validate the arguments and throw a runtime error if
* the values are unsuitable for this type of node.
*/
protected[diplomacy] def resolveStar(iKnown: Int, oKnown: Int, iStar: Int, oStar: Int): (Int, Int)
protected[diplomacy] def mapParamsD(n: Int, p: Seq[DI]): Seq[DO]
protected[diplomacy] def mapParamsU(n: Int, p: Seq[UO]): Seq[UI]
Expand Down Expand Up @@ -435,6 +609,9 @@ sealed abstract class MixedNode[DI, UI, EI, BI <: Data, DO, UO, EO, BO <: Data](
def outputs = oPorts map { case (i, n, _, _) => (n, n.inputs(i)._2) }
}

/**
* A MixedNode that may be extended with custom behavior.
*/
abstract class MixedCustomNode[DI, UI, EI, BI <: Data, DO, UO, EO, BO <: Data](
inner: InwardNodeImp [DI, UI, EI, BI],
outer: OutwardNodeImp[DO, UO, EO, BO])(
Expand All @@ -447,10 +624,18 @@ abstract class MixedCustomNode[DI, UI, EI, BI <: Data, DO, UO, EO, BO <: Data](
def mapParamsU(n: Int, p: Seq[UO]): Seq[UI]
}

/**
* A Node that may be extended with custom behavior.
*
* Different from a MixedNode in that the inner and outer NodeImps are the same.
*/
abstract class CustomNode[D, U, EO, EI, B <: Data](imp: NodeImp[D, U, EO, EI, B])(
implicit valName: ValName)
extends MixedCustomNode(imp, imp)

/**
* A node that can connect multiple inward edges of one NodeImp to multiple outward edges of a different NodeImp
*/
class MixedJunctionNode[DI, UI, EI, BI <: Data, DO, UO, EO, BO <: Data](
inner: InwardNodeImp [DI, UI, EI, BI],
outer: OutwardNodeImp[DO, UO, EO, BO])(
Expand Down Expand Up @@ -547,7 +732,9 @@ class AdapterNode[D, U, EO, EI, B <: Data](imp: NodeImp[D, U, EO, EI, B])(
implicit valName: ValName)
extends MixedAdapterNode[D, U, EI, B, D, U, EO, B](imp, imp)(dFn, uFn)

// IdentityNodes automatically connect their inputs to outputs
/**
* IdentityNodes automatically connect their inputs to outputs
*/
class IdentityNode[D, U, EO, EI, B <: Data](imp: NodeImp[D, U, EO, EI, B])()(implicit valName: ValName)
extends AdapterNode(imp)({ s => s }, { s => s })
{
Expand All @@ -560,7 +747,9 @@ class IdentityNode[D, U, EO, EI, B <: Data](imp: NodeImp[D, U, EO, EI, B])()(imp
}
}

// EphemeralNodes disappear from the final node graph
/**
* EphemeralNodes disappear from the final node graph
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i never understood what this meant. So you put them beween two nodes and then it's like they were never there...?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that I didn't touch this at all besides changing the plain comment to a Scaladoc comment. I have not yet gotten to a point where I understand what an EphemeralNode is or what it should be used for.

*/
class EphemeralNode[D, U, EO, EI, B <: Data](imp: NodeImp[D, U, EO, EI, B])()(implicit valName: ValName)
extends AdapterNode(imp)({ s => s }, { s => s })
{
Expand Down Expand Up @@ -601,7 +790,11 @@ class NexusNode[D, U, EO, EI, B <: Data](imp: NodeImp[D, U, EO, EI, B])(
implicit valName: ValName)
extends MixedNexusNode[D, U, EI, B, D, U, EO, B](imp, imp)(dFn, uFn, inputRequiresOutput, outputRequiresInput)

// There are no Mixed SourceNodes
/**
* A Node with no inward edges.
*
* There are no Mixed SourceNodes because there is no other direction to mix.
*/
class SourceNode[D, U, EO, EI, B <: Data](imp: NodeImp[D, U, EO, EI, B])(po: Seq[D])(implicit valName: ValName)
extends MixedNode(imp, imp)
{
Expand All @@ -626,7 +819,11 @@ class SourceNode[D, U, EO, EI, B <: Data](imp: NodeImp[D, U, EO, EI, B])(po: Seq
}
}

// There are no Mixed SinkNodes
/**
* A Node with no outward edges.
*
* There are no Mixed SinkNodes because there is no other direction to mix.
*/
class SinkNode[D, U, EO, EI, B <: Data](imp: NodeImp[D, U, EO, EI, B])(pi: Seq[U])(implicit valName: ValName)
extends MixedNode(imp, imp)
{
Expand Down