Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into pmos
Browse files Browse the repository at this point in the history
# Conflicts:
#	electronics_lib/__init__.py
  • Loading branch information
Suke0811 committed Apr 23, 2024
2 parents 4a47930 + da7a2ea commit f4a2f8e
Show file tree
Hide file tree
Showing 150 changed files with 161,692 additions and 73,028 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/pr-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
# mypy: https://doedotdev.medium.com/mypy-for-github-action-7da1ebee99e7
# optimization: https://github.com/marketplace/actions/skip-duplicate-actions#usage-examples

name: Python PR build and tests
name: HDL and example tests

on:
push:
branches:
- "master"
pull_request:
types: [assigned, opened, synchronize, reopened, ready_for_review]

Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/pr-scala.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# Based on https://www.scala-sbt.org/1.x/docs/GitHub-Actions-with-sbt.html
# optimization: https://github.com/marketplace/actions/skip-duplicate-actions#usage-examples

name: Scala PR build and tests
name: Compiler tests

on:
push:
branches:
- "master"
pull_request:
types: [assigned, opened, synchronize, reopened, ready_for_review]

Expand Down
232 changes: 155 additions & 77 deletions README.md

Large diffs are not rendered by default.

Binary file added docs/boards/candapter.webp
Binary file not shown.
Binary file added docs/boards/iotfandriver.webp
Binary file not shown.
Binary file added docs/boards/ledmatrix.webp
Binary file not shown.
Binary file added docs/boards/multimeter.webp
Binary file not shown.
Binary file added docs/boards/simon.webp
Binary file not shown.
Binary file added docs/boards/usb_smu.webp
Binary file not shown.
Binary file added docs/keyboard.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions edg/BoardCompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ def compile_board(design: Type[Block], target_dir_name: Optional[Tuple[str, str]

design_filename = os.path.join(target_dir, f'{target_name}.edg')
netlist_filename = os.path.join(target_dir, f'{target_name}.net')
netlist_refdes_filename = os.path.join(target_dir, f'{target_name}.ref.net')
bom_filename = os.path.join(target_dir, f'{target_name}.csv')

with suppress(FileNotFoundError):
os.remove(design_filename)
with suppress(FileNotFoundError):
os.remove(netlist_filename)
with suppress(FileNotFoundError):
os.remove(netlist_refdes_filename)
with suppress(FileNotFoundError):
os.remove(bom_filename)

Expand All @@ -39,13 +42,17 @@ def compile_board(design: Type[Block], target_dir_name: Optional[Tuple[str, str]
raise edg_core.ScalaCompilerInterface.CompilerCheckError(f"error during compilation: \n{compiled.error}")

netlist_all = NetlistBackend().run(compiled)
netlist_refdes = NetlistBackend().run(compiled, {'RefdesMode': 'refdes'})
bom_all = GenerateBom().run(compiled)
assert len(netlist_all) == 1

if target_dir_name is not None:
with open(netlist_filename, 'w', encoding='utf-8') as net_file:
net_file.write(netlist_all[0][1])

with open(netlist_refdes_filename, 'w', encoding='utf-8') as net_file:
net_file.write(netlist_refdes[0][1])

with open(bom_filename, 'w', encoding='utf-8') as bom_file:
bom_file.write(bom_all[0][1])

Expand Down
1 change: 1 addition & 0 deletions edg/BoardTop.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def refinements(self) -> Refinements:
(Opamp, Lmv321),
(SpiMemory, W25q), # 128M version is a basic part
(TestPoint, Keystone5015), # this is larger, but is part of JLC's parts inventory
(UflConnector, Bwipx_1_001e),
],
class_values=[ # realistically only RCs are going to likely be basic parts
(JlcResistor, ['require_basic_part'], True),
Expand Down
12 changes: 12 additions & 0 deletions edg_core/ConstraintExpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,15 @@ def _from_lit(cls, pb: edgir.ValueLit) -> Range:
assert pb.HasField('range') and pb.range.minimum.HasField('floating') and pb.range.maximum.HasField('floating')
return Range(pb.range.minimum.floating.val, pb.range.maximum.floating.val)

@classmethod
def cancel_multiply(cls, input_side: RangeLike, output_side: RangeLike) -> RangeExpr:
"""See Range.cancel_multiply"""
input_expr = cls._to_expr_type(input_side)
output_expr = cls._to_expr_type(output_side)
lower = input_expr.upper() * output_expr.lower()
upper = input_expr.lower() * output_expr.upper()
return cls._to_expr_type((lower, upper)) # rely on internally to check for non-empty range

def __init__(self, initializer: Optional[RangeLike] = None) -> None:
# must cast non-empty initializer type, because range supports wider initializers
# TODO and must ignore initializers of self-type (because model weirdness) - remove model support!
Expand Down Expand Up @@ -396,6 +405,9 @@ def lower(self) -> FloatExpr:
def upper(self) -> FloatExpr:
return self._upper

def center(self) -> FloatExpr:
return (self._lower + self._upper) / 2

@classmethod
def _create_range_float_binary_op(cls,
lhs: RangeExpr,
Expand Down
10 changes: 7 additions & 3 deletions edg_core/ScalaCompilerInterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,13 @@ def __init__(self, design: edgir.Design, values: Dict[bytes, edgir.LitTypes], er

# Reserved.V is a string because it doesn't load properly at runtime
# Serialized strings are used since proto objects are mutable and unhashable
def get_value(self, path: Iterable[Union[str, 'edgir.Reserved.V']]) -> Optional[edgir.LitTypes]:
path_key = edgir.LocalPathList(path).SerializeToString()
return self._values.get(path_key, None)
def get_value(self, path: Union[edgir.LocalPath, Iterable[Union[str, 'edgir.Reserved.V']]]) ->\
Optional[edgir.LitTypes]:
if isinstance(path, edgir.LocalPath):
localpath = path
else:
localpath = edgir.LocalPathList(path)
return self._values.get(localpath.SerializeToString(), None)

def append_values(self, values: List[Tuple[edgir.LocalPath, edgir.ValueLit]]):
"""Append solved values to this design, such as from a refinement pass"""
Expand Down
44 changes: 33 additions & 11 deletions edg_core/TransformUtil.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,39 @@ def __repr__(self) -> str:
def empty(cls) -> Path:
return Path((), (), (), ())

def append_block(self, name: str) -> Path:
assert not self.links and not self.ports and not self.params, f"tried to append block {name} to {self}"
return Path(self.blocks + (name, ), self.links, self.ports, self.params)

def append_link(self, name: str) -> Path:
assert not self.ports and not self.params, f"tried to append link {name} to {self}"
return Path(self.blocks, self.links + (name, ), self.ports, self.params)

def append_port(self, name: str) -> Path:
assert not self.params, f"tried to append port {name} to {self}"
return Path(self.blocks, self.links, self.ports + (name, ), self.params)
def startswith(self, prefix: Path) -> bool:
if self.blocks == prefix.blocks: # exact match, check subpaths
if self.links == prefix.links:
if self.ports == prefix.ports:
return len(self.params) >= len(prefix.params) and self.params[:len(prefix.params)] == prefix.params
elif len(self.ports) >= len(prefix.ports) and self.ports[:len(prefix.ports)] == prefix.ports:
return (not self.params) and (not prefix.params)
else:
return False

elif len(self.links) >= len(prefix.links) and self.links[:len(prefix.links)] == prefix.links:
return (not self.ports) and (not prefix.ports) and (not self.params) and (not prefix.params)
else:
return False

elif len(self.blocks) >= len(prefix.blocks) and self.blocks[:len(prefix.blocks)] == prefix.blocks:
# partial match, check subpaths don't exist
return (not self.links) and (not prefix.links) and (not self.ports) and (not prefix.ports) and \
(not self.params) and (not prefix.params)
else: # no match
return False

def append_block(self, *names: str) -> Path:
assert not self.links and not self.ports and not self.params, f"tried to append block {names} to {self}"
return Path(self.blocks + tuple(names), self.links, self.ports, self.params)

def append_link(self, *names: str) -> Path:
assert not self.ports and not self.params, f"tried to append link {names} to {self}"
return Path(self.blocks, self.links + tuple(names), self.ports, self.params)

def append_port(self, *names: str) -> Path:
assert not self.params, f"tried to append port {names} to {self}"
return Path(self.blocks, self.links, self.ports + tuple(names), self.params)

def append_param(self, name: str) -> Path:
return Path(self.blocks, self.links, self.ports, self.params + (name, ))
Expand Down
21 changes: 21 additions & 0 deletions edg_core/test_path.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import unittest

from .TransformUtil import Path


class PathTestCase(unittest.TestCase):
def test_startswith(self) -> None:
path = Path.empty()
self.assertTrue(path.append_block('a', 'b').startswith(path.append_block('a')))
self.assertFalse(path.append_block('a').startswith(path.append_block('a', 'b')))
self.assertTrue(path.append_block('a').startswith(path.append_block('a')))
self.assertTrue(path.append_block('a', 'b').startswith(path.append_block('a', 'b')))

self.assertFalse(path.append_block('a').startswith(path.append_link('a')))

self.assertTrue(path.append_block('a').append_link('b').startswith(path.append_block('a')))
self.assertTrue(path.append_block('a').append_link('b').startswith(path.append_block('a').append_link('b')))
self.assertTrue(path.append_block('a').append_link('b', 'c').startswith(path.append_block('a').append_link('b')))
self.assertTrue(path.append_block('a').append_link('b', 'c').startswith(path.append_block('a').append_link('b', 'c')))
self.assertFalse(path.append_block('a').append_link('b').startswith(path.append_link('b')))
self.assertFalse(path.append_block('a').append_link('b').startswith(path.append_block('a', 'b')))
104 changes: 90 additions & 14 deletions electronics_abstract_parts/AbstractCapacitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,19 @@ def __init__(self, *args, **kwargs) -> None:
self.neg = self.Port(Passive.empty())


@abstract_block
class CeramicCapacitor(Capacitor):
"""Abstract base class for ceramic capacitors, which appear more ideal in terms of lower ESP"""
pass


@abstract_block
class AluminumCapacitor(Capacitor):
"""Abstract base class for aluminum electrolytic capacitors capacitors which provide compact bulk capacitance
but at the cost of ESR"""
pass


@non_library
class CapacitorStandardFootprint(Capacitor, StandardFootprint[Capacitor]):
REFDES_PREFIX = 'C'
Expand All @@ -99,6 +112,56 @@ class CapacitorStandardFootprint(Capacitor, StandardFootprint[Capacitor]):
'Capacitor_SMD:C_1210_3225Metric',
'Capacitor_SMD:C_1812_4532Metric',
'Capacitor_SMD:C_2512_6332Metric',

'Capacitor_SMD:CP_Elec_3x5.3',
'Capacitor_SMD:CP_Elec_3x5.4',
'Capacitor_SMD:CP_Elec_4x3',
'Capacitor_SMD:CP_Elec_4x3.9',
'Capacitor_SMD:CP_Elec_4x4.5',
'Capacitor_SMD:CP_Elec_4x5.3',
'Capacitor_SMD:CP_Elec_4x5.4',
'Capacitor_SMD:CP_Elec_4x5.7',
'Capacitor_SMD:CP_Elec_4x5.8',
'Capacitor_SMD:CP_Elec_5x3',
'Capacitor_SMD:CP_Elec_5x3.9',
'Capacitor_SMD:CP_Elec_5x4.4',
'Capacitor_SMD:CP_Elec_5x4.5',
'Capacitor_SMD:CP_Elec_5x5.3',
'Capacitor_SMD:CP_Elec_5x5.4',
'Capacitor_SMD:CP_Elec_5x5.7',
'Capacitor_SMD:CP_Elec_5x5.8',
'Capacitor_SMD:CP_Elec_5x5.9',
'Capacitor_SMD:CP_Elec_6.3x3',
'Capacitor_SMD:CP_Elec_6.3x3.9',
'Capacitor_SMD:CP_Elec_6.3x4.5',
'Capacitor_SMD:CP_Elec_6.3x4.9',
'Capacitor_SMD:CP_Elec_6.3x5.2',
'Capacitor_SMD:CP_Elec_6.3x5.3',
'Capacitor_SMD:CP_Elec_6.3x5.4',
'Capacitor_SMD:CP_Elec_6.3x5.7',
'Capacitor_SMD:CP_Elec_6.3x5.8',
'Capacitor_SMD:CP_Elec_6.3x5.9',
'Capacitor_SMD:CP_Elec_6.3x7.7',
'Capacitor_SMD:CP_Elec_6.3x9.9',
'Capacitor_SMD:CP_Elec_8x5.4',
'Capacitor_SMD:CP_Elec_8x6.2',
'Capacitor_SMD:CP_Elec_8x6.5',
'Capacitor_SMD:CP_Elec_8x6.7',
'Capacitor_SMD:CP_Elec_8x6.9',
'Capacitor_SMD:CP_Elec_8x10',
'Capacitor_SMD:CP_Elec_8x10.5',
'Capacitor_SMD:CP_Elec_8x11.9',
'Capacitor_SMD:CP_Elec_10x7.7',
'Capacitor_SMD:CP_Elec_10x7.9',
'Capacitor_SMD:CP_Elec_10x10',
'Capacitor_SMD:CP_Elec_10x10.5',
'Capacitor_SMD:CP_Elec_10x12.5',
'Capacitor_SMD:CP_Elec_10x12.6',
'Capacitor_SMD:CP_Elec_10x14.3',
'Capacitor_SMD:CP_Elec_16x17.5',
'Capacitor_SMD:CP_Elec_16x22',
'Capacitor_SMD:CP_Elec_18x7.5',
'Capacitor_SMD:CP_Elec_18x22',
): lambda block: {
'1': block.pos,
'2': block.neg,
Expand All @@ -121,16 +184,34 @@ class CapacitorStandardFootprint(Capacitor, StandardFootprint[Capacitor]):


@non_library
class TableCapacitor(Capacitor):
"""Abstract table-based capacitor, providing some interface column definitions.
DO NOT USE DIRECTLY - this provides no selection logic implementation."""
class TableCapacitor(CapacitorStandardFootprint, PartsTableFootprintSelector):
"""Abstract table-based capacitor, providing some interface column definitions."""
CAPACITANCE = PartsTableColumn(Range)
NOMINAL_CAPACITANCE = PartsTableColumn(float) # nominal capacitance, even with asymmetrical tolerances
VOLTAGE_RATING = PartsTableColumn(Range)

@init_in_parent
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.generator_param(self.capacitance, self.voltage, self.voltage_rating_derating, self.exact_capacitance)

def _row_generate(self, row: PartsTableRow) -> None:
super()._row_generate(row)
self.assign(self.actual_voltage_rating, row[self.VOLTAGE_RATING])
self.assign(self.actual_capacitance, row[self.CAPACITANCE])

def _row_filter(self, row: PartsTableRow) -> bool:
derated_voltage = self.get(self.voltage) / self.get(self.voltage_rating_derating)
return super()._row_filter(row) and \
derated_voltage.fuzzy_in(row[self.VOLTAGE_RATING]) and \
self._row_filter_capacitance(row)

def _row_filter_capacitance(self, row: PartsTableRow) -> bool:
return row[self.CAPACITANCE].fuzzy_in(self.get(self.capacitance))


@non_library
class TableDeratingCapacitor(CapacitorStandardFootprint, TableCapacitor, PartsTableFootprintSelector):
class TableDeratingCapacitor(TableCapacitor):
"""Abstract table-based capacitor with derating based on a part-part voltage coefficient."""
VOLTCO = PartsTableColumn(float)
DERATED_CAPACITANCE = PartsTableColumn(Range)
Expand All @@ -151,16 +232,13 @@ class TableDeratingCapacitor(CapacitorStandardFootprint, TableCapacitor, PartsTa
def __init__(self, *args, single_nominal_capacitance: RangeLike = (0, 22)*uFarad, **kwargs):
super().__init__(*args, **kwargs)
self.single_nominal_capacitance = self.ArgParameter(single_nominal_capacitance)
self.generator_param(self.capacitance, self.voltage, self.single_nominal_capacitance,
self.voltage_rating_derating, self.exact_capacitance)
self.generator_param(self.single_nominal_capacitance)

self.actual_derated_capacitance = self.Parameter(RangeExpr())

def _row_filter(self, row: PartsTableRow) -> bool:
derated_voltage = self.get(self.voltage) / self.get(self.voltage_rating_derating)
return super()._row_filter(row) and \
derated_voltage.fuzzy_in(row[self.VOLTAGE_RATING]) and \
Range.exact(row[self.NOMINAL_CAPACITANCE]).fuzzy_in(self.get(self.single_nominal_capacitance))
def _row_filter_capacitance(self, row: PartsTableRow) -> bool:
# post-derating capacitance filtering is in _table_postprocess
return Range.exact(row[self.NOMINAL_CAPACITANCE]).fuzzy_in(self.get(self.single_nominal_capacitance))

def _table_postprocess(self, table: PartsTable) -> PartsTable:
def add_derated_row(row: PartsTableRow) -> Optional[Dict[PartsTableColumn, Any]]:
Expand Down Expand Up @@ -201,8 +279,6 @@ def add_derated_row(row: PartsTableRow) -> Optional[Dict[PartsTableColumn, Any]]
def _row_generate(self, row: PartsTableRow) -> None:
if row[self.PARALLEL_COUNT] == 1:
super()._row_generate(row) # creates the footprint
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]}")
Expand Down Expand Up @@ -256,7 +332,7 @@ def __init__(self, capacitance: RangeLike, *, exact_capacitance: BoolLike = Fals
self.pwr = self.Export(self.cap.pos.adapt_to(VoltageSink(
voltage_limits=(self.cap.actual_voltage_rating + self.gnd.link().voltage).hull(self.gnd.link().voltage),
current_draw=0*Amp(tol=0)
)), [Power])
)), [Power, InOut])

self.assign(self.cap.voltage, self.pwr.link().voltage - self.gnd.link().voltage)

Expand Down
21 changes: 21 additions & 0 deletions electronics_abstract_parts/AbstractConnector.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,24 @@ def __init__(self) -> None:
class BananaSafetyJack(BananaJack):
"""Base class for a single terminal 4mm banana jack supporting a safety sheath,
such as on multimeter leads."""


@abstract_block
class RfConnector(Connector):
"""Base class for a RF connector, with a signal and ground. Signal is passive-typed."""
def __init__(self) -> None:
super().__init__()
self.sig = self.Port(Passive.empty())
self.gnd = self.Port(Ground(), [Common])


class RfConnectorTestPoint(BlockInterfaceMixin[RfConnector]):
"""Test point mixin that allows the footprint to take a name"""
@init_in_parent
def __init__(self, name: StringLike):
super().__init__()
self.tp_name = self.ArgParameter(name)


class UflConnector(RfConnector):
"""Base class for a U.FL / IPEX / UMCC connector, miniature RF connector."""
2 changes: 2 additions & 0 deletions electronics_abstract_parts/AbstractDiodes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Dict
from deprecated import deprecated

from electronics_model import *
from .DummyDevices import ForcedAnalogVoltage
Expand Down Expand Up @@ -183,6 +184,7 @@ def contents(self):
self.connect(self.diode.anode.adapt_to(Ground()), self.gnd)


@deprecated("Use AnalogClampResistor, which should be cheaper and cause less signal distortion")
class AnalogClampZenerDiode(Protection, KiCadImportableBlock):
"""Analog overvoltage protection diode to clamp the input voltage"""
@init_in_parent
Expand Down
Loading

0 comments on commit f4a2f8e

Please sign in to comment.