-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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. | ||
* | ||
* 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why did you decide to put so much here about the There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think handle is implemented as its name.
will return a new handle with |
||
* 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) | ||
|
||
|
@@ -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] | ||
{ | ||
|
@@ -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 | ||
|
@@ -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 } | ||
|
@@ -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] | ||
|
@@ -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] | ||
|
@@ -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 } | ||
|
@@ -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)]() | ||
|
@@ -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)]() | ||
|
@@ -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 | ||
|
@@ -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])( | ||
|
@@ -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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 :) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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] | ||
|
@@ -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])( | ||
|
@@ -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])( | ||
|
@@ -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 }) | ||
{ | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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...? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 }) | ||
{ | ||
|
@@ -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) | ||
{ | ||
|
@@ -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) | ||
{ | ||
|
There was a problem hiding this comment.
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?