From 07527e915424b827f7412c544cfb852bffa8e441 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 26 Sep 2026 15:18:39 -0700 Subject: [PATCH 01/15] Test cases revert directly use packedpart abstract block Update MultipackBlock.py wip change multipack api clean up test cases cleaning cleaning fix generator logic cleaning add defaults fix up tests Update test_elaboration_common.py wip wip test cleanup Update Link.py delete duplicative tests Add vector case tests checks work Add overassign test at use site Update test_block_errors.py Update test_block_errors.py --- edg/core/Blocks.py | 44 ++++++- edg/core/Generator.py | 7 ++ edg/core/HdlUserExceptions.py | 24 +++- edg/core/HierarchyBlock.py | 25 ++++ edg/core/Link.py | 7 +- edg/core/MultipackBlock.py | 49 ++++++-- edg/core/ScalaCompilerInterface.py | 3 +- edg/core/test_assertion.py | 11 -- edg/core/test_block.py | 24 ++-- edg/core/test_block_errors.py | 112 +++++++++++++++++- edg/core/test_block_portvector.py | 6 +- edg/core/test_bundle.py | 6 +- edg/core/test_compiled_design_export.py | 24 +++- edg/core/test_elaboration_common.py | 20 ++-- edg/core/test_generator.py | 6 +- edg/core/test_generator_portvector.py | 2 +- edg/core/test_initializer.py | 50 +++++--- edg/core/test_port_bridge.py | 10 +- edg/core/test_simple_const_prop.py | 49 ++++---- edg/core/test_simple_expr_eval.py | 21 ---- edg/electronics_interfaces/SpiPort.py | 2 - edg/electronics_interfaces/test_bom.py | 12 +- .../test_kicad_import_blackbox.py | 6 +- .../test_multipack_netlist.py | 7 +- edg/electronics_model/CircuitBlock.py | 8 +- 25 files changed, 380 insertions(+), 155 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 4d8512ab9..e91772a00 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -304,6 +304,7 @@ def __init__(self) -> None: Connection, List[Connection] ]() # for net joins, joined connect -> prior connects self._constraints: List[ConstraintExpr] = [] + self._assigns = IdentityDict[ConstraintExpr, ConstraintExpr]() # target -> assign constraint self._name = StringExpr()._bind(NameBinding(self)) @@ -362,6 +363,10 @@ def _add_doc_metadata(self) -> None: metadata_dict[self._name_of(port)] = self._port_docs[port] self._docs = self.Metadata(metadata_dict) + @classmethod + def _is_abstract(cls) -> bool: + return (cls, AbstractBlockProperty) in cls._elt_properties + def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: """Populates the structural parts of a block proto: parameters, ports, superclasses""" assert ( @@ -369,7 +374,7 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: or self._elaboration_state == BlockElaborationState.post_generate ) - if (self.__class__, AbstractBlockProperty) in self._elt_properties: + if self._is_abstract(): assert isinstance(pb, edgir.HierarchyBlock) pb.is_abstract = True pb.is_mixin = False @@ -430,6 +435,37 @@ def _populate_def_proto_port_init(self, pb: edgir.BlockLikeTypes, ref_map: Refab ref_map, ) + def _check_port_params_assigned(self, container_port: BasePort, path: List[str]) -> None: + if container_port in self._connects_by_port: + # TODO check for absence of assignments + return # connected boundary ports inherit parameters + + if isinstance(container_port, Port): + for port in container_port._ports: + self._check_port_params_assigned(port, path + [container_port._name_of(port)]) + + for param in container_port._parameters: + if param not in self._assigns and param.initializer is None: + raise MissingParameterError(self, path + [container_port._name_of(param)]) + elif isinstance(container_port, Vector): + if container_port._elts is not None: + for name, elt in container_port._elts.items(): + self._check_port_params_assigned(elt, path + [name]) + else: + raise ValueError(f"unsupported port type {container_port}") + + def _check_params_assigned(self) -> None: + """Walks through all params and checks that they have been assigned.""" + if (self.__class__, AbstractBlockProperty) in self._elt_properties: + return # abstract blocks do not need assigned parameters + + for param in self._parameters: + if param not in self._assigns and param.initializer is None: + raise MissingParameterError(self, [self._name_of(param)]) + + for port in self._ports: + self._check_port_params_assigned(port, [self._name_of(port)]) + def _populate_def_proto_block_contents(self, pb: edgir.BlockLikeTypes, ref_map: Refable.RefMapType) -> None: """Populates the contents of a block proto: constraints""" assert ( @@ -437,6 +473,8 @@ def _populate_def_proto_block_contents(self, pb: edgir.BlockLikeTypes, ref_map: or self._elaboration_state == BlockElaborationState.post_generate ) + self._check_params_assigned() + for i, constraint in enumerate(self._constraints): name = self._name_of(constraint) constraint._populate_expr_proto(edgir.add_pair(pb.constraints, name), ref_map) @@ -531,6 +569,10 @@ def assign( self._constraints.append(constraint) self._namer.add_expected(constraint, SourceLocator.from_caller(), default_prefix="anon_constr") + if target in self._assigns: + raise OverassignParameterError(self, target) + self._assigns[target] = constraint + if name: # TODO unify naming API with everything else? self._namer.add_name(constraint, name) diff --git a/edg/core/Generator.py b/edg/core/Generator.py index 19e3fdde2..8220ec8b5 100644 --- a/edg/core/Generator.py +++ b/edg/core/Generator.py @@ -92,6 +92,13 @@ def generate(self) -> None: # Generator serialization and parsing # + @override + def _check_params_assigned(self) -> None: + # the block is only complete when generating, don't check for missing params before then + if self._elaboration_state != BlockElaborationState.post_generate: + return + super()._check_params_assigned() + @override def _def_to_proto(self) -> edgir.HierarchyBlock: self._namer.resolve_names() diff --git a/edg/core/HdlUserExceptions.py b/edg/core/HdlUserExceptions.py index c7bc7285f..5a2108f50 100644 --- a/edg/core/HdlUserExceptions.py +++ b/edg/core/HdlUserExceptions.py @@ -1,7 +1,7 @@ -from typing import Any, Type, Union, Tuple, TYPE_CHECKING +from typing import Any, Type, Union, Tuple, TYPE_CHECKING, List if TYPE_CHECKING: - from edg import BaseBlock + from edg import BaseBlock, ConstraintExpr class EdslUserError(Exception): @@ -61,3 +61,23 @@ class ChainError(BlockDefinitionError): class ElementNamingError(BlockDefinitionError): """Error for when an element has an invalid (missing, duplicated) name""" + + +class MissingParameterError(EdslUserError): + """Error if a block is missing an (output) parameter assignment / value""" + + def __init__(self, block: "BaseBlock", param_path: List[str]) -> None: + super().__init__( + f"{type(block).__name__} parameter {'.'.join(param_path)} is missing an assignment", + "assign a value to the parameter", + ) + + +class OverassignParameterError(EdslUserError): + """Error if a block is assigned a parameter value more than once""" + + def __init__(self, block: "BaseBlock", param: "ConstraintExpr") -> None: + super().__init__( + f"{type(block).__name__} parameter has conflicting assignment (assigned multiple times)", + "remove an assignment", + ) diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index bdd8df545..95ff45182 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -499,6 +499,31 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab ref_map, ) + @override + def _check_params_assigned(self) -> None: + if (self.__class__, AbstractBlockProperty) in self._elt_properties: + return # abstract blocks do not need assigned parameters + + for param in self._parameters: + if ( + param not in self._assigns + and param.initializer is None + and not isinstance(param.binding, InitParamBinding) + ): + raise MissingParameterError(self, [self._name_of(param)]) + + for port in self._ports: + self._check_port_params_assigned(port, [self._name_of(port)]) + + for block in self._blocks: + for param in block._parameters: + if ( + param not in self._assigns + and isinstance(param.binding, InitParamBinding) + and (isinstance(param.binding, InitParamBinding) and param.binding.value is None) + ): + raise MissingParameterError(self, [self._name_of(block), block._name_of(param)]) + # TODO make this non-overriding? @override def _def_to_proto(self) -> edgir.HierarchyBlock: diff --git a/edg/core/Link.py b/edg/core/Link.py index fc01e621f..2e7d61963 100644 --- a/edg/core/Link.py +++ b/edg/core/Link.py @@ -12,7 +12,7 @@ from .Core import Refable, non_library from .HdlUserExceptions import UnconnectableError from .IdentityDict import IdentityDict -from .Ports import Port +from .Ports import Port, BasePort class LinkMeta(BaseBlockMeta): @@ -97,3 +97,8 @@ def _def_to_proto(self) -> edgir.Link: @override def _elaborated_def_to_proto(self) -> edgir.Link: return cast(edgir.Link, super()._elaborated_def_to_proto()) + + @override + def _check_port_params_assigned(self, container_port: BasePort, path: List[str]) -> None: + # link ports should not have params assigned and this is a no-op + pass diff --git a/edg/core/MultipackBlock.py b/edg/core/MultipackBlock.py index e0b64c466..45f003cbe 100644 --- a/edg/core/MultipackBlock.py +++ b/edg/core/MultipackBlock.py @@ -1,12 +1,13 @@ from __future__ import annotations -from typing import NamedTuple, Optional, Union, List, Tuple, Generic, Callable, overload +import warnings +from typing import NamedTuple, Optional, Union, List, Tuple, Generic, Callable, overload, Any, override from typing_extensions import TypeVar from deprecated import deprecated from .Array import Vector from .ArrayExpr import ArrayExpr, ArrayBoolExpr, ArrayStringExpr, ArrayRangeExpr, ArrayFloatExpr, ArrayIntExpr -from .Binding import InitParamBinding +from .Binding import ParamBinding from .Blocks import BlockElaborationState from .HdlUserExceptions import BlockDefinitionError, EdgTypeError from .IdentityDict import IdentityDict @@ -14,6 +15,7 @@ from .ConstraintExpr import ConstraintExpr, BoolExpr, IntExpr, FloatExpr, RangeExpr, StringExpr from .Ports import BasePort, Port from .HierarchyBlock import Block, BlockPrototype +from ..core.IdentitySet import IdentitySet class PackedBlockAllocate(NamedTuple): @@ -48,8 +50,8 @@ class PackedBlockParam(NamedTuple): # a parameter replicated from an array of b class PackedBlockArray(Generic[PackedBlockElementType]): - """A container "block" (for multipack packing only) for an arbitrary-length array of Blocks. - This is meant to be analogous to Vector (port arrays), though there isn't an use case for this in general + """A container "block" (for multipack packing only) for an arbitrary-length array of abstract Blocks. + This is meant to be analogous to Vector (port arrays), though there isn't a use case for this in general (non-multipack) core infrastructure yet.""" def __init__(self, tpe: PackedBlockElementType): @@ -138,6 +140,8 @@ def __init__(self) -> None: self._packed_connects_by_packed_block = IdentityDict[ PackedBlockTypes, IdentityDict[BasePort, PackedPortTypes] ]() + # TODO can this be deduplicated with above? + self._packed_connected_ports = IdentitySet[BasePort]() # exterior ports involved in packed connections # packed block -> (self param -> packed param) (reverse of assign direction) self._packed_assigns_by_packed_block = IdentityDict[ PackedBlockTypes, IdentityDict[ConstraintExpr, PackedParamTypes] @@ -147,21 +151,36 @@ def __init__(self) -> None: PackedBlockTypes, IdentityDict[ConstraintExpr, UnpackedParamTypes] ]() + @override + def _check_port_params_assigned(self, container_port: BasePort, path: List[str]) -> None: + if container_port in self._packed_connected_ports: + return + super()._check_port_params_assigned(container_port, path) + PackedPartType = TypeVar("PackedPartType", bound=Union[Block, PackedBlockArray]) def PackedPart(self, tpe: PackedPartType) -> PackedPartType: """Adds a block type that can be packed into this block. The block is a "virtual block" that will not appear in the design tree.""" - if isinstance(tpe, BlockPrototype): - tpe_cls = tpe._tpe + if isinstance(tpe, PackedBlockArray): + unpacked_tpe: Any = tpe._tpe + elif isinstance(tpe, (Block, BlockPrototype)): + unpacked_tpe = tpe else: - tpe_cls = tpe.__class__ - - if not issubclass(tpe_cls, (Block, PackedBlockArray)): raise EdgTypeError(f"PackedPart(...) param", tpe, Block) if self._elaboration_state != BlockElaborationState.init: raise BlockDefinitionError(type(self), "can only define multipack in init") + if isinstance(unpacked_tpe, Block): + tpe_cls = type(unpacked_tpe) + elif isinstance(unpacked_tpe, BlockPrototype): + tpe_cls = unpacked_tpe._tpe + else: + raise ValueError("unexpected unpacked_tpe") + + if tpe_cls._is_abstract(): + raise BlockDefinitionError(type(self), "multipack PackedPart must not be abstract") + elt = tpe._bind(self) # TODO: does this actually need to be bound? self._namer.add_expected(elt, SourceLocator.from_caller()) self._packed_connects_by_packed_block[elt] = IdentityDict[BasePort, PackedPortTypes]() @@ -179,12 +198,14 @@ def packed_connect(self, exterior_port: BasePort, packed_port: PackedPortTypes) block_parent = packed_port._block_parent() assert isinstance(block_parent, Block) self._packed_connects_by_packed_block[block_parent][exterior_port] = packed_port + self._packed_connected_ports.add(exterior_port) elif isinstance(packed_port, PackedBlockPortArray): assert isinstance(exterior_port, Vector), "can only connect vector from packed port array" assert type(exterior_port._elt_sample) == type( packed_port.port ), "packed_connect ports must be of the same type" self._packed_connects_by_packed_block[packed_port.parent][exterior_port] = packed_port + self._packed_connected_ports.add(exterior_port) else: raise TypeError() @@ -192,6 +213,7 @@ def packed_connect(self, exterior_port: BasePort, packed_port: PackedPortTypes) @overload def PackedExport(self, packed_port: PackedPortType, *, optional: bool = False) -> PackedPortType: ... + @overload def PackedExport( self, packed_port: PackedBlockPortArray[PackedPortType], *, optional: bool = False @@ -230,14 +252,19 @@ def packed_assign(self, self_param: ConstraintExpr, packed_param: PackedParamTyp @overload def PackedParameter(self, packed_param: PackedParamType) -> PackedParamType: ... + @overload def PackedParameter(self, packed_param: PackedBlockParamArray[BoolExpr]) -> ArrayBoolExpr: ... + @overload def PackedParameter(self, packed_param: PackedBlockParamArray[IntExpr]) -> ArrayIntExpr: ... + @overload def PackedParameter(self, packed_param: PackedBlockParamArray[FloatExpr]) -> ArrayFloatExpr: ... + @overload def PackedParameter(self, packed_param: PackedBlockParamArray[RangeExpr]) -> ArrayRangeExpr: ... + @overload def PackedParameter(self, packed_param: PackedBlockParamArray[StringExpr]) -> ArrayStringExpr: ... @@ -246,9 +273,9 @@ def PackedParameter(self, packed_param: PackedParamTypes) -> ConstraintExpr: Combines self.Parameter(...) with self.packed_assign(...), and additionally compatible with generators where self.Parameter(...) would error out.""" if isinstance(packed_param, ConstraintExpr): - new_param = type(packed_param)()._bind(InitParamBinding(self)) + new_param = type(packed_param)()._bind(ParamBinding(self)) elif isinstance(packed_param, PackedBlockParamArray): - new_param = ArrayExpr.array_of_elt(packed_param.param)._bind(InitParamBinding(self)) + new_param = ArrayExpr.array_of_elt(packed_param.param)._bind(ParamBinding(self)) else: raise TypeError() self.packed_assign(new_param, packed_param) diff --git a/edg/core/ScalaCompilerInterface.py b/edg/core/ScalaCompilerInterface.py index 25c91ada6..734bc9d7d 100644 --- a/edg/core/ScalaCompilerInterface.py +++ b/edg/core/ScalaCompilerInterface.py @@ -72,10 +72,9 @@ def get_value( return self._values.get(localpath.SerializeToString(), None) def append_values(self, values: List[Tuple[edgir.LocalPath, edgir.ValueLit]]) -> None: - """Append solved values to this design, such as from a refinement pass""" + """Append / replaces solved values to this design, such as from a refinement pass""" for value_path, value_value in values: value_path_str = value_path.SerializeToString() - assert value_path_str not in self._values self._values[value_path_str] = edgir.valuelit_to_lit(value_value) def get_connected_link_port(self, block_port: edgir.LocalPath) -> Optional[edgir.LocalPath]: diff --git a/edg/core/test_assertion.py b/edg/core/test_assertion.py index 679afce35..bd09d1d0b 100644 --- a/edg/core/test_assertion.py +++ b/edg/core/test_assertion.py @@ -9,18 +9,7 @@ def __init__(self) -> None: self.require(BoolExpr._to_expr_type(False)) -class TestAssertionMissingBlock(Block): - def __init__(self) -> None: - super().__init__() - self.missing = self.Parameter(BoolExpr()) - self.require(self.missing) - - class AssertionTestCase(unittest.TestCase): def test_failure(self) -> None: with self.assertRaises(CompilerCheckError): ScalaCompiler.compile(TestAssertionFailureBlock) - - def test_missing(self) -> None: - with self.assertRaises(CompilerCheckError): - ScalaCompiler.compile(TestAssertionMissingBlock) diff --git a/edg/core/test_block.py b/edg/core/test_block.py index 6aa99b799..b8e320588 100644 --- a/edg/core/test_block.py +++ b/edg/core/test_block.py @@ -11,7 +11,7 @@ class TestBlockBase(Block): def __init__(self) -> None: super().__init__() - self.base_float = self.Parameter(FloatExpr()) + self.base_float = self.Parameter(FloatExpr(0.0)) self.base_port = self.Port(TestPortBase()) # required to test required constraint self.base_port_constr = self.Port(TestPortBase(self.base_float), optional=True) self.base_port_optional = self.Port(TestPortBase(), optional=self.base_port.is_connected()) @@ -84,9 +84,9 @@ def test_optional_required_constraint(self) -> None: self.assertEqual(self.pb.constraints[1].value, expected_constr) def test_port_init(self) -> None: - self.assertEqual(self.pb.constraints[2].name, "(init)base_port_constr.float_param") + self.assertEqual(self.pb.constraints[3].name, "(init)base_port_constr.float_param") self.assertEqual( - self.pb.constraints[2].value, edgir.AssignRef(["base_port_constr", "float_param"], ["base_float"]) + self.pb.constraints[3].value, edgir.AssignRef(["base_port_constr", "float_param"], ["base_float"]) ) @@ -144,17 +144,17 @@ def test_param_def(self) -> None: self.assertTrue(self.pb.params[3].value.HasField("array")) def test_superclass_init(self) -> None: - self.assertEqual(self.pb.constraints[2].name, "(init)base_port_constr.float_param") + self.assertEqual(self.pb.constraints[3].name, "(init)base_port_constr.float_param") self.assertEqual( - self.pb.constraints[2].value, edgir.AssignRef(["base_port_constr", "float_param"], ["base_float"]) + self.pb.constraints[3].value, edgir.AssignRef(["base_port_constr", "float_param"], ["base_float"]) ) def test_port_init(self) -> None: - self.assertEqual(self.pb.constraints[3].name, "(init)port_lit.float_param") + self.assertEqual(self.pb.constraints[5].name, "(init)port_lit.float_param") def test_param_init(self) -> None: - self.assertEqual(self.pb.constraints[4].name, "(init)range_init") - self.assertEqual(self.pb.constraints[4].value, edgir.AssignLit(["range_init"], Range(-4.2, -1.3))) + self.assertEqual(self.pb.constraints[7].name, "(init)range_init") + self.assertEqual(self.pb.constraints[7].value, edgir.AssignLit(["range_init"], Range(-4.2, -1.3))) expected_assign = edgir.ValueExpr() expected_assign.assign.dst.CopyFrom(edgir.LocalPathList(["array_init"])) @@ -162,14 +162,14 @@ def test_param_init(self) -> None: expected_array.vals.add().CopyFrom(edgir.lit_to_expr(False)) expected_array.vals.add().CopyFrom(edgir.lit_to_expr(True)) expected_array.vals.add().CopyFrom(edgir.lit_to_expr(False)) - self.assertEqual(self.pb.constraints[5].name, "(init)array_init") - self.assertEqual(self.pb.constraints[5].value, expected_assign) + self.assertEqual(self.pb.constraints[8].name, "(init)array_init") + self.assertEqual(self.pb.constraints[8].value, expected_assign) expected_assign = edgir.ValueExpr() expected_assign.assign.dst.CopyFrom(edgir.LocalPathList(["array_empty"])) expected_assign.assign.src.array.SetInParent() - self.assertEqual(self.pb.constraints[6].name, "(init)array_empty") - self.assertEqual(self.pb.constraints[6].value, expected_assign) + self.assertEqual(self.pb.constraints[9].name, "(init)array_empty") + self.assertEqual(self.pb.constraints[9].value, expected_assign) def test_docs(self) -> None: self.assertEqual(self.pb.meta.members.node["_docs"].members.node[""].text_leaf, "Test docstring") diff --git a/edg/core/test_block_errors.py b/edg/core/test_block_errors.py index c379816fc..348984aa9 100644 --- a/edg/core/test_block_errors.py +++ b/edg/core/test_block_errors.py @@ -2,6 +2,7 @@ from typing_extensions import override + from . import * from .HdlUserExceptions import * from .test_common import TestPortSource, TestBlockSource, TestBlockSink @@ -89,7 +90,7 @@ def __init__(self) -> None: def test_unnamed_param(self) -> None: with self.assertRaises(ElementNamingError) as exc: self.UnnamedParamBlock()._elaborated_def_to_proto() - self.assertIn("86", str(exc.exception)) + self.assertIn("87", str(exc.exception)) class DuplicateNameBlock(Block): def __init__(self) -> None: @@ -112,3 +113,112 @@ def test_duplicate_anon_name(self) -> None: with self.assertRaises(ElementNamingError) as exc: self.DuplicateAnonNameBlock()._elaborated_def_to_proto() self.assertIn("anon_constr0", str(exc.exception)) + + +class HasParamInnerBlock(Block): + def __init__(self, in_param: IntLike) -> None: + super().__init__() + self.in_param = self.ArgParameter(in_param) + + +class HasParamLink(Link): + def __init__(self) -> None: + super().__init__() + self.source = self.Port(HasParamPort.empty()) + + +class HasParamPort(Port[HasParamLink]): + link_type = HasParamLink + + def __init__(self, param: IntLike = 0) -> None: + super().__init__() + self.param = self.Parameter(IntExpr(param)) + + +class MissingParamTestCase(unittest.TestCase): + + class MissingParamBlock(Block): + def __init__(self) -> None: + super().__init__() + self.param = self.Parameter(IntExpr()) # value never defined + + def test_missing_param(self) -> None: + with self.assertRaises(MissingParameterError): + self.MissingParamBlock()._elaborated_def_to_proto() + + class OverassignParamBlock(Block): + def __init__(self) -> None: + super().__init__() + self.param = self.Parameter(IntExpr()) + self.assign(self.param, 1) + self.assign(self.param, 2) + + def test_overassign_param(self) -> None: + with self.assertRaises(OverassignParameterError): + self.OverassignParamBlock()._elaborated_def_to_proto() + + class OverassignInitializerParamBlock(Block): + """This has a conflicting assign between the initializer and an explicit assign statement.""" + + def __init__(self) -> None: + super().__init__() + self.param = self.Parameter(IntExpr(1)) + self.assign(self.param, 2) + + def test_overassign_initializer_param(self) -> None: + with self.assertRaises(OverassignParameterError): + self.OverassignInitializerParamBlock()._elaborated_def_to_proto() + + class MissingParamInnerBlock(Block): + """This block defines an arg-param but does not define an output parameter value.""" + + def __init__(self, in_param: IntLike) -> None: + super().__init__() + self.in_param = self.ArgParameter(in_param) + self.param = self.Parameter(IntExpr()) + + def test_missing_param_inner(self) -> None: + with self.assertRaises(MissingParameterError): + self.MissingParamInnerBlock(0)._elaborated_def_to_proto() + + class MissingParamContainerBlock(Block): + """This block contains a child that defines an arg-param but does not define an output parameter value.""" + + def __init__(self) -> None: + super().__init__() + self.inner = self.Block(HasParamInnerBlock(IntExpr())) + + def test_missing_param_container(self) -> None: + with self.assertRaises(MissingParameterError): + self.MissingParamContainerBlock()._elaborated_def_to_proto() + + class MissingParamPortBlock(Block): + def __init__(self) -> None: + super().__init__() + self.port = self.Port(HasParamPort(IntExpr())) + + def test_missing_param_port(self) -> None: + with self.assertRaises(MissingParameterError): + self.MissingParamPortBlock()._elaborated_def_to_proto() + + class MissingParamVectorBlock(Block): + def __init__(self) -> None: + super().__init__() + self.port = self.Port(Vector(HasParamPort.empty())) + self.port.append_elt(HasParamPort(2), "0") + self.port.append_elt(HasParamPort(IntExpr()), "1") # missing value + self.port.defined() + + def test_missing_param_vector(self) -> None: + with self.assertRaises(MissingParameterError): + self.MissingParamVectorBlock()._elaborated_def_to_proto() + + class MissingParamLink(Link): + def __init__(self) -> None: + super().__init__() + self.source = self.Port(TestPortSource(), optional=True) + self.param = self.Parameter(IntExpr()) + + def test_missing_param_link(self) -> None: + with self.assertRaises(MissingParameterError): + self.MissingParamLink()._elaborated_def_to_proto() diff --git a/edg/core/test_block_portvector.py b/edg/core/test_block_portvector.py index a777cfade..4cdecac38 100644 --- a/edg/core/test_block_portvector.py +++ b/edg/core/test_block_portvector.py @@ -30,8 +30,8 @@ def __init__(self) -> None: class TestBlockPortVectorExport(TestBlockPortVectorBase): def __init__(self) -> None: super().__init__() - vector0 = self.vector.append_elt(TestPortSink()) - vector1 = self.vector.append_elt(TestPortSink()) + vector0 = self.vector.append_elt(TestPortSink.empty()) + vector1 = self.vector.append_elt(TestPortSink.empty()) self.block0 = self.Block(TestBlockSink()) self.exported0 = self.connect(self.block0.sink, vector0) self.block1 = self.Block(TestBlockSink()) @@ -41,7 +41,7 @@ def __init__(self) -> None: class TestBlockPortVectorBridged(TestBlockPortVectorBase): def __init__(self) -> None: super().__init__() - vector_port = self.vector.append_elt(TestPortSink()) + vector_port = self.vector.append_elt(TestPortSink.empty()) self.block0 = self.Block(TestBlockSink()) self.block1 = self.Block(TestBlockSink()) self.conn = self.connect(self.block0.sink, self.block1.sink, vector_port) diff --git a/edg/core/test_bundle.py b/edg/core/test_bundle.py index c7498a917..c51024191 100644 --- a/edg/core/test_bundle.py +++ b/edg/core/test_bundle.py @@ -9,9 +9,9 @@ class TestBundle(Port): def __init__( self, - float_param: FloatLike = FloatExpr(), - a_float_param: FloatLike = FloatExpr(), - b_float_param: FloatLike = FloatExpr(), + float_param: FloatLike = 0.0, + a_float_param: FloatLike = 0.0, + b_float_param: FloatLike = 0.0, ) -> None: super().__init__() diff --git a/edg/core/test_compiled_design_export.py b/edg/core/test_compiled_design_export.py index ad39bfa71..5f44f35e5 100644 --- a/edg/core/test_compiled_design_export.py +++ b/edg/core/test_compiled_design_export.py @@ -1,10 +1,25 @@ import unittest -from typing import cast +from typing import cast, override from . import * from .CompiledDesignExport import CompiledPort +class ParamErrorsBlock(Block): + def __init__(self) -> None: + super().__init__() + self.param_a = self.Parameter(FloatExpr()) + self.param_b = self.Parameter(FloatExpr()) + + @override + def contents(self) -> None: + super().contents() + self.assign(self.param_a, self.param_b + 1.0) + self.assign(self.param_b, self.param_a + 1.0) + + self.require(self.param_a == 1.0) + + class CompiledDesignExportTestCase(unittest.TestCase): def test_hierarchy(self) -> None: from .test_hierarchy_block import TopHierarchyBlock @@ -34,9 +49,6 @@ def test_param_values(self) -> None: self.assertEqual(result.links["link"].params["range_intersection"].value, (5.0, 10.0)) def test_param_error(self) -> None: - from .test_simple_expr_eval import TestEvalExprErrorBlock - - compiled = ScalaCompiler.compile(TestEvalExprErrorBlock, ignore_errors=True) + compiled = ScalaCompiler.compile(ParamErrorsBlock, ignore_errors=True) result = CompiledDesignExportTransform(compiled).transform() - self.assertIn("overassign", cast(str, result.params["overassign_float"].error)) - self.assertEqual(result.params["overassign_float"].value, None) + self.assertEqual(result.params["param_a"].value, None) diff --git a/edg/core/test_elaboration_common.py b/edg/core/test_elaboration_common.py index fa932b12b..2a553dbd8 100644 --- a/edg/core/test_elaboration_common.py +++ b/edg/core/test_elaboration_common.py @@ -33,7 +33,7 @@ def __init__(self) -> None: class TestPortBase(Port[TestLink]): link_type = TestLink - def __init__(self, float_param: FloatLike = FloatExpr()) -> None: + def __init__(self, float_param: FloatLike = 0.0) -> None: super().__init__() self.float_param = self.Parameter(FloatExpr(float_param)) @@ -41,9 +41,9 @@ def __init__(self, float_param: FloatLike = FloatExpr()) -> None: class TestPortSource(TestPortBase): def __init__( self, - float_param_limit: RangeLike = RangeExpr(), - range_param: RangeLike = RangeExpr(), - float_param: FloatLike = FloatExpr(), + float_param_limit: RangeLike = RangeExpr.ALL, + range_param: RangeLike = Range(0, 0), + float_param: FloatLike = 0.0, ) -> None: super().__init__(float_param) self.float_param_limit = self.Parameter(RangeExpr(float_param_limit)) @@ -53,17 +53,19 @@ def __init__( class TestPortBridge(PortBridge): def __init__(self) -> None: super().__init__() - self.outer_port = self.Port(TestPortSink()) self.inner_link = self.Port(TestPortSource()) - - self.assign(self.outer_port.float_param, self.inner_link.link().float_param_sink_sum) - self.assign(self.outer_port.range_limit, self.inner_link.link().range_param_sink_common) + self.outer_port = self.Port( + TestPortSink( + float_param=self.inner_link.link().float_param_sink_sum, + range_limit=self.inner_link.link().range_param_sink_common, + ) + ) class TestPortSink(TestPortBase): bridge_type = TestPortBridge - def __init__(self, range_limit: RangeLike = RangeExpr(), float_param: FloatLike = FloatExpr()) -> None: + def __init__(self, range_limit: RangeLike = RangeExpr.ALL, float_param: FloatLike = 0.0) -> None: super().__init__(float_param) self.range_limit = self.Parameter(RangeExpr(range_limit)) diff --git a/edg/core/test_generator.py b/edg/core/test_generator.py index 877e519cc..b451b300e 100644 --- a/edg/core/test_generator.py +++ b/edg/core/test_generator.py @@ -94,7 +94,7 @@ def __init__(self) -> None: class TestPortSource(Port[TestLink]): link_type = TestLink - def __init__(self, float_value: FloatLike = FloatExpr()) -> None: + def __init__(self, float_value: FloatLike = 0.0) -> None: super().__init__() self.float_param = self.Parameter(FloatExpr(float_value)) @@ -102,7 +102,7 @@ def __init__(self, float_value: FloatLike = FloatExpr()) -> None: class TestPortSink(Port[TestLink]): link_type = TestLink - def __init__(self, range_value: RangeLike = RangeExpr()) -> None: + def __init__(self, range_value: RangeLike = Range(0.0, 0.0)) -> None: super().__init__() self.range_param = self.Parameter(RangeExpr(range_value)) @@ -152,7 +152,7 @@ def __init__(self) -> None: class GeneratorInnerConnect(GeneratorBlock): def __init__(self) -> None: super().__init__() - self.port = self.Port(TestPortSource(), optional=True) + self.port = self.Port(TestPortSource.empty(), optional=True) @override def generate(self) -> None: diff --git a/edg/core/test_generator_portvector.py b/edg/core/test_generator_portvector.py index c8bfca0b2..9b73bf1df 100644 --- a/edg/core/test_generator_portvector.py +++ b/edg/core/test_generator_portvector.py @@ -53,7 +53,7 @@ def test_initializer(self) -> None: class InnerBlockInvalid(Block): def __init__(self) -> None: super().__init__() - self.ports = self.Port(Vector(TestPortSink())) + self.ports = self.Port(Vector(TestPortSink.empty())) self.ports.append_elt(TestPortSink(), "haha") diff --git a/edg/core/test_initializer.py b/edg/core/test_initializer.py index 8d9f2cac6..90da7ea20 100644 --- a/edg/core/test_initializer.py +++ b/edg/core/test_initializer.py @@ -14,7 +14,7 @@ def __init__(self) -> None: class TestInternalBlock(Block): - def __init__(self, inner_param: FloatLike = 3.0, bundle_param: FloatLike = FloatExpr()) -> None: + def __init__(self, inner_param: FloatLike = 3.0, bundle_param: FloatLike = 5.0) -> None: super().__init__() self.inner_bundle = self.Port(TestBundle(bundle_param, 0, 24), optional=True) self.inner_param = inner_param @@ -51,51 +51,67 @@ class InitializerTestCase(unittest.TestCase): def test_initializer(self) -> None: pb = TestSingleInitializerBlock()._elaborated_def_to_proto() - self.assertEqual(len(pb.constraints), 3) + self.assertEqual(len(pb.constraints), 5) self.assertEqual(pb.constraints[0].name, "(init)bundle_port.float_param") self.assertEqual(pb.constraints[0].value, edgir.AssignLit(["bundle_port", "float_param"], 42.0)) self.assertEqual(pb.constraints[1].name, "(init)bundle_port.a.float_param") self.assertEqual(pb.constraints[1].value, edgir.AssignLit(["bundle_port", "a", "float_param"], 1.0)) - self.assertEqual(pb.constraints[2].name, "(init)bundle_port.b.float_param") - self.assertEqual(pb.constraints[2].value, edgir.AssignLit(["bundle_port", "b", "float_param"], -1.0)) + self.assertEqual(pb.constraints[2].name, "(init)bundle_port.a.range_limit") + self.assertEqual(pb.constraints[2].value, edgir.AssignLit(["bundle_port", "a", "range_limit"], RangeExpr.ALL)) + self.assertEqual(pb.constraints[3].name, "(init)bundle_port.b.float_param") + self.assertEqual(pb.constraints[3].value, edgir.AssignLit(["bundle_port", "b", "float_param"], -1.0)) + self.assertEqual(pb.constraints[4].name, "(init)bundle_port.b.range_limit") + self.assertEqual(pb.constraints[4].value, edgir.AssignLit(["bundle_port", "b", "range_limit"], RangeExpr.ALL)) def test_nested_initializer(self) -> None: pb = TestNestedBlock()._elaborated_def_to_proto() - self.assertEqual(len(pb.constraints), 5) + self.assertEqual(len(pb.constraints), 7) self.assertEqual(pb.constraints[0].name, "(init)outer_bundle.float_param") self.assertEqual(pb.constraints[0].value, edgir.AssignLit(["outer_bundle", "float_param"], 21.0)) self.assertEqual(pb.constraints[1].name, "(init)outer_bundle.a.float_param") self.assertEqual(pb.constraints[1].value, edgir.AssignLit(["outer_bundle", "a", "float_param"], 1.0)) - self.assertEqual(pb.constraints[2].name, "(init)outer_bundle.b.float_param") - self.assertEqual(pb.constraints[2].value, edgir.AssignLit(["outer_bundle", "b", "float_param"], -1.0)) - self.assertEqual(pb.constraints[3].name, "(init)inner.inner_param") - self.assertEqual(pb.constraints[3].value, edgir.AssignLit(["inner", "inner_param"], 62.0)) - self.assertEqual(pb.constraints[4].name, "(init)inner.bundle_param") - self.assertEqual(pb.constraints[4].value, edgir.AssignLit(["inner", "bundle_param"], 31.0)) + self.assertEqual(pb.constraints[2].name, "(init)outer_bundle.a.range_limit") + self.assertEqual(pb.constraints[2].value, edgir.AssignLit(["outer_bundle", "a", "range_limit"], RangeExpr.ALL)) + self.assertEqual(pb.constraints[3].name, "(init)outer_bundle.b.float_param") + self.assertEqual(pb.constraints[3].value, edgir.AssignLit(["outer_bundle", "b", "float_param"], -1.0)) + self.assertEqual(pb.constraints[4].name, "(init)outer_bundle.b.range_limit") + self.assertEqual(pb.constraints[4].value, edgir.AssignLit(["outer_bundle", "b", "range_limit"], RangeExpr.ALL)) + self.assertEqual(pb.constraints[5].name, "(init)inner.inner_param") + self.assertEqual(pb.constraints[5].value, edgir.AssignLit(["inner", "inner_param"], 62.0)) + self.assertEqual(pb.constraints[6].name, "(init)inner.bundle_param") + self.assertEqual(pb.constraints[6].value, edgir.AssignLit(["inner", "bundle_param"], 31.0)) def test_nested_inner(self) -> None: pb = TestInternalBlock()._elaborated_def_to_proto() - self.assertEqual(len(pb.constraints), 3) # should not generate initializers for constructors + self.assertEqual(len(pb.constraints), 5) self.assertEqual(pb.constraints[0].name, "(init)inner_bundle.float_param") self.assertEqual(pb.constraints[0].value, edgir.AssignRef(["inner_bundle", "float_param"], ["bundle_param"])) # don't care about value of literal initializers self.assertEqual(pb.constraints[1].name, "(init)inner_bundle.a.float_param") - self.assertEqual(pb.constraints[2].name, "(init)inner_bundle.b.float_param") + self.assertEqual(pb.constraints[2].name, "(init)inner_bundle.a.range_limit") + self.assertEqual(pb.constraints[3].name, "(init)inner_bundle.b.float_param") + self.assertEqual(pb.constraints[4].name, "(init)inner_bundle.b.range_limit") def test_default_initializer(self) -> None: pb = TestDefaultBlock()._elaborated_def_to_proto() - self.assertEqual(len(pb.constraints), 1) + self.assertEqual(len(pb.constraints), 2) self.assertEqual(pb.constraints[0].name, "(init)inner.inner_param") self.assertEqual(pb.constraints[0].value, edgir.AssignLit(["inner", "inner_param"], 3.0)) + self.assertEqual(pb.constraints[1].name, "(init)inner.bundle_param") + self.assertEqual(pb.constraints[1].value, edgir.AssignLit(["inner", "bundle_param"], 5.0)) def test_multiple_initializer(self) -> None: pb = TestMultipleInstantiationBlock()._elaborated_def_to_proto() - self.assertEqual(len(pb.constraints), 2) + self.assertEqual(len(pb.constraints), 4) self.assertEqual(pb.constraints[0].name, "(init)inner1.inner_param") self.assertEqual(pb.constraints[0].value, edgir.AssignLit(["inner1", "inner_param"], 3.0)) - self.assertEqual(pb.constraints[1].name, "(init)inner2.inner_param") - self.assertEqual(pb.constraints[1].value, edgir.AssignLit(["inner2", "inner_param"], 3.0)) + self.assertEqual(pb.constraints[1].name, "(init)inner1.bundle_param") + self.assertEqual(pb.constraints[1].value, edgir.AssignLit(["inner1", "bundle_param"], 5.0)) + self.assertEqual(pb.constraints[2].name, "(init)inner2.inner_param") + self.assertEqual(pb.constraints[2].value, edgir.AssignLit(["inner2", "inner_param"], 3.0)) + self.assertEqual(pb.constraints[3].name, "(init)inner2.bundle_param") + self.assertEqual(pb.constraints[3].value, edgir.AssignLit(["inner2", "bundle_param"], 5.0)) diff --git a/edg/core/test_port_bridge.py b/edg/core/test_port_bridge.py index 2bbf96cda..0a80d860f 100644 --- a/edg/core/test_port_bridge.py +++ b/edg/core/test_port_bridge.py @@ -13,13 +13,13 @@ def setUp(self) -> None: self.pb = TestPortBridge()._elaborated_def_to_proto() def test_contains_param(self) -> None: - self.assertEqual(self.pb.ports[0].name, "outer_port") - self.assertEqual(self.pb.ports[0].value.lib_elem.target.name, "edg.core.test_elaboration_common.TestPortSink") - self.assertEqual(self.pb.ports[1].name, "inner_link") - self.assertEqual(self.pb.ports[1].value.lib_elem.target.name, "edg.core.test_elaboration_common.TestPortSource") + self.assertEqual(self.pb.ports[0].name, "inner_link") + self.assertEqual(self.pb.ports[0].value.lib_elem.target.name, "edg.core.test_elaboration_common.TestPortSource") + self.assertEqual(self.pb.ports[1].name, "outer_port") + self.assertEqual(self.pb.ports[1].value.lib_elem.target.name, "edg.core.test_elaboration_common.TestPortSink") def test_constraints(self) -> None: - self.assertEqual(len(self.pb.constraints), 2) + self.assertEqual(len(self.pb.constraints), 5) constraints = list(map(lambda pair: pair.value, self.pb.constraints)) expected_constr = edgir.ValueExpr() diff --git a/edg/core/test_simple_const_prop.py b/edg/core/test_simple_const_prop.py index fbcbc4db8..8acfb8db0 100644 --- a/edg/core/test_simple_const_prop.py +++ b/edg/core/test_simple_const_prop.py @@ -7,11 +7,11 @@ class TestConstPropInternal(Block): - def __init__(self) -> None: + def __init__(self, float_param: FloatLike, range_param: RangeLike) -> None: super().__init__() - self.float_param = self.Parameter(FloatExpr()) - self.range_param = self.Parameter(RangeExpr()) + self.float_param = self.ArgParameter(float_param) + self.range_param = self.ArgParameter(range_param) class TestParameterConstProp(Block): @@ -32,9 +32,7 @@ def contents(self) -> None: self.assign(self.range_const, Range(1.0, 42.0)) self.assign(self.range_param, self.range_const) - self.block = self.Block(TestConstPropInternal()) - self.assign(self.block.float_param, self.float_param) - self.assign(self.block.range_param, self.range_param) + self.block = self.Block(TestConstPropInternal(self.float_param, self.range_param)) class ConstPropTestCase(unittest.TestCase): @@ -68,32 +66,31 @@ def __init__(self) -> None: class TestPortConstPropPort(Port[TestPortConstPropLink]): link_type = TestPortConstPropLink - def __init__(self) -> None: + def __init__(self, float_param: FloatLike = 0.0) -> None: super().__init__() - self.float_param = self.Parameter(FloatExpr()) + self.float_param = self.Parameter(FloatExpr(float_param)) class TestPortConstPropInnerBlock(Block): - def __init__(self) -> None: + def __init__(self, float_param: FloatLike = 0.0) -> None: super().__init__() - self.port = self.Port(TestPortConstPropPort(), optional=True) + self.port = self.Port(TestPortConstPropPort(float_param), optional=True) class TestPortConstPropOuterBlock(Block): - def __init__(self) -> None: + def __init__(self, inner_float_param: FloatLike = 0.0) -> None: super().__init__() - self.inner = self.Block(TestPortConstPropInnerBlock()) - self.port = self.Port(TestPortConstPropPort()) + self.inner = self.Block(TestPortConstPropInnerBlock(inner_float_param)) + self.port = self.Port(TestPortConstPropPort.empty()) self.connect(self.inner.port, self.port) class TestPortConstPropTopBlock(Block): def __init__(self) -> None: super().__init__() - self.block1 = self.Block(TestPortConstPropInnerBlock()) - self.block2 = self.Block(TestPortConstPropOuterBlock()) # dummy, just to infer a connection + self.block1 = self.Block(TestPortConstPropInnerBlock(3.5)) + self.block2 = self.Block(TestPortConstPropOuterBlock(0.0)) # dummy, just to infer a connection self.link = self.connect(self.block1.port, self.block2.port) - self.assign(self.block1.port.float_param, 3.5) class ConstPropPortTestCase(unittest.TestCase): @@ -115,8 +112,7 @@ def test_connected_link(self) -> None: class TestDisconnectedTopBlock(Block): def __init__(self) -> None: super().__init__() - self.block1 = self.Block(TestPortConstPropInnerBlock()) - self.assign(self.block1.port.float_param, 3.5) + self.block1 = self.Block(TestPortConstPropInnerBlock(3.5)) class DisconnectedPortTestCase(unittest.TestCase): @@ -142,16 +138,16 @@ def __init__(self) -> None: class TestPortConstPropBundle(Port[TestPortConstPropBundleLink]): link_type = TestPortConstPropBundleLink - def __init__(self) -> None: + def __init__(self, elt1_float_value: FloatLike = 0.0, elt2_float_value: FloatLike = 0.0) -> None: super().__init__() - self.elt1 = self.Port(TestPortConstPropPort()) - self.elt2 = self.Port(TestPortConstPropPort()) + self.elt1 = self.Port(TestPortConstPropPort(elt1_float_value)) + self.elt2 = self.Port(TestPortConstPropPort(elt2_float_value)) class TestPortConstPropBundleInnerBlock(Block): - def __init__(self) -> None: + def __init__(self, elt1_float_value: FloatLike, elt2_float_value: FloatLike) -> None: super().__init__() - self.port = self.Port(TestPortConstPropBundle()) + self.port = self.Port(TestPortConstPropBundle(elt1_float_value, elt2_float_value)) class TestPortConstPropBundleTopBlock(Block): @@ -160,13 +156,10 @@ def __init__(self) -> None: @override def contents(self) -> None: - self.block1 = self.Block(TestPortConstPropBundleInnerBlock()) - self.block2 = self.Block(TestPortConstPropBundleInnerBlock()) # dummy, just to infer a connection + self.block1 = self.Block(TestPortConstPropBundleInnerBlock(3.5, 6.0)) + self.block2 = self.Block(TestPortConstPropBundleInnerBlock(0.0, 0.0)) # dummy, just to infer a connection self.link = self.connect(self.block1.port, self.block2.port) - self.assign(self.block1.port.elt1.float_param, 3.5) - self.assign(self.block1.port.elt2.float_param, 6.0) - class ConstPropBundleTestCase(unittest.TestCase): @override diff --git a/edg/core/test_simple_expr_eval.py b/edg/core/test_simple_expr_eval.py index df72cb3b6..1b68c2df8 100644 --- a/edg/core/test_simple_expr_eval.py +++ b/edg/core/test_simple_expr_eval.py @@ -28,27 +28,6 @@ def test_sum(self) -> None: self.assertEqual(self.compiled.get_value(["sum_range"]), Range(9.0, 14.0)) -class TestEvalExprErrorBlock(Block): - def __init__(self) -> None: - super().__init__() - self.overassign_float = self.Parameter(FloatExpr()) - - @override - def contents(self) -> None: - self.assign(self.overassign_float, 1.0) - self.assign(self.overassign_float, 2.0) - - -class EvalExprErrorTestCase(unittest.TestCase): - @override - def setUp(self) -> None: - self.compiled = ScalaCompiler.compile(TestEvalExprErrorBlock, ignore_errors=True) - - def test_sum(self) -> None: - self.assertEqual(len(self.compiled.errors), 1) - self.assertIsInstance(self.compiled.get_value(["overassign_float"]), edgir.ErrorValue) - - class TestReductionLink(Link): def __init__(self) -> None: super().__init__() diff --git a/edg/electronics_interfaces/SpiPort.py b/edg/electronics_interfaces/SpiPort.py index 3c3a8d4f8..974271894 100644 --- a/edg/electronics_interfaces/SpiPort.py +++ b/edg/electronics_interfaces/SpiPort.py @@ -55,7 +55,6 @@ def __init__(self, model: Optional[DigitalBidir] = None, *, frequency_limit: Ran self.miso = self.Port(DigitalSink.from_bidir(model)) self.frequency_limit = self.Parameter(RangeExpr(frequency_limit)) - self.mode = self.Parameter(RangeExpr()) # modes supported, in [0, 3] TODO: what about sparse modes? class SpiPeripheral(Port[SpiLink]): @@ -72,7 +71,6 @@ def __init__(self, model: Optional[DigitalBidir] = None, *, frequency_limit: Ran # TODO: (?) CS is defined separately self.frequency_limit = self.Parameter(RangeExpr(frequency_limit)) # range of acceptable frequencies - self.mode_limit = self.Parameter(RangeExpr()) # range of acceptable modes, in [0, 3] # legacy names diff --git a/edg/electronics_interfaces/test_bom.py b/edg/electronics_interfaces/test_bom.py index c8cb9121a..6ec172184 100644 --- a/edg/electronics_interfaces/test_bom.py +++ b/edg/electronics_interfaces/test_bom.py @@ -44,15 +44,15 @@ def test_basic_bom(self) -> None: self.assertEqual(bom_csv_dict[0]["Designator"], "C1") self.assertEqual(bom_csv_dict[0]["Footprint"], "Capacitor_SMD:C_0603_1608Metric") self.assertEqual(bom_csv_dict[0]["Quantity"], "1") - self.assertEqual(bom_csv_dict[0]["PNP Rotation Offset"], "") # unspecified - self.assertEqual(bom_csv_dict[0]["PNP Offset Y"], "") # unspecified - self.assertEqual(bom_csv_dict[0]["PNP Offset X"], "") # unspecified + self.assertEqual(bom_csv_dict[0]["PNP Rotation Offset"], "0.0") # unspecified + self.assertEqual(bom_csv_dict[0]["PNP Offset Y"], "0.0") # unspecified + self.assertEqual(bom_csv_dict[0]["PNP Offset X"], "0.0") # unspecified self.assertEqual(bom_csv_dict[1]["Designator"], "R1") self.assertEqual(bom_csv_dict[1]["Footprint"], "Resistor_SMD:R_0603_1608Metric") self.assertEqual(bom_csv_dict[1]["Quantity"], "1") - self.assertEqual(bom_csv_dict[1]["PNP Rotation Offset"], "") # unspecified - self.assertEqual(bom_csv_dict[1]["PNP Offset Y"], "") # unspecified - self.assertEqual(bom_csv_dict[1]["PNP Offset X"], "") # unspecified + self.assertEqual(bom_csv_dict[1]["PNP Rotation Offset"], "0.0") # unspecified + self.assertEqual(bom_csv_dict[1]["PNP Offset Y"], "0.0") # unspecified + self.assertEqual(bom_csv_dict[1]["PNP Offset X"], "0.0") # unspecified def test_multisink_bom(self) -> None: # test aggregation of similar components diff --git a/edg/electronics_interfaces/test_kicad_import_blackbox.py b/edg/electronics_interfaces/test_kicad_import_blackbox.py index c8e7373c0..c32a28d90 100644 --- a/edg/electronics_interfaces/test_kicad_import_blackbox.py +++ b/edg/electronics_interfaces/test_kicad_import_blackbox.py @@ -14,9 +14,9 @@ class KiCadBlackboxBlock(KiCadSchematicBlock): def __init__(self) -> None: super().__init__() - self.pwr = self.Port(Passive.empty()) - self.gnd = self.Port(Passive.empty()) - self.out = self.Port(Passive.empty()) + self.pwr = self.Port(Passive()) + self.gnd = self.Port(Passive()) + self.out = self.Port(Passive()) self.import_kicad(self.file_path("resources", "test_kicad_import_blackbox.kicad_sch")) diff --git a/edg/electronics_interfaces/test_multipack_netlist.py b/edg/electronics_interfaces/test_multipack_netlist.py index 2ca61fb45..100d0aa4f 100644 --- a/edg/electronics_interfaces/test_multipack_netlist.py +++ b/edg/electronics_interfaces/test_multipack_netlist.py @@ -8,16 +8,11 @@ from .test_netlist import TestFakeSource, TestFakeSink, TestBaseFakeSink, NetlistTestCase, Net, net_pin, net_block -class TestFakeSinkElement(TestBaseFakeSink): - # just exists to not be an abstract part - pass - - class TestPackedSink(MultipackBlock): def __init__(self) -> None: super().__init__() - self.elements = self.PackedPart(PackedBlockArray(TestFakeSinkElement())) + self.elements = self.PackedPart(PackedBlockArray(TestBaseFakeSink())) self.pos = self.PackedExport(self.elements.ports_array(lambda x: x.pos)) self.neg = self.PackedExport(self.elements.ports_array(lambda x: x.neg)) diff --git a/edg/electronics_model/CircuitBlock.py b/edg/electronics_model/CircuitBlock.py index fa40b7c21..d4a27bca2 100644 --- a/edg/electronics_model/CircuitBlock.py +++ b/edg/electronics_model/CircuitBlock.py @@ -29,7 +29,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.fp_mfr = self.Parameter(StringExpr()) self.fp_part = self.Parameter(StringExpr()) self.fp_value = self.Parameter(StringExpr()) - self.fp_refdes = self.Parameter(StringExpr()) + self.fp_refdes = self.Parameter(StringExpr("")) # dummy value, replaced during compilation self.fp_refdes_prefix = self.Parameter(StringExpr()) self.fp_pnp_rot = self.Parameter(FloatExpr()) @@ -144,9 +144,15 @@ def footprint( # PNP rotation is left explicitly unassigned if not defined if pnp_rot is not None: self.assign(self.fp_pnp_rot, pnp_rot) + else: + self.assign(self.fp_pnp_rot, 0.0) + if pnp_offset is not None: self.assign(self.fp_pnp_offset_x, pnp_offset[0]) self.assign(self.fp_pnp_offset_y, pnp_offset[1]) + else: + self.assign(self.fp_pnp_offset_x, 0.0) + self.assign(self.fp_pnp_offset_y, 0.0) @non_library From ae75194bec990f673c3293fa52db53e5ad2f7fef Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 1 Nov 2026 15:40:04 -0800 Subject: [PATCH 02/15] clean core --- edg/core/Blocks.py | 51 ++++++++++++++++++++++++----------- edg/core/HdlUserExceptions.py | 5 ++-- edg/core/HierarchyBlock.py | 21 ++++++--------- 3 files changed, 46 insertions(+), 31 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index e91772a00..a45054168 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -10,7 +10,7 @@ from .. import edgir from .Array import BaseVector, Vector -from .Binding import AssignBinding, NameBinding +from .Binding import AssignBinding, NameBinding, InitParamBinding from .ConstraintExpr import ConstraintExpr, BoolExpr, ParamBinding, AssignExpr, StringExpr, BoolLike from .Core import ( Refable, @@ -435,36 +435,55 @@ def _populate_def_proto_port_init(self, pb: edgir.BlockLikeTypes, ref_map: Refab ref_map, ) - def _check_port_params_assigned(self, container_port: BasePort, path: List[str]) -> None: + def _collect_unassigned_params(self) -> List[List[str]]: + """Walks through all params and checks that they have been assigned. + TODO: return List[ConstraintExpr] when param names can be resolved.""" + if (self.__class__, AbstractBlockProperty) in self._elt_properties: + return [] # abstract blocks do not need assigned parameters + + unassigned: List[List[str]] = [] + + # TODO instead of filtering by binding, init should have assign record + for param in self._parameters: + if ( + param not in self._assigns + and param.initializer is None + and not isinstance(param.binding, InitParamBinding) + ): + unassigned.append([self._name_of(param)]) + + for port in self._ports: + unassigned.extend(self._collect_unassigned_port_params(port, [self._name_of(port)])) + + return unassigned + + def _collect_unassigned_port_params(self, container_port: BasePort, path: List[str]) -> List[List[str]]: if container_port in self._connects_by_port: # TODO check for absence of assignments - return # connected boundary ports inherit parameters + return [] # connected boundary ports inherit parameters + + unassigned: List[List[str]] = [] if isinstance(container_port, Port): for port in container_port._ports: - self._check_port_params_assigned(port, path + [container_port._name_of(port)]) + unassigned.extend(self._collect_unassigned_port_params(port, path + [container_port._name_of(port)])) for param in container_port._parameters: if param not in self._assigns and param.initializer is None: - raise MissingParameterError(self, path + [container_port._name_of(param)]) + unassigned.append(path + [container_port._name_of(param)]) elif isinstance(container_port, Vector): if container_port._elts is not None: for name, elt in container_port._elts.items(): - self._check_port_params_assigned(elt, path + [name]) + unassigned.extend(self._collect_unassigned_port_params(elt, path + [name])) else: raise ValueError(f"unsupported port type {container_port}") - def _check_params_assigned(self) -> None: - """Walks through all params and checks that they have been assigned.""" - if (self.__class__, AbstractBlockProperty) in self._elt_properties: - return # abstract blocks do not need assigned parameters + return unassigned - for param in self._parameters: - if param not in self._assigns and param.initializer is None: - raise MissingParameterError(self, [self._name_of(param)]) - - for port in self._ports: - self._check_port_params_assigned(port, [self._name_of(port)]) + def _check_params_assigned(self) -> None: + unassigned = self._collect_unassigned_params() + if unassigned: + raise MissingParameterError(self, unassigned) def _populate_def_proto_block_contents(self, pb: edgir.BlockLikeTypes, ref_map: Refable.RefMapType) -> None: """Populates the contents of a block proto: constraints""" diff --git a/edg/core/HdlUserExceptions.py b/edg/core/HdlUserExceptions.py index 5a2108f50..ab1577e34 100644 --- a/edg/core/HdlUserExceptions.py +++ b/edg/core/HdlUserExceptions.py @@ -66,9 +66,10 @@ class ElementNamingError(BlockDefinitionError): class MissingParameterError(EdslUserError): """Error if a block is missing an (output) parameter assignment / value""" - def __init__(self, block: "BaseBlock", param_path: List[str]) -> None: + def __init__(self, block: "BaseBlock", param_paths: List[List[str]]) -> None: + params_str = ", ".join([".".join(path) for path in param_paths]) super().__init__( - f"{type(block).__name__} parameter {'.'.join(param_path)} is missing an assignment", + f"{type(block).__name__} parameters {params_str} missing assignment", "assign a value to the parameter", ) diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index 95ff45182..47997bb35 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -500,20 +500,13 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab ) @override - def _check_params_assigned(self) -> None: - if (self.__class__, AbstractBlockProperty) in self._elt_properties: - return # abstract blocks do not need assigned parameters + def _collect_unassigned_params(self) -> List[List[str]]: + unassigned = super()._collect_unassigned_params() - for param in self._parameters: - if ( - param not in self._assigns - and param.initializer is None - and not isinstance(param.binding, InitParamBinding) - ): - raise MissingParameterError(self, [self._name_of(param)]) + if (self.__class__, AbstractBlockProperty) in self._elt_properties: + return [] # abstract blocks do not need assigned parameters - for port in self._ports: - self._check_port_params_assigned(port, [self._name_of(port)]) + # TODO instead of filtering by binding, init should have assign record for block in self._blocks: for param in block._parameters: @@ -522,7 +515,9 @@ def _check_params_assigned(self) -> None: and isinstance(param.binding, InitParamBinding) and (isinstance(param.binding, InitParamBinding) and param.binding.value is None) ): - raise MissingParameterError(self, [self._name_of(block), block._name_of(param)]) + unassigned.append([self._name_of(block), block._name_of(param)]) + + return unassigned # TODO make this non-overriding? @override From 4b47ea4a3ea056ac25bcf395a35bda8ff0cc6102 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 1 Nov 2026 15:42:40 -0800 Subject: [PATCH 03/15] wip --- edg/core/Link.py | 2 +- edg/core/MultipackBlock.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/edg/core/Link.py b/edg/core/Link.py index 2e7d61963..5aa7698a5 100644 --- a/edg/core/Link.py +++ b/edg/core/Link.py @@ -99,6 +99,6 @@ def _elaborated_def_to_proto(self) -> edgir.Link: return cast(edgir.Link, super()._elaborated_def_to_proto()) @override - def _check_port_params_assigned(self, container_port: BasePort, path: List[str]) -> None: + def _collect_unassigned_port_params(self, container_port: BasePort, path: List[str]) -> List[List[str]]: # link ports should not have params assigned and this is a no-op pass diff --git a/edg/core/MultipackBlock.py b/edg/core/MultipackBlock.py index 45f003cbe..61c6fedb3 100644 --- a/edg/core/MultipackBlock.py +++ b/edg/core/MultipackBlock.py @@ -152,10 +152,11 @@ def __init__(self) -> None: ]() @override - def _check_port_params_assigned(self, container_port: BasePort, path: List[str]) -> None: + def _collect_unassigned_port_params(self, container_port: BasePort, path: List[str]) -> List[List[str]]: if container_port in self._packed_connected_ports: - return - super()._check_port_params_assigned(container_port, path) + # packed ports delegate their implementations + return [] + return super()._collect_unassigned_port_params(container_port, path) PackedPartType = TypeVar("PackedPartType", bound=Union[Block, PackedBlockArray]) From 2e4619951bdf67b541f7f550fead62ebf503651a Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 1 Nov 2026 15:43:02 -0800 Subject: [PATCH 04/15] Update Link.py --- edg/core/Link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edg/core/Link.py b/edg/core/Link.py index 5aa7698a5..ceee93799 100644 --- a/edg/core/Link.py +++ b/edg/core/Link.py @@ -101,4 +101,4 @@ def _elaborated_def_to_proto(self) -> edgir.Link: @override def _collect_unassigned_port_params(self, container_port: BasePort, path: List[str]) -> List[List[str]]: # link ports should not have params assigned and this is a no-op - pass + return [] From 9e454ac0cf31c033b5d1d941912cc0697f0549d3 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 1 Nov 2026 16:36:23 -0800 Subject: [PATCH 05/15] cleaning --- edg/abstract_parts/SelectorArea.py | 2 +- edg/parts/power/motor/Bldc_Drv8313.py | 1 + edg/vendor_parts/generic/GenericResistor.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/edg/abstract_parts/SelectorArea.py b/edg/abstract_parts/SelectorArea.py index bc8dc1851..803394e4b 100644 --- a/edg/abstract_parts/SelectorArea.py +++ b/edg/abstract_parts/SelectorArea.py @@ -9,7 +9,7 @@ @abstract_block -class SelectorArea(PartsTablePart): +class SelectorArea(Block): """A base mixin that defines a filter_area range specification for blocks that automatically select parts. Provides no implementation, only defines the specification parameter. diff --git a/edg/parts/power/motor/Bldc_Drv8313.py b/edg/parts/power/motor/Bldc_Drv8313.py index 56e10c8d4..79d930705 100644 --- a/edg/parts/power/motor/Bldc_Drv8313.py +++ b/edg/parts/power/motor/Bldc_Drv8313.py @@ -107,6 +107,7 @@ def contents(self) -> None: pnp_rot=-90, ) self.assign(self.lcsc_part, "C92482") + self.assign(self.actual_basic_part, False) class Drv8313(BldcDriver, GeneratorBlock): diff --git a/edg/vendor_parts/generic/GenericResistor.py b/edg/vendor_parts/generic/GenericResistor.py index 8c2196bfc..751779e6b 100644 --- a/edg/vendor_parts/generic/GenericResistor.py +++ b/edg/vendor_parts/generic/GenericResistor.py @@ -64,6 +64,7 @@ def generate(self) -> None: if not suitable_packages: raise ValueError("no suitable resistor packages") + self.assign(self.actual_voltage_rating, Range.all()) # ideal resistor self.assign(self.actual_resistance, selected_range) self.assign(self.actual_power_rating, Range.zero_to_upper(suitable_packages[0][0])) From 8eaaeef49b0614c1c18c3de88c3f0466a20c1591 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 1 Nov 2026 16:49:49 -0800 Subject: [PATCH 06/15] cleaning --- edg/abstract_parts/Neopixel.py | 1 + edg/core/Generator.py | 10 ++++++++++ edg/parts/analog/Comparator_Lmv331.py | 1 + edg/parts/microcontroller/nRF52840.py | 1 - 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/edg/abstract_parts/Neopixel.py b/edg/abstract_parts/Neopixel.py index 4d85ed899..3b6d0739a 100644 --- a/edg/abstract_parts/Neopixel.py +++ b/edg/abstract_parts/Neopixel.py @@ -3,6 +3,7 @@ from ..electronics_interfaces import * +@abstract_block class Neopixel(Light, Block): """Abstract base class for individually-addressable, serially-connected Neopixel-type (typically RGB) LEDs and defines the pwr/gnd/din/dout interface.""" diff --git a/edg/core/Generator.py b/edg/core/Generator.py index 8220ec8b5..24bf284fd 100644 --- a/edg/core/Generator.py +++ b/edg/core/Generator.py @@ -99,6 +99,16 @@ def _check_params_assigned(self) -> None: return super()._check_params_assigned() + @override + def _collect_unassigned_port_params(self, container_port: BasePort, path: List[str]) -> List[List[str]]: + # generators can inspect ports for connected-ness, if not connected, it does not need an assignment + if self._elaboration_state == BlockElaborationState.post_generate: + if isinstance(container_port, Port): + port_connected = container_port.is_connected() + if port_connected in self._generator_param_values and not self.get(port_connected): + return [] # port is not connected, so no assignment needed + return super()._collect_unassigned_port_params(container_port, path) + @override def _def_to_proto(self) -> edgir.HierarchyBlock: self._namer.resolve_names() diff --git a/edg/parts/analog/Comparator_Lmv331.py b/edg/parts/analog/Comparator_Lmv331.py index c7fdd04f3..b6fb9e021 100644 --- a/edg/parts/analog/Comparator_Lmv331.py +++ b/edg/parts/analog/Comparator_Lmv331.py @@ -42,6 +42,7 @@ def contents(self) -> None: pnp_rot=180, ) self.assign(self.lcsc_part, "C7976") + self.assign(self.actual_basic_part, False) class Lmv331(Comparator): diff --git a/edg/parts/microcontroller/nRF52840.py b/edg/parts/microcontroller/nRF52840.py index 6a3573bff..69ace6ad4 100644 --- a/edg/parts/microcontroller/nRF52840.py +++ b/edg/parts/microcontroller/nRF52840.py @@ -667,7 +667,6 @@ class Feather_Nrf52840( IoController, WrapperSubboardBlock, GeneratorBlock, - FootprintBlock, ): """Feather nRF52840 socketed dev board as either power source or sink""" From 3c630e9eaeb9f0368860abf490d97d0fa27787bb Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 1 Nov 2026 17:05:39 -0800 Subject: [PATCH 07/15] cleaning --- edg/parts/connector/Fpc.py | 1 + edg/parts/connector/UsbPorts.py | 2 ++ edg/parts/interface/RfModules.py | 2 +- edg/parts/interface/UsbUart_Cp2102.py | 2 +- edg/parts/logic/Logic_74Ahct.py | 1 + edg/parts/microcontroller/Ice40up.py | 2 +- 6 files changed, 7 insertions(+), 3 deletions(-) diff --git a/edg/parts/connector/Fpc.py b/edg/parts/connector/Fpc.py index 0407c9b3b..dd43dc526 100644 --- a/edg/parts/connector/Fpc.py +++ b/edg/parts/connector/Fpc.py @@ -104,6 +104,7 @@ def contents(self) -> None: def generate(self) -> None: super().generate() self.conn = self.Block(Fpc050Top(self.length)) + self.assign(self.actual_length, self.conn.actual_length) length = self.get(self.length) assert length > 0, "explicit length required" for pin in self.get(self.pins.requested()): diff --git a/edg/parts/connector/UsbPorts.py b/edg/parts/connector/UsbPorts.py index 6c1f054d0..7eb887c08 100644 --- a/edg/parts/connector/UsbPorts.py +++ b/edg/parts/connector/UsbPorts.py @@ -245,6 +245,8 @@ def contents(self) -> None: part="TPD2E009", datasheet="https://www.ti.com/lit/ds/symlink/tpd2e009.pdf", ) + self.assign(self.lcsc_part, "C2871371") + self.asign(self.actual_basic_part, False) class Pesd5v0x1bt(UsbEsdDiode, FootprintBlock, JlcPart): diff --git a/edg/parts/interface/RfModules.py b/edg/parts/interface/RfModules.py index afa3e5bb4..d8cd5896a 100644 --- a/edg/parts/interface/RfModules.py +++ b/edg/parts/interface/RfModules.py @@ -67,7 +67,7 @@ def contents(self) -> None: ) -class Xbee_S3b(Interface, Radiofrequency, FootprintBlock): +class Xbee_S3b(Interface, Radiofrequency): """XBee-PRO 900HP, product numbers XBP9B-*""" def __init__(self) -> None: diff --git a/edg/parts/interface/UsbUart_Cp2102.py b/edg/parts/interface/UsbUart_Cp2102.py index 66373ee69..aa9a76a8c 100644 --- a/edg/parts/interface/UsbUart_Cp2102.py +++ b/edg/parts/interface/UsbUart_Cp2102.py @@ -95,7 +95,7 @@ def __init__(self) -> None: self.assign(self.actual_basic_part, True) -class Cp2102(Interface, PinMappable): +class Cp2102(Interface): """USB-UART converter""" def __init__(self) -> None: diff --git a/edg/parts/logic/Logic_74Ahct.py b/edg/parts/logic/Logic_74Ahct.py index 840fb21df..193707fae 100644 --- a/edg/parts/logic/Logic_74Ahct.py +++ b/edg/parts/logic/Logic_74Ahct.py @@ -51,6 +51,7 @@ def contents(self) -> None: pnp_rot=-90, ) self.assign(self.lcsc_part, "C842287") + self.assign(self.actual_basic_part, False) class L74Ahct1g125(Interface, GeneratorBlock): diff --git a/edg/parts/microcontroller/Ice40up.py b/edg/parts/microcontroller/Ice40up.py index 649397c61..e4ddbadca 100644 --- a/edg/parts/microcontroller/Ice40up.py +++ b/edg/parts/microcontroller/Ice40up.py @@ -7,7 +7,7 @@ from ..connector.Headers import PinHeader127DualShrouded -class Ice40TargetHeader(ProgrammingConnector, FootprintBlock): +class Ice40TargetHeader(ProgrammingConnector): """Custom programming header for iCE40 loosely based on the SWD pinning""" def __init__(self) -> None: From a61b94ab9cd389b9830eb1e651518d721491a369 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 1 Nov 2026 17:17:01 -0800 Subject: [PATCH 08/15] cleaning --- edg/abstract_parts/Capacitor.py | 2 +- edg/abstract_parts/IdealIoController.py | 1 + edg/abstract_parts/SwitchingVoltageRegulator.py | 7 ++++++- edg/abstract_parts/test_kicad_import_netlist.py | 7 +++++-- edg/circuits/test_opamp.py | 2 ++ edg/parts/display/oled/Er_Oled_096_1_1.py | 4 +++- edg/parts/display/oled/Er_Oled_096_1c.py | 4 +++- edg/parts/interface/IoExpander_Pca9554.py | 1 + edg/parts/power/motor/MotorDriver_Drv8833.py | 1 + edg/vendor_parts/Leds.py | 2 ++ 10 files changed, 25 insertions(+), 6 deletions(-) diff --git a/edg/abstract_parts/Capacitor.py b/edg/abstract_parts/Capacitor.py index 81878526e..5c5ba46a9 100644 --- a/edg/abstract_parts/Capacitor.py +++ b/edg/abstract_parts/Capacitor.py @@ -338,7 +338,7 @@ def _make_parallel_footprints(self, row: PartsTableRow) -> None: ... -class DummyCapacitorFootprint(DummyDevice, Capacitor, FootprintBlock): +class DummyCapacitorFootprint(Capacitor, DummyDevice, FootprintBlock): """Dummy capacitor that takes in all its parameters (footprint, value, etc) and does not do any computation. Used as the leaf block for generating parallel capacitors. diff --git a/edg/abstract_parts/IdealIoController.py b/edg/abstract_parts/IdealIoController.py index 9ee1479a4..5228e74e1 100644 --- a/edg/abstract_parts/IdealIoController.py +++ b/edg/abstract_parts/IdealIoController.py @@ -102,3 +102,4 @@ def generate(self) -> None: self.i2s.append_elt(I2sController(dio_model), elt) self.assign(self.pwr.current_draw, io_current_draw_builder) + self.assign(self.actual_pin_assigns, []) diff --git a/edg/abstract_parts/SwitchingVoltageRegulator.py b/edg/abstract_parts/SwitchingVoltageRegulator.py index 58f8b6e3e..26db0b5be 100644 --- a/edg/abstract_parts/SwitchingVoltageRegulator.py +++ b/edg/abstract_parts/SwitchingVoltageRegulator.py @@ -81,6 +81,7 @@ def contents(self) -> None: ) self.pwr_out.init_from(VoltageSource(voltage=effective_output_voltage)) self.reset.init_from(DigitalSink()) + self.assign(self.actual_frequency, RangeExpr.EMPTY) @abstract_block_default(lambda: IdealBoostConverter) @@ -113,6 +114,7 @@ def contents(self) -> None: ) self.pwr_out.init_from(VoltageSource(voltage=effective_output_voltage)) self.reset.init_from(DigitalSink()) + self.assign(self.actual_frequency, RangeExpr.EMPTY) @abstract_block_default(lambda: BuckBoostConverter) @@ -121,7 +123,10 @@ class BuckBoostConverter(SwitchingVoltageRegulator): class IdealBuckBoostConverter(IdealVoltageRegulator, BuckBoostConverter): - pass + @override + def contents(self) -> None: + super().contents() + self.assign(self.actual_frequency, RangeExpr.EMPTY) class DiscreteBuckBoostConverter(BuckBoostConverter): diff --git a/edg/abstract_parts/test_kicad_import_netlist.py b/edg/abstract_parts/test_kicad_import_netlist.py index 2e4a85122..e7c7ccdb8 100644 --- a/edg/abstract_parts/test_kicad_import_netlist.py +++ b/edg/abstract_parts/test_kicad_import_netlist.py @@ -3,7 +3,7 @@ # to avoid re-defining NetBlock, this makes specific imports instead of 'from . import *' from ..core import * -from ..electronics_model import Passive, FootprintBlock +from ..electronics_model import Passive, FootprintBlock, Ohm, Volt, Watt, DummyDevice from ..electronics_interfaces.test_netlist import NetlistTestCase, Net, net_pin, net_block from ..electronics_interfaces.test_kicad_import_blackbox import KiCadBlackboxBlock from .Resistor import Resistor @@ -24,7 +24,7 @@ def __init__(self) -> None: (self.dummyout,), _ = self.chain(self.dut.out, self.Block(PassiveDummy())) -class DummyResistor(Resistor, FootprintBlock): +class DummyResistor(Resistor, DummyDevice, FootprintBlock): def __init__(self) -> None: super().__init__(Range.all()) self.footprint( @@ -35,6 +35,9 @@ def __init__(self) -> None: "2": self.b, }, ) + self.assign(self.actual_resistance, (0, 0) * Ohm) + self.assign(self.actual_power_rating, (0, 0) * Watt) + self.assign(self.actual_voltage_rating, (0, 0) * Volt) class KiCadImportBlackboxTestCase(unittest.TestCase): diff --git a/edg/circuits/test_opamp.py b/edg/circuits/test_opamp.py index f48f3216b..01d42b37e 100644 --- a/edg/circuits/test_opamp.py +++ b/edg/circuits/test_opamp.py @@ -22,6 +22,8 @@ class TestResistor(Resistor): def contents(self) -> None: super().contents() self.assign(self.actual_resistance, self.resistance) + self.assign(self.actual_power_rating, (0, 0) * Watt) + self.assign(self.actual_voltage_rating, (0, 0) * Volt) class AmplifierTestTop(Block): diff --git a/edg/parts/display/oled/Er_Oled_096_1_1.py b/edg/parts/display/oled/Er_Oled_096_1_1.py index c9e6e96b7..b98ceafe3 100644 --- a/edg/parts/display/oled/Er_Oled_096_1_1.py +++ b/edg/parts/display/oled/Er_Oled_096_1_1.py @@ -126,7 +126,9 @@ def __init__(self) -> None: self.i2c = self.Port( I2cTarget(DigitalBidir.empty(), addresses=[0x3C], frequency_limit=(0, 400) * kHertz), optional=True ) - self.generator_param(self.spi.is_connected(), self.dc.is_connected(), self.i2c.is_connected()) + self.generator_param( + self.spi.is_connected(), self.cs.is_connected(), self.dc.is_connected(), self.i2c.is_connected() + ) @override def contents(self) -> None: diff --git a/edg/parts/display/oled/Er_Oled_096_1c.py b/edg/parts/display/oled/Er_Oled_096_1c.py index 5004066a5..4152a7d36 100644 --- a/edg/parts/display/oled/Er_Oled_096_1c.py +++ b/edg/parts/display/oled/Er_Oled_096_1c.py @@ -121,7 +121,9 @@ def __init__(self) -> None: self.i2c = self.Port( I2cTarget(DigitalBidir.empty(), addresses=[0x3C], frequency_limit=(0, 400) * kHertz), optional=True ) - self.generator_param(self.spi.is_connected(), self.dc.is_connected(), self.i2c.is_connected()) + self.generator_param( + self.spi.is_connected(), self.cs.is_connected(), self.dc.is_connected(), self.i2c.is_connected() + ) @override def contents(self) -> None: diff --git a/edg/parts/interface/IoExpander_Pca9554.py b/edg/parts/interface/IoExpander_Pca9554.py index 9e8be919e..a7e6c0884 100644 --- a/edg/parts/interface/IoExpander_Pca9554.py +++ b/edg/parts/interface/IoExpander_Pca9554.py @@ -87,6 +87,7 @@ def generate(self) -> None: pnp_rot=-90, ) self.assign(self.lcsc_part, "C86803") + self.assign(self.actual_basic_part, False) class Pca9554(IoExpander, PinMappable): diff --git a/edg/parts/power/motor/MotorDriver_Drv8833.py b/edg/parts/power/motor/MotorDriver_Drv8833.py index 95f5005f1..6dd123315 100644 --- a/edg/parts/power/motor/MotorDriver_Drv8833.py +++ b/edg/parts/power/motor/MotorDriver_Drv8833.py @@ -100,6 +100,7 @@ def contents(self) -> None: pnp_rot=-90, ) self.assign(self.lcsc_part, "C50506") + self.assign(self.actual_basic_part, False) class Drv8833(BrushedMotorDriver, GeneratorBlock): diff --git a/edg/vendor_parts/Leds.py b/edg/vendor_parts/Leds.py index 4a7209e32..ab2d88a4d 100644 --- a/edg/vendor_parts/Leds.py +++ b/edg/vendor_parts/Leds.py @@ -17,6 +17,7 @@ def contents(self) -> None: }, part="LED", ) + self.assign(self.actual_color, "") class ThtLed(Led, FootprintBlock): @@ -32,6 +33,7 @@ def contents(self) -> None: }, part="LED", ) + self.assign(self.actual_color, "") class Smt0606RgbLed(RgbLedCommonAnode, JlcPart, FootprintBlock): From 770afe0fefbaed843a553409ba3cbd971ae6baea Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 1 Nov 2026 17:24:24 -0800 Subject: [PATCH 09/15] pruning --- edg/parts/connector/UsbPorts.py | 2 +- edg/vendor_parts/generic/GenericCapacitor.py | 3 +++ edg/vendor_parts/jlc/JlcCapacitor.py | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/edg/parts/connector/UsbPorts.py b/edg/parts/connector/UsbPorts.py index 7eb887c08..b1607e38c 100644 --- a/edg/parts/connector/UsbPorts.py +++ b/edg/parts/connector/UsbPorts.py @@ -246,7 +246,7 @@ def contents(self) -> None: datasheet="https://www.ti.com/lit/ds/symlink/tpd2e009.pdf", ) self.assign(self.lcsc_part, "C2871371") - self.asign(self.actual_basic_part, False) + self.assign(self.actual_basic_part, False) class Pesd5v0x1bt(UsbEsdDiode, FootprintBlock, JlcPart): diff --git a/edg/vendor_parts/generic/GenericCapacitor.py b/edg/vendor_parts/generic/GenericCapacitor.py index 08978977c..9b4c82563 100644 --- a/edg/vendor_parts/generic/GenericCapacitor.py +++ b/edg/vendor_parts/generic/GenericCapacitor.py @@ -164,6 +164,9 @@ def select_package(nominal_capacitance: float, voltage: Range) -> Optional[str]: self.c[i] = self.Block(cap_model) self.connect(self.c[i].pos, self.pos) self.connect(self.c[i].neg, self.neg) + + self.footprint("", "", {}) # no footprint, but needed to satisfy required params + else: value = ESeriesUtil.choose_preferred_number(nominal_capacitance, ESeriesUtil.SERIES[24], 0) assert value is not None, "cannot select a preferred number" diff --git a/edg/vendor_parts/jlc/JlcCapacitor.py b/edg/vendor_parts/jlc/JlcCapacitor.py index 33368cfee..82aa14a9e 100644 --- a/edg/vendor_parts/jlc/JlcCapacitor.py +++ b/edg/vendor_parts/jlc/JlcCapacitor.py @@ -119,6 +119,7 @@ def _row_generate(self, row: PartsTableRow) -> None: super()._row_generate(row) # creates the footprint else: TableCapacitor._row_generate(self, row) # skips creating the footprint in PartsTableSelectorFootprint + self.assign(self.lcsc_part, "") self.assign(self.actual_basic_part, True) # dummy value self._make_parallel_footprints(row) @@ -140,6 +141,8 @@ def _make_parallel_footprints(self, row: PartsTableRow) -> None: self.connect(self.c[i].pos, self.pos) self.connect(self.c[i].neg, self.neg) + self.footprint("", "", {}) # no footprint, but needed to satisfy required params + class JlcDummyCapacitor(JlcPart, DummyCapacitorFootprint): """Dummy capacitor that additionally has JLC part fields""" From 800531e5de91d937a683da6258c8be9a41544fd6 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 1 Nov 2026 17:29:54 -0800 Subject: [PATCH 10/15] wip --- edg/abstract_parts/Connectors.py | 4 ++++ edg/parts/interface/IoExpander_Pcf8574.py | 1 + edg/parts/sensor/DistanceArray_Vl53l5cx.py | 2 +- examples/test_swd_debugger.py | 4 ++-- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/edg/abstract_parts/Connectors.py b/edg/abstract_parts/Connectors.py index 8d07f565a..461605fd1 100644 --- a/edg/abstract_parts/Connectors.py +++ b/edg/abstract_parts/Connectors.py @@ -46,6 +46,10 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.connect(self.conn.sig, self.a) self.connect(self.conn.gnd, self.gnd) + self.assign(self.actual_frequency_rating, (0, float("inf") * Henry)) + self.assign(self.actual_impedance, (0, float("inf") * Ohm)) + self.assign(self.actual_power_rating, (0, float("inf") * Watt)) + @abstract_block class UflConnector(RfConnector): diff --git a/edg/parts/interface/IoExpander_Pcf8574.py b/edg/parts/interface/IoExpander_Pcf8574.py index a23e9ef40..72c28991e 100644 --- a/edg/parts/interface/IoExpander_Pcf8574.py +++ b/edg/parts/interface/IoExpander_Pcf8574.py @@ -86,6 +86,7 @@ def generate(self) -> None: pnp_rot=-90, ) self.assign(self.lcsc_part, "C86832") + self.assign(self.actual_basic_part, False) class Pcf8574(IoExpander, PinMappable): diff --git a/edg/parts/sensor/DistanceArray_Vl53l5cx.py b/edg/parts/sensor/DistanceArray_Vl53l5cx.py index b7a897932..5f88e96ca 100644 --- a/edg/parts/sensor/DistanceArray_Vl53l5cx.py +++ b/edg/parts/sensor/DistanceArray_Vl53l5cx.py @@ -86,7 +86,7 @@ def contents(self) -> None: self.assign(self.actual_basic_part, False) -class Vl53l5cx(DistanceSensor, Resettable, GeneratorBlock): +class Vl53l5cx(DistanceSensor, GeneratorBlock): """Time-of-flight 8x8 multizone laser ranging sensor, up to 4m. Requires using the ST API, does not have a defined register map. Exposed pad must be thermally connected to GND. diff --git a/examples/test_swd_debugger.py b/examples/test_swd_debugger.py index f5703ad8a..969cc5b00 100644 --- a/examples/test_swd_debugger.py +++ b/examples/test_swd_debugger.py @@ -6,7 +6,7 @@ from .util import run_test_board -class SwdCortexSourceHeader(ProgrammingConnector, FootprintBlock): +class SwdCortexSourceHeader(ProgrammingConnector): def __init__(self) -> None: super().__init__() @@ -33,7 +33,7 @@ def contents(self) -> None: ) -class SwdCortexSourceTagConnect(ProgrammingConnector, FootprintBlock): +class SwdCortexSourceTagConnect(ProgrammingConnector): def __init__(self) -> None: super().__init__() From a1e59de798bbe0608f7e5479f50196476b2411ef Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 1 Nov 2026 17:32:53 -0800 Subject: [PATCH 11/15] clean linregs --- edg/abstract_parts/PartsTablePart.py | 2 +- edg/parts/power/converter/LinearRegulators.py | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/edg/abstract_parts/PartsTablePart.py b/edg/abstract_parts/PartsTablePart.py index 558d4a46e..6507c0d39 100644 --- a/edg/abstract_parts/PartsTablePart.py +++ b/edg/abstract_parts/PartsTablePart.py @@ -100,7 +100,7 @@ def generate(self) -> None: @abstract_block -class SelectorFootprint(PartsTablePart): +class SelectorFootprint(Block): """Mixin that allows a specified footprint, for Blocks that automatically select a part.""" def __init__( diff --git a/edg/parts/power/converter/LinearRegulators.py b/edg/parts/power/converter/LinearRegulators.py index f948c1bcc..45f6879ca 100644 --- a/edg/parts/power/converter/LinearRegulators.py +++ b/edg/parts/power/converter/LinearRegulators.py @@ -573,7 +573,6 @@ class Lp5907_Device( InternalSubcircuit, LinearRegulatorDevice, SelectorFootprint, - PartsTablePart, JlcPart, GeneratorBlock, FootprintBlock, @@ -587,7 +586,7 @@ def __init__(self, output_voltage: RangeLike): self.assign(self.actual_dropout, (50, 250) * mVolt) self.output_voltage = self.ArgParameter(output_voltage) - self.generator_param(self.output_voltage, self.part, self.filter_footprints) + self.generator_param(self.output_voltage, self.filter_footprints) self.en = self.Port( DigitalSink( @@ -707,9 +706,7 @@ def contents(self) -> None: self.connect(self.pwr_out, self.ic.pwr_out, self.out_cap.pwr) -class Tlv757p_Device( - InternalSubcircuit, LinearRegulatorDevice, PartsTablePart, JlcPart, GeneratorBlock, FootprintBlock -): +class Tlv757p_Device(InternalSubcircuit, LinearRegulatorDevice, JlcPart, GeneratorBlock, FootprintBlock): def __init__(self, output_voltage: RangeLike): super().__init__() @@ -718,7 +715,7 @@ def __init__(self, output_voltage: RangeLike): self.assign(self.actual_quiescent_current, (0.1, 40) * uAmp) # shutdown to worst-case ground current self.output_voltage = self.ArgParameter(output_voltage) - self.generator_param(self.output_voltage, self.part) + self.generator_param(self.output_voltage) self.en = self.Port(DigitalSink(voltage_limits=(0, 5.5) * Volt, input_thresholds=(0.3, 1) * Volt)) From 8ee28dc8d83dc48244b41043ecd243bb5da82af7 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 1 Nov 2026 17:34:20 -0800 Subject: [PATCH 12/15] Update JlcCapacitor.py --- edg/vendor_parts/jlc/JlcCapacitor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/edg/vendor_parts/jlc/JlcCapacitor.py b/edg/vendor_parts/jlc/JlcCapacitor.py index 82aa14a9e..35d2aa8a1 100644 --- a/edg/vendor_parts/jlc/JlcCapacitor.py +++ b/edg/vendor_parts/jlc/JlcCapacitor.py @@ -118,7 +118,9 @@ def _row_generate(self, row: PartsTableRow) -> None: if row[self.PARALLEL_COUNT] == 1: super()._row_generate(row) # creates the footprint else: - TableCapacitor._row_generate(self, row) # skips creating the footprint in PartsTableSelectorFootprint + TableDeratingCapacitor._row_generate( + self, row + ) # skips creating the footprint in PartsTableSelectorFootprint self.assign(self.lcsc_part, "") self.assign(self.actual_basic_part, True) # dummy value self._make_parallel_footprints(row) From 59c2670fa001c90aab96db2f0e596f3ce819fbbd Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 1 Nov 2026 17:42:37 -0800 Subject: [PATCH 13/15] wip --- edg/abstract_parts/Capacitor.py | 5 +++-- edg/circuits/test_switching_converters.py | 1 + edg/core/HdlUserExceptions.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/edg/abstract_parts/Capacitor.py b/edg/abstract_parts/Capacitor.py index 5c5ba46a9..4f89f65d5 100644 --- a/edg/abstract_parts/Capacitor.py +++ b/edg/abstract_parts/Capacitor.py @@ -323,11 +323,12 @@ def add_derated_row(row: PartsTableRow) -> Optional[Dict[PartsTableColumn, Any]] def _row_generate(self, row: PartsTableRow) -> None: """This one is weird. Because this is the last in the class order, this is called last. So the top subclass needs explicit logic to handle parallel capacitors.""" - super()._row_generate(row) + PartsTableSelector._row_generate(self, row) # ignore Capacitor _row_generate if row[self.PARALLEL_COUNT] == 1: + self.assign(self.actual_voltage_rating, row[self.VOLTAGE_RATING]) + self.assign(self.actual_capacitance, row[self.CAPACITANCE]) self.assign(self.actual_derated_capacitance, row[self.DERATED_CAPACITANCE]) else: - self.assign(self.actual_part, f"{row[self.PARALLEL_COUNT]}x {row[self.PART_NUMBER_COL]}") self.assign(self.actual_voltage_rating, row[self.VOLTAGE_RATING]) self.assign(self.actual_capacitance, row[self.PARALLEL_CAPACITANCE]) self.assign(self.actual_derated_capacitance, row[self.PARALLEL_DERATED_CAPACITANCE]) diff --git a/edg/circuits/test_switching_converters.py b/edg/circuits/test_switching_converters.py index 52181cc1b..212136e8c 100644 --- a/edg/circuits/test_switching_converters.py +++ b/edg/circuits/test_switching_converters.py @@ -155,6 +155,7 @@ def contents(self) -> None: self.assign(self.actual_inductance, self.inductance) self.assign(self.actual_current_rating, (0, 1.5) * Amp) self.assign(self.actual_frequency_rating, Range.all()) + self.assign(self.actual_resistance_dc, (0, 0) * Ohm) class BuckPowerPathTestTop(DesignTop): diff --git a/edg/core/HdlUserExceptions.py b/edg/core/HdlUserExceptions.py index ab1577e34..087d5c7c6 100644 --- a/edg/core/HdlUserExceptions.py +++ b/edg/core/HdlUserExceptions.py @@ -79,6 +79,6 @@ class OverassignParameterError(EdslUserError): def __init__(self, block: "BaseBlock", param: "ConstraintExpr") -> None: super().__init__( - f"{type(block).__name__} parameter has conflicting assignment (assigned multiple times)", + f"{type(block).__name__} parameter {block._name_of(param, debug=True)} has conflicting assignment (assigned multiple times)", "remove an assignment", ) From 59786c7f031fc9d80abdfb7c02676274ecac0cac Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 1 Nov 2026 17:46:57 -0800 Subject: [PATCH 14/15] Update LedDriver_Tps92200.py --- edg/parts/power/LedDriver_Tps92200.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/edg/parts/power/LedDriver_Tps92200.py b/edg/parts/power/LedDriver_Tps92200.py index 84f727ad1..a37cca6c9 100644 --- a/edg/parts/power/LedDriver_Tps92200.py +++ b/edg/parts/power/LedDriver_Tps92200.py @@ -5,7 +5,7 @@ class Tps92200_Device(InternalSubcircuit, JlcPart, FootprintBlock): - def __init__(self, peak_output_current: FloatLike): + def __init__(self): super().__init__() self.gnd = self.Port(Ground(), [Common]) @@ -70,7 +70,7 @@ def __init__( ) -> None: super().__init__() - self.ic = self.Block(Tps92200_Device(FloatExpr())) + self.ic = self.Block(Tps92200_Device()) self.gnd = self.Export(self.ic.gnd, [Common]) self.pwr = self.Export(self.ic.vin, [Power]) self.pwm = self.Export(self.ic.dim) From 627d3158016609b91a82ec16a738aa49c49c23fb Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 1 Nov 2026 17:53:59 -0800 Subject: [PATCH 15/15] Update Generator.py --- edg/core/Generator.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/edg/core/Generator.py b/edg/core/Generator.py index 24bf284fd..3bfe68eca 100644 --- a/edg/core/Generator.py +++ b/edg/core/Generator.py @@ -43,13 +43,15 @@ def generator_param(self, *params: ConstraintExpr) -> None: ) for param in params: if not isinstance(param, ConstraintExpr): - raise EdgTypeError(f"generator_param(...) param", param, ConstraintExpr) + raise EdgTypeError( + f"generator_param(...) param {self._name_of(param, debug=True)}", param, ConstraintExpr + ) if param.binding is None: raise BlockDefinitionError(type(self), "generator_param(...) param must be bound") if not isinstance(param.binding, (InitParamBinding, AllocatedBinding, IsConnectedBinding)): raise BlockDefinitionError( type(self), - "generator_param(...) param must be an __init__ param, port requested, or port is_connected", + f"generator_param(...) param {self._name_of(param, debug=True)} must be an __init__ param, port requested, or port is_connected", ) self._generator_params_list.append(param)