From dfc70d183034dfa5f3efc192ce936bd8abc5e58f Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 18 Jul 2026 01:54:09 -0700 Subject: [PATCH 1/8] Update reference_library.md --- reference_library.md | 52 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/reference_library.md b/reference_library.md index ff018df3f..1e22f9590 100644 --- a/reference_library.md +++ b/reference_library.md @@ -6,11 +6,63 @@ New? Consider reading through the [getting started guide](getting-started.md), i Also see the [top-level board design reference](reference.md). +This document describes primitives and design patterns, also check out the many examples in the parts libraries. ## Dragons be Ahead The library design experience is much less polished and more intricate than the top-level board design experience. + +## Blocks + +### Port Interfaces + +Modeling conventions + +### Parameters + +### Footprints + +## Schematic-Defined Blocks + +## Generators + +## Abstract Blocks + +### Mixins + +### Parts Tables and Passives + +## Design Patterns + +### _Device Footprint and Subcircuit + +### Passive and Typed Layers + +### PassiveConnector + + + +## Custom Ports and Links + +### Links + +Design patterns: aggregation and assertion + +### Bridges + +Design pattern: propagation + +### Adapters + + + + + + + + + ## Under Construction Parts of this reference are outdated. From ed4f9891bf917dfca72c473050e12e4000d6a787 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 18 Jul 2026 17:15:34 -0700 Subject: [PATCH 2/8] Update reference_library.md --- reference_library.md | 144 ++++++++++++++++++++++++++----------------- 1 file changed, 86 insertions(+), 58 deletions(-) diff --git a/reference_library.md b/reference_library.md index 1e22f9590..d814ae0d6 100644 --- a/reference_library.md +++ b/reference_library.md @@ -15,12 +15,88 @@ The library design experience is much less polished and more intricate than the ## Blocks +`Block`s are a subcircuit that contain boundary ports, optional parameters, internal sub-blocks, and connections between them. + +```python +class IndicatorLed(Block): + def __init__(self, current_draw: RangeLike = (1, 10) * mAmp) -> None: + super().__init__() + self.current_draw = self.ArgParameter(current_draw) + self.actual_current_draw = self.Parameter(RangeExpr()) + self.gnd = self.Port(Ground()) + self.signal = self.Port(DigitalSink(...)) + + def contents() -> None: + super().contents() + self.led = self.Block(Led(...)) + self.res = self.Block(Resistor(resistance=self.signal.link().voltage / self.current_draw)) + self.assign(self.actual_current_draw, self.signal.voltage / self.res.resistance) + self.connect(self.signal.net, self.led.a) + self.connect(self.res.a, self.led.k) + self.connect(self.gnd.net, self.res.b) +``` + ### Port Interfaces -Modeling conventions +- Boundary ports must be defined in `__init__`. +- Ports are connected with `self.connect(...)`; these may be part of a connect: + - Boundary ports (as a unit) or their bundle inner ports (separately). + - Boundary ports of sub-blocks (as a unit only). + +>
+> Common Port Types +> +> - `Passive`: a single netlist pin without electrical modeling, a building block for ports with electrical modeling on top +> +> Single Wire Port that wrap (has-a) `Passive`: +> - `VoltageSource`, `VoltageSink`: models voltage and current, and their limits. +> - `DigitalSource`, `DigitalSink`, `DigitalBidir`: models voltage, voltage thresholds, current, and their limits. +> Checks for multiple-driver conflicts, with multiple `DigitalBidir` drivers are allowed. +> - `AnalogSource`, `AnalogSink`: models voltage, current, and input/output impedance, and their limits. +> Arbitrary requirement that the source has 1/10 the parallel impedance of sinks. +> +> Bundle Ports +> - `I2cController`, `I2cTarget`: models I2C as two digital ports and address. +> Checks for address conflicts. +> - `SpiController`, `SpiPeripheral`: models the shared SPI lines (excluding CS) as three digital ports. +> - `UartPort`: models UART TX/RX as two digital ports. +> Connections generate a crossover connection. +> - `UsbHostPort`, `UsbDevicePort`: USB D+/D- ports +> - `CanControllerPort`, `CanTransceiverPort`: controller-side and transceiver-side RXD and TXD ports. +> - `CanDiffPort`: differential-side CAN (CANH, CANL) ports +> +> See their class docstring and constructor for details. +> +> See Design Patterns below for adapting between (some) port types. +> +>
### Parameters +Parameters are variables that can be passed into and through blocks. + +- Parameters are defined purely symbolically and concrete values are not available to (non-generator) `Block`s. +- Parameters are restricted to a set of types supported by the compiler and the operations on them: + - `BoolExpr` / `BoolLike`: boolean (true / false) variable + - `IntExpr` / `IntLike`: numeric variable + - `FloatExpr` / `FloatLike`: numeric variable + - `RangeExpr` / `RangeLike`: range (interval) variable + - `StringExpr` / `StringLike`: string (text) variable + - See the `xExpr` class definition for supported operations. + - See the generator section for how to use arbitrary Python code for calculations. +- Parameters must be defined in `__init__`, either with: + - `self.ArgParameter(arg)`, to wrap `__init__` argument `arg`. + Use the `xLike` type for `__init__` arguments, which also allow the literal Python value. + - `self.Parameter(ParameterType())` (for a parameter without a value, to be assigned in `contents()`). + Use the `xExpr` type in `Parameter(...)` to declare a parameter without a value, to be assigned in `contents()`. +- `RangeExpr` types are used to represent a device tolerance or toleranced specification + - Most operations between `RangeExpr` types are tolerance-expanding (computes the worst-case range of the inputs) + - `a = c.shrink_multiply(b)` returns `a` such that tolerance-expanding `a * b` is equal to `c`. + Example: for `v = i * r`, to solve for allowable (target) `r` given requirement `i` and contributing tolerance `v`, use `r = (1/i).shrink_multiply(v)` +- Access parameters on the link of ports using `port.link().link_param`. + Links contain the aggregated parameter of all connected ports, for example the voltage. + + ### Footprints ## Schematic-Defined Blocks @@ -33,11 +109,17 @@ Modeling conventions ### Parts Tables and Passives -## Design Patterns +## Design Patterns and Conventions + +### Link Circular Dependencies +Be cognizant of circular dependencies on links, e.g. something depending on a link's current to calculate its voltage will play badly with another block on the link that calculates its voltage based on the link's current. + +### Target and Actual Parameters ### _Device Footprint and Subcircuit ### Passive and Typed Layers +use passive .net and adapt_to ### PassiveConnector @@ -69,14 +151,7 @@ Parts of this reference are outdated. ## Parameters -- `BoolExpr`: boolean (true / false) variable - - Supports standard boolean operations -- `FloatExpr`: numeric variable - - Supports standard numeric operations -- `RangeExpr`: range (interval) variable - - Supports standard numeric operations (multiplication and division are undefined), some set operations (`.within(other)`, `.contains(other)`, `.intersect(other)`), and get operations (`.lower()`, `.upper()`) -- `StringExpr`: string (text) variable - - Supports equality operations only + ## Blocks Blocks represent a subcircuit (or hierarchical schematic sheet), and consist of boundary ports and internal subblocks and connections (links) between ports on those subblocks. @@ -139,54 +214,7 @@ These can be called inside `contents()`: - Pinning argument format: `{'pin_name': self.port, ...}`: associates footprint pins with ports or subports - Optional arguments: `mfr='Manufacturer A', part='Part Number', value='1kOhm', datasheet='www.example.net'` - -## Port and Link Libraries - -### Single-wire Ports -- `VoltageLink`: voltage rail - - `VoltageSource(voltage, current_limits)`: voltage source - - `VoltageSink(voltage_limits, current_draw)`: voltage sink (power input) -- `DigitalLink`: low-speed (up to ~100 MHz) digital signals, modeling voltage limits and input / output thresholds - - `DigitalSource(voltage, current_limits, output_thresholds)`: digital output-only pin - - `output_thresholds` is the range of (maximum low output, minimum high output) - - `DigitalSink(voltage_limits, current_draw, input_thresholds)`: digital input-only pin - - `input_thresholds` is the range of (maximum low input, minimum high input) - - `DigitalBidir(...)`: digital bidirectional (eg, GPIO) pin - - Has all arguments of `DigitalSource` and `DigitalSink` -- `AnalogLink`: analog signal that models input and output impedance - - `AnalogSource(voltage, current_limits, impedance)`: analog output - - `AnalogSink(voltage_limits, current_draw, impedance)`: analog signal input -- `PassiveLink`: connected copper that contains no additional data - - `Passive`: single wire port that contains no additional data, but can be adapted to other types (eg, with `.as_voltage_source(...)`). - Useful for low-level elements (eg, resistors) that can be used in several ways in higher-level constructs (eg, pull-up resistor for digital applications). - -### Bundle Ports -- `I2cLink`: I2C net, consisting of `.scl` and `.sda` Digital sub-ports. - - `I2CMaster(model)`: I2C master port - - `model`: DigitalBidir model for both SCL and SDA sub-ports - - `I2CSlave(model)`: I2C slave port -- `SpiLink`: SPI net, consisting of `.sck`, `.miso`, and `.mosi` Digital sub-ports. CS must be handled separately. - - `SpiMaster(model, frequency)`: SPI master port - - `frequency`: range of allowable frequencies, generally including zero - - `SpiSlave(model, frequency)`: SPI slave port -- `UartLink`: UART net, consisting of `.tx` and `.rx` Digital sub-ports in a crossover point-to-point connection. - - `UartPort(model)`: UART port -- `UsbLink`: USB data net, consisting of `.dp` (D+) and `.dm` (D-) Digital sub-ports in a point-to-point connection. - - `UsbHostPort`: USB host - - `UsbDevicePort`: USB device - - `UsbPassivePort`: other components (eg, TVS diode) on the USB line -- `CanLogicLink`: CAN logic-level (TXD/RXD) net, consisting of `.txd` and `.rxd` Digital sub-ports in a point-to-point connection. - - `CanControllerPort(model)`: CAN controller-side port - - `CanTransceiverPort(model)`: CAN transceiver-side port -- `CanDiffLink`: CAN differential (CANH/CANL) net, consisting of `.canh` and `.canl` Digital sub-ports in a bus connection. - - `CanDiffPort(model)`: CAN node port -- `SwdLink`: SWD programming net, consisting of `.swdio`, `.swclk`, `.swo`, and `.reset` Digital sub-ports in a point-to-point connection. - - `SwdHostPort(model)`: SWD host-side (programmer) port - - `SwdTargetPort(model)`: SWD target-side (DUT) port -- `CrystalLink`: crystal net, consisting of `.xi` and `.xo` sub-ports in a point-to-point connection. - - `CrystalDriver(frequency_limits, voltage_out)`: driver-side port - - `CrystalPort(frequency)`: crystal-side port, indicating the frequency of the crystal - + ## Block Libraries The IDE's library tab provides a categorized list of available library blocks. From 1a9bf6714d083f93114932f923384b0d2e7430c5 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 18 Jul 2026 17:42:49 -0700 Subject: [PATCH 3/8] Update reference_library.md --- reference_library.md | 69 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/reference_library.md b/reference_library.md index d814ae0d6..5d6f28152 100644 --- a/reference_library.md +++ b/reference_library.md @@ -6,7 +6,8 @@ New? Consider reading through the [getting started guide](getting-started.md), i Also see the [top-level board design reference](reference.md). -This document describes primitives and design patterns, also check out the many examples in the parts libraries. +This document provides a quick reference and list of common primitives and design patterns. +However, your best resource is going to be all the examples in the parts libraries. ## Dragons be Ahead @@ -23,7 +24,7 @@ class IndicatorLed(Block): super().__init__() self.current_draw = self.ArgParameter(current_draw) self.actual_current_draw = self.Parameter(RangeExpr()) - self.gnd = self.Port(Ground()) + self.gnd = self.Port(Ground(), [Common]) self.signal = self.Port(DigitalSink(...)) def contents() -> None: @@ -38,7 +39,11 @@ class IndicatorLed(Block): ### Port Interfaces -- Boundary ports must be defined in `__init__`. +- Boundary ports are defined with `self.Port(PortType(...)` and must be defined in `__init__`. + - Boundary ports may optionally have `tags=[...]` to support implicit connections. + Common tags are `Power` (v+), `Common` (gnd). + Tags to support `chain` are `Input`, `Output`, and `InOut`. + - Boundary ports may optionally define `optional=True` to indicate that the port may be left unconnected. - Ports are connected with `self.connect(...)`; these may be part of a connect: - Boundary ports (as a unit) or their bundle inner ports (separately). - Boundary ports of sub-blocks (as a unit only). @@ -96,11 +101,69 @@ Parameters are variables that can be passed into and through blocks. - Access parameters on the link of ports using `port.link().link_param`. Links contain the aggregated parameter of all connected ports, for example the voltage. +Quirks: +- `port.link().link_param` will be undefined for an unconnected port. + For optional ports, gate checks with `port.is_connected().else_then(..., ...)`. + This may be fixed eventually, see [#360](https://github.com/BerkeleyHCI/PolymorphicBlocks/issues/360). ### Footprints +The `FootprintBlock` base class is a `Block` that defines at most one footprint. + +```python +class Sk6812Mini_E(FootprintBlock): + def __init__(self) -> None: + super().__init__() + self.gnd = self.Port(Ground()) + self.vdd = self.Port(VoltageSink(...)) + self.din = self.Port(DigitalSink(...)) + self.dout = self.Port(DigitalSource(...)) + + def contents(self) -> None: + self.footprint( + "D", + "edg:LED_SK6812MINI-E", + {"1": self.vdd, "2": self.dout, "3": self.gnd, "4": self.din}, + mfr="Opsco Optoelectronics", + part="SK6812MINI-E", + datasheet="https://cdn-shop.adafruit.com/product-files/4960/4960_SK6812MINI-E_REV02_EN.pdf", + ) +``` + +- `self.footprint(...)` defines the footprint associated with this block and its pinning and takes these arguments: + - `refdes` + - `footprint`: KiCad footprint name. + - `pinning`: dictionary mapping footprint pin numbers to ports. + - Pin numbers must be `str` or `Tuple[str, ...]` (for multi-pin pads). + - Ports must be `Passive` or `HasPassive`. + - Optional metadata `mfr`, `part`, `value`, `datasheet`. + - Optional pick-and-place metadata `pnp_rot`, `pnp_offset` for KiCad footprint to JLC PCBA PnP data. +- While not (yet) forbidden, `FootprintBlock`s should not have inner sub-`Block`s. + ## Schematic-Defined Blocks +`KiCadSchematicBlock` is a `Block` that is defined by a KiCad schematic sheet. + +```python +class MySchematicDefinedBlock(KiCadSchematicBlock): + def __init__(self) -> None: + super().__init__() + self.gnd = self.Port(Ground()) + self.pwr = self.Port(VoltageSink()) + + def contents(self) -> None: + super().contents() + self.import_kicad(self.file_path(f"{self.__class__.__name__}.kicad_sch")) +``` + +- The HDL stub is required to define the boundary ports and parameters. +- + +Guidance: +- Good uses include analog subcircuits where the graphical connectivity is meaningful. +- This can be used to construct both library subcircuits as well a higher-level subcircuits like signal-processing chains using amplifier subcircuits. +- We typically do not use this to implement chip subcircuits, instead preferring a full HDL definition. + ## Generators ## Abstract Blocks From 35d4843e9d4bf3fbe04416ada878658f883dabf0 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 18 Jul 2026 18:23:16 -0700 Subject: [PATCH 4/8] wip --- getting_started_schimport.md | 34 ++----------- reference_library.md | 92 +++++++++++++++++++++++++----------- 2 files changed, 68 insertions(+), 58 deletions(-) diff --git a/getting_started_schimport.md b/getting_started_schimport.md index 7ad0b1626..61056c08e 100644 --- a/getting_started_schimport.md +++ b/getting_started_schimport.md @@ -209,37 +209,9 @@ However, instead of using `auto_adapt`, we can instead define `conversions` on a > ``` -## Reference - -These common symbols can be used in schematic import and map to the following passive-typed HDL blocks: - -| Symbol | HDL Block | Value Parsing | Notes | -|------------------------------------|--------------------|-----------------|----------------------------------| -| Device:C, Device:C_Polarized | Capacitor | e.g. `10uF 10V` | Voltage rating must be specified | -| Device:R | Resistor | e.g. `100` | | -| Device:L | Inductor | | | -| Device:Q_NPN_\*, Device:Q_PNP_\* | Bjt.Npn, Bjt.Pnp | | | -| Device:D | Diode | | | -| Device:D_Zener | ZenerDiode | | | -| Device:L_Ferrite | FerriteBead | | | -| Device:Q_NMOS_\*, Device:Q_PMOS_\* | Fet.NFet, Fet.PFet | | | -| Switch:SW_SPST | Switch | | | - -Notes: -- Blocks are Passive-typed unless otherwise noted. -- If Value Parsing is empty, the Block can only be defined by HDL instantiation or inline HDL. -- All Blocks that can be used in schematic import can be found by searching for all subclasses of `KiCadImportableBlock`. -- In many cases, the `_Small` (like `Device:C_Small`) symbol can also be used. - -These higher-level symbols have typed pins (like VoltageSink, Ground, and AnalogSink) and can be used to make higher-level analog signal chains: - -| Symbol | HDL Block | Notes | -|--------------------------------------|-----------------------|---------------------------------------------------------------------------------------------------------------------------| -| Simulation_SPICE:OPAMP | Opamp | Supports value parsing (with empty value); is the full application circuit for an opamp (including decoupling capacitors) | -| edg_importable:Amplifier | Amplifier | | -| edg_importable:DifferentialAmplifier | DifferentialAmplifier | | -| edg_importable:IntegratorInverting | IntegratorInverting | | -| edg_importable:OpampCurrentSensor | OpampCurrentSensor | | +## References + +See the [library construction reference](reference_library.md) for a list of KiCad symbols. ## Defining Library Parts diff --git a/reference_library.md b/reference_library.md index 5d6f28152..9aa1d480f 100644 --- a/reference_library.md +++ b/reference_library.md @@ -37,6 +37,8 @@ class IndicatorLed(Block): self.connect(self.gnd.net, self.res.b) ``` +- The `Block` API for top-level board design is also available in subcircuit `Block`s. + ### Port Interfaces - Boundary ports are defined with `self.Port(PortType(...)` and must be defined in `__init__`. @@ -44,6 +46,7 @@ class IndicatorLed(Block): Common tags are `Power` (v+), `Common` (gnd). Tags to support `chain` are `Input`, `Output`, and `InOut`. - Boundary ports may optionally define `optional=True` to indicate that the port may be left unconnected. + - Use `PortType.empty()` to define a port without modeling, where its modeling is defined by the inner port it is connected to. - Ports are connected with `self.connect(...)`; these may be part of a connect: - Boundary ports (as a unit) or their bundle inner ports (separately). - Boundary ports of sub-blocks (as a unit only). @@ -149,18 +152,75 @@ class MySchematicDefinedBlock(KiCadSchematicBlock): def __init__(self) -> None: super().__init__() self.gnd = self.Port(Ground()) - self.pwr = self.Port(VoltageSink()) - + self.pwr = self.Port(VoltageSink(...)) + def contents(self) -> None: super().contents() - self.import_kicad(self.file_path(f"{self.__class__.__name__}.kicad_sch")) + self.import_kicad( + self.file_path(f"{self.__class__.__name__}.kicad_sch"), + conversions={'pwr': VoltageSink(...), 'gnd': Ground()}, + auto_adapt=True + ) ``` - The HDL stub is required to define the boundary ports and parameters. -- +- Graphical schematic components map as follows: + - Labels, including symbols like GND and VCC, connect internally + - Hierarchical labels connect to the boundary ports + - True global labels (that connect design-wide) are not supported) + - Components map to `Block`s, with rhe refdes mapping to the `Block` name. + - Wires map to `connect`s. + - Pins must be connected at a wire end or bend. +- Components can be defined as: + - Using a `KiCadInstantiableBlock` (which defines the symbol to port mapping), and the symbol has no footprint: + - **Value parsing**: some blocks can parse the symbol value to parameters, see the table below. + - **HDL instantiation**: the block is instantiated in HDL and the symbol refdes matches the HDL `Block` name and has no value. + - **Inline HDL**: the symbol value starts with a `#` and contains Python code to instantiate the `Block`. + Multi-line values supported using the `Value2`, `Value3`, ... fields. + - **Blackboxing**, where the symbol has a footprint specified and is created as a `Block` with all `Passive` ports. +- Port connections are direct and types of connected pins must be compatible. + - Use `conversions` to optionally specify a electrically typed `Port` model for a `Passive` `Port`. + - Use `auto_adapt` to automatically insert ideal adapters from `Passive` `Port`s to the HDL-defined electrically typed boundary ports. + +>
+> Common `KiCadInstantiableBlock`s +> +> With Passive-typed Ports, typically for constructing basic (sub)circuits: +> +> | Symbol | HDL Block | Value Parsing | +> |------------------------------------|--------------------|------------------| +> | Device:C, Device:C_Polarized | Capacitor | e.g. `10uF 10V`* | +> | Device:R | Resistor | e.g. `100` | +> | Device:L | Inductor | | +> | Device:Q_NPN_\*, Device:Q_PNP_\* | Bjt.Npn, Bjt.Pnp | | +> | Device:D | Diode | | +> | Device:D_Zener | ZenerDiode | | +> | Device:L_Ferrite | FerriteBead | | +> | Device:Q_NMOS_\*, Device:Q_PMOS_\* | Fet.NFet, Fet.PFet | | +> | Switch:SW_SPST | Switch | | +> * Capacitor voltage is required and is specified as the expected operating voltage, not a rating. +> +> Where value parsing is empty, the Block can only be defined by HDL instantiation or inline HDL. +> +> In many cases, the `_Small` (like `Device:C_Small`) symbol can also be used. +> +> With electrically-typed ports, typically used in higher-level subcircuits like analog signal-processing chains: +> +> | Symbol | HDL Block | +> |--------------------------------------|-----------------------| +> | Simulation_SPICE:OPAMP | Opamp* | +> | edg_importable:Amplifier | Amplifier | +> | edg_importable:DifferentialAmplifier | DifferentialAmplifier | +> | edg_importable:IntegratorInverting | IntegratorInverting | +> | edg_importable:OpampCurrentSensor | OpampCurrentSensor | +> * `Opamp`s include any datasheet-required supporting circuitry like decoupling capacitors. +> +> Search for all subclasses of `KiCadImportableBlock` for a complete listing. +> +>
Guidance: -- Good uses include analog subcircuits where the graphical connectivity is meaningful. +- Good uses include analog subcircuits where the graphical connectivity is complex and meaningful. - This can be used to construct both library subcircuits as well a higher-level subcircuits like signal-processing chains using amplifier subcircuits. - We typically do not use this to implement chip subcircuits, instead preferring a full HDL definition. @@ -208,28 +268,6 @@ Design pattern: propagation -## Under Construction - -Parts of this reference are outdated. - - -## Parameters - - -## Blocks -Blocks represent a subcircuit (or hierarchical schematic sheet), and consist of boundary ports and internal subblocks and connections (links) between ports on those subblocks. - -Skeleton structure: -```python -class MyBlock(Block): - def __init__(self) -> None: - super().__init__() # essential to call the superclass method beforehand to initialize state - # declare ports here, and subblocks whose ports are exported - - def contents(self) -> None: - super().contents() # essential to call the superclass method beforehand to initialize state - # declare subblocks and connections here -``` These are properties of Blocks: - The Block type hierarchy defines allowed refinements From 736f3e6c6b400f7a9d02e1e819ffac7f815a1164 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 18 Jul 2026 18:23:49 -0700 Subject: [PATCH 5/8] Update reference_library.md --- reference_library.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reference_library.md b/reference_library.md index 9aa1d480f..bbececd25 100644 --- a/reference_library.md +++ b/reference_library.md @@ -222,7 +222,7 @@ class MySchematicDefinedBlock(KiCadSchematicBlock): Guidance: - Good uses include analog subcircuits where the graphical connectivity is complex and meaningful. - This can be used to construct both library subcircuits as well a higher-level subcircuits like signal-processing chains using amplifier subcircuits. -- We typically do not use this to implement chip subcircuits, instead preferring a full HDL definition. +- Most chip subcircuits are defined purely in HDL. ## Generators From db423667ae67f2272bb69d41a4775615f9ec0600 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 18 Jul 2026 18:52:24 -0700 Subject: [PATCH 6/8] Update reference_library.md --- reference_library.md | 98 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 93 insertions(+), 5 deletions(-) diff --git a/reference_library.md b/reference_library.md index bbececd25..e37a41d74 100644 --- a/reference_library.md +++ b/reference_library.md @@ -226,24 +226,112 @@ Guidance: ## Generators +`GeneratorBlock` is a `Block` that can retrieve the concrete value of its parameters and run arbitrary Python code to create its inner definition, including structural circuitry and parameters. + +```python +class IndicatorLedArray(GeneratorBlock): + def __init__(self, count: IntLike): + super().__init__() + self.count = self.ArgParameter(count) + self.generator_param(self.count) + + self.gnd = self.Port(Ground.empty(), [Common]) + self.signals = self.Port(Vector(DigitalSink.empty())) + + def generate(self) -> None: + super().generate() + self.led = ElementDict[IndicatorLed]() + for led_i in range(self.get(self.count)): + led = self.led[str(led_i)] = self.Block(IndicatorLed()) + self.connect(...) +``` + +- `self.generator_param(...)` is required to declare parameters that the generator needs. + - Only `ArgParameter`s, `port.is_connected()` and `port.elements()` are allowed. +- `self.get(...)` can be invoked in `generate()` to retrieve the concrete value of a parameter. + +Guidance: +- Generators are necessary for parametric structural circuit construction. +- Prefer using non-generator expression operations for parameter calculations where possible. + Generators should only be used where the expression operations are insufficient. + ## Abstract Blocks +`abstract_block`s are `Blocks` that define an interface that can be implemented by (concrete) subclasses. +They can be instantiated but will error during compilation if not refined. + +```python +@abstract_block +class Led(Block): + def __init__(self): + self.a = self.Port(Passive.empty()) + self.k = self.Port(Passive.empty()) +``` + +- Boundary ports and parameters are defined as usual. +- Ports are defined with `PortType.empty()` to allow subclasses flexibility in modeling. + - Subclasses can specify modeling with `self.port.init_from(PortType(...))`. + Replacing the port object is not allowed. +- A default refinement can be attached to the `abstract_block` with `@abstract_block_default(lambda: DefaultRefinementBlock)`. + - The `lambda` is required to break circular definitions. + - Top-level designers can still override these refinements. + +Guidance: +- Use `abstract_block`s to define a common interface for `Block`s that are drop-in interchangeable. +- You can always refactor to pull out a common `abstract_block` later. + ### Mixins -### Parts Tables and Passives +`BlockInterfaceMixin`s define an interface that can be added to an `abstract_block`. -## Design Patterns and Conventions +```python +# mixin interface definition +class RotaryEncoderSwitch(BlockInterfaceMixin[RotaryEncoder]): + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self.sw = self.Port(Passive.empty(), optional=True) + +# concrete class implementing the mixin +class Ec11eWithSwitch(RotaryEncoderSwitch, RotaryEncoder): + ... +``` -### Link Circular Dependencies -Be cognizant of circular dependencies on links, e.g. something depending on a link's current to calculate its voltage will play badly with another block on the link that calculates its voltage based on the link's current. +- A `Block` definition can inherit multiple mixins (the `Block` implements multiple mixins). +- A `Block` instantiation can use multiple mixins (requiring the concrete `Block` refinement to implement those mixins). +- Mixin interface ports are typically `optional` to allow usage without the mixin. + This may be a generator that provides a default connection in a basic usage configuration. -### Target and Actual Parameters +### Abstract Passives and Parts Tables + +Many common passive and discrete parts (like `Resistor`, `Capacitor`, `Diode`) are `abstract_blocks` and support different (including user-defined) implementations, e.g. to support different vendors and distributors. + +Many of these include a utility subclass for selecting a part from a table (like `TableResistor`, `TableCapacitor`, `TableDiode`). + +These also provide a separate utility subclass that defines KiCad footprint to port mappings that implement the `HasStandardFootprint` interface (like `ResistorStandardFootprint`, `CapacitorStandardFootprint`, `DiodeStandardFootprint`). + +These classes provide utility functions for parts table selectors: +- `PartsTablePart`: interface parameters (part requirement, excluded parts, and more) for part selected from a parts table. +- `PartsTableSelector`: utility code to select a part from a `PartsTable`, implementing `PartsTableSelector`. +- `SelectorFootprint`: interface parameters to allow filtering by footprint. +- `PartsTableFootprintFilter`: utility code to filter a parts table by footprint, implementing `SelectorFootprint`. +- `PartsTableSelectorFootprint`: utility code to construct the footprint, for a `HasStandardFootprint`. + +Look at some example implementations to for the `PartsTable` API and utilities. + +## Design Patterns and Conventions ### _Device Footprint and Subcircuit ### Passive and Typed Layers use passive .net and adapt_to + +### Link Circular Dependencies +Be cognizant of circular dependencies on links, e.g. something depending on a link's current to calculate its voltage will play badly with another block on the link that calculates its voltage based on the link's current. + +### Target and Actual Parameters + + ### PassiveConnector From 2bfe8ab95c8ac460e00a58671a59ac7c70cb8d19 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 18 Jul 2026 19:14:24 -0700 Subject: [PATCH 7/8] Update reference_library.md --- reference_library.md | 185 +++++++++---------------------------------- 1 file changed, 36 insertions(+), 149 deletions(-) diff --git a/reference_library.md b/reference_library.md index e37a41d74..5b90a10ea 100644 --- a/reference_library.md +++ b/reference_library.md @@ -320,177 +320,64 @@ Look at some example implementations to for the `PartsTable` API and utilities. ## Design Patterns and Conventions -### _Device Footprint and Subcircuit - -### Passive and Typed Layers -use passive .net and adapt_to - - -### Link Circular Dependencies -Be cognizant of circular dependencies on links, e.g. something depending on a link's current to calculate its voltage will play badly with another block on the link that calculates its voltage based on the link's current. - -### Target and Actual Parameters - - -### PassiveConnector - - - -## Custom Ports and Links - -### Links - -Design patterns: aggregation and assertion - -### Bridges - -Design pattern: propagation - -### Adapters - - +These are design patterns and conventions used in the included parts library. +### `_Device` Footprint and Subcircuit +The `x_Device` `Block` defines the footprint only, while the application circuit `Block` is the user-friendly name and instantiates `x_Device`. +`x_Device` is not exported in `__init__.py`. +### Passive and Typed Layers +Passive components are `Passive` port typed, and applications of them are separate `Block`s that instantiate the `Passive` typed `Block`s. +This allows different implementation of the same `Passive` device, eg, `PullupResistor`, `PulldownResistor`. +Subcircuits follow this pattern too, with them instantiating the `Passive` typed `Block`, connecting the `Passive` ports internally and to the `Passive` `.net` of the electrically-typed boundary `Port`s. +### Target and Actual Parameters +Most parameters are defined as a target requirement. +The actual expected operating value may be calculated as a readout parameter and is prefixed `actual_`. -These are properties of Blocks: -- The Block type hierarchy defines allowed refinements -- `@abstract_block` decorates a block as abstract, which will error on netlisting - -These can be called inside `__init__(...)`: -- `self.Parameter(ParameterType(optional_value))`: declare parameters, optionally with a value -- `self.Port(PortType(...))`: declare ports - - Optional arguments: `tags=[ImplicitTags], optional=False` - - `self.Export(self.subblock.port)`: export a subblock's port -- `@init_in_parent` decorator is needed if `__init__(...)` takes Parameter arguments and must generate constraints in the parent Block - -These can be called inside `contents()`: -- `self.Block(BlockType(...))`: declare sub-blocks - - Also allowed inside `__init__(...)`, to allow the use of `Export`s -- `self.constrain(...)`: constrain some `BoolExpr` (which can be an inline expression, eg `self.param1 == self.param2`) to be true -- `self.connect(self.subblock1.port, self.subblock2.port, ...)`: connect own ports and/or subblock's ports - - Naming is optional. -- `with self.implicit_connect(...) as imp:`: open an implicit connection scope - - Implicit connection arguments of the form `ImplicitConnect(connect_to_port, [MatchingTags])` - - `imp.Block(BlockType(...))`: similar to `self.Block(...)` but implicitly connects ports with matching tags -- `self.chain(self.Block(BlockType(...)), self.Block(BlockType(...)), ...)`: chain-connect between blocks, in `contents` - - Return of `self.chain` can be unpacked into a tuple of chained blocks, and the chain object (itself). - Naming of the chain object is optional. - - Elements are chained from left (outputs) to right (inputs) - - The first argument to chain may be a port or block with an `InOut` or `Output`-tagged port. - - Middle elements must be a block with an `InOut`-tagged port, or `Input`- and `Output`-tagged ports. - - The last argument to chain may be a port or block with an `InOut` or `Input`-tagged port. -- Assign names to components by assigning the object to a `self` variable: - - `self.subblock_name = self.Block(BlockType(...))` - - `(self.subblock_name1, self.subblock_name2, ...), self.chain_name = self.chain(...)` - -### Generators -Generators allow some Python code to run that has access to the solved values of some parameters. -TODO - write this section pending generator refactoring, in the meantime see the port array section of the getting started tutorial. - - -### Footprint -FootprintBlock is a block that is associated with a PCB footprint. - -All primitives that can be called inside `Block`'s `__init__(...)` can be called inside `__init__(...)` - -These can be called inside `contents()`: -- `self.footprint(refdes='R', footprint='Resistor_SMD:R_0603_1608Metric', pinning={...})`: associates a footprint with this block - - Pinning argument format: `{'pin_name': self.port, ...}`: associates footprint pins with ports or subports - - Optional arguments: `mfr='Manufacturer A', part='Part Number', value='1kOhm', datasheet='www.example.net'` - - +### PassiveConnector -## Block Libraries -The IDE's library tab provides a categorized list of available library blocks. -Many blocks also have a short descriptive docstring. +Devices that use a standard connector (like FPCs) should instantiate the appropriate `PassiveConnector` subclass to allow the system designer to make choices for the concrete connector. +`PassiveConnector` supports parameterized pin counts. +### Circular Dependencies -## Advanced Core Primitives -These are core primitives needed to model new ports and links +There is currently no structural prevention of circular parameter dependencies. -### Ports -Ports can have parameters and may be connected to each other via a Link. +This may happen when a `Block` has a parameter that is calculated from a `Port`'s link, and the `Port`'s link is calculated from the `Block`'s parameter. +For example, the LED calculates current from the link's voltage, while a source block that calculates voltage from the link's current would be unsolvable. -Skeleton structure: -```python -class MyPort(Port[MyPortLinkType]): - link_type = MyPortLinkType # required - bridge_type = MyPortBridgeType # optional, if a bridge is needed - - def __init__(self) -> None: - super().__init__() # essential to call the superclass method beforehand to initialize state - # declare elements like parameters here -``` +We typically resolve this by using the user-specified range for one of the parameters and accepting that it will be a wider range than the actual operating range. -These are properties of Ports: -- The Port type hierarchy currently is not used. However, inheritance may still be useful for code re-use / de-duplication. -These can be called inside `__init__(...)`: -- `__init__(...)` may take arguments, and no decorators (as with Block) are needed -- `self.Parameter(ParameterType(optional_value))`: declare parameters, optionally with a value +## Custom Ports and Links -### Bundles -Bundles are a special type of Port that is made up of constituent sub-Ports. +Custom `Port`s and `Link`s can be defined. -Skeleton structure: -```python -class MyBundle(Bundle): - link_type = MyBundleLinkType - bridge_type = MyBundleBridgeType # optional, if a bridge is needed - - def __init__(self) -> None: - super().__init__() # essential to call the superclass method beforehand to initialize state - # declare elements like parameters here -``` +`Port`s should define properties of that `Port` only, not properties influenced by any other connected `Port`. +The `Link` aggregates parameters on the connected `Ports` and optionally defines constraints as assertions. -In addition to the primitives that can be called inside `Port`'s `__init__(...)`, these can be called inside `Bundle`'s `__init__(...)`: -- `self.Port(PortType(...))`: declare bundle sub-port +`Port`s can contain other `Port`s, such as: +- bundles of `Port`s, like `SpiController` +- an inner `Passive` that provides the connectivity layer -### Links -Links define propagation rules for connected Ports. +Look at examples in the standard interfaces library for details. -Links are structured the same as Blocks, with similar primitives (see the Block documentation for details): -```python -class MyPortLink(Link): - def __init__(self) -> None: - super().__init__() # essential to call the superclass method beforehand to initialize state - # declare ports here - - def contents(self) -> None: - super().contents() # essential to call the superclass method beforehand to initialize state - # declare constraints and internal connections here -``` +### Bridges -These are properties of Links: -- Each Port can have one type of associated link. -- The Link type hierarchy currently is not used. However, inheritance may still be useful for code re-use / de-duplication. -- Extend `CircuitLink` instead of `Link` to copper-connect all ports. - -These can be called inside `__init__()`: -- `__init__()` cannot have arguments, since links are always inferred -- `self.Parameter(ParameterType(optional_value))` -- `self.Port(PortType(...))` (without tags, but can have optional) - -These can be called inside `contents()`: -- `self.connect(self.port1.subport, self.port2.subport, ...)`: for Bundle ports, connect sub-ports and infer inner link - - Naming is optional. -- `self.constrain(...)` +`PortBridge` are a quirk of the hierarchical model: how to expose a single boundary `Port` of a `Block` that is internally connecting multiple internal `Port`s. -### Bridges -Bridges are a special type of `Block` that adapts from a Link-facing (inner, `self.inner_link`) port to a Block edge (outer, `self.outer_port`) port. -Extend `CircuitPortBridge` instead of `PortBridge` to copper-connect all ports. +`PortBridge` "aggregate" the `inner_link` (by connecting to a `Port` on that `Link`) and expose a single `outer_port`. -In `__init__()` (may not take arguments), declare these two required ports. -In `contents()`, add constraints as needed. +General design patterns: +- Parameters always propagate outward, never inward. +- Checks that require context at the outer level are handled by propagating the relevant parameters outward. +- If needed, define additional internal parameters (`_`-prefixed) on the `Port` to propagate all the relevant data outward. -### Adapters -PortAdapters are a special type of `Block` that adapts / converts from a source (`self.src`) port to a destination (`self.dst`) port. -Extend `CircuitPortAdapter` instead of `PortAdapter` to copper-connect all ports. +### Mixins -In `__init__(...)` (may take arguments, must decorate with `@init_in_parent` if arguments), declare these two required ports. -In `contents()`, add constraints as needed. +Mixins on `Port`s may be an interesting concept to support opt-in specialized parameter propagation without an excessive base (see [#333](https://github.com/BerkeleyHCI/PolymorphicBlocks/issues/333)) but do not have an implementation plan yet. From 86d5dbcad7f3929b750ba8a3f0bcd51372ca31d3 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 18 Jul 2026 19:41:53 -0700 Subject: [PATCH 8/8] Update reference_library.md --- reference_library.md | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/reference_library.md b/reference_library.md index 5b90a10ea..ce1f7a188 100644 --- a/reference_library.md +++ b/reference_library.md @@ -27,7 +27,7 @@ class IndicatorLed(Block): self.gnd = self.Port(Ground(), [Common]) self.signal = self.Port(DigitalSink(...)) - def contents() -> None: + def contents(self) -> None: super().contents() self.led = self.Block(Led(...)) self.res = self.Block(Resistor(resistance=self.signal.link().voltage / self.current_draw)) @@ -99,7 +99,7 @@ Parameters are variables that can be passed into and through blocks. Use the `xExpr` type in `Parameter(...)` to declare a parameter without a value, to be assigned in `contents()`. - `RangeExpr` types are used to represent a device tolerance or toleranced specification - Most operations between `RangeExpr` types are tolerance-expanding (computes the worst-case range of the inputs) - - `a = c.shrink_multiply(b)` returns `a` such that tolerance-expanding `a * b` is equal to `c`. + - `a = c.shrink_multiply(b)` is a tolerance-shrinking operation to calculate the maximum allowable toleranced `a`, such that tolerance-expanding `a * b` is equal to `c`. Example: for `v = i * r`, to solve for allowable (target) `r` given requirement `i` and contributing tolerance `v`, use `r = (1/i).shrink_multiply(v)` - Access parameters on the link of ports using `port.link().link_param`. Links contain the aggregated parameter of all connected ports, for example the voltage. @@ -165,10 +165,10 @@ class MySchematicDefinedBlock(KiCadSchematicBlock): - The HDL stub is required to define the boundary ports and parameters. - Graphical schematic components map as follows: - - Labels, including symbols like GND and VCC, connect internally - - Hierarchical labels connect to the boundary ports - - True global labels (that connect design-wide) are not supported) - - Components map to `Block`s, with rhe refdes mapping to the `Block` name. + - Labels, including symbols like GND and VCC, connect internally. + - Hierarchical labels connect to the boundary ports. + - True global labels (that connect design-wide) are not supported. + - Components map to `Block`s, with the refdes mapping to the `Block` name. - Wires map to `connect`s. - Pins must be connected at a wire end or bend. - Components can be defined as: @@ -316,7 +316,7 @@ These classes provide utility functions for parts table selectors: - `PartsTableFootprintFilter`: utility code to filter a parts table by footprint, implementing `SelectorFootprint`. - `PartsTableSelectorFootprint`: utility code to construct the footprint, for a `HasStandardFootprint`. -Look at some example implementations to for the `PartsTable` API and utilities. +Look at some example implementations for the `PartsTable` API and utilities. ## Design Patterns and Conventions @@ -362,8 +362,16 @@ Custom `Port`s and `Link`s can be defined. The `Link` aggregates parameters on the connected `Ports` and optionally defines constraints as assertions. `Port`s can contain other `Port`s, such as: -- bundles of `Port`s, like `SpiController` -- an inner `Passive` that provides the connectivity layer +- Bundles of `Port`s, like `SpiController` +- An inner `Passive` that provides the connectivity layer + +The design convention for whether bundle `Port`s are `Passive` or electrically typed is: +- Protocols that define an electrical specification are `Passive` typed. + The electrical modeling is functionally redundant and connections between any parts should be compatible. + Example: CAN, USB. +- Protocols that only define the signal specification are electrically typed. + Electrical modeling is needed to check for compatibility. + Examples: SPI, I2C, which use `DigitalSource` / `DigitalSink` / `DigitalBidir` internally. Look at examples in the standard interfaces library for details.