diff --git a/compiler/src/main/scala/edg/ElemBuilder.scala b/compiler/src/main/scala/edg/ElemBuilder.scala index e21780f8a..ceb9e7c8f 100644 --- a/compiler/src/main/scala/edg/ElemBuilder.scala +++ b/compiler/src/main/scala/edg/ElemBuilder.scala @@ -44,23 +44,27 @@ object ElemBuilder { def Exported( external: ref.LocalPath, internal: ref.LocalPath, - expanded: Seq[expr.ValueExpr] = Seq() + expanded: Seq[expr.ValueExpr] = Seq(), + tap: Boolean = false ): expr.ValueExpr = expr.ValueExpr( expr = expr.ValueExpr.Expr.Exported(expr.ExportedExpr( exteriorPort = Some(ValueExpr.Ref(external)), internalBlockPort = Some(ValueExpr.Ref(internal)), - expanded = expanded.map(_.getExported) + expanded = expanded.map(_.getExported), + tap = tap )) ) def ExportedArray( external: ref.LocalPath, internal: ref.LocalPath, - expanded: Seq[expr.ValueExpr] = Seq() + expanded: Seq[expr.ValueExpr] = Seq(), + tap: Boolean = false ): expr.ValueExpr = expr.ValueExpr( expr = expr.ValueExpr.Expr.ExportedArray(expr.ExportedExpr( exteriorPort = Some(ValueExpr.Ref(external)), internalBlockPort = Some(ValueExpr.Ref(internal)), - expanded = expanded.map(_.getExported) + expanded = expanded.map(_.getExported), + tap = tap )) ) // variation for map_extract diff --git a/compiler/src/main/scala/edg/compiler/Compiler.scala b/compiler/src/main/scala/edg/compiler/Compiler.scala index 59b2058a0..7de306945 100644 --- a/compiler/src/main/scala/edg/compiler/Compiler.scala +++ b/compiler/src/main/scala/edg/compiler/Compiler.scala @@ -37,7 +37,7 @@ object ElaborateRecord { // Connection to be elaborated, to set port parameter, IS_CONNECTED, and CONNECTED_LINK equivalences. // Only elaborates the direct connect, and for bundles, creates sub-Connect tasks since it needs // connectedLink and linkParams. - case class Connect(toLinkPortPath: DesignPath, toBlockPortPath: DesignPath, root: DesignPath) + case class Connect(toLinkPortPath: DesignPath, toBlockPortPath: DesignPath, root: DesignPath, tap: Boolean = false) extends ElaborateTask // Elaborates the contents of a port array, based on the port array's ELEMENTS parameter. @@ -117,7 +117,7 @@ class AssignNamer() { } object Compiler { - final val kExpectedProtoVersion = 11 + final val kExpectedProtoVersion = 12 } /** Compiler for a particular design, with an associated library to elaborate references from. @@ -279,12 +279,14 @@ class Compiler private ( val toLinkPort = resolvePort(connect.toLinkPortPath).asInstanceOf[wir.HasParams] val connectedParam = toLinkPort.getParams.keys.map(IndirectStep.Element(_)) for (connectedStep <- connectedParam) { // note: can't happen for top level connect! - constProp.addAssignEqual( - connect.toLinkPortPath.asIndirect + connectedStep, - connect.toBlockPortPath.asIndirect + connectedStep, - connect.root, - "connect" - ) + if (!connect.tap) { // tap is non-propagating + constProp.addAssignEqual( + connect.toLinkPortPath.asIndirect + connectedStep, + connect.toBlockPortPath.asIndirect + connectedStep, + connect.root, + "connect" + ) + } } // Add sub-ports to the elaboration dependency graph, as appropriate @@ -295,7 +297,8 @@ class Compiler private ( ElaborateRecord.Connect( connect.toLinkPortPath + portName, connect.toBlockPortPath + portName, - connect.root + connect.root, + connect.tap ), Seq() ) @@ -499,11 +502,12 @@ class Compiler private ( case (ValueExpr.Ref(extPort), ValueExpr.Ref(intPort)) => if (!isInLink) { elaboratePending.addNode( - ElaborateRecord.Connect(blockPath ++ extPort, blockPath ++ intPort, blockPath), + ElaborateRecord.Connect(blockPath ++ extPort, blockPath ++ intPort, blockPath, tap = exported.tap), Seq(ElaborateRecord.Port(blockPath ++ extPort)) ) constProp.setConnection(blockPath ++ extPort, blockPath ++ intPort) } else { // for links, the internal port is towards the inner link, so the args are flipped + require(!exported.tap, "tap not allowed in links") elaboratePending.addNode( ElaborateRecord.Connect(blockPath ++ intPort, blockPath ++ extPort, blockPath), Seq(ElaborateRecord.Port(blockPath ++ intPort)) @@ -514,6 +518,7 @@ class Compiler private ( case _ => false // anything with allocates is not processed } case expr.ValueExpr.Expr.ExportedTunnel(exported) => + require(!exported.tap, "tap not allowed in tunnel") (exported.getExteriorPort, exported.getInternalBlockPort) match { case (ValueExpr.Ref(extPort), ValueExpr.Ref(intPort)) => require(!isInLink) @@ -845,12 +850,14 @@ class Compiler private ( case expr.ValueExpr.Expr.ExportedArray(exported) => // note internal port is portPostfix val ValueExpr.Ref(extPostfix) = exported.getExteriorPort - constProp.addAssignEqual( - path.asIndirect ++ extPostfix + IndirectStep.Elements, - path.asIndirect ++ portPostfix + IndirectStep.Elements, - path, - constrName - ) + if (!exported.tap) { // elements do not propagate in tap case, but are checked to be equal + constProp.addAssignEqual( + path.asIndirect ++ extPostfix + IndirectStep.Elements, + path.asIndirect ++ portPostfix + IndirectStep.Elements, + path, + constrName + ) + } constProp.addAssignEqual( path.asIndirect ++ portPostfix + IndirectStep.Allocated, path.asIndirect ++ extPostfix + IndirectStep.Allocated, diff --git a/compiler/src/main/scala/edg/compiler/ExportTapCheck.scala b/compiler/src/main/scala/edg/compiler/ExportTapCheck.scala new file mode 100644 index 000000000..446ec7c76 --- /dev/null +++ b/compiler/src/main/scala/edg/compiler/ExportTapCheck.scala @@ -0,0 +1,109 @@ +package edg.compiler + +import edg.ExprBuilder.ValueExpr +import edg.wir.{DesignPath, IndirectStep} +import edg.wir.ProtoUtil.{ConstraintProtoToSeqMap, ParamProtoToSeqMap} +import edgir.elem.elem +import edgir.expr.expr +import edgir.ref.ref + +import scala.collection.SeqMap +import scala.collection.mutable + +/** Checks export tap validity, that inner-side parameters are undefined and elements are consistent. + */ +class ExportTapCheck(compiler: Compiler) + extends DesignMap[Unit, Seq[CompilerError], Unit] { + val portParams = mutable.HashMap[DesignPath, Seq[String]]() + + def mapExported( + containingPath: DesignPath, + exportName: String, + exported: expr.ExportedExpr + ): Seq[CompilerError] = { + val (ValueExpr.Ref(extPort), ValueExpr.Ref(intPort)) = (exported.getExteriorPort, exported.getInternalBlockPort) + portParams(containingPath ++ extPort).flatMap { paramName => + val paramPath = containingPath.asIndirect ++ intPort + paramName + val exportedErrors = compiler.getValue(paramPath) match { + case Some(_) => Seq(CompilerError.ExprError( + paramPath, + "export tap internal port parameter must be undefined" + )) + case None => Seq() + } + exportedErrors ++ exported.expanded.flatMap(expr => + mapExported(containingPath, exportName, expr) + ) + } + } + + def mapConstraint( + containingPath: DesignPath, + constrName: String, + constr: expr.ValueExpr + ): Seq[CompilerError] = { + constr.expr match { + case expr.ValueExpr.Expr.Exported(exported) if exported.tap => + mapExported(containingPath, constrName, exported) + case expr.ValueExpr.Expr.ExportedArray(exported) if exported.tap => + val (ValueExpr.Ref(extPort), ValueExpr.Ref(intPort)) = (exported.getExteriorPort, exported.getInternalBlockPort) + val exportedArrayContainerErrors = + if ( + compiler.getValue(containingPath.asIndirect ++ extPort + IndirectStep.Elements) == compiler.getValue( + containingPath.asIndirect ++ intPort + IndirectStep.Elements + ) + ) { + Seq() + } else { + Seq(CompilerError.ExprError( + containingPath.asIndirect + constrName, + "inconsistent export tap array port elements" + )) + } + exportedArrayContainerErrors ++ mapExported(containingPath, constrName, exported) + case _ => Seq() // other constructs ignored + } + } + + override def mapPort(path: DesignPath, port: elem.Port, ports: SeqMap[String, Unit]): Unit = { + portParams.put(path, port.params.asPairs.map { case (name, _) => name }.toSeq) + } + override def mapPortArray(path: DesignPath, port: elem.PortArray, ports: SeqMap[String, Unit]): Unit = { + portParams.put(path, Seq()) + } + override def mapPortLibrary(path: DesignPath, port: ref.LibraryPath): Unit = {} + + override def mapBlock( + path: DesignPath, + block: elem.HierarchyBlock, + ports: SeqMap[String, Unit], + blocks: SeqMap[String, Seq[CompilerError]], + links: SeqMap[String, Unit] + ): Seq[CompilerError] = { + block.constraints.asPairs.flatMap { + case (name, constr) => mapConstraint(path, name, constr) + }.toSeq ++ blocks.values.flatten + } + override def mapBlockLibrary(path: DesignPath, block: ref.LibraryPath): Seq[CompilerError] = { + Seq() // block library errors should be checked elsewhere + } + + override def mapLink( + path: DesignPath, + link: elem.Link, + ports: SeqMap[String, Unit], + links: SeqMap[String, Unit] + ): Unit = {} // export tap not valid in links + + override def mapLinkArray( + path: DesignPath, + link: elem.LinkArray, + ports: SeqMap[String, Unit], + links: SeqMap[String, Unit] + ): Unit = {} // export tap not valid in links + + override def mapLinkLibrary( + path: DesignPath, + link: ref.LibraryPath + ): Unit = {} // link library errors should be checked elsewhere +} diff --git a/compiler/src/main/scala/edg/compiler/ExprToString.scala b/compiler/src/main/scala/edg/compiler/ExprToString.scala index 44f08f4f5..204854b9f 100644 --- a/compiler/src/main/scala/edg/compiler/ExprToString.scala +++ b/compiler/src/main/scala/edg/compiler/ExprToString.scala @@ -191,7 +191,11 @@ class ExprToString() extends ValueExprMap[String] { expandedExteriorPort: String, expandedInterorPort: String ): String = { - s"exported($exteriorPort, $internalBlockPort)" + if (!exported.tap) { + s"exported($exteriorPort, $internalBlockPort)" + } else { + s"exported[tap]($exteriorPort, $internalBlockPort)" + } } override def mapExportedTunnel( diff --git a/compiler/src/main/scala/edg/wir/BlockConnectivityAnalysis.scala b/compiler/src/main/scala/edg/wir/BlockConnectivityAnalysis.scala deleted file mode 100644 index e6ba1a7fb..000000000 --- a/compiler/src/main/scala/edg/wir/BlockConnectivityAnalysis.scala +++ /dev/null @@ -1,213 +0,0 @@ -package edg.wir - -import edg.wir.ProtoUtil._ -import edgir.elem.elem -import edgir.expr.expr -import edgir.ref.ref -import edgir.ref.ref.LocalPath - -/** A connection to a link, from the point of view of (and relative to) some block - */ -sealed trait Connection { - def getPorts: Seq[ref.LocalPath] -} -case object Connection { - case class Disconnected() extends Connection { - override def getPorts: Seq[ref.LocalPath] = Seq() - } - - case class Link( - linkName: String, - linkConnects: Seq[(ref.LocalPath, String)], // including bridge link-facing ports, as (port ref, constr name) - bridgedExports: Seq[(ref.LocalPath, String, String)] // (exterior port ref, bridge block name, constr name) - ) extends Connection { - override def getPorts: Seq[ref.LocalPath] = linkConnects.map(_._1) ++ bridgedExports.map(_._1) - } - case class Export( - constraintName: String, - exteriorPort: ref.LocalPath, - innerBlockPort: ref.LocalPath - ) extends Connection { - override def getPorts: Seq[LocalPath] = Seq(exteriorPort, innerBlockPort) - } -} - -object BlockConnectivityAnalysis { - def typeOfPortLike(portLike: elem.PortLike): ref.LibraryPath = portLike.is match { - case elem.PortLike.Is.LibElem(lib) => lib - case elem.PortLike.Is.Port(port) => port.getSelfClass - case elem.PortLike.Is.Array(port) => port.getSelfClass - case other => throw new IllegalArgumentException(s"Unexpected PortLike ${other.getClass}") - } -} - -/** Class that "wraps" a block to provide connectivity analysis for constraints and links inside the block. - */ -class BlockConnectivityAnalysis(block: elem.HierarchyBlock) { - lazy val allExports: Seq[(ref.LocalPath, ref.LocalPath, String)] = { // external ref, internal ref, constr name - block.constraints.asPairs.map { case (name, constr) => - (name, constr.expr) - }.collect { // filter for exported only, into (inner block port path, exterior port path) pairs - case (name, expr.ValueExpr.Expr.Exported(exported)) => - (exported.getExteriorPort.getRef, exported.getInternalBlockPort.getRef, name) - }.toSeq - } - - lazy val allConnects: Seq[(ref.LocalPath, ref.LocalPath, String)] = { // block ref, link ref, constr name - block.constraints.asPairs.map { case (name, constr) => - (name, constr.expr) - }.collect { // filter for exported only, into (inner block port path, exterior port path) pairs - case (name, expr.ValueExpr.Expr.Connected(connected)) => - (connected.getBlockPort.getRef, connected.getLinkPort.getRef, name) - }.toSeq - } - - // All exports, structured as exterior port ref -> (interior port ref, constr name) - lazy val exportsByOuter: Map[ref.LocalPath, (ref.LocalPath, String)] = { - allExports.groupBy(_._1) - .view.mapValues { - case Seq((exteriorRef, interiorRef, constrName)) => (interiorRef, constrName) - case other => throw new IllegalArgumentException(s"unexpected grouped exports $other") - }.toMap - } - - // All exports, structured as inner port ref -> (exterior port ref, constr name) - lazy val exportsByInner: Map[ref.LocalPath, (ref.LocalPath, String)] = { - allExports.groupBy(_._2) - .view.mapValues { - case Seq((exteriorRef, interiorRef, constrName)) => (exteriorRef, constrName) - case other => throw new IllegalArgumentException(s"unexpected grouped exports $other") - }.toMap - } - - // All exports, structured as inner block port ref -> (link port ref, constr name) - lazy val connectsByBlock: Map[ref.LocalPath, (ref.LocalPath, String)] = { - allConnects.groupBy(_._1) - .view.mapValues { - case Seq((innerRef, linkRef, constrName)) => (linkRef, constrName) - case other => throw new IllegalArgumentException(s"unexpected grouped connects $other") - }.toMap - } - - // Returns all internal connected ports - def getAllConnectedInternalPorts: Seq[ref.LocalPath] = allConnects.map(_._1) ++ allExports.map(_._2) - // Returns all external (boundary) connected ports - def getAllConnectedExternalPorts: Seq[ref.LocalPath] = allExports.map(_._1) - - private def blockIsBridge(block: elem.HierarchyBlock): Boolean = { - // TODO superclass check once the infrastructure is there - block.ports.asPairs.map(_._1).toSet == Set( - LibraryConnectivityAnalysis.portBridgeOuterPort, - LibraryConnectivityAnalysis.portBridgeLinkPort - ) - } - - /** If innerPortRef cconnects to a bridge block, returns the exterior port ref, bridge name, and export constraint - */ - private def bridgedToOuterOption(innerPortRef: ref.LocalPath): Option[(ref.LocalPath, String, String)] = { - val bridgeName = innerPortRef.steps.head.getName // name for the block in question, which MAY be a bridge - block.blocks.get(bridgeName).map { blockLike => // filter by block exists - blockLike.getHierarchy - }.collect { - case block if blockIsBridge(block) => // filter by is-bridge, transform to path, exported - val bridgeOuterRef = ref.LocalPath().update(_.steps := Seq( - ref.LocalStep().update(_.name := bridgeName), - ref.LocalStep().update(_.name := LibraryConnectivityAnalysis.portBridgeOuterPort) - )) - exportsByInner.get(bridgeOuterRef) - }.flatten - .collect { case (exportedRef, exportConstrName) => - (exportedRef, bridgeName, exportConstrName) - } - } - - private def bridgedToInnerOption(outerPortRef: ref.LocalPath): Option[ref.LocalPath] = { - val bridgeName = outerPortRef.steps.head.getName // name for the block in question, which MAY be a bridge - block.blocks.get(bridgeName).map { blockLike => // filter by block exists - blockLike.getHierarchy - }.collect { - case block if blockIsBridge(block) => // filter by is-bridge, transform to path, exported - ref.LocalPath().update(_.steps := Seq( - ref.LocalStep().update(_.name := bridgeName), - ref.LocalStep().update(_.name := LibraryConnectivityAnalysis.portBridgeLinkPort) - )) - } - } - - def getConnectedToLink(linkName: String): Connection.Link = { - val allBlockRefConstrs = allConnects.collect { // filter by link name, and map to (port ref, constr name) - case (blockPortRef, linkPortRef, constrName) - if linkPortRef.steps.nonEmpty && linkPortRef.steps.head.getName == linkName => - (blockPortRef, constrName) - } - - // Find all bridged exports - val allExportRefBlockConstrs = allBlockRefConstrs.flatMap { case (blockPortRef, constrName) => - bridgedToOuterOption(blockPortRef) - } - - Connection.Link( - linkName, - allBlockRefConstrs, - allExportRefBlockConstrs - ) - } - - /** Returns the Connection that portPath is part of, or None if it is not connected. - */ - def getConnected(portRef: ref.LocalPath): Connection = { - if (connectsByBlock.contains(portRef)) { - require(!exportsByInner.contains(portRef), s"overconnected port $portRef") - require(!exportsByOuter.contains(portRef), s"overconnected port $portRef") - val (linkPortRef, constrName) = connectsByBlock(portRef) - require(linkPortRef.steps.nonEmpty) - val linkName = linkPortRef.steps.head.getName - require( - block.links.asPairs.map(_._1).toSet.contains(linkName), - s"reference to nonexistent link $linkName connected to $portRef" - ) - - getConnectedToLink(linkName) - } else if (exportsByInner.contains(portRef)) { - require(!exportsByOuter.contains(portRef), s"overconnected port $portRef") - val (exteriorRef, constrName) = exportsByInner(portRef) - bridgedToInnerOption(portRef) match { - // TODO: possible edge case with bridge with disconnected inner that would ignore the export - case Some(bridgeInnerRef) => getConnected(bridgeInnerRef) - case None => Connection.Export(constrName, exteriorRef, portRef) - } - } else if (exportsByOuter.contains(portRef)) { - val (innerRef, constrName) = exportsByOuter(portRef) - getConnected(innerRef) // delegate to handle both export and bridged case - } else { - Connection.Disconnected() - } - } - - case class ConnectablePorts( - innerPortTypes: Set[(ref.LocalPath, ref.LibraryPath)], - exteriorPortTypes: Set[(ref.LocalPath, ref.LibraryPath)] - ) - - /** Returns all the connectable ports and types of this block, including inner block ports (and their types) and - * exterior ports (and their non-bridged type) - */ - def allConnectablePortTypes: ConnectablePorts = { - val innerPortTypes = block.blocks.asPairs.flatMap { case (blockName, blockLike) => - blockLike.getHierarchy.ports.asPairs.map { case (portName, portLike) => - val portRef = ref.LocalPath().update(_.steps := Seq( - ref.LocalStep().update(_.name := blockName), - ref.LocalStep().update(_.name := portName) - )) - (portRef, BlockConnectivityAnalysis.typeOfPortLike(portLike)) - } - }.toSet - val exteriorPortTypes = block.ports.asPairs.map { case (portName, portLike) => - val portRef = ref.LocalPath().update(_.steps := Seq( - ref.LocalStep().update(_.name := portName) - )) - (portRef, BlockConnectivityAnalysis.typeOfPortLike(portLike)) - }.toSet - ConnectablePorts(innerPortTypes, exteriorPortTypes) - } -} diff --git a/compiler/src/test/scala/edg/CompilerTestUtil.scala b/compiler/src/test/scala/edg/CompilerTestUtil.scala index 4b286ed83..679c01964 100644 --- a/compiler/src/test/scala/edg/CompilerTestUtil.scala +++ b/compiler/src/test/scala/edg/CompilerTestUtil.scala @@ -1,6 +1,6 @@ package edg -import edg.compiler.{Compiler, DesignAssertionCheck, DesignRefsValidate, DesignStructuralValidate} +import edg.compiler.{Compiler, DesignAssertionCheck, DesignRefsValidate, DesignStructuralValidate, ExportTapCheck} import edg.wir.{EdgirLibrary, Refinements} import edgir.schema.schema.{Design, Library} import org.scalatest.flatspec.AnyFlatSpec @@ -19,6 +19,7 @@ trait CompilerTestUtil extends AnyFlatSpec { new DesignStructuralValidate().map(compiled) shouldBe empty new DesignRefsValidate().validate(compiled) shouldBe empty new DesignAssertionCheck(compiler).map(compiled) shouldBe empty + new ExportTapCheck(compiler).map(compiled) shouldBe empty expectedDesign match { // toProtoString is a more readable and diff-able case Some(expectedDesign) => compiled should equal(expectedDesign) case _ => diff --git a/compiler/src/test/scala/edg/compiler/ExportTapTest.scala b/compiler/src/test/scala/edg/compiler/ExportTapTest.scala new file mode 100644 index 000000000..428f015e2 --- /dev/null +++ b/compiler/src/test/scala/edg/compiler/ExportTapTest.scala @@ -0,0 +1,238 @@ +package edg.compiler + +import edg.CompilerTestUtil +import edg.ElemBuilder._ +import edg.ExprBuilder.{Ref, ValInit, ValueExpr} +import edg.wir.ProtoUtil.ConstraintProtoToSeqMap +import edg.wir.{IndirectDesignPath, IndirectStep} +import org.scalatest.exceptions.TestFailedException +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers._ + +import scala.collection.SeqMap + +class ExportTapTest extends AnyFlatSpec with CompilerTestUtil { + import edgir.expr.expr.UnarySetExpr.Op + val library = Library( + ports = Seq( + Port.Port( + "port", + params = SeqMap( + "floatVal" -> ValInit.Floating, + ) + ), + ), + blocks = Seq( + Block.Block( + "leafBlock", + ports = SeqMap( + "port" -> Port.Library("port"), + ), + constraints = SeqMap( + "constFloatVal" -> ValueExpr.Assign(Ref("port", "floatVal"), ValueExpr.Literal(1.0)), + ) + ), + Block.Block( + "emptyLeafBlock", + ports = SeqMap( + "port" -> Port.Library("port"), + ), + ), + Block.Block( + "exportTapBlock", + ports = SeqMap( + "port" -> Port.Library("port"), + ), + blocks = SeqMap( + "inner" -> Block.Library("leafBlock"), + "innerTap" -> Block.Library("emptyLeafBlock") + ), + constraints = SeqMap( + "export" -> Constraint.Exported(Ref("port"), Ref("inner", "port")), + "tap" -> Constraint.Exported(Ref("port"), Ref("innerTap", "port"), tap = true), + ) + ), + Block.Block( + "badExportTapBlock", + ports = SeqMap( + "port" -> Port.Library("port"), + ), + blocks = SeqMap( + "inner" -> Block.Library("leafBlock"), + "innerTap" -> Block.Library("leafBlock") // also defines parameters + ), + constraints = SeqMap( + "export" -> Constraint.Exported(Ref("port"), Ref("inner", "port")), + "tap" -> Constraint.Exported(Ref("port"), Ref("innerTap", "port"), tap = true), + ) + ), + // array test support + Block.Block( + "leafArrayBlock", + ports = SeqMap( + "port" -> Port.Array("port", Seq("0", "1"), Port.Library("port")), + ), + constraints = SeqMap( + "constFloatVal0" -> ValueExpr.Assign(Ref("port", "0", "floatVal"), ValueExpr.Literal(1.0)), + "constFloatVal1" -> ValueExpr.Assign(Ref("port", "1", "floatVal"), ValueExpr.Literal(2.0)), + ) + ), + Block.Block( + "emptyLeafArrayBlock", + ports = SeqMap( // port definition must be synchronized with the outer block + "port" -> Port.Array("port", Seq("0", "1"), Port.Library("port"))), + ), + Block.Block( + "exportArrayTapBlock", + ports = SeqMap( + "port" -> Port.Array("port"), + ), + blocks = SeqMap( + "inner" -> Block.Library("leafArrayBlock"), + "innerTap" -> Block.Library("emptyLeafArrayBlock") + ), + constraints = SeqMap( + "export" -> Constraint.ExportedArray(Ref("port"), Ref("inner", "port")), + "tap" -> Constraint.ExportedArray(Ref("port"), Ref("innerTap", "port"), tap = true), + ) + ), + Block.Block( + "emptyLeafArrayBlockBad", + ports = SeqMap( // contains mismatched port elements + "port" -> Port.Array("port", Seq("0"), Port.Library("port"))), + ), + Block.Block( + "exportArrayTapBlockBad", + ports = SeqMap( + "port" -> Port.Array("port"), + ), + blocks = SeqMap( + "inner" -> Block.Library("leafArrayBlock"), + "innerTap" -> Block.Library("emptyLeafArrayBlockBad") + ), + constraints = SeqMap( + "export" -> Constraint.ExportedArray(Ref("port"), Ref("inner", "port")), + "tap" -> Constraint.ExportedArray(Ref("port"), Ref("innerTap", "port"), tap = true), + ) + ), + ), + links = Seq( + Link.Link( + "link", + ports = SeqMap( + "ports" -> Port.Array("port"), + ), + params = SeqMap( + "floatSum" -> ValInit.Floating, + ), + constraints = SeqMap( + "calcFloatSum" -> ValueExpr.Assign( + Ref("floatSum"), + ValueExpr.UnarySetOp(Op.SUM, ValueExpr.MapExtract(Ref("ports"), Ref("floatVal")), ValueExpr.Literal(0.0)) + ), + ) + ), + ) + ) + + "Compiler on design with tap exports" should "propagate and evaluate values" in { + val inputDesign = Design(Block.Block( + "topDesign", + blocks = SeqMap( + "leaf" -> Block.Library("leafBlock"), + "tap" -> Block.Library("exportTapBlock"), + ), + links = SeqMap( + "link" -> Link.Library("link") + ), + constraints = SeqMap( + "leafConnect" -> Constraint.Connected(Ref("leaf", "port"), Ref.Allocate(Ref("link", "ports"))), + "tapConnect" -> Constraint.Connected(Ref("tap", "port"), Ref.Allocate(Ref("link", "ports"))), + ) + )) + val (compiler, compiled) = testCompile(inputDesign, library) + + compiler.getValue(IndirectDesignPath() + "link" + "floatSum") should equal( + Some(FloatValue(2.0)) + ) + compiler.getValue( + IndirectDesignPath() + "tap" + "innerTap" + "port" + IndirectStep.ConnectedLink + "floatSum" + ) should equal( + Some(FloatValue(2.0)) + ) + } + + "Compiler on design with bad tap exports" should "error" in { + val inputDesign = Design(Block.Block( + "topDesign", + blocks = SeqMap( + "tap" -> Block.Library("badExportTapBlock"), + ), + links = SeqMap( + "link" -> Link.Library("link") + ), + constraints = SeqMap( + "tapConnect" -> Constraint.Connected(Ref("tap", "port"), Ref.Allocate(Ref("link", "ports"))), + ) + )) + an[TestFailedException] should be thrownBy testCompile(inputDesign, library) + } + + "Compiler on design with tap export array" should "propagate allocated values" in { + val inputDesign = Design(Block.Block( + "topDesign", + blocks = SeqMap( + "tap" -> Block.Library("exportArrayTapBlock"), + ), + links = SeqMap( + "link" -> Link.Library("link") + ), + constraints = SeqMap( + "tapConnect0" -> Constraint.Connected( + Ref.Allocate(Ref("tap", "port"), Some("0")), + Ref.Allocate(Ref("link", "ports")) + ), + "tapConnect1" -> Constraint.Connected( + Ref.Allocate(Ref("tap", "port"), Some("1")), + Ref.Allocate(Ref("link", "ports")) + ), + ) + )) + val (compiler, compiled) = testCompile(inputDesign, library) + + compiler.getValue(IndirectDesignPath() + "tap" + "innerTap" + "port" + IndirectStep.Allocated) should equal( + Some(ArrayValue(Seq(TextValue("0"), TextValue("1")))) + ) + compiler.getValue(IndirectDesignPath() + "link" + "floatSum") should equal( + Some(FloatValue(3.0)) + ) + compiler.getValue( + IndirectDesignPath() + "tap" + "innerTap" + "port" + "0" + IndirectStep.ConnectedLink + "floatSum" + ) should equal( + Some(FloatValue(3.0)) + ) + } + + "Compiler on design with tap export array with inconsistent elements" should "error" in { + val inputDesign = Design(Block.Block( + "topDesign", + blocks = SeqMap( + "tap" -> Block.Library("exportArrayTapBlockBad"), + ), + links = SeqMap( + "link" -> Link.Library("link") + ), + constraints = SeqMap( + "tapConnect0" -> Constraint.Connected( + Ref.Allocate(Ref("tap", "port"), Some("0")), + Ref.Allocate(Ref("link", "ports")) + ), + "tapConnect1" -> Constraint.Connected( + Ref.Allocate(Ref("tap", "port"), Some("1")), + Ref.Allocate(Ref("link", "ports")) + ), + ) + )) + an[TestFailedException] should be thrownBy testCompile(inputDesign, library) + } +} diff --git a/compiler/src/test/scala/edg/compiler/TunnelExportTest.scala b/compiler/src/test/scala/edg/compiler/ExportTunnelTest.scala similarity index 99% rename from compiler/src/test/scala/edg/compiler/TunnelExportTest.scala rename to compiler/src/test/scala/edg/compiler/ExportTunnelTest.scala index d72d47712..f989ec0ce 100644 --- a/compiler/src/test/scala/edg/compiler/TunnelExportTest.scala +++ b/compiler/src/test/scala/edg/compiler/ExportTunnelTest.scala @@ -14,7 +14,7 @@ import scala.collection.SeqMap /** Tests tunnel exports. */ -class TunnelExportTest extends AnyFlatSpec with CompilerTestUtil { +class ExportTunnelTest extends AnyFlatSpec with CompilerTestUtil { import edgir.expr.expr.UnarySetExpr.Op val library = Library( ports = Seq( diff --git a/compiler/src/test/scala/edg/wir/BlockConnectivityAnalysisTest.scala b/compiler/src/test/scala/edg/wir/BlockConnectivityAnalysisTest.scala deleted file mode 100644 index b2936d70e..000000000 --- a/compiler/src/test/scala/edg/wir/BlockConnectivityAnalysisTest.scala +++ /dev/null @@ -1,212 +0,0 @@ -package edg.wir - -import org.scalatest._ -import org.scalatest.flatspec.AnyFlatSpec -import matchers.should.Matchers._ -import edg.ElemBuilder._ -import edg.ExprBuilder.Ref -import edg.compiler.Compiler -import edg.wir.ProtoUtil.BlockProtoToSeqMap -import edg.{CompilerTestUtil, wir} - -import scala.collection.SeqMap - -class BlockConnectivityAnalysisTest extends AnyFlatSpec with CompilerTestUtil { - private val library = Library( - ports = Seq( - Port.Port("sourcePort"), - Port.Port("sinkPort"), - ), - links = Seq( - Link.Link( - "link", - ports = SeqMap( - "source" -> Port.Library("sourcePort"), - "sinks" -> Port.Array("sinkPort") - ), - // practically invalid, missing connect constraints - ), - ), - blocks = Seq( - Block.Block( - "sourceBlock", - ports = SeqMap( - "port" -> Port.Library("sourcePort"), - ) - ), - Block.Block( - "sinkBlock", - ports = SeqMap( - "port" -> Port.Library("sinkPort"), - ) - ), - Block.Block( - "sourceFromExtSinkBridge", - superclasses = Seq(LibraryConnectivityAnalysis.portBridge.getTarget.getName), - ports = SeqMap( - LibraryConnectivityAnalysis.portBridgeLinkPort -> Port.Library("sourcePort"), - LibraryConnectivityAnalysis.portBridgeOuterPort -> Port.Library("sinkPort"), - ) - ), - Block.Block( - "sinkFromExtSourceBridge", - superclasses = Seq(LibraryConnectivityAnalysis.portBridge.getTarget.getName), - ports = SeqMap( - LibraryConnectivityAnalysis.portBridgeLinkPort -> Port.Library("sinkPort"), - LibraryConnectivityAnalysis.portBridgeOuterPort -> Port.Library("sourcePort"), - ) - ), - Block.Block( - "exportSinkBlock", - ports = SeqMap( - "port" -> Port.Library("sinkPort"), - ), - blocks = SeqMap( - "inner" -> Block.Library("sinkBlock") - ), - constraints = SeqMap( - "export" -> Constraint.Exported(Ref("port"), Ref("inner", "port")) - ) - ), - Block.Block( - "bridgedSinkBlock", - ports = SeqMap( - "port" -> Port.Library("sinkPort"), - ), - blocks = SeqMap( - "bridge" -> Block.Library("sourceFromExtSinkBridge"), - "sink1Block" -> Block.Library("sinkBlock"), - "sink2Block" -> Block.Library("sinkBlock"), - ), - links = SeqMap( - "link" -> Link.Library("link") - ), - constraints = SeqMap( - "export" -> Constraint.Exported(Ref("port"), Ref("bridge", LibraryConnectivityAnalysis.portBridgeOuterPort)), - "sourceConnect" -> Constraint.Connected( - Ref("bridge", LibraryConnectivityAnalysis.portBridgeLinkPort), - Ref("link", "source") - ), - "sink1Connect" -> Constraint.Connected(Ref("sink1Block", "port"), Ref.Allocate(Ref("link", "sinks"))), - "sink2Connect" -> Constraint.Connected(Ref("sink2Block", "port"), Ref.Allocate(Ref("link", "sinks"))), - ) - ) - ), - ) - - it should "get connected for direct exports" in { - val inputDesign = Design(Block.Block( - "topDesign", - blocks = SeqMap( - "dut" -> Block.Library("exportSinkBlock") - ) - )) - val (compiler, compiled) = testCompile(inputDesign, library) - val analysis = new BlockConnectivityAnalysis(compiled.getContents.blocks("dut").getHierarchy) - - analysis.getConnected(Ref("port")) should equal( - Connection.Export("export", Ref("port"), Ref("inner", "port")) - ) - - analysis.getAllConnectedInternalPorts should equal( - Seq(Ref("inner", "port")) - ) - analysis.getAllConnectedExternalPorts should equal( - Seq(Ref("port")) - ) - } - - it should "get connected for links only" in { - val inputDesign = Design(Block.Block( - "topDesign", - blocks = SeqMap( - "sourceBlock" -> Block.Library("sourceBlock"), - "sink1Block" -> Block.Library("sinkBlock"), - "sink2Block" -> Block.Library("sinkBlock"), - ), - links = SeqMap( - "link" -> Link.Library("link") - ), - constraints = SeqMap( - "sourceConnect" -> Constraint.Connected(Ref("sourceBlock", "port"), Ref("link", "source")), - "sink1Connect" -> Constraint.Connected(Ref("sink1Block", "port"), Ref.Allocate(Ref("link", "sinks"))), - "sink2Connect" -> Constraint.Connected(Ref("sink2Block", "port"), Ref.Allocate(Ref("link", "sinks"))), - ) - )) - val (compiler, compiled) = testCompile(inputDesign, library) - val analysis = new BlockConnectivityAnalysis(compiled.getContents) - - val expectedConnects = Connection.Link( - "link", - Seq( - (Ref("sourceBlock", "port"), "sourceConnect"), - (Ref("sink1Block", "port"), "sink1Connect"), - (Ref("sink2Block", "port"), "sink2Connect"), - ), - Seq() - ) - - analysis.getConnected(Ref("sourceBlock", "port")) should equal(expectedConnects) - analysis.getConnected(Ref("sink1Block", "port")) should equal(expectedConnects) - analysis.getConnected(Ref("sink2Block", "port")) should equal(expectedConnects) - - analysis.getAllConnectedInternalPorts should equal( - Seq(Ref("sourceBlock", "port"), Ref("sink1Block", "port"), Ref("sink2Block", "port")) - ) - } - - it should "get connected for mixed link and exports" in { - val inputDesign = Design(Block.Block( - "topDesign", - blocks = SeqMap( - "dut" -> Block.Library("bridgedSinkBlock") - ) - )) - val (compiler, compiled) = testCompile(inputDesign, library) - val analysis = new BlockConnectivityAnalysis(compiled.getContents.blocks("dut").getHierarchy) - - val expectedConnects = Connection.Link( - "link", - Seq( - (Ref("bridge", LibraryConnectivityAnalysis.portBridgeLinkPort), "sourceConnect"), - (Ref("sink1Block", "port"), "sink1Connect"), - (Ref("sink2Block", "port"), "sink2Connect"), - ), - Seq( - (Ref("port"), "bridge", "export") - ) - ) - - analysis.getConnected(Ref("sink1Block", "port")) should equal(expectedConnects) - analysis.getConnected(Ref("sink2Block", "port")) should equal(expectedConnects) - analysis.getConnected(Ref("port")) should equal(expectedConnects) - analysis.getConnected(Ref("bridge", LibraryConnectivityAnalysis.portBridgeLinkPort)) should equal(expectedConnects) - analysis.getConnected(Ref("bridge", LibraryConnectivityAnalysis.portBridgeOuterPort)) should equal(expectedConnects) - - analysis.getAllConnectedInternalPorts.toSet should equal( - Set( - Ref("bridge", LibraryConnectivityAnalysis.portBridgeLinkPort), - Ref("bridge", LibraryConnectivityAnalysis.portBridgeOuterPort), - Ref("sink1Block", "port"), - Ref("sink2Block", "port") - ) - ) - analysis.getAllConnectedExternalPorts should equal( - Seq(Ref("port")) - ) - - analysis.allConnectablePortTypes should equal( - analysis.ConnectablePorts( - innerPortTypes = Set( - (Ref("sink1Block", "port"), LibraryPath("sinkPort")), - (Ref("sink2Block", "port"), LibraryPath("sinkPort")), - (Ref("bridge", LibraryConnectivityAnalysis.portBridgeLinkPort), LibraryPath("sourcePort")), - (Ref("bridge", LibraryConnectivityAnalysis.portBridgeOuterPort), LibraryPath("sinkPort")), - ), - exteriorPortTypes = Set( - (Ref("port"), LibraryPath("sinkPort")), - ) - ) - ) - } -} diff --git a/edg/core/resources/edg-compiler-precompiled.jar b/edg/core/resources/edg-compiler-precompiled.jar index 08fa3b7e8..7476dad38 100644 Binary files a/edg/core/resources/edg-compiler-precompiled.jar and b/edg/core/resources/edg-compiler-precompiled.jar differ diff --git a/edg/edgir/expr_pb2.py b/edg/edgir/expr_pb2.py index e4fd24453..ae21c29a3 100644 --- a/edg/edgir/expr_pb2.py +++ b/edg/edgir/expr_pb2.py @@ -11,7 +11,7 @@ from ..edgir import lit_pb2 as edgir_dot_lit__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( - b'\n\x10edgir/expr.proto\x12\nedgir.expr\x1a\x0fedgir/ref.proto\x1a\x12edgir/common.proto\x1a\x0fedgir/lit.proto"\xb4\x01\n\tUnaryExpr\x12$\n\x02op\x18\x01 \x01(\x0e2\x18.edgir.expr.UnaryExpr.Op\x12"\n\x03val\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr"]\n\x02Op\x12\r\n\tUNDEFINED\x10\x00\x12\n\n\x06NEGATE\x10\x01\x12\x07\n\x03NOT\x10\x02\x12\n\n\x06INVERT\x10\x03\x12\x07\n\x03MIN\x10\x04\x12\x07\n\x03MAX\x10\x05\x12\n\n\x06CENTER\x10\x06\x12\t\n\x05WIDTH\x10\x07"\xcb\x02\n\x0cUnarySetExpr\x12\'\n\x02op\x18\x01 \x01(\x0e2\x1b.edgir.expr.UnarySetExpr.Op\x12#\n\x04vals\x18\x04 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12*\n\x0bempty_value\x18\x05 \x01(\x0b2\x15.edgir.expr.ValueExpr"\xc0\x01\n\x02Op\x12\r\n\tUNDEFINED\x10\x00\x12\x07\n\x03SUM\x10\x01\x12\x0c\n\x08ALL_TRUE\x10\x02\x12\x0c\n\x08ANY_TRUE\x10\x03\x12\n\n\x06ALL_EQ\x10\x04\x12\x0e\n\nALL_UNIQUE\x10\x05\x12\x0b\n\x07MAXIMUM\x10\n\x12\x0b\n\x07MINIMUM\x10\x0b\x12\x0f\n\x0bSET_EXTRACT\x10\x0c\x12\x10\n\x0cINTERSECTION\x10\r\x12\x08\n\x04HULL\x10\x0e\x12\n\n\x06NEGATE\x10\x14\x12\n\n\x06INVERT\x10\x15\x12\x0b\n\x07FLATTEN\x10\x1e"\xd4\x02\n\nBinaryExpr\x12%\n\x02op\x18\x01 \x01(\x0e2\x19.edgir.expr.BinaryExpr.Op\x12"\n\x03lhs\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12"\n\x03rhs\x18\x03 \x01(\x0b2\x15.edgir.expr.ValueExpr"\xd6\x01\n\x02Op\x12\r\n\tUNDEFINED\x10\x00\x12\x07\n\x03ADD\x10\n\x12\x08\n\x04MULT\x10\x0c\x12\x0f\n\x0bSHRINK_MULT\x107\x12\x07\n\x03AND\x10\x14\x12\x06\n\x02OR\x10\x15\x12\x07\n\x03XOR\x10\x16\x12\x0b\n\x07IMPLIES\x10\x17\x12\x06\n\x02EQ\x10\x1e\x12\x07\n\x03NEQ\x10\x1f\x12\x06\n\x02GT\x10(\x12\x07\n\x03GTE\x10)\x12\x06\n\x02LT\x10*\x12\x07\n\x03LTE\x10,\x12\x07\n\x03MAX\x10-\x12\x07\n\x03MIN\x10.\x12\x10\n\x0cINTERSECTION\x103\x12\x08\n\x04HULL\x106\x12\n\n\x06WITHIN\x105\x12\t\n\x05RANGE\x10\x01"\xbf\x01\n\rBinarySetExpr\x12(\n\x02op\x18\x01 \x01(\x0e2\x1c.edgir.expr.BinarySetExpr.Op\x12$\n\x05lhset\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12"\n\x03rhs\x18\x03 \x01(\x0b2\x15.edgir.expr.ValueExpr":\n\x02Op\x12\r\n\tUNDEFINED\x10\x00\x12\x07\n\x03ADD\x10\n\x12\x08\n\x04MULT\x10\x0c\x12\n\n\x06CONCAT\x10\x14\x12\x06\n\x02EQ\x10\x1e"0\n\tArrayExpr\x12#\n\x04vals\x18\x01 \x03(\x0b2\x15.edgir.expr.ValueExpr"[\n\tRangeExpr\x12&\n\x07minimum\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12&\n\x07maximum\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr"\x80\x01\n\nStructExpr\x12.\n\x04vals\x18\x01 \x03(\x0b2 .edgir.expr.StructExpr.ValsEntry\x1aB\n\tValsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12$\n\x05value\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr:\x028\x01"\xa3\x01\n\x0eIfThenElseExpr\x12#\n\x04cond\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12"\n\x03tru\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12"\n\x03fal\x18\x03 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12$\n\x04meta\x18\x7f \x01(\x0b2\x16.edgir.common.Metadata"]\n\x0bExtractExpr\x12(\n\tcontainer\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12$\n\x05index\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr"^\n\x0eMapExtractExpr\x12(\n\tcontainer\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12"\n\x04path\x18\x02 \x01(\x0b2\x14.edgir.ref.LocalPath"\x91\x01\n\rConnectedExpr\x12)\n\nblock_port\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12(\n\tlink_port\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12+\n\x08expanded\x18\x03 \x03(\x0b2\x19.edgir.expr.ConnectedExpr"\x9c\x01\n\x0cExportedExpr\x12,\n\rexterior_port\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x122\n\x13internal_block_port\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12*\n\x08expanded\x18\x03 \x03(\x0b2\x18.edgir.expr.ExportedExpr"S\n\nAssignExpr\x12!\n\x03dst\x18\x01 \x01(\x0b2\x14.edgir.ref.LocalPath\x12"\n\x03src\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr"\x97\x07\n\tValueExpr\x12&\n\x07literal\x18\x01 \x01(\x0b2\x13.edgir.lit.ValueLitH\x00\x12(\n\x06binary\x18\x02 \x01(\x0b2\x16.edgir.expr.BinaryExprH\x00\x12/\n\nbinary_set\x18\x12 \x01(\x0b2\x19.edgir.expr.BinarySetExprH\x00\x12&\n\x05unary\x18\x03 \x01(\x0b2\x15.edgir.expr.UnaryExprH\x00\x12-\n\tunary_set\x18\x04 \x01(\x0b2\x18.edgir.expr.UnarySetExprH\x00\x12&\n\x05array\x18\x06 \x01(\x0b2\x15.edgir.expr.ArrayExprH\x00\x12(\n\x06struct\x18\x07 \x01(\x0b2\x16.edgir.expr.StructExprH\x00\x12&\n\x05range\x18\x08 \x01(\x0b2\x15.edgir.expr.RangeExprH\x00\x120\n\nifThenElse\x18\n \x01(\x0b2\x1a.edgir.expr.IfThenElseExprH\x00\x12*\n\x07extract\x18\x0c \x01(\x0b2\x17.edgir.expr.ExtractExprH\x00\x121\n\x0bmap_extract\x18\x0e \x01(\x0b2\x1a.edgir.expr.MapExtractExprH\x00\x12.\n\tconnected\x18\x0f \x01(\x0b2\x19.edgir.expr.ConnectedExprH\x00\x12,\n\x08exported\x18\x10 \x01(\x0b2\x18.edgir.expr.ExportedExprH\x00\x123\n\x0econnectedArray\x18\x13 \x01(\x0b2\x19.edgir.expr.ConnectedExprH\x00\x121\n\rexportedArray\x18\x14 \x01(\x0b2\x18.edgir.expr.ExportedExprH\x00\x12(\n\x06assign\x18\x11 \x01(\x0b2\x16.edgir.expr.AssignExprH\x00\x122\n\x0eexportedTunnel\x18\x15 \x01(\x0b2\x18.edgir.expr.ExportedExprH\x00\x12.\n\x0cassignTunnel\x18\x16 \x01(\x0b2\x16.edgir.expr.AssignExprH\x00\x12#\n\x03ref\x18c \x01(\x0b2\x14.edgir.ref.LocalPathH\x00\x12$\n\x04meta\x18\x7f \x01(\x0b2\x16.edgir.common.MetadataB\x06\n\x04exprb\x06proto3' + b'\n\x10edgir/expr.proto\x12\nedgir.expr\x1a\x0fedgir/ref.proto\x1a\x12edgir/common.proto\x1a\x0fedgir/lit.proto"\xb4\x01\n\tUnaryExpr\x12$\n\x02op\x18\x01 \x01(\x0e2\x18.edgir.expr.UnaryExpr.Op\x12"\n\x03val\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr"]\n\x02Op\x12\r\n\tUNDEFINED\x10\x00\x12\n\n\x06NEGATE\x10\x01\x12\x07\n\x03NOT\x10\x02\x12\n\n\x06INVERT\x10\x03\x12\x07\n\x03MIN\x10\x04\x12\x07\n\x03MAX\x10\x05\x12\n\n\x06CENTER\x10\x06\x12\t\n\x05WIDTH\x10\x07"\xcb\x02\n\x0cUnarySetExpr\x12\'\n\x02op\x18\x01 \x01(\x0e2\x1b.edgir.expr.UnarySetExpr.Op\x12#\n\x04vals\x18\x04 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12*\n\x0bempty_value\x18\x05 \x01(\x0b2\x15.edgir.expr.ValueExpr"\xc0\x01\n\x02Op\x12\r\n\tUNDEFINED\x10\x00\x12\x07\n\x03SUM\x10\x01\x12\x0c\n\x08ALL_TRUE\x10\x02\x12\x0c\n\x08ANY_TRUE\x10\x03\x12\n\n\x06ALL_EQ\x10\x04\x12\x0e\n\nALL_UNIQUE\x10\x05\x12\x0b\n\x07MAXIMUM\x10\n\x12\x0b\n\x07MINIMUM\x10\x0b\x12\x0f\n\x0bSET_EXTRACT\x10\x0c\x12\x10\n\x0cINTERSECTION\x10\r\x12\x08\n\x04HULL\x10\x0e\x12\n\n\x06NEGATE\x10\x14\x12\n\n\x06INVERT\x10\x15\x12\x0b\n\x07FLATTEN\x10\x1e"\xd4\x02\n\nBinaryExpr\x12%\n\x02op\x18\x01 \x01(\x0e2\x19.edgir.expr.BinaryExpr.Op\x12"\n\x03lhs\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12"\n\x03rhs\x18\x03 \x01(\x0b2\x15.edgir.expr.ValueExpr"\xd6\x01\n\x02Op\x12\r\n\tUNDEFINED\x10\x00\x12\x07\n\x03ADD\x10\n\x12\x08\n\x04MULT\x10\x0c\x12\x0f\n\x0bSHRINK_MULT\x107\x12\x07\n\x03AND\x10\x14\x12\x06\n\x02OR\x10\x15\x12\x07\n\x03XOR\x10\x16\x12\x0b\n\x07IMPLIES\x10\x17\x12\x06\n\x02EQ\x10\x1e\x12\x07\n\x03NEQ\x10\x1f\x12\x06\n\x02GT\x10(\x12\x07\n\x03GTE\x10)\x12\x06\n\x02LT\x10*\x12\x07\n\x03LTE\x10,\x12\x07\n\x03MAX\x10-\x12\x07\n\x03MIN\x10.\x12\x10\n\x0cINTERSECTION\x103\x12\x08\n\x04HULL\x106\x12\n\n\x06WITHIN\x105\x12\t\n\x05RANGE\x10\x01"\xbf\x01\n\rBinarySetExpr\x12(\n\x02op\x18\x01 \x01(\x0e2\x1c.edgir.expr.BinarySetExpr.Op\x12$\n\x05lhset\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12"\n\x03rhs\x18\x03 \x01(\x0b2\x15.edgir.expr.ValueExpr":\n\x02Op\x12\r\n\tUNDEFINED\x10\x00\x12\x07\n\x03ADD\x10\n\x12\x08\n\x04MULT\x10\x0c\x12\n\n\x06CONCAT\x10\x14\x12\x06\n\x02EQ\x10\x1e"0\n\tArrayExpr\x12#\n\x04vals\x18\x01 \x03(\x0b2\x15.edgir.expr.ValueExpr"[\n\tRangeExpr\x12&\n\x07minimum\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12&\n\x07maximum\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr"\x80\x01\n\nStructExpr\x12.\n\x04vals\x18\x01 \x03(\x0b2 .edgir.expr.StructExpr.ValsEntry\x1aB\n\tValsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12$\n\x05value\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr:\x028\x01"\xa3\x01\n\x0eIfThenElseExpr\x12#\n\x04cond\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12"\n\x03tru\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12"\n\x03fal\x18\x03 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12$\n\x04meta\x18\x7f \x01(\x0b2\x16.edgir.common.Metadata"]\n\x0bExtractExpr\x12(\n\tcontainer\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12$\n\x05index\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr"^\n\x0eMapExtractExpr\x12(\n\tcontainer\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12"\n\x04path\x18\x02 \x01(\x0b2\x14.edgir.ref.LocalPath"\x91\x01\n\rConnectedExpr\x12)\n\nblock_port\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12(\n\tlink_port\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12+\n\x08expanded\x18\x03 \x03(\x0b2\x19.edgir.expr.ConnectedExpr"\xa9\x01\n\x0cExportedExpr\x12,\n\rexterior_port\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x122\n\x13internal_block_port\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12\x0b\n\x03tap\x18\x04 \x01(\x08\x12*\n\x08expanded\x18\x03 \x03(\x0b2\x18.edgir.expr.ExportedExpr"S\n\nAssignExpr\x12!\n\x03dst\x18\x01 \x01(\x0b2\x14.edgir.ref.LocalPath\x12"\n\x03src\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr"\x97\x07\n\tValueExpr\x12&\n\x07literal\x18\x01 \x01(\x0b2\x13.edgir.lit.ValueLitH\x00\x12(\n\x06binary\x18\x02 \x01(\x0b2\x16.edgir.expr.BinaryExprH\x00\x12/\n\nbinary_set\x18\x12 \x01(\x0b2\x19.edgir.expr.BinarySetExprH\x00\x12&\n\x05unary\x18\x03 \x01(\x0b2\x15.edgir.expr.UnaryExprH\x00\x12-\n\tunary_set\x18\x04 \x01(\x0b2\x18.edgir.expr.UnarySetExprH\x00\x12&\n\x05array\x18\x06 \x01(\x0b2\x15.edgir.expr.ArrayExprH\x00\x12(\n\x06struct\x18\x07 \x01(\x0b2\x16.edgir.expr.StructExprH\x00\x12&\n\x05range\x18\x08 \x01(\x0b2\x15.edgir.expr.RangeExprH\x00\x120\n\nifThenElse\x18\n \x01(\x0b2\x1a.edgir.expr.IfThenElseExprH\x00\x12*\n\x07extract\x18\x0c \x01(\x0b2\x17.edgir.expr.ExtractExprH\x00\x121\n\x0bmap_extract\x18\x0e \x01(\x0b2\x1a.edgir.expr.MapExtractExprH\x00\x12.\n\tconnected\x18\x0f \x01(\x0b2\x19.edgir.expr.ConnectedExprH\x00\x12,\n\x08exported\x18\x10 \x01(\x0b2\x18.edgir.expr.ExportedExprH\x00\x123\n\x0econnectedArray\x18\x13 \x01(\x0b2\x19.edgir.expr.ConnectedExprH\x00\x121\n\rexportedArray\x18\x14 \x01(\x0b2\x18.edgir.expr.ExportedExprH\x00\x12(\n\x06assign\x18\x11 \x01(\x0b2\x16.edgir.expr.AssignExprH\x00\x122\n\x0eexportedTunnel\x18\x15 \x01(\x0b2\x18.edgir.expr.ExportedExprH\x00\x12.\n\x0cassignTunnel\x18\x16 \x01(\x0b2\x16.edgir.expr.AssignExprH\x00\x12#\n\x03ref\x18c \x01(\x0b2\x14.edgir.ref.LocalPathH\x00\x12$\n\x04meta\x18\x7f \x01(\x0b2\x16.edgir.common.MetadataB\x06\n\x04exprb\x06proto3' ) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "edgir.expr_pb2", globals()) @@ -52,8 +52,8 @@ _CONNECTEDEXPR._serialized_start = 1772 _CONNECTEDEXPR._serialized_end = 1917 _EXPORTEDEXPR._serialized_start = 1920 - _EXPORTEDEXPR._serialized_end = 2076 - _ASSIGNEXPR._serialized_start = 2078 - _ASSIGNEXPR._serialized_end = 2161 - _VALUEEXPR._serialized_start = 2164 - _VALUEEXPR._serialized_end = 3083 + _EXPORTEDEXPR._serialized_end = 2089 + _ASSIGNEXPR._serialized_start = 2091 + _ASSIGNEXPR._serialized_end = 2174 + _VALUEEXPR._serialized_start = 2177 + _VALUEEXPR._serialized_end = 3096 diff --git a/edg/edgir/expr_pb2.pyi b/edg/edgir/expr_pb2.pyi index bdcf85342..48dd92064 100644 --- a/edg/edgir/expr_pb2.pyi +++ b/edg/edgir/expr_pb2.pyi @@ -586,7 +586,10 @@ class ExportedExpr(_message.Message): DESCRIPTOR: _descriptor.Descriptor EXTERIOR_PORT_FIELD_NUMBER: _builtins.int INTERNAL_BLOCK_PORT_FIELD_NUMBER: _builtins.int + TAP_FIELD_NUMBER: _builtins.int EXPANDED_FIELD_NUMBER: _builtins.int + tap: _builtins.bool + "if true, this is a tap, which allows the exterior port to have multiple internal connections" @_builtins.property def exterior_port(self) -> Global___ValueExpr: ... @@ -594,13 +597,20 @@ class ExportedExpr(_message.Message): def internal_block_port(self) -> Global___ValueExpr: ... @_builtins.property def expanded(self) -> _containers.RepeatedCompositeFieldContainer[Global___ExportedExpr]: - """see comment in ConnectedExpr""" + """(one non-tap connection, and optionally multiple tap connections) + These additional rules apply to export taps: + - parameters do not propagate, internal port should not have parameter values + - the internal port's link resolves to the external port + + see comment in ConnectedExpr + """ def __init__( self, *, exterior_port: Global___ValueExpr | None = ..., internal_block_port: Global___ValueExpr | None = ..., + tap: _builtins.bool = ..., expanded: _abc.Iterable[Global___ExportedExpr] | None = ..., ) -> None: ... _HasFieldArgType: _TypeAlias = _typing.Literal[ @@ -609,7 +619,14 @@ class ExportedExpr(_message.Message): def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... _ClearFieldArgType: _TypeAlias = _typing.Literal[ - "expanded", b"expanded", "exterior_port", b"exterior_port", "internal_block_port", b"internal_block_port" + "expanded", + b"expanded", + "exterior_port", + b"exterior_port", + "internal_block_port", + b"internal_block_port", + "tap", + b"tap", ] def ClearField(self, field_name: _ClearFieldArgType) -> None: ... diff --git a/edg/electronics_model/BoardScopedTransform.py b/edg/electronics_model/BoardScopedTransform.py new file mode 100644 index 000000000..c250ae0e5 --- /dev/null +++ b/edg/electronics_model/BoardScopedTransform.py @@ -0,0 +1,67 @@ +from typing import Optional, Dict, List, cast +from typing_extensions import override + +from .. import edgir +from ..core import TransformUtil, CompiledDesign +from ..core.TransformUtil import TransformContext + + +class BoardScopedTransform(TransformUtil.Transform): + """Transform subclass that handles board scoping for sub-boards and wrappers. + Board scopes are defined as a Path (root for the "main" board) or None (for sub-board wrappers + that discard their implementation). + Subclasses may define additional data structures, indexed by this board scope Path.""" + + def __init__(self, design: CompiledDesign) -> None: + super().__init__() + self._design = design + self._board_scopes: Dict[TransformUtil.Path, Optional[TransformUtil.Path]] = { + TransformUtil.Path.empty(): TransformUtil.Path.empty() + } # always initialized in parent + + def visit_block_scoped( + self, context: TransformUtil.TransformContext, scope: Optional[TransformUtil.Path], block: edgir.BlockTypes + ) -> None: + pass + + def visit_link_scoped( + self, context: TransformUtil.TransformContext, scope: Optional[TransformUtil.Path], link: edgir.Link + ) -> None: + pass + + def visit_linkarray_scoped( + self, context: TransformUtil.TransformContext, scope: Optional[TransformUtil.Path], link: edgir.LinkArray + ) -> None: + pass + + @override + def visit_block(self, context: TransformContext, block: edgir.HierarchyBlock) -> None: + scope = self._board_scopes[context.path] + + if "fp_subboard" in block.meta.members.node: + fp_external_blocks = self._design.get_value(context.path.to_tuple() + ("fp_external_blocks",)) + assert isinstance(fp_external_blocks, list) + external_blocks: Optional[List[str]] = cast(List[str], fp_external_blocks) + if "fp_subblocks_ignored" in block.meta.members.node: + internal_scope = None + else: + internal_scope = context.path + else: + external_blocks = None + internal_scope = scope + + for block_pair in block.blocks: + if external_blocks is not None and block_pair.name not in external_blocks: + self._board_scopes[context.path.append_block(block_pair.name)] = internal_scope + else: + self._board_scopes[context.path.append_block(block_pair.name)] = scope + + self.visit_block_scoped(context, scope, block) + + @override + def visit_link(self, context: TransformContext, link: edgir.Link) -> None: + self.visit_link_scoped(context, self._board_scopes[context.path.block_component()], link) + + @override + def visit_linkarray(self, context: TransformContext, link: edgir.LinkArray) -> None: + self.visit_linkarray_scoped(context, self._board_scopes[context.path.block_component()], link) diff --git a/edg/electronics_model/NetlistGenerator.py b/edg/electronics_model/NetlistGenerator.py index c8cf93974..d41ab023d 100644 --- a/edg/electronics_model/NetlistGenerator.py +++ b/edg/electronics_model/NetlistGenerator.py @@ -4,6 +4,7 @@ from typing_extensions import override +from .BoardScopedTransform import BoardScopedTransform from .. import edgir from ..core import * @@ -63,7 +64,7 @@ def empty(cls, path: TransformUtil.Path) -> "BoardScope": # returns a fresh, em ] # Path -> class names corresponding to shortened path name -class NetlistTransform(TransformUtil.Transform): +class NetlistTransform(BoardScopedTransform): @staticmethod def flatten_port(path: TransformUtil.Path, port: edgir.PortLike) -> Iterable[TransformUtil.Path]: if port.HasField("port"): @@ -79,39 +80,32 @@ def flatten_port(path: TransformUtil.Path, port: edgir.PortLike) -> Iterable[Tra raise ValueError(f"don't know how to flatten netlistable port {port}") def __init__(self, design: CompiledDesign): + super().__init__(design) + self.all_scopes = [BoardScope.empty(TransformUtil.Path.empty())] # list of unique scopes self.scopes: Scopes = {TransformUtil.Path.empty(): self.all_scopes[0]} self.class_paths: ClassPaths = {TransformUtil.Path.empty(): []} # seed root self.path_traverse_order: List[TransformUtil.Path] = [] - self.design = design - def process_blocklike( - self, path: TransformUtil.Path, block: Union[edgir.Link, edgir.LinkArray, edgir.HierarchyBlock] + self, + path: TransformUtil.Path, + scope: Optional[TransformUtil.Path], + block: Union[edgir.Link, edgir.LinkArray, edgir.HierarchyBlock], ) -> None: - # TODO may need rethought to support multi-board assemblies - scope = self.scopes[path] # including footprint and exports, and everything within a link - internal_scope = scope # for internal blocks + if scope is not None: + scope_obj: Optional[BoardScope] = self.scopes.setdefault(scope, BoardScope.empty(scope)) + else: + scope_obj = None if isinstance(block, edgir.HierarchyBlock): - if "fp_is_wrapper" in block.meta.members.node: # wrapper internal blocks ignored - internal_scope = None - for block_pair in block.blocks: - self.scopes[path.append_block(block_pair.name)] = internal_scope - for link_pair in block.links: # links considered to be the same scope as self - self.scopes[path.append_link(link_pair.name)] = scope - class_path = self.class_paths[path] for block_pair in block.blocks: self.class_paths[path.append_block(block_pair.name)] = class_path + [ block_pair.value.hierarchy.self_class ] - elif isinstance(block, (edgir.Link, edgir.LinkArray)): - for link_pair in block.links: - self.scopes[path.append_link(link_pair.name)] = scope - - if "nets" in block.meta.members.node and scope is not None: + if "nets" in block.meta.members.node and scope_obj is not None: # add self as a net # list conversion to deal with iterable-once flat_ports = list( @@ -119,11 +113,11 @@ def process_blocklike( *[self.flatten_port(path.append_port(port_pair.name), port_pair.value) for port_pair in block.ports] ) ) - scope.edges.setdefault(path, []).extend(flat_ports) + scope_obj.edges.setdefault(path, []).extend(flat_ports) for port_path in flat_ports: - scope.edges.setdefault(port_path, []).append(path) + scope_obj.edges.setdefault(port_path, []).append(path) - if "nets_packed" in block.meta.members.node and scope is not None: + if "nets_packed" in block.meta.members.node and scope_obj is not None: # this connects the first source to all destinations, then asserts all the sources are equal # this leaves the sources unconnected, to be connected externally and checked at the end src_port_name = block.meta.members.node["nets_packed"].members.node["src"].text_leaf @@ -136,20 +130,20 @@ def process_blocklike( ) assert flat_srcs, "missing source port(s) for packed net" for dst_path in flat_dsts: - scope.edges.setdefault(flat_srcs[0], []).append(dst_path) - scope.edges.setdefault(dst_path, []).append(flat_srcs[0]) + scope_obj.edges.setdefault(flat_srcs[0], []).append(dst_path) + scope_obj.edges.setdefault(dst_path, []).append(flat_srcs[0]) for src_path in flat_srcs: # assert all sources connected for dst_path in flat_srcs: - scope.assert_connected.append((src_path, dst_path)) + scope_obj.assert_connected.append((src_path, dst_path)) - if "fp_is_footprint" in block.meta.members.node and scope is not None: - footprint_name = self.design.get_value(path.to_tuple() + ("fp_footprint",)) - footprint_pinning = self.design.get_value(path.to_tuple() + ("fp_pinning",)) - mfr = self.design.get_value(path.to_tuple() + ("fp_mfr",)) - part = self.design.get_value(path.to_tuple() + ("fp_part",)) - value = self.design.get_value(path.to_tuple() + ("fp_value",)) - refdes = self.design.get_value(path.to_tuple() + ("fp_refdes",)) - lcsc_part = self.design.get_value(path.to_tuple() + ("lcsc_part",)) + if "fp_is_footprint" in block.meta.members.node and scope_obj is not None: + footprint_name = self._design.get_value(path.to_tuple() + ("fp_footprint",)) + footprint_pinning = self._design.get_value(path.to_tuple() + ("fp_pinning",)) + mfr = self._design.get_value(path.to_tuple() + ("fp_mfr",)) + part = self._design.get_value(path.to_tuple() + ("fp_part",)) + value = self._design.get_value(path.to_tuple() + ("fp_value",)) + refdes = self._design.get_value(path.to_tuple() + ("fp_refdes",)) + lcsc_part = self._design.get_value(path.to_tuple() + ("lcsc_part",)) assert isinstance(footprint_name, str) assert isinstance(footprint_pinning, list) @@ -162,7 +156,9 @@ def process_blocklike( part_comps = [part, f"({mfr})" if mfr else ""] part_str = " ".join(filter(None, part_comps)) value_str = value if value else (part if part else "") - scope.footprints[path] = NetBlock(footprint_name, refdes, part_str, value_str, path, self.class_paths[path]) + scope_obj.footprints[path] = NetBlock( + footprint_name, refdes, part_str, value_str, path, self.class_paths[path] + ) for pin_spec in footprint_pinning: assert isinstance(pin_spec, str) @@ -172,23 +168,23 @@ def process_blocklike( pin_port_path = edgir.LocalPathList(pin_spec_split[1].split(".")) src_path = path.follow(pin_port_path, block)[0] - scope.edges.setdefault(src_path, []) # make sure there is a port entry so single-pin nets are named - scope.pins.setdefault(src_path, []).append(NetPin(path, pin_name)) + scope_obj.edges.setdefault(src_path, []) # make sure there is a port entry so single-pin nets are named + scope_obj.pins.setdefault(src_path, []).append(NetPin(path, pin_name)) for constraint_pair in block.constraints: - if scope is not None: + if scope_obj is not None: if constraint_pair.value.HasField("connected"): - self.process_connected(path, block, scope, constraint_pair.value.connected) + self.process_connected(path, block, scope_obj, constraint_pair.value.connected) elif constraint_pair.value.HasField("connectedArray"): for expanded_connect in constraint_pair.value.connectedArray.expanded: - self.process_connected(path, block, scope, expanded_connect) + self.process_connected(path, block, scope_obj, expanded_connect) elif constraint_pair.value.HasField("exported"): - self.process_exported(path, block, scope, constraint_pair.value.exported) + self.process_exported(path, block, scope_obj, constraint_pair.value.exported) elif constraint_pair.value.HasField("exportedArray"): for expanded_export in constraint_pair.value.exportedArray.expanded: - self.process_exported(path, block, scope, expanded_export) + self.process_exported(path, block, scope_obj, expanded_export) elif constraint_pair.value.HasField("exportedTunnel"): - self.process_exported(path, block, scope, constraint_pair.value.exportedTunnel) + self.process_exported(path, block, scope_obj, constraint_pair.value.exportedTunnel) def process_connected( self, path: TransformUtil.Path, current: edgir.EltTypes, scope: BoardScope, constraint: edgir.ConnectedExpr @@ -242,19 +238,25 @@ def connect_ports( raise ValueError(f"can't connect types {elt1}, {elt2}") @override - def visit_block(self, context: TransformUtil.TransformContext, block: edgir.BlockTypes) -> None: + def visit_block_scoped( + self, context: TransformUtil.TransformContext, scope: Optional[TransformUtil.Path], block: edgir.BlockTypes + ) -> None: self.path_traverse_order.append(context.path) - self.process_blocklike(context.path, block) + self.process_blocklike(context.path, scope, block) @override - def visit_link(self, context: TransformUtil.TransformContext, link: edgir.Link) -> None: + def visit_link_scoped( + self, context: TransformUtil.TransformContext, scope: Optional[TransformUtil.Path], link: edgir.Link + ) -> None: self.path_traverse_order.append(context.path) - self.process_blocklike(context.path, link) + self.process_blocklike(context.path, scope, link) @override - def visit_linkarray(self, context: TransformUtil.TransformContext, link: edgir.LinkArray) -> None: + def visit_linkarray_scoped( + self, context: TransformUtil.TransformContext, scope: Optional[TransformUtil.Path], link: edgir.LinkArray + ) -> None: self.path_traverse_order.append(context.path) - self.process_blocklike(context.path, link) + self.process_blocklike(context.path, scope, link) @override def visit_portlike(self, context: TransformUtil.TransformContext, port: edgir.PortLike) -> None: @@ -341,7 +343,7 @@ def scope_to_netlist(self, scope: BoardScope) -> Netlist: key=lambda pair: path_ordering[pair[0].port_component(must_have_port=False)], ) - board_refdes_prefix = self.design.get_value(("refdes_prefix",)) + board_refdes_prefix = self._design.get_value(("refdes_prefix",)) if board_refdes_prefix is not None: assert isinstance(board_refdes_prefix, str) net_prefix = board_refdes_prefix @@ -370,9 +372,10 @@ def port_ignored_paths(path: TransformUtil.Path) -> bool: # ignore link ports f return Netlist(netlist_footprints, netlist_nets) def run(self) -> Netlist: - self.transform_design(self.design.design) + self.transform_design(self._design.design) - return self.scope_to_netlist(self.all_scopes[0]) # TODO support multiple scopes + assert len(self.all_scopes) == 1, "TODO: support multiple boards" + return self.scope_to_netlist(self.all_scopes[0]) class PathShortener: diff --git a/edg/electronics_model/RefdesRefinementPass.py b/edg/electronics_model/RefdesRefinementPass.py index d1ef12fbb..851601856 100644 --- a/edg/electronics_model/RefdesRefinementPass.py +++ b/edg/electronics_model/RefdesRefinementPass.py @@ -1,7 +1,8 @@ -from typing import List, Tuple, Dict, Set, Optional +from typing import List, Tuple, Dict, Set, Optional, cast from typing_extensions import override +from .BoardScopedTransform import BoardScopedTransform from .. import edgir from ..core import CompiledDesign, TransformUtil from ..core.BaseRefinementPass import BaseRefinementPass @@ -17,36 +18,26 @@ def run(self, design: CompiledDesign) -> List[Tuple[edgir.LocalPath, edgir.Value ] -class RefdesTransform(TransformUtil.Transform): +class RefdesTransform(BoardScopedTransform): def __init__(self, design: CompiledDesign): - self.design = design + super().__init__(design) - board_refdes_prefix = self.design.get_value(("refdes_prefix",)) + board_refdes_prefix = design.get_value(("refdes_prefix",)) if board_refdes_prefix is None: self.board_refdes_prefix = "" else: assert isinstance(board_refdes_prefix, str) self.board_refdes_prefix = board_refdes_prefix - self.scopes: Dict[TransformUtil.Path, Optional[TransformUtil.Path]] = { - TransformUtil.Path.empty(): TransformUtil.Path.empty() - } - self.block_refdes_list: List[Tuple[TransformUtil.Path, str]] = [] # populated in traversal order self.refdes_last: Dict[Tuple[TransformUtil.Path, str], int] = {} # (scope, prefix) -> num @override - def visit_block(self, context: TransformUtil.TransformContext, block: edgir.BlockTypes) -> None: - scope = self.scopes[context.path] - internal_scope = scope - if "fp_is_wrapper" in block.meta.members.node: # wrapper internal blocks ignored - internal_scope = None - - for block_pair in block.blocks: - self.scopes[context.path.append_block(block_pair.name)] = internal_scope - + def visit_block_scoped( + self, context: TransformUtil.TransformContext, scope: Optional[TransformUtil.Path], block: edgir.BlockTypes + ) -> None: if "fp_is_footprint" in block.meta.members.node and scope is not None: - refdes_prefix = self.design.get_value(context.path.to_tuple() + ("fp_refdes_prefix",)) + refdes_prefix = self._design.get_value(context.path.to_tuple() + ("fp_refdes_prefix",)) assert isinstance(refdes_prefix, str) refdes_id = self.refdes_last.get((scope, refdes_prefix), 0) + 1 @@ -54,5 +45,5 @@ def visit_block(self, context: TransformUtil.TransformContext, block: edgir.Bloc self.block_refdes_list.append((context.path, self.board_refdes_prefix + refdes_prefix + str(refdes_id))) def run(self) -> List[Tuple[TransformUtil.Path, str]]: - self.transform_design(self.design.design) + self.transform_design(self._design.design) return self.block_refdes_list diff --git a/edg/electronics_model/SubboardBlock.py b/edg/electronics_model/SubboardBlock.py new file mode 100644 index 000000000..95458f9ab --- /dev/null +++ b/edg/electronics_model/SubboardBlock.py @@ -0,0 +1,79 @@ +from typing import List, Tuple, Any +from typing_extensions import TypeVar, override + +from ..core import * +from ..core.Core import Refable +from ..core.HdlUserExceptions import UnconnectableError +from .. import edgir + + +class SubboardBlock(Block): + """A block that is a sub-board, where all its blocks not marked external are part of a different board. + Provides the export_tap construct to tack connectors onto ports without breaking modeling. + + IMPORTANT: pseudo-blocks like bridges and adapters are considered internal blocks and do not affect + netlisting in the exterior board. In general, external blocks should only be connected via export-tap + and not direct connections where they may generate pseudo-blocks that end up in the wrong board.""" + + def __init__(self) -> None: + super().__init__() + self._external_blocks: List[Block] = [] + self._export_taps: List[Tuple[BasePort, BasePort]] = [] + + self.fp_subboard = self.Metadata("A") # dummy distinct value + self.fp_external_blocks = self.Parameter(ArrayStringExpr()) # names of all external blocks + + BlockType = TypeVar("BlockType", bound=Block) + + @override + def Block(self, tpe: BlockType, *args: Any, external: bool = False, **kwargs: Any) -> BlockType: + """Creates an internal Block. + By default, these are internal (part of the sub-board), unless marked external (in which case + it is placed in the containing board, eg for connectors). + Connector-pairs should be marked external, but will resolve to an external-internal pair.""" + ret = super().Block(tpe, *args, **kwargs) + if external: + self._external_blocks.append(ret) + return ret + + def export_tap(self, exterior_port: BasePort, internal_port: BasePort) -> None: + """Connects an exterior port (on self) to an interior port (on an internal block) + as a non-propagating connection which may coexist with other connections on the exterior port + (but not the interior port). + The interior port may not have parameters defined. + This is used to tack a connector onto a port that already has electrical modeling from the internal blocks.""" + if exterior_port._block_parent() is not self: + raise UnconnectableError("Exterior port must be on the block") + internal_port_parent = internal_port._block_parent() + if internal_port_parent is None or internal_port_parent._parent is not self: + raise UnconnectableError("Internal port must be a block within this block") + if exterior_port._type_of() != internal_port._type_of(): + raise UnconnectableError("Exported ports must be the same type") + self._export_taps.append((exterior_port, internal_port)) + + @override + def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refable.RefMapType) -> None: + self.assign(self.fp_external_blocks, [self._blocks.name_of(block) for block in self._external_blocks]) + + super()._populate_def_proto_hierarchy(pb, ref_map) + + for exterior_port, internal_port in self._export_taps: + internal_port_name = internal_port._name_from(self).replace(".", "_") + constraint_pb = edgir.add_pair(pb.constraints, f"(export_tap){internal_port_name}") + if isinstance(exterior_port, Vector): + constraint_pb.exportedArray.exterior_port.ref.CopyFrom(ref_map[exterior_port]) + constraint_pb.exportedArray.internal_block_port.ref.CopyFrom(ref_map[internal_port]) + constraint_pb.exportedArray.tap = True + else: + constraint_pb.exported.exterior_port.ref.CopyFrom(ref_map[exterior_port]) + constraint_pb.exported.internal_block_port.ref.CopyFrom(ref_map[internal_port]) + constraint_pb.exported.tap = True + + +class WrapperSubboardBlock(SubboardBlock): + """A wrapper block where the internal blocks are skipped for netlisting and used for modeling only. + Useful for eg, dev boards that only generate a connector or socket but re-use modeling from the raw subcircuit.""" + + def __init__(self) -> None: + super().__init__() + self.fp_subblocks_ignored = self.Metadata("B") # dummy distinct value diff --git a/edg/electronics_model/__init__.py b/edg/electronics_model/__init__.py index 6f74ad166..22e13774c 100644 --- a/edg/electronics_model/__init__.py +++ b/edg/electronics_model/__init__.py @@ -2,12 +2,12 @@ from .CircuitBlock import ( FootprintBlock, - WrapperFootprintBlock, CircuitPortBridge, CircuitPortAdapter, NetBlock, CircuitPort, ) +from .SubboardBlock import SubboardBlock, WrapperSubboardBlock from .Units import Farad, uFarad, nFarad, pFarad, MOhm, kOhm, Ohm, mOhm, Henry, uHenry, nHenry from .Units import Volt, mVolt, Watt, Amp, mAmp, uAmp, nAmp, pAmp diff --git a/edg/electronics_model/test_netlist_subboard.py b/edg/electronics_model/test_netlist_subboard.py new file mode 100644 index 000000000..1bf8d16d7 --- /dev/null +++ b/edg/electronics_model/test_netlist_subboard.py @@ -0,0 +1,213 @@ +import unittest + +from typing_extensions import override + +from .. import FootprintBlock, DesignTop +from ..core import TransformUtil +from .test_netlist import NetlistTestCase, TestFakeSource, TestFakeSink, NetBlock, Net, NetPin +from . import SubboardBlock, WrapperSubboardBlock, VoltageSink, Passive + + +class SinkWrapperExterior(FootprintBlock): + def __init__(self) -> None: + super().__init__() + + self.pos = self.Port(VoltageSink.empty()) # must remain empty + self.neg = self.Port(VoltageSink.empty()) + + @override + def contents(self) -> None: + super().contents() + + self.footprint( # only this footprint shows up + "L", + "Inductor_SMD:L_0603_1608Metric", # distinct footprint and value from internal blocks + {"1": self.pos, "2": self.neg}, + value="100", + ) + + +class SinkWrapperBlock(WrapperSubboardBlock): + """Wrapper block with a single footprint for two internal sinks whose footprints are ignored.""" + + def __init__(self) -> None: + super().__init__() + + self.pos = self.Port(VoltageSink.empty()) + self.neg = self.Port(VoltageSink.empty()) + + @override + def contents(self) -> None: + super().contents() + + # these define the modeling and are internal + self.model1 = self.Block(TestFakeSink()) + self.model2 = self.Block(TestFakeSink()) + self.vpos = self.connect(self.pos, self.model1.pos, self.model2.pos) + self.gnd = self.connect(self.neg, self.model1.neg, self.model2.neg) + + # these define the external interface block + self.wrapper = self.Block(SinkWrapperExterior(), external=True) + self.export_tap(self.pos, self.wrapper.pos) + self.export_tap(self.neg, self.wrapper.neg) + + +class TestWrapperCircuit(DesignTop): + @override + def contents(self) -> None: + super().contents() + + self.source = self.Block(TestFakeSource()) + self.sink = self.Block(SinkWrapperBlock()) + + self.vpos = self.connect(self.source.pos, self.sink.pos) + self.gnd = self.connect(self.source.neg, self.sink.neg) + + +class SinkWrapperPassiveExterior(FootprintBlock): + def __init__(self) -> None: + super().__init__() + + self.pos = self.Port(Passive.empty()) # must remain empty + self.neg = self.Port(Passive.empty()) + + @override + def contents(self) -> None: + super().contents() + + self.footprint( # only this footprint shows up + "L", + "Inductor_SMD:L_0603_1608Metric", # distinct footprint and value from internal blocks + {"1": self.pos, "2": self.neg}, + value="100", + ) + + +class SinkWrapperPassiveBlock(WrapperSubboardBlock): + """Wrapper block that taps the passive sub-port on its external port.""" + + def __init__(self) -> None: + super().__init__() + + self.pos = self.Port(VoltageSink.empty()) + self.neg = self.Port(VoltageSink.empty()) + + @override + def contents(self) -> None: + super().contents() + + # these define the modeling and are internal + self.model = self.Block(TestFakeSink()) + self.vpos = self.connect(self.pos, self.model.pos) + self.gnd = self.connect(self.neg, self.model.neg) + + # these define the external interface block + self.wrapper = self.Block(SinkWrapperPassiveExterior(), external=True) + self.export_tap(self.pos.net, self.wrapper.pos) + self.export_tap(self.neg.net, self.wrapper.neg) + + +class TestWrapperPassiveCircuit(DesignTop): + @override + def contents(self) -> None: + super().contents() + + self.source = self.Block(TestFakeSource()) + self.sink = self.Block(SinkWrapperPassiveBlock()) + + self.vpos = self.connect(self.source.pos, self.sink.pos) + self.gnd = self.connect(self.source.neg, self.sink.neg) + + +class NetlistWrapperTestCase(unittest.TestCase): + def test_wrapper_netlist(self) -> None: + net = NetlistTestCase.generate_net(TestWrapperCircuit) + + self.assertIn( + NetBlock( + "Inductor_SMD:L_0603_1608Metric", + "L1", + "", + "100", + ["sink", "wrapper"], + [ + "edg.electronics_model.test_netlist_subboard.SinkWrapperBlock", + "edg.electronics_model.test_netlist_subboard.SinkWrapperExterior", + ], + ), + net.blocks, + ) + self.assertEqual(len(net.blocks), 2) # should only generate top-level source and sink + + self.assertIn( + Net( + "vpos", + [NetPin(["source"], "1"), NetPin(["sink", "wrapper"], "1")], # ensure extraneous nets not generated + [ + TransformUtil.Path.empty().append_block("source").append_port("pos", "net"), + TransformUtil.Path.empty().append_block("sink").append_port("pos", "net"), + TransformUtil.Path.empty().append_block("sink", "wrapper").append_port("pos", "net"), + ], + ), + net.nets, + ) + self.assertIn( + Net( + "gnd", + [NetPin(["source"], "2"), NetPin(["sink", "wrapper"], "2")], + [ + TransformUtil.Path.empty().append_block("source").append_port("neg", "net"), + TransformUtil.Path.empty().append_block("sink").append_port("neg", "net"), + TransformUtil.Path.empty().append_block("sink", "wrapper").append_port("neg", "net"), + ], + ), + net.nets, + ) + self.assertEqual(len(net.nets), 2) # ensure empty nets pruned + + def test_wrapper_passive_netlist(self) -> None: + net = NetlistTestCase.generate_net(TestWrapperPassiveCircuit) + + self.assertIn( + NetBlock( + "Inductor_SMD:L_0603_1608Metric", + "L1", + "", + "100", + ["sink", "wrapper"], + [ + "edg.electronics_model.test_netlist_subboard.SinkWrapperPassiveBlock", + "edg.electronics_model.test_netlist_subboard.SinkWrapperPassiveExterior", + ], + ), + net.blocks, + ) + self.assertEqual(len(net.blocks), 2) # should only generate top-level source and sink + + self.assertIn( + Net( + "vpos", + [NetPin(["source"], "1"), NetPin(["sink", "wrapper"], "1")], # ensure extraneous nets not generated + [ + TransformUtil.Path.empty().append_block("source").append_port("pos", "net"), + TransformUtil.Path.empty().append_block("sink").append_port("pos", "net"), + TransformUtil.Path.empty().append_block("sink", "model").append_port("pos", "net"), + TransformUtil.Path.empty().append_block("sink", "wrapper").append_port("pos"), + ], + ), + net.nets, + ) + self.assertIn( + Net( + "gnd", + [NetPin(["source"], "2"), NetPin(["sink", "wrapper"], "2")], + [ + TransformUtil.Path.empty().append_block("source").append_port("neg", "net"), + TransformUtil.Path.empty().append_block("sink").append_port("neg", "net"), + TransformUtil.Path.empty().append_block("sink", "model").append_port("neg", "net"), + TransformUtil.Path.empty().append_block("sink", "wrapper").append_port("neg"), + ], + ), + net.nets, + ) + self.assertEqual(len(net.nets), 2) # ensure empty nets pruned diff --git a/edg/electronics_model/test_netlist_subboard_array.py b/edg/electronics_model/test_netlist_subboard_array.py new file mode 100644 index 000000000..9e350dfe7 --- /dev/null +++ b/edg/electronics_model/test_netlist_subboard_array.py @@ -0,0 +1,125 @@ +import unittest + +from typing_extensions import override + +from .. import FootprintBlock +from ..core import * +from ..core import TransformUtil +from .test_netlist import NetlistTestCase, TestFakeSource, NetBlock, Net, NetPin +from . import WrapperSubboardBlock, VoltageSink + + +class TestFakeSinkArray(GeneratorBlock, FootprintBlock): + def __init__(self) -> None: + super().__init__() + self.ports = self.Port(Vector(VoltageSink())) + self.generator_param(self.ports.requested()) + + @override + def generate(self) -> None: + super().generate() + pins = {} + self.ports.defined() + for port_name in self.get(self.ports.requested()): + pins[port_name] = self.ports.append_elt(VoltageSink(), port_name) + + self.footprint("R", "Resistor_SMD:R_0603_1608Metric", pins, value="1k") # load resistor + + +class SinkArrayWrapperExterior(GeneratorBlock, FootprintBlock): + def __init__(self) -> None: + super().__init__() + self.ports = self.Port(Vector(VoltageSink.empty())) # must remain empty + self.generator_param(self.ports.requested()) + + @override + def generate(self) -> None: + super().generate() + pins = {} + self.ports.defined() + for port_name in self.get(self.ports.requested()): + pins[port_name] = self.ports.append_elt(VoltageSink.empty(), port_name) + + self.footprint( # only this footprint shows up + "L", + "Inductor_SMD:L_0603_1608Metric", # distinct footprint and value from internal blocks + pins, + value="100", + ) + + +class SinkArrayWrapperBlock(WrapperSubboardBlock): + """Wrapper block with a single footprint for two internal sinks whose footprints are ignored.""" + + def __init__(self) -> None: + super().__init__() + self.model = self.Block(TestFakeSinkArray()) + self.ports = self.Export(self.model.ports) + + @override + def contents(self) -> None: + super().contents() + + self.wrapper = self.Block(SinkArrayWrapperExterior(), external=True) + self.export_tap(self.ports, self.wrapper.ports) + + +class TestArrayWrapperCircuit(DesignTop): + @override + def contents(self) -> None: + super().contents() + + self.source = self.Block(TestFakeSource()) + self.sink = self.Block(SinkArrayWrapperBlock()) + + self.vpos = self.connect(self.source.pos, self.sink.ports.request("1")) + self.gnd = self.connect(self.source.neg, self.sink.ports.request("2")) + + +class NetlistArrayWrapperTestCase(unittest.TestCase): + def test_wrapper_netlist(self) -> None: + net = NetlistTestCase.generate_net(TestArrayWrapperCircuit) + + self.assertIn( + NetBlock( + "Inductor_SMD:L_0603_1608Metric", + "L1", + "", + "100", + ["sink", "wrapper"], + [ + "edg.electronics_model.test_netlist_subboard_array.SinkArrayWrapperBlock", + "edg.electronics_model.test_netlist_subboard_array.SinkArrayWrapperExterior", + ], + ), + net.blocks, + ) + self.assertEqual(len(net.blocks), 2) # should only generate top-level source and sink + + self.assertIn( + Net( + "vpos", + [NetPin(["source"], "1"), NetPin(["sink", "wrapper"], "1")], # ensure extraneous nets not generated + [ + TransformUtil.Path.empty().append_block("source").append_port("pos", "net"), + TransformUtil.Path.empty().append_block("sink").append_port("ports", "1", "net"), + TransformUtil.Path.empty().append_block("sink", "model").append_port("ports", "1", "net"), + TransformUtil.Path.empty().append_block("sink", "wrapper").append_port("ports", "1", "net"), + ], + ), + net.nets, + ) + self.assertIn( + Net( + "gnd", + [NetPin(["source"], "2"), NetPin(["sink", "wrapper"], "2")], + [ + TransformUtil.Path.empty().append_block("source").append_port("neg", "net"), + TransformUtil.Path.empty().append_block("sink").append_port("ports", "2", "net"), + TransformUtil.Path.empty().append_block("sink", "model").append_port("ports", "2", "net"), + TransformUtil.Path.empty().append_block("sink", "wrapper").append_port("ports", "2", "net"), + ], + ), + net.nets, + ) + self.assertEqual(len(net.nets), 2) # ensure empty nets pruned diff --git a/edg/electronics_model/test_netlist_wrapper.py b/edg/electronics_model/test_netlist_wrapper.py deleted file mode 100644 index b6c854294..000000000 --- a/edg/electronics_model/test_netlist_wrapper.py +++ /dev/null @@ -1,87 +0,0 @@ -import unittest - -from typing_extensions import override - -from ..core import Block, TransformUtil -from .test_netlist import NetlistTestCase, TestFakeSource, TestFakeSink, NetBlock, Net, NetPin -from . import WrapperFootprintBlock, VoltageSink - - -class SinkWrapperBlock(WrapperFootprintBlock): - """Wrapper block with a single footprint for two internal sinks whose footprints are ignored.""" - - def __init__(self) -> None: - super().__init__() - - self.pos = self.Port(VoltageSink.empty()) - self.neg = self.Port(VoltageSink.empty()) - - @override - def contents(self) -> None: - super().contents() - - self.model1 = self.Block(TestFakeSink()) - self.model2 = self.Block(TestFakeSink()) - self.vpos = self.connect(self.pos, self.model1.pos, self.model2.pos) - self.gnd = self.connect(self.neg, self.model1.neg, self.model2.neg) - - self.footprint( # only this footprint shows up - "L", - "Inductor_SMD:L_0603_1608Metric", # distinct footprint and value from internal blocks - {"1": self.pos, "2": self.neg}, - value="100", - ) - - -class TestWrapperCircuit(Block): - @override - def contents(self) -> None: - super().contents() - - self.source = self.Block(TestFakeSource()) - self.sink = self.Block(SinkWrapperBlock()) - - self.vpos = self.connect(self.source.pos, self.sink.pos) - self.gnd = self.connect(self.source.neg, self.sink.neg) - - -class NetlistWrapperTestCase(unittest.TestCase): - def test_warpper_netlist(self) -> None: - net = NetlistTestCase.generate_net(TestWrapperCircuit) - - self.assertIn( - NetBlock( - "Inductor_SMD:L_0603_1608Metric", - "L1", - "", - "100", - ["sink"], - ["edg.electronics_model.test_netlist_wrapper.SinkWrapperBlock"], - ), - net.blocks, - ) - self.assertEqual(len(net.blocks), 2) # should only generate top-level source and sink - - self.assertEqual(len(net.nets), 2) # ensure empty nets pruned - self.assertIn( - Net( - "vpos", - [NetPin(["source"], "1"), NetPin(["sink"], "1")], # ensure extraneous nets not generated - [ - TransformUtil.Path.empty().append_block("source").append_port("pos", "net"), - TransformUtil.Path.empty().append_block("sink").append_port("pos", "net"), - ], - ), - net.nets, - ) - self.assertIn( - Net( - "gnd", - [NetPin(["source"], "2"), NetPin(["sink"], "2")], - [ - TransformUtil.Path.empty().append_block("source").append_port("neg", "net"), - TransformUtil.Path.empty().append_block("sink").append_port("neg", "net"), - ], - ), - net.nets, - ) diff --git a/edg/hdl_server/__main__.py b/edg/hdl_server/__main__.py index 001f996a4..15a1d33ad 100644 --- a/edg/hdl_server/__main__.py +++ b/edg/hdl_server/__main__.py @@ -9,7 +9,7 @@ from ..core import * from ..core.Core import NonLibraryProperty -EDG_PROTO_VERSION = 11 +EDG_PROTO_VERSION = 12 class LibraryElementIndexer: diff --git a/edg/parts/Distance_Vl53l0x.py b/edg/parts/Distance_Vl53l0x.py index 6d24fceaf..74e81913e 100644 --- a/edg/parts/Distance_Vl53l0x.py +++ b/edg/parts/Distance_Vl53l0x.py @@ -1,6 +1,7 @@ from typing_extensions import override from ..abstract_parts import * +from .PassiveConnector_Header import PinSocket254 from .JlcPart import JlcPart @@ -100,7 +101,7 @@ def generate(self) -> None: self.vdd_cap[1] = self.Block(DecouplingCapacitor(4.7 * uFarad(tol=0.2))).connected(self.gnd, self.pwr) -class Vl53l0xConnector(Vl53l0x, WrapperFootprintBlock): +class Vl53l0xConnector(Vl53l0x, WrapperSubboardBlock): """Connector to an external VL53L0X breakout board. Uses the pinout from the Adafruit product: https://www.adafruit.com/product/3317 This has an onboard 2.8v regulator, but thankfully the IO tolerance is not referenced to Vdd @@ -110,18 +111,13 @@ class Vl53l0xConnector(Vl53l0x, WrapperFootprintBlock): @override def generate(self) -> None: super().generate() - self.footprint( - "J", - "Connector_PinSocket_2.54mm:PinSocket_1x06_P2.54mm_Vertical", - { - "1": self.pwr, - "2": self.gnd, - "3": self.i2c.scl, - "4": self.i2c.sda, - "5": self.ic.gpio1, - "6": self.ic.xshut, - }, - ) + self.conn = self.Block(PinSocket254(6), external=True) + self.export_tap(self.pwr.net, self.conn.pins.request("1")) + self.export_tap(self.gnd.net, self.conn.pins.request("2")) + self.export_tap(self.i2c.scl.net, self.conn.pins.request("3")) + self.export_tap(self.i2c.sda.net, self.conn.pins.request("4")) + self.export_tap(self.int.net, self.conn.pins.request("5")) + self.export_tap(self.reset.net, self.conn.pins.request("6")) class Vl53l0xArray(DistanceSensor, GeneratorBlock): diff --git a/edg/parts/StepperDriver_A4988.py b/edg/parts/StepperDriver_A4988.py index ea925e75a..da0162dda 100644 --- a/edg/parts/StepperDriver_A4988.py +++ b/edg/parts/StepperDriver_A4988.py @@ -221,83 +221,61 @@ def generate(self) -> None: self.connect(self.pwr_logic.as_digital_source(), self.ic.sleep) -class PololuA4988(BrushedMotorDriver, WrapperFootprintBlock, GeneratorBlock): - """Pololu breakout board for the A4988 stepper driver. Adjustable current limit with onboard trimpot.""" - - def __init__(self, step_resolution: IntLike = 16): +class PololuA4988_Device(InternalSubcircuit, FootprintBlock, GeneratorBlock): + def __init__(self, step_resolution: IntLike) -> None: super().__init__() - self.step_resolution = self.ArgParameter(step_resolution, doc="microstepping resolution (1, 2, 4, 8, or 16)") + self.step_resolution = self.ArgParameter(step_resolution) self.generator_param(self.step_resolution) - self.model = self.Block(A4988_Device()) - self.gnd = self.Export(self.model.gnd, [Common]) - self.pwr = self.Export(self.model.vbb1) - self.pwr_logic = self.Export(self.model.vdd) + self.gnd = self.Port(Ground.empty()) + self.pwr = self.Port(VoltageSink.empty()) + self.pwr_logic = self.Port(VoltageSink.empty()) - self.step = self.Export(self.model.step) - self.dir = self.Export(self.model.dir) + self.step = self.Port(DigitalSink.empty()) + self.dir = self.Port(DigitalSink.empty()) - self.enable = self.Port(DigitalSink.empty(), optional=True, doc="disables FET outputs when high") - self.reset = self.Port(DigitalSink.empty(), optional=True, doc="forces translator to Home state when low") - self.sleep = self.Port( - DigitalSink.empty(), optional=True, doc="disables device (to reduce current draw) when low" - ) + self.enable = self.Port(DigitalSink.empty(), optional=True) + self.reset = self.Port(DigitalSink.empty(), optional=True) + self.sleep = self.Port(DigitalSink.empty(), optional=True) self.generator_param(self.enable.is_connected(), self.reset.is_connected(), self.sleep.is_connected()) - self.out1a = self.Export(self.model.out1a) - self.out1b = self.Export(self.model.out1b) - self.out2a = self.Export(self.model.out2a) - self.out2b = self.Export(self.model.out2b) + self.out1a = self.Port(DigitalSource.empty()) + self.out1b = self.Port(DigitalSource.empty()) + self.out2a = self.Port(DigitalSource.empty()) + self.out2b = self.Port(DigitalSource.empty()) + + # unlike the other ports, these are directly connected and not tapped and must have modeling + self.ms1 = self.Port(DigitalSink()) + self.ms2 = self.Port(DigitalSink()) + self.ms3 = self.Port(DigitalSink()) @override def generate(self) -> None: - super().generate() - - self.connect(self.pwr, self.model.vbb2) - # TODO: deduplicate w/ A4988 application circuit step_resolution = self.get(self.step_resolution) if step_resolution == 1: # full step - self.connect(self.gnd.as_digital_source(), self.model.ms1, self.model.ms2, self.model.ms3) + ms1: HasPassivePort = self.gnd + ms2: HasPassivePort = self.gnd + ms3: HasPassivePort = self.gnd elif step_resolution == 2: # half step - self.connect(self.gnd.as_digital_source(), self.model.ms2, self.model.ms3) - self.connect(self.pwr_logic.as_digital_source(), self.model.ms1) + ms1 = self.pwr_logic + ms2 = self.gnd + ms3 = self.gnd elif step_resolution == 4: # quarter step - self.connect(self.gnd.as_digital_source(), self.model.ms1, self.model.ms3) - self.connect(self.pwr_logic.as_digital_source(), self.model.ms2) + ms1 = self.gnd + ms2 = self.pwr_logic + ms3 = self.gnd elif step_resolution == 8: # eighth step - self.connect(self.gnd.as_digital_source(), self.model.ms3) - self.connect(self.pwr_logic.as_digital_source(), self.model.ms1, self.model.ms2) + ms1 = self.pwr_logic + ms2 = self.pwr_logic + ms3 = self.gnd elif step_resolution == 16: # sixteenth step - self.connect(self.pwr_logic.as_digital_source(), self.model.ms1, self.model.ms2, self.model.ms3) + ms1 = self.pwr_logic + ms2 = self.pwr_logic + ms3 = self.pwr_logic else: raise ValueError(f"unknown step_resolution {step_resolution}") - if self.get(self.enable.is_connected()): - self.connect(self.enable, self.model.enable) - else: - self.connect(self.gnd.as_digital_source(), self.model.enable) - - if self.get(self.reset.is_connected()): - self.connect(self.reset, self.model.reset) - else: - self.connect(self.pwr_logic.as_digital_source(), self.model.reset) - - if self.get(self.sleep.is_connected()): - self.connect(self.sleep, self.model.sleep) - else: - self.connect(self.pwr_logic.as_digital_source(), self.model.sleep) - - # these are implemented internal to the breakout board - (self.dummy_vreg,), _ = self.chain(self.Block(DummyVoltageSink()), self.model.vreg) - (self.dummy_vcp,), _ = self.chain(self.Block(DummyPassive()), self.model.vcp) - (self.dummy_cp1,), _ = self.chain(self.Block(DummyPassive()), self.model.cp1) - (self.dummy_cp2,), _ = self.chain(self.Block(DummyPassive()), self.model.cp2) - (self.dummy_rosc,), _ = self.chain(self.Block(DummyPassive()), self.model.rosc) - (self.dummy_ref,), _ = self.chain(self.Block(DummyAnalogSource()), self.model.ref) - (self.dummy_sense1,), _ = self.chain(self.Block(DummyPassive()), self.model.sense1) - (self.dummy_sense2,), _ = self.chain(self.Block(DummyPassive()), self.model.sense2) - self.footprint( "U", "edg:DIP-16_W12.70mm", @@ -312,14 +290,56 @@ def generate(self) -> None: "8": self.gnd, "9": self.dir, "10": self.step, - "11": self.model.sleep, - "12": self.model.reset, - "13": self.model.ms3, - "14": self.model.ms2, - "15": self.model.ms1, - "16": self.model.enable, + "11": self.sleep if self.get(self.sleep.is_connected()) else self.pwr_logic, + "12": self.reset if self.get(self.reset.is_connected()) else self.pwr_logic, + "13": ms3, + "14": ms2, + "15": ms1, + "16": self.enable if self.get(self.enable.is_connected()) else self.gnd, }, mfr="Pololu", part="1182", datasheet="https://www.pololu.com/product/1182", ) + + +class PololuA4988(BrushedMotorDriver, WrapperSubboardBlock): + """Pololu breakout board for the A4988 stepper driver. Adjustable current limit with onboard trimpot.""" + + def __init__(self, step_resolution: IntLike = 16): + super().__init__() + self.step_resolution = self.ArgParameter(step_resolution, doc="microstepping resolution (1, 2, 4, 8, or 16)") + + self.model = self.Block(A4988(itrip=2 * Amp(tol=0.15))) + self.gnd = self.Export(self.model.gnd, [Common]) + self.pwr = self.Export(self.model.pwr) + self.pwr_logic = self.Export(self.model.pwr_logic) + + self.step = self.Export(self.model.step) + self.dir = self.Export(self.model.dir) + self.enable = self.Export(self.model.enable, optional=True) + self.reset = self.Export(self.model.reset, optional=True) + self.sleep = self.Export(self.model.sleep, optional=True) + + self.out1a = self.Export(self.model.out1a) + self.out1b = self.Export(self.model.out1b) + self.out2a = self.Export(self.model.out2a) + self.out2b = self.Export(self.model.out2b) + + @override + def contents(self) -> None: + super().contents() + + self.wrapper = self.Block(PololuA4988_Device(self.step_resolution), external=True) + self.export_tap(self.gnd, self.wrapper.gnd) + self.export_tap(self.pwr, self.wrapper.pwr) + self.export_tap(self.pwr_logic, self.wrapper.pwr_logic) + self.export_tap(self.step, self.wrapper.step) + self.export_tap(self.dir, self.wrapper.dir) + self.export_tap(self.enable, self.wrapper.enable) + self.export_tap(self.reset, self.wrapper.reset) + self.export_tap(self.sleep, self.wrapper.sleep) + self.export_tap(self.out1a, self.wrapper.out1a) + self.export_tap(self.out1b, self.wrapper.out1b) + self.export_tap(self.out2a, self.wrapper.out2a) + self.export_tap(self.out2b, self.wrapper.out2b) diff --git a/examples/PcbBot/PcbBot.net.ref b/examples/PcbBot/PcbBot.net.ref index 7f2f7addd..c54a3cb5e 100644 --- a/examples/PcbBot/PcbBot.net.ref +++ b/examples/PcbBot/PcbBot.net.ref @@ -509,11 +509,11 @@ (footprint "Connector_PinSocket_2.54mm:PinSocket_1x06_P2.54mm_Vertical") (property (name "Sheetname") (value "tof")) (property (name "Sheetfile") (value "edg.parts.Distance_Vl53l0x.Vl53l0xArray")) - (property (name "edg_path") (value "tof.elt[0]")) + (property (name "edg_path") (value "tof.elt[0].conn")) (property (name "edg_short_path") (value "tof.elt[0]")) (property (name "edg_refdes") (value "J4")) - (property (name "edg_part") (value "")) - (property (name "edg_value") (value "")) + (property (name "edg_part") (value "PinSocket2.54 1x6 (Generic)")) + (property (name "edg_value") (value "PinSocket2.54 1x6")) (sheetpath (names "/tof/") (tstamps "/02a3014a/")) (tstamps "081e022e")) (comp (ref "J5") @@ -521,11 +521,11 @@ (footprint "Connector_PinSocket_2.54mm:PinSocket_1x06_P2.54mm_Vertical") (property (name "Sheetname") (value "tof")) (property (name "Sheetfile") (value "edg.parts.Distance_Vl53l0x.Vl53l0xArray")) - (property (name "edg_path") (value "tof.elt[1]")) + (property (name "edg_path") (value "tof.elt[1].conn")) (property (name "edg_short_path") (value "tof.elt[1]")) (property (name "edg_refdes") (value "J5")) - (property (name "edg_part") (value "")) - (property (name "edg_value") (value "")) + (property (name "edg_part") (value "PinSocket2.54 1x6 (Generic)")) + (property (name "edg_value") (value "PinSocket2.54 1x6")) (sheetpath (names "/tof/") (tstamps "/02a3014a/")) (tstamps "0820022f")) (comp (ref "J6") @@ -533,11 +533,11 @@ (footprint "Connector_PinSocket_2.54mm:PinSocket_1x06_P2.54mm_Vertical") (property (name "Sheetname") (value "tof")) (property (name "Sheetfile") (value "edg.parts.Distance_Vl53l0x.Vl53l0xArray")) - (property (name "edg_path") (value "tof.elt[2]")) + (property (name "edg_path") (value "tof.elt[2].conn")) (property (name "edg_short_path") (value "tof.elt[2]")) (property (name "edg_refdes") (value "J6")) - (property (name "edg_part") (value "")) - (property (name "edg_value") (value "")) + (property (name "edg_part") (value "PinSocket2.54 1x6 (Generic)")) + (property (name "edg_value") (value "PinSocket2.54 1x6")) (sheetpath (names "/tof/") (tstamps "/02a3014a/")) (tstamps "08220230")) (comp (ref "J7") @@ -545,11 +545,11 @@ (footprint "Connector_PinSocket_2.54mm:PinSocket_1x06_P2.54mm_Vertical") (property (name "Sheetname") (value "tof")) (property (name "Sheetfile") (value "edg.parts.Distance_Vl53l0x.Vl53l0xArray")) - (property (name "edg_path") (value "tof.elt[3]")) + (property (name "edg_path") (value "tof.elt[3].conn")) (property (name "edg_short_path") (value "tof.elt[3]")) (property (name "edg_refdes") (value "J7")) - (property (name "edg_part") (value "")) - (property (name "edg_value") (value "")) + (property (name "edg_part") (value "PinSocket2.54 1x6 (Generic)")) + (property (name "edg_value") (value "PinSocket2.54 1x6")) (sheetpath (names "/tof/") (tstamps "/02a3014a/")) (tstamps "08240231")) (comp (ref "R10") @@ -1595,13 +1595,13 @@ (net (code 28) (name "led.package.k") (node (ref D7) (pin 1)) (node (ref R9) (pin 1))) -(net (code 29) (name "tof.elt[0].ic.gpio1") +(net (code 29) (name "tof.elt[0].int") (node (ref J4) (pin 5))) -(net (code 30) (name "tof.elt[1].ic.gpio1") +(net (code 30) (name "tof.elt[1].int") (node (ref J5) (pin 5))) -(net (code 31) (name "tof.elt[2].ic.gpio1") +(net (code 31) (name "tof.elt[2].int") (node (ref J6) (pin 5))) -(net (code 32) (name "tof.elt[3].ic.gpio1") +(net (code 32) (name "tof.elt[3].int") (node (ref J7) (pin 5))) (net (code 33) (name "imu.int1") (node (ref U5) (pin 4))) diff --git a/examples/PcbBot/PcbBot.svgpcb.js b/examples/PcbBot/PcbBot.svgpcb.js index 584d3a526..e215059f4 100644 --- a/examples/PcbBot/PcbBot.svgpcb.js +++ b/examples/PcbBot/PcbBot.svgpcb.js @@ -210,22 +210,22 @@ const R9 = board.add(R_0603_1608Metric, { translate: pt(2.510, 3.494), rotate: 0, id: 'R9' }) -// tof.elt[0] +// tof.elt[0].conn const J4 = board.add(PinSocket_1x06_P2_54mm_Vertical, { translate: pt(1.457, 2.309), rotate: 0, id: 'J4' }) -// tof.elt[1] +// tof.elt[1].conn const J5 = board.add(PinSocket_1x06_P2_54mm_Vertical, { translate: pt(1.636, 2.309), rotate: 0, id: 'J5' }) -// tof.elt[2] +// tof.elt[2].conn const J6 = board.add(PinSocket_1x06_P2_54mm_Vertical, { translate: pt(1.815, 2.309), rotate: 0, id: 'J6' }) -// tof.elt[3] +// tof.elt[3].conn const J7 = board.add(PinSocket_1x06_P2_54mm_Vertical, { translate: pt(1.994, 2.309), rotate: 0, id: 'J7' @@ -570,10 +570,10 @@ board.setNetlist([ {name: "mcu.program_en_node", pads: [["U3", "3"], ["R8", "2"], ["C7", "1"]]}, {name: "mcu.program_boot_node", pads: [["U3", "27"], ["SW2", "1"], ["D7", "2"]]}, {name: "led.package.k", pads: [["D7", "1"], ["R9", "1"]]}, - {name: "tof.elt[0].ic.gpio1", pads: [["J4", "5"]]}, - {name: "tof.elt[1].ic.gpio1", pads: [["J5", "5"]]}, - {name: "tof.elt[2].ic.gpio1", pads: [["J6", "5"]]}, - {name: "tof.elt[3].ic.gpio1", pads: [["J7", "5"]]}, + {name: "tof.elt[0].int", pads: [["J4", "5"]]}, + {name: "tof.elt[1].int", pads: [["J5", "5"]]}, + {name: "tof.elt[2].int", pads: [["J6", "5"]]}, + {name: "tof.elt[3].int", pads: [["J7", "5"]]}, {name: "imu.int1", pads: [["U5", "4"]]}, {name: "imu.int2", pads: [["U5", "9"]]}, {name: "mag.drdy", pads: [["U6", "15"]]}, diff --git a/examples/RobotDriver/RobotDriver.net.ref b/examples/RobotDriver/RobotDriver.net.ref index 09327fa89..ee384177b 100644 --- a/examples/RobotDriver/RobotDriver.net.ref +++ b/examples/RobotDriver/RobotDriver.net.ref @@ -341,11 +341,11 @@ (footprint "Connector_PinSocket_2.54mm:PinSocket_1x06_P2.54mm_Vertical") (property (name "Sheetname") (value "tof")) (property (name "Sheetfile") (value "edg.parts.Distance_Vl53l0x.Vl53l0xArray")) - (property (name "edg_path") (value "tof.elt[0]")) + (property (name "edg_path") (value "tof.elt[0].conn")) (property (name "edg_short_path") (value "tof.elt[0]")) (property (name "edg_refdes") (value "J3")) - (property (name "edg_part") (value "")) - (property (name "edg_value") (value "")) + (property (name "edg_part") (value "PinSocket2.54 1x6 (Generic)")) + (property (name "edg_value") (value "PinSocket2.54 1x6")) (sheetpath (names "/tof/") (tstamps "/02a3014a/")) (tstamps "081e022e")) (comp (ref "J4") @@ -353,11 +353,11 @@ (footprint "Connector_PinSocket_2.54mm:PinSocket_1x06_P2.54mm_Vertical") (property (name "Sheetname") (value "tof")) (property (name "Sheetfile") (value "edg.parts.Distance_Vl53l0x.Vl53l0xArray")) - (property (name "edg_path") (value "tof.elt[1]")) + (property (name "edg_path") (value "tof.elt[1].conn")) (property (name "edg_short_path") (value "tof.elt[1]")) (property (name "edg_refdes") (value "J4")) - (property (name "edg_part") (value "")) - (property (name "edg_value") (value "")) + (property (name "edg_part") (value "PinSocket2.54 1x6 (Generic)")) + (property (name "edg_value") (value "PinSocket2.54 1x6")) (sheetpath (names "/tof/") (tstamps "/02a3014a/")) (tstamps "0820022f")) (comp (ref "J5") @@ -365,11 +365,11 @@ (footprint "Connector_PinSocket_2.54mm:PinSocket_1x06_P2.54mm_Vertical") (property (name "Sheetname") (value "tof")) (property (name "Sheetfile") (value "edg.parts.Distance_Vl53l0x.Vl53l0xArray")) - (property (name "edg_path") (value "tof.elt[2]")) + (property (name "edg_path") (value "tof.elt[2].conn")) (property (name "edg_short_path") (value "tof.elt[2]")) (property (name "edg_refdes") (value "J5")) - (property (name "edg_part") (value "")) - (property (name "edg_value") (value "")) + (property (name "edg_part") (value "PinSocket2.54 1x6 (Generic)")) + (property (name "edg_value") (value "PinSocket2.54 1x6")) (sheetpath (names "/tof/") (tstamps "/02a3014a/")) (tstamps "08220230")) (comp (ref "R9") @@ -1188,11 +1188,11 @@ (node (ref SW1) (pin 1))) (net (code 19) (name "mcu.ic.io2") (node (ref U3) (pin 24))) -(net (code 20) (name "tof.elt[0].ic.gpio1") +(net (code 20) (name "tof.elt[0].int") (node (ref J3) (pin 5))) -(net (code 21) (name "tof.elt[1].ic.gpio1") +(net (code 21) (name "tof.elt[1].int") (node (ref J4) (pin 5))) -(net (code 22) (name "tof.elt[2].ic.gpio1") +(net (code 22) (name "tof.elt[2].int") (node (ref J5) (pin 5))) (net (code 23) (name "lcd.reset") (node (ref U3) (pin 12)) diff --git a/examples/RobotDriver/RobotDriver.svgpcb.js b/examples/RobotDriver/RobotDriver.svgpcb.js index 2a1ae5d21..803d2629f 100644 --- a/examples/RobotDriver/RobotDriver.svgpcb.js +++ b/examples/RobotDriver/RobotDriver.svgpcb.js @@ -140,17 +140,17 @@ const C6 = board.add(C_0603_1608Metric, { translate: pt(1.987, 0.903), rotate: 0, id: 'C6' }) -// tof.elt[0] +// tof.elt[0].conn const J3 = board.add(PinSocket_1x06_P2_54mm_Vertical, { translate: pt(0.803, 2.311), rotate: 0, id: 'J3' }) -// tof.elt[1] +// tof.elt[1].conn const J4 = board.add(PinSocket_1x06_P2_54mm_Vertical, { translate: pt(0.982, 2.311), rotate: 0, id: 'J4' }) -// tof.elt[2] +// tof.elt[2].conn const J5 = board.add(PinSocket_1x06_P2_54mm_Vertical, { translate: pt(1.161, 2.311), rotate: 0, id: 'J5' @@ -436,9 +436,9 @@ board.setNetlist([ {name: "mcu.program_en_node", pads: [["U3", "3"], ["R8", "2"], ["C6", "1"]]}, {name: "mcu.program_boot_node", pads: [["U3", "25"], ["SW1", "1"]]}, {name: "mcu.ic.io2", pads: [["U3", "24"]]}, - {name: "tof.elt[0].ic.gpio1", pads: [["J3", "5"]]}, - {name: "tof.elt[1].ic.gpio1", pads: [["J4", "5"]]}, - {name: "tof.elt[2].ic.gpio1", pads: [["J5", "5"]]}, + {name: "tof.elt[0].int", pads: [["J3", "5"]]}, + {name: "tof.elt[1].int", pads: [["J4", "5"]]}, + {name: "tof.elt[2].int", pads: [["J5", "5"]]}, {name: "lcd.reset", pads: [["U3", "12"], ["J6", "9"]]}, {name: "lcd.spi.sck", pads: [["U3", "9"], ["J6", "11"]]}, {name: "lcd.spi.mosi", pads: [["U3", "8"], ["J6", "12"]]}, diff --git a/proto/edgir/expr.proto b/proto/edgir/expr.proto index 8ca7c9839..649cb6d0a 100644 --- a/proto/edgir/expr.proto +++ b/proto/edgir/expr.proto @@ -361,6 +361,11 @@ message ConnectedExpr { message ExportedExpr { ValueExpr exterior_port = 1; ValueExpr internal_block_port = 2; + bool tap = 4; // if true, this is a tap, which allows the exterior port to have multiple internal connections + // (one non-tap connection, and optionally multiple tap connections) + // These additional rules apply to export taps: + // - parameters do not propagate, internal port should not have parameter values + // - the internal port's link resolves to the external port repeated ExportedExpr expanded = 3; // see comment in ConnectedExpr }