From 146efc50700d893d551e4eef8b4cd5a2cff0d46c Mon Sep 17 00:00:00 2001 From: Sam Crow Date: Thu, 13 Jul 2023 16:45:05 -0700 Subject: [PATCH 01/15] implement pin_spice object --- compiler/base/design.py | 2 +- compiler/base/hierarchy_spice.py | 94 ++++++++++++++------------------ compiler/base/lef.py | 2 +- compiler/base/pin_spice.py | 43 +++++++++++++++ 4 files changed, 86 insertions(+), 55 deletions(-) create mode 100644 compiler/base/pin_spice.py diff --git a/compiler/base/design.py b/compiler/base/design.py index 94fd407f..329883c0 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -45,7 +45,7 @@ class design(hierarchy_design): "Custom cell pin names do not match spice file:\n{0} vs {1}".format(prop.port_names, self.pins)) self.add_pin_indices(prop.port_indices) self.add_pin_names(prop.port_map) - self.add_pin_types(prop.port_types) + self.update_pin_types(prop.port_types) (width, height) = utils.get_libcell_size(self.cell_name, diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index 64dc0b2b..90c65109 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -17,6 +17,7 @@ from .delay_data import delay_data from .wire_spice_model import wire_spice_model from .power_data import power_data from .logical_effort import convert_relative_c_to_farad, convert_farad_to_relative_c +from .pin_spice import pin_spice class spice(): @@ -49,14 +50,10 @@ class spice(): if not os.path.exists(self.lvs_file): self.lvs_file = self.sp_file - self.valid_signal_types = ["INOUT", "INPUT", "OUTPUT", "BIAS", "POWER", "GROUND"] # Holds subckts/mods for this module self.mods = set() # Holds the pins for this module (in order) self.pins = [] - # The type map of each pin: INPUT, OUTPUT, INOUT, POWER, GROUND - # for each instance, this is the set of nets/nodes that map to the pins for this instance - self.pin_type = {} # An (optional) list of indices to reorder the pins to match the spice. self.pin_indices = [] # THE CONNECTIONS MUST MATCH THE ORDER OF THE PINS (restriction imposed by the @@ -90,106 +87,97 @@ class spice(): def add_pin(self, name, pin_type="INOUT"): """ Adds a pin to the pins list. Default type is INOUT signal. """ - self.pins.append(name) - self.pin_type[name]=pin_type - debug.check(pin_type in self.valid_signal_types, - "Invalid signaltype for {0}: {1}".format(name, - pin_type)) + new_pin = pin_spice(name, pin_type) + debug.check(new_pin not in self.pins, "cannot add duplicate spice pin") + self.pins.append(new_pin) def add_pin_list(self, pin_list, pin_type="INOUT"): """ Adds a pin_list to the pins list """ - # The type list can be a single type for all pins + # The pin type list can be a single type for all pins # or a list that is the same length as the pin list. - if type(pin_type)==str: + if type(pin_type) == str: for pin in pin_list: - debug.check(pin_type in self.valid_signal_types, - "Invalid signaltype for {0}: {1}".format(pin, - pin_type)) self.add_pin(pin, pin_type) elif len(pin_type)==len(pin_list): - for (pin, ptype) in zip(pin_list, pin_type): - debug.check(ptype in self.valid_signal_types, - "Invalid signaltype for {0}: {1}".format(pin, - ptype)) - self.add_pin(pin, ptype) + for (pin, type) in zip(pin_list, pin_type): + self.add_pin(pin, type) else: - debug.error("Mismatch in type and pin list lengths.", -1) + debug.error("Pin type must be a string or list of strings the same length as pin_list", -1) def add_pin_indices(self, index_list): - """ - Add pin indices for all the cell's pins. - """ + """ Add pin indices for all the cell's pins. """ self.pin_indices = index_list def get_ordered_inputs(self, input_list): - """ - Return the inputs reordered to match the pins. - """ + """ Return the inputs reordered to match the pins. """ if not self.pin_indices: return input_list new_list = [input_list[x] for x in self.pin_indices] return new_list - def add_pin_types(self, type_list): - """ - Add pin types for all the cell's pins. - """ - # This only works if self.pins == bitcell.pin_names + def update_pin_types(self, type_list): + """ Change pin types for all the cell's pins. """ if len(type_list) != len(self.pins): debug.error("{} spice subcircuit number of port types does not match number of pins\ \n SPICE names={}\ \n Module names={}\ ".format(self.name, self.pins, type_list), 1) - self.pin_type = {pin: type for pin, type in zip(self.pins, type_list)} + for pin, type in zip(self.pins, type_list): + pin.set_pin_type(type) def get_pin_type(self, name): """ Returns the type of the signal pin. """ - pin_type = self.pin_type[name] - debug.check(pin_type in self.valid_signal_types, - "Invalid signaltype for {0}: {1}".format(name, pin_type)) - return pin_type + for pin in self.pins: + if pin.name == name: + return pin.type + debug.error("Spice pin {} not found".format(name)) def get_pin_dir(self, name): """ Returns the direction of the pin. (Supply/ground are INOUT). """ - if self.pin_type[name] in ["POWER", "GROUND"]: + pin_type = self.get_pin_type(name) + if pin_type in ["POWER", "GROUND"]: return "INOUT" else: - return self.pin_type[name] + return pin_type def get_inputs(self): - """ These use pin types to determine pin lists. These - may be over-ridden by submodules that didn't use pin directions yet.""" + """ + These use pin types to determine pin lists. + Returns names only to maintain historical interface. + """ input_list = [] for pin in self.pins: - if self.pin_type[pin]=="INPUT": - input_list.append(pin) + if pin.type == "INPUT": + input_list.append(pin.name) return input_list def get_outputs(self): - """ These use pin types to determine pin lists. These - may be over-ridden by submodules that didn't use pin directions yet.""" + """ + These use pin types to determine pin lists. + Returns names only to maintain historical interface. + """ output_list = [] for pin in self.pins: - if self.pin_type[pin]=="OUTPUT": - output_list.append(pin) + if pin.type == "OUTPUT": + output_list.append(pin.name) return output_list - def copy_pins(self, other_module, suffix=""): - """ This will copy all of the pins from the other module and add an optional suffix.""" - for pin in other_module.pins: - self.add_pin(pin + suffix, other_module.get_pin_type(pin)) - def get_inouts(self): """ These use pin types to determine pin lists. These may be over-ridden by submodules that didn't use pin directions yet.""" inout_list = [] for pin in self.pins: - if self.pin_type[pin]=="INOUT": - inout_list.append(pin) + if pin.type == "INOUT": + inout_list.append(pin.name) return inout_list + def copy_pins(self, other_module, suffix=""): + """ This will copy all of the pins from the other module and add an optional suffix.""" + for pin in other_module.pins: + self.add_pin(pin.name + suffix, pin.type) + def connect_inst(self, args, check=True): """ Connects the pins of the last instance added diff --git a/compiler/base/lef.py b/compiler/base/lef.py index b138799b..e08f0efc 100644 --- a/compiler/base/lef.py +++ b/compiler/base/lef.py @@ -18,7 +18,7 @@ class lef: """ SRAM LEF Class open GDS file, read pins information, obstruction and write them to LEF file. - This is inherited by the sram_base class. + This is inherited by the sram_1bank class. """ def __init__(self, layers): # LEF db units per micron diff --git a/compiler/base/pin_spice.py b/compiler/base/pin_spice.py new file mode 100644 index 00000000..1754550e --- /dev/null +++ b/compiler/base/pin_spice.py @@ -0,0 +1,43 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +from openram import debug + + +class pin_spice: + """ + A class to represent a spice netlist pin. + """ + + valid_pin_types = ["INOUT", "INPUT", "OUTPUT", "POWER", "GROUND"] + + def __init__(self, name, type): + self.name = name + self.set_type(type) + + self._hash = hash(self.name) + + def set_type(self, type): + debug.check(type in pin_spice.valid_pin_types, + "Invalid pin type for {0}: {1}".format(self.name, type)) + self.type = type + + def __str__(self): + """ override print function output """ + return "(pin_name={} type={})".format(self.name, self.type) + + def __repr__(self): + """ override repr function output """ + return self.name + + def __hash__(self): + """ + Implement the hash function for sets etc. + Only hash name since spice does not allow two pins to share a name. + Provides a speedup if pin_spice is used as a key for dicts. + """ + return self._hash From c8c43f75d9b93e6311e92ca13e3accb2ae4d1365 Mon Sep 17 00:00:00 2001 From: Sam Crow Date: Fri, 14 Jul 2023 16:18:10 -0700 Subject: [PATCH 02/15] add spice nets and a way to connect them to pins --- compiler/base/geometry.py | 17 ++++++++-- compiler/base/hierarchy_spice.py | 54 +++++++++++++++----------------- compiler/base/net_spice.py | 51 ++++++++++++++++++++++++++++++ compiler/base/pin_spice.py | 27 +++++++++++++++- 4 files changed, 117 insertions(+), 32 deletions(-) create mode 100644 compiler/base/net_spice.py diff --git a/compiler/base/geometry.py b/compiler/base/geometry.py index 2e33885c..18e1a39b 100644 --- a/compiler/base/geometry.py +++ b/compiler/base/geometry.py @@ -161,8 +161,8 @@ class geometry: class instance(geometry): """ - An instance of an instance/module with a specified location and - rotation + An instance of a module with a specified location, rotation, + spice pins, and spice nets """ def __init__(self, name, mod, offset=[0, 0], mirror="R0", rotate=0): """Initializes an instance to represent a module""" @@ -176,6 +176,14 @@ class instance(geometry): self.rotate = rotate self.offset = vector(offset).snap_to_grid() self.mirror = mirror + + # deepcopy because this instance needs to + # change attributes in these spice objects + self.spice_pins = copy.deepcopy(self.mod.pins) + self.spice_nets = copy.deepcopy(self.mod.nets) + for spice_obj in self.spice_pins + self.spice_nets: + spice_obj.set_inst(self) + if OPTS.netlist_only: self.width = 0 self.height = 0 @@ -274,6 +282,11 @@ class instance(geometry): new_pins.append(p) return new_pins + def connect_spice_pins(self, nets_list): + for i in range(len(self.pins)): + self.pins[i].set_inst_net(nets_list[i]) + nets_list[i].connect_pin(self.pins[i]) + def calculate_transform(self, node): #set up the rotation matrix angle = math.radians(float(node.rotate)) diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index 90c65109..3bf2af7f 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -13,6 +13,8 @@ from pprint import pformat from openram import debug from openram import tech from openram import OPTS + +from compiler.base.net_spice import net_spice from .delay_data import delay_data from .wire_spice_model import wire_spice_model from .power_data import power_data @@ -58,7 +60,8 @@ class spice(): self.pin_indices = [] # THE CONNECTIONS MUST MATCH THE ORDER OF THE PINS (restriction imposed by the # Spice format) - self.conns = [] + # internal nets, which may or may not be connected to pins of the same name + self.nets = [] # If this is set, it will out output subckt or isntances of this (for row/col caps etc.) self.no_instances = False # If we are doing a trimmed netlist, these are the instance that will be filtered @@ -178,16 +181,13 @@ class spice(): for pin in other_module.pins: self.add_pin(pin.name + suffix, pin.type) - def connect_inst(self, args, check=True): + def connect_inst(self, args): """ Connects the pins of the last instance added - It is preferred to use the function with the check to find if - there is a problem. The check option can be set to false - where we dynamically generate groups of connections after a - group of modules are generated. """ - num_pins = len(self.insts[-1].mod.pins) + spice_pins = self.insts[-1].spice_pins + num_pins = len(spice_pins) num_args = len(args) # Order the arguments if the hard cell has a custom port order @@ -195,11 +195,11 @@ class spice(): if (check and num_pins != num_args): if num_pins < num_args: - mod_pins = self.insts[-1].mod.pins + [""] * (num_args - num_pins) + mod_pins = spice_pins + [""] * (num_args - num_pins) arg_pins = ordered_args else: arg_pins = ordered_args + [""] * (num_pins - num_args) - mod_pins = self.insts[-1].mod.pins + mod_pins = spice_pins modpins_string = "\n".join(["{0} -> {1}".format(arg, mod) for (arg, mod) in zip(arg_pins, mod_pins)]) debug.error("Connection mismatch:\nInst ({0}) -> Mod ({1})\n{2}".format(num_args, @@ -207,27 +207,22 @@ class spice(): modpins_string), 1) - self.conns.append(ordered_args) + ordered_nets = self.create_nets(ordered_args) + self.insts[-1].connect_spice_pins(ordered_nets) - # This checks if we don't have enough instance port connections for the number of insts - if check and (len(self.insts)!=len(self.conns)): - insts_string=pformat(self.insts) - conns_string=pformat(self.conns) + # TODO: replace functionality that checked if user forgot to connect an inst before connecting the next - debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name, - len(self.insts), - len(self.conns))) - debug.error("Instances: \n" + str(insts_string)) - debug.error("-----") - debug.error("Connections: \n" + str(conns_string), 1) - - def get_conns(self, inst): - """Returns the connections of a given instance.""" - for i in range(len(self.insts)): - if inst is self.insts[i]: - return self.conns[i] - # If not found, returns None - return None + def create_nets(self, names_list): + nets = [] + for name in names_list: + try: + i = self.nets.index(name) + nets.append(self.nets[i]) + except ValueError: + net = net_spice(name) + self.nets.append(net) + nets.append(net) + return nets def sp_read(self): """ @@ -246,6 +241,7 @@ class spice(): subckt = re.compile("^.subckt {}".format(self.cell_name), re.IGNORECASE) subckt_line = list(filter(subckt.search, self.spice))[0] # parses line into ports and remove subckt + # FIXME: needs to use new pins interface self.pins = subckt_line.split(" ")[2:] else: debug.info(4, "no spfile {0}".format(self.sp_file)) @@ -345,7 +341,7 @@ class spice(): # Also write pins as comments for pin in self.pins: - sp.write("* {1:6}: {0} \n".format(pin, self.pin_type[pin])) + sp.write("* {1:6}: {0} \n".format(pin, pin.type)) for line in self.comments: sp.write("* {}\n".format(line)) diff --git a/compiler/base/net_spice.py b/compiler/base/net_spice.py new file mode 100644 index 00000000..d1c24ab6 --- /dev/null +++ b/compiler/base/net_spice.py @@ -0,0 +1,51 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +from openram import debug +from .pin_spice import pin_spice + + +class net_spice: + """ + A class to represent a spice net. + pins are all the pins connected to this net. + inst is the instance this net is a part of, if any. + """ + + def __init__(self, name): + self.name = name + self.pins = [] + self.inst = None + + # TODO: evaluate if this makes sense... and works + self._hash = hash(self.name) + + def connect_pin(self, pin): + debug.check(isinstance(pin, pin_spice), "pin must be a pin_spice object") + if pin in self.pins: + debug.warning("pin {} was already connected to net {} ... why was it connected again?".format(pin.name, self.name)) + else: + self.pins.append(pin) + + def __str__(self): + """ override print function output """ + return "(pin_name={} type={})".format(self.name, self.type) + + def __repr__(self): + """ override repr function output """ + return self.name + + def __eq__(self, name): + return (name == self.name) if isinstance(name, str) else super().__eq__(name) + + def __hash__(self): + """ + Implement the hash function for sets etc. + Only hash name since spice does not allow two pins to share a name. + Provides a speedup if pin_spice is used as a key for dicts. + """ + return self._hash diff --git a/compiler/base/pin_spice.py b/compiler/base/pin_spice.py index 1754550e..0d2d2342 100644 --- a/compiler/base/pin_spice.py +++ b/compiler/base/pin_spice.py @@ -6,19 +6,29 @@ # All rights reserved. # from openram import debug +from .net_spice import net_spice class pin_spice: """ A class to represent a spice netlist pin. + mod is the parent module that created this pin. + mod_net is the net object of this pin's parent module. + inst is the instance this pin is a part of, if any. + inst_net is the net object from mod's nets which connects to this pin. """ valid_pin_types = ["INOUT", "INPUT", "OUTPUT", "POWER", "GROUND"] - def __init__(self, name, type): + def __init__(self, name, type, mod): self.name = name self.set_type(type) + self.mod = mod + self.mod_net = None + self.inst = None + self.inst_net = None + # TODO: evaluate if this makes sense... and works self._hash = hash(self.name) def set_type(self, type): @@ -26,6 +36,18 @@ class pin_spice: "Invalid pin type for {0}: {1}".format(self.name, type)) self.type = type + def set_mod_net(self, net): + debug.check(isinstance(net, net_spice), "net must be a net_spice object") + debug.check(net.name == self.name, "module spice net must have same name as spice pin") + self.mod_net = net + + def set_inst(self, inst): + self.inst = inst + + def set_inst_net(self, net): + debug.check(isinstance(net, net_spice), "net must be a net_spice object") + self.inst_net = net + def __str__(self): """ override print function output """ return "(pin_name={} type={})".format(self.name, self.type) @@ -34,6 +56,9 @@ class pin_spice: """ override repr function output """ return self.name + def __eq__(self, name): + return (name == self.name) if isinstance(name, str) else super().__eq__(name) + def __hash__(self): """ Implement the hash function for sets etc. From 7581df22557bffe6997a4d87da770bc2093aa496 Mon Sep 17 00:00:00 2001 From: Sam Crow Date: Mon, 17 Jul 2023 14:32:39 -0700 Subject: [PATCH 03/15] change pins to OrderedDict --- compiler/base/design.py | 6 +-- compiler/base/hierarchy_design.py | 14 +++--- compiler/base/hierarchy_spice.py | 82 +++++++++++++------------------ 3 files changed, 45 insertions(+), 57 deletions(-) diff --git a/compiler/base/design.py b/compiler/base/design.py index 329883c0..fe9b5495 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -41,8 +41,8 @@ class design(hierarchy_design): if prop and prop.hard_cell: # The pins get added from the spice file, so just check # that they matched here - debug.check(prop.port_names == self.pins, - "Custom cell pin names do not match spice file:\n{0} vs {1}".format(prop.port_names, self.pins)) + debug.check(prop.port_names == list(self.pins), + "Custom cell pin names do not match spice file:\n{0} vs {1}".format(prop.port_names, list(self.pins))) self.add_pin_indices(prop.port_indices) self.add_pin_names(prop.port_map) self.update_pin_types(prop.port_types) @@ -51,7 +51,7 @@ class design(hierarchy_design): (width, height) = utils.get_libcell_size(self.cell_name, GDS["unit"], layer[prop.boundary_layer]) - self.pin_map = utils.get_libcell_pins(self.pins, + self.pin_map = utils.get_libcell_pins(list(self.pins), self.cell_name, GDS["unit"]) diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index 42c0543d..c34bc5f5 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -135,9 +135,9 @@ class hierarchy_design(spice, layout): # Translate port names to external nets if len(port_nets) != len(self.pins): debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets, - self.pins), + list(self.pins)), 1) - port_dict = {pin: port for pin, port in zip(self.pins, port_nets)} + port_dict = {pin: port for pin, port in zip(list(self.pins), port_nets)} debug.info(3, "Instance name={}".format(inst_name)) for subinst, conns in zip(self.insts, self.conns): if subinst in self.graph_inst_exclude: @@ -153,9 +153,9 @@ class hierarchy_design(spice, layout): # Translate port names to external nets if len(port_nets) != len(self.pins): debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets, - self.pins), + list(self.pins)), 1) - port_dict = {pin: port for pin, port in zip(self.pins, port_nets)} + port_dict = {pin: port for pin, port in zip(list(self.pins), port_nets)} debug.info(3, "Instance name={}".format(inst_name)) for subinst, conns in zip(self.insts, self.conns): subinst_name = inst_name + "{}x".format(OPTS.hier_seperator) + subinst.name @@ -186,7 +186,7 @@ class hierarchy_design(spice, layout): """ # The final pin names will depend on the spice hierarchy, so # they are passed as an input. - pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)} + pin_dict = {pin: port for pin, port in zip(list(self.pins), port_nets)} input_pins = self.get_inputs() output_pins = self.get_outputs() inout_pins = self.get_inouts() @@ -197,7 +197,7 @@ class hierarchy_design(spice, layout): def __str__(self): """ override print function output """ - pins = ",".join(self.pins) + pins = ",".join(list(self.pins)) insts = [" {}".format(x) for x in self.insts] objs = [" {}".format(x) for x in self.objs] s = "********** design {0} **********".format(self.cell_name) @@ -208,7 +208,7 @@ class hierarchy_design(spice, layout): def __repr__(self): """ override print function output """ - text="( design: " + self.name + " pins=" + str(self.pins) + " " + str(self.width) + "x" + str(self.height) + " )\n" + text="( design: " + self.name + " pins=" + str(list(self.pins)) + " " + str(self.width) + "x" + str(self.height) + " )\n" for i in self.objs: text+=str(i) + ",\n" for i in self.insts: diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index 3bf2af7f..914ee2f0 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -13,13 +13,13 @@ from pprint import pformat from openram import debug from openram import tech from openram import OPTS - -from compiler.base.net_spice import net_spice +from collections import OrderedDict from .delay_data import delay_data from .wire_spice_model import wire_spice_model from .power_data import power_data from .logical_effort import convert_relative_c_to_farad, convert_farad_to_relative_c from .pin_spice import pin_spice +from .net_spice import net_spice class spice(): @@ -55,14 +55,15 @@ class spice(): # Holds subckts/mods for this module self.mods = set() # Holds the pins for this module (in order) - self.pins = [] + # on Python3.7+ regular dictionaries guarantee order too, but we allow use of v3.5+ + self.pins = OrderedDict() # An (optional) list of indices to reorder the pins to match the spice. self.pin_indices = [] # THE CONNECTIONS MUST MATCH THE ORDER OF THE PINS (restriction imposed by the # Spice format) # internal nets, which may or may not be connected to pins of the same name - self.nets = [] - # If this is set, it will out output subckt or isntances of this (for row/col caps etc.) + self.nets = {} + # If this is set, it will not output subckt or instances of this (for row/col caps etc.) self.no_instances = False # If we are doing a trimmed netlist, these are the instance that will be filtered self.trim_insts = set() @@ -90,9 +91,8 @@ class spice(): def add_pin(self, name, pin_type="INOUT"): """ Adds a pin to the pins list. Default type is INOUT signal. """ - new_pin = pin_spice(name, pin_type) - debug.check(new_pin not in self.pins, "cannot add duplicate spice pin") - self.pins.append(new_pin) + debug.check(name not in self.pins, "cannot add duplicate spice pin {}".format(name)) + self.pins[name] = pin_spice(name, pin_type) def add_pin_list(self, pin_list, pin_type="INOUT"): """ Adds a pin_list to the pins list """ @@ -122,20 +122,17 @@ class spice(): def update_pin_types(self, type_list): """ Change pin types for all the cell's pins. """ - if len(type_list) != len(self.pins): - debug.error("{} spice subcircuit number of port types does not match number of pins\ - \n SPICE names={}\ - \n Module names={}\ - ".format(self.name, self.pins, type_list), 1) - for pin, type in zip(self.pins, type_list): + debug.check(len(type_list) == len(self.pins), + "{} spice subcircuit number of port types does not match number of pins\ + \n pin names={}\n port types={}".format(self.name, list(self.pins), type_list)) + for pin, type in zip(self.pins.values(), type_list): pin.set_pin_type(type) def get_pin_type(self, name): """ Returns the type of the signal pin. """ - for pin in self.pins: - if pin.name == name: - return pin.type - debug.error("Spice pin {} not found".format(name)) + pin = self.pins.get(name) + debug.check(pin is not None, "Spice pin {} not found".format(name)) + return pin.type def get_pin_dir(self, name): """ Returns the direction of the pin. (Supply/ground are INOUT). """ @@ -148,10 +145,10 @@ class spice(): def get_inputs(self): """ These use pin types to determine pin lists. - Returns names only to maintain historical interface. + Returns names only, to maintain historical interface. """ input_list = [] - for pin in self.pins: + for pin in self.pins.values(): if pin.type == "INPUT": input_list.append(pin.name) return input_list @@ -159,26 +156,28 @@ class spice(): def get_outputs(self): """ These use pin types to determine pin lists. - Returns names only to maintain historical interface. + Returns names only, to maintain historical interface. """ output_list = [] - for pin in self.pins: + for pin in self.pins.values(): if pin.type == "OUTPUT": output_list.append(pin.name) return output_list def get_inouts(self): - """ These use pin types to determine pin lists. These - may be over-ridden by submodules that didn't use pin directions yet.""" + """ + These use pin types to determine pin lists. + Returns names only, to maintain historical interface. + """ inout_list = [] - for pin in self.pins: + for pin in self.pins.values(): if pin.type == "INOUT": inout_list.append(pin.name) return inout_list def copy_pins(self, other_module, suffix=""): """ This will copy all of the pins from the other module and add an optional suffix.""" - for pin in other_module.pins: + for pin in other_module.pins.values(): self.add_pin(pin.name + suffix, pin.type) def connect_inst(self, args): @@ -186,7 +185,7 @@ class spice(): Connects the pins of the last instance added """ - spice_pins = self.insts[-1].spice_pins + spice_pins = list(self.insts[-1].spice_pins) num_pins = len(spice_pins) num_args = len(args) @@ -241,8 +240,7 @@ class spice(): subckt = re.compile("^.subckt {}".format(self.cell_name), re.IGNORECASE) subckt_line = list(filter(subckt.search, self.spice))[0] # parses line into ports and remove subckt - # FIXME: needs to use new pins interface - self.pins = subckt_line.split(" ")[2:] + self.add_pin_list(subckt_line.split(" ")[2:]) else: debug.info(4, "no spfile {0}".format(self.sp_file)) self.spice = [] @@ -263,10 +261,10 @@ class spice(): subckt_line = list(filter(subckt.search, self.lvs))[0] # parses line into ports and remove subckt lvs_pins = subckt_line.split(" ")[2:] - debug.check(lvs_pins == self.pins, + debug.check(lvs_pins == list(self.pins), "Spice netlists for LVS and simulation have port mismatches:\n{0} (LVS {1})\nvs\n{2} (sim {3})".format(lvs_pins, self.lvs_file, - self.pins, + list(self.pins), self.sp_file)) def check_net_in_spice(self, net_name): @@ -299,6 +297,8 @@ class spice(): return False def sp_write_file(self, sp, usedMODS, lvs=False, trim=False): + # FIXME: this function refers to conns for connections but that no longer exists. + # it should be possible to just query the instances themselves for their connections. """ Recursive spice subcircuit write; Writes the spice subcircuit from the library or the dynamically generated one. @@ -319,29 +319,17 @@ class spice(): if len(self.insts) == 0: return - if self.pins == []: + if len(self.pins) == 0: return # write out the first spice line (the subcircuit) - wrapped_pins = "\n+ ".join(tr.wrap(" ".join(self.pins))) + wrapped_pins = "\n+ ".join(tr.wrap(" ".join(list(self.pins)))) sp.write("\n.SUBCKT {0}\n+ {1}\n".format(self.cell_name, wrapped_pins)) - # write a PININFO line - if False: - pin_info = "*.PININFO" - for pin in self.pins: - if self.pin_type[pin] == "INPUT": - pin_info += " {0}:I".format(pin) - elif self.pin_type[pin] == "OUTPUT": - pin_info += " {0}:O".format(pin) - else: - pin_info += " {0}:B".format(pin) - sp.write(pin_info + "\n") - # Also write pins as comments - for pin in self.pins: - sp.write("* {1:6}: {0} \n".format(pin, pin.type)) + for pin in self.pins.values(): + sp.write("* {1:6}: {0} \n".format(pin.name, pin.type)) for line in self.comments: sp.write("* {}\n".format(line)) From e15feb236148e96d76c2673ff529a179b1d29cbe Mon Sep 17 00:00:00 2001 From: Sam Crow Date: Mon, 17 Jul 2023 15:36:22 -0700 Subject: [PATCH 04/15] change nets list to dictionary --- compiler/base/hierarchy_spice.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index 914ee2f0..b03fa4ef 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -214,13 +214,10 @@ class spice(): def create_nets(self, names_list): nets = [] for name in names_list: - try: - i = self.nets.index(name) - nets.append(self.nets[i]) - except ValueError: - net = net_spice(name) - self.nets.append(net) - nets.append(net) + # setdefault adds to the dict if it doesn't find the net in it already + # then it returns the net it found or created, a net_spice object + net = self.nets.setdefault(name, net_spice(name)) + nets.append(net) return nets def sp_read(self): From d0339a90e63939c75e74a07ded6035b6bc6b023a Mon Sep 17 00:00:00 2001 From: Sam Crow Date: Mon, 17 Jul 2023 15:36:57 -0700 Subject: [PATCH 05/15] change spice_nets and spice_pins to dicts --- compiler/base/geometry.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/base/geometry.py b/compiler/base/geometry.py index 18e1a39b..29093a2d 100644 --- a/compiler/base/geometry.py +++ b/compiler/base/geometry.py @@ -181,8 +181,10 @@ class instance(geometry): # change attributes in these spice objects self.spice_pins = copy.deepcopy(self.mod.pins) self.spice_nets = copy.deepcopy(self.mod.nets) - for spice_obj in self.spice_pins + self.spice_nets: - spice_obj.set_inst(self) + for pin in self.spice_pins.values(): + pin.set_inst(self) + for net in self.spice_nets.values(): + net.set_inst(self) if OPTS.netlist_only: self.width = 0 From 45b88889e4e03648ac718c15b6066dadb3219b0c Mon Sep 17 00:00:00 2001 From: Sam Crow Date: Mon, 17 Jul 2023 16:04:56 -0700 Subject: [PATCH 06/15] use pin and net objects in connect_inst --- compiler/base/geometry.py | 14 +++++++++++--- compiler/base/hierarchy_spice.py | 2 +- compiler/base/pin_spice.py | 5 ++++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/compiler/base/geometry.py b/compiler/base/geometry.py index 29093a2d..a8ac89cb 100644 --- a/compiler/base/geometry.py +++ b/compiler/base/geometry.py @@ -285,9 +285,17 @@ class instance(geometry): return new_pins def connect_spice_pins(self, nets_list): - for i in range(len(self.pins)): - self.pins[i].set_inst_net(nets_list[i]) - nets_list[i].connect_pin(self.pins[i]) + """ + add the connection between instance pins and module nets + to both of their respective objects + nets_list must be the same length as self.spice_pins + """ + debug.check(len(self.spice_pins) == len(nets_list), + "must provide list of nets the same length as pin list\ + when connecting an instance") + for i in range(len(self.spice_pins)): + self.spice_pins[i].set_inst_net(nets_list[i]) + nets_list[i].connect_pin(self.spice_pins[i]) def calculate_transform(self, node): #set up the rotation matrix diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index b03fa4ef..e0d4f573 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -192,7 +192,7 @@ class spice(): # Order the arguments if the hard cell has a custom port order ordered_args = self.get_ordered_inputs(args) - if (check and num_pins != num_args): + if (num_pins != num_args): if num_pins < num_args: mod_pins = spice_pins + [""] * (num_args - num_pins) arg_pins = ordered_args diff --git a/compiler/base/pin_spice.py b/compiler/base/pin_spice.py index 0d2d2342..1b101bfd 100644 --- a/compiler/base/pin_spice.py +++ b/compiler/base/pin_spice.py @@ -13,7 +13,7 @@ class pin_spice: """ A class to represent a spice netlist pin. mod is the parent module that created this pin. - mod_net is the net object of this pin's parent module. + mod_net is the net object of this pin's parent module. It must have the same name as the pin. inst is the instance this pin is a part of, if any. inst_net is the net object from mod's nets which connects to this pin. """ @@ -45,6 +45,9 @@ class pin_spice: self.inst = inst def set_inst_net(self, net): + debug.check(self.inst_net is None, + "pin {} is already connected to net {} so it cannot also be connected to net {}\ + ".format(self.name, self.inst_net.name, net.name)) debug.check(isinstance(net, net_spice), "net must be a net_spice object") self.inst_net = net From 478c76c1cae2ae90c33fa7fed7d3d68ef68c656c Mon Sep 17 00:00:00 2001 From: Sam Crow Date: Tue, 18 Jul 2023 10:50:50 -0700 Subject: [PATCH 07/15] get connections from spice objects in instances --- compiler/base/geometry.py | 11 ++++++ compiler/base/hierarchy_design.py | 4 +- compiler/base/hierarchy_spice.py | 63 ++++++++++++++++--------------- 3 files changed, 46 insertions(+), 32 deletions(-) diff --git a/compiler/base/geometry.py b/compiler/base/geometry.py index a8ac89cb..9eedc7f5 100644 --- a/compiler/base/geometry.py +++ b/compiler/base/geometry.py @@ -176,6 +176,8 @@ class instance(geometry): self.rotate = rotate self.offset = vector(offset).snap_to_grid() self.mirror = mirror + # track if the instance's spice pin connections have been made + self.connected = False # deepcopy because this instance needs to # change attributes in these spice objects @@ -290,12 +292,21 @@ class instance(geometry): to both of their respective objects nets_list must be the same length as self.spice_pins """ + debug.check(not self.connected, + "instance {} has already been connected".format(self.name)) debug.check(len(self.spice_pins) == len(nets_list), "must provide list of nets the same length as pin list\ when connecting an instance") for i in range(len(self.spice_pins)): self.spice_pins[i].set_inst_net(nets_list[i]) nets_list[i].connect_pin(self.spice_pins[i]) + self.connected = True + + def get_connections(self): + conns = [] + for pin in self.spice_pins: + conns.append(pin.inst_net.name) + return conns def calculate_transform(self, node): #set up the rotation matrix diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index c34bc5f5..31a1e191 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -139,7 +139,7 @@ class hierarchy_design(spice, layout): 1) port_dict = {pin: port for pin, port in zip(list(self.pins), port_nets)} debug.info(3, "Instance name={}".format(inst_name)) - for subinst, conns in zip(self.insts, self.conns): + for subinst, conns in zip(self.insts, self.get_instance_connections()): if subinst in self.graph_inst_exclude: continue subinst_name = inst_name + "{}x".format(OPTS.hier_seperator) + subinst.name @@ -157,7 +157,7 @@ class hierarchy_design(spice, layout): 1) port_dict = {pin: port for pin, port in zip(list(self.pins), port_nets)} debug.info(3, "Instance name={}".format(inst_name)) - for subinst, conns in zip(self.insts, self.conns): + for subinst, conns in zip(self.insts, self.get_instance_connections()): subinst_name = inst_name + "{}x".format(OPTS.hier_seperator) + subinst.name subinst_ports = self.translate_nets(conns, port_dict, inst_name) for si_port, conn in zip(subinst_ports, conns): diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index e0d4f573..96357209 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -209,8 +209,6 @@ class spice(): ordered_nets = self.create_nets(ordered_args) self.insts[-1].connect_spice_pins(ordered_nets) - # TODO: replace functionality that checked if user forgot to connect an inst before connecting the next - def create_nets(self, names_list): nets = [] for name in names_list: @@ -294,8 +292,6 @@ class spice(): return False def sp_write_file(self, sp, usedMODS, lvs=False, trim=False): - # FIXME: this function refers to conns for connections but that no longer exists. - # it should be possible to just query the instances themselves for their connections. """ Recursive spice subcircuit write; Writes the spice subcircuit from the library or the dynamically generated one. @@ -308,11 +304,11 @@ class spice(): # If spice isn't defined, we dynamically generate one. # recursively write the modules - for i in self.mods: - if self.contains(i, usedMODS): + for mod in self.mods: + if self.contains(mod, usedMODS): continue - usedMODS.append(i) - i.sp_write_file(sp, usedMODS, lvs, trim) + usedMODS.append(mod) + mod.sp_write_file(sp, usedMODS, lvs, trim) if len(self.insts) == 0: return @@ -331,43 +327,44 @@ class spice(): for line in self.comments: sp.write("* {}\n".format(line)) - # every instance must have a set of connections, even if it is empty. - if len(self.insts) != len(self.conns): - debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.cell_name, - len(self.insts), - len(self.conns))) - debug.error("Instances: \n" + str(self.insts)) - debug.error("-----") - debug.error("Connections: \n" + str(self.conns), 1) + # every instance must be connected with the connect_inst function + # TODO: may run into empty pin lists edge case, not sure yet + connected = True + for inst in self.insts: + if inst.connected: + continue + debug.error("Instance {} spice pins not connected".format(str(inst))) + connected = False + debug.check(connected, "{0} : Not all instance spice pins are connected.".format(self.cell_name)) - for i in range(len(self.insts)): + for inst in self.insts: # we don't need to output connections of empty instances. # these are wires and paths - if self.conns[i] == []: + if len(inst.spice_pins) == 0: continue # Instance with no devices in it needs no subckt/instance - if self.insts[i].mod.no_instances: + if inst.mod.no_instances: continue # If this is a trimmed netlist, skip it by adding comment char - if trim and self.insts[i].name in self.trim_insts: + if trim and inst.name in self.trim_insts: sp.write("* ") - if lvs and hasattr(self.insts[i].mod, "lvs_device"): - sp.write(self.insts[i].mod.lvs_device.format(self.insts[i].name, - " ".join(self.conns[i]))) + if lvs and hasattr(inst.mod, "lvs_device"): + sp.write(inst.mod.lvs_device.format(inst.name, + " ".join(inst.get_connections()))) sp.write("\n") - elif hasattr(self.insts[i].mod, "spice_device"): - sp.write(self.insts[i].mod.spice_device.format(self.insts[i].name, - " ".join(self.conns[i]))) + elif hasattr(inst.mod, "spice_device"): + sp.write(inst.mod.spice_device.format(inst.name, + " ".join(inst.get_connections()))) sp.write("\n") else: - wrapped_connections = "\n+ ".join(tr.wrap(" ".join(self.conns[i]))) + wrapped_connections = "\n+ ".join(tr.wrap(" ".join(inst.get_connections()))) - sp.write("X{0}\n+ {1}\n+ {2}\n".format(self.insts[i].name, + sp.write("X{0}\n+ {1}\n+ {2}\n".format(inst.name, wrapped_connections, - self.insts[i].mod.cell_name)) + inst.mod.cell_name)) sp.write(".ENDS {0}\n".format(self.cell_name)) @@ -696,6 +693,12 @@ class spice(): aliases.append(net) return aliases + def get_instance_connections(self): + conns = [] + for inst in self.instances: + conns.append(inst.get_connections()) + return conns + def is_net_alias(self, known_net, net_alias, mod, exclusion_set): """ Checks if the alias_net in input mod is the same as the input net for this mod (self). @@ -708,7 +711,7 @@ class spice(): return True # Check connections of all other subinsts mod_set = set() - for subinst, inst_conns in zip(self.insts, self.conns): + for subinst, inst_conns in zip(self.insts, self.get_instance_connections()): for inst_conn, mod_pin in zip(inst_conns, subinst.mod.pins): if self.is_net_alias_name_check(known_net, inst_conn, net_alias, mod): return True From aa71785bd51f945d4f6ae1b3ed70f2d8b078b18c Mon Sep 17 00:00:00 2001 From: Sam Crow Date: Tue, 18 Jul 2023 11:28:30 -0700 Subject: [PATCH 08/15] fix circular import with pin and net --- compiler/base/hierarchy_spice.py | 106 ++++++++++++++++++++++++++++++- compiler/base/net_spice.py | 51 --------------- compiler/base/pin_spice.py | 71 --------------------- 3 files changed, 104 insertions(+), 124 deletions(-) delete mode 100644 compiler/base/net_spice.py delete mode 100644 compiler/base/pin_spice.py diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index 96357209..4803fb65 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -18,8 +18,6 @@ from .delay_data import delay_data from .wire_spice_model import wire_spice_model from .power_data import power_data from .logical_effort import convert_relative_c_to_farad, convert_farad_to_relative_c -from .pin_spice import pin_spice -from .net_spice import net_spice class spice(): @@ -728,3 +726,107 @@ class spice(): return self == mod and \ child_net.lower() == alias_net.lower() and \ parent_net.lower() == alias_net.lower() + + +class pin_spice(): + """ + A class to represent a spice netlist pin. + mod is the parent module that created this pin. + mod_net is the net object of this pin's parent module. It must have the same name as the pin. + inst is the instance this pin is a part of, if any. + inst_net is the net object from mod's nets which connects to this pin. + """ + + valid_pin_types = ["INOUT", "INPUT", "OUTPUT", "POWER", "GROUND"] + + def __init__(self, name, type, mod): + self.name = name + self.set_type(type) + self.mod = mod + self.mod_net = None + self.inst = None + self.inst_net = None + + # TODO: evaluate if this makes sense... and works + self._hash = hash(self.name) + + def set_type(self, type): + debug.check(type in pin_spice.valid_pin_types, + "Invalid pin type for {0}: {1}".format(self.name, type)) + self.type = type + + def set_mod_net(self, net): + debug.check(isinstance(net, net_spice), "net must be a net_spice object") + debug.check(net.name == self.name, "module spice net must have same name as spice pin") + self.mod_net = net + + def set_inst(self, inst): + self.inst = inst + + def set_inst_net(self, net): + debug.check(self.inst_net is None, + "pin {} is already connected to net {} so it cannot also be connected to net {}\ + ".format(self.name, self.inst_net.name, net.name)) + debug.check(isinstance(net, net_spice), "net must be a net_spice object") + self.inst_net = net + + def __str__(self): + """ override print function output """ + return "(pin_name={} type={})".format(self.name, self.type) + + def __repr__(self): + """ override repr function output """ + return self.name + + def __eq__(self, name): + return (name == self.name) if isinstance(name, str) else super().__eq__(name) + + def __hash__(self): + """ + Implement the hash function for sets etc. + Only hash name since spice does not allow two pins to share a name. + Provides a speedup if pin_spice is used as a key for dicts. + """ + return self._hash + + +class net_spice(): + """ + A class to represent a spice net. + pins are all the pins connected to this net. + inst is the instance this net is a part of, if any. + """ + + def __init__(self, name): + self.name = name + self.pins = [] + self.inst = None + + # TODO: evaluate if this makes sense... and works + self._hash = hash(self.name) + + def connect_pin(self, pin): + debug.check(isinstance(pin, pin_spice), "pin must be a pin_spice object") + if pin in self.pins: + debug.warning("pin {} was already connected to net {} ... why was it connected again?".format(pin.name, self.name)) + else: + self.pins.append(pin) + + def __str__(self): + """ override print function output """ + return "(pin_name={} type={})".format(self.name, self.type) + + def __repr__(self): + """ override repr function output """ + return self.name + + def __eq__(self, name): + return (name == self.name) if isinstance(name, str) else super().__eq__(name) + + def __hash__(self): + """ + Implement the hash function for sets etc. + Only hash name since spice does not allow two pins to share a name. + Provides a speedup if pin_spice is used as a key for dicts. + """ + return self._hash diff --git a/compiler/base/net_spice.py b/compiler/base/net_spice.py deleted file mode 100644 index d1c24ab6..00000000 --- a/compiler/base/net_spice.py +++ /dev/null @@ -1,51 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2023 Regents of the University of California and The Board -# of Regents for the Oklahoma Agricultural and Mechanical College -# (acting for and on behalf of Oklahoma State University) -# All rights reserved. -# -from openram import debug -from .pin_spice import pin_spice - - -class net_spice: - """ - A class to represent a spice net. - pins are all the pins connected to this net. - inst is the instance this net is a part of, if any. - """ - - def __init__(self, name): - self.name = name - self.pins = [] - self.inst = None - - # TODO: evaluate if this makes sense... and works - self._hash = hash(self.name) - - def connect_pin(self, pin): - debug.check(isinstance(pin, pin_spice), "pin must be a pin_spice object") - if pin in self.pins: - debug.warning("pin {} was already connected to net {} ... why was it connected again?".format(pin.name, self.name)) - else: - self.pins.append(pin) - - def __str__(self): - """ override print function output """ - return "(pin_name={} type={})".format(self.name, self.type) - - def __repr__(self): - """ override repr function output """ - return self.name - - def __eq__(self, name): - return (name == self.name) if isinstance(name, str) else super().__eq__(name) - - def __hash__(self): - """ - Implement the hash function for sets etc. - Only hash name since spice does not allow two pins to share a name. - Provides a speedup if pin_spice is used as a key for dicts. - """ - return self._hash diff --git a/compiler/base/pin_spice.py b/compiler/base/pin_spice.py deleted file mode 100644 index 1b101bfd..00000000 --- a/compiler/base/pin_spice.py +++ /dev/null @@ -1,71 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2023 Regents of the University of California and The Board -# of Regents for the Oklahoma Agricultural and Mechanical College -# (acting for and on behalf of Oklahoma State University) -# All rights reserved. -# -from openram import debug -from .net_spice import net_spice - - -class pin_spice: - """ - A class to represent a spice netlist pin. - mod is the parent module that created this pin. - mod_net is the net object of this pin's parent module. It must have the same name as the pin. - inst is the instance this pin is a part of, if any. - inst_net is the net object from mod's nets which connects to this pin. - """ - - valid_pin_types = ["INOUT", "INPUT", "OUTPUT", "POWER", "GROUND"] - - def __init__(self, name, type, mod): - self.name = name - self.set_type(type) - self.mod = mod - self.mod_net = None - self.inst = None - self.inst_net = None - - # TODO: evaluate if this makes sense... and works - self._hash = hash(self.name) - - def set_type(self, type): - debug.check(type in pin_spice.valid_pin_types, - "Invalid pin type for {0}: {1}".format(self.name, type)) - self.type = type - - def set_mod_net(self, net): - debug.check(isinstance(net, net_spice), "net must be a net_spice object") - debug.check(net.name == self.name, "module spice net must have same name as spice pin") - self.mod_net = net - - def set_inst(self, inst): - self.inst = inst - - def set_inst_net(self, net): - debug.check(self.inst_net is None, - "pin {} is already connected to net {} so it cannot also be connected to net {}\ - ".format(self.name, self.inst_net.name, net.name)) - debug.check(isinstance(net, net_spice), "net must be a net_spice object") - self.inst_net = net - - def __str__(self): - """ override print function output """ - return "(pin_name={} type={})".format(self.name, self.type) - - def __repr__(self): - """ override repr function output """ - return self.name - - def __eq__(self, name): - return (name == self.name) if isinstance(name, str) else super().__eq__(name) - - def __hash__(self): - """ - Implement the hash function for sets etc. - Only hash name since spice does not allow two pins to share a name. - Provides a speedup if pin_spice is used as a key for dicts. - """ - return self._hash From 5907cbb3e2904b1d0e2700e023e1246b728d6f8b Mon Sep 17 00:00:00 2001 From: Sam Crow Date: Tue, 18 Jul 2023 16:12:42 -0700 Subject: [PATCH 09/15] remove pins overwrite from contact class --- compiler/base/contact.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/base/contact.py b/compiler/base/contact.py index 5fe59bcb..84f60d0e 100644 --- a/compiler/base/contact.py +++ b/compiler/base/contact.py @@ -66,8 +66,6 @@ class contact(hierarchy_design): self.offset = vector(0, 0) self.implant_type = implant_type self.well_type = well_type - # Module does not have pins, but has empty pin list. - self.pins = [] self.create_layout() def create_layout(self): From 09aa395174513f750f66bb5bd4b855d78d91d15b Mon Sep 17 00:00:00 2001 From: Sam Crow Date: Tue, 18 Jul 2023 16:13:29 -0700 Subject: [PATCH 10/15] cast pins dict to list --- compiler/modules/column_decoder.py | 2 +- compiler/modules/pbitcell.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/modules/column_decoder.py b/compiler/modules/column_decoder.py index 92149a71..ab80b1c9 100644 --- a/compiler/modules/column_decoder.py +++ b/compiler/modules/column_decoder.py @@ -42,7 +42,7 @@ class column_decoder(design): def create_instances(self): self.column_decoder_inst = self.add_inst(name="column_decoder", mod=self.column_decoder) - self.connect_inst(self.pins) + self.connect_inst(list(self.pins)) def create_layout(self): self.column_decoder_inst.place(vector(0,0)) diff --git a/compiler/modules/pbitcell.py b/compiler/modules/pbitcell.py index 2eab6e28..9c1800bd 100644 --- a/compiler/modules/pbitcell.py +++ b/compiler/modules/pbitcell.py @@ -1212,7 +1212,7 @@ class pbitcell(bitcell_base): if self.dummy_bitcell: return - pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)} + pin_dict = {pin: port for pin, port in zip(list(self.pins), port_nets)} # Edges added wl->bl, wl->br for every port except write ports rw_pin_names = zip(self.r_wl_names, self.r_bl_names, self.r_br_names) From bfabe64f33f9fe230931b7d71edafcf3eed7781f Mon Sep 17 00:00:00 2001 From: Sam Crow Date: Tue, 18 Jul 2023 16:14:38 -0700 Subject: [PATCH 11/15] fix pin/net dictionary deepcopy-ing --- compiler/base/geometry.py | 14 +++++--- compiler/base/hierarchy_layout.py | 3 +- compiler/base/hierarchy_spice.py | 58 ++++++++++++++++++++++++++----- 3 files changed, 61 insertions(+), 14 deletions(-) diff --git a/compiler/base/geometry.py b/compiler/base/geometry.py index 9eedc7f5..d397f7a3 100644 --- a/compiler/base/geometry.py +++ b/compiler/base/geometry.py @@ -292,19 +292,25 @@ class instance(geometry): to both of their respective objects nets_list must be the same length as self.spice_pins """ + if len(nets_list) == 0 and len(self.spice_pins) == 0: + # this is the only valid case to skip the following debug check + # because this with no pins are often connected arbitrarily + self.connected = True + return debug.check(not self.connected, "instance {} has already been connected".format(self.name)) debug.check(len(self.spice_pins) == len(nets_list), "must provide list of nets the same length as pin list\ when connecting an instance") - for i in range(len(self.spice_pins)): - self.spice_pins[i].set_inst_net(nets_list[i]) - nets_list[i].connect_pin(self.spice_pins[i]) + for pin in self.spice_pins.values(): + net = nets_list.pop(0) + pin.set_inst_net(net) + net.connect_pin(pin) self.connected = True def get_connections(self): conns = [] - for pin in self.spice_pins: + for pin in self.spice_pins.values(): conns.append(pin.inst_net.name) return conns diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index d33d552d..f9d3e889 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -629,7 +629,7 @@ class layout(): """ Return a pin list of all pins """ - return self.pins + return list(self.pins) def copy_layout_pin(self, instance, pin_name, new_name="", relative_offset=vector(0, 0)): """ @@ -1523,6 +1523,7 @@ class layout(): """ Return the pin shapes as blockages for non-top-level blocks. """ # FIXME: We don't have a body contact in ptx, so just ignore it for now import copy + # FIXME: this may not work now that self.pins is a dict as defined in hierarchy_spice pin_names = copy.deepcopy(self.pins) if self.name.startswith("pmos") or self.name.startswith("nmos"): pin_names.remove("B") diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index 4803fb65..952fa080 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -90,13 +90,13 @@ class spice(): def add_pin(self, name, pin_type="INOUT"): """ Adds a pin to the pins list. Default type is INOUT signal. """ debug.check(name not in self.pins, "cannot add duplicate spice pin {}".format(name)) - self.pins[name] = pin_spice(name, pin_type) + self.pins[name] = pin_spice(name, pin_type, self) def add_pin_list(self, pin_list, pin_type="INOUT"): """ Adds a pin_list to the pins list """ # The pin type list can be a single type for all pins # or a list that is the same length as the pin list. - if type(pin_type) == str: + if isinstance(pin_type, str): for pin in pin_list: self.add_pin(pin, pin_type) @@ -124,7 +124,7 @@ class spice(): "{} spice subcircuit number of port types does not match number of pins\ \n pin names={}\n port types={}".format(self.name, list(self.pins), type_list)) for pin, type in zip(self.pins.values(), type_list): - pin.set_pin_type(type) + pin.set_type(type) def get_pin_type(self, name): """ Returns the type of the signal pin. """ @@ -212,7 +212,7 @@ class spice(): for name in names_list: # setdefault adds to the dict if it doesn't find the net in it already # then it returns the net it found or created, a net_spice object - net = self.nets.setdefault(name, net_spice(name)) + net = self.nets.setdefault(name, net_spice(name, self)) nets.append(net) return nets @@ -764,9 +764,10 @@ class pin_spice(): self.inst = inst def set_inst_net(self, net): - debug.check(self.inst_net is None, - "pin {} is already connected to net {} so it cannot also be connected to net {}\ - ".format(self.name, self.inst_net.name, net.name)) + if self.inst_net is not None: + debug.error("pin {} is already connected to net {}\ + so it cannot also be connected to net {}\ + ".format(self.name, self.inst_net.name, net.name), 1) debug.check(isinstance(net, net_spice), "net must be a net_spice object") self.inst_net = net @@ -789,6 +790,23 @@ class pin_spice(): """ return self._hash + def __deepcopy__(original, memo): + """ + This function is defined so that instances of modules can make deep + copies of their parent module's pins dictionary. It is only expected + to be called by the instance class __init__ function. Mod and mod_net + should not be deep copies but references to the existing mod and net + objects they refer to in the original. If inst is already defined this + function will throw an error because that means it was called on a pin + from an instance, which is not defined behavior. + """ + debug.check(original.inst is None, + "cannot make a deepcopy of a spice pin from an inst") + pin = pin_spice(original.name, original.type, original.mod) + if original.mod_net is not None: + pin.set_mod_net(original.mod_net) + return pin + class net_spice(): """ @@ -797,9 +815,10 @@ class net_spice(): inst is the instance this net is a part of, if any. """ - def __init__(self, name): + def __init__(self, name, mod): self.name = name self.pins = [] + self.mod = mod self.inst = None # TODO: evaluate if this makes sense... and works @@ -812,9 +831,12 @@ class net_spice(): else: self.pins.append(pin) + def set_inst(self, inst): + self.inst = inst + def __str__(self): """ override print function output """ - return "(pin_name={} type={})".format(self.name, self.type) + return "(net_name={} type={})".format(self.name, self.type) def __repr__(self): """ override repr function output """ @@ -830,3 +852,21 @@ class net_spice(): Provides a speedup if pin_spice is used as a key for dicts. """ return self._hash + + def __deepcopy__(original, memo): + """ + This function is defined so that instances of modules can make deep + copies of their parent module's nets dictionary. It is only expected + to be called by the instance class __init__ function. Mod and mod_net + should not be deep copies but references to the existing mod and net + objects they refer to in the original. If inst is already defined this + function will throw an error because that means it was called on a pin + from an instance, which is not defined behavior. + """ + debug.check(original.inst is None, + "cannot make a deepcopy of a spice net from an inst") + net = net_spice(original.name, original.mod) + if original.pins != []: + # TODO: honestly I'm not sure if this is right but we'll see... + net.pins = original.pins + return net From 2b9e70d318bdc6a2f69c7043512cf890c665a790 Mon Sep 17 00:00:00 2001 From: Sam Crow Date: Wed, 19 Jul 2023 10:51:19 -0700 Subject: [PATCH 12/15] remove line ending whitespace from comment --- compiler/base/geometry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/base/geometry.py b/compiler/base/geometry.py index d397f7a3..c691db5d 100644 --- a/compiler/base/geometry.py +++ b/compiler/base/geometry.py @@ -179,7 +179,7 @@ class instance(geometry): # track if the instance's spice pin connections have been made self.connected = False - # deepcopy because this instance needs to + # deepcopy because this instance needs to # change attributes in these spice objects self.spice_pins = copy.deepcopy(self.mod.pins) self.spice_nets = copy.deepcopy(self.mod.nets) From c67fdd8bd82e22b0a48776778609da974a25e6c7 Mon Sep 17 00:00:00 2001 From: Sam Crow Date: Wed, 19 Jul 2023 12:15:21 -0700 Subject: [PATCH 13/15] fix insts typo --- compiler/base/hierarchy_spice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index 952fa080..4eeaba93 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -693,7 +693,7 @@ class spice(): def get_instance_connections(self): conns = [] - for inst in self.instances: + for inst in self.insts: conns.append(inst.get_connections()) return conns From 2ced895b32d4750ee5c1268885d5d01e8ef421c0 Mon Sep 17 00:00:00 2001 From: Sam Crow Date: Wed, 19 Jul 2023 12:15:47 -0700 Subject: [PATCH 14/15] add BIAS pin type back to valid types --- compiler/base/hierarchy_spice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index 4eeaba93..acca174f 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -737,7 +737,7 @@ class pin_spice(): inst_net is the net object from mod's nets which connects to this pin. """ - valid_pin_types = ["INOUT", "INPUT", "OUTPUT", "POWER", "GROUND"] + valid_pin_types = ["INOUT", "INPUT", "OUTPUT", "POWER", "GROUND", "BIAS"] def __init__(self, name, type, mod): self.name = name From 6e3e964c1206fd98cc0fe10c5936064c2769c321 Mon Sep 17 00:00:00 2001 From: Sam Crow Date: Wed, 19 Jul 2023 12:45:41 -0700 Subject: [PATCH 15/15] cleanup net_spice docstrings --- compiler/base/hierarchy_spice.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index e8fe532c..2294208e 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -816,6 +816,7 @@ class pin_spice(): class net_spice(): """ A class to represent a spice net. + mod is the parent module that created this net. pins are all the pins connected to this net. inst is the instance this net is a part of, if any. """ @@ -853,8 +854,9 @@ class net_spice(): def __hash__(self): """ Implement the hash function for sets etc. - Only hash name since spice does not allow two pins to share a name. - Provides a speedup if pin_spice is used as a key for dicts. + Only hash name since spice does not allow two nets to share a name + (on the same level of hierarchy, or rather they will be the same net). + Provides a speedup if net_spice is used as a key for dicts. """ return self._hash @@ -862,10 +864,10 @@ class net_spice(): """ This function is defined so that instances of modules can make deep copies of their parent module's nets dictionary. It is only expected - to be called by the instance class __init__ function. Mod and mod_net - should not be deep copies but references to the existing mod and net - objects they refer to in the original. If inst is already defined this - function will throw an error because that means it was called on a pin + to be called by the instance class __init__ function. Mod + should not be a deep copy but a reference to the existing mod + object it refers to in the original. If inst is already defined this + function will throw an error because that means it was called on a net from an instance, which is not defined behavior. """ debug.check(original.inst is None,