From 89de24a5145b3ae0eb32594b117aa1786fda6619 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 17:36:42 -0700 Subject: [PATCH 01/23] Update UsbUart_Cp2102.py --- edg/parts/interface/UsbUart_Cp2102.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edg/parts/interface/UsbUart_Cp2102.py b/edg/parts/interface/UsbUart_Cp2102.py index a4eb7c20e..66373ee69 100644 --- a/edg/parts/interface/UsbUart_Cp2102.py +++ b/edg/parts/interface/UsbUart_Cp2102.py @@ -43,7 +43,7 @@ def __init__(self) -> None: self.suspend = self.Port(dout_model, optional=True) self.nsuspend = self.Port(dout_model, optional=True) - self.uart = self.Port(UartPort(dio_model), optional=True) # baud up to 921600bps + self.uart = self.Port(UartPort(dio_model, baud_limit=(0, 921600) * Hertz), optional=True) self.ri = self.Port(din_model, optional=True) self.dcd = self.Port(din_model, optional=True) self.dtr = self.Port(dout_model, optional=True) From 095a7e59b7d873fc4e34a66bec0b194f532c7dac Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 18:01:21 -0700 Subject: [PATCH 02/23] rename --- edg/core/Blocks.py | 4 ++-- edg/electronics_interfaces/test_i2c_link.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 71e7fcfaa..d1a7420ca 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -492,12 +492,12 @@ def check_subexpr(expr: Union[ConstraintExpr, BasePort]) -> None: # TODO rewrit for subexpr in constraint._get_exprs(): check_subexpr(subexpr) - def require(self, constraint: BoolLike, name: Optional[str] = None, *, unchecked: bool = False) -> BoolExpr: + def require(self, constraint: BoolLike, name: Optional[str] = None, *, _unchecked: bool = False) -> BoolExpr: constraint_typed = BoolExpr._to_expr_type(constraint) if not isinstance(name, (str, type(None))): raise EdgTypeError(f"require(...) name", name, (str, None)) - if not unchecked: # before we have const prop need to manually set nested params + if not _unchecked: # allow breaking structural / abstraction rules, used in some unit tests self._check_constraint(constraint_typed) self._constraints.register(constraint_typed) diff --git a/edg/electronics_interfaces/test_i2c_link.py b/edg/electronics_interfaces/test_i2c_link.py index cf30e60bb..94d5401d8 100644 --- a/edg/electronics_interfaces/test_i2c_link.py +++ b/edg/electronics_interfaces/test_i2c_link.py @@ -32,7 +32,7 @@ def __init__(self) -> None: self.device2 = self.Block(I2cTargetBlock(2)) self.link = self.connect(self.controller.port, self.pull.port, self.device1.port, self.device2.port) - self.require(self.controller.port.link().addresses == [1, 2], unchecked=True) + self.require(self.controller.port.link().addresses == [1, 2], _unchecked=True) class I2cNoPullTest(DesignTop): From b5a19dc489fca068d1693a2b2dd8895340b2e02f Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 18:27:12 -0700 Subject: [PATCH 03/23] i2c frequency test --- edg/electronics_interfaces/I2cPort.py | 1 + edg/electronics_interfaces/test_i2c_link.py | 39 ++++++++++++++++++--- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/edg/electronics_interfaces/I2cPort.py b/edg/electronics_interfaces/I2cPort.py index 20a496ff5..d7d0c3980 100644 --- a/edg/electronics_interfaces/I2cPort.py +++ b/edg/electronics_interfaces/I2cPort.py @@ -41,6 +41,7 @@ def contents(self) -> None: self.frequency, self.controller.frequency_limit.intersect(self.targets.intersection(lambda x: x.frequency_limit)), ) + self.require(self.frequency != RangeExpr.EMPTY, "no compatible frequency between devices") self.scl = self.connect( self.pull.map_extract(lambda device: device.scl), diff --git a/edg/electronics_interfaces/test_i2c_link.py b/edg/electronics_interfaces/test_i2c_link.py index 94d5401d8..2b5d849a1 100644 --- a/edg/electronics_interfaces/test_i2c_link.py +++ b/edg/electronics_interfaces/test_i2c_link.py @@ -6,9 +6,9 @@ class I2cControllerBlock(Block): - def __init__(self) -> None: + def __init__(self, frequency_limit: RangeLike = RangeExpr.ALL) -> None: super().__init__() - self.port = self.Port(I2cController()) + self.port = self.Port(I2cController(frequency_limit=frequency_limit)) class I2cPullupBlock(Block): @@ -18,9 +18,9 @@ def __init__(self) -> None: class I2cTargetBlock(Block): - def __init__(self, address: IntLike): + def __init__(self, address: IntLike, frequency_limit: RangeLike = RangeExpr.ALL) -> None: super().__init__() - self.port = self.Port(I2cTarget(DigitalBidir(), addresses=[address])) + self.port = self.Port(I2cTarget(DigitalBidir(), addresses=[address], frequency_limit=frequency_limit)) class I2cTest(DesignTop): @@ -83,6 +83,30 @@ def __init__(self) -> None: self.link = self.connect(self.controller.port, self.pull.port, self.device.port) +class I2cFrequencyTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.controller = self.Block(I2cControllerBlock(frequency_limit=(0, 400) * kHertz)) + self.pull = self.Block(I2cPullupBlock()) + self.device1 = self.Block(I2cTargetBlock(1, frequency_limit=(0, 100) * kHertz)) + self.device2 = self.Block(I2cTargetBlock(2, frequency_limit=(10, 400) * kHertz)) + self.connect(self.controller.port, self.pull.port, self.device1.port, self.device2.port) + # whole bus must run within all devices' limits + self.require(self.controller.port.link().frequency == (10, 100) * kHertz, _unchecked=True) + + +class I2cFrequencyInvalidTest(DesignTop): + """Checks that non-overlapping frequencies are an error. + This generally doesn't happen in practice, since devices tend to support low speeds""" + + def __init__(self) -> None: + super().__init__() + self.controller = self.Block(I2cControllerBlock(frequency_limit=(400, 400) * kHertz)) + self.pull = self.Block(I2cPullupBlock()) + self.device = self.Block(I2cTargetBlock(1, frequency_limit=(100, 100) * kHertz)) + self.connect(self.controller.port, self.pull.port, self.device.port) + + class I2cTestCase(unittest.TestCase): def test_i2c(self) -> None: ScalaCompiler.compile(I2cTest) @@ -101,3 +125,10 @@ def test_i2c_nested(self) -> None: def test_i2c_nested_extrapull(self) -> None: with self.assertRaises(CompilerCheckError): ScalaCompiler.compile(I2cNestedExtraPullTest) + + def test_i2c_frequency(self) -> None: + ScalaCompiler.compile(I2cFrequencyTest) + + def test_i2c_frequency_invalid(self) -> None: + with self.assertRaises(CompilerCheckError): + ScalaCompiler.compile(I2cFrequencyInvalidTest) From 8b61a15d6dd7692de41111bf9fc36925a32695ec Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 18:38:29 -0700 Subject: [PATCH 04/23] spi tests --- edg/electronics_interfaces/SpiPort.py | 7 +++ edg/electronics_interfaces/test_spi_link.py | 59 +++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 edg/electronics_interfaces/test_spi_link.py diff --git a/edg/electronics_interfaces/SpiPort.py b/edg/electronics_interfaces/SpiPort.py index 58017cf34..51ca935db 100644 --- a/edg/electronics_interfaces/SpiPort.py +++ b/edg/electronics_interfaces/SpiPort.py @@ -21,6 +21,8 @@ def __init__(self) -> None: self.controller = self.Port(SpiController(DigitalBidir.empty())) self.peripherals = self.Port(Vector(SpiPeripheral(DigitalBidir.empty()))) + self.frequency = self.Parameter(RangeExpr()) + @override def contents(self) -> None: super().contents() @@ -33,6 +35,11 @@ def contents(self) -> None: self.mosi = self.connect( self.controller.mosi, self.peripherals.map_extract(lambda device: device.mosi), flatten=True ) + self.assign( + self.frequency, + self.controller.frequency_limit.intersect(self.peripherals.hull(lambda x: x.frequency_limit)), + ) + self.require(self.frequency != RangeExpr.EMPTY, "no compatible frequency between devices") class SpiController(Port[SpiLink]): diff --git a/edg/electronics_interfaces/test_spi_link.py b/edg/electronics_interfaces/test_spi_link.py new file mode 100644 index 000000000..17509343a --- /dev/null +++ b/edg/electronics_interfaces/test_spi_link.py @@ -0,0 +1,59 @@ +import unittest + +from ..electronics_model import * +from .DigitalPorts import DigitalBidir +from .SpiPort import SpiController, SpiPeripheral + + +class SpiControllerBlock(Block): + def __init__(self, frequency_limit: RangeLike = RangeExpr.ALL) -> None: + super().__init__() + self.port = self.Port(SpiController(frequency_limit=frequency_limit)) + + +class SpiPeripheralBlock(Block): + def __init__(self, frequency_limit: RangeLike = RangeExpr.ALL) -> None: + super().__init__() + self.port = self.Port(SpiPeripheral(DigitalBidir(), frequency_limit=frequency_limit)) + + +class SpiTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.controller = self.Block(SpiControllerBlock()) + self.device1 = self.Block(SpiPeripheralBlock()) + self.device2 = self.Block(SpiPeripheralBlock()) + self.connect(self.controller.port, self.device1.port, self.device2.port) + + +class SpiFrequencyTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.controller = self.Block(SpiControllerBlock(frequency_limit=(1, 10) * MHertz)) + self.device1 = self.Block(SpiPeripheralBlock(frequency_limit=(2, 8) * MHertz)) + self.device2 = self.Block(SpiPeripheralBlock(frequency_limit=(0.5, 5) * MHertz)) + self.connect(self.controller.port, self.device1.port, self.device2.port) + # bus may run at any frequency the controller and any peripheral is capable of + self.require(self.controller.port.link().frequency == (1, 8) * MHertz, _unchecked=True) + + +class SpiFrequencyInvalidTest(DesignTop): + """Invalid connection with no overlapping frequency range""" + + def __init__(self) -> None: + super().__init__() + self.controller = self.Block(SpiControllerBlock(frequency_limit=(1, 1) * MHertz)) + self.device = self.Block(SpiPeripheralBlock(frequency_limit=(0.01, 0.1) * MHertz)) + self.connect(self.controller.port, self.device.port) + + +class SpiTestCase(unittest.TestCase): + def test_link(self) -> None: + ScalaCompiler.compile(SpiTest) + + def test_frequency(self) -> None: + ScalaCompiler.compile(SpiFrequencyTest) + + def test_frequency_invalid(self) -> None: + with self.assertRaises(CompilerCheckError): + ScalaCompiler.compile(SpiFrequencyInvalidTest) From 6e427ce6032dbb79d9995295120ed160f4928778 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 18:39:42 -0700 Subject: [PATCH 05/23] renaming + spi tests --- edg/electronics_interfaces/I2cPort.py | 6 +++--- edg/electronics_interfaces/SpiPort.py | 6 +++--- edg/electronics_interfaces/test_i2c_link.py | 2 +- edg/electronics_interfaces/test_spi_link.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/edg/electronics_interfaces/I2cPort.py b/edg/electronics_interfaces/I2cPort.py index d7d0c3980..8c3a596da 100644 --- a/edg/electronics_interfaces/I2cPort.py +++ b/edg/electronics_interfaces/I2cPort.py @@ -25,7 +25,7 @@ def __init__(self) -> None: self.addresses = self.Parameter( ArrayIntExpr(self.targets.flatten(lambda x: x.addresses).concat(self.controller.addresses)) ) - self.frequency = self.Parameter(RangeExpr()) + self.frequency_limit = self.Parameter(RangeExpr()) self.has_pull = self.Parameter(BoolExpr(self.pull.any_connected())) @@ -38,10 +38,10 @@ def contents(self) -> None: self.require(self.addresses.all_unique(), "conflicting addresses on I2C bus") self.assign( - self.frequency, + self.frequency_limit, self.controller.frequency_limit.intersect(self.targets.intersection(lambda x: x.frequency_limit)), ) - self.require(self.frequency != RangeExpr.EMPTY, "no compatible frequency between devices") + self.require(self.frequency_limit != RangeExpr.EMPTY, "no compatible frequency between devices") self.scl = self.connect( self.pull.map_extract(lambda device: device.scl), diff --git a/edg/electronics_interfaces/SpiPort.py b/edg/electronics_interfaces/SpiPort.py index 51ca935db..3c3a8d4f8 100644 --- a/edg/electronics_interfaces/SpiPort.py +++ b/edg/electronics_interfaces/SpiPort.py @@ -21,7 +21,7 @@ def __init__(self) -> None: self.controller = self.Port(SpiController(DigitalBidir.empty())) self.peripherals = self.Port(Vector(SpiPeripheral(DigitalBidir.empty()))) - self.frequency = self.Parameter(RangeExpr()) + self.frequency_limit = self.Parameter(RangeExpr()) @override def contents(self) -> None: @@ -36,10 +36,10 @@ def contents(self) -> None: self.controller.mosi, self.peripherals.map_extract(lambda device: device.mosi), flatten=True ) self.assign( - self.frequency, + self.frequency_limit, self.controller.frequency_limit.intersect(self.peripherals.hull(lambda x: x.frequency_limit)), ) - self.require(self.frequency != RangeExpr.EMPTY, "no compatible frequency between devices") + self.require(self.frequency_limit != RangeExpr.EMPTY, "no compatible frequency between devices") class SpiController(Port[SpiLink]): diff --git a/edg/electronics_interfaces/test_i2c_link.py b/edg/electronics_interfaces/test_i2c_link.py index 2b5d849a1..75759adc3 100644 --- a/edg/electronics_interfaces/test_i2c_link.py +++ b/edg/electronics_interfaces/test_i2c_link.py @@ -92,7 +92,7 @@ def __init__(self) -> None: self.device2 = self.Block(I2cTargetBlock(2, frequency_limit=(10, 400) * kHertz)) self.connect(self.controller.port, self.pull.port, self.device1.port, self.device2.port) # whole bus must run within all devices' limits - self.require(self.controller.port.link().frequency == (10, 100) * kHertz, _unchecked=True) + self.require(self.controller.port.link().frequency_limit == (10, 100) * kHertz, _unchecked=True) class I2cFrequencyInvalidTest(DesignTop): diff --git a/edg/electronics_interfaces/test_spi_link.py b/edg/electronics_interfaces/test_spi_link.py index 17509343a..f31bdaf68 100644 --- a/edg/electronics_interfaces/test_spi_link.py +++ b/edg/electronics_interfaces/test_spi_link.py @@ -34,7 +34,7 @@ def __init__(self) -> None: self.device2 = self.Block(SpiPeripheralBlock(frequency_limit=(0.5, 5) * MHertz)) self.connect(self.controller.port, self.device1.port, self.device2.port) # bus may run at any frequency the controller and any peripheral is capable of - self.require(self.controller.port.link().frequency == (1, 8) * MHertz, _unchecked=True) + self.require(self.controller.port.link().frequency_limit == (1, 8) * MHertz, _unchecked=True) class SpiFrequencyInvalidTest(DesignTop): From 36444054ed6c9589df5fceee48836a62f84ba0b5 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 18:40:02 -0700 Subject: [PATCH 06/23] Update I2cPort.py --- edg/electronics_interfaces/I2cPort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edg/electronics_interfaces/I2cPort.py b/edg/electronics_interfaces/I2cPort.py index 8c3a596da..8b8f6dac8 100644 --- a/edg/electronics_interfaces/I2cPort.py +++ b/edg/electronics_interfaces/I2cPort.py @@ -7,7 +7,7 @@ class I2cLink(Link): - """I2C connection, using terminology from the auhtoritative NXP specification at + """I2C connection, using terminology from the authoritative NXP specification at https://www.nxp.com/docs/en/user-guide/UM10204.pdf. """ From 4ec24dc136e506ede1b2c7d173f3bf53dbb918a5 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 18:43:38 -0700 Subject: [PATCH 07/23] Update test_spi_link.py --- edg/electronics_interfaces/test_spi_link.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/edg/electronics_interfaces/test_spi_link.py b/edg/electronics_interfaces/test_spi_link.py index f31bdaf68..5f547b40b 100644 --- a/edg/electronics_interfaces/test_spi_link.py +++ b/edg/electronics_interfaces/test_spi_link.py @@ -38,7 +38,8 @@ def __init__(self) -> None: class SpiFrequencyInvalidTest(DesignTop): - """Invalid connection with no overlapping frequency range""" + """Invalid connection with no overlapping frequency range, e.g. fixed-frequency non-programmable + controller with incompatible peripheral""" def __init__(self) -> None: super().__init__() From da24c67704de8e72faf0075cdac267254f5d8215 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 18:49:16 -0700 Subject: [PATCH 08/23] uart structural test --- edg/electronics_interfaces/test_spi_link.py | 2 +- edg/electronics_interfaces/test_uart_link.py | 38 ++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 edg/electronics_interfaces/test_uart_link.py diff --git a/edg/electronics_interfaces/test_spi_link.py b/edg/electronics_interfaces/test_spi_link.py index 5f547b40b..e4ff45001 100644 --- a/edg/electronics_interfaces/test_spi_link.py +++ b/edg/electronics_interfaces/test_spi_link.py @@ -8,7 +8,7 @@ class SpiControllerBlock(Block): def __init__(self, frequency_limit: RangeLike = RangeExpr.ALL) -> None: super().__init__() - self.port = self.Port(SpiController(frequency_limit=frequency_limit)) + self.port = self.Port(SpiController(DigitalBidir(), frequency_limit=frequency_limit)) class SpiPeripheralBlock(Block): diff --git a/edg/electronics_interfaces/test_uart_link.py b/edg/electronics_interfaces/test_uart_link.py new file mode 100644 index 000000000..ebbee59f2 --- /dev/null +++ b/edg/electronics_interfaces/test_uart_link.py @@ -0,0 +1,38 @@ +import unittest + +from ..core.HdlUserExceptions import UnconnectableError +from ..electronics_model import * +from .DigitalPorts import DigitalBidir +from .UartPort import UartPort + + +class UartBlock(Block): + def __init__(self, baud_limit: RangeLike = RangeExpr.ALL) -> None: + super().__init__() + self.port = self.Port(UartPort(DigitalBidir(), baud_limit=baud_limit)) + + +class UartTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.device1 = self.Block(UartBlock()) + self.device2 = self.Block(UartBlock()) + self.connect(self.device1.port, self.device2.port) + + +class UartOverconnectTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.device1 = self.Block(UartBlock()) + self.device2 = self.Block(UartBlock()) + self.device3 = self.Block(UartBlock()) + self.connect(self.device1.port, self.device2.port, self.device3.port) + + +class UartTestCase(unittest.TestCase): + def test_link(self) -> None: + ScalaCompiler.compile(UartTest) + + def test_overconnect(self) -> None: + with self.assertRaises(UnconnectableError): + ScalaCompiler.compile(UartOverconnectTest) From 3166b128514dfb5afc5c220d795171acfb1eafd0 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 18:54:38 -0700 Subject: [PATCH 09/23] UART baud test --- edg/electronics_interfaces/UartPort.py | 5 ++++ edg/electronics_interfaces/test_uart_link.py | 24 ++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/edg/electronics_interfaces/UartPort.py b/edg/electronics_interfaces/UartPort.py index 826979fda..5e5d57839 100644 --- a/edg/electronics_interfaces/UartPort.py +++ b/edg/electronics_interfaces/UartPort.py @@ -11,6 +11,8 @@ def __init__(self) -> None: self.a = self.Port(UartPort(DigitalBidir.empty())) self.b = self.Port(UartPort(DigitalBidir.empty())) + self.baud_limit = self.Parameter(RangeExpr()) + @override def contents(self) -> None: super().contents() @@ -18,6 +20,9 @@ def contents(self) -> None: self.a_tx = self.connect(self.a.tx, self.b.rx) self.b_tx = self.connect(self.b.tx, self.a.rx) + self.assign(self.baud_limit, self.a.baud_limit.intersect(self.b.baud_limit)) + self.require(self.baud_limit != RangeExpr.EMPTY, "no compatible frequency between devices") + class UartPort(Port[UartLink]): link_type = UartLink diff --git a/edg/electronics_interfaces/test_uart_link.py b/edg/electronics_interfaces/test_uart_link.py index ebbee59f2..ee9f22a55 100644 --- a/edg/electronics_interfaces/test_uart_link.py +++ b/edg/electronics_interfaces/test_uart_link.py @@ -29,6 +29,23 @@ def __init__(self) -> None: self.connect(self.device1.port, self.device2.port, self.device3.port) +class UartFrequencyTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.device1 = self.Block(UartBlock(baud_limit=(9600, 115200) * Hertz)) + self.device2 = self.Block(UartBlock(baud_limit=(38400, 921600) * Hertz)) + self.connect(self.device1.port, self.device2.port) + self.require(self.device1.port.link().baud_limit == (38400, 115200) * Hertz, _unchecked=True) + + +class UartFrequencyInvalidTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.device1 = self.Block(UartBlock(baud_limit=(9600, 38400) * Hertz)) + self.device2 = self.Block(UartBlock(baud_limit=(115200, 921600) * Hertz)) + self.connect(self.device1.port, self.device2.port) + + class UartTestCase(unittest.TestCase): def test_link(self) -> None: ScalaCompiler.compile(UartTest) @@ -36,3 +53,10 @@ def test_link(self) -> None: def test_overconnect(self) -> None: with self.assertRaises(UnconnectableError): ScalaCompiler.compile(UartOverconnectTest) + + def test_frequency(self) -> None: + ScalaCompiler.compile(UartFrequencyTest) + + def test_frequency_invalid(self) -> None: + with self.assertRaises(CompilerCheckError): + ScalaCompiler.compile(UartFrequencyInvalidTest) From 41c58fbd3deef3b9e9104aab908661d09758fa98 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 19:23:35 -0700 Subject: [PATCH 10/23] refactor i2s --- edg/electronics_interfaces/I2sPort.py | 32 +++++++++++++++++-- .../SpeakerDriver_Max98357a.py | 2 +- edg/parts/microcontroller/Esp32c3.py | 3 +- edg/parts/microcontroller/Esp32s3.py | 3 +- edg/parts/microcontroller/Stm32g031.py | 4 ++- edg/parts/microcontroller/Stm32g431.py | 7 ++-- edg/parts/microcontroller/nRF52840.py | 3 +- 7 files changed, 42 insertions(+), 12 deletions(-) diff --git a/edg/electronics_interfaces/I2sPort.py b/edg/electronics_interfaces/I2sPort.py index 6d3ff6221..7687c40c7 100644 --- a/edg/electronics_interfaces/I2sPort.py +++ b/edg/electronics_interfaces/I2sPort.py @@ -13,6 +13,9 @@ def __init__(self) -> None: self.target_receiver = self.Port(I2sTargetReceiver(DigitalSink.empty())) # TODO: multiple receivers, target transmitters, eg microphones + self.sample_rate_limit = self.Parameter(RangeExpr()) + self.bit_limit = self.Parameter(RangeExpr()) + @override def contents(self) -> None: super().contents() @@ -20,13 +23,26 @@ def contents(self) -> None: self.ws = self.connect(self.controller.ws, self.target_receiver.ws) self.sd = self.connect(self.controller.sd, self.target_receiver.sd) + self.assign( + self.sample_rate_limit, self.controller.sample_rate_limit.intersect(self.target_receiver.sample_rate_limit) + ) + self.require(self.sample_rate_limit != RangeExpr.EMPTY, "no compatible sample rate between devices") + self.assign(self.bit_limit, self.controller.bit_limit.intersect(self.target_receiver.bit_limit)) + self.require(self.bit_limit != RangeExpr.EMPTY, "no compatible sample bits (resolution) between devices") + class I2sController(Port[I2sLink]): """Controller is both controller (drives SCK and WS lines) and transmitter (SD is output)""" link_type = I2sLink - def __init__(self, model: Optional[DigitalBidir] = None, *, bitrate_limit: RangeLike = RangeExpr.ALL) -> None: + def __init__( + self, + model: Optional[DigitalBidir] = None, + *, + sample_rate_limit: RangeLike = RangeExpr.ALL, + bit_limit: RangeLike = RangeExpr.ALL, + ) -> None: super().__init__() if model is None: model = DigitalBidir() # ideal by default @@ -34,7 +50,8 @@ def __init__(self, model: Optional[DigitalBidir] = None, *, bitrate_limit: Range self.ws = self.Port(DigitalSource.from_bidir(model)) self.sd = self.Port(model) # bidirectional - self.bitrate_limit = self.Parameter(RangeExpr(bitrate_limit)) # bitrate + self.sample_rate_limit = self.Parameter(RangeExpr(sample_rate_limit)) + self.bit_limit = self.Parameter(RangeExpr(bit_limit)) class I2sTargetReceiver(Port[I2sLink]): @@ -42,10 +59,19 @@ class I2sTargetReceiver(Port[I2sLink]): link_type = I2sLink - def __init__(self, model: Optional[DigitalSink] = None) -> None: + def __init__( + self, + model: Optional[DigitalSink] = None, + *, + sample_rate_limit: RangeLike = RangeExpr.ALL, + bit_limit: RangeLike = RangeExpr.ALL, + ) -> None: super().__init__() if model is None: model = DigitalSink() # ideal by default self.sck = self.Port(model) self.ws = self.Port(model) self.sd = self.Port(model) + + self.sample_rate_limit = self.Parameter(RangeExpr(sample_rate_limit)) + self.bit_limit = self.Parameter(RangeExpr(bit_limit)) diff --git a/edg/parts/human_interface/SpeakerDriver_Max98357a.py b/edg/parts/human_interface/SpeakerDriver_Max98357a.py index eb7e8e28a..eb6d5cb65 100644 --- a/edg/parts/human_interface/SpeakerDriver_Max98357a.py +++ b/edg/parts/human_interface/SpeakerDriver_Max98357a.py @@ -26,7 +26,7 @@ def __init__(self, *, part: StringLike = "") -> None: voltage_limits=(-0.3, 6) * Volt, # abs max ratings input_thresholds=(0.6, 0.6) * Volt, # only input low voltage given ) - self.i2s = self.Port(I2sTargetReceiver(din_model)) + self.i2s = self.Port(I2sTargetReceiver(din_model, sample_rate_limit=(8, 96) * kHertz, bit_limit=(16, 32) * Bit)) self.out = self.Port(SpeakerDriverPort(AnalogSource()), [Output]) diff --git a/edg/parts/microcontroller/Esp32c3.py b/edg/parts/microcontroller/Esp32c3.py index 17df39fee..9aa70f1bd 100644 --- a/edg/parts/microcontroller/Esp32c3.py +++ b/edg/parts/microcontroller/Esp32c3.py @@ -157,7 +157,8 @@ def _io_pinmap(self) -> PinMapUtil: spi_peripheral_model = SpiPeripheral(DigitalBidir.empty(), frequency_limit=(0, 60) * MHertz) i2c_model = I2cController(DigitalBidir.empty(), frequency_limit=(100, 800) * kHertz) # section 3.4.4 i2c_target_model = I2cTarget(DigitalBidir.empty(), frequency_limit=(100, 800) * kHertz) - i2s_model = I2sController(DigitalBidir.empty(), bitrate_limit=(0.01, 40) * MHertz) + # specified up to 40 Mbps, sample rate backed out with 8 bits and two channels + i2s_model = I2sController(DigitalBidir.empty(), sample_rate_limit=(0, 2.5) * MHertz, bit_limit=(8, 32) * Bit) can_model = CanControllerPort(DigitalBidir.empty(), bitrate_limit=(1, 1000) * kHertz) # aka TWAI return ( diff --git a/edg/parts/microcontroller/Esp32s3.py b/edg/parts/microcontroller/Esp32s3.py index a8fa2b974..095c3a2ca 100644 --- a/edg/parts/microcontroller/Esp32s3.py +++ b/edg/parts/microcontroller/Esp32s3.py @@ -139,7 +139,8 @@ def _io_pinmap(self) -> PinMapUtil: i2c_target_model = I2cTarget(DigitalBidir.empty(), frequency_limit=(100, 800) * kHertz) touch_model = TouchDriver() can_model = CanControllerPort(DigitalBidir.empty(), bitrate_limit=(1, 1000) * kHertz) - i2s_model = I2sController(DigitalBidir.empty(), bitrate_limit=(0.01, 40) * MHertz) + # specified up to 40 Mbps, sample rate backed out with 8 bits and two channels + i2s_model = I2sController(DigitalBidir.empty(), sample_rate_limit=(0, 2.5) * MHertz, bit_limit=(8, 32) * Bit) dvp8_model = Dvp8Host(DigitalBidir.empty()) return ( diff --git a/edg/parts/microcontroller/Stm32g031.py b/edg/parts/microcontroller/Stm32g031.py index bb3909563..4a76bbbb7 100644 --- a/edg/parts/microcontroller/Stm32g031.py +++ b/edg/parts/microcontroller/Stm32g031.py @@ -109,6 +109,8 @@ def _io_pinmap(self) -> PinMapUtil: # TODO SPI peripherals, which have fixed-pin CS lines i2c_model = I2cController(DigitalBidir.empty(), frequency_limit=(0, 1) * MHertz) i2c_target_model = I2cTarget(DigitalBidir.empty(), frequency_limit=(0, 1) * MHertz) + # 192kHz specified sample rate * 2 channels * 32 bits data resolution + i2s_model = I2sController(DigitalBidir.empty(), sample_rate_limit=(0, 192) * kHertz, bit_limit=(16, 32) * Bit) return PinMapUtil( [ # Table 12, partial table for up to 32-pin only @@ -159,7 +161,7 @@ def _io_pinmap(self) -> PinMapUtil: ), PeripheralFixedResource( "I2S1", - I2sController(DigitalBidir.empty()), + i2s_model, {"sck": ["PA1", "PA5", "PB3"], "ws": ["PA4", "PB0", "PA15"], "sd": ["PA2", "PA7", "PA12", "PB5"]}, ), PeripheralFixedResource("USART2", uart_model, {"tx": ["PA2", "PA14"], "rx": ["PA3", "PA15"]}), diff --git a/edg/parts/microcontroller/Stm32g431.py b/edg/parts/microcontroller/Stm32g431.py index 7739c6ad9..c3ff898cd 100644 --- a/edg/parts/microcontroller/Stm32g431.py +++ b/edg/parts/microcontroller/Stm32g431.py @@ -140,6 +140,7 @@ def _io_pinmap(self) -> PinMapUtil: i2c_model = I2cController(DigitalBidir.empty(), frequency_limit=(0, 1) * MHertz) i2c_target_model = I2cTarget(DigitalBidir.empty(), frequency_limit=(0, 1) * MHertz) fdcan_model = CanControllerPort(DigitalBidir.empty(), bitrate_limit=(0, 8) * MHertz) + i2s_model = I2sController(DigitalBidir.empty(), sample_rate_limit=(0, 192) * kHertz, bit_limit=(16, 32) * Bit) return PinMapUtil( [ # for 32 pins only for now @@ -176,12 +177,10 @@ def _io_pinmap(self) -> PinMapUtil: PeripheralFixedResource("SPI3", spi_model, {"sck": ["PB3"], "miso": ["PB4"], "mosi": ["PB5"]}), PeripheralFixedResource( "I2S2", - I2sController(DigitalBidir.empty()), + i2s_model, {"sck": ["PB13", "PF1"], "ws": ["PB12", "PF0"], "sd": ["PA11", "PB15"]}, ), - PeripheralFixedResource( - "I2S3", I2sController(DigitalBidir.empty()), {"sck": ["PB3"], "ws": ["PA4", "PA15"], "sd": ["PB5"]} - ), + PeripheralFixedResource("I2S3", i2s_model, {"sck": ["PB3"], "ws": ["PA4", "PA15"], "sd": ["PB5"]}), PeripheralFixedResource("USART1", uart_model, {"tx": ["PA9", "PB6"], "rx": ["PA10", "PB7"]}), PeripheralFixedResource( "USART2", uart_model, {"tx": ["PA2", "PA14", "PB3"], "rx": ["PA3", "PA15", "PB4"]} diff --git a/edg/parts/microcontroller/nRF52840.py b/edg/parts/microcontroller/nRF52840.py index cb5b74cd4..6a3573bff 100644 --- a/edg/parts/microcontroller/nRF52840.py +++ b/edg/parts/microcontroller/nRF52840.py @@ -161,7 +161,8 @@ def _io_pinmap(self) -> PinMapUtil: ) # tristated by CS pin i2c_model = I2cController(DigitalBidir.empty(), frequency_limit=(100, 400) * kHertz) i2c_target_model = I2cTarget(DigitalBidir.empty(), frequency_limit=(100, 400) * kHertz) - i2s_model = I2sController(DigitalBidir.empty(), bitrate_limit=(0, 2000) * kHertz) + # 8-24 bits, with 24 is sign-extended to 32 on the wire + i2s_model = I2sController(DigitalBidir.empty(), sample_rate_limit=(0, 48) * kHertz, bit_limit=(8, 32) * Bit) hf_io_pins = [ "P0.00", From 7168fdd7ecc0d0d74e7408025ea4d55e10c45bbf Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 19:30:03 -0700 Subject: [PATCH 11/23] Create test_i2s_link.py --- edg/electronics_interfaces/test_i2s_link.py | 94 +++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 edg/electronics_interfaces/test_i2s_link.py diff --git a/edg/electronics_interfaces/test_i2s_link.py b/edg/electronics_interfaces/test_i2s_link.py new file mode 100644 index 000000000..df5020687 --- /dev/null +++ b/edg/electronics_interfaces/test_i2s_link.py @@ -0,0 +1,94 @@ +import unittest + +from ..core.HdlUserExceptions import UnconnectableError +from ..electronics_model import * +from .DigitalPorts import DigitalBidir, DigitalSink +from .I2sPort import I2sController, I2sTargetReceiver + + +class I2sControllerBlock(Block): + def __init__(self, sample_rate_limit: RangeLike = RangeExpr.ALL, bit_limit: RangeLike = RangeExpr.ALL) -> None: + super().__init__() + self.port = self.Port(I2sController(DigitalBidir(), sample_rate_limit=sample_rate_limit, bit_limit=bit_limit)) + + +class I2sTargetReceiverBlock(Block): + def __init__(self, sample_rate_limit: RangeLike = RangeExpr.ALL, bit_limit: RangeLike = RangeExpr.ALL) -> None: + super().__init__() + self.port = self.Port( + I2sTargetReceiver(DigitalSink(), sample_rate_limit=sample_rate_limit, bit_limit=bit_limit) + ) + + +class I2sTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.controller = self.Block(I2sControllerBlock()) + self.target = self.Block(I2sTargetReceiverBlock()) + self.connect(self.controller.port, self.target.port) + + +class I2sOverconnectTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.controller = self.Block(I2sControllerBlock()) + self.target1 = self.Block(I2sTargetReceiverBlock()) + self.target2 = self.Block(I2sTargetReceiverBlock()) + self.connect(self.controller.port, self.target1.port, self.target2.port) + + +class I2sSampleRateTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.controller = self.Block(I2sControllerBlock(sample_rate_limit=(0, 48) * kHertz)) + self.target = self.Block(I2sTargetReceiverBlock(sample_rate_limit=(8, 96) * kHertz)) + self.connect(self.controller.port, self.target.port) + self.require(self.controller.port.link().sample_rate_limit == (8, 48) * kHertz, _unchecked=True) + + +class I2sSampleRateInvalidTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.controller = self.Block(I2sControllerBlock(sample_rate_limit=(0, 16) * kHertz)) + self.target = self.Block(I2sTargetReceiverBlock(sample_rate_limit=(48, 96) * kHertz)) + self.connect(self.controller.port, self.target.port) + + +class I2sBitsTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.controller = self.Block(I2sControllerBlock(bit_limit=(8, 16) * Bit)) + self.target = self.Block(I2sTargetReceiverBlock(bit_limit=(16, 32) * Bit)) + self.connect(self.controller.port, self.target.port) + self.require(self.controller.port.link().sample_rate_limit == (16, 16) * Bit, _unchecked=True) + + +class I2sBitsInvalidTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.controller = self.Block(I2sControllerBlock(sample_rate_limit=(0, 8) * Bit)) + self.target = self.Block(I2sTargetReceiverBlock(sample_rate_limit=(16, 16) * Bit)) + self.connect(self.controller.port, self.target.port) + + +class I2sTestCase(unittest.TestCase): + def test_link(self) -> None: + ScalaCompiler.compile(I2sTest) + + def test_overconnect(self) -> None: + with self.assertRaises(UnconnectableError): + ScalaCompiler.compile(I2sOverconnectTest) + + def test_frequency(self) -> None: + ScalaCompiler.compile(I2sSampleRateTest) + + def test_frequency_invalid(self) -> None: + with self.assertRaises(CompilerCheckError): + ScalaCompiler.compile(I2sSampleRateInvalidTest) + + def test_bits(self) -> None: + ScalaCompiler.compile(I2sBitsTest) + + def test_bits_invalid(self) -> None: + with self.assertRaises(CompilerCheckError): + ScalaCompiler.compile(I2sBitsInvalidTest) From 894dddadc6c9c1f1e72844b6dccae8e7d09a5d52 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 19:30:45 -0700 Subject: [PATCH 12/23] Update test_i2s_link.py --- edg/electronics_interfaces/test_i2s_link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edg/electronics_interfaces/test_i2s_link.py b/edg/electronics_interfaces/test_i2s_link.py index df5020687..8d9690b2d 100644 --- a/edg/electronics_interfaces/test_i2s_link.py +++ b/edg/electronics_interfaces/test_i2s_link.py @@ -60,7 +60,7 @@ def __init__(self) -> None: self.controller = self.Block(I2sControllerBlock(bit_limit=(8, 16) * Bit)) self.target = self.Block(I2sTargetReceiverBlock(bit_limit=(16, 32) * Bit)) self.connect(self.controller.port, self.target.port) - self.require(self.controller.port.link().sample_rate_limit == (16, 16) * Bit, _unchecked=True) + self.require(self.controller.port.link().bit_limit == (16, 16) * Bit, _unchecked=True) class I2sBitsInvalidTest(DesignTop): From 8fd8d759fd6838539b946156685c357805585c0d Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 19:43:15 -0700 Subject: [PATCH 13/23] can bitrates --- edg/electronics_interfaces/CanPort.py | 23 +++++++++++-------- edg/parts/interface/CanTransceiver_Iso1050.py | 6 +++-- .../interface/CanTransceiver_Sn65hvd230.py | 5 ++-- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/edg/electronics_interfaces/CanPort.py b/edg/electronics_interfaces/CanPort.py index aa1d7feb2..437a3e3b5 100644 --- a/edg/electronics_interfaces/CanPort.py +++ b/edg/electronics_interfaces/CanPort.py @@ -16,13 +16,12 @@ def __init__(self) -> None: self.transceiver = self.Port(CanTransceiverPort(DigitalBidir.empty())) self.passive = self.Port(Vector(CanPassivePort(DigitalBidir.empty())), optional=True) - # TODO write custom top level digital constraints - # TODO model frequency ... somewhere + # 0-1 Mbit/s are standard CANbus, 1-8 Mbit/s imply CAN-FD + self.bitrate_limit = self.Parameter(RangeExpr()) @override def contents(self) -> None: super().contents() - # TODO future: digital constraints through link inference self.txd = self.connect( self.controller.txd, self.transceiver.txd, self.passive.map_extract(lambda port: port.txd), flatten=True @@ -31,13 +30,14 @@ def contents(self) -> None: self.controller.rxd, self.transceiver.rxd, self.passive.map_extract(lambda port: port.rxd), flatten=True ) + self.assign(self.bitrate_limit, self.controller.bitrate_limit.intersect(self.transceiver.bitrate_limit)) + self.require(self.bitrate_limit != RangeExpr.EMPTY, "no compatible bitrate between devices") + class CanControllerPort(Port[CanLogicLink]): link_type = CanLogicLink def __init__(self, model: Optional[DigitalBidir] = None, *, bitrate_limit: RangeLike = RangeExpr.ALL) -> None: - # 0-1 Mbit/s are standard CANbus - # 1-8 Mbit/s imply CAN-FD super().__init__() if model is None: # ideal by default model = DigitalBidir() @@ -49,12 +49,13 @@ def __init__(self, model: Optional[DigitalBidir] = None, *, bitrate_limit: Range class CanTransceiverPort(Port[CanLogicLink]): link_type = CanLogicLink - def __init__(self, model: Optional[DigitalBidir] = None) -> None: + def __init__(self, model: Optional[DigitalBidir] = None, *, bitrate_limit: RangeLike = RangeExpr.ALL) -> None: super().__init__() if model is None: # ideal by default model = DigitalBidir() self.txd = self.Port(DigitalSink.from_bidir(model)) self.rxd = self.Port(DigitalSource.from_bidir(model)) + self.bitrate_limit = self.Parameter(RangeExpr(bitrate_limit)) class CanPassivePort(Port[CanLogicLink]): @@ -75,8 +76,7 @@ def __init__(self) -> None: super().__init__() self.nodes = self.Port(Vector(CanDiffPort.empty())) # TODO mark as required - # TODO write custom top level digital constraints - # TODO future: digital constraints through link inference + self.bitrate_limit = self.Parameter(RangeExpr()) @override def contents(self) -> None: @@ -85,6 +85,9 @@ def contents(self) -> None: self.canh = self.connect(self.nodes.map_extract(lambda node: node.canh), flatten=True) self.canl = self.connect(self.nodes.map_extract(lambda node: node.canl), flatten=True) + self.assign(self.bitrate_limit, self.nodes.intersection(lambda x: x.bitrate_limit)) + self.require(self.bitrate_limit != RangeExpr.EMPTY, "no compatible bitrate between devices") + class CanDiffBridge(PortBridge): def __init__(self) -> None: @@ -110,7 +113,9 @@ class CanDiffPort(Port[CanDiffLink]): link_type = CanDiffLink bridge_type = CanDiffBridge - def __init__(self) -> None: + def __init__(self, *, bitrate_limit: RangeLike = RangeExpr.ALL) -> None: super().__init__() self.canh = self.Port(Passive()) self.canl = self.Port(Passive()) + + self.bitrate_limit = self.Parameter(RangeExpr(bitrate_limit)) diff --git a/edg/parts/interface/CanTransceiver_Iso1050.py b/edg/parts/interface/CanTransceiver_Iso1050.py index 8c852f8bd..ad1528a1f 100644 --- a/edg/parts/interface/CanTransceiver_Iso1050.py +++ b/edg/parts/interface/CanTransceiver_Iso1050.py @@ -12,6 +12,7 @@ def __init__(self) -> None: self.gnd1 = self.Port(Ground()) self.gnd2 = self.Port(Ground()) + # dominant timeout with maximum successive bits sets minimum rate self.controller = self.Port( CanTransceiverPort( DigitalBidir( @@ -20,11 +21,12 @@ def __init__(self) -> None: current_limits=(-5, 5) * uAmp, input_thresholds=(0.8, 2) * Volt, output_thresholds=(0 * Volt, self.vcc1.link().voltage.lower()), - ) + ), + bitrate_limit=(0.000916, 1) * MHertz, ) ) - self.can = self.Port(CanDiffPort()) + self.can = self.Port(CanDiffPort(bitrate_limit=self.controller.link().bitrate_limit)) @override def contents(self) -> None: diff --git a/edg/parts/interface/CanTransceiver_Sn65hvd230.py b/edg/parts/interface/CanTransceiver_Sn65hvd230.py index 94d13c325..a19eb7855 100644 --- a/edg/parts/interface/CanTransceiver_Sn65hvd230.py +++ b/edg/parts/interface/CanTransceiver_Sn65hvd230.py @@ -18,11 +18,12 @@ def __init__(self) -> None: current_limits=(-8, 8) * mAmp, # driver pin actually -40-48mA input_thresholds=(0.8, 2) * Volt, output_thresholds=(0 * Volt, self.vcc.link().voltage.lower()), - ) + ), + bitrate_limit=(0, 1) * MHertz, ) ) - self.can = self.Port(CanDiffPort()) + self.can = self.Port(CanDiffPort(bitrate_limit=self.controller.link().bitrate_limit)) @override def contents(self) -> None: From 01989acfc595e3cc04e018a4fec35d10f88018d4 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 19:49:53 -0700 Subject: [PATCH 14/23] Create test_can_link.py --- edg/electronics_interfaces/test_can_link.py | 69 +++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 edg/electronics_interfaces/test_can_link.py diff --git a/edg/electronics_interfaces/test_can_link.py b/edg/electronics_interfaces/test_can_link.py new file mode 100644 index 000000000..7cd3e41af --- /dev/null +++ b/edg/electronics_interfaces/test_can_link.py @@ -0,0 +1,69 @@ +import unittest + +from ..core.HdlUserExceptions import UnconnectableError +from ..electronics_model import * +from .DigitalPorts import DigitalBidir +from .CanPort import CanControllerPort, CanTransceiverPort + + +class CanControllerBlock(Block): + def __init__(self, bitrate_limit: RangeLike = RangeExpr.ALL) -> None: + super().__init__() + self.port = self.Port(CanControllerPort(DigitalBidir(), bitrate_limit=bitrate_limit)) + + +class CanTransceiverBlock(Block): + def __init__(self, bitrate_limit: RangeLike = RangeExpr.ALL) -> None: + super().__init__() + self.port = self.Port(CanTransceiverPort(DigitalBidir(), bitrate_limit=bitrate_limit)) + + +class CanLogicTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.controller = self.Block(CanControllerBlock()) + self.transceiver = self.Block(CanTransceiverBlock()) + self.connect(self.controller.port, self.transceiver.port) + + +class CanLogicOverconnectTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.controller = self.Block(CanControllerBlock()) + self.transceiver1 = self.Block(CanTransceiverBlock()) + self.transceiver2 = self.Block(CanTransceiverBlock()) + self.connect(self.controller.port, self.transceiver1.port, self.transceiver2.port) + + +class CanLogicFrequencyTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.controller = self.Block(CanControllerBlock(bitrate_limit=(0, 1) * MHertz)) + self.transceiver = self.Block(CanTransceiverBlock(bitrate_limit=(0.5, 1) * MHertz)) + self.connect(self.controller.port, self.transceiver.port) + self.require(self.controller.port.link().bitrate_limit == (0.5, 1) * MHertz, _unchecked=True) + + +class CanLogicFrequencyInvalidTest(DesignTop): + def __init__(self) -> None: + super().__init__() + # this generally isn't plausible in practice + self.controller = self.Block(CanControllerBlock(bitrate_limit=(0, 0.25) * MHertz)) + self.transceiver = self.Block(CanTransceiverBlock(bitrate_limit=(0.5, 1) * MHertz)) + self.connect(self.controller.port, self.transceiver.port) + + +class CanLogicTestCase(unittest.TestCase): + def test_link(self) -> None: + ScalaCompiler.compile(CanLogicTest) + + def test_overconnect(self) -> None: + with self.assertRaises(UnconnectableError): + ScalaCompiler.compile(CanLogicOverconnectTest) + + def test_frequency(self) -> None: + ScalaCompiler.compile(CanLogicFrequencyTest) + + def test_frequency_invalid(self) -> None: + with self.assertRaises(CompilerCheckError): + ScalaCompiler.compile(CanLogicFrequencyInvalidTest) From 7b2c8fcc8aec22cd56960137616719e889fef7e9 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 19:53:43 -0700 Subject: [PATCH 15/23] Update test_can_link.py --- edg/electronics_interfaces/test_can_link.py | 67 +++++++++++++++++++-- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/edg/electronics_interfaces/test_can_link.py b/edg/electronics_interfaces/test_can_link.py index 7cd3e41af..9dfc039da 100644 --- a/edg/electronics_interfaces/test_can_link.py +++ b/edg/electronics_interfaces/test_can_link.py @@ -3,7 +3,7 @@ from ..core.HdlUserExceptions import UnconnectableError from ..electronics_model import * from .DigitalPorts import DigitalBidir -from .CanPort import CanControllerPort, CanTransceiverPort +from .CanPort import CanControllerPort, CanTransceiverPort, CanDiffPort class CanControllerBlock(Block): @@ -53,17 +53,72 @@ def __init__(self) -> None: self.connect(self.controller.port, self.transceiver.port) -class CanLogicTestCase(unittest.TestCase): - def test_link(self) -> None: +class CanDiffBlock(Block): + def __init__(self, bitrate_limit: RangeLike = RangeExpr.ALL) -> None: + super().__init__() + self.port = self.Port(CanDiffPort(bitrate_limit=bitrate_limit)) + + +class CanDiffTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.node1 = self.Block(CanDiffBlock()) + self.node2 = self.Block(CanDiffBlock()) + self.node3 = self.Block(CanDiffBlock()) + self.connect(self.node1.port, self.node2.port, self.node3.port) + + +class CanBadConnectTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.controller = self.Block(CanControllerBlock()) + self.diff = self.Block(CanDiffBlock()) + self.connect(self.controller.port, self.diff.port) + + +class CanDiffFrequencyTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.node1 = self.Block(CanDiffBlock(bitrate_limit=(0, 1) * MHertz)) + self.node2 = self.Block(CanDiffBlock(bitrate_limit=(0.5, 1) * MHertz)) + self.node3 = self.Block(CanDiffBlock(bitrate_limit=(0.25, 0.5) * MHertz)) + self.connect(self.node1.port, self.node2.port, self.node3.port) + self.require(self.node1.port.link().bitrate_limit == (0.5, 0.5) * MHertz, _unchecked=True) + + +class CanDiffFrequencyInvalidTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.node1 = self.Block(CanDiffBlock(bitrate_limit=(0, 0.25) * MHertz)) + self.node2 = self.Block(CanDiffBlock(bitrate_limit=(0.5, 1) * MHertz)) + self.connect(self.node1.port, self.node2.port) + + +class CanTestCase(unittest.TestCase): + def test_logic_link(self) -> None: ScalaCompiler.compile(CanLogicTest) - def test_overconnect(self) -> None: + def test_logic_overconnect(self) -> None: with self.assertRaises(UnconnectableError): ScalaCompiler.compile(CanLogicOverconnectTest) - def test_frequency(self) -> None: + def test_logic_frequency(self) -> None: ScalaCompiler.compile(CanLogicFrequencyTest) - def test_frequency_invalid(self) -> None: + def test_logic_frequency_invalid(self) -> None: with self.assertRaises(CompilerCheckError): ScalaCompiler.compile(CanLogicFrequencyInvalidTest) + + def test_diff_link(self) -> None: + ScalaCompiler.compile(CanDiffTest) + + def test_bad_connect(self) -> None: + with self.assertRaises(UnconnectableError): + ScalaCompiler.compile(CanBadConnectTest) + + def test_diff_frequency(self) -> None: + ScalaCompiler.compile(CanDiffFrequencyTest) + + def test_diff_frequency_invalid(self) -> None: + with self.assertRaises(CompilerCheckError): + ScalaCompiler.compile(CanDiffFrequencyInvalidTest) From 3d67e6e9c961fed2c1829c8ef74cd02a2f74f752 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 19:56:43 -0700 Subject: [PATCH 16/23] CAN nested tests --- edg/electronics_interfaces/CanPort.py | 4 +++- edg/electronics_interfaces/test_can_link.py | 25 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/edg/electronics_interfaces/CanPort.py b/edg/electronics_interfaces/CanPort.py index 437a3e3b5..dc523e521 100644 --- a/edg/electronics_interfaces/CanPort.py +++ b/edg/electronics_interfaces/CanPort.py @@ -94,7 +94,7 @@ def __init__(self) -> None: super().__init__() self.outer_port = self.Port(CanDiffPort.empty()) - self.inner_link = self.Port(CanDiffPort.empty()) + self.inner_link = self.Port(CanDiffPort()) @override def contents(self) -> None: @@ -108,6 +108,8 @@ def contents(self) -> None: self.connect(self.outer_port.canl, self.canl_bridge.outer_port) self.connect(self.canl_bridge.inner_link, self.inner_link.canl) + self.assign(self.outer_port.bitrate_limit, self.inner_link.link().bitrate_limit) + class CanDiffPort(Port[CanDiffLink]): link_type = CanDiffLink diff --git a/edg/electronics_interfaces/test_can_link.py b/edg/electronics_interfaces/test_can_link.py index 9dfc039da..4c5a8be35 100644 --- a/edg/electronics_interfaces/test_can_link.py +++ b/edg/electronics_interfaces/test_can_link.py @@ -59,6 +59,17 @@ def __init__(self, bitrate_limit: RangeLike = RangeExpr.ALL) -> None: self.port = self.Port(CanDiffPort(bitrate_limit=bitrate_limit)) +class CanNestedDiffBlock(Block): + def __init__( + self, node1_bitrate_limit: RangeLike = RangeExpr.ALL, node2_bitrate_limit: RangeLike = RangeExpr.ALL + ) -> None: + super().__init__() + self.port = self.Port(CanDiffPort.empty()) + self.node1 = self.Block(CanDiffBlock(node1_bitrate_limit)) + self.node2 = self.Block(CanDiffBlock(node2_bitrate_limit)) + self.connect(self.node1.port, self.node2.port, self.port) + + class CanDiffTest(DesignTop): def __init__(self) -> None: super().__init__() @@ -94,6 +105,17 @@ def __init__(self) -> None: self.connect(self.node1.port, self.node2.port) +class CanDiffNestedFrequencyTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.node1 = self.Block(CanDiffBlock(bitrate_limit=(0, 1) * MHertz)) + self.nodes = self.Block( + CanNestedDiffBlock(node1_bitrate_limit=(0.5, 1) * MHertz, node2_bitrate_limit=(0.25, 0.5) * MHertz) + ) + self.connect(self.node1.port, self.nodes.port) + self.require(self.node1.port.link().bitrate_limit == (0.5, 0.5) * MHertz, _unchecked=True) + + class CanTestCase(unittest.TestCase): def test_logic_link(self) -> None: ScalaCompiler.compile(CanLogicTest) @@ -122,3 +144,6 @@ def test_diff_frequency(self) -> None: def test_diff_frequency_invalid(self) -> None: with self.assertRaises(CompilerCheckError): ScalaCompiler.compile(CanDiffFrequencyInvalidTest) + + def test_diff_nested_frequency(self) -> None: + ScalaCompiler.compile(CanDiffNestedFrequencyTest) From 91ff96f7ad4b62916c39b1794f7f360dbface8a0 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 19:59:56 -0700 Subject: [PATCH 17/23] i2c frequency through bridges --- edg/electronics_interfaces/I2cPort.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/edg/electronics_interfaces/I2cPort.py b/edg/electronics_interfaces/I2cPort.py index 8b8f6dac8..bd0a29265 100644 --- a/edg/electronics_interfaces/I2cPort.py +++ b/edg/electronics_interfaces/I2cPort.py @@ -83,6 +83,7 @@ def contents(self) -> None: DigitalBidir.empty(), has_pullup=self.inner_link.link().has_pull, addresses=self.inner_link.link().addresses, + frequency_limit=self.inner_link.link().frequency_limit, ) ) @@ -129,7 +130,13 @@ def __init__(self) -> None: def contents(self) -> None: super().contents() - self.outer_port.init_from(I2cTarget(DigitalBidir.empty(), addresses=self.inner_link.link().addresses)) + self.outer_port.init_from( + I2cTarget( + DigitalBidir.empty(), + addresses=self.inner_link.link().addresses, + frequency_limit=self.inner_link.link().frequency_limit, + ) + ) self.scl_bridge = self.Block(DigitalSinkBridge()) self.connect(self.outer_port.scl, self.scl_bridge.outer_port) From 3459596581e652920405de3d29948d6e920946a6 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 20:06:54 -0700 Subject: [PATCH 18/23] i2c nesting tests --- edg/electronics_interfaces/test_i2c_link.py | 47 +++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/edg/electronics_interfaces/test_i2c_link.py b/edg/electronics_interfaces/test_i2c_link.py index 75759adc3..179169b9f 100644 --- a/edg/electronics_interfaces/test_i2c_link.py +++ b/edg/electronics_interfaces/test_i2c_link.py @@ -64,7 +64,22 @@ def __init__(self) -> None: self.link = self.connect(self.port, self.controller.port, self.pull.port, self.device1.port, self.device2.port) -class I2cNestedTest(DesignTop): +class I2cNestedBlock(Block): + def __init__( + self, + dev1_addr: IntLike = 0, + dev1_freq_limit: RangeLike = RangeExpr.ALL, + dev2_addr: IntLike = 1, + dev2_freq_limit: RangeLike = RangeExpr.ALL, + ) -> None: + super().__init__() + self.port = self.Port(I2cTarget.empty()) + self.device1 = self.Block(I2cTargetBlock(dev1_addr, frequency_limit=dev1_freq_limit)) + self.device2 = self.Block(I2cTargetBlock(dev2_addr, frequency_limit=dev2_freq_limit)) + self.link = self.connect(self.port, self.device1.port, self.device2.port) + + +class I2cControllerNestedTest(DesignTop): def __init__(self) -> None: super().__init__() self.controller = self.Block(I2cControllerNestedBlock()) @@ -83,6 +98,25 @@ def __init__(self) -> None: self.link = self.connect(self.controller.port, self.pull.port, self.device.port) +class I2cNestedTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.controller = self.Block(I2cControllerBlock()) + self.pull = self.Block(I2cPullupBlock()) + self.devices = self.Block(I2cNestedBlock(dev1_addr=1, dev2_addr=2)) + self.link = self.connect(self.controller.port, self.pull.port, self.devices.port) + self.require(self.controller.port.link().addresses == [1, 2], _unchecked=True) + + +class I2cNestedConflictTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.controller = self.Block(I2cControllerBlock()) + self.pull = self.Block(I2cPullupBlock()) + self.devices = self.Block(I2cNestedBlock(dev1_addr=1, dev2_addr=1)) + self.link = self.connect(self.controller.port, self.pull.port, self.devices.port) + + class I2cFrequencyTest(DesignTop): def __init__(self) -> None: super().__init__() @@ -119,13 +153,20 @@ def test_i2c_conflict(self) -> None: with self.assertRaises(CompilerCheckError): ScalaCompiler.compile(I2cConflictTest) - def test_i2c_nested(self) -> None: - ScalaCompiler.compile(I2cNestedTest) + def test_i2c_nested_controller(self) -> None: + ScalaCompiler.compile(I2cControllerNestedTest) def test_i2c_nested_extrapull(self) -> None: with self.assertRaises(CompilerCheckError): ScalaCompiler.compile(I2cNestedExtraPullTest) + def test_i2c_nested(self) -> None: + ScalaCompiler.compile(I2cNestedTest) + + def test_i2c_nested_conflict(self) -> None: + with self.assertRaises(CompilerCheckError): + ScalaCompiler.compile(I2cNestedConflictTest) + def test_i2c_frequency(self) -> None: ScalaCompiler.compile(I2cFrequencyTest) From 57236b259d6305344beebe83b991277fd91ae6a9 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 20:08:09 -0700 Subject: [PATCH 19/23] Update test_i2c_link.py --- edg/electronics_interfaces/test_i2c_link.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/edg/electronics_interfaces/test_i2c_link.py b/edg/electronics_interfaces/test_i2c_link.py index 179169b9f..7e6f7bbf1 100644 --- a/edg/electronics_interfaces/test_i2c_link.py +++ b/edg/electronics_interfaces/test_i2c_link.py @@ -141,6 +141,16 @@ def __init__(self) -> None: self.connect(self.controller.port, self.pull.port, self.device.port) +class I2cFrequencyNestedTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.controller = self.Block(I2cControllerBlock(frequency_limit=(0, 400) * kHertz)) + self.pull = self.Block(I2cPullupBlock()) + self.devices = self.Block(I2cNestedBlock(dev1_freq_limit=(0, 100) * kHertz, dev2_freq_limit=(10, 400) * kHertz)) + self.connect(self.controller.port, self.pull.port, self.devices.port) + self.require(self.controller.port.link().frequency_limit == (10, 100) * kHertz, _unchecked=True) + + class I2cTestCase(unittest.TestCase): def test_i2c(self) -> None: ScalaCompiler.compile(I2cTest) @@ -173,3 +183,6 @@ def test_i2c_frequency(self) -> None: def test_i2c_frequency_invalid(self) -> None: with self.assertRaises(CompilerCheckError): ScalaCompiler.compile(I2cFrequencyInvalidTest) + + def test_i2c_frequency_nested(self) -> None: + ScalaCompiler.compile(I2cFrequencyNestedTest) From 8ae5e20e9526f962654029f93a08a5133c08885f Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 22:57:00 -0700 Subject: [PATCH 20/23] Update test_deskcontroller.py --- examples/test_deskcontroller.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/test_deskcontroller.py b/examples/test_deskcontroller.py index 328638817..87e544b6e 100644 --- a/examples/test_deskcontroller.py +++ b/examples/test_deskcontroller.py @@ -16,7 +16,7 @@ def __init__(self) -> None: self.pwr = self.Port( VoltageSource(voltage=5 * Volt(tol=0), current_limits=(0, 300) * mAmp) ) # reportedly drives at least 300mA - self.uart = self.Port(UartPort(DigitalBidir.from_supply(self.gnd, self.pwr))) + self.uart = self.Port(UartPort(DigitalBidir.from_supply(self.gnd, self.pwr), baud_limit=9600 * Hertz)) self.conn = self.Block(PassiveConnector(length=6)).connected( { @@ -40,9 +40,10 @@ def __init__( ) -> None: super().__init__() self.lv_pwr = self.Port(VoltageSink.empty()) - self.lv_uart = self.Port(UartPort.empty()) + self.lv_uart = self.Port(UartPort(DigitalBidir.empty(), baud_limit=RangeExpr.ALL)) self.hv_pwr = self.Port(VoltageSink.empty()) - self.hv_uart = self.Port(UartPort.empty()) + # arbitrarily treated as outer for dataflow purposes + self.hv_uart = self.Port(UartPort(DigitalBidir.empty(), baud_limit=self.lv_uart.link().baud_limit)) self.hv_tx_shift = self.Block(BidirectionalLevelShifter(lv_res=lv_res, hv_res=hv_res, src_hint="hv")) self.lv_tx_shift = self.Block(BidirectionalLevelShifter(lv_res=lv_res, hv_res=hv_res, src_hint="lv")) From 2ce56ee53ab21f739bbf2154b2c6bd713620b110 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 23:11:33 -0700 Subject: [PATCH 21/23] fix device models --- edg/electronics_interfaces/MergedBlocks.py | 6 ++++-- edg/parts/display/oled/Er_Oled_096_1c.py | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/edg/electronics_interfaces/MergedBlocks.py b/edg/electronics_interfaces/MergedBlocks.py index a621694bd..5c8aee1e0 100644 --- a/edg/electronics_interfaces/MergedBlocks.py +++ b/edg/electronics_interfaces/MergedBlocks.py @@ -3,7 +3,7 @@ from ..electronics_model import * from .VoltagePorts import VoltageSource, VoltageSink, VoltageLink -from .DigitalPorts import DigitalSource, DigitalSink, DigitalLink +from .DigitalPorts import DigitalSource, DigitalSink, DigitalLink, DigitalBidir from .AnalogPort import AnalogSource, AnalogSink, AnalogLink from .SpiPort import SpiController, SpiPeripheral, SpiLink @@ -125,11 +125,13 @@ def generate(self) -> None: self.ins.defined() for in_request in self.get(self.ins.requested()): - in_port = self.ins.append_elt(SpiPeripheral.empty(), in_request) + in_port = self.ins.append_elt(SpiPeripheral(DigitalBidir.empty()), in_request) self.connect(miso_net, in_port.miso) self.connect(self.sck_merge.ins.request(in_request), in_port.sck) self.connect(self.mosi_merge.ins.request(in_request), in_port.mosi) + self.assign(self.out.frequency_limit, self.ins.hull(lambda x: x.link().frequency_limit)) + def connected_from(self, *ins: Port[SpiLink]) -> "MergedSpiController": for in_port in ins: builder.block().connect(in_port, self.ins.request()) diff --git a/edg/parts/display/oled/Er_Oled_096_1c.py b/edg/parts/display/oled/Er_Oled_096_1c.py index e490ad4ca..5004066a5 100644 --- a/edg/parts/display/oled/Er_Oled_096_1c.py +++ b/edg/parts/display/oled/Er_Oled_096_1c.py @@ -115,10 +115,12 @@ def __init__(self) -> None: self.gnd = self.Export(self.device.vss, [Common]) self.vcc = self.Export(self.device.vcc) # device power self.pwr = self.Export(self.device.vdd) # logic power - self.spi = self.Port(SpiSlave.empty(), optional=True) + self.spi = self.Port(SpiPeripheral(DigitalBidir.empty(), frequency_limit=(0, 10) * MHertz), optional=True) self.cs = self.Port(DigitalSink.empty(), optional=True) self.dc = self.Port(DigitalSink.empty(), optional=True) - self.i2c = self.Port(I2cSlave.empty(), optional=True) + self.i2c = self.Port( + I2cTarget(DigitalBidir.empty(), addresses=[0x3C], frequency_limit=(0, 400) * kHertz), optional=True + ) self.generator_param(self.spi.is_connected(), self.dc.is_connected(), self.i2c.is_connected()) @override @@ -171,7 +173,6 @@ def generate(self) -> None: self.connect(self.device.dc, gnd_digital) # addr, TODO support I2C addr self.connect(self.device.cs, gnd_digital) self.require(~self.spi.is_connected() & ~self.cs.is_connected() & ~self.dc.is_connected()) - self.assign(self.i2c.addresses, [0x3C]) elif self.get(self.spi.is_connected()): self.connect(self.device.bs1, gnd_digital) self.connect(self.spi.sck, self.device.d0) From 03c020bf48d401334a3d4fd241df158f37d49c87 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 23:13:20 -0700 Subject: [PATCH 22/23] Update test_deskcontroller.py --- examples/test_deskcontroller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/test_deskcontroller.py b/examples/test_deskcontroller.py index 87e544b6e..032101ccd 100644 --- a/examples/test_deskcontroller.py +++ b/examples/test_deskcontroller.py @@ -16,7 +16,7 @@ def __init__(self) -> None: self.pwr = self.Port( VoltageSource(voltage=5 * Volt(tol=0), current_limits=(0, 300) * mAmp) ) # reportedly drives at least 300mA - self.uart = self.Port(UartPort(DigitalBidir.from_supply(self.gnd, self.pwr), baud_limit=9600 * Hertz)) + self.uart = self.Port(UartPort(DigitalBidir.from_supply(self.gnd, self.pwr), baud_limit=9600 * Hertz(tol=0))) self.conn = self.Block(PassiveConnector(length=6)).connected( { From dd101a72224a04eae0457e361bf1ea56e396f033 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 29 Aug 2026 23:23:25 -0700 Subject: [PATCH 23/23] fix from reviews --- edg/electronics_interfaces/UartPort.py | 2 +- edg/electronics_interfaces/test_i2s_link.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/edg/electronics_interfaces/UartPort.py b/edg/electronics_interfaces/UartPort.py index 5e5d57839..2af9efe8c 100644 --- a/edg/electronics_interfaces/UartPort.py +++ b/edg/electronics_interfaces/UartPort.py @@ -21,7 +21,7 @@ def contents(self) -> None: self.b_tx = self.connect(self.b.tx, self.a.rx) self.assign(self.baud_limit, self.a.baud_limit.intersect(self.b.baud_limit)) - self.require(self.baud_limit != RangeExpr.EMPTY, "no compatible frequency between devices") + self.require(self.baud_limit != RangeExpr.EMPTY, "no compatible baud rate between devices") class UartPort(Port[UartLink]): diff --git a/edg/electronics_interfaces/test_i2s_link.py b/edg/electronics_interfaces/test_i2s_link.py index 8d9690b2d..19723302e 100644 --- a/edg/electronics_interfaces/test_i2s_link.py +++ b/edg/electronics_interfaces/test_i2s_link.py @@ -66,8 +66,8 @@ def __init__(self) -> None: class I2sBitsInvalidTest(DesignTop): def __init__(self) -> None: super().__init__() - self.controller = self.Block(I2sControllerBlock(sample_rate_limit=(0, 8) * Bit)) - self.target = self.Block(I2sTargetReceiverBlock(sample_rate_limit=(16, 16) * Bit)) + self.controller = self.Block(I2sControllerBlock(bit_limit=(0, 8) * Bit)) + self.target = self.Block(I2sTargetReceiverBlock(bit_limit=(16, 16) * Bit)) self.connect(self.controller.port, self.target.port)