Skip to content

Connector-pairs for daughterboardsΒ #367

@ducky64

Description

@ducky64

Goals

The idea here is, for a Block, to be able to replace part of its implementation with another Block. This is meant to support daughterboards, and has two main use cases:

  • where the design is partitioned into one-of daughterboard(s) (as opposed to commercially available modules that are re-used across designs), and
  • to support the definition of dev boards and modules (eg, ESP32-WROOM) that contain some subcircuit (eg, ESP32 chip application circuit), by directly instantiating the subcircuit Block to pass through modeling parameters, instead of duplicating explicit modeling parameters on the dev board footprint or the complex, cumbersome inheritance structure (as is currently done for microcontrollers)

Requirements

  • A Block where part of its implementation (eg, microcontroller subcircuit) can be replaced with some other "physical" Block (eg, the dev board footprint)
  • The implementation should be the single source of truth for electrical modeling, even if it is ignored by the netlister
  • The "physical" Block should be a normal Block, and integrate with existing generic connectors generators like the JST PH-K and 0.5mm FPC connectors
    • This specifically means its ports must be properly defined, which is necessary for generator functionality
  • Support for connector pairs, which define both ends of a connector
    • Connector pairs should be compositional, using existing connector (generators) as needed
    • This means connector through-pinning can be part of libraries, instead of managed on a per-design basis
  • Support for multiple "physical" blocks across a daughterboard connection, eg a JST PHK for power and 0.5mm FPC for data
  • This is an advanced user feature. While novices may use the feature indirectly by instantiating microcontroller dev boards, they do not need to write HDL using this feature. However, they may need to inspect designs that use this feature.

Non-requirements

  • This is only meant to support the case where the subcircuit is known and part of the library, but alternate packaging is available
  • Dev boards which are true blackboxes (either because the implementation is not known, or it is not worth implementing the subcircuit) should NOT use this facility
    • Though with good naming discipline, there is a pathway to create the subcircuit and replace the dev board with the daughterboard facility

Proposed Implementation

Dev boards can be defined with a new WrapperBlock class. This behaves like a normal Block (with the ability to contain boundary ports, sub-blocks, and connections between its boundary ports and sub-blocks' ports), but also has the option to mark sub-blocks as internal. The netlister recognizes WrapperBlock-derived classes as special and skips internal sub-blocks. Everything is processed (eg, in terms of parameter propagation and elaboration) normally by the core compiler.

In a WrapperBlock, the implementation is connected to its boundary ports as it normally would be. The connector is instantiated and marked as interface, then a new export-tap connection connects the boundary ports to the connector's ports.

Export-tap has the same semantics as export, but does not propagate parameters and may be connected to a port that has an existing connection. Multiple export-taps may be supported on a single port, and can be on internal elements of a Bundle port. The inner port (eg, connector side) of the export tap sees a connection as normal, and .is_connected(), .requested(), and .link() behave as they would with a normal connection. Port types must match exactly (as with Export). It is a compiler elaboration error if the inner port defines parameter values, it must be .empty().

For now, export-tap is only exposed in the frontend HDL in WrapperBlock and similar classes. It is not exposed in the general Block class, at least until additional concrete use cases become clear.

As a separate phase, a SubboardBlock class supports daughterboards. Instead of the netlister discarding non-interface blocks, those are moved into a separate board file.

A ConnectorPair class, also a Block subclass, defines a connector pair. Its IO is the outer-facing port (the ports if instantiated in the motherboard), and it instantiates both the motherboard-side and daughterboard-side connectors. The motherboard-side ports are connected directly, and the daughterboard-side connectors are connected via export-tap. There is a way to mark blocks as inner-facing, other blocks are outer-facing by default.

The daughterboard netlist would have hierarchical names starting from the SubboardBlock itself. This makes daughterboard netlists (and tstamps) independent of (and stable regardless of) its position in the parent hierarchy. The daughterboard netlist file would be named with the hierarchical name of the SubboardBlock. One would be generated for each daughterboard. In the future, there may be some mechanism for deduplication and possibly enforcing that multiple instances are identical.

Note, requires compositional Passive #114 , so connector generators with Passive-typed ports can attach to the inner Passive-type ports of typed ports (like Digital).

Mockup, MCU dev board case

class McuDevBoard(WrapperBlock):
  def __init__(self):
    self.impl = self.Block(DiscreteMcuSubcircuit(), non_netlist=True)  # contains the discrete subcircuit, using the MCU IC, for electrical modeling only, skipped by the netlister
    self.ios = self.Export(self.impl.ios)
    # other top-level IOs also get exported here

  def contents(self):
    self.device = self.Block(McuDevBoard_Device())  # contains the footprint of the dev board
    self.export_tap(self.ios, self.device.ios)
    # for MCUs with automatic pinning, also need to propagate pinning data from DiscreteMcuSubcircuit into McuDevBoard_Device, and McuDevBoard_Device would need to translate chip-level pinning into its own pinning

This can also be used to handle other dev boards, eg the PTH-friendly VL53L0X carrier board or the stepper driver carrier board.

Note that McuDevBoard_Device() IOs must have the same type as DiscreteMcuSubcircuit and McuDevBoard. There's going to be a bit of duplication with IO types here.

Mockups, one-of daughterboard case

class PhKConnectorPair(ConnectorPair):
  def __init__(self):
    self.outer = self.Block(PhK())
    self.ios = self.Export(self.outer.ios)
    self.inner = self.Block(PhK(), internal=True)  # internal=True means it's the inner-facing block
    self.export_tap(self.ios, self.inner.ios)  # this sets the outer-innter connectivity
    # in concept, with generators, we can create other pinning permutations, like crossover/reversed pinnings

class MyDaughterboard(SubblockBlock):
  def __init__(self):
    self.impl = self.Block(..., internal=True)  # internal=True means this goes in the daughterboard
    self.gnd = self.Export(self.impl.gnd)
    self.io = self.Export(self.impl.io)
    # other top-level IOs also get exported here

  def contents(self):  # similar structure to the WrapperBlock and McuDevBoard, but supports a ConnectorPair
    self.conn = self.Block(PhKConnectorPair())
    self.export_tap(self.gnd, self.conn.request('1'))
    self.export_tap(self.io, self.conn.request('2'))

This can also be used to handle a FPC connector component on the outer side, and the flex-PCB side connector on the inner side.

TODOs

  • How to handle arrays in external-facing IO?
    • What if they're typed (eg, array of DigitalBidir), whereas the connector is not (Passive). Should there be a way to map_extract the inner port type?
    • Generators are likely necessary to get the elements of arrays, unwrap them to export_tap them one-by-one, and propagate them inwards. Some syntactic sugar may help here, but array connections are limited.

Other Applications

ExportTap allows applications that need to bypass the electronics model:

  • An alternative implementation for chicken blocks, additional circuitry during prototyping that may not be model-valid. For instance, having options for two voltage regulators, but without using the voltage-source-compiler block.

Potential Drawbacks

  • For dev boards, its instantiation requires creating the whole microcontroller subcircuit, with capacitors and potentially even RF matching circuits.
    • This may be computationally expensive - but probably not significantly so.
    • This requires internal generators to run including parts selection, which may produce errors (eg, parts not in parts tables, abstract blocks missing refinements). Future work may need to provide an ideal-board environment with ideal-part refinements.
  • ExportTap is another core compiler construct, and a model-breaking one at that. Is it a general enough construct that it could have other uses?
  • As a wrapper block, all the IOs still need to be exported, which creates some boilerplate.
  • Conceptually, the wrapper instantiates both the implementation and interface blocks within the same namespace. This may be confusing, though WrapperBlock is an advanced-user construct and the netlist_only flag and documentation can address this.
    • Connectivity-wise, the interface block is hanging off the connection it is interposed in, which is physically unreal. But this seems like a least-worst solution in terms of minimizing boilerplate and re-using existing compiler constructs.

Other (Rejected) Options

  • In general, any implementation that does not instantiate and connect the connector as a normal Block precludes the use of generator Blocks, which is a substantial (and deal-breaking) limitation.
  • Instead of using ExportTap, define a mux / combiner block for each Port type that allows multiple connections to the port. This requires such a block definition for each port type (which is likely too cumbersome to be practical), but does not require additional core constructs.
  • Implementing an ExportTap construct using netlister support only, eg using metadata or parameters to mark which boundary ports are connected to which internal connector ports. However, additional hacks are needed to have the internal connectors generate (eg, creating typed dummy blocks to connect to the internal connector IOs).
  • The concept of splitting a Block int multiple views, eg def impl(): and def interface():, that separates the electrical modeling view and the physical view. The structural mutual exclusivity is nice, though it's unclear how the view split should be handled in the compiler - likely it's effectively ExportTap with a different API.
  • Associate a connector definition with the Port. This makes a lot of conceptual sense for daughterboards, since the connector can be conceptually thought of as an interposer on the port. However, from a compiler perpsective, there isn't a way to have Blocks on the Ports, and it effectively likely boils down to ExportTaps anyways, just with more magic.

Roadmap

  • ExportTap compiler construct with compiler-level uniittests
  • WrapperBlock construct with export_tap API, netlister support, with unit tests
  • Refactor existing non-microcontroller-dev boards
  • Checkpoint PR
  • Refactor microcontrollers
  • Checkpoint PR
  • Composition Passive Should CircuitPort subclasses be compositional instead of inheriting?Β #114
  • Implement ConnectorPair, SubboardBlock
  • Implement netlister support, with unit tests
  • Checkpoint PR
  • Some example design using daughterboards, possibly a space-optimized design that uses a multi-board design for packing. Possibly a design with a USB connector + PD controller daughterboard, or one with a temperature sensor downstream of a FPC for sensing accuracy

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions