diff --git a/compiler/src/main/scala/edg/ExprBuilder.scala b/compiler/src/main/scala/edg/ExprBuilder.scala index 4b2ef1756..abd55b7f3 100644 --- a/compiler/src/main/scala/edg/ExprBuilder.scala +++ b/compiler/src/main/scala/edg/ExprBuilder.scala @@ -133,6 +133,15 @@ object ExprBuilder { ) } + def RangeEmpty(): lit.ValueLit = { + lit.ValueLit(`type` = + lit.ValueLit.Type.Range(lit.RangeLit( + minimum = Some(Floating(Float.NaN)), + maximum = Some(Floating(Float.NaN)) + )) + ) + } + def Array(values: Seq[lit.ValueLit]): lit.ValueLit = { lit.ValueLit(`type` = lit.ValueLit.Type.Array(lit.ArrayLit( diff --git a/compiler/src/main/scala/edg/compiler/Compiler.scala b/compiler/src/main/scala/edg/compiler/Compiler.scala index eb613cdd7..2d1114fe8 100644 --- a/compiler/src/main/scala/edg/compiler/Compiler.scala +++ b/compiler/src/main/scala/edg/compiler/Compiler.scala @@ -117,7 +117,7 @@ class AssignNamer() { } object Compiler { - final val kExpectedProtoVersion = 8 + final val kExpectedProtoVersion = 9 } /** Compiler for a particular design, with an associated library to elaborate references from. diff --git a/compiler/src/main/scala/edg/compiler/ExprEvaluate.scala b/compiler/src/main/scala/edg/compiler/ExprEvaluate.scala index 2d53cd4ab..d06f4a90e 100644 --- a/compiler/src/main/scala/edg/compiler/ExprEvaluate.scala +++ b/compiler/src/main/scala/edg/compiler/ExprEvaluate.scala @@ -224,6 +224,16 @@ object ExprEvaluate { binarySet.op match { // Note promotion rules: range takes precedence, then float, then int // TODO: can we deduplicate these cases to delegate them to evalBinary? + case Op.EQ => (lhs, rhs) match { + case (lhss: ArrayValue[ExprValue] @unchecked, rhs: ExprValue) => + val resultElts = lhss.values.map { arrayElt => + evalBinary(expr.BinaryExpr(op = expr.BinaryExpr.Op.EQ), arrayElt, rhs) + } + ArrayValue(resultElts) + case _ => throw new ExprEvaluateException( + s"Unknown binary set operand types in $lhs ${binarySet.op} $rhs from $binarySet" + ) + } case Op.ADD => (lhs, rhs) match { case (ArrayValue.ExtractRange(arrayElts), rhs: RangeType) => val resultElts = arrayElts.map { arrayElt => @@ -363,6 +373,8 @@ object ExprEvaluate { evalUnary(expr.UnaryExpr(op = expr.UnaryExpr.Op.NEGATE), arrayElt) } ArrayValue(resultElts) + case ArrayValue.ExtractBoolean(arrayElts) => + ArrayValue(arrayElts.map { arrayElt => BooleanValue(!arrayElt) }) case _ => throw new ExprEvaluateException(s"Unknown unary set operand in ${unarySet.op} $vals from $unarySet") } diff --git a/compiler/src/main/scala/edg/compiler/ExprToString.scala b/compiler/src/main/scala/edg/compiler/ExprToString.scala index 794dd5501..1c622a38b 100644 --- a/compiler/src/main/scala/edg/compiler/ExprToString.scala +++ b/compiler/src/main/scala/edg/compiler/ExprToString.scala @@ -82,6 +82,7 @@ class ExprToString() extends ValueExprMap[String] { import expr.BinarySetExpr.Op object InfixOp { def unapply(op: Op): Option[String] = op match { + case Op.EQ => Some("==") case Op.ADD => Some("+") case Op.MULT => Some("×") case Op.CONCAT => None @@ -91,7 +92,7 @@ class ExprToString() extends ValueExprMap[String] { object PrefixOp { def unapply(op: Op): Option[String] = op match { case Op.CONCAT => Some("concat") - case Op.ADD | Op.MULT => None + case Op.EQ | Op.ADD | Op.MULT => None case Op.UNDEFINED | Op.Unrecognized(_) => None } } diff --git a/compiler/src/test/scala/edg/compiler/ExprEvaluateTest.scala b/compiler/src/test/scala/edg/compiler/ExprEvaluateTest.scala index 1667a507d..3c09f2bed 100644 --- a/compiler/src/test/scala/edg/compiler/ExprEvaluateTest.scala +++ b/compiler/src/test/scala/edg/compiler/ExprEvaluateTest.scala @@ -300,6 +300,17 @@ class ExprEvaluateTest extends AnyFlatSpec { it should "handle array unary set ops" in { import edg.ExprBuilder.Literal import edgir.expr.expr.UnarySetExpr.Op + evalTest.map( + ValueExpr.UnarySetOp( + Op.NEGATE, + ValueExpr.Literal(Seq( + Literal.Boolean(false), + Literal.Boolean(true), + Literal.Boolean(false), + )) + ) + ) should equal(ArrayValue(Seq(BooleanValue(true), BooleanValue(false), BooleanValue(true)))) + evalTest.map( ValueExpr.UnarySetOp( Op.FLATTEN, @@ -327,6 +338,21 @@ class ExprEvaluateTest extends AnyFlatSpec { it should "handle array-value (broadcast) ops" in { import edg.ExprBuilder.Literal import edgir.expr.expr.BinarySetExpr.Op + evalTest.map( + ValueExpr.BinSetOp( + Op.EQ, + ValueExpr.Literal(Seq(Literal.Range(0, 10), Literal.Range(1, 11))), + ValueExpr.Literal(0, 10) + ) + ) should equal(ArrayValue(Seq(BooleanValue(true), BooleanValue(false)))) + evalTest.map( + ValueExpr.BinSetOp( + Op.EQ, + ValueExpr.Literal(Seq(Literal.Range(0, 10), Literal.RangeEmpty())), + ValueExpr.LiteralRangeEmpty() + ) + ) should equal(ArrayValue(Seq(BooleanValue(false), BooleanValue(true)))) + evalTest.map( ValueExpr.BinSetOp( Op.ADD, diff --git a/edg/abstract_parts/Categories.py b/edg/abstract_parts/Categories.py index 7174f76d4..169c26b9a 100644 --- a/edg/abstract_parts/Categories.py +++ b/edg/abstract_parts/Categories.py @@ -384,13 +384,6 @@ class PassiveComponent(DiscreteComponent): pass -@abstract_block -class DummyDevice(InternalBlock): - """Non-physical "device" used to affect parameters.""" - - pass - - @abstract_block class IdealModel(InternalBlock): """Ideal model device that can be used as a placeholder to get a design compiling diff --git a/edg/abstract_parts/DummyDevices.py b/edg/abstract_parts/DummyDevices.py index 7bd0ab83a..0ef9b3865 100644 --- a/edg/abstract_parts/DummyDevices.py +++ b/edg/abstract_parts/DummyDevices.py @@ -10,31 +10,6 @@ def __init__(self) -> None: self.io = self.Port(Passive(), [InOut]) -class DummyGround(DummyDevice): - def __init__(self) -> None: - super().__init__() - self.gnd = self.Port(Ground(), [Common, InOut]) - - -class DummyVoltageSource(DummyDevice): - def __init__(self, voltage_out: RangeLike = RangeExpr.ZERO, current_limits: RangeLike = RangeExpr.ALL) -> None: - super().__init__() - - self.pwr = self.Port(VoltageSource(voltage_out=voltage_out, current_limits=current_limits), [Power, InOut]) - - self.current_drawn = self.Parameter(RangeExpr(self.pwr.link().current_drawn)) - self.voltage_limits = self.Parameter(RangeExpr(self.pwr.link().voltage_limits)) - - -class DummyVoltageSink(DummyDevice): - def __init__(self, voltage_limit: RangeLike = RangeExpr.ALL, current_draw: RangeLike = RangeExpr.ZERO) -> None: - super().__init__() - - self.pwr = self.Port(VoltageSink(voltage_limits=voltage_limit, current_draw=current_draw), [Power, InOut]) - self.voltage = self.Parameter(RangeExpr(self.pwr.link().voltage)) - self.current_limits = self.Parameter(RangeExpr(self.pwr.link().current_limits)) - - class DummyDigitalSource(DummyDevice): def __init__(self, voltage_out: RangeLike = RangeExpr.ZERO, current_limits: RangeLike = RangeExpr.ALL) -> None: super().__init__() diff --git a/edg/abstract_parts/__init__.py b/edg/abstract_parts/__init__.py index e20252e13..49cba6a7b 100644 --- a/edg/abstract_parts/__init__.py +++ b/edg/abstract_parts/__init__.py @@ -11,7 +11,6 @@ PartsTableSelectorFootprint, ) -from .Categories import DummyDevice from .Categories import DiscreteComponent, DiscreteSemiconductor, PassiveComponent from .Categories import DiscreteApplication from .Categories import Analog, OpampApplication diff --git a/edg/core/ArrayExpr.py b/edg/core/ArrayExpr.py index d564508d1..cbacebad7 100644 --- a/edg/core/ArrayExpr.py +++ b/edg/core/ArrayExpr.py @@ -135,6 +135,9 @@ def hull(self) -> ArrayEltType: class ArrayBoolExpr(ArrayExpr[BoolExpr, List[bool], ArrayBoolLike]): _elt_type = BoolExpr + def __invert__(self) -> ArrayBoolExpr: + return self._new_bind(UnarySetOpBinding(self, BoolOp.op_not)) + def any(self) -> BoolExpr: return BoolExpr()._new_bind(UnarySetOpBinding(self, BoolOp.op_or)) @@ -179,6 +182,11 @@ def __rtruediv__(self, other: Union[FloatLike, RangeLike]) -> ArrayRangeExpr: self._create_unary_set_op(NumericOp.invert), RangeExpr._to_expr_type(other), NumericOp.mul ) + def elts_equals(self, other: RangeLike) -> ArrayBoolExpr: + """Returns an ArrayBoolExpr of equality between each element of this and single-element other. + TODO: generalize to equality for other array types, needs some generic version of _to_expr_type""" + return ArrayBoolExpr()._new_bind(BinarySetOpBinding(self, RangeExpr._to_expr_type(other), EqOp.all_equal)) + ArrayStringLike = Union["ArrayStringExpr", Sequence[StringLike]] diff --git a/edg/core/Binding.py b/edg/core/Binding.py index 25d2f503f..62620bf06 100644 --- a/edg/core/Binding.py +++ b/edg/core/Binding.py @@ -314,6 +314,7 @@ def __init__(self, src: ConstraintExpr, op: Union[NumericOp, BoolOp, EqOp, Range NumericOp.sum: edgir.UnarySetExpr.SUM, BoolOp.op_and: edgir.UnarySetExpr.ALL_TRUE, BoolOp.op_or: edgir.UnarySetExpr.ANY_TRUE, + BoolOp.op_not: edgir.UnarySetExpr.NEGATE, EqOp.all_equal: edgir.UnarySetExpr.ALL_EQ, EqOp.all_unique: edgir.UnarySetExpr.ALL_UNIQUE, RangeSetOp.min: edgir.UnarySetExpr.MINIMUM, @@ -391,11 +392,12 @@ class BinarySetOpBinding(Binding): def __repr__(self) -> str: return f"BinaryOp({self.op}, ...)" - def __init__(self, lhset: ConstraintExpr, rhs: ConstraintExpr, op: NumericOp): + def __init__(self, lhset: ConstraintExpr, rhs: ConstraintExpr, op: Union[NumericOp, EqOp]): self.op_map = { # Numeric NumericOp.add: edgir.BinarySetExpr.ADD, NumericOp.mul: edgir.BinarySetExpr.MULT, + EqOp.all_equal: edgir.BinarySetExpr.EQ, } super().__init__() diff --git a/edg/core/resources/edg-compiler-precompiled.jar b/edg/core/resources/edg-compiler-precompiled.jar index 5aa815ab5..d9facb499 100644 Binary files a/edg/core/resources/edg-compiler-precompiled.jar and b/edg/core/resources/edg-compiler-precompiled.jar differ diff --git a/edg/edgir/expr_pb2.py b/edg/edgir/expr_pb2.py index 6b0f7950b..9bc92c74b 100644 --- a/edg/edgir/expr_pb2.py +++ b/edg/edgir/expr_pb2.py @@ -11,7 +11,7 @@ from ..edgir import lit_pb2 as edgir_dot_lit__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( - b'\n\x10edgir/expr.proto\x12\nedgir.expr\x1a\x0fedgir/ref.proto\x1a\x12edgir/common.proto\x1a\x0fedgir/lit.proto"\xb4\x01\n\tUnaryExpr\x12$\n\x02op\x18\x01 \x01(\x0e2\x18.edgir.expr.UnaryExpr.Op\x12"\n\x03val\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr"]\n\x02Op\x12\r\n\tUNDEFINED\x10\x00\x12\n\n\x06NEGATE\x10\x01\x12\x07\n\x03NOT\x10\x02\x12\n\n\x06INVERT\x10\x03\x12\x07\n\x03MIN\x10\x04\x12\x07\n\x03MAX\x10\x05\x12\n\n\x06CENTER\x10\x06\x12\t\n\x05WIDTH\x10\x07"\x9f\x02\n\x0cUnarySetExpr\x12\'\n\x02op\x18\x01 \x01(\x0e2\x1b.edgir.expr.UnarySetExpr.Op\x12#\n\x04vals\x18\x04 \x01(\x0b2\x15.edgir.expr.ValueExpr"\xc0\x01\n\x02Op\x12\r\n\tUNDEFINED\x10\x00\x12\x07\n\x03SUM\x10\x01\x12\x0c\n\x08ALL_TRUE\x10\x02\x12\x0c\n\x08ANY_TRUE\x10\x03\x12\n\n\x06ALL_EQ\x10\x04\x12\x0e\n\nALL_UNIQUE\x10\x05\x12\x0b\n\x07MAXIMUM\x10\n\x12\x0b\n\x07MINIMUM\x10\x0b\x12\x0f\n\x0bSET_EXTRACT\x10\x0c\x12\x10\n\x0cINTERSECTION\x10\r\x12\x08\n\x04HULL\x10\x0e\x12\n\n\x06NEGATE\x10\x14\x12\n\n\x06INVERT\x10\x15\x12\x0b\n\x07FLATTEN\x10\x1e"\xd4\x02\n\nBinaryExpr\x12%\n\x02op\x18\x01 \x01(\x0e2\x19.edgir.expr.BinaryExpr.Op\x12"\n\x03lhs\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12"\n\x03rhs\x18\x03 \x01(\x0b2\x15.edgir.expr.ValueExpr"\xd6\x01\n\x02Op\x12\r\n\tUNDEFINED\x10\x00\x12\x07\n\x03ADD\x10\n\x12\x08\n\x04MULT\x10\x0c\x12\x0f\n\x0bSHRINK_MULT\x107\x12\x07\n\x03AND\x10\x14\x12\x06\n\x02OR\x10\x15\x12\x07\n\x03XOR\x10\x16\x12\x0b\n\x07IMPLIES\x10\x17\x12\x06\n\x02EQ\x10\x1e\x12\x07\n\x03NEQ\x10\x1f\x12\x06\n\x02GT\x10(\x12\x07\n\x03GTE\x10)\x12\x06\n\x02LT\x10*\x12\x07\n\x03LTE\x10,\x12\x07\n\x03MAX\x10-\x12\x07\n\x03MIN\x10.\x12\x10\n\x0cINTERSECTION\x103\x12\x08\n\x04HULL\x106\x12\n\n\x06WITHIN\x105\x12\t\n\x05RANGE\x10\x01"\xb7\x01\n\rBinarySetExpr\x12(\n\x02op\x18\x01 \x01(\x0e2\x1c.edgir.expr.BinarySetExpr.Op\x12$\n\x05lhset\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12"\n\x03rhs\x18\x03 \x01(\x0b2\x15.edgir.expr.ValueExpr"2\n\x02Op\x12\r\n\tUNDEFINED\x10\x00\x12\x07\n\x03ADD\x10\n\x12\x08\n\x04MULT\x10\x0c\x12\n\n\x06CONCAT\x10\x14"0\n\tArrayExpr\x12#\n\x04vals\x18\x01 \x03(\x0b2\x15.edgir.expr.ValueExpr"[\n\tRangeExpr\x12&\n\x07minimum\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12&\n\x07maximum\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr"\x80\x01\n\nStructExpr\x12.\n\x04vals\x18\x01 \x03(\x0b2 .edgir.expr.StructExpr.ValsEntry\x1aB\n\tValsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12$\n\x05value\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr:\x028\x01"\xa3\x01\n\x0eIfThenElseExpr\x12#\n\x04cond\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12"\n\x03tru\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12"\n\x03fal\x18\x03 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12$\n\x04meta\x18\x7f \x01(\x0b2\x16.edgir.common.Metadata"]\n\x0bExtractExpr\x12(\n\tcontainer\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12$\n\x05index\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr"^\n\x0eMapExtractExpr\x12(\n\tcontainer\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12"\n\x04path\x18\x02 \x01(\x0b2\x14.edgir.ref.LocalPath"\x91\x01\n\rConnectedExpr\x12)\n\nblock_port\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12(\n\tlink_port\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12+\n\x08expanded\x18\x03 \x03(\x0b2\x19.edgir.expr.ConnectedExpr"\x9c\x01\n\x0cExportedExpr\x12,\n\rexterior_port\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x122\n\x13internal_block_port\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12*\n\x08expanded\x18\x03 \x03(\x0b2\x18.edgir.expr.ExportedExpr"S\n\nAssignExpr\x12!\n\x03dst\x18\x01 \x01(\x0b2\x14.edgir.ref.LocalPath\x12"\n\x03src\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr"\x97\x07\n\tValueExpr\x12&\n\x07literal\x18\x01 \x01(\x0b2\x13.edgir.lit.ValueLitH\x00\x12(\n\x06binary\x18\x02 \x01(\x0b2\x16.edgir.expr.BinaryExprH\x00\x12/\n\nbinary_set\x18\x12 \x01(\x0b2\x19.edgir.expr.BinarySetExprH\x00\x12&\n\x05unary\x18\x03 \x01(\x0b2\x15.edgir.expr.UnaryExprH\x00\x12-\n\tunary_set\x18\x04 \x01(\x0b2\x18.edgir.expr.UnarySetExprH\x00\x12&\n\x05array\x18\x06 \x01(\x0b2\x15.edgir.expr.ArrayExprH\x00\x12(\n\x06struct\x18\x07 \x01(\x0b2\x16.edgir.expr.StructExprH\x00\x12&\n\x05range\x18\x08 \x01(\x0b2\x15.edgir.expr.RangeExprH\x00\x120\n\nifThenElse\x18\n \x01(\x0b2\x1a.edgir.expr.IfThenElseExprH\x00\x12*\n\x07extract\x18\x0c \x01(\x0b2\x17.edgir.expr.ExtractExprH\x00\x121\n\x0bmap_extract\x18\x0e \x01(\x0b2\x1a.edgir.expr.MapExtractExprH\x00\x12.\n\tconnected\x18\x0f \x01(\x0b2\x19.edgir.expr.ConnectedExprH\x00\x12,\n\x08exported\x18\x10 \x01(\x0b2\x18.edgir.expr.ExportedExprH\x00\x123\n\x0econnectedArray\x18\x13 \x01(\x0b2\x19.edgir.expr.ConnectedExprH\x00\x121\n\rexportedArray\x18\x14 \x01(\x0b2\x18.edgir.expr.ExportedExprH\x00\x12(\n\x06assign\x18\x11 \x01(\x0b2\x16.edgir.expr.AssignExprH\x00\x122\n\x0eexportedTunnel\x18\x15 \x01(\x0b2\x18.edgir.expr.ExportedExprH\x00\x12.\n\x0cassignTunnel\x18\x16 \x01(\x0b2\x16.edgir.expr.AssignExprH\x00\x12#\n\x03ref\x18c \x01(\x0b2\x14.edgir.ref.LocalPathH\x00\x12$\n\x04meta\x18\x7f \x01(\x0b2\x16.edgir.common.MetadataB\x06\n\x04exprb\x06proto3' + b'\n\x10edgir/expr.proto\x12\nedgir.expr\x1a\x0fedgir/ref.proto\x1a\x12edgir/common.proto\x1a\x0fedgir/lit.proto"\xb4\x01\n\tUnaryExpr\x12$\n\x02op\x18\x01 \x01(\x0e2\x18.edgir.expr.UnaryExpr.Op\x12"\n\x03val\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr"]\n\x02Op\x12\r\n\tUNDEFINED\x10\x00\x12\n\n\x06NEGATE\x10\x01\x12\x07\n\x03NOT\x10\x02\x12\n\n\x06INVERT\x10\x03\x12\x07\n\x03MIN\x10\x04\x12\x07\n\x03MAX\x10\x05\x12\n\n\x06CENTER\x10\x06\x12\t\n\x05WIDTH\x10\x07"\x9f\x02\n\x0cUnarySetExpr\x12\'\n\x02op\x18\x01 \x01(\x0e2\x1b.edgir.expr.UnarySetExpr.Op\x12#\n\x04vals\x18\x04 \x01(\x0b2\x15.edgir.expr.ValueExpr"\xc0\x01\n\x02Op\x12\r\n\tUNDEFINED\x10\x00\x12\x07\n\x03SUM\x10\x01\x12\x0c\n\x08ALL_TRUE\x10\x02\x12\x0c\n\x08ANY_TRUE\x10\x03\x12\n\n\x06ALL_EQ\x10\x04\x12\x0e\n\nALL_UNIQUE\x10\x05\x12\x0b\n\x07MAXIMUM\x10\n\x12\x0b\n\x07MINIMUM\x10\x0b\x12\x0f\n\x0bSET_EXTRACT\x10\x0c\x12\x10\n\x0cINTERSECTION\x10\r\x12\x08\n\x04HULL\x10\x0e\x12\n\n\x06NEGATE\x10\x14\x12\n\n\x06INVERT\x10\x15\x12\x0b\n\x07FLATTEN\x10\x1e"\xd4\x02\n\nBinaryExpr\x12%\n\x02op\x18\x01 \x01(\x0e2\x19.edgir.expr.BinaryExpr.Op\x12"\n\x03lhs\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12"\n\x03rhs\x18\x03 \x01(\x0b2\x15.edgir.expr.ValueExpr"\xd6\x01\n\x02Op\x12\r\n\tUNDEFINED\x10\x00\x12\x07\n\x03ADD\x10\n\x12\x08\n\x04MULT\x10\x0c\x12\x0f\n\x0bSHRINK_MULT\x107\x12\x07\n\x03AND\x10\x14\x12\x06\n\x02OR\x10\x15\x12\x07\n\x03XOR\x10\x16\x12\x0b\n\x07IMPLIES\x10\x17\x12\x06\n\x02EQ\x10\x1e\x12\x07\n\x03NEQ\x10\x1f\x12\x06\n\x02GT\x10(\x12\x07\n\x03GTE\x10)\x12\x06\n\x02LT\x10*\x12\x07\n\x03LTE\x10,\x12\x07\n\x03MAX\x10-\x12\x07\n\x03MIN\x10.\x12\x10\n\x0cINTERSECTION\x103\x12\x08\n\x04HULL\x106\x12\n\n\x06WITHIN\x105\x12\t\n\x05RANGE\x10\x01"\xbf\x01\n\rBinarySetExpr\x12(\n\x02op\x18\x01 \x01(\x0e2\x1c.edgir.expr.BinarySetExpr.Op\x12$\n\x05lhset\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12"\n\x03rhs\x18\x03 \x01(\x0b2\x15.edgir.expr.ValueExpr":\n\x02Op\x12\r\n\tUNDEFINED\x10\x00\x12\x07\n\x03ADD\x10\n\x12\x08\n\x04MULT\x10\x0c\x12\n\n\x06CONCAT\x10\x14\x12\x06\n\x02EQ\x10\x1e"0\n\tArrayExpr\x12#\n\x04vals\x18\x01 \x03(\x0b2\x15.edgir.expr.ValueExpr"[\n\tRangeExpr\x12&\n\x07minimum\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12&\n\x07maximum\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr"\x80\x01\n\nStructExpr\x12.\n\x04vals\x18\x01 \x03(\x0b2 .edgir.expr.StructExpr.ValsEntry\x1aB\n\tValsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12$\n\x05value\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr:\x028\x01"\xa3\x01\n\x0eIfThenElseExpr\x12#\n\x04cond\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12"\n\x03tru\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12"\n\x03fal\x18\x03 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12$\n\x04meta\x18\x7f \x01(\x0b2\x16.edgir.common.Metadata"]\n\x0bExtractExpr\x12(\n\tcontainer\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12$\n\x05index\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr"^\n\x0eMapExtractExpr\x12(\n\tcontainer\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12"\n\x04path\x18\x02 \x01(\x0b2\x14.edgir.ref.LocalPath"\x91\x01\n\rConnectedExpr\x12)\n\nblock_port\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12(\n\tlink_port\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12+\n\x08expanded\x18\x03 \x03(\x0b2\x19.edgir.expr.ConnectedExpr"\x9c\x01\n\x0cExportedExpr\x12,\n\rexterior_port\x18\x01 \x01(\x0b2\x15.edgir.expr.ValueExpr\x122\n\x13internal_block_port\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr\x12*\n\x08expanded\x18\x03 \x03(\x0b2\x18.edgir.expr.ExportedExpr"S\n\nAssignExpr\x12!\n\x03dst\x18\x01 \x01(\x0b2\x14.edgir.ref.LocalPath\x12"\n\x03src\x18\x02 \x01(\x0b2\x15.edgir.expr.ValueExpr"\x97\x07\n\tValueExpr\x12&\n\x07literal\x18\x01 \x01(\x0b2\x13.edgir.lit.ValueLitH\x00\x12(\n\x06binary\x18\x02 \x01(\x0b2\x16.edgir.expr.BinaryExprH\x00\x12/\n\nbinary_set\x18\x12 \x01(\x0b2\x19.edgir.expr.BinarySetExprH\x00\x12&\n\x05unary\x18\x03 \x01(\x0b2\x15.edgir.expr.UnaryExprH\x00\x12-\n\tunary_set\x18\x04 \x01(\x0b2\x18.edgir.expr.UnarySetExprH\x00\x12&\n\x05array\x18\x06 \x01(\x0b2\x15.edgir.expr.ArrayExprH\x00\x12(\n\x06struct\x18\x07 \x01(\x0b2\x16.edgir.expr.StructExprH\x00\x12&\n\x05range\x18\x08 \x01(\x0b2\x15.edgir.expr.RangeExprH\x00\x120\n\nifThenElse\x18\n \x01(\x0b2\x1a.edgir.expr.IfThenElseExprH\x00\x12*\n\x07extract\x18\x0c \x01(\x0b2\x17.edgir.expr.ExtractExprH\x00\x121\n\x0bmap_extract\x18\x0e \x01(\x0b2\x1a.edgir.expr.MapExtractExprH\x00\x12.\n\tconnected\x18\x0f \x01(\x0b2\x19.edgir.expr.ConnectedExprH\x00\x12,\n\x08exported\x18\x10 \x01(\x0b2\x18.edgir.expr.ExportedExprH\x00\x123\n\x0econnectedArray\x18\x13 \x01(\x0b2\x19.edgir.expr.ConnectedExprH\x00\x121\n\rexportedArray\x18\x14 \x01(\x0b2\x18.edgir.expr.ExportedExprH\x00\x12(\n\x06assign\x18\x11 \x01(\x0b2\x16.edgir.expr.AssignExprH\x00\x122\n\x0eexportedTunnel\x18\x15 \x01(\x0b2\x18.edgir.expr.ExportedExprH\x00\x12.\n\x0cassignTunnel\x18\x16 \x01(\x0b2\x16.edgir.expr.AssignExprH\x00\x12#\n\x03ref\x18c \x01(\x0b2\x14.edgir.ref.LocalPathH\x00\x12$\n\x04meta\x18\x7f \x01(\x0b2\x16.edgir.common.MetadataB\x06\n\x04exprb\x06proto3' ) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "edgir.expr_pb2", globals()) @@ -32,28 +32,28 @@ _BINARYEXPR_OP._serialized_start = 686 _BINARYEXPR_OP._serialized_end = 900 _BINARYSETEXPR._serialized_start = 903 - _BINARYSETEXPR._serialized_end = 1086 + _BINARYSETEXPR._serialized_end = 1094 _BINARYSETEXPR_OP._serialized_start = 1036 - _BINARYSETEXPR_OP._serialized_end = 1086 - _ARRAYEXPR._serialized_start = 1088 - _ARRAYEXPR._serialized_end = 1136 - _RANGEEXPR._serialized_start = 1138 - _RANGEEXPR._serialized_end = 1229 - _STRUCTEXPR._serialized_start = 1232 - _STRUCTEXPR._serialized_end = 1360 - _STRUCTEXPR_VALSENTRY._serialized_start = 1294 - _STRUCTEXPR_VALSENTRY._serialized_end = 1360 - _IFTHENELSEEXPR._serialized_start = 1363 - _IFTHENELSEEXPR._serialized_end = 1526 - _EXTRACTEXPR._serialized_start = 1528 - _EXTRACTEXPR._serialized_end = 1621 - _MAPEXTRACTEXPR._serialized_start = 1623 - _MAPEXTRACTEXPR._serialized_end = 1717 - _CONNECTEDEXPR._serialized_start = 1720 - _CONNECTEDEXPR._serialized_end = 1865 - _EXPORTEDEXPR._serialized_start = 1868 - _EXPORTEDEXPR._serialized_end = 2024 - _ASSIGNEXPR._serialized_start = 2026 - _ASSIGNEXPR._serialized_end = 2109 - _VALUEEXPR._serialized_start = 2112 - _VALUEEXPR._serialized_end = 3031 + _BINARYSETEXPR_OP._serialized_end = 1094 + _ARRAYEXPR._serialized_start = 1096 + _ARRAYEXPR._serialized_end = 1144 + _RANGEEXPR._serialized_start = 1146 + _RANGEEXPR._serialized_end = 1237 + _STRUCTEXPR._serialized_start = 1240 + _STRUCTEXPR._serialized_end = 1368 + _STRUCTEXPR_VALSENTRY._serialized_start = 1302 + _STRUCTEXPR_VALSENTRY._serialized_end = 1368 + _IFTHENELSEEXPR._serialized_start = 1371 + _IFTHENELSEEXPR._serialized_end = 1534 + _EXTRACTEXPR._serialized_start = 1536 + _EXTRACTEXPR._serialized_end = 1629 + _MAPEXTRACTEXPR._serialized_start = 1631 + _MAPEXTRACTEXPR._serialized_end = 1725 + _CONNECTEDEXPR._serialized_start = 1728 + _CONNECTEDEXPR._serialized_end = 1873 + _EXPORTEDEXPR._serialized_start = 1876 + _EXPORTEDEXPR._serialized_end = 2032 + _ASSIGNEXPR._serialized_start = 2034 + _ASSIGNEXPR._serialized_end = 2117 + _VALUEEXPR._serialized_start = 2120 + _VALUEEXPR._serialized_end = 3039 diff --git a/edg/edgir/expr_pb2.pyi b/edg/edgir/expr_pb2.pyi index 47241f6f9..5ef2e0b8f 100644 --- a/edg/edgir/expr_pb2.pyi +++ b/edg/edgir/expr_pb2.pyi @@ -318,6 +318,8 @@ class BinarySetExpr(_message.Message): "* Mult :: Numeric a => (lhset : Set a, rhs : a) -> Set a\n :: Numeric a => (lhset : Set a, rhs : Range a) -> Set (Range a)\n :: Numeric a => (lhset : Set (Range a), rhs : a) -> Set (Range a)\n :: Numeric a => (lhset : Set (Range a), rhs : Range a) -> Set (Range a)\n " CONCAT: BinarySetExpr._Op.ValueType "String concatenate operator\n Concatenate : (lhs: String, rhss: Set[String]) -> Set[String] (prepend lhs to all elements)\n : (lhss: Set[String], rhs: String) -> Set[String] (append rhs to all elements)\n " + EQ: BinarySetExpr._Op.ValueType + "Equality operator\n (lhss: Set[A], elt: A) -> Set[Bool] (pointwise equality of all elements in the set to the element)\n " class Op(_Op, metaclass=_OpEnumTypeWrapper): ... UNDEFINED: BinarySetExpr.Op.ValueType @@ -327,6 +329,8 @@ class BinarySetExpr(_message.Message): "* Mult :: Numeric a => (lhset : Set a, rhs : a) -> Set a\n :: Numeric a => (lhset : Set a, rhs : Range a) -> Set (Range a)\n :: Numeric a => (lhset : Set (Range a), rhs : a) -> Set (Range a)\n :: Numeric a => (lhset : Set (Range a), rhs : Range a) -> Set (Range a)\n " CONCAT: BinarySetExpr.Op.ValueType "String concatenate operator\n Concatenate : (lhs: String, rhss: Set[String]) -> Set[String] (prepend lhs to all elements)\n : (lhss: Set[String], rhs: String) -> Set[String] (append rhs to all elements)\n " + EQ: BinarySetExpr.Op.ValueType + "Equality operator\n (lhss: Set[A], elt: A) -> Set[Bool] (pointwise equality of all elements in the set to the element)\n " OP_FIELD_NUMBER: _builtins.int LHSET_FIELD_NUMBER: _builtins.int RHS_FIELD_NUMBER: _builtins.int diff --git a/edg/electronics_model/DummyDevice.py b/edg/electronics_model/DummyDevice.py new file mode 100644 index 000000000..a02d86a95 --- /dev/null +++ b/edg/electronics_model/DummyDevice.py @@ -0,0 +1,8 @@ +from ..core import abstract_block, InternalBlock + + +@abstract_block +class DummyDevice(InternalBlock): + """Non-physical "device" used to affect parameters or for unit testing.""" + + pass diff --git a/edg/electronics_model/GroundDummyDevice.py b/edg/electronics_model/GroundDummyDevice.py new file mode 100644 index 000000000..183ac13e4 --- /dev/null +++ b/edg/electronics_model/GroundDummyDevice.py @@ -0,0 +1,9 @@ +from .DummyDevice import DummyDevice +from .GroundPort import Ground, Common +from ..core import InOut + + +class DummyGround(DummyDevice): + def __init__(self) -> None: + super().__init__() + self.gnd = self.Port(Ground(), [Common, InOut]) diff --git a/edg/electronics_model/PassivePort.py b/edg/electronics_model/PassivePort.py index 91850e338..4fbe5d528 100644 --- a/edg/electronics_model/PassivePort.py +++ b/edg/electronics_model/PassivePort.py @@ -27,18 +27,44 @@ def __init__(self, voltage_limits: RangeLike = RangeExpr.ALL): class PassiveAdapterVoltageSource(CircuitPortAdapter[VoltageSource]): # TODO we can't use **kwargs b/c init_in_parent needs the initializer list - def __init__(self, voltage_out: RangeLike = RangeExpr.ZERO, current_limits: RangeLike = RangeExpr.ALL): + def __init__( + self, + voltage_out: RangeLike = RangeExpr.ZERO, + current_limits: RangeLike = RangeExpr.ALL, + reverse_voltage_limits: RangeLike = RangeExpr.EMPTY, + reverse_current_draw: RangeLike = RangeExpr.EMPTY, + ): super().__init__() self.src = self.Port(Passive()) - self.dst = self.Port(VoltageSource(voltage_out=voltage_out, current_limits=current_limits)) + self.dst = self.Port( + VoltageSource( + voltage_out=voltage_out, + current_limits=current_limits, + reverse_voltage_limits=reverse_voltage_limits, + reverse_current_draw=reverse_current_draw, + ) + ) class PassiveAdapterVoltageSink(CircuitPortAdapter[VoltageSink]): # TODO we can't use **kwargs b/c the init hook needs an initializer list - def __init__(self, voltage_limits: RangeLike = RangeExpr.ALL, current_draw: RangeLike = RangeExpr.ZERO): + def __init__( + self, + voltage_limits: RangeLike = RangeExpr.ALL, + current_draw: RangeLike = RangeExpr.ZERO, + reverse_voltage_out: RangeLike = RangeExpr.EMPTY, + reverse_current_limits: RangeLike = RangeExpr.EMPTY, + ): super().__init__() self.src = self.Port(Passive()) - self.dst = self.Port(VoltageSink(voltage_limits=voltage_limits, current_draw=current_draw)) + self.dst = self.Port( + VoltageSink( + voltage_limits=voltage_limits, + current_draw=current_draw, + reverse_voltage_out=reverse_voltage_out, + reverse_current_limits=reverse_current_limits, + ) + ) class PassiveAdapterDigitalSource(CircuitPortAdapter[DigitalSource]): diff --git a/edg/electronics_model/VoltageDummyDevice.py b/edg/electronics_model/VoltageDummyDevice.py new file mode 100644 index 000000000..8ca1c52a7 --- /dev/null +++ b/edg/electronics_model/VoltageDummyDevice.py @@ -0,0 +1,53 @@ +from .DummyDevice import DummyDevice +from .VoltagePorts import VoltageSource, VoltageSink, Power +from ..core import BoolExpr, RangeExpr, RangeLike, InOut + + +class DummyVoltageSource(DummyDevice): + def __init__( + self, + voltage_out: RangeLike = RangeExpr.ZERO, + current_limits: RangeLike = RangeExpr.ALL, + reverse_voltage_limits: RangeLike = RangeExpr.EMPTY, + reverse_current_draw: RangeLike = RangeExpr.EMPTY, + ) -> None: + super().__init__() + + self.pwr = self.Port( + VoltageSource( + voltage_out=voltage_out, + current_limits=current_limits, + reverse_voltage_limits=reverse_voltage_limits, + reverse_current_draw=reverse_current_draw, + ), + [Power, InOut], + ) + + self.current_drawn = self.Parameter(RangeExpr(self.pwr.link().current_drawn)) + self.voltage_limits = self.Parameter(RangeExpr(self.pwr.link().voltage_limits)) + self.reverse_voltage = self.Parameter(RangeExpr(self.pwr.link().reverse_voltage)) + + +class DummyVoltageSink(DummyDevice): + + def __init__( + self, + voltage_limit: RangeLike = RangeExpr.ALL, + current_draw: RangeLike = RangeExpr.ZERO, + reverse_voltage_out: RangeLike = RangeExpr.EMPTY, + reverse_current_limits: RangeLike = RangeExpr.EMPTY, + ) -> None: + super().__init__() + + self.pwr = self.Port( + VoltageSink( + voltage_limits=voltage_limit, + current_draw=current_draw, + reverse_voltage_out=reverse_voltage_out, + reverse_current_limits=reverse_current_limits, + ), + [Power, InOut], + ) + + self.voltage = self.Parameter(RangeExpr(self.pwr.link().voltage)) + self.current_limits = self.Parameter(RangeExpr(self.pwr.link().current_limits)) diff --git a/edg/electronics_model/VoltagePorts.py b/edg/electronics_model/VoltagePorts.py index c1a479eef..5c6adeaac 100644 --- a/edg/electronics_model/VoltagePorts.py +++ b/edg/electronics_model/VoltagePorts.py @@ -41,6 +41,11 @@ def __init__(self) -> None: self.current_drawn = self.Parameter(RangeExpr()) self.current_limits = self.Parameter(RangeExpr()) + self.reverse_voltage = self.Parameter(RangeExpr()) + self.reverse_voltage_limits = self.Parameter(RangeExpr()) + self.reverse_current_drawn = self.Parameter(RangeExpr()) + self.reverse_current_limits = self.Parameter(RangeExpr()) + @override def contents(self) -> None: super().contents() @@ -58,24 +63,71 @@ def contents(self) -> None: self.assign(self.voltage, self.source.voltage_out) self.assign(self.voltage_limits, self.sinks.intersection(lambda x: x.voltage_limits)) - self.require(self.voltage_limits.contains(self.voltage), "overvoltage") + self.require(self.voltage_limits.contains(self.voltage), "voltage out of limits") self.assign(self.current_limits, self.source.current_limits) - self.assign(self.current_drawn, self.sinks.sum(lambda x: x.current_draw)) - self.require(self.current_limits.contains(self.current_drawn), "overcurrent") + self.require(self.current_limits.contains(self.current_drawn), "current draw out of limits") + + has_reverse_voltage = self.reverse_voltage != RangeExpr.EMPTY + self.assign(self.reverse_voltage, self.sinks.hull(lambda x: x.reverse_voltage_out)) + self.assign(self.reverse_voltage_limits, self.source.reverse_voltage_limits) + # use implications to gate the reverse voltage requirements, since not all sources will support reverse voltage + self.require( + has_reverse_voltage.implies(self.reverse_voltage_limits != RangeExpr.EMPTY), + "reverse voltage source must have reverse voltage sink", + ) + self.require( + (~(self.sinks.map_extract(lambda x: x.reverse_voltage_out).elts_equals(RangeExpr.EMPTY))).count() <= 1, + "multiple reverse voltage sources not allowed", + ) + + self.require( + has_reverse_voltage.implies(self.reverse_voltage_limits.contains(self.reverse_voltage)), + "reverse voltage out of range", + ) + self.require( + has_reverse_voltage.implies(self.voltage_limits.contains(self.reverse_voltage)), + "reverse voltage out of range of voltage limits", + ) + self.require( + has_reverse_voltage.implies(self.reverse_voltage_limits.contains(self.voltage)), + "voltage out of range of reverse voltage limits", + ) + + self.assign(self.reverse_current_drawn, self.source.reverse_current_draw) + self.assign(self.reverse_current_limits, self.sinks.hull(lambda x: x.reverse_current_limits)) + self.require( + has_reverse_voltage.implies(self.reverse_current_limits.contains(self.reverse_current_drawn)), + "reverse current out of limits", + ) class VoltageSinkBridge(CircuitPortBridge): def __init__(self) -> None: super().__init__() - self.outer_port = self.Port(VoltageSink(current_draw=RangeExpr(), voltage_limits=RangeExpr())) + self.outer_port = self.Port( + VoltageSink( + current_draw=RangeExpr(), + voltage_limits=RangeExpr(), + reverse_voltage_out=RangeExpr(), + reverse_current_limits=RangeExpr(), + ) + ) # Here we ignore the current_limits of the inner port, instead relying on the main link to handle it # The outer port's voltage_limits is untouched and should be defined in the port def. # TODO: it's a slightly optimization to handle them here. Should it be done? # TODO: or maybe current_limits / voltage_limits shouldn't be a port, but rather a block property? - self.inner_link = self.Port(VoltageSource(current_limits=RangeExpr.ALL, voltage_out=RangeExpr())) + # However, reverse_voltage_limit is assigned explicitly since it determines reverse sink support + self.inner_link = self.Port( + VoltageSource( + current_limits=RangeExpr.ALL, + voltage_out=RangeExpr(), + reverse_voltage_limits=RangeExpr(), + reverse_current_draw=RangeExpr(), + ) + ) @override def contents(self) -> None: @@ -83,21 +135,39 @@ def contents(self) -> None: self.assign(self.outer_port.current_draw, self.inner_link.link().current_drawn) self.assign(self.outer_port.voltage_limits, self.inner_link.link().voltage_limits) + self.assign(self.outer_port.reverse_voltage_out, self.inner_link.link().reverse_voltage) + self.assign(self.outer_port.reverse_current_limits, self.inner_link.link().reverse_current_limits) self.assign(self.inner_link.voltage_out, self.outer_port.link().voltage) + self.assign(self.inner_link.reverse_voltage_limits, self.outer_port.link().reverse_voltage_limits) + self.assign(self.inner_link.reverse_current_draw, self.outer_port.link().reverse_current_drawn) class VoltageSourceBridge(CircuitPortBridge): # basic passthrough port, sources look the same inside and outside def __init__(self) -> None: super().__init__() - self.outer_port = self.Port(VoltageSource(voltage_out=RangeExpr(), current_limits=RangeExpr())) + self.outer_port = self.Port( + VoltageSource( + voltage_out=RangeExpr(), + current_limits=RangeExpr(), + reverse_voltage_limits=RangeExpr(), + reverse_current_draw=RangeExpr(), + ) + ) # Here we ignore the voltage_limits of the inner port, instead relying on the main link to handle it # The outer port's current_limits is untouched and should be defined in tte port def. # TODO: it's a slightly optimization to handle them here. Should it be done? # TODO: or maybe current_limits / voltage_limits shouldn't be a port, but rather a block property? - self.inner_link = self.Port(VoltageSink(voltage_limits=RangeExpr.ALL, current_draw=RangeExpr())) + self.inner_link = self.Port( + VoltageSink( + voltage_limits=RangeExpr.ALL, + current_draw=RangeExpr(), + reverse_voltage_out=RangeExpr(), + reverse_current_limits=RangeExpr.ALL, + ) + ) @override def contents(self) -> None: @@ -107,8 +177,11 @@ def contents(self) -> None: self.assign( self.outer_port.current_limits, self.inner_link.link().current_limits ) # TODO adjust for inner current drawn + self.assign(self.outer_port.reverse_voltage_limits, self.inner_link.link().reverse_voltage_limits) + self.assign(self.outer_port.reverse_current_draw, self.inner_link.link().reverse_current_drawn) self.assign(self.inner_link.current_draw, self.outer_port.link().current_drawn) + self.assign(self.inner_link.reverse_voltage_out, self.outer_port.link().reverse_voltage) class VoltageBase(CircuitPort[VoltageLink]): @@ -139,11 +212,20 @@ def from_gnd( ) -> "VoltageSink": return VoltageSink(voltage_limits=gnd.link().voltage + voltage_limits, current_draw=current_draw) - def __init__(self, voltage_limits: RangeLike = RangeExpr.ALL, current_draw: RangeLike = RangeExpr.ZERO) -> None: + def __init__( + self, + voltage_limits: RangeLike = RangeExpr.ALL, + current_draw: RangeLike = RangeExpr.ZERO, + reverse_voltage_out: RangeLike = RangeExpr.EMPTY, + reverse_current_limits: RangeLike = RangeExpr.EMPTY, + ) -> None: super().__init__() self.voltage_limits: RangeExpr = self.Parameter(RangeExpr(voltage_limits)) self.current_draw: RangeExpr = self.Parameter(RangeExpr(current_draw)) + self.reverse_voltage_out: RangeExpr = self.Parameter(RangeExpr(reverse_voltage_out)) + self.reverse_current_limits: RangeExpr = self.Parameter(RangeExpr(reverse_current_limits)) + class VoltageSinkAdapterGroundReference(CircuitPortAdapter["GroundReference"]): def __init__(self, current_draw: RangeLike): @@ -194,10 +276,19 @@ def __init__(self) -> None: class VoltageSource(VoltageBase): bridge_type = VoltageSourceBridge - def __init__(self, voltage_out: RangeLike = RangeExpr.ZERO, current_limits: RangeLike = RangeExpr.ALL) -> None: + def __init__( + self, + voltage_out: RangeLike = RangeExpr.ZERO, + current_limits: RangeLike = RangeExpr.ALL, + reverse_voltage_limits: RangeLike = RangeExpr.EMPTY, + reverse_current_draw: RangeLike = RangeExpr.EMPTY, + ) -> None: super().__init__() self.voltage_out: RangeExpr = self.Parameter(RangeExpr(voltage_out)) self.current_limits: RangeExpr = self.Parameter(RangeExpr(current_limits)) + self.reverse_voltage_limits: RangeExpr = self.Parameter(RangeExpr(reverse_voltage_limits)) + self.reverse_current_draw: RangeExpr = self.Parameter(RangeExpr(reverse_current_draw)) + Power = PortTag(VoltageSink) # General positive voltage port, should only be mutually exclusive with the below diff --git a/edg/electronics_model/__init__.py b/edg/electronics_model/__init__.py index acdb0fdaa..0a9da5ec5 100644 --- a/edg/electronics_model/__init__.py +++ b/edg/electronics_model/__init__.py @@ -40,6 +40,11 @@ from .KiCadImportableBlock import KiCadImportableBlock, KiCadInstantiableBlock from .KiCadSchematicBlock import KiCadSchematicBlock +# model-breaking constructs, including for unit testing +from .DummyDevice import DummyDevice +from .GroundDummyDevice import DummyGround +from .VoltageDummyDevice import DummyVoltageSource, DummyVoltageSink + # for power users to build custom blackbox handlers from .KiCadSchematicParser import KiCadSymbol, KiCadLibSymbol from .KiCadSchematicBlock import KiCadBlackbox, KiCadBlackboxBase diff --git a/edg/electronics_model/test_voltage_link.py b/edg/electronics_model/test_voltage_link.py index 1e8ca8c60..9b3d06153 100644 --- a/edg/electronics_model/test_voltage_link.py +++ b/edg/electronics_model/test_voltage_link.py @@ -3,12 +3,209 @@ from typing_extensions import override from .VoltagePorts import VoltageLink +from . import * + + +class VoltageTestTop(DesignTop): + """Test design with single voltage source and sink with valid ranges""" + + def __init__(self) -> None: + super().__init__() + self.src = self.Block(DummyVoltageSource(voltage_out=5 * Volt(tol=0), current_limits=(0, 1) * Amp)) + self.sink = self.Block(DummyVoltageSink(voltage_limit=5 * Volt(tol=0.1), current_draw=1 * Amp(tol=0))) + self.connect(self.src.pwr, self.sink.pwr) + + +class NoSourceVoltageTest(DesignTop): + """Test design with missing source""" + + def __init__(self) -> None: + super().__init__() + self.sink = self.Block(DummyVoltageSink(voltage_limit=5 * Volt(tol=0.1), current_draw=1 * Amp(tol=0))) + + +class OvervoltageTestTop(DesignTop): + """Test design with single source and single restrictive sink""" + + def __init__(self) -> None: + super().__init__() + self.src = self.Block(DummyVoltageSource(voltage_out=5 * Volt(tol=0), current_limits=(0, 1) * Amp)) + self.sink = self.Block(DummyVoltageSink(voltage_limit=3.3 * Volt(tol=0.1), current_draw=1 * Amp(tol=0))) + self.connect(self.src.pwr, self.sink.pwr) + + +class OvercurrentTestTop(DesignTop): + """Test design with single source and single restrictive sink""" + + def __init__(self) -> None: + super().__init__() + self.src = self.Block(DummyVoltageSource(voltage_out=5 * Volt(tol=0), current_limits=(0, 1) * Amp)) + self.sink1 = self.Block(DummyVoltageSink(voltage_limit=5 * Volt(tol=0.1), current_draw=1 * Amp(tol=0))) + self.sink2 = self.Block(DummyVoltageSink(voltage_limit=5 * Volt(tol=0.1), current_draw=1 * Amp(tol=0))) + self.connect(self.src.pwr, self.sink1.pwr, self.sink2.pwr) + + +class ReverseVoltageTestTop(DesignTop): + """Test design with valid forward and reverse voltage flows""" + + def __init__(self) -> None: + super().__init__() + self.src = self.Block( + DummyVoltageSource( + voltage_out=5 * Volt(tol=0), + current_limits=(0, 1) * Amp, + reverse_voltage_limits=5 * Volt(tol=0.1), + reverse_current_draw=0 * Amp(tol=0), + ) + ) + self.sink = self.Block( + DummyVoltageSink( + voltage_limit=5 * Volt(tol=0.1), + current_draw=0 * Amp(tol=0), + reverse_voltage_out=5 * Volt(tol=0), + reverse_current_limits=(0, 1) * Amp, + ) + ) + self.sink2 = self.Block( + DummyVoltageSink( + voltage_limit=5 * Volt(tol=0.1), + current_draw=0 * Amp(tol=0), + ) + ) + self.connect(self.src.pwr, self.sink.pwr, self.sink2.pwr) + self.require(self.src.reverse_voltage == 5 * Volt(tol=0)) + + +class ReverseMultiSourceTestTop(DesignTop): + """Test design with (invalid) multiple reverse voltage sources""" + + def __init__(self) -> None: + super().__init__() + self.src = self.Block( + DummyVoltageSource( + voltage_out=5 * Volt(tol=0), + current_limits=(0, 1) * Amp, + reverse_voltage_limits=5 * Volt(tol=0.1), + reverse_current_draw=0 * Amp(tol=0), + ) + ) + self.sink1 = self.Block( + DummyVoltageSink( + voltage_limit=5 * Volt(tol=0.1), + current_draw=0 * Amp(tol=0), + reverse_voltage_out=5 * Volt(tol=0), + reverse_current_limits=(0, 1) * Amp, + ) + ) + self.sink2 = self.Block( + DummyVoltageSink( + voltage_limit=5 * Volt(tol=0.1), + current_draw=0 * Amp(tol=0), + reverse_voltage_out=5 * Volt(tol=0), + reverse_current_limits=(0, 1) * Amp, + ) + ) + self.connect(self.src.pwr, self.sink1.pwr, self.sink2.pwr) + + +class ReverseNoSinkTest(DesignTop): + """Test design with reverse voltage source but no sink""" + + def __init__(self) -> None: + super().__init__() + self.src = self.Block(DummyVoltageSource(voltage_out=5 * Volt(tol=0), current_limits=(0, 1) * Amp)) + self.sink = self.Block( + DummyVoltageSink( + voltage_limit=5 * Volt(tol=0.1), + current_draw=0 * Amp(tol=0), + reverse_voltage_out=5 * Volt(tol=0), + reverse_current_limits=(0, 1) * Amp, + ) + ) + self.connect(self.src.pwr, self.sink.pwr) + + +class ReverseOvervoltageTestTop(DesignTop): + """Test design with reverse voltage incompatibility""" + + def __init__(self) -> None: + super().__init__() + self.src = self.Block( + DummyVoltageSource( + voltage_out=3.3 * Volt(tol=0), + current_limits=(0, 1) * Amp, + reverse_voltage_limits=3.3 * Volt(tol=0.1), + reverse_current_draw=0 * Amp(tol=0), + ) + ) + self.sink = self.Block( + DummyVoltageSink( + voltage_limit=(0, 14) * Volt, + current_draw=0 * Amp(tol=0), + reverse_voltage_out=5 * Volt(tol=0), + reverse_current_limits=(0, 1) * Amp, + ) + ) + self.connect(self.src.pwr, self.sink.pwr) + self.require(self.src.reverse_voltage != RangeExpr.EMPTY) + + +class ReverseForwardOvervoltageTestTop(DesignTop): + """Test design with reverse voltage incompatibility with forward limits""" + + def __init__(self) -> None: + super().__init__() + self.src = self.Block( + DummyVoltageSource( + voltage_out=5 * Volt(tol=0), + current_limits=(0, 1) * Amp, + reverse_voltage_limits=(0, 14) * Volt, + reverse_current_draw=0 * Amp(tol=0), + ) + ) + self.sink = self.Block( + DummyVoltageSink( + voltage_limit=5 * Volt(tol=0.1), + current_draw=0 * Amp(tol=0), + reverse_voltage_out=12 * Volt(tol=0), + reverse_current_limits=(0, 1) * Amp, + ) + ) + self.connect(self.src.pwr, self.sink.pwr) + self.require(self.src.reverse_voltage != RangeExpr.EMPTY) class VoltageLinkTestCase(unittest.TestCase): - @override - def setUp(self) -> None: - self.pb = VoltageLink()._elaborated_def_to_proto() + def test_voltage(self) -> None: + ScalaCompiler.compile(VoltageTestTop) + + def test_no_source(self) -> None: + with self.assertRaises(CompilerCheckError): + ScalaCompiler.compile(NoSourceVoltageTest) + + def test_overvoltage(self) -> None: + with self.assertRaises(CompilerCheckError): + ScalaCompiler.compile(OvervoltageTestTop) + + def test_overcurrent(self) -> None: + with self.assertRaises(CompilerCheckError): + ScalaCompiler.compile(OvercurrentTestTop) + + def test_reverse_voltage(self) -> None: + ScalaCompiler.compile(ReverseVoltageTestTop) + + def test_reverse_multi_source(self) -> None: + with self.assertRaises(CompilerCheckError): + ScalaCompiler.compile(ReverseMultiSourceTestTop) + + def test_reverse_no_sink(self) -> None: + with self.assertRaises(CompilerCheckError): + ScalaCompiler.compile(ReverseNoSinkTest) + + def test_reverse_overvoltage(self) -> None: + with self.assertRaises(CompilerCheckError): + ScalaCompiler.compile(ReverseOvervoltageTestTop) - def test_metadata(self) -> None: - self.assertIn("nets", self.pb.meta.members.node) + def test_reverse_forward_overvoltage(self) -> None: + with self.assertRaises(CompilerCheckError): + ScalaCompiler.compile(ReverseForwardOvervoltageTestTop) diff --git a/edg/hdl_server/__main__.py b/edg/hdl_server/__main__.py index ea6d3aa5d..ecfc4bef1 100644 --- a/edg/hdl_server/__main__.py +++ b/edg/hdl_server/__main__.py @@ -9,7 +9,7 @@ from ..core import * from ..core.Core import NonLibraryProperty -EDG_PROTO_VERSION = 8 +EDG_PROTO_VERSION = 9 class LibraryElementIndexer: diff --git a/edg/parts/BatteryCharger_Mcp73831.py b/edg/parts/BatteryCharger_Mcp73831.py index 8f97238c4..68269355d 100644 --- a/edg/parts/BatteryCharger_Mcp73831.py +++ b/edg/parts/BatteryCharger_Mcp73831.py @@ -22,9 +22,9 @@ def __init__(self, actual_charging_current: RangeLike) -> None: DigitalSource.from_supply(self.vss, self.vdd, current_limits=(-25, 35) * mAmp), optional=True ) self.vbat = self.Port( - VoltageSource( - voltage_out=(4.168, 4.232) * Volt, # -2 variant - current_limits=self.actual_charging_current.hull(0 * Amp(tol=0)), + VoltageSink( + reverse_voltage_out=(4.168, 4.232) * Volt, # -2 variant + reverse_current_limits=self.actual_charging_current.hull(0 * Amp(tol=0)), ) ) self.prog = self.Port(Passive()) diff --git a/edg/parts/Connectors.py b/edg/parts/Connectors.py index 2c239c6e2..a7ae6ea12 100644 --- a/edg/parts/Connectors.py +++ b/edg/parts/Connectors.py @@ -1,3 +1,4 @@ +import warnings from typing import Any from typing_extensions import override @@ -71,17 +72,28 @@ class LipoConnector(Connector, Battery): Connector type not specified, up to the user through a refinement.""" + def __getattr__(self, item: str) -> Any: + if item == "chg": + warnings.warn( + f"Use pwr instead. pwr is sink-capable (bidirectional) and chg is unnecessary.", + DeprecationWarning, + stacklevel=2, + ) + return self.pwr + else: + raise AttributeError( + item + ) # ideally we'd use super().__getattr__(...), but that's not defined in base classes + def __init__( self, voltage: RangeLike = (2.5, 4.2) * Volt, *args: Any, actual_voltage: RangeLike = (2.5, 4.2) * Volt, + charge_tolerance: RangeLike = (1.0, 1.01) * Ratio, **kwargs: Any, ) -> None: - from ..electronics_model.PassivePort import PassiveAdapterVoltageSink - super().__init__(voltage, *args, **kwargs) - self.chg = self.Port(VoltageSink.empty(), optional=True) # ideal port for charging self.conn = self.Block(PassiveConnector()) self.connect(self.gnd, self.conn.pins.request("1").adapt_to(Ground())) @@ -92,12 +104,11 @@ def __init__( VoltageSource( voltage_out=actual_voltage, # arbitrary from https://www.mouser.com/catalog/additional/Adafruit_3262.pdf current_limits=(0, 5.5) * Amp, # arbitrary assuming low capacity, 10 C discharge + reverse_voltage_limits=actual_voltage * RangeExpr._to_expr_type(charge_tolerance), + reverse_current_draw=(0, 0) * Amp, ) ), ) - self.chg_adapter = self.Block(PassiveAdapterVoltageSink()) - self.connect(pwr_pin, self.chg_adapter.src) - self.connect(self.chg, self.chg_adapter.dst) self.assign(self.actual_capacity, (500, 600) * mAmp) # arbitrary diff --git a/edg/parts/PowerConditioning.py b/edg/parts/PowerConditioning.py index 24b688707..47c76d3d4 100644 --- a/edg/parts/PowerConditioning.py +++ b/edg/parts/PowerConditioning.py @@ -1,4 +1,5 @@ -from typing import Optional, cast +import warnings +from typing import Optional, cast, Any from typing_extensions import override @@ -317,6 +318,7 @@ def connected_from( class PmosReverseProtection(PowerConditioner, KiCadSchematicBlock, Block): """Reverse polarity protection using a PMOS. This method has lower power loss over diode-based protection. 100R-330R is good but 1k-50k can be used for continuous load. + Not reverse / bidirectional current flow capable (eg, can't charge a battery through this). Ref: https://components101.com/articles/design-guide-pmos-mosfet-for-reverse-voltage-polarity-protection """ @@ -360,12 +362,35 @@ def contents(self) -> None: class PmosChargerReverseProtection(PowerConditioner, KiCadSchematicBlock, Block): - """Charging capable a battery reverse protection using PMOS transistors. The highest battery voltage is bounded by the - transistors' Vgs/Vds. There is also a rare case when this circuit being disconnected when a charger is connected first. + """PMOS-based reverse voltage protection, with charging (reverse / bidirectional flow) capability + using additional FETs. + + The highest battery voltage is bounded by the transistors' Vgs/Vds. + There is also a rare case when this circuit being disconnected when a charger is connected first. But always reverse protect. R1 and R2 are the pullup bias resistors for mp1 and mp2 PFet. More info at: https://www.edn.com/reverse-voltage-protection-for-battery-chargers/ """ + def __getattr__(self, item: str) -> Any: + if item == "chg_in": + warnings.warn( + f"Use pwr_out instead. pwr_out is sink-capable (bidirectional) and chg_in is unnecessary.", + DeprecationWarning, + stacklevel=2, + ) + return self.pwr_out + elif item == "chg_out": + warnings.warn( + f"Use pwr_in instead. pwr_in is source-capable (bidirectional) and chg_out is unnecessary.", + DeprecationWarning, + stacklevel=2, + ) + return self.pwr_in + else: + raise AttributeError( + item + ) # ideally we'd use super().__getattr__(...), but that's not defined in base classes + def __init__( self, r1_val: RangeLike = 100 * kOhm(tol=0.01), @@ -375,15 +400,12 @@ def __init__( super().__init__() self.gnd = self.Port(Ground.empty(), [Common]) + self.pwr_in = self.Port(VoltageSink.empty(), [Input], doc="Power input from the battery") + self.pwr_out = self.Port( - VoltageSource.empty(), doc="Power output for a load which will be also reverse protected from the battery" - ) - self.pwr_in = self.Port(VoltageSink.empty(), doc="Power input from the battery") - self.chg_in = self.Port( - VoltageSink.empty(), doc="Charger input to charge the battery. Must be connected to pwr_out." - ) - self.chg_out = self.Port( - VoltageSource.empty(), doc="Charging output to the battery chg port. Must be connected to pwr_in," + VoltageSource.empty(), + [Output], + doc="Power output for a load which will be also reverse protected from the battery", ) self.r1_val = self.ArgParameter(r1_val) @@ -396,10 +418,10 @@ def contents(self) -> None: self.r1 = self.Block(Resistor(resistance=self.r1_val)) self.r2 = self.Block(Resistor(resistance=self.r2_val)) - batt_voltage = self.pwr_in.link().voltage.hull(self.chg_in.link().voltage) + # use the maximum voltages and currents accounting for both directions + batt_voltage = self.pwr_in.link().voltage.hull(self.pwr_out.link().reverse_voltage) + batt_current = self.pwr_out.link().current_drawn.hull(self.pwr_in.link().reverse_current_drawn) - # taking the max of the current for the both direction. 0 lower bound - batt_current = self.pwr_out.link().current_drawn.hull(self.chg_out.link().current_drawn).hull((0, 0)) power = batt_current * batt_current * self.rds_on r1_current = batt_voltage / self.r1.resistance @@ -422,21 +444,19 @@ def contents(self) -> None: ) ) - chg_in_adapter = self.Block(PassiveAdapterVoltageSink()) - setattr(self, "(adapter)chg_in", chg_in_adapter) # hack so the netlister recognizes this as an adapter - self.connect(self.mp1.source, chg_in_adapter.src) - self.connect(self.chg_in, chg_in_adapter.dst) - - chg_out_adapter = self.Block(PassiveAdapterVoltageSource()) - setattr(self, "(adapter)chg_out", chg_out_adapter) # hack so the netlister recognizes this as an adapter - self.connect(self.r2.b, chg_out_adapter.src) - self.connect(self.chg_out, chg_out_adapter.dst) - self.import_kicad( self.file_path("resources", f"{self.__class__.__name__}.kicad_sch"), conversions={ - "pwr_in": VoltageSink(current_draw=batt_current), - "pwr_out": VoltageSource(voltage_out=batt_voltage), + "pwr_in": VoltageSink( + current_draw=batt_current, + reverse_voltage_out=self.pwr_out.link().reverse_voltage, + reverse_current_limits=RangeExpr.ALL, + ), + "pwr_out": VoltageSource( + voltage_out=batt_voltage, + reverse_voltage_limits=self.pwr_in.link().reverse_voltage_limits, + reverse_current_draw=self.pwr_in.link().reverse_current_drawn, + ), "gnd": Ground(), }, ) diff --git a/examples/test_pcbbot.py b/examples/test_pcbbot.py index 406b181c4..56fca98b3 100644 --- a/examples/test_pcbbot.py +++ b/examples/test_pcbbot.py @@ -71,7 +71,7 @@ def contents(self) -> None: ) self.v3v3 = self.connect(self.reg_3v3.pwr_out) - (self.charger,), _ = self.chain(self.vusb, imp.Block(Mcp73831(200 * mAmp(tol=0.2))), self.batt.chg) + (self.charger,), _ = self.chain(self.vusb, imp.Block(Mcp73831(200 * mAmp(tol=0.2))), self.batt.pwr) (self.charge_led,), _ = self.chain(self.Block(IndicatorSinkLed(Led.Yellow)), self.charger.stat) self.connect(self.vusb, self.charge_led.pwr) diff --git a/examples/test_protected_charger.py b/examples/test_protected_charger.py index 3d2123897..a85ee1990 100644 --- a/examples/test_protected_charger.py +++ b/examples/test_protected_charger.py @@ -30,10 +30,9 @@ def contents(self) -> None: ) as imp: self.tp = self.Block(VoltageTestPoint()).connected(self.batt.pwr) self.pmos = imp.Block(PmosChargerReverseProtection()) - - (self.charger,), _ = self.chain(self.vusb, imp.Block(Mcp73831(200 * mAmp(tol=0.2))), self.pmos.chg_in) self.connect(self.pmos.pwr_in, self.batt.pwr) - self.connect(self.pmos.chg_out, self.batt.chg) + + (self.charger,), _ = self.chain(self.vusb, imp.Block(Mcp73831(200 * mAmp(tol=0.2))), self.pmos.pwr_out) (self.charge_led,), _ = self.chain(self.Block(IndicatorSinkLed(Led.Yellow)), self.charger.stat) self.connect(self.vusb, self.charge_led.pwr) diff --git a/proto/edgir/expr.proto b/proto/edgir/expr.proto index 94080801c..80767e24a 100644 --- a/proto/edgir/expr.proto +++ b/proto/edgir/expr.proto @@ -283,6 +283,10 @@ message BinarySetExpr { // Concatenate : (lhs: String, rhss: Set[String]) -> Set[String] (prepend lhs to all elements) // : (lhss: Set[String], rhs: String) -> Set[String] (append rhs to all elements) CONCAT = 20; + + // Equality operator + // (lhss: Set[A], elt: A) -> Set[Bool] (pointwise equality of all elements in the set to the element) + EQ = 30; } Op op = 1; ValueExpr lhset = 2;