Skip to content
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
2 changes: 1 addition & 1 deletion .claude/commands/ir-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -922,7 +922,7 @@ All require `using MemberGetSet`.
```scala
dfVal.getReadDeps // Set[DFValReadDep] — things that read this value
dfVal.getPartialAliases // Set[DFVal.Alias.Partial] — aliases of this value
dfVal.getConnectionTo // Option[DFNet] — single connection driving this value
dfVal.getConnectionsTo // Set[DFNet] — connections driving this value (per-slice; a Dcl may have multiple, one per disjoint bit range)
dfVal.getConnectionsFrom // Set[DFNet] — connections driven from this value
dfVal.getAssignmentsTo // Set[DFVal] — values assigned to this
dfVal.getAssignmentsFrom // Set[DFVal] — values assigned from this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,8 @@ extension (dfVal: DFVal)
// case _ => false
// }
// end hasPrevAlias
def getConnectionTo(using MemberGetSet): Option[DFNet] =
getSet.designDB.connectionTable.getNets(dfVal).headOption
def getConnectionsTo(using MemberGetSet): Set[DFNet] =
getSet.designDB.connectionTable.getNets(dfVal)
def getConnectionsFrom(using MemberGetSet): Set[DFNet] =
getSet.designDB.connectionTableInverted.getOrElse(dfVal, Set())
def getAssignmentsTo(using MemberGetSet): Set[DFVal] =
Expand Down Expand Up @@ -385,7 +385,7 @@ extension (dfVal: DFVal)
def isConstVAR(using MemberGetSet): Boolean =
dfVal match
case dcl @ DclVar() =>
dcl.getAssignmentsTo.isEmpty && dcl.getConnectionTo.isEmpty
dcl.getAssignmentsTo.isEmpty && dcl.getConnectionsTo.isEmpty
case _ => false
def isAllowedMultipleReferences(using MemberGetSet): Boolean = dfVal match
case _ if !dfVal.isAnonymous => true // allow named
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import dfhdl.internals.*
import scala.annotation.tailrec

object StateAnalysis:
@tailrec private def consumeFrom(
private def consumeFrom(
value: DFVal,
slice: Slice,
assignMap: AssignMap,
Expand Down Expand Up @@ -54,31 +54,40 @@ object StateAnalysis:
val rvSet = consumeFrom(relValRef.get, assignMap, currentSet)
val idxSet = consumeFrom(idxRef.get, assignMap, currentSet)
(rvSet union idxSet)
case DFVal.Alias.SelectField(
dfType = dfType: DFStruct,
relValRef = relValRef,
fieldName = fieldName
) =>
// var rbh = dfType.width - 1
// dfType.fieldMap.foreach { case (n, t) =>
// val rbl = rbh - t.width + 1
// if (n == fieldName)
// rbh = rbl - 1
// }
???
case sf @ DFVal.Alias.SelectField(relValRef = relValRef, fieldName = fieldName) =>
// Re-seed the slice to the field's extent in the parent struct's coordinates,
// mirroring the ApplyRange case above.
val relVal = relValRef.get
relVal.dfType match
case structType: DFStruct =>
val low = structType.fieldRelBitLow(fieldName)
val newSlice: Slice = sf.dfType.widthIntOpt match
case Some(w) => Slice.Concrete(Range(low, low + w))
case None => Slice.Unknown
consumeFrom(relVal, newSlice, assignMap, currentSet)
case _ => consumeFrom(relVal, slice, assignMap, currentSet)
case IteratorDcl() => currentSet
// out ports of child designs are not consuming state within the current design
case dcl @ DclOut()
if dcl.getOwnerDesign.isOneLevelBelow(currentBlock.getThisOrOwnerDesign) => currentSet
case dcl: DFVal.Dcl if (dcl.isPortOut || dcl.isVar) && !dcl.isReg && !dcl.isInProcess =>
value.getConnectionTo match
case Some(DFNet.Connection(_, fromVal: DFVal, _)) =>
consumeFrom(fromVal, slice, assignMap, currentSet)
case _ =>
// A declaration may be driven by multiple connections, each covering a
// disjoint bit range. Consume state from every connecting producer, and
// for the portion of `slice` not driven by any connection fall back to the
// assignment-coverage check (an uncovered, unassigned slice still consumes
// its implicit previous value).
value.getConnectionsTo.toList match
case Nil =>
val scope = assignMap(value)
scope.contains(slice, value.dfType.widthIntOpt) match
case Tri.Yes => currentSet
case _ => currentSet + value
case conns =>
conns.foldLeft(currentSet) {
case (cs, DFNet.Connection(_, fromVal: DFVal, _)) =>
consumeFrom(fromVal, slice, assignMap, cs)
case (cs, _) => cs
}
case _ => currentSet
end match
end consumeFrom
Expand Down Expand Up @@ -112,12 +121,12 @@ object StateAnalysis:
case DFVal.Alias.ApplyIdx(relValRef = relValRef, relIdx = idxRef) =>
// for simplification, assigning the entirety of the array
assignTo(relValRef.get, assignMap)
case DFVal.Alias.SelectField(
dfType = dfType: DFStruct,
relValRef = relValRef,
fieldName = fieldName
) =>
???
case DFVal.Alias.SelectField(relValRef = relValRef, fieldName = fieldName) =>
// shift the field-relative slice into the parent struct's coordinates
relValRef.get.dfType match
case structType: DFStruct =>
assignTo(relValRef.get, slice.shift(structType.fieldRelBitLow(fieldName)), assignMap)
case _ => assignTo(relValRef.get, slice, assignMap)
case x => assignMap.assignTo(x, slice)
end match
end assignTo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,10 @@ protected trait DFOwnerPrinter extends AbstractOwnerPrinter:
val design = inst.getDesignBlock
val ports = getSet.designDB.designInstPBNS(inst).view.collect {
case pbns if pbns.isIn =>
val DFNet.Connection(_, from: DFVal, _) = pbns.getConnectionTo.get.runtimeChecked
// the positional def-instance form expects a single producer per input port;
// a piecewise-connected input port (multiple partial nets) cannot be rendered
// here, so we fall back to the first connection's producer.
val DFNet.Connection(_, from: DFVal, _) = pbns.getConnectionsTo.head.runtimeChecked
printer.csDFValRef(from, inst.getOwner)
}.mkString(", ")
val designParamList = csDesignParamList(inst.paramMap)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ trait Printer
lhsOrig match
case dfIfc: DFInterfaceOwner => "<->"
case dfVal: DFVal =>
if (dfVal.getConnectionTo.contains(net) ^ swapLR) "<--"
if (dfVal.getConnectionsTo.contains(net) ^ swapLR) "<--"
else "-->"
val (lhsRef, rhsRef) = if (swapLR) (net.rhsRef, net.lhsRef) else (net.lhsRef, net.rhsRef)
def csNode(ref: DFNet.Ref): String =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,40 +17,49 @@ case object ViaConnection extends HierarchyStage:
val pbnsSkip = mutable.Set.empty[DFVal.PortByNameSelect]
val pbnsGrps =
subDB.designInstPBNS.getOrElse(ib, Nil).groupBy(_.portNamePath).values.toList
pbnsGrps.foreach {
// a single PBNS is connected to no more than a single net
case List(pbns) =>
if (pbns.isOut)
pbns.getConnectionsFrom.headOption match
// already has via connections
case Some(n) if n.isViaConnection => pbnsSkip += pbns
// connected to OPEN, so we skip it from variable creation, but add the net to
// the list of nets to be moved as via connections
case Some(n @ DFNet.Connection(toVal = openVal: DFVal.Special))
if openVal.isOpen =>
pbnsSkip += pbns
nets += n
// already connected to a variable, but without via connection, so we add the net
// to the list of nets to be moved as via connections
case Some(n @ DFNet.Connection(toVal = DclVar())) =>
pbnsSkip += pbns
nets += n
// not connected through a net
case _ => // do nothing
end match
else // input port
pbns.getConnectionTo match
// already has via connections
case Some(n) if n.isViaConnection => pbnsSkip += pbns
// we have a single net that is assigned not more than once (otherwise, for RTL purposes we require another value so an internal multi-assignment rtl variable/reg can be assigned into a signal/wire)
case Some(n @ DFNet.Connection(fromVal = v @ DclVar()))
if v.getAssignmentsTo.isEmpty =>
nets += n
pbnsSkip += pbns
// not connected through a net
case _ => // do nothing
// multiple PBNS (all belong to the same port)
case _ => // do nothing
pbnsGrps.foreach { pbnss =>
// all connection nets driving (input) or driven by (output) this port group.
// a port may be wired by several nets, each covering a disjoint bit range.
val groupNets =
pbnss.flatMap(p => if (p.isOut) p.getConnectionsFrom else p.getConnectionsTo).toSet
// a port already wired through via-connections (possibly several partial ones)
// is left untouched
if (groupNets.nonEmpty && groupNets.forall(_.isViaConnection))
pbnss.foreach(pbnsSkip += _)
else
pbnss match
// a single PBNS that is not already a via-connection
case List(pbns) =>
if (pbns.isOut)
pbns.getConnectionsFrom.headOption match
// connected to OPEN, so we skip it from variable creation, but add the net to
// the list of nets to be moved as via connections
case Some(n @ DFNet.Connection(toVal = openVal: DFVal.Special))
if openVal.isOpen =>
pbnsSkip += pbns
nets += n
// already connected to a variable, but without via connection, so we add the
// net to the list of nets to be moved as via connections
case Some(n @ DFNet.Connection(toVal = DclVar())) =>
pbnsSkip += pbns
nets += n
// not connected through a net
case _ => // do nothing
end match
else // input port
pbns.getConnectionsTo.toList match
// we have a single net that is assigned not more than once (otherwise, for RTL purposes we require another value so an internal multi-assignment rtl variable/reg can be assigned into a signal/wire)
case (n @ DFNet.Connection(fromVal = v @ DclVar())) :: Nil
if v.getAssignmentsTo.isEmpty =>
nets += n
pbnsSkip += pbns
// not connected through a net
case _ => // do nothing
end match
// multiple PBNS (all belong to the same port) that are not via-connected are
// bridged through a synthesized variable
case _ => // do nothing
end match
}

// Meta design to construct the variables to be connected to the ports
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ protected trait VerilogValPrinter extends AbstractValPrinter:
def regOrWireRep = dfVal.modifier.dir match
case Modifier.IN => ""
case _ =>
if (dfVal.getConnectionTo.nonEmpty) "wire "
if (dfVal.getConnectionsTo.nonEmpty) "wire "
else "reg "
val fixedDFTypeStr =
if (supportLogicType) dfTypeStr
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,13 @@ protected trait VHDLValPrinter extends AbstractValPrinter:
def csDFValAliasApplyRange(dfVal: Alias.ApplyRange): String =
dfVal.dfType match
case DFBits(_) | DFUInt(_) | DFSInt(_) =>
s"${dfVal.relValCodeString}(${dfVal.idxHighRef.refCodeString} downto ${dfVal.idxLowRef.refCodeString})"
val slice =
s"${dfVal.relValCodeString}(${dfVal.idxHighRef.refCodeString} downto ${dfVal.idxLowRef.refCodeString})"
// SInt slice now produces DFUInt; wrap with `unsigned(...)` since
// VHDL preserves the signed subtype across slicing.
(dfVal.relValRef.get.dfType, dfVal.dfType) match
case (DFSInt(_), DFUInt(_)) => s"unsigned($slice)"
case _ => slice
case _ =>
s"${dfVal.relValCodeString}(${dfVal.idxLowRef.refCodeString} to ${dfVal.idxHighRef.refCodeString})"
end match
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1943,6 +1943,66 @@ class PrintCodeStringSpec extends StageSpec(stageCreatesUnrefAnons = true):
)
}

test("partial bit and range connections to a single port") {
class Foo extends EDDesign:
val a = Bits(8) <> IN
val y = Bits(8) <> OUT
y(0) <> a(7)
y(7, 1) <> a(6, 0)
end Foo
val top = (new Foo).getCodeString
assertNoDiff(
top,
"""|class Foo extends EDDesign:
| val a = Bits(8) <> IN
| val y = Bits(8) <> OUT
| y(0) <> a(7)
| y(7, 1) <> a(6, 0)
|end Foo""".stripMargin
)
}
test("struct field connections to a single port") {
case class AB(a: Bits[4] <> VAL, b: Bits[4] <> VAL) extends Struct
class Foo extends EDDesign:
val i = AB <> IN
val y = AB <> OUT
y.a <> i.b
y.b <> i.a
end Foo
val top = (new Foo).getCodeString
assertNoDiff(
top,
"""|final case class AB(
| a: Bits[4] <> VAL
| b: Bits[4] <> VAL
|) extends Struct
|
|class Foo extends EDDesign:
| val i = AB <> IN
| val y = AB <> OUT
| y.a <> i.b
| y.b <> i.a
|end Foo""".stripMargin
)
}
test("partial connect and assign mix under RTDesign") {
class Foo extends RTDesign:
val a = Bits(8) <> IN
val y = Bits(8) <> OUT
y(3, 0) <> a(3, 0)
y(7, 4) := a(7, 4)
end Foo
val top = (new Foo).getCodeString
assertNoDiff(
top,
"""|class Foo extends RTDesign:
| val a = Bits(8) <> IN
| val y = Bits(8) <> OUT
| y(3, 0) <> a(3, 0)
| y(7, 4) := a(7, 4)
|end Foo""".stripMargin
)
}
test("different width parameters in nested designs") {
class ID(val WIDTH: Int <> CONST = 10) extends EDDesign:
val x = Bits(WIDTH) <> IN
Expand Down
Loading
Loading