Removing loadless Instances and Wires¶
Netlist Carpentry is capable of removing instances and wires without load. This means if the inputs of an instance are connected to the rest of the circuit, but all outputs are unused (i.e. directly unconnected, or the connected wire itself has no load). In such case, the instance (or pontentially the whole instance subbranch) can be removed. Consider the following graph:
flowchart LR
In1([Input 1])
In2([Input 2])
In3([Input 3])
Gate1{{AND Gate 1}}
Gate2{{AND Gate 2}}
Gate3{{NOT Gate 3.1}}
Gate4{{NOT Gate 3.2}}
Out1([Output])
In1 --> Gate1
In2 --> Gate1
In2 --> Gate2
In3 --> Gate2
Gate2 --> Out1
In3 --> Gate3
Gate3 --> Gate4
The gate AND Gate 1 does not have any load on its output and can thus be removed from the module.
In contrast, the output of AND Gate 2 is connected to the module output and thus necessary for the module to behave correctly.
In the third case, two NOT gates are used, where NOT Gate 3.1 has a load, which is NOT Gate 3.2, but the latter has no load.
Accordingly, both instances could be removed as well.
As a result, the optimized structure would look like this:
flowchart LR
In1([Input 1])
In2([Input 2])
In3([Input 3])
Gate2{{AND Gate 2}}
Out1([Output])
In2 --> Gate2
In3 --> Gate2
Gate2 --> Out1
Execute the cell below to set up a structure similar to the first graph.
from netlist_carpentry import Circuit, Direction
from netlist_carpentry.utils.gate_lib_factory import and_gate, not_gate
c = Circuit(name="c")
m = c.create_module("m")
in1 = m.create_port("Input1", Direction.IN)
in2 = m.create_port("Input2", Direction.IN)
in3 = m.create_port("Input3", Direction.IN)
out = m.create_port("Output", Direction.OUT)
and1 = and_gate(m, "and1", A=in1, B=in2)
and2 = and_gate(m, "and2", A=in2, B=in3, Y=out)
not31 = not_gate(m, "not31", A=in3)
not32 = not_gate(m, "not32", A=not31.ports["Y"])
m.show(interactive=True).run()
Now to start the optimization algorithm to remove the loadless instances and wires, there are two approaches.
The easiest is to execute Module.optimize, which runs a bunch of optimizations, with the removal of loadless elements being one of them.
However, to only execute the optimization task to remove loadless instances and wires, a corresponding function can be imported directly from the corresponding module routines.opt.loadless, as shown in the cell below.
The method opt_loadless will recursively remove all loadless wires and instance until no loadless wires and instances are left.
After the optimization, the shown graph will only contain one instance (the AND Gate 2), and the three inputs and one output port.
from netlist_carpentry.routines.opt import opt_loadless
print(f"Module {m.name} has these instances: {', '.join(inst for inst in m.instances)}")
for iname, inst in m.instances.items():
if inst.ports["Y"].is_unconnected:
print(f">>> Instance {iname} has no load and will be removed!")
else:
# Instance "not31" also has load, but its load instance itself is loadless!
# So after the first optimization iteration, "not31" will become loadless as well!
print(f">>> Instance {iname} has load!")
opt_loadless(m)
m.show(interactive=True).run()
print(f"Module {m.name} now has these instances: {', '.join(inst for inst in m.instances)}")