Recursion & Cyclic Circuit Graphs#

You can use recursion to create a cycle in your circuit graph, but the circuit must be realizable. For example, a recursive circuit definition must be guaranteed to stop recursing at some point - otherwise this will result in a stack overflow.

Here is an example:

class A(Circuit):
  VIN = Port()
  GND = Port()
  def __init__(self, depth = 2):
    self.obj = B(depth - 1)

    self.R = Resistor(resistance=100 * ohm)

    self += self.R.p1 + self.VIN
    self += self.R.p2 + self.obj.VIN
    self += self.GND + self.obj.GND


class B(Circuit):
  VIN = Port()
  GND = Port()
  def __init__(self, depth):

    self.R = Resistor(resistance=10 * ohm)
    self += self.R.p1 + self.VIN
    self += self.R.p2 + self.GND

    if depth >  0:
      self.obj = A(depth)

      self += self.obj.VIN + self.VIN
      self += self.obj.GND + self.GND

class TopLevel(Circuit):
  # Instantiate Recursive Circuit
  obj = A(depth = 4)

This will produce a circuit structure like this:

Recursive Ladder of Resistors

For all depth > 0, this circuit will always halt. Notice the use of the if clause in B - this is what stops the recursive loop.

This structure can often be very cumbersome to directly access individual elements. For example:


class TopLevel(Circuit):
  obj = A(depth = 4)

  def __init__(self):
    # Accessing an internally instantiated resistor
    R1 = self.obj.obj.obj.R
    # but wait, is this too many `obj` ?
    R2 = self.obj.obj.obj.obj.obj.obj.R

Additionally, accessing the internal ports of the last element (ie, VDD and GND of the last B element) can be rather difficult both for direct access and introspection.

For these reasons, creating an array of components can often be a more convenient way to write these circuits, but may require some more sophisticated for loops:


class ForLoopCircuit(Circuit):
  VIN = Port()
  GND = Port()
  
  def __init__(self, depth = 4):
    assert depth > 0

    self.series_resistors = [Resistor(resistance=100 * ohm) for _ in range(depth)]
    self.shunt_resistors = [Resistor(resistance=10 * ohm) for _ in range(depth)]

    current_node = self.VIN

    # Loop from the top of the ladder (depth-1) down to the bottom (0).
    for i in range(depth - 1, -1, -1):
      r_series = self.series_resistors[i]
      r_shunt = self.shunt_resistors[i]

      self += r_series.p1 + current_node

      next_node = r_series.p2

      self += r_shunt.p1 + next_node
      self += r_shunt.p2 + self.GND

      current_node = next_node      

This produces the same circuit but we can now more easily access the final B elements with self.obj.shunt_resistors[-1]

Device Tree Structure#

The other consideration is the readability of the device tree for a circuit. Here is an example of the recursive circuit definition:

Recursive Device Tree

Note how the A and B instances alternate as we descend the device tree. This is hard to read and navigate.

Here is the ForLoopCircuit design:

ForLoop Device Tree

This is much easier to read and navigate.