Skip to content
Draft
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
7 changes: 4 additions & 3 deletions edg/abstract_parts/Capacitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand All @@ -338,7 +339,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.

Expand Down
4 changes: 4 additions & 0 deletions edg/abstract_parts/Connectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
1 change: 1 addition & 0 deletions edg/abstract_parts/IdealIoController.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, [])
1 change: 1 addition & 0 deletions edg/abstract_parts/Neopixel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
2 changes: 1 addition & 1 deletion edg/abstract_parts/PartsTablePart.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__(
Expand Down
2 changes: 1 addition & 1 deletion edg/abstract_parts/SelectorArea.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
7 changes: 6 additions & 1 deletion edg/abstract_parts/SwitchingVoltageRegulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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):
Expand Down
7 changes: 5 additions & 2 deletions edg/abstract_parts/test_kicad_import_netlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -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):
Expand Down
2 changes: 2 additions & 0 deletions edg/circuits/test_opamp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
1 change: 1 addition & 0 deletions edg/circuits/test_switching_converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
65 changes: 63 additions & 2 deletions edg/core/Blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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))

Expand Down Expand Up @@ -362,14 +363,18 @@ 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 (
self._elaboration_state == BlockElaborationState.post_contents
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
Expand Down Expand Up @@ -430,13 +435,65 @@ def _populate_def_proto_port_init(self, pb: edgir.BlockLikeTypes, ref_map: Refab
ref_map,
)

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

unassigned: List[List[str]] = []

if isinstance(container_port, Port):
for port in container_port._ports:
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:
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():
unassigned.extend(self._collect_unassigned_port_params(elt, path + [name]))
else:
raise ValueError(f"unsupported port type {container_port}")

return unassigned

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"""
assert (
self._elaboration_state == BlockElaborationState.post_contents
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)
Expand Down Expand Up @@ -531,6 +588,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)

Expand Down
23 changes: 21 additions & 2 deletions edg/core/Generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -92,6 +94,23 @@ 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 _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()
Expand Down
25 changes: 23 additions & 2 deletions edg/core/HdlUserExceptions.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -61,3 +61,24 @@ 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_paths: List[List[str]]) -> None:
params_str = ", ".join([".".join(path) for path in param_paths])
super().__init__(
f"{type(block).__name__} parameters {params_str} missing 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 {block._name_of(param, debug=True)} has conflicting assignment (assigned multiple times)",
"remove an assignment",
)
20 changes: 20 additions & 0 deletions edg/core/HierarchyBlock.py
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,26 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab
ref_map,
)

@override
def _collect_unassigned_params(self) -> List[List[str]]:
unassigned = super()._collect_unassigned_params()

if (self.__class__, AbstractBlockProperty) in self._elt_properties:
return [] # abstract blocks do not need assigned parameters

# TODO instead of filtering by binding, init should have assign record

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)
):
unassigned.append([self._name_of(block), block._name_of(param)])

return unassigned

# TODO make this non-overriding?
@override
def _def_to_proto(self) -> edgir.HierarchyBlock:
Expand Down
7 changes: 6 additions & 1 deletion edg/core/Link.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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 _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
return []
Loading
Loading