Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion edg/electronics_model/BoardScopedTransform.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@ def visit_linkarray_scoped(
def visit_block(self, context: TransformContext, block: edgir.HierarchyBlock) -> None:
parent_scope = self._board_parent_scopes[context.path]

if "fp_subboard" in block.meta.members.node:
if "fp_subboard" in block.meta.members.node or "fp_subboard_connector_pair" 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
elif "fp_subboard_connector_pair" in block.meta.members.node:
internal_scope = self._board_scopes[TransformUtil.Path.empty().append_block(*context.path.blocks[:-1])]
else:
internal_scope = context.path
else:
Expand Down
5 changes: 2 additions & 3 deletions edg/electronics_model/NetlistGenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,9 @@ def process_blocklike(
else:
scope_obj = None

if isinstance(block, edgir.HierarchyBlock) and "fp_subboard" in block.meta.members.node:
# only valid for sub-board blocks, where some things happen in the parent scope
if isinstance(block, edgir.HierarchyBlock):
parent_scope = self._board_parent_scopes[path]
if parent_scope is not None:
if parent_scope is not None and parent_scope != scope:
parent_scope_obj: Optional[BoardScope] = self.scopes[parent_scope]
else:
parent_scope_obj = None
Expand Down
46 changes: 37 additions & 9 deletions edg/electronics_model/SubboardBlock.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,14 @@
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."""
@non_library
class HasSubboardBlockApi(Block):
"""Base class that provides the subboard construction API."""

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)
Expand Down Expand Up @@ -70,10 +64,44 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab
constraint_pb.exported.tap = True


class SubboardBlock(HasSubboardBlockApi, 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.fp_subboard = self.Metadata("A") # dummy distinct value


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


class SubboardConnectorPair(HasSubboardBlockApi, Block):
"""A block meant for a connector pair, one in the exterior and one in the interior, of a SubboardBlock.
When in a SubboardBlock scope and marked external, this inherits the parent and self board scope of its container,
so inner Blocks marked external are part of the SubboardBlock's parent scope, while internal Blocks
are part of the SubboardBlock's inner scope.

Inner SubboardConnectorPairs marked external similarly inherit board scopes of its containing
SubboardConnectorPair.

Recommended convention, similar to SubboardBlock, is to directly export ports from the internal Block
while export-tapping ports from the external Block. The external Block should be generated first
for refdes ordering. This block's pin numbering should correspond to the external Block.

These should not be instantiated outside a SubboardBlock or SubboardConnectorPair. Bad things can happen.
"""

def __init__(self) -> None:
super().__init__()
self.fp_subboard_connector_pair = self.Metadata("C") # dummy distinct value
2 changes: 1 addition & 1 deletion edg/electronics_model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
NetBlock,
CircuitPort,
)
from .SubboardBlock import SubboardBlock, WrapperSubboardBlock
from .SubboardBlock import SubboardBlock, WrapperSubboardBlock, SubboardConnectorPair

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
Expand Down
239 changes: 239 additions & 0 deletions edg/electronics_model/test_netlist_connector_pair.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
import unittest

from typing_extensions import override

from .NetlistGenerator import NetlistTransform
from .. import FootprintBlock, DesignTop, ScalaCompiler, RefdesRefinementPass, SubboardConnectorPair
from ..core import TransformUtil
from .test_netlist import TestFakeSource, TestFakeSink, NetBlock, Net, NetPin
from . import SubboardBlock, VoltageSink, Passive


class SinkExteriorConnector(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
"J",
"Connector_PinSocket_2.54mm:PinSocket_1x02_P2.54mm_Vertical",
{"1": self.pos, "2": self.neg},
)


class SinkInternalConnector(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
"J",
"Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical",
{"1": self.pos, "2": self.neg},
)


class SinkConnectorPair(SubboardConnectorPair):
def __init__(self) -> None:
super().__init__()

self.ext = self.Block(SinkExteriorConnector(), external=True)
self.int = self.Block(SinkInternalConnector())
self.pos = self.Export(self.int.pos)
self.neg = self.Export(self.int.neg)
self.export_tap(self.pos, self.ext.pos)
self.export_tap(self.neg, self.ext.neg)


class SinkConnectorPairBlock(SubboardBlock):
"""Subboard block with a connector pair and internal circuits."""

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 blocks are part of the sub-board
self.inner1 = self.Block(TestFakeSink())
self.inner2 = self.Block(TestFakeSink())
self.vpos = self.connect(self.pos, self.inner1.pos, self.inner2.pos)
self.gnd = self.connect(self.neg, self.inner1.neg, self.inner2.neg)

# these define the external interface block
self.conn = self.Block(SinkConnectorPair(), external=True)
self.export_tap(self.pos.net, self.conn.pos)
self.export_tap(self.neg.net, self.conn.neg)


class TestConnectorPairCircuit(DesignTop):
@override
def contents(self) -> None:
super().contents()

self.source = self.Block(TestFakeSource())
self.sink = self.Block(SinkConnectorPairBlock())

self.vpos = self.connect(self.source.pos, self.sink.pos)
self.gnd = self.connect(self.source.neg, self.sink.neg)


class NetlistConnectorPairTestCase(unittest.TestCase):
def test_subboard_netlist(self) -> None:
compiled = ScalaCompiler.compile(TestConnectorPairCircuit)
compiled.append_values(RefdesRefinementPass().run(compiled))
board_netlists = NetlistTransform(compiled).run()

top_net = board_netlists[TransformUtil.Path.empty()]

self.assertIn(
NetBlock(
"Connector_PinSocket_2.54mm:PinSocket_1x02_P2.54mm_Vertical",
"J1",
"",
"",
["sink", "conn", "ext"],
[
"edg.electronics_model.test_netlist_connector_pair.SinkConnectorPairBlock",
"edg.electronics_model.test_netlist_connector_pair.SinkConnectorPair",
"edg.electronics_model.test_netlist_connector_pair.SinkExteriorConnector",
],
),
top_net.blocks,
)
self.assertEqual(len(top_net.blocks), 2) # should only generate top-level source and sink

self.assertIn(
Net(
"vpos",
[
NetPin(["source"], "1"),
NetPin(["sink", "conn", "ext"], "1"),
],
[
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", "conn").append_port("pos"),
TransformUtil.Path.empty().append_block("sink", "conn", "int").append_port("pos"),
TransformUtil.Path.empty().append_block("sink", "conn", "ext").append_port("pos"),
],
),
top_net.nets,
)
self.assertIn(
Net(
"gnd",
[NetPin(["source"], "2"), NetPin(["sink", "conn", "ext"], "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", "conn").append_port("neg"),
TransformUtil.Path.empty().append_block("sink", "conn", "int").append_port("neg"),
TransformUtil.Path.empty().append_block("sink", "conn", "ext").append_port("neg"),
],
),
top_net.nets,
)
self.assertEqual(len(top_net.nets), 2) # ensure empty nets pruned

inner_net = board_netlists[TransformUtil.Path.empty().append_block("sink")]
self.assertIn(
NetBlock(
"Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical",
"J2",
"",
"",
["sink", "conn", "int"],
[
"edg.electronics_model.test_netlist_connector_pair.SinkConnectorPairBlock",
"edg.electronics_model.test_netlist_connector_pair.SinkConnectorPair",
"edg.electronics_model.test_netlist_connector_pair.SinkInternalConnector",
],
),
inner_net.blocks,
)
self.assertIn(
NetBlock(
"Resistor_SMD:R_0603_1608Metric",
"R1",
"",
"1k",
["sink", "inner1"],
[
"edg.electronics_model.test_netlist_connector_pair.SinkConnectorPairBlock",
"edg.electronics_model.test_netlist.TestFakeSink",
],
),
inner_net.blocks,
)
self.assertIn(
NetBlock(
"Resistor_SMD:R_0603_1608Metric",
"R2",
"",
"1k",
["sink", "inner2"],
[
"edg.electronics_model.test_netlist_connector_pair.SinkConnectorPairBlock",
"edg.electronics_model.test_netlist.TestFakeSink",
],
),
inner_net.blocks,
)
self.assertEqual(len(inner_net.blocks), 3)

self.assertIn(
Net(
"sink.vpos",
[
NetPin(["sink", "inner1"], "1"),
NetPin(["sink", "inner2"], "1"),
NetPin(["sink", "conn", "int"], "1"),
],
[
TransformUtil.Path.empty().append_block("sink").append_port("pos", "net"),
TransformUtil.Path.empty().append_block("sink", "conn").append_port("pos"),
TransformUtil.Path.empty().append_block("sink", "conn", "int").append_port("pos"),
TransformUtil.Path.empty().append_block("sink", "conn", "ext").append_port("pos"),
TransformUtil.Path.empty().append_block("sink", "inner1").append_port("pos", "net"),
TransformUtil.Path.empty().append_block("sink", "inner2").append_port("pos", "net"),
],
),
inner_net.nets,
)
self.assertIn(
Net(
"sink.gnd",
[
NetPin(["sink", "inner1"], "2"),
NetPin(["sink", "inner2"], "2"),
NetPin(["sink", "conn", "int"], "2"),
],
[
TransformUtil.Path.empty().append_block("sink").append_port("neg", "net"),
TransformUtil.Path.empty().append_block("sink", "conn").append_port("neg"),
TransformUtil.Path.empty().append_block("sink", "conn", "int").append_port("neg"),
TransformUtil.Path.empty().append_block("sink", "conn", "ext").append_port("neg"),
TransformUtil.Path.empty().append_block("sink", "inner1").append_port("neg", "net"),
TransformUtil.Path.empty().append_block("sink", "inner2").append_port("neg", "net"),
],
),
inner_net.nets,
)
self.assertEqual(len(inner_net.nets), 2) # ensure empty nets pruned
Loading
Loading