Skip to content

Commit 2fa08c8

Browse files
authored
Digital single-source refactor and abstract comparator (#388)
Merges DigitalSingleSource into DigitalSource, plus a bunch of associated changes. Infrastructure to support an AbstractComparator class, which can have open-drain and push-pull outputs. Resolves #387 Core compiler changes: - Sum(BoolArray) does a counting operation. Add `Vector.count(elt => BoolExpr)` and `ArrayExpr.count()` in the frontend. - More robust NaN (RangeEmpty) propagation to reduce compiler crashes Model and part changes: - DigitalSource now supports separate high / low (open-drain) / push-pull / no driver (pullup / down) ports. Some of the reasons why DigitalSingleSource was necessary before are obsoleted by introducing the concept of bridged internal ports, which have some checks disabled (here, presence of a signal driver) which could resolved by the upper-level link. - Support DigitalSink with internal pullup / pulldown (as is common on reset pins) - Improve I2C bridge parameter propagation when pullup is not connected (eg, in an inner link) - Change SWD and ESP32 reset ports to use DigitalSource pulldown - kind of a hack, but a bit cleaner than before - Change chip reset modeling to use DigitalSink with pullup - Generate netlists for blinky examples
1 parent 437c4b4 commit 2fa08c8

File tree

71 files changed

+8204
-708
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+8204
-708
lines changed

.github/workflows/pr-scala.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ jobs:
3636
- uses: actions/setup-python@v1
3737
with:
3838
python-version: '3.10'
39+
- name: Setup sbt launcher
40+
uses: sbt/setup-sbt@v1
3941
- name: install dependencies
4042
run: pip install -r requirements.txt
4143
- name: sbt test

compiler/src/main/scala/edg/compiler/ExprEvaluate.scala

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,16 @@ object ExprEvaluate {
188188
}
189189

190190
case Op.RANGE => (lhs, rhs) match {
191-
case (FloatPromotable(lhs), FloatPromotable(rhs)) => RangeValue(math.min(lhs, rhs), math.max(lhs, rhs))
191+
case (FloatPromotable(lhs), FloatPromotable(rhs)) =>
192+
if (lhs.isNaN && rhs.isNaN) { // here, NaN is treated as empty and dispreferred (instead of NaN prop)
193+
RangeEmpty
194+
} else if (lhs.isNaN) {
195+
RangeValue(rhs, rhs)
196+
} else if (rhs.isNaN) {
197+
RangeValue(lhs, lhs)
198+
} else {
199+
RangeValue(math.min(lhs, rhs), math.max(lhs, rhs))
200+
}
192201
case _ =>
193202
throw new ExprEvaluateException(s"Unknown binary operands types in $lhs ${binary.op} $rhs from $binary")
194203
}
@@ -259,6 +268,12 @@ object ExprEvaluate {
259268

260269
case (Op.MIN, RangeValue(valMin, _)) => FloatValue(valMin)
261270
case (Op.MAX, RangeValue(_, valMax)) => FloatValue(valMax)
271+
272+
// TODO can we have stricter semantics to avoid min(RangeEmpty) and max(RangeEmpty)?
273+
// This just NaNs out so at least it propagates
274+
case (Op.MAX, RangeEmpty) => FloatValue(Float.NaN)
275+
case (Op.MIN, RangeEmpty) => FloatValue(Float.NaN)
276+
262277
case (Op.CENTER, RangeValue(valMin, valMax)) => FloatValue((valMin + valMax) / 2)
263278
case (Op.WIDTH, RangeValue(valMin, valMax)) => FloatValue(math.abs(valMax - valMin))
264279

@@ -273,6 +288,7 @@ object ExprEvaluate {
273288
case (Op.SUM, ArrayValue.Empty(_)) => FloatValue(0) // TODO type needs to be dynamic
274289
case (Op.SUM, ArrayValue.ExtractFloat(vals)) => FloatValue(vals.sum)
275290
case (Op.SUM, ArrayValue.ExtractInt(vals)) => IntValue(vals.sum)
291+
case (Op.SUM, ArrayValue.ExtractBoolean(vals)) => IntValue(vals.count(_ == true))
276292
case (Op.SUM, ArrayValue.UnpackRange(extracted)) => extracted match {
277293
case ArrayValue.UnpackRange.FullRange(valMins, valMaxs) => RangeValue(valMins.sum, valMaxs.sum)
278294
case _ => RangeEmpty // TODO how should sum behave on empty ranges?
@@ -294,15 +310,6 @@ object ExprEvaluate {
294310
case (Op.MINIMUM, ArrayValue.ExtractFloat(vals)) => FloatValue(vals.min)
295311
case (Op.MINIMUM, ArrayValue.ExtractInt(vals)) => IntValue(vals.min)
296312

297-
// TODO this is definitely a hack in the absence of a proper range extractor
298-
case (Op.MAXIMUM, RangeValue(lower, upper)) => FloatValue(upper)
299-
case (Op.MINIMUM, RangeValue(lower, upper)) => FloatValue(lower)
300-
301-
// TODO can we have stricter semantics to avoid min(RangeEmpty) and max(RangeEmpty)?
302-
// This just NaNs out so at least it propagates
303-
case (Op.MAXIMUM, RangeEmpty) => FloatValue(Float.NaN)
304-
case (Op.MINIMUM, RangeEmpty) => FloatValue(Float.NaN)
305-
306313
// TODO this should be a user-level assertion instead of a compiler error
307314
case (Op.SET_EXTRACT, ArrayValue.Empty(_)) =>
308315
throw new ExprEvaluateException(s"SetExtract with empty values from $unarySet")

developing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ python -m unittest discover
3838

3939
Or, to run tests for a specific package (eg, `edg_core` in this command):
4040
```
41-
python -m unittest discover -s edg_core -t .
41+
python -m unittest discover -s edg.core -t .
4242
```
4343

4444
Or, to run one specific test:

edg/BoardTop.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ def refinements(self) -> Refinements:
9595
(Fpc050Top, Afc07Top),
9696
(Fpc030Bottom, HiroseFh35cshw),
9797
(UsbEsdDiode, Pesd5v0x1bt),
98+
(Comparator, Lmv331),
9899
(Opamp, Lmv321),
99100
(SpiMemory, W25q), # 128M version is a basic part
100101
(TestPoint, Keystone5015), # this is larger, but is part of JLC's parts inventory
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from typing import Mapping
2+
3+
from ..electronics_model import *
4+
5+
6+
class Comparator(KiCadInstantiableBlock, Block):
7+
"""Abstract comparator interface, output goes high when inp > inn."""
8+
def symbol_pinning(self, symbol_name: str) -> Mapping[str, BasePort]:
9+
assert symbol_name in ('Simulation_SPICE:OPAMP', 'edg_importable:Opamp')
10+
return {'+': self.inp, '-': self.inn, '3': self.out, 'V+': self.pwr, 'V-': self.gnd}
11+
12+
@classmethod
13+
def block_from_symbol(cls, symbol_name: str, properties: Mapping[str, str]) -> 'Comparator':
14+
return Comparator()
15+
16+
@init_in_parent
17+
def __init__(self) -> None:
18+
super().__init__()
19+
20+
self.pwr = self.Port(VoltageSink.empty(), [Power])
21+
self.gnd = self.Port(Ground.empty(), [Common])
22+
self.inn = self.Port(AnalogSink.empty())
23+
self.inp = self.Port(AnalogSink.empty())
24+
self.out = self.Port(DigitalSource.empty())

edg/abstract_parts/AbstractDebugHeaders.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class SwdCortexTargetConnectorReset(BlockInterfaceMixin[SwdCortexTargetConnector
1717
"""Mixin for SWD connectors with adding the optional reset pin"""
1818
def __init__(self, *args, **kwargs) -> None:
1919
super().__init__(*args, **kwargs)
20-
self.reset = self.Port(DigitalBidir.empty(), optional=True) # can tri-state when not asserted
20+
self.reset = self.Port(DigitalSource.empty(), optional=True) # as open-drain
2121

2222

2323
class SwdCortexTargetConnectorSwo(BlockInterfaceMixin[SwdCortexTargetConnector]):

edg/abstract_parts/AbstractResistor.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def __init__(self, resistance: RangeLike) -> None:
131131

132132
self.pwr = self.Export(self.res.a.adapt_to(VoltageSink()), [Power])
133133
self.io = self.Export(self.res.b.adapt_to(
134-
DigitalSingleSource.high_from_supply(self.pwr, is_pullup=True)
134+
DigitalSource.pullup_from_supply(self.pwr)
135135
), [InOut])
136136

137137
def connected(self, pwr: Optional[Port[VoltageLink]] = None, io: Optional[Port[DigitalLink]] = None) -> \
@@ -154,7 +154,7 @@ def __init__(self, resistance: RangeLike) -> None:
154154

155155
self.gnd = self.Export(self.res.a.adapt_to(Ground()), [Common])
156156
self.io = self.Export(self.res.b.adapt_to(
157-
DigitalSingleSource.low_from_supply(self.gnd, is_pulldown=True)
157+
DigitalSource.pulldown_from_supply(self.gnd)
158158
), [InOut])
159159

160160
def connected(self, gnd: Optional[Port[VoltageLink]] = None, io: Optional[Port[DigitalLink]] = None) -> \
@@ -173,7 +173,7 @@ class PullupResistorArray(TypedTestPoint, GeneratorBlock):
173173
def __init__(self, resistance: RangeLike):
174174
super().__init__()
175175
self.pwr = self.Port(VoltageSink.empty(), [Power])
176-
self.io = self.Port(Vector(DigitalSingleSource.empty()), [InOut])
176+
self.io = self.Port(Vector(DigitalSource.empty()), [InOut])
177177
self.generator_param(self.io.requested())
178178
self.resistance = self.ArgParameter(resistance)
179179

@@ -183,7 +183,7 @@ def generate(self):
183183
for requested in self.get(self.io.requested()):
184184
res = self.res[requested] = self.Block(PullupResistor(self.resistance))
185185
self.connect(self.pwr, res.pwr)
186-
self.connect(self.io.append_elt(DigitalSingleSource.empty(), requested), res.io)
186+
self.connect(self.io.append_elt(DigitalSource.empty(), requested), res.io)
187187

188188

189189
class PulldownResistorArray(TypedTestPoint, GeneratorBlock):
@@ -192,7 +192,7 @@ class PulldownResistorArray(TypedTestPoint, GeneratorBlock):
192192
def __init__(self, resistance: RangeLike):
193193
super().__init__()
194194
self.gnd = self.Port(Ground.empty(), [Common])
195-
self.io = self.Port(Vector(DigitalSingleSource.empty()), [InOut])
195+
self.io = self.Port(Vector(DigitalSource.empty()), [InOut])
196196
self.generator_param(self.io.requested())
197197
self.resistance = self.ArgParameter(resistance)
198198

@@ -202,7 +202,7 @@ def generate(self):
202202
for requested in self.get(self.io.requested()):
203203
res = self.res[requested] = self.Block(PulldownResistor(self.resistance))
204204
self.connect(self.gnd, res.gnd)
205-
self.connect(self.io.append_elt(DigitalSingleSource.empty(), requested), res.io)
205+
self.connect(self.io.append_elt(DigitalSource.empty(), requested), res.io)
206206

207207

208208
class SeriesPowerResistor(DiscreteApplication):

edg/abstract_parts/AbstractSwitch.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,14 @@ def __init__(self) -> None:
8989
super().__init__()
9090

9191
self.gnd = self.Port(Ground.empty(), [Common])
92-
self.out = self.Port(DigitalSingleSource.empty(), [Output])
92+
self.out = self.Port(DigitalSource.empty(), [Output])
9393

9494
def contents(self):
9595
super().contents()
9696
self.package = self.Block(Switch(current=self.out.link().current_drawn,
9797
voltage=self.out.link().voltage))
9898

99-
self.connect(self.out, self.package.sw.adapt_to(DigitalSingleSource.low_from_supply(self.gnd)))
99+
self.connect(self.out, self.package.sw.adapt_to(DigitalSource.low_from_supply(self.gnd)))
100100
self.connect(self.gnd, self.package.com.adapt_to(Ground()))
101101

102102

@@ -107,8 +107,8 @@ def __init__(self) -> None:
107107
super().__init__()
108108

109109
self.gnd = self.Port(Ground.empty(), [Common])
110-
self.a = self.Port(DigitalSingleSource.empty())
111-
self.b = self.Port(DigitalSingleSource.empty())
110+
self.a = self.Port(DigitalSource.empty())
111+
self.b = self.Port(DigitalSource.empty())
112112

113113

114114
class DigitalWrapperRotaryEncoder(DigitalRotaryEncoder):
@@ -118,7 +118,7 @@ def contents(self):
118118
self.package = self.Block(RotaryEncoder(current=self.a.link().current_drawn.hull(self.b.link().current_drawn),
119119
voltage=self.a.link().voltage.hull(self.b.link().voltage)))
120120

121-
dio_model = DigitalSingleSource.low_from_supply(self.gnd)
121+
dio_model = DigitalSource.low_from_supply(self.gnd)
122122
self.connect(self.a, self.package.a.adapt_to(dio_model))
123123
self.connect(self.b, self.package.b.adapt_to(dio_model))
124124
self.connect(self.gnd, self.package.com.adapt_to(Ground()))
@@ -130,7 +130,7 @@ class DigitalRotaryEncoderSwitch(BlockInterfaceMixin[DigitalRotaryEncoder]):
130130
def __init__(self, *args, **kwargs) -> None:
131131
super().__init__(*args, **kwargs)
132132

133-
self.sw = self.Port(DigitalSingleSource.empty(), optional=True)
133+
self.sw = self.Port(DigitalSource.empty(), optional=True)
134134

135135

136136
class DigitalWrapperRotaryEncoderWithSwitch(DigitalRotaryEncoderSwitch, DigitalWrapperRotaryEncoder, GeneratorBlock):
@@ -142,7 +142,7 @@ def generate(self):
142142
super().generate()
143143
if self.get(self.sw.is_connected()):
144144
package_sw = self.package.with_mixin(RotaryEncoderSwitch())
145-
dio_model = DigitalSingleSource.low_from_supply(self.gnd)
145+
dio_model = DigitalSource.low_from_supply(self.gnd)
146146
self.connect(self.sw, package_sw.sw.adapt_to(dio_model))
147147

148148

@@ -153,10 +153,10 @@ def __init__(self) -> None:
153153
super().__init__()
154154

155155
self.gnd = self.Port(Ground.empty(), [Common])
156-
self.a = self.Port(DigitalSingleSource.empty())
157-
self.b = self.Port(DigitalSingleSource.empty())
158-
self.c = self.Port(DigitalSingleSource.empty())
159-
self.d = self.Port(DigitalSingleSource.empty())
156+
self.a = self.Port(DigitalSource.empty())
157+
self.b = self.Port(DigitalSource.empty())
158+
self.c = self.Port(DigitalSource.empty())
159+
self.d = self.Port(DigitalSource.empty())
160160

161161

162162
class DigitalWrapperDirectionSwitch(DigitalDirectionSwitch):
@@ -166,7 +166,7 @@ def contents(self):
166166
self.package = self.Block(DirectionSwitch(current=self.a.link().current_drawn.hull(self.b.link().current_drawn),
167167
voltage=self.a.link().voltage.hull(self.b.link().voltage)))
168168

169-
dio_model = DigitalSingleSource.low_from_supply(self.gnd)
169+
dio_model = DigitalSource.low_from_supply(self.gnd)
170170
self.connect(self.a, self.package.a.adapt_to(dio_model))
171171
self.connect(self.b, self.package.b.adapt_to(dio_model))
172172
self.connect(self.c, self.package.c.adapt_to(dio_model))
@@ -180,7 +180,7 @@ class DigitalDirectionSwitchCenter(BlockInterfaceMixin[DigitalDirectionSwitch]):
180180
def __init__(self, *args, **kwargs) -> None:
181181
super().__init__(*args, **kwargs)
182182

183-
self.center = self.Port(DigitalSingleSource.empty(), optional=True)
183+
self.center = self.Port(DigitalSource.empty(), optional=True)
184184

185185

186186
class DigitalWrapperDirectionSwitchWithCenter(DigitalDirectionSwitchCenter, DigitalWrapperDirectionSwitch,
@@ -193,5 +193,5 @@ def generate(self):
193193
super().generate()
194194
if self.get(self.center.is_connected()):
195195
package_sw = self.package.with_mixin(DirectionSwitchCenter())
196-
dio_model = DigitalSingleSource.low_from_supply(self.gnd)
196+
dio_model = DigitalSource.low_from_supply(self.gnd)
197197
self.connect(self.center, package_sw.center.adapt_to(dio_model))

edg/abstract_parts/DigitalAmplifiers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def __init__(self, max_rds: FloatLike = 1*Ohm, frequency: RangeLike = RangeExpr.
115115

116116
self.gnd = self.Port(Ground.empty(), [Common])
117117
self.control = self.Port(DigitalSink.empty(), [Input])
118-
self.output = self.Port(DigitalSingleSource.empty(), [Output])
118+
self.output = self.Port(DigitalSource.empty(), [Output])
119119

120120
self.max_rds = self.ArgParameter(max_rds)
121121
self.frequency = self.ArgParameter(frequency)
@@ -131,7 +131,7 @@ def contents(self):
131131
frequency=self.frequency,
132132
drive_current=self.control.link().current_limits
133133
))
134-
self.connect(self.drv.drain.adapt_to(DigitalSingleSource.low_from_supply(self.gnd
134+
self.connect(self.drv.drain.adapt_to(DigitalSource.low_from_supply(self.gnd
135135
)), self.output)
136136
self.connect(self.drv.source.adapt_to(Ground()), self.gnd)
137137
self.connect(self.drv.gate.adapt_to(DigitalSink()),

edg/abstract_parts/PassiveFilters.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def __init__(self, impedance: RangeLike, time_constant: RangeLike):
4848
voltage=self.pwr.link().voltage))
4949

5050
self.connect(self.pwr, self.rc.input.adapt_to(VoltageSink()))
51-
self.io = self.Export(self.rc.output.adapt_to(DigitalSingleSource.high_from_supply(self.pwr)), [Output])
51+
self.io = self.Export(self.rc.output.adapt_to(DigitalSource.pullup_from_supply(self.pwr)), [Output])
5252
self.gnd = self.Export(self.rc.gnd.adapt_to(Ground()), [Common])
5353

5454
def connected(self, *, gnd: Optional[Port[VoltageLink]] = None, pwr: Optional[Port[VoltageLink]] = None,

0 commit comments

Comments
 (0)