Accessing Circuit Data with Netlist Carpentry¶
This notebook demonstrates how to access the data of a circuit using Netlist Carpentry, based on a very simple circuit design.
Read from the Verilog file¶
- First read the verilog file and transform into a Circuit Object.
- Modules can be created and added to the circuit via the
Circuit.create_modulemethod. - Modules can also be removed from the circuit via the
Circuit.remove_modulemethod. - Both methods return True or False depending on whether the operation was successful or not.
- Top module of the circuit can be accessed and changed via
Circuit.set_top.
In [ ]:
Copied!
import netlist_carpentry
from netlist_carpentry.core.exceptions import ObjectNotFoundError
circuit = netlist_carpentry.read("files/simpleAdder.v")
print(f"The circuit currently has {len(circuit.modules)} module: {', '.join(circuit.modules.keys())}")
circuit.create_module("new_empty_module")
print(f"The circuit now has {len(circuit.modules)} modules: {', '.join(circuit.modules.keys())}")
print(f"The top module of the circuit is currently '{circuit.top_name}'.")
circuit.set_top("new_empty_module")
print(f"Now, it's '{circuit.top_name}'.")
circuit.remove_module("new_empty_module")
print(f"The circuit again has {len(circuit.modules)} module: {', '.join(circuit.modules.keys())}")
print(f"The top module of the circuit is now '{circuit.top_name}', since the module specified as top module no longer exists!")
try:
print(f"The object returned by circuit.top is thus {circuit.top}!")
except ObjectNotFoundError as e:
print(f"Catched an ObjectNotFoundError: {e}")
circuit.set_top("simpleAdder")
print(f"Now, the top module is '{circuit.top_name}' again.")
import netlist_carpentry
from netlist_carpentry.core.exceptions import ObjectNotFoundError
circuit = netlist_carpentry.read("files/simpleAdder.v")
print(f"The circuit currently has {len(circuit.modules)} module: {', '.join(circuit.modules.keys())}")
circuit.create_module("new_empty_module")
print(f"The circuit now has {len(circuit.modules)} modules: {', '.join(circuit.modules.keys())}")
print(f"The top module of the circuit is currently '{circuit.top_name}'.")
circuit.set_top("new_empty_module")
print(f"Now, it's '{circuit.top_name}'.")
circuit.remove_module("new_empty_module")
print(f"The circuit again has {len(circuit.modules)} module: {', '.join(circuit.modules.keys())}")
print(f"The top module of the circuit is now '{circuit.top_name}', since the module specified as top module no longer exists!")
try:
print(f"The object returned by circuit.top is thus {circuit.top}!")
except ObjectNotFoundError as e:
print(f"Catched an ObjectNotFoundError: {e}")
circuit.set_top("simpleAdder")
print(f"Now, the top module is '{circuit.top_name}' again.")
Data Structure Overview¶
- Within Netlist Carpentry, circuits are modeled as a collection of individual modules (similar to modules in RTL design).
- These modules contain instances and wires, and have ports that define their interfaces.
- Below is a simple representation of the data structure how it is handled in the Netlist Carpentry framework.
classDiagram
class NetlistElement {
+ path: ElementPath
+ set_name(new_name: str)
}
class Module {
+ instances: Dict[str, Instance]
+ ports: Dict[str, Port]
+ wires: Dict[str, Wire]
}
class Instance {
+ module_def: Optional[Module]
+ ports: Dict[str, Port]
+ parent: Module
}
class Port {
+ direction: Direction
+ is_module_port: bool
+ width: PositiveInt
+ parent: Module
}
class Wire {
+ width: PositiveInt
+ parent: Module
}
NetlistElement <|-- Module
NetlistElement <|-- Instance
NetlistElement <|-- Port
NetlistElement <|-- Wire
Module "1" *-- "0..*" Instance
Module "1" *-- "0..*" Wire
Module "1" *-- "0..*" Port
Instance "1" *-- "0..*" Port
Collect names of instances, ports and wires¶
- Retrieve the top module via
Circuit.top_module. - The content of a module can be directly accessed via
Module.instances,Module.portsorModule.wires, which are dictionaries of the formDict[str, Instance],Dict[str, Port]andDict[str, Wire]respectively. - To retrieve key-value pairs of each of these dictionaries, you can use
Module.instances.items(),Module.ports.items()orModule.wires.items()and iterate over them. - The keys of each dictionary are the names of each instance, port or wire.
- The values of each dictionary are the corresponding instance, port or wire objects.
- Using
Instance.parent,Port.parentorWire.parent, the module containing the object can be retrieved.
In [ ]:
Copied!
top_module = circuit.top
print(f"The top module '{top_module.name}' has the following instances:")
for instance_name, instance_object in top_module.instances.items():
print(f"\tInstance '{instance_name}' of type '{instance_object.instance_type}' (Class name: {instance_object.__class__.__name__}).")
print(f"Instance {instance_name} has this parent: {instance_object.parent}")
print(f"The top module '{top_module.name}' has the following ports:")
for port_name, port_object in top_module.ports.items():
print(f"\tPort '{port_name}', which is an {port_object.direction} port and {port_object.width} bit wide!")
print(f"Port {port_name} has this parent: {port_object.parent}")
print(f"The top module '{top_module.name}' has the following wires:")
for wire_name, wire_object in top_module.wires.items():
print(f"\tWire '{wire_name}', which is {wire_object.width} bit wide!")
print(f"Wire {wire_name} has this parent: {wire_object.parent}")
top_module = circuit.top
print(f"The top module '{top_module.name}' has the following instances:")
for instance_name, instance_object in top_module.instances.items():
print(f"\tInstance '{instance_name}' of type '{instance_object.instance_type}' (Class name: {instance_object.__class__.__name__}).")
print(f"Instance {instance_name} has this parent: {instance_object.parent}")
print(f"The top module '{top_module.name}' has the following ports:")
for port_name, port_object in top_module.ports.items():
print(f"\tPort '{port_name}', which is an {port_object.direction} port and {port_object.width} bit wide!")
print(f"Port {port_name} has this parent: {port_object.parent}")
print(f"The top module '{top_module.name}' has the following wires:")
for wire_name, wire_object in top_module.wires.items():
print(f"\tWire '{wire_name}', which is {wire_object.width} bit wide!")
print(f"Wire {wire_name} has this parent: {wire_object.parent}")
Analyze the contents of a module¶
- Ports define the interface of the module, e.g. what inputs it takes and what outputs it produces.
- Wires represent signals that are passed between module ports and instances of the module.
- Instances define how the module processes signals, and how signals are combined to create new signals.
- In the cell below, some attributes and methods related to ports are presented.
In [ ]:
Copied!
clock_port = top_module.get_port("clk")
print(f"The port '{clock_port.name}' is a {clock_port.direction} port.")
if clock_port.is_driver: # Clock Port is a signal driving port
print(f"The port '{clock_port.name}' is also a signal driving port.")
else:
print(f"The port '{clock_port.name}' is not a signal driving port.")
if clock_port.is_load: # Clock Port is NOT a signal receiving port
print(f"The port '{clock_port.name}' is also a signal receiving port.")
else:
print(f"The port '{clock_port.name}' is not a signal receiving port.")
print(f"The port '{clock_port.name}' has {len(clock_port.connected_wires)} wire(s) connected to it.")
print(f"The full paths of the connected wires are: {', '.join([wire.raw for wire in clock_port.connected_wires])}")
print("The paths follow the format <module_name>.<wire_name>.<wire_index>")
clock_port = top_module.get_port("clk")
print(f"The port '{clock_port.name}' is a {clock_port.direction} port.")
if clock_port.is_driver: # Clock Port is a signal driving port
print(f"The port '{clock_port.name}' is also a signal driving port.")
else:
print(f"The port '{clock_port.name}' is not a signal driving port.")
if clock_port.is_load: # Clock Port is NOT a signal receiving port
print(f"The port '{clock_port.name}' is also a signal receiving port.")
else:
print(f"The port '{clock_port.name}' is not a signal receiving port.")
print(f"The port '{clock_port.name}' has {len(clock_port.connected_wires)} wire(s) connected to it.")
print(f"The full paths of the connected wires are: {', '.join([wire.raw for wire in clock_port.connected_wires])}")
print("The paths follow the format ..")
- In the cell below, some methods related to instances are shown.
- Instances of primitive gates often have seemingly cryptic names generated by Yosys (e.g.
$add$simpleAdder.v:26$2), which are internally simplified. - Any special characters in the name are replaced with paragraph symbols (
§). - Instances can be selected in many different ways -- in the cell below, all instances of a certain type (Adder) are selected.
- Selecting an instance from the list of Adder instances allows to access its properties, such as its ports.
In [ ]:
Copied!
print(f"The module {top_module.name} contains instances with the names: {', '.join(top_module.instances.keys())}")
print(f"Instances can also be sorted by instance type: {top_module.instances_by_types}")
print("All instances of a certain type can be selected via 'Module.instances_by_types[instance_type]'.")
adder_instances = top_module.instances_by_types["§add"]
print(f"The module contains {len(adder_instances)} adders: {adder_instances}")
for adder_inst in adder_instances:
print(f"The first adder instance has the name {adder_inst.name} and {len(adder_inst.ports)} ports:")
for port_name, port_object in adder_inst.ports.items():
print(f"\tPort {port_name} is {port_object.width} bit wide and thus contains {len(port_object.segments)} segments.")
# Alternative representation: 'for seg_idx, segment in port_object.segments.items()'
for seg_idx, segment in port_object: # Segments are used to represent multi-bit signals
print(f"\t\tPort Segment {seg_idx} is connected to wire segment {segment.raw_ws_path}. Alternative representation: index {segment.ws_path.name} of wire {segment.ws_path.parent.name}")
print(f"The module {top_module.name} contains instances with the names: {', '.join(top_module.instances.keys())}")
print(f"Instances can also be sorted by instance type: {top_module.instances_by_types}")
print("All instances of a certain type can be selected via 'Module.instances_by_types[instance_type]'.")
adder_instances = top_module.instances_by_types["§add"]
print(f"The module contains {len(adder_instances)} adders: {adder_instances}")
for adder_inst in adder_instances:
print(f"The first adder instance has the name {adder_inst.name} and {len(adder_inst.ports)} ports:")
for port_name, port_object in adder_inst.ports.items():
print(f"\tPort {port_name} is {port_object.width} bit wide and thus contains {len(port_object.segments)} segments.")
# Alternative representation: 'for seg_idx, segment in port_object.segments.items()'
for seg_idx, segment in port_object: # Segments are used to represent multi-bit signals
print(f"\t\tPort Segment {seg_idx} is connected to wire segment {segment.raw_ws_path}. Alternative representation: index {segment.ws_path.name} of wire {segment.ws_path.parent.name}")
- To retrieve the wires connected to an instance, use
Module.get_edges. - This returns a dictionary, where the keys are the names of the instance's ports, and the values are dictionaries with edge indices and their corresponding wire segments as values.
- Execute the cell below to see which wires are connected to which ports of the adder instance.
In [ ]:
Copied!
edges = top_module.get_edges(adder_inst.name)
for port_name, edge_object in edges.items():
print(f"The edges connected to port {port_name} are:")
for edge_idx, edge_object in edge_object.items():
print(f"\tEdge {edge_idx} is connected to wire segment {edge_object.raw_path}!")
edges = top_module.get_edges(adder_inst.name)
for port_name, edge_object in edges.items():
print(f"The edges connected to port {port_name} are:")
for edge_idx, edge_object in edge_object.items():
print(f"\tEdge {edge_idx} is connected to wire segment {edge_object.raw_path}!")
- The neighboring instances can also be accessed as objects via
Module.get_neighbors, where the neighbors of the provided instance are returned. - This method returns a dictionary, where for each port of the instance, a number of neighboring ports is given.
- The dictionary contains all instance or module ports that are directly connected to one of the instance's ports via a wire.
- The neighboring port is either an instance port or a module port.
- This means that a given neighbor is either an instance (if the neigboring port belongs to an instance) or a module port (if the neigboring port is a module port).
- Execute the cell below to see the neighboring ports of the adder.
In [ ]:
Copied!
neighbors = top_module.get_neighbors(adder_inst.name)
for port_name, neighbor_dict in neighbors.items():
print(f"Neighbors of port {port_name}:")
for neighbor_idx, neighbor_list in neighbor_dict.items():
print(f"\tIndex {neighbor_idx} is connected to port {', '.join(f'{p.raw_path}' for p in neighbor_list)}")
neighbors = top_module.get_neighbors(adder_inst.name)
for port_name, neighbor_dict in neighbors.items():
print(f"Neighbors of port {port_name}:")
for neighbor_idx, neighbor_list in neighbor_dict.items():
print(f"\tIndex {neighbor_idx} is connected to port {', '.join(f'{p.raw_path}' for p in neighbor_list)}")
- To collect all preceeding instances (either module ports or instances), use the method
Module.get_preceeding_instances. - Preceeding instances are instances that directly connect to one of the instance's input ports via wires, i.e. that drives a signal to an input port of this instance.
Module.get_preceeding_instancesreturns a dictionary, where the keys are the names of the instance's input ports (in this case A and B), and the values are again dictionaries, where the port index is the key, and the values are lists of instances that directly connect to this input port via wires.- Execute the cell below to see, which instances drive the signal on the input ports of the adder instance.
Info: Unlike Module.get_neighbors, this method returns the instance or port itself, and not only the port segment of either the module port or instance port.
Warning: Normally, each signal should be driven by only one driver.
The list of preceeding instances (i.e. signals drivers) should thus only contain one element.
Otherwise, there will be multiple driver conflicts.
This can be checked via Wire.has_multiple_drivers, which is explained further below.
In [ ]:
Copied!
preceeding_instances = top_module.get_preceeding_instances(adder_inst.name)
for port_name, instance_dict in preceeding_instances.items():
print(f"Port {port_name} is driven by instances:")
for port_idx, inst_list in instance_dict.items():
print(f"\t{inst_list}")
preceeding_instances = top_module.get_preceeding_instances(adder_inst.name)
for port_name, instance_dict in preceeding_instances.items():
print(f"Port {port_name} is driven by instances:")
for port_idx, inst_list in instance_dict.items():
print(f"\t{inst_list}")
- Similarly, the succeeding instances can be retrieved via
Module.get_succeeding_instances. - Succeeding instances are instances that directly connect to one of the instance's output ports via wires, i.e. that receive a signal which is driven by an output port of this instance.
Module.get_succeeding_instancesreturns a dictionary, where the keys are the names of the instance's output ports (in this case Y), and the values are again dictionaries, where the port index is the key, and the values are lists of instances that directly connect to this output port via wires.- Execute the cell below to see, which instances receive the signals of the output port Y of the adder instance.
In [ ]:
Copied!
succeeding_instances = top_module.get_succeeding_instances(adder_inst.name)
for port_name, instance_dict in succeeding_instances.items():
print(f"Port {port_name} drives signals to instances:")
for port_idx, inst_list in instance_dict.items():
print(f"\t{inst_list}")
succeeding_instances = top_module.get_succeeding_instances(adder_inst.name)
for port_name, instance_dict in succeeding_instances.items():
print(f"Port {port_name} drives signals to instances:")
for port_idx, inst_list in instance_dict.items():
print(f"\t{inst_list}")
- Wires can either be retrieved via
Module.get_wire(returns None if missing) or orModule.wires[<wire_name>](raises error if missing). - Similar to ports, wires consist of segments, where an
nbit wide wire consists ofnsegments. - Execute the cell below to see the properties of a wire exemplarily.
In [ ]:
Copied!
wire_in1 = top_module.get_wire("in1")
print(f"Wire {wire_in1.name} with path {wire_in1.raw_path} is {wire_in1.width} bit wide.")
print(f"Wire {wire_in1.name} is connected to the following ports:")
for seg_idx, segment in wire_in1:
print(f"\tSegment {seg_idx} is connected to port segments: {', '.join(p.raw_path for p in segment.port_segments)}")
wire_in1 = top_module.get_wire("in1")
print(f"Wire {wire_in1.name} with path {wire_in1.raw_path} is {wire_in1.width} bit wide.")
print(f"Wire {wire_in1.name} is connected to the following ports:")
for seg_idx, segment in wire_in1:
print(f"\tSegment {seg_idx} is connected to port segments: {', '.join(p.raw_path for p in segment.port_segments)}")
Analyze wire connectivity¶
- The circuit's topology can be explored, especially in regards to wire connectivity.
- In the cell below, this is shown exemplarily for the wire
in1along with methods to check for possible issues regarding this wire. - These methods can be used to check any wire for issues.
In [ ]:
Copied!
print(f"Checking wire {wire_in1.name} for possible issues...")
if wire_in1.has_no_driver():
print(f"\tWire {wire_in1.name} has no driver.")
else:
print(f"\tWire {wire_in1.name} does have a driver.")
if wire_in1.has_multiple_drivers():
print(f"\tWire {wire_in1.name} has multiple drivers.")
else:
print(f"\tWire {wire_in1.name} does not have multiple drivers.")
if wire_in1.has_no_loads():
print(f"\tWire {wire_in1.name} has no loads.")
else:
print(f"\tWire {wire_in1.name} has loads.")
if wire_in1.is_dangling():
print(f"\tWire {wire_in1.name} is dangling (either no driver or no load).")
else:
print(f"\tWire {wire_in1.name} is not dangling (driver and load exist).")
if wire_in1.has_problems():
print(f"\tWire {wire_in1.name} has issues (e.g. multiple drivers, no driver or no loads).")
else:
print(f"\tWire {wire_in1.name} does not have any issues.")
print(f"Checking wire {wire_in1.name} for possible issues...")
if wire_in1.has_no_driver():
print(f"\tWire {wire_in1.name} has no driver.")
else:
print(f"\tWire {wire_in1.name} does have a driver.")
if wire_in1.has_multiple_drivers():
print(f"\tWire {wire_in1.name} has multiple drivers.")
else:
print(f"\tWire {wire_in1.name} does not have multiple drivers.")
if wire_in1.has_no_loads():
print(f"\tWire {wire_in1.name} has no loads.")
else:
print(f"\tWire {wire_in1.name} has loads.")
if wire_in1.is_dangling():
print(f"\tWire {wire_in1.name} is dangling (either no driver or no load).")
else:
print(f"\tWire {wire_in1.name} is not dangling (driver and load exist).")
if wire_in1.has_problems():
print(f"\tWire {wire_in1.name} has issues (e.g. multiple drivers, no driver or no loads).")
else:
print(f"\tWire {wire_in1.name} does not have any issues.")
- The methods presented in the cell above may also take a parameter
get_mapping(bool), which defaults toFalse. - If set to
True, they return a dictionary with the indices of the wire and the corresponding boolean value for the method call. - Execute the cell below to see how the method
Wire.has_multiple_driversbehaves for the wirein1whenget_mappingisTrue.
In [ ]:
Copied!
has_multiple_drivers_mapping = wire_in1.has_multiple_drivers(get_mapping=True)
print(f"Wire.has_multiple_drivers returns the following mapping for {wire_in1.name}:")
for idx, has_multiple_drivers in has_multiple_drivers_mapping.items():
print(f"\t{idx} -> {has_multiple_drivers}")
print("'False' means that this wire segment does not have multiple drivers, 'True' means it does.")
has_multiple_drivers_mapping = wire_in1.has_multiple_drivers(get_mapping=True)
print(f"Wire.has_multiple_drivers returns the following mapping for {wire_in1.name}:")
for idx, has_multiple_drivers in has_multiple_drivers_mapping.items():
print(f"\t{idx} -> {has_multiple_drivers}")
print("'False' means that this wire segment does not have multiple drivers, 'True' means it does.")
- The signal driving and signal load ports can be retrieved for each segment of the wire via
Wire.driverandWire.load. - Execute the cell below to see the driver and load instances for each segment of the wire
wire_in1.
In [ ]:
Copied!
drivers = wire_in1.driver()
print(f"Drivers of wire {wire_in1.name}:")
for idx, driver in drivers.items():
print(f"\tSegment {idx} is driven by the following port segment: {driver}")
loads = wire_in1.loads()
print(f"Loads of wire {wire_in1.name}:")
for idx, load_list in loads.items():
print(f"\tSegment {idx} drives the following port segments: {', '.join(d.raw_path for d in load_list)}")
drivers = wire_in1.driver()
print(f"Drivers of wire {wire_in1.name}:")
for idx, driver in drivers.items():
print(f"\tSegment {idx} is driven by the following port segment: {driver}")
loads = wire_in1.loads()
print(f"Loads of wire {wire_in1.name}:")
for idx, load_list in loads.items():
print(f"\tSegment {idx} drives the following port segments: {', '.join(d.raw_path for d in load_list)}")
- Similarly, the object to which the port segment belongs to (either an instance or a module port) can be retrieved via
Module.get_connected_ports. - Especially
Module.get_driving_portsandModule.get_load_portsreturn the driving and load objects, respectively. - All three mentioned methods return a set of PortSegment objects.
Module.get_connected_ports: all port segments connected to the given wire segment.Module.get_driving_ports: only the drivers (which should only be one).Module.get_load_ports: only the load port segments.
- Execute the cell below to see the port segments sets for the different use cases.
In [ ]:
Copied!
driving_nodes = top_module.get_driving_ports(wire_in1[0].path)
for port_segment in driving_nodes:
if port_segment.parent.is_module_port:
print(f"Wire {wire_in1.name} is driven by port segment {port_segment.raw_path}, which belongs to a module port.")
else:
print(f"Wire {wire_in1.name} is driven by port segment {port_segment.raw_path}, which belongs to an instance port.")
print(f"\tRaw data: {port_segment}")
load_nodes = top_module.get_load_ports(wire_in1[0].path)
for port_segment in load_nodes:
if port_segment.parent.is_module_port:
print(f"Wire {wire_in1.name} drives port segment {port_segment.raw_path}, which belongs to a module port.")
else:
print(f"Wire {wire_in1.name} drives port segment {port_segment.raw_path}, which belongs to an instance port.")
print(f"\tRaw data: {port_segment}")
driving_nodes = top_module.get_driving_ports(wire_in1[0].path)
for port_segment in driving_nodes:
if port_segment.parent.is_module_port:
print(f"Wire {wire_in1.name} is driven by port segment {port_segment.raw_path}, which belongs to a module port.")
else:
print(f"Wire {wire_in1.name} is driven by port segment {port_segment.raw_path}, which belongs to an instance port.")
print(f"\tRaw data: {port_segment}")
load_nodes = top_module.get_load_ports(wire_in1[0].path)
for port_segment in load_nodes:
if port_segment.parent.is_module_port:
print(f"Wire {wire_in1.name} drives port segment {port_segment.raw_path}, which belongs to a module port.")
else:
print(f"Wire {wire_in1.name} drives port segment {port_segment.raw_path}, which belongs to an instance port.")
print(f"\tRaw data: {port_segment}")
Selecting slices (segments) of a port or wire¶
- Often, ports or wires are wider than 1 bit.
- Each segment of the port or wire is represented by a single-bit slice of the port or wire.
- Segments can be accessed either directly via their dictionary
Port.segments[idx]andWire.segments[idx], or via a shortcutPort[idx]andWire[idx]. - The dictionaries follow the format {idx: segment}.
- In the cell below, this is shown exemplarily for the 9-bit wide output port
out, which thus consists of 9 segments.
In [ ]:
Copied!
out_port = top_module.ports["out"]
print(f"Port '{out_port.name}' has the following segments:")
for idx, segment in out_port:
print(f"\tIndex {idx}: {segment}")
print(f"This is the segment at index 0: {out_port.segments[0]}")
print(f"This is also the segment at index 0: {out_port[0]}")
out_port = top_module.ports["out"]
print(f"Port '{out_port.name}' has the following segments:")
for idx, segment in out_port:
print(f"\tIndex {idx}: {segment}")
print(f"This is the segment at index 0: {out_port.segments[0]}")
print(f"This is also the segment at index 0: {out_port[0]}")
Selecting Elements based on their path¶
- Circuit elements can be accessed via their path.
- An
ElementPathobject consists of the path string itself and the type of the element, which is used for faster look-up. - The path string is a dot-separated string used to identify elements in the circuit.
- For example, the path string
simpleAdder.in1identifies the input portin1of the modulesimpleAdder, and the type of the path is required to look in the correct dictionary, and for certain additional operations. - The part
simpleAdderof the path string identifies the module, and any following part is therefore located inside this module. - The path string
in1identifies an object inside the module (which here is the input portin1). - With
Module.get_from_path, elements from inside the module can be retrieved by using its path. - Execute the cell below to retrieve the input port
in1of the modulesimpleAdderby using its path.
In [ ]:
Copied!
from netlist_carpentry.core.netlist_elements.element_path import PortPath, PortSegmentPath
path_in1 = PortPath(raw="simpleAdder.in1")
port_in1 = top_module.get_from_path(path_in1)
print(f"Port {port_in1.name} has the path '{port_in1.raw_path}', which forms this ElementPath object: {port_in1.path}")
from netlist_carpentry.core.netlist_elements.element_path import PortPath, PortSegmentPath
path_in1 = PortPath(raw="simpleAdder.in1")
port_in1 = top_module.get_from_path(path_in1)
print(f"Port {port_in1.name} has the path '{port_in1.raw_path}', which forms this ElementPath object: {port_in1.path}")
- In the same way, elements further down in the hierarchy can be retrieved as well.
- To select a segment of the port
in1, it can be included into the element path. - In this concrete case, to retrieve the segment
0of the portin1, the full element path issimpleAdder.in1.0. - Execute the cell below to retrieve the port segment
0of the modulesimpleAdderby using its path andModule.get_from_path.
In [ ]:
Copied!
path_in1_0 = PortSegmentPath(raw="simpleAdder.in1.0")
port_in1_0 = top_module.get_from_path(path_in1_0)
print(f"Port Segment {port_in1_0.name} has the path '{port_in1_0.raw_path}', which forms this ElementPath object: {port_in1_0.path}")
path_in1_0 = PortSegmentPath(raw="simpleAdder.in1.0")
port_in1_0 = top_module.get_from_path(path_in1_0)
print(f"Port Segment {port_in1_0.name} has the path '{port_in1_0.raw_path}', which forms this ElementPath object: {port_in1_0.path}")
- Alternatively, the paths can be retrieved directly via the
Circuitobject in the same way usingCircuit.get_from_path. - Execute the cell below to retrieve a Port Segment similar to the cell above, but now using the
Circuit.get_from_pathmethod.
Info: This also works for paths with multiple nested hierarchies, e.g. module.inst1.inst2.inst3.some_port.
Unlike the previously presented method Module.get_from_path, the method Circuit.get_from_path includes all modules of the circuit for possible submodule instantiations.
This way, paths across several hierarchy levels are also evaluated correctly.
In [ ]:
Copied!
path_in1_1 = PortSegmentPath(raw="simpleAdder.in1.1")
port_in1_1 = circuit.get_from_path(path_in1_1)
print(f"Port Segment {port_in1_1.name} has the path '{port_in1_1.raw_path}', which forms this ElementPath object: {port_in1_1.path}")
path_in1_1 = PortSegmentPath(raw="simpleAdder.in1.1")
port_in1_1 = circuit.get_from_path(path_in1_1)
print(f"Port Segment {port_in1_1.name} has the path '{port_in1_1.raw_path}', which forms this ElementPath object: {port_in1_1.path}")