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:

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:

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:

This is much easier to read and navigate.