diff --git a/compiler/base/custom_cell_properties.py b/compiler/base/custom_cell_properties.py index 2407f9c2..bb211842 100644 --- a/compiler/base/custom_cell_properties.py +++ b/compiler/base/custom_cell_properties.py @@ -6,13 +6,16 @@ # All rights reserved. # -class _cell: - def __init__(self, port_order, port_types, port_map=None, hard_cell=True, boundary_layer="boundary"): +class cell: + def __init__(self, port_order, port_types, port_map=None, body_bias=None, hard_cell=True, boundary_layer="boundary"): + + # Some cells may have body bias (well taps) exposed as ports + self._body_bias = body_bias # Specifies if this is a hard (i.e. GDS) cell self._hard_cell = hard_cell self._boundary_layer = boundary_layer - + # Specifies the port directions self._port_types_map = {x: y for (x, y) in zip(port_order, port_types)} @@ -20,7 +23,8 @@ class _cell: # by default it is 1:1 if not port_map: self._port_map = {x: x for x in port_order} - + else: + self._port_map = port_map # Update mapping of names self._original_port_order = port_order self._port_order = port_order @@ -47,14 +51,24 @@ class _cell: return self._port_order @port_order.setter - def port_order(self, x): - self._port_order = x - # Update ordered name list in the new order - self._port_names = [self._port_map[x] for x in self._port_order] - # Update ordered type list in the new order - self._port_types = [self._port_types_map[x] for x in self._port_order] - # Update the index array - self._port_indices = [self._port_order.index(x) for x in self._original_port_order] + def port_order(self, port_order): + # If we are going to redefine more ports (i.e. well biases) don't init stuff + old_port_len = len(self._port_order) + if old_port_len == len(port_order): + self._port_order = port_order + # Update ordered name list in the new order + self._port_names = [self._port_map[x] for x in self._port_order] + # Update ordered type list in the new order + self._port_types = [self._port_types_map[x] for x in self._port_order] + # Update the index array + self._port_indices = [self._port_order.index(x) for x in self._original_port_order] + else: + # Do the default constructor again except for types stuff which hasn't been set yet + self._port_order = port_order + self._original_port_order = self._port_order + self._port_map = {x: x for x in self._port_order} + self._port_indices = [self._port_order.index(x) for x in self._original_port_order] + self._port_names = [self._port_map[x] for x in self._port_order] @property def port_indices(self): @@ -65,15 +79,36 @@ class _cell: return self._port_map @port_map.setter - def port_map(self, x): - self._port_map = x + def port_map(self, port_map): + self._port_map = port_map # Update ordered name list to use the new names - self._port_names = [self.port_map[x] for x in self._port_order] + self._port_names = [self._port_map[x] for x in self._port_order] + + @property + def body_bias(self): + return self._body_bias + @body_bias.setter + def body_bias(self, body_bias): + # It is assumed it is [nwell, pwell] + self._body_bias = body_bias + self._port_map['vnb'] = body_bias[0] + self._port_types['vnb'] = "POWER" + self._port_map['vpb'] = body_bias[1] + self._port_types['vpb'] = "GROUND" + @property def port_types(self): return self._port_types + @port_types.setter + def port_types(self, port_types): + self._port_types = port_types + # Specifies the port directions + self._port_types_map = {x: y for (x, y) in zip(self._port_order, self._port_types)} + # Update ordered type list + self._port_types = [self._port_types_map[x] for x in self._port_order] + @property def boundary_layer(self): return self._boundary_layer @@ -108,7 +143,7 @@ class _pgate: self.add_implants = add_implants -class _bitcell(_cell): +class bitcell(cell): def __init__(self, port_order, port_types, port_map=None, storage_nets=["Q", "Q_bar"], mirror=None, end_caps=False): super().__init__(port_order, port_types, port_map) @@ -147,39 +182,45 @@ class cell_properties(): self._pgate = _pgate(add_implants=False) - self._inv_dec = _cell(["A", "Z", "vdd", "gnd"], + self._inv_dec = cell(["A", "Z", "vdd", "gnd"], ["INPUT", "OUTPUT", "POWER", "GROUND"]) - self._nand2_dec = _cell(["A", "B", "Z", "vdd", "gnd"], + self._nand2_dec = cell(["A", "B", "Z", "vdd", "gnd"], ["INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"]) - self._nand3_dec = _cell(["A", "B", "C", "Z", "vdd", "gnd"], + self._nand3_dec = cell(["A", "B", "C", "Z", "vdd", "gnd"], ["INPUT", "INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"]) - self._nand4_dec = _cell(["A", "B", "C", "D", "Z", "vdd", "gnd"], + self._nand4_dec = cell(["A", "B", "C", "D", "Z", "vdd", "gnd"], ["INPUT", "INPUT", "INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"]) - self._dff = _cell(["D", "Q", "clk", "vdd", "gnd"], + self._dff = cell(["D", "Q", "clk", "vdd", "gnd"], ["INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]) - self._write_driver = _cell(['din', 'bl', 'br', 'en', 'vdd', 'gnd'], + self._write_driver = cell(['din', 'bl', 'br', 'en', 'vdd', 'gnd'], ["INPUT", "OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]) - self._sense_amp = _cell(['bl', 'br', 'dout', 'en', 'vdd', 'gnd'], + self._sense_amp = cell(['bl', 'br', 'dout', 'en', 'vdd', 'gnd'], ["INPUT", "INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]) - self._bitcell_1port = _bitcell(["bl", "br", "wl", "vdd", "gnd"], + self._bitcell_1port = bitcell(["bl", "br", "wl", "vdd", "gnd"], ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]) - self._bitcell_2port = _bitcell(["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"], + self._bitcell_2port = bitcell(["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"], ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"]) - self._col_cap_2port = _bitcell(["bl0", "br0", "bl1", "br1", "vdd"], + self._col_cap_1port = bitcell(["bl", "br", "vdd"], + ["OUTPUT", "OUTPUT", "POWER"]) + + self._row_cap_1port = bitcell(["wl", "gnd"], + ["INPUT", "POWER", "GROUND"]) + + self._col_cap_2port = bitcell(["bl0", "br0", "bl1", "br1", "vdd"], ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "POWER"]) - self._row_cap_2port = _bitcell(["wl0", "wl1", "gnd"], + self._row_cap_2port = bitcell(["wl0", "wl1", "gnd"], ["INPUT", "INPUT", "POWER", "GROUND"]) - + @property def ptx(self): return self._ptx @@ -224,6 +265,14 @@ class cell_properties(): def bitcell_2port(self): return self._bitcell_2port + @property + def col_cap_1port(self): + return self._col_cap_1port + + @property + def row_cap_1port(self): + return self._row_cap_1port + @property def col_cap_2port(self): return self._col_cap_2port diff --git a/compiler/base/design.py b/compiler/base/design.py index 71c1ef92..c8bf3070 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -167,8 +167,8 @@ class design(hierarchy_design): from tech import layer_indices import tech - for layer in layer_indices: - key = "{}_stack".format(layer) + for layer_id in layer_indices: + key = "{}_stack".format(layer_id) # Set the stack as a local helper try: @@ -177,24 +177,24 @@ class design(hierarchy_design): except AttributeError: pass - # Skip computing the pitch for active - if layer == "active": + # Skip computing the pitch for non-routing layers + if layer_id in ["active", "nwell"]: continue # Add the pitch setattr(design, - "{}_pitch".format(layer), - design.compute_pitch(layer, True)) + "{}_pitch".format(layer_id), + design.compute_pitch(layer_id, True)) # Add the non-preferrd pitch (which has vias in the "wrong" way) setattr(design, - "{}_nonpref_pitch".format(layer), - design.compute_pitch(layer, False)) + "{}_nonpref_pitch".format(layer_id), + design.compute_pitch(layer_id, False)) if False: from tech import preferred_directions print(preferred_directions) - from tech import layer, layer_indices + from tech import layer_indices for name in layer_indices: if name == "active": continue diff --git a/compiler/base/graph_util.py b/compiler/base/graph_util.py index d7afbf26..909e94c8 100644 --- a/compiler/base/graph_util.py +++ b/compiler/base/graph_util.py @@ -117,6 +117,14 @@ class timing_graph(): cur_slew = delays[-1].slew return delays + + def get_edge_mods(self, path): + """Return all edge mods associated with path""" + + if len(path) == 0: + return [] + + return [self.edge_mods[(path[i], path[i+1])] for i in range(len(path)-1)] def __str__(self): """ override print function output """ diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index b5d9b8de..e776ac3c 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -8,9 +8,7 @@ import hierarchy_layout import hierarchy_spice import debug -import os from globals import OPTS -import tech class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): @@ -72,10 +70,6 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): "LVS failed for {0} with {1} errors(s)".format(self.cell_name, self.lvs_errors)) - if not OPTS.keep_temp: - os.remove(tempspice) - os.remove(tempgds) - def DRC(self, final_verification=False): """Checks DRC for a module""" import verify @@ -96,9 +90,6 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): "DRC failed for {0} with {1} error(s)".format(self.cell_name, num_errors)) - if not OPTS.keep_temp: - os.remove(tempgds) - def LVS(self, final_verification=False): """Checks LVS for a module""" import verify @@ -118,9 +109,6 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): debug.check(num_errors == 0, "LVS failed for {0} with {1} error(s)".format(self.cell_name, num_errors)) - if not OPTS.keep_temp: - os.remove(tempspice) - os.remove(tempgds) def init_graph_params(self): """ diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index 4a279ba7..29f03c7f 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -48,7 +48,7 @@ class spice(): else: self.lvs_file = self.sp_file - self.valid_signal_types = ["INOUT", "INPUT", "OUTPUT", "POWER", "GROUND"] + self.valid_signal_types = ["INOUT", "INPUT", "OUTPUT", "BIAS", "POWER", "GROUND"] # Holds subckts/mods for this module self.mods = [] # Holds the pins for this module (in order) diff --git a/compiler/bitcells/bitcell_1port.py b/compiler/bitcells/bitcell_1port.py index f3ff4d10..08180607 100644 --- a/compiler/bitcells/bitcell_1port.py +++ b/compiler/bitcells/bitcell_1port.py @@ -22,41 +22,14 @@ class bitcell_1port(bitcell_base.bitcell_base): super().__init__(name, prop=props.bitcell_1port) debug.info(2, "Create bitcell") - def get_all_wl_names(self): - """ Creates a list of all wordline pin names """ - row_pins = ["wl"] - return row_pins - - def get_all_bitline_names(self): - """ Creates a list of all bitline pin names (both bl and br) """ - return ["bl", "br"] - - def get_all_bl_names(self): - """ Creates a list of all bl pins names """ - return ["bl"] - - def get_all_br_names(self): - """ Creates a list of all br pins names """ - return ["br"] - - def get_bl_name(self, port=0): - """Get bl name""" - debug.check(port == 0, "One port for bitcell only.") - return "bl" - - def get_br_name(self, port=0): - """Get bl name""" - debug.check(port == 0, "One port for bitcell only.") - return "br" - - def get_wl_name(self, port=0): - """Get wl name""" - debug.check(port == 0, "One port for bitcell only.") - return "wl" - def build_graph(self, graph, inst_name, port_nets): """ Adds edges based on inputs/outputs. Overrides base class function. """ self.add_graph_edges(graph, port_nets) + + def is_non_inverting(self): + """Return input to output polarity for module""" + + return False diff --git a/compiler/bitcells/bitcell_base.py b/compiler/bitcells/bitcell_base.py index 09f33482..388d1b7b 100644 --- a/compiler/bitcells/bitcell_base.py +++ b/compiler/bitcells/bitcell_base.py @@ -26,9 +26,7 @@ class bitcell_base(design.design): self.nets_match = self.do_nets_exist(prop.storage_nets) self.mirror = prop.mirror self.end_caps = prop.end_caps - - self.supplies = ["vdd", "gnd"] - + def get_stage_effort(self, load): parasitic_delay = 1 # This accounts for bitline being drained @@ -95,7 +93,7 @@ class bitcell_base(design.design): labels for pex simulation. """ # If we generated the bitcell, we already know where Q and Q_bar are - if OPTS.bitcell is not "pbitcell": + if OPTS.bitcell != "pbitcell": self.storage_net_offsets = [] for i in range(len(self.get_storage_net_names())): for text in self.gds.getTexts(layer["m1"]): @@ -105,7 +103,6 @@ class bitcell_base(design.design): for i in range(len(self.storage_net_offsets)): self.storage_net_offsets[i] = tuple([self.gds.info["units"][0] * x for x in self.storage_net_offsets[i]]) - return(self.storage_net_offsets) def get_bitline_offset(self): @@ -150,7 +147,7 @@ class bitcell_base(design.design): of the bitcell. This is useful for making sense of offsets outside of the bitcell. """ - if OPTS.bitcell is not "pbitcell": + if OPTS.bitcell != "pbitcell": normalized_storage_net_offset = self.get_storage_net_offset() else: @@ -160,12 +157,12 @@ class bitcell_base(design.design): Q_bar_x = net_offset[1][0] - self.leftmost_xpos Q_bar_y = net_offset[1][1] - self.botmost_ypos - normalized_storage_net_offset = [[Q_x,Q_y],[Q_bar_x,Q_bar_y]] + normalized_storage_net_offset = [[Q_x, Q_y], [Q_bar_x, Q_bar_y]] return normalized_storage_net_offset def get_normalized_bitline_offset(self): - return self.get_bitline_offset() + return self.get_bitline_offset() def build_graph(self, graph, inst_name, port_nets): """ @@ -173,3 +170,36 @@ class bitcell_base(design.design): """ return + + def get_all_wl_names(self): + """ Creates a list of all wordline pin names """ + row_pins = ["wl"] + return row_pins + + def get_all_bitline_names(self): + """ Creates a list of all bitline pin names (both bl and br) """ + return ["bl", "br"] + + def get_all_bl_names(self): + """ Creates a list of all bl pins names """ + return ["bl"] + + def get_all_br_names(self): + """ Creates a list of all br pins names """ + return ["br"] + + def get_bl_name(self, port=0): + """Get bl name""" + debug.check(port == 0, "One port for bitcell only.") + return "bl" + + def get_br_name(self, port=0): + """Get bl name""" + debug.check(port == 0, "One port for bitcell only.") + return "br" + + def get_wl_name(self, port=0): + """Get wl name""" + debug.check(port == 0, "One port for bitcell only.") + return "wl" + diff --git a/compiler/bitcells/replica_bitcell_1port.py b/compiler/bitcells/replica_bitcell_1port.py index 58087b97..efdb5020 100644 --- a/compiler/bitcells/replica_bitcell_1port.py +++ b/compiler/bitcells/replica_bitcell_1port.py @@ -48,3 +48,8 @@ class replica_bitcell_1port(bitcell_base.bitcell_base): def build_graph(self, graph, inst_name, port_nets): """Adds edges based on inputs/outputs. Overrides base class function.""" self.add_graph_edges(graph, port_nets) + + def is_non_inverting(self): + """Return input to output polarity for module""" + + return False \ No newline at end of file diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 93e1b0f0..1535ebc5 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -113,6 +113,7 @@ class delay(simulation): read_measures.append(self.create_bitline_measurement_objects()) read_measures.append(self.create_debug_measurement_objects()) read_measures.append(self.create_read_bit_measures()) + read_measures.append(self.create_sen_and_bitline_path_measures()) return read_measures @@ -234,6 +235,98 @@ class delay(simulation): qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name) return {bit_polarity.NONINVERTING: q_meas, bit_polarity.INVERTING: qbar_meas} + + def create_sen_and_bitline_path_measures(self): + """Create measurements for the s_en and bitline paths for individual delays per stage.""" + + # FIXME: There should be a default_read_port variable in this case, pathing is done with this + # but is never mentioned otherwise + port = self.read_ports[0] + sen_and_port = self.sen_name+str(port) + bl_and_port = self.bl_name.format(port) # bl_name contains a '{}' for the port + # Isolate the s_en and bitline paths + debug.info(1, "self.bl_name = {}".format(self.bl_name)) + debug.info(1, "self.graph.all_paths = {}".format(self.graph.all_paths)) + sen_paths = [path for path in self.graph.all_paths if sen_and_port in path] + bl_paths = [path for path in self.graph.all_paths if bl_and_port in path] + debug.check(len(sen_paths)==1, 'Found {} paths which contain the s_en net.'.format(len(sen_paths))) + debug.check(len(bl_paths)==1, 'Found {} paths which contain the bitline net.'.format(len(bl_paths))) + sen_path = sen_paths[0] + bitline_path = bl_paths[0] + + # Get the measures + self.sen_path_meas = self.create_delay_path_measures(sen_path) + self.bl_path_meas = self.create_delay_path_measures(bitline_path) + all_meas = self.sen_path_meas + self.bl_path_meas + + # Paths could have duplicate measurements, remove them before they go to the stim file + all_meas = self.remove_duplicate_meas_names(all_meas) + # FIXME: duplicate measurements still exist in the member variables, since they have the same + # name it will still work, but this could cause an issue in the future. + + return all_meas + + def remove_duplicate_meas_names(self, measures): + """Returns new list of measurements without duplicate names""" + + name_set = set() + unique_measures = [] + for meas in measures: + if meas.name not in name_set: + name_set.add(meas.name) + unique_measures.append(meas) + + return unique_measures + + def create_delay_path_measures(self, path): + """Creates measurements for each net along given path.""" + + # Determine the directions (RISE/FALL) of signals + path_dirs = self.get_meas_directions(path) + + # Create the measurements + path_meas = [] + for i in range(len(path)-1): + cur_net, next_net = path[i], path[i+1] + cur_dir, next_dir = path_dirs[i], path_dirs[i+1] + meas_name = "delay_{}_to_{}".format(cur_net, next_net) + if i+1 != len(path)-1: + path_meas.append(delay_measure(meas_name, cur_net, next_net, cur_dir, next_dir, measure_scale=1e9, has_port=False)) + else: # Make the last measurement always measure on FALL because is a read 0 + path_meas.append(delay_measure(meas_name, cur_net, next_net, cur_dir, "FALL", measure_scale=1e9, has_port=False)) + # Some bitcell logic is hardcoded for only read zeroes, force that here as well. + path_meas[-1].meta_str = sram_op.READ_ZERO + path_meas[-1].meta_add_delay = True + + return path_meas + + def get_meas_directions(self, path): + """Returns SPICE measurements directions based on path.""" + + # Get the edges modules which define the path + edge_mods = self.graph.get_edge_mods(path) + + # Convert to booleans based on function of modules (inverting/non-inverting) + mod_type_bools = [mod.is_non_inverting() for mod in edge_mods] + + #FIXME: obtuse hack to differentiate s_en input from bitline in sense amps + if self.sen_name in path: + # Force the sense amp to be inverting for s_en->DOUT. + # bitline->DOUT is non-inverting, but the module cannot differentiate inputs. + s_en_index = path.index(self.sen_name) + mod_type_bools[s_en_index] = False + debug.info(2,'Forcing sen->dout to be inverting.') + + # Use these to determine direction list assuming delay start on neg. edge of clock (FALL) + # Also, use shorthand that 'FALL' == False, 'RISE' == True to simplify logic + bool_dirs = [False] + cur_dir = False # All Paths start on FALL edge of clock + for mod_bool in mod_type_bools: + cur_dir = (cur_dir == mod_bool) + bool_dirs.append(cur_dir) + + # Convert from boolean to string + return ['RISE' if dbool else 'FALL' for dbool in bool_dirs] def set_load_slew(self, load, slew): """ Set the load and slew """ @@ -651,6 +744,8 @@ class delay(simulation): debug.error("Failed to Measure Read Port Values:\n\t\t{0}".format(read_port_dict), 1) result[port].update(read_port_dict) + + self.check_path_measures() return (True, result) @@ -752,6 +847,21 @@ class delay(simulation): debug.info(1, "min_dicharge={}, min_diff={}".format(min_dicharge, min_diff)) return (min_dicharge and min_diff) + def check_path_measures(self): + """Get and check all the delays along the sen and bitline paths""" + + # Get and set measurement, no error checking done other than prints. + debug.info(2, "Checking measures in Delay Path") + value_dict = {} + for meas in self.sen_path_meas+self.bl_path_meas: + val = meas.retrieve_measure() + debug.info(2, '{}={}'.format(meas.name, val)) + if type(val) != float or val > self.period/2: + debug.info(1,'Failed measurement:{}={}'.format(meas.name, val)) + value_dict[meas.name] = val + + return value_dict + def run_power_simulation(self): """ This simulates a disabled SRAM to get the leakage power when it is off. @@ -1020,7 +1130,7 @@ class delay(simulation): # The inverse address needs to share the same bitlines as the probe address as the trimming will remove all other bitlines # This is only an issue when there is a column mux and the address maps to different bitlines. - column_addr = self.probe_address[:self.sram.col_addr_size] # do not invert this part + column_addr = self.get_column_addr() # do not invert this part inverse_address = "" for c in self.probe_address[self.sram.col_addr_size:]: # invert everything else if c=="0": diff --git a/compiler/characterizer/model_check.py b/compiler/characterizer/model_check.py index 794b3988..4ff67b39 100644 --- a/compiler/characterizer/model_check.py +++ b/compiler/characterizer/model_check.py @@ -412,7 +412,7 @@ class model_check(delay): data_dict[self.bl_meas_name] = bl_delays[read_port] data_dict[self.power_name] = powers[read_port] - if not OPTS.use_tech_delay_chain_size: #Model is not used in this case + if OPTS.auto_delay_chain_sizing: #Model is not used in this case wl_model_delays, sae_model_delays = self.get_model_delays(read_port) debug.info(1,"Wordline model delays:\n\t {}".format(wl_model_delays)) debug.info(1,"SAE model delays:\n\t {}".format(sae_model_delays)) @@ -439,7 +439,7 @@ class model_check(delay): name_dict[self.power_name] = self.power_meas_names #name_dict[self.wl_slew_name] = self.wl_slew_meas_names - if not OPTS.use_tech_delay_chain_size: + if OPTS.auto_delay_chain_sizing: name_dict[self.wl_model_name] = name_dict["wl_measures"] #model uses same names as measured. name_dict[self.sae_model_name] = name_dict["sae_measures"] diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py index 61f5914a..e73dac38 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -444,6 +444,10 @@ class simulation(): pin_names.append("{0}".format("gnd")) return pin_names + def get_column_addr(self): + """Returns column address of probe bit""" + return self.probe_address[:self.sram.col_addr_size] + def add_graph_exclusions(self): """ Exclude portions of SRAM from timing graph which are not relevant @@ -475,11 +479,12 @@ class simulation(): debug.warning("Error occurred while determining SEN name. Can cause faults in simulation.") debug.info(2, "s_en name = {}".format(self.sen_name)) - + + column_addr = self.get_column_addr() bl_name_port, br_name_port = self.get_bl_name(self.graph.all_paths, port) - port_pos = -1 - len(str(self.probe_data)) - len(str(port)) + port_pos = -1 - len(str(column_addr)) - len(str(port)) - if bl_name_port.endswith(str(port) + "_" + str(self.probe_data)): + if bl_name_port.endswith(str(port) + "_" + str(column_addr)): self.bl_name = bl_name_port[:port_pos] + "{}" + bl_name_port[port_pos + len(str(port)):] elif not bl_name_port[port_pos].isdigit(): # single port SRAM case, bl will not be numbered eg bl_0 self.bl_name = bl_name_port @@ -487,7 +492,7 @@ class simulation(): self.bl_name = bl_name_port debug.warning("Error occurred while determining bitline names. Can cause faults in simulation.") - if br_name_port.endswith(str(port) + "_" + str(self.probe_data)): + if br_name_port.endswith(str(port) + "_" + str(column_addr)): self.br_name = br_name_port[:port_pos] + "{}" + br_name_port[port_pos + len(str(port)):] elif not br_name_port[port_pos].isdigit(): # single port SRAM case, bl will not be numbered eg bl_0 self.br_name = br_name_port diff --git a/compiler/custom/sense_amp.py b/compiler/custom/sense_amp.py index d57bdaac..3de664fc 100644 --- a/compiler/custom/sense_amp.py +++ b/compiler/custom/sense_amp.py @@ -73,3 +73,9 @@ class sense_amp(design.design): def build_graph(self, graph, inst_name, port_nets): """Adds edges based on inputs/outputs. Overrides base class function.""" self.add_graph_edges(graph, port_nets) + + def is_non_inverting(self): + """Return input to output polarity for module""" + + #FIXME: This only applied to bl/br -> dout and not s_en->dout + return True diff --git a/compiler/gdsMill/gdsMill/gds2reader.py b/compiler/gdsMill/gdsMill/gds2reader.py index 2f9976a5..448355a8 100644 --- a/compiler/gdsMill/gdsMill/gds2reader.py +++ b/compiler/gdsMill/gdsMill/gds2reader.py @@ -79,7 +79,8 @@ class Gds2reader: recordLength = struct.unpack(">h",recordLengthAscii) #gives us a tuple with a short int inside offset_int = int(recordLength[0]) # extract length offset += offset_int # count offset - #print(offset) #print out the record numbers for de-bugging + if(self.debugToTerminal==1): + print("Offset: " + str(offset)) #print out the record numbers for de-bugging record = self.fileHandle.read(recordLength[0]-2) #read the rest of it (first 2 bytes were already read) return record @@ -176,7 +177,7 @@ class Gds2reader: def readBoundary(self): ##reads in a boundary type structure = a filled polygon if(self.debugToTerminal==1): - print("\t\t\tBeginBoundary") + print("\t\tBeginBoundary") thisBoundary=GdsBoundary() while 1: record = self.readNextRecord() @@ -214,13 +215,13 @@ class Gds2reader: print("\t\t\tXY Point: "+str(x)+","+str(y)) elif(idBits==b'\x11\x00'): #End Of Element if(self.debugToTerminal==1): - print("\t\t\tEndBoundary") + print("\t\tEndBoundary") break; return thisBoundary def readPath(self): #reads in a path structure if(self.debugToTerminal==1): - print("\t\t\tBeginPath") + print("\t\tBeginPath") thisPath=GdsPath() while 1: @@ -274,7 +275,7 @@ class Gds2reader: print("\t\t\tXY Point: "+str(x)+","+str(y)) elif(idBits==b'\x11\x00'): #End Of Element if(self.debugToTerminal==1): - print("\t\t\tEndPath") + print("\t\tEndPath") break; return thisPath diff --git a/compiler/gdsMill/gdsMill/vlsiLayout.py b/compiler/gdsMill/gdsMill/vlsiLayout.py index 19018119..68e32762 100644 --- a/compiler/gdsMill/gdsMill/vlsiLayout.py +++ b/compiler/gdsMill/gdsMill/vlsiLayout.py @@ -600,7 +600,7 @@ class VlsiLayout: shapes = self.getAllShapes(lpp) if len(shapes) != 1: - debug.warning("More than one boundary found in cell: {}".format(structure)) + debug.warning("More than one or no boundaries found in cell: {}".format(structure)) debug.check(len(shapes) != 0, "Error: "+str(structure)+".cell_size information not found yet") max_boundary = None diff --git a/compiler/globals.py b/compiler/globals.py index 8c6d7fd5..1fce6acd 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -19,7 +19,7 @@ import re import copy import importlib -VERSION = "1.1.6" +VERSION = "1.1.7" NAME = "OpenRAM v{}".format(VERSION) USAGE = "openram.py [options] \nUse -h for help.\n" @@ -353,6 +353,21 @@ def end_openram(): verify.print_lvs_stats() verify.print_pex_stats() + +def purge_temp(): + """ Remove the temp directory. """ + debug.info(1, + "Purging temp directory: {}".format(OPTS.openram_temp)) + # This annoyingly means you have to re-cd into + # the directory each debug iteration + # shutil.rmtree(OPTS.openram_temp, ignore_errors=True) + contents = [os.path.join(OPTS.openram_temp, i) for i in os.listdir(OPTS.openram_temp)] + for i in contents: + if os.path.isfile(i) or os.path.islink(i): + os.remove(i) + else: + shutil.rmtree(i) + def cleanup_paths(): """ @@ -364,19 +379,9 @@ def cleanup_paths(): "Preserving temp directory: {}".format(OPTS.openram_temp)) return elif os.path.exists(OPTS.openram_temp): - debug.info(1, - "Purging temp directory: {}".format(OPTS.openram_temp)) - # This annoyingly means you have to re-cd into - # the directory each debug iteration - # shutil.rmtree(OPTS.openram_temp, ignore_errors=True) - contents = [os.path.join(OPTS.openram_temp, i) for i in os.listdir(OPTS.openram_temp)] - for i in contents: - if os.path.isfile(i) or os.path.islink(i): - os.remove(i) - else: - shutil.rmtree(i) - + purge_temp() + def setup_paths(): """ Set up the non-tech related paths. """ debug.info(2, "Setting up paths...") @@ -405,7 +410,7 @@ def setup_paths(): OPTS.openram_temp += "/" debug.info(1, "Temporary files saved in " + OPTS.openram_temp) - + def is_exe(fpath): """ Return true if the given is an executable file that exists. """ return os.path.exists(fpath) and os.access(fpath, os.X_OK) @@ -427,15 +432,17 @@ def find_exe(check_exe): def init_paths(): """ Create the temp and output directory if it doesn't exist """ - - # make the directory if it doesn't exist - try: - debug.info(1, - "Creating temp directory: {}".format(OPTS.openram_temp)) - os.makedirs(OPTS.openram_temp, 0o750) - except OSError as e: - if e.errno == 17: # errno.EEXIST - os.chmod(OPTS.openram_temp, 0o750) + if os.path.exists(OPTS.openram_temp): + purge_temp() + else: + # make the directory if it doesn't exist + try: + debug.info(1, + "Creating temp directory: {}".format(OPTS.openram_temp)) + os.makedirs(OPTS.openram_temp, 0o750) + except OSError as e: + if e.errno == 17: # errno.EEXIST + os.chmod(OPTS.openram_temp, 0o750) # Don't delete the output dir, it may have other files! # make the directory if it doesn't exist diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index e0668700..fbe60ff0 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -729,7 +729,6 @@ class bank(design.design): inst2_bl_name=inst2_bl_name, inst2_br_name=inst2_br_name) - # Connect the replica bitlines for (array_name, data_name) in zip(["rbl_bl_{0}_{0}".format(port), "rbl_br_{0}_{0}".format(port)], ["rbl_bl", "rbl_br"]): self.connect_bitline(inst1, inst2, array_name, data_name) @@ -876,15 +875,14 @@ class bank(design.design): mid1 = driver_wl_pos.scale(0, 1) + vector(0.5 * port_address_pos + 0.5 * bitcell_array_pos, 0) mid2 = mid1.scale(1, 0) + bitcell_wl_pos.scale(0, 1) if driver_wl_pin.layer != bitcell_wl_pin.layer: - self.add_path(driver_wl_pin.layer, [driver_wl_pos, mid1, mid2]) + self.add_path(driver_wl_pin.layer, [driver_wl_pos, mid1]) self.add_via_stack_center(from_layer=driver_wl_pin.layer, to_layer=bitcell_wl_pin.layer, - offset=mid2) - self.add_path(bitcell_wl_pin.layer, [mid2, bitcell_wl_pos]) + offset=mid1) + self.add_path(bitcell_wl_pin.layer, [mid1, mid2, bitcell_wl_pos]) else: self.add_path(bitcell_wl_pin.layer, [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) - def route_port_address_right(self, port): """ Connecting Wordline driver output to Bitcell WL connection """ diff --git a/compiler/modules/bitcell_base_array.py b/compiler/modules/bitcell_base_array.py index 088308f9..a9bfefd5 100644 --- a/compiler/modules/bitcell_base_array.py +++ b/compiler/modules/bitcell_base_array.py @@ -35,11 +35,6 @@ class bitcell_base_array(design.design): self.rbl_wordline_names = [[] for port in self.all_ports] self.all_rbl_wordline_names = [] - # The supply pin names - self.bitcell_supplies = self.cell.supplies - # If the technology needs renaming of the supplies - self.supplies = ["vdd", "gnd"] - def create_all_bitline_names(self): for col in range(self.column_size): for port in self.all_ports: @@ -63,8 +58,8 @@ class bitcell_base_array(design.design): self.add_pin(bl_name, "INOUT") for wl_name in self.get_wordline_names(): self.add_pin(wl_name, "INPUT") - self.add_pin(self.supplies[0], "POWER") - self.add_pin(self.supplies[1], "GROUND") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def get_bitcell_pins(self, row, col): """ @@ -75,8 +70,8 @@ class bitcell_base_array(design.design): for port in self.all_ports: bitcell_pins.extend([x for x in self.get_bitline_names(port) if x.endswith("_{0}".format(col))]) bitcell_pins.extend([x for x in self.all_wordline_names if x.endswith("_{0}".format(row))]) - bitcell_pins.append(self.bitcell_supplies[0]) - bitcell_pins.append(self.bitcell_supplies[1]) + bitcell_pins.append("vdd") + bitcell_pins.append("gnd") return bitcell_pins @@ -166,8 +161,8 @@ class bitcell_base_array(design.design): for row in range(self.row_size): for col in range(self.column_size): inst = self.cell_inst[row, col] - for (pin_name, new_name) in zip(self.bitcell_supplies, self.supplies): - self.copy_layout_pin(inst, pin_name, new_name) + for pin_name in ["vdd", "gnd"]: + self.copy_layout_pin(inst, pin_name) def _adjust_x_offset(self, xoffset, col, col_offset): tempx = xoffset diff --git a/compiler/modules/col_cap_array.py b/compiler/modules/col_cap_array.py index 71a59cf8..1be29327 100644 --- a/compiler/modules/col_cap_array.py +++ b/compiler/modules/col_cap_array.py @@ -39,6 +39,10 @@ class col_cap_array(bitcell_base_array): self.place_array("dummy_r{0}_c{1}", self.mirror) self.add_layout_pins() + + self.height = self.dummy_cell.height + self.width = self.column_size * self.cell.width + self.add_boundary() self.DRC_LVS() @@ -96,7 +100,7 @@ class col_cap_array(bitcell_base_array): inst = self.cell_inst[row, col] for pin_name in ["vdd", "gnd"]: for pin in inst.get_pins(pin_name): - self.add_power_pin(name=pin.name, + self.add_power_pin(name=pin_name, loc=pin.center(), start_layer=pin.layer) diff --git a/compiler/modules/column_mux_array.py b/compiler/modules/column_mux_array.py index 55d67eee..43a24a20 100644 --- a/compiler/modules/column_mux_array.py +++ b/compiler/modules/column_mux_array.py @@ -34,18 +34,9 @@ class column_mux_array(design.design): self.column_offset = column_offset self.sel_layer = layer_props.column_mux_array.select_layer - self.sel_pitch = getattr(self, layer_props.column_mux_array.select_pitch) + self.sel_pitch = getattr(self, self.sel_layer + "_pitch") self.bitline_layer = layer_props.column_mux_array.bitline_layer - # if OPTS.tech_name == "sky130": - # self.sel_layer = "m3" - # self.sel_pitch = self.m3_pitch - # self.bitline_layer = "m1" - # else: - # self.sel_layer = "m1" - # self.sel_pitch = self.m2_pitch - # self.bitline_layer = "m2" - if preferred_directions[self.sel_layer] == "V": self.via_directions = ("H", "H") else: diff --git a/compiler/modules/hierarchical_predecode.py b/compiler/modules/hierarchical_predecode.py index 9b5cab65..72fb2a6d 100644 --- a/compiler/modules/hierarchical_predecode.py +++ b/compiler/modules/hierarchical_predecode.py @@ -268,9 +268,11 @@ class hierarchical_predecode(design.design): height=via.mod.second_layer_height, width=via.mod.second_layer_width) - if layer_props.hierarchical_predecode.vertical_supply: - below_rail = vector(self.decode_rails[out_pin].cx(), y_offset - (self.cell_height / 2)) - self.add_path(self.bus_layer, [rail_pos, below_rail], width=self.li_width + self.m1_enclose_mcon * 2) + # This is a hack to fix via-to-via spacing issues, but it is currently + # causing its own DRC problems. + # if layer_props.hierarchical_predecode.vertical_supply: + # below_rail = vector(self.decode_rails[out_pin].cx(), y_offset - (self.cell_height / 2)) + # self.add_path(self.bus_layer, [rail_pos, below_rail], width=self.li_width + self.m1_enclose_mcon * 2) def route_and_to_rails(self): # This 2D array defines the connection mapping diff --git a/compiler/modules/port_address.py b/compiler/modules/port_address.py index 125f8765..382aca56 100644 --- a/compiler/modules/port_address.py +++ b/compiler/modules/port_address.py @@ -8,7 +8,7 @@ import debug import design from sram_factory import factory from vector import vector -from tech import layer +from tech import layer, drc from globals import OPTS from tech import layer_properties as layer_props @@ -86,6 +86,13 @@ class port_address(design.design): else: self.add_power_pin("vdd", rbl_vdd_pin.lc()) + # Also connect the B input of the RBL and_dec to vdd + if OPTS.local_array_size == 0: + rbl_b_pin = self.rbl_driver_inst.get_pin("B") + rbl_loc = rbl_b_pin.center() - vector(3 * self.m1_pitch, 0) + self.add_path(rbl_b_pin.layer, [rbl_b_pin.center(), rbl_loc]) + self.add_power_pin("vdd", rbl_loc, start_layer=rbl_b_pin.layer) + def route_pins(self): for row in range(self.addr_size): decoder_name = "addr_{}".format(row) @@ -157,11 +164,13 @@ class port_address(design.design): b = factory.create(module_type=OPTS.bitcell) if local_array_size > 0: + # The local wordline driver will change the polarity self.rbl_driver = factory.create(module_type="inv_dec", size=driver_size, height=b.height) else: - self.rbl_driver = factory.create(module_type="buf_dec", + # There is no local wordline driver + self.rbl_driver = factory.create(module_type="and2_dec", size=driver_size, height=b.height) @@ -189,6 +198,8 @@ class port_address(design.design): temp = [] temp.append("wl_en") + if OPTS.local_array_size == 0: + temp.append("vdd") temp.append("rbl_wl") temp.append("vdd") temp.append("gnd") @@ -221,7 +232,10 @@ class port_address(design.design): wordline_driver_array_offset = vector(self.row_decoder_inst.rx(), 0) self.wordline_driver_array_inst.place(wordline_driver_array_offset) - x_offset = self.wordline_driver_array_inst.rx() - self.rbl_driver.width - self.m1_pitch + # The wordline driver also had an extra gap on the right, so use this offset + well_gap = 2 * drc("pwell_to_nwell") + drc("nwell_enclose_active") + x_offset = self.wordline_driver_array_inst.rx() - well_gap - self.rbl_driver.width + if self.port == 0: rbl_driver_offset = vector(x_offset, 0) diff --git a/compiler/modules/replica_bitcell_array.py b/compiler/modules/replica_bitcell_array.py index ebfd06ba..75d47670 100644 --- a/compiler/modules/replica_bitcell_array.py +++ b/compiler/modules/replica_bitcell_array.py @@ -6,7 +6,7 @@ import debug from bitcell_base_array import bitcell_base_array -from tech import drc, spice, cell_properties +from tech import drc, spice from vector import vector from globals import OPTS from sram_factory import factory @@ -121,18 +121,18 @@ class replica_bitcell_array(bitcell_base_array): # the array. # These go from the top (where the bitcell array starts ) down replica_bit = self.rbl[0] - port + column_offset = self.rbl[0] + elif port in self.right_rbl: # We will always have self.rbl[0] rows of replica wordlines below # the array. # These go from the bottom up replica_bit = self.rbl[0] + self.row_size + port + column_offset = self.rbl[0] + self.column_size + 1 else: continue - # If we have an odd numer on the bottom - column_offset = self.rbl[0] + 1 - self.replica_columns[port] = factory.create(module_type="replica_column", rows=self.row_size, rbl=self.rbl, @@ -313,19 +313,14 @@ class replica_bitcell_array(bitcell_base_array): def create_layout(self): # We will need unused wordlines grounded, so we need to know their layer + # and create a space on the left and right for the vias to connect to ground pin = self.cell.get_pin(self.cell.get_all_wl_names()[0]) pin_layer = pin.layer self.unused_pitch = 1.5 * getattr(self, "{}_pitch".format(pin_layer)) self.unused_offset = vector(self.unused_pitch, 0) - # Add extra width on the left and right for the unused WLs - self.height = (self.row_size + self.extra_rows) * self.dummy_row.height - self.width = (self.column_size + self.extra_cols) * self.cell.width + 2 * self.unused_pitch - - # This is a bitcell x bitcell offset to scale self.bitcell_offset = vector(self.cell.width, self.cell.height) - self.strap_offset = vector(0, 0) self.col_end_offset = vector(self.cell.width, self.cell.height) self.row_end_offset = vector(self.cell.width, self.cell.height) @@ -336,12 +331,16 @@ class replica_bitcell_array(bitcell_base_array): self.add_end_caps() - # Array was at (0, 0) but move everything so it is at the lower left # We move DOWN the number of left RBL even if we didn't add the column to this bitcell array + # Note that this doesn't include the row/col cap array_offset = self.bitcell_offset.scale(1 + len(self.left_rbl), 1 + self.rbl[0]) self.translate_all(array_offset.scale(-1, -1)) + # Add extra width on the left and right for the unused WLs + self.width = self.dummy_col_insts[1].rx() + self.unused_offset.x + self.height = self.dummy_row_insts[1].uy() + self.add_layout_pins() self.route_unused_wordlines() @@ -374,19 +373,11 @@ class replica_bitcell_array(bitcell_base_array): # Grow from left to right, toward the array for bit, port in enumerate(self.left_rbl): - if not self.cell.end_caps: - offset = self.bitcell_offset.scale(-len(self.left_rbl) + bit, -self.rbl[0] - 1) + self.strap_offset.scale(-len(self.left_rbl) + bit, 0) + self.unused_offset - else: - offset = self.bitcell_offset.scale(-len(self.left_rbl) + bit, -self.rbl[0] - (self.col_end_offset.y/self.cell.height)) + self.strap_offset.scale(-len(self.left_rbl) + bit, 0) + self.unused_offset - + offset = self.bitcell_offset.scale(-len(self.left_rbl) + bit, -self.rbl[0] - 1) + self.unused_offset self.replica_col_insts[bit].place(offset) # Grow to the right of the bitcell array, array outward for bit, port in enumerate(self.right_rbl): - if not self.cell.end_caps: - offset = self.bitcell_array_inst.lr() + self.bitcell_offset.scale(bit, -self.rbl[0] - 1) + self.strap_offset.scale(bit, -self.rbl[0] - 1) - else: - offset = self.bitcell_array_inst.lr() + self.bitcell_offset.scale(bit, -self.rbl[0] - (self.col_end_offset.y/self.cell.height)) + self.strap_offset.scale(bit, -self.rbl[0] - 1) - + offset = self.bitcell_array_inst.lr() + self.bitcell_offset.scale(bit, -self.rbl[0] - 1) self.replica_col_insts[self.rbl[0] + bit].place(offset) # Replica dummy rows @@ -408,37 +399,24 @@ class replica_bitcell_array(bitcell_base_array): # FIXME: These depend on the array size itself # Far top dummy row (first row above array is NOT flipped) flip_dummy = self.rbl[1] % 2 - if not self.cell.end_caps: - dummy_row_offset = self.bitcell_offset.scale(0, self.rbl[1] + flip_dummy) + self.bitcell_array_inst.ul() - else: - dummy_row_offset = self.bitcell_offset.scale(0, self.rbl[1] + flip_dummy) + self.bitcell_array_inst.ul() - + dummy_row_offset = self.bitcell_offset.scale(0, self.rbl[1] + flip_dummy) + self.bitcell_array_inst.ul() self.dummy_row_insts[1].place(offset=dummy_row_offset, mirror="MX" if flip_dummy else "R0") + # FIXME: These depend on the array size itself # Far bottom dummy row (first row below array IS flipped) flip_dummy = (self.rbl[0] + 1) % 2 - if not self.cell.end_caps: - dummy_row_offset = self.bitcell_offset.scale(0, -self.rbl[0] - 1 + flip_dummy) + self.unused_offset - else: - dummy_row_offset = self.bitcell_offset.scale(0, -self.rbl[0] - (self.col_end_offset.y/self.cell.height) + flip_dummy) + self.unused_offset + dummy_row_offset = self.bitcell_offset.scale(0, -self.rbl[0] - 1 + flip_dummy) + self.unused_offset self.dummy_row_insts[0].place(offset=dummy_row_offset, - mirror="MX" if flip_dummy else "R0") + mirror="MX" if flip_dummy else "R0") # Far left dummy col # Shifted down by the number of left RBLs even if we aren't adding replica column to this bitcell array - if not self.cell.end_caps: - dummy_col_offset = self.bitcell_offset.scale(-len(self.left_rbl) - 1, -self.rbl[0] - 1) + self.unused_offset - else: - dummy_col_offset = self.bitcell_offset.scale(-(len(self.left_rbl)*(1+self.strap_offset.x/self.cell.width)) - (self.row_end_offset.x/self.cell.width), -len(self.left_rbl) - (self.col_end_offset.y/self.cell.height)) - + dummy_col_offset = self.bitcell_offset.scale(-len(self.left_rbl) - 1, -self.rbl[0] - 1) + self.unused_offset self.dummy_col_insts[0].place(offset=dummy_col_offset) + # Far right dummy col # Shifted down by the number of left RBLs even if we aren't adding replica column to this bitcell array - if not self.cell.end_caps: - dummy_col_offset = self.bitcell_offset.scale(len(self.right_rbl), -self.rbl[0] - 1) + self.bitcell_array_inst.lr() - else: - dummy_col_offset = self.bitcell_offset.scale(len(self.right_rbl)*(1+self.strap_offset.x/self.cell.width), -self.rbl[0] - (self.col_end_offset.y/self.cell.height)) + self.bitcell_array_inst.lr() - + dummy_col_offset = self.bitcell_offset.scale(len(self.right_rbl), -self.rbl[0] - 1) + self.bitcell_array_inst.lr() self.dummy_col_insts[1].place(offset=dummy_col_offset) def add_layout_pins(self): @@ -455,6 +433,7 @@ class replica_bitcell_array(bitcell_base_array): offset=pin.ll().scale(0, 1), width=self.width, height=pin.height()) + # Replica wordlines (go by the row instead of replica column because we may have to add a pin # even though the column is in another local bitcell array) for (names, inst) in zip(self.rbl_wordline_names, self.dummy_row_replica_insts): @@ -549,8 +528,7 @@ class replica_bitcell_array(bitcell_base_array): self.add_power_pin("gnd", right_loc, directions=("H", "H")) # Add a path to connect to the array - self.add_path(pin_layer, [left_loc, left_pin_loc]) - self.add_path(pin_layer, [right_loc, right_pin_loc]) + self.add_path(pin_layer, [left_loc, right_loc], width=pin.height()) def gen_bl_wire(self): if OPTS.netlist_only: diff --git a/compiler/modules/replica_column.py b/compiler/modules/replica_column.py index f7e0c0fb..2a333c8e 100644 --- a/compiler/modules/replica_column.py +++ b/compiler/modules/replica_column.py @@ -14,37 +14,38 @@ from tech import layer_properties as layer_props class replica_column(bitcell_base_array): """ Generate a replica bitline column for the replica array. - Rows is the total number of rows i the main array. + Rows is the total number of rows in the main array. rbl is a tuple with the number of left and right replica bitlines. Replica bit specifies which replica column this is (to determine where to put the replica cell relative to the bottom (including the dummy bit at 0). """ def __init__(self, name, rows, rbl, replica_bit, column_offset=0): - super().__init__(rows=sum(rbl) + rows + 2, cols=1, column_offset=column_offset, name=name) + # Used for pin names and properties + self.cell = factory.create(module_type=OPTS.bitcell) + # Row size is the number of rows with word lines + self.row_size = sum(rbl) + rows + # Start of regular word line rows + self.row_start = rbl[0] + 1 + # End of regular word line rows + self.row_end = self.row_start + rows + if not self.cell.end_caps: + self.row_size += 2 + super().__init__(rows=self.row_size, cols=1, column_offset=column_offset, name=name) self.rows = rows self.left_rbl = rbl[0] self.right_rbl = rbl[1] self.replica_bit = replica_bit - # left, right, regular rows plus top/bottom dummy cells - self.total_size = self.left_rbl + rows + self.right_rbl - - # Used for pin names and properties - self.cell = factory.create(module_type=OPTS.bitcell) - # For end caps - try: - if not self.cell.end_caps: - self.total_size += 2 - except AttributeError: - self.total_size += 2 + # Total size includes the replica rows and column cap rows + self.total_size = self.left_rbl + rows + self.right_rbl + 2 self.column_offset = column_offset - debug.check(replica_bit != 0 and replica_bit != rows, - "Replica bit cannot be the dummy row.") - debug.check(replica_bit <= self.left_rbl or replica_bit >= self.total_size - self.right_rbl - 1, + debug.check(replica_bit != 0 and replica_bit != self.total_size - 1, + "Replica bit cannot be the dummy/cap row.") + debug.check(replica_bit < self.row_start or replica_bit >= self.row_end, "Replica bit cannot be in the regular array.") if layer_props.replica_column.even_rows: debug.check(rows % 2 == 0 and (self.left_rbl + 1) % 2 == 0, @@ -61,18 +62,20 @@ class replica_column(bitcell_base_array): self.create_instances() def create_layout(self): - self.height = self.total_size * self.cell.height - self.width = self.cell.width - self.place_instances() + + self.height = self.cell_inst[-1].uy() + self.width = self.cell_inst[0].rx() + self.add_layout_pins() + self.add_boundary() self.DRC_LVS() def add_pins(self): self.create_all_bitline_names() - self.create_all_wordline_names(self.total_size) + self.create_all_wordline_names(self.row_size) self.add_pin_list(self.all_bitline_names, "OUTPUT") self.add_pin_list(self.all_wordline_names, "INPUT") @@ -93,32 +96,32 @@ class replica_column(bitcell_base_array): self.add_mod(self.edge_cell) def create_instances(self): - self.cell_inst = {} + self.cell_inst = [] for row in range(self.total_size): name="rbc_{0}".format(row) - # Top/bottom cell are always dummy cells. - # Regular array cells are replica cells (>left_rbl and self.left_rbl and row < self.total_size - self.right_rbl - 1): - self.cell_inst[row]=self.add_inst(name=name, - mod=self.replica_cell) - self.connect_inst(self.get_bitcell_pins(row, 0)) - elif row==self.replica_bit: - self.cell_inst[row]=self.add_inst(name=name, - mod=self.replica_cell) - self.connect_inst(self.get_bitcell_pins(row, 0)) - elif (row == 0 or row == self.total_size - 1): - self.cell_inst[row]=self.add_inst(name=name, - mod=self.edge_cell) + real_row = row + if self.cell.end_caps: + real_row -= 1 + + # Regular array cells are replica cells + # Replic bit specifies which other bit (in the full range (0,total_size) to make a replica cell. + if (row == 0 or row == self.total_size - 1): + self.cell_inst.append(self.add_inst(name=name, + mod=self.edge_cell)) if self.cell.end_caps: - self.connect_inst(self.get_bitcell_pins_col_cap(row, 0)) + self.connect_inst(self.get_bitcell_pins_col_cap(real_row, 0)) else: - self.connect_inst(self.get_bitcell_pins(row, 0)) + self.connect_inst(self.get_bitcell_pins(real_row, 0)) + elif (row==self.replica_bit) or (row >= self.row_start and row < self.row_end): + self.cell_inst.append(self.add_inst(name=name, + mod=self.replica_cell)) + self.connect_inst(self.get_bitcell_pins(real_row, 0)) else: - self.cell_inst[row]=self.add_inst(name=name, - mod=self.dummy_cell) - self.connect_inst(self.get_bitcell_pins(row, 0)) + # Top/bottom cell are always dummy/cap cells. + self.cell_inst.append(self.add_inst(name=name, + mod=self.dummy_cell)) + self.connect_inst(self.get_bitcell_pins(real_row, 0)) def place_instances(self): # Flip the mirrors if we have an odd number of replica+dummy rows at the bottom @@ -184,7 +187,7 @@ class replica_column(bitcell_base_array): height=wl_pin.height()) # Supplies are only connected in the ends - for (index, inst) in self.cell_inst.items(): + for (index, inst) in enumerate(self.cell_inst): for pin_name in ["vdd", "gnd"]: if inst in [self.cell_inst[0], self.cell_inst[self.total_size - 1]]: self.copy_power_pins(inst, pin_name) @@ -231,7 +234,7 @@ class replica_column(bitcell_base_array): Excludes all bits except the replica cell (self.replica_bit). """ - for row, cell in self.cell_inst.items(): + for row, cell in enumerate(self.cell_inst): if row != self.replica_bit: self.graph_inst_exclude.add(cell) diff --git a/compiler/modules/row_cap_array.py b/compiler/modules/row_cap_array.py index 97776eee..850dd5f9 100644 --- a/compiler/modules/row_cap_array.py +++ b/compiler/modules/row_cap_array.py @@ -34,6 +34,10 @@ class row_cap_array(bitcell_base_array): self.place_array("dummy_r{0}_c{1}", self.mirror) self.add_layout_pins() + + self.width = max([x.rx() for x in self.insts]) + self.height = max([x.uy() for x in self.insts]) + self.add_boundary() self.DRC_LVS() @@ -48,7 +52,7 @@ class row_cap_array(bitcell_base_array): """ Create the module instances used in this design """ self.cell_inst = {} for col in range(self.column_size): - for row in range(1, self.row_size - 1): + for row in range(0, self.row_size): name = "bit_r{0}_c{1}".format(row, col) self.cell_inst[row, col]=self.add_inst(name=name, mod=self.dummy_cell) @@ -67,17 +71,13 @@ class row_cap_array(bitcell_base_array): return bitcell_pins def place_array(self, name_template, row_offset=0): - # We increase it by a well enclosure so the precharges don't overlap our wells - self.height = self.row_size * self.cell.height - self.width = self.column_size * self.cell.width - xoffset = 0.0 for col in range(self.column_size): yoffset = self.cell.height tempx, dir_y = self._adjust_x_offset(xoffset, col, self.column_offset) - for row in range(1, self.row_size - 1): - tempy, dir_x = self._adjust_y_offset(yoffset, row, row_offset) + for row in range(self.row_size): + tempy, dir_x = self._adjust_y_offset(yoffset, row + 1, row_offset) if dir_x and dir_y: dir_key = "XY" @@ -113,7 +113,7 @@ class row_cap_array(bitcell_base_array): inst = self.cell_inst[row, col] for pin_name in ["vdd", "gnd"]: for pin in inst.get_pins(pin_name): - self.add_power_pin(name=pin.name, + self.add_power_pin(name=pin_name, loc=pin.center(), start_layer=pin.layer) diff --git a/compiler/options.py b/compiler/options.py index 2dab8308..e4a61052 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -9,7 +9,6 @@ import optparse import getpass import os - class options(optparse.Values): """ Class for holding all of the OpenRAM options. All @@ -61,7 +60,7 @@ class options(optparse.Values): rbl_delay_percentage = 0.5 # Allow manual adjustment of the delay chain over automatic - use_tech_delay_chain_size = False + auto_delay_chain_sizing = False delay_chain_stages = 9 delay_chain_fanout_per_stage = 4 @@ -103,7 +102,7 @@ class options(optparse.Values): # Run with extracted parasitics use_pex = False # Output config with all options - output_extended_config = False + output_extended_config = True ################### @@ -124,7 +123,7 @@ class options(optparse.Values): pex_exe = None # For sky130, we need magic for filtering. magic_exe = None - + # Number of threads to use num_threads = 2 @@ -153,6 +152,8 @@ class options(optparse.Values): bitcell = "bitcell" buf_dec = "pbuf" column_mux_array = "column_mux_array" + col_cap = "col_cap" + col_cap_array = "col_cap_array" control_logic = "control_logic" decoder = "hierarchical_decoder" delay_chain = "delay_chain" @@ -165,6 +166,8 @@ class options(optparse.Values): precharge_array = "precharge_array" ptx = "ptx" replica_bitline = "replica_bitline" + row_cap = "row_cap" + row_cap_array = "row_cap_array" sense_amp_array = "sense_amp_array" sense_amp = "sense_amp" tri_gate_array = "tri_gate_array" diff --git a/compiler/pgates/pand2.py b/compiler/pgates/pand2.py index a6efc93b..56aeffe3 100644 --- a/compiler/pgates/pand2.py +++ b/compiler/pgates/pand2.py @@ -146,3 +146,8 @@ class pand2(pgate.pgate): offset=pin.center(), width=pin.width(), height=pin.height()) + + def is_non_inverting(self): + """Return input to output polarity for module""" + + return True \ No newline at end of file diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index eff1337b..6a39ccd4 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -337,3 +337,8 @@ class pinv(pgate.pgate): Overrides base class function. """ self.add_graph_edges(graph, port_nets) + + def is_non_inverting(self): + """Return input to output polarity for module""" + + return False diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index b2fe7bae..56563b31 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -314,3 +314,8 @@ class pnand2(pgate.pgate): Overrides base class function. """ self.add_graph_edges(graph, port_nets) + + def is_non_inverting(self): + """Return input to output polarity for module""" + + return False \ No newline at end of file diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index 24396cd4..9df03926 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -347,3 +347,8 @@ class pnand3(pgate.pgate): Overrides base class function. """ self.add_graph_edges(graph, port_nets) + + def is_non_inverting(self): + """Return input to output polarity for module""" + + return False \ No newline at end of file diff --git a/compiler/pgates/ptx.py b/compiler/pgates/ptx.py index ae32e557..05ec75c0 100644 --- a/compiler/pgates/ptx.py +++ b/compiler/pgates/ptx.py @@ -13,7 +13,6 @@ from sram_factory import factory import contact import logical_effort from globals import OPTS -from pgate import pgate from tech import cell_properties as cell_props @@ -151,14 +150,14 @@ class ptx(design.design): self.spice.append("\n* spice ptx " + self.spice_device) if cell_props.ptx.model_is_subckt and OPTS.lvs_exe and OPTS.lvs_exe[0] == "calibre": - # sky130 requires mult parameter too - # self.lvs_device = "X{{0}} {{1}} {0} m={1} w={2} l={3} mult={1}".format(spice[self.tx_type], + # sky130 requires mult parameter too. It is not the same as m, but I don't understand it. + # self.lvs_device = "X{{0}} {{1}} {0} m={1} w={2} l={3} mult=1".format(spice[self.tx_type], # self.mults, # self.tx_width, # drc("minwidth_poly")) # TEMP FIX: Use old device names if using Calibre. - self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2} l={3} mult={1}".format("nshort" if self.tx_type == "nmos" else "pshort", + self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2} l={3} mult=1".format("nshort" if self.tx_type == "nmos" else "pshort", self.mults, self.tx_width, drc("minwidth_poly")) @@ -549,3 +548,7 @@ class ptx(design.design): """ self.add_graph_edges(graph, port_nets) + def is_non_inverting(self): + """Return input to output polarity for module""" + + return True diff --git a/compiler/printGDS.py b/compiler/printGDS.py new file mode 100755 index 00000000..b0683203 --- /dev/null +++ b/compiler/printGDS.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +import sys +from gdsMill import gdsMill + +if len(sys.argv) < 2: + print("Usage: {0} file.gds".format(sys.argv[0])) + sys.exit(1) + +gds_file = sys.argv[1] +arrayCellLayout = gdsMill.VlsiLayout() +reader = gdsMill.Gds2reader(arrayCellLayout,debugToTerminal = 1) +reader.loadFromFile(gds_file) + diff --git a/compiler/sram/sram.py b/compiler/sram/sram.py index a3480213..4fdb1f9b 100644 --- a/compiler/sram/sram.py +++ b/compiler/sram/sram.py @@ -23,7 +23,13 @@ class sram(): def __init__(self, sram_config, name): sram_config.set_local_config(self) - + + # FIXME: adjust this to not directly change OPTS. + # Word-around to have values relevant to OPTS be displayed if not directly set. + OPTS.words_per_row = self.words_per_row + debug.info(1, "Changed OPTS wpr={}".format(self.words_per_row)) + debug.info(1, "OPTS wpr={}".format(OPTS.words_per_row)) + # reset the static duplicate name checker for unit tests # in case we create more than one SRAM from design import design diff --git a/compiler/tests/14_replica_bitcell_array_1rw_1r_test.py b/compiler/tests/14_replica_bitcell_array_1rw_1r_test.py index cf66692e..cbcb40d5 100755 --- a/compiler/tests/14_replica_bitcell_array_1rw_1r_test.py +++ b/compiler/tests/14_replica_bitcell_array_1rw_1r_test.py @@ -25,14 +25,14 @@ class replica_bitcell_array_1rw_1r_test(openram_test): OPTS.num_w_ports = 0 globals.setup_bitcell() - debug.info(2, "Testing 4x4 non-replica array for cell_1rw_1r") + debug.info(2, "Testing 4x4 non-replica array for dp cell") a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, rbl=[1, 1]) self.local_check(a) - debug.info(2, "Testing 4x4 left replica array for cell_1rw_1r") + debug.info(2, "Testing 4x4 left replica array for dp cell") a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, @@ -40,7 +40,7 @@ class replica_bitcell_array_1rw_1r_test(openram_test): left_rbl=[0]) self.local_check(a) - debug.info(2, "Testing 4x4 array left and right replica for cell_1rw_1r") + debug.info(2, "Testing 4x4 array left and right replica for dp cell") a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, @@ -52,7 +52,7 @@ class replica_bitcell_array_1rw_1r_test(openram_test): # Sky 130 has restrictions on the symmetries if OPTS.tech_name != "sky130": - debug.info(2, "Testing 4x4 array right only replica for cell_1rw_1r") + debug.info(2, "Testing 4x4 array right only replica for dp cell") a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, diff --git a/compiler/tests/14_replica_column_1rw_1r_test.py b/compiler/tests/14_replica_column_1rw_1r_test.py index d197a234..bf0a1108 100755 --- a/compiler/tests/14_replica_column_1rw_1r_test.py +++ b/compiler/tests/14_replica_column_1rw_1r_test.py @@ -25,18 +25,22 @@ class replica_column_test(openram_test): OPTS.num_w_ports = 0 globals.setup_bitcell() - debug.info(2, "Testing replica column for 6t_cell") + debug.info(2, "Testing one left replica column for dual port") a = factory.create(module_type="replica_column", rows=4, rbl=[1, 0], replica_bit=1) self.local_check(a) - debug.info(2, "Testing replica column for 6t_cell") + debug.info(2, "Testing one right replica column for dual port") + a = factory.create(module_type="replica_column", rows=4, rbl=[0, 1], replica_bit=5) + self.local_check(a) + + debug.info(2, "Testing two (left, right) replica columns for dual port") + a = factory.create(module_type="replica_column", rows=4, rbl=[1, 1], replica_bit=1) + self.local_check(a) + + debug.info(2, "Testing two (left, right) replica columns for dual port") a = factory.create(module_type="replica_column", rows=4, rbl=[1, 1], replica_bit=6) self.local_check(a) - - debug.info(2, "Testing replica column for 6t_cell") - a = factory.create(module_type="replica_column", rows=4, rbl=[2, 0], replica_bit=2) - self.local_check(a) - + globals.end_openram() # run the test from the command line diff --git a/compiler/tests/14_replica_column_test.py b/compiler/tests/14_replica_column_test.py index 4701cdca..7d73524a 100755 --- a/compiler/tests/14_replica_column_test.py +++ b/compiler/tests/14_replica_column_test.py @@ -20,18 +20,10 @@ class replica_column_test(openram_test): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) - debug.info(2, "Testing replica column for cell_6t") + debug.info(2, "Testing replica column for single port") a = factory.create(module_type="replica_column", rows=4, rbl=[1, 0], replica_bit=1) self.local_check(a) - debug.info(2, "Testing replica column for cell_1rw_1r") - a = factory.create(module_type="replica_column", rows=4, rbl=[1, 1], replica_bit=6) - self.local_check(a) - - debug.info(2, "Testing replica column for cell_1rw_1r") - a = factory.create(module_type="replica_column", rows=4, rbl=[2, 0], replica_bit=2) - self.local_check(a) - globals.end_openram() # run the test from the command line diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py index f7af5152..552308ed 100755 --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -38,7 +38,6 @@ class timing_sram_test(openram_test): # num_words=256, # num_banks=1) # c.words_per_row=2 - # OPTS.use_tech_delay_chain_size = True c.recompute_sizes() debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank") s = factory.create(module_type="sram", sram_config=c) diff --git a/compiler/verify/calibre.py b/compiler/verify/calibre.py index 5e49e6e1..9d079c2a 100644 --- a/compiler/verify/calibre.py +++ b/compiler/verify/calibre.py @@ -103,10 +103,10 @@ def write_lvs_script(cell_name, gds_name, sp_name, final_verification=False, out 'cmnTranscriptEchoToFile': 1, 'lvsRecognizeGates': 'NONE', } - # FIXME: Remove when vdd/gnd connected - #'cmnVConnectNamesState' : 'ALL', #connects all nets with the same namee - # FIXME: Remove when vdd/gnd connected - #'lvsAbortOnSupplyError' : 0 + # FIXME: Remove when vdd/gnd connected + # 'cmnVConnectNamesState' : 'ALL', #connects all nets with the same namee + # FIXME: Remove when vdd/gnd connected + # 'lvsAbortOnSupplyError' : 0 if not final_verification or not OPTS.route_supplies: lvs_runset['cmnVConnectReport']=1 @@ -115,8 +115,6 @@ def write_lvs_script(cell_name, gds_name, sp_name, final_verification=False, out else: lvs_runset['lvsAbortOnSupplyError']=1 - - # write the runset file f = open(output_path + "lvs_runset", "w") for k in sorted(iter(lvs_runset.keys())): @@ -127,8 +125,6 @@ def write_lvs_script(cell_name, gds_name, sp_name, final_verification=False, out run_file = output_path + "run_lvs.sh" f = open(run_file, "w") f.write("#!/bin/sh\n") - PDK_DIR=os.environ.get("PDK_DIR") - f.write("export PDK_DIR={}\n".format(PDK_DIR)) cmd = "{0} -gui -lvs {1}lvs_runset -batch".format(OPTS.lvs_exe[1], output_path) f.write(cmd) diff --git a/compiler/verify/magic.py b/compiler/verify/magic.py index a8345a96..07a2efab 100644 --- a/compiler/verify/magic.py +++ b/compiler/verify/magic.py @@ -81,25 +81,28 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa run_file = output_path + "run_drc.sh" f = open(run_file, "w") f.write("#!/bin/sh\n") + f.write('export OPENRAM_TECH="{}"\n'.format(os.environ['OPENRAM_TECH'])) + f.write('echo "$(date): Starting DRC using Magic {}"\n'.format(OPTS.drc_exe[1])) + f.write('\n') f.write("{} -dnull -noconsole << EOF\n".format(OPTS.drc_exe[1])) f.write("gds polygon subcell true\n") f.write("gds warning default\n") f.write("gds flatten true\n") f.write("gds readonly true\n") + f.write("gds ordering true\n") f.write("gds read {}\n".format(gds_name)) + f.write('puts "Finished reading gds {}"\n'.format(gds_name)) f.write("load {}\n".format(cell_name)) - # Flatten the cell to get rid of DRCs spanning multiple layers - # (e.g. with routes) - #f.write("flatten {}_new\n".format(cell_name)) - #f.write("load {}_new\n".format(cell_name)) - #f.write("cellname rename {0}_new {0}\n".format(cell_name)) - #f.write("load {}\n".format(cell_name)) + f.write('puts "Finished loading cell {}"\n'.format(cell_name)) f.write("cellname delete \\(UNNAMED\\)\n") f.write("writeall force\n") f.write("select top cell\n") f.write("expand\n") + f.write('puts "Finished expanding"\n') f.write("drc check\n") + f.write('puts "Finished drc check"\n') f.write("drc catchup\n") + f.write('puts "Finished drc catchup"\n') f.write("drc count total\n") f.write("drc count\n") if not sp_name: @@ -116,6 +119,7 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa if OPTS.tech_name=="sky130": f.write(pre + "extract style ngspice(si)\n") f.write(pre + "extract\n") + f.write('puts "Finished extract"\n') # f.write(pre + "ext2spice hierarchy on\n") # f.write(pre + "ext2spice scale off\n") # lvs exists in 8.2.79, but be backword compatible for now @@ -134,8 +138,12 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa # but they all seem compatible enough. f.write(pre + "ext2spice format ngspice\n") f.write(pre + "ext2spice {}\n".format(cell_name)) + f.write('puts "Finished ext2spice"\n') f.write("quit -noprompt\n") f.write("EOF\n") + f.write("magic_retcode=$?\n") + f.write('echo "$(date): Finished ($magic_retcode) DRC using Magic {}"\n'.format(OPTS.drc_exe[1])) + f.write("exit $magic_retcode\n") f.close() os.system("chmod u+x {}".format(run_file)) @@ -210,12 +218,17 @@ def write_lvs_script(cell_name, gds_name, sp_name, final_verification=False, out run_file = output_path + "/run_lvs.sh" f = open(run_file, "w") f.write("#!/bin/sh\n") + f.write('export OPENRAM_TECH="{}"\n'.format(os.environ['OPENRAM_TECH'])) + f.write('echo "$(date): Starting LVS using Netgen {}"\n'.format(OPTS.lvs_exe[1])) f.write("{} -noconsole << EOF\n".format(OPTS.lvs_exe[1])) # f.write("readnet spice {0}.spice\n".format(cell_name)) # f.write("readnet spice {0}\n".format(sp_name)) f.write("lvs {{{0}.spice {0}}} {{{1} {0}}} {2} {0}.lvs.report -full -json\n".format(cell_name, sp_name, setup_file)) f.write("quit\n") f.write("EOF\n") + f.write("magic_retcode=$?\n") + f.write('echo "$(date): Finished ($magic_retcode) LVS using Netgen {}"\n'.format(OPTS.lvs_exe[1])) + f.write("exit $magic_retcode\n") f.close() os.system("chmod u+x {}".format(run_file)) @@ -408,7 +421,9 @@ def write_script_pex_rule(gds_name, cell_name, sp_name, output): run_file = OPTS.openram_temp + "run_pex.sh" f = open(run_file, "w") f.write("#!/bin/sh\n") - f.write("{} -dnull -noconsole << eof\n".format(OPTS.drc_exe[1])) + f.write('export OPENRAM_TECH="{}"\n'.format(os.environ['OPENRAM_TECH'])) + f.write('echo "$(date): Starting PEX using Magic {}"\n'.format(OPTS.drc_exe[1])) + f.write("{} -dnull -noconsole << EOF\n".format(OPTS.drc_exe[1])) f.write("gds polygon subcell true\n") f.write("gds warning default\n") f.write("gds read {}\n".format(gds_name)) @@ -434,8 +449,11 @@ def write_script_pex_rule(gds_name, cell_name, sp_name, output): f.write("ext2spice extresist on\n") f.write("ext2spice {}\n".format(cell_name)) f.write("quit -noprompt\n") - f.write("eof\n") + f.write("EOF\n") + f.write("magic_retcode=$?\n") f.write("mv {0}.spice {1}\n".format(cell_name, output)) + f.write('echo "$(date): Finished PEX using Magic {}"\n'.format(OPTS.drc_exe[1])) + f.write("exit $magic_retcode\n") f.close() os.system("chmod u+x {}".format(run_file)) diff --git a/compiler/verify/run_script.py b/compiler/verify/run_script.py index f7bb2d9f..05a9afb9 100644 --- a/compiler/verify/run_script.py +++ b/compiler/verify/run_script.py @@ -11,24 +11,66 @@ Some baseline functions to run scripts. import os import debug +import subprocess +import time from globals import OPTS def run_script(cell_name, script="lvs"): """ Run script and create output files. """ + echo_cmd_output = OPTS.verbose_level > 1 + cwd = os.getcwd() os.chdir(OPTS.openram_temp) errfile = "{0}{1}.{2}.err".format(OPTS.openram_temp, cell_name, script) outfile = "{0}{1}.{2}.out".format(OPTS.openram_temp, cell_name, script) resultsfile = "{0}{1}.{2}.report".format(OPTS.openram_temp, cell_name, script) - cmd = "{0}run_{1}.sh 2> {2} 1> {3}".format(OPTS.openram_temp, - script, - errfile, - outfile) - debug.info(2, cmd) - os.system(cmd) + scriptpath = '{0}run_{1}.sh'.format(OPTS.openram_temp, script) + + debug.info(2, "Starting {}".format(scriptpath)) + start = time.time() + with open(outfile, 'wb') as fo, open(errfile, 'wb') as fe: + p = subprocess.Popen( + [scriptpath], stdout=fo, stderr=fe, cwd=OPTS.openram_temp) + + if echo_cmd_output: + tailo = subprocess.Popen([ + 'tail', + '-f', # Follow the output + '--pid', str(p.pid), # Close when this pid exits + outfile, + ]) + taile = subprocess.Popen([ + 'tail', + '-f', # Follow the output + '--pid', str(p.pid), # Close when this pid exits + errfile, + ]) + + lastoutput = start + while p.poll() == None: + runningfor = time.time() - start + outputdelta = time.time() - lastoutput + if outputdelta > 30: + lastoutput = time.time() + debug.info(1, "Still running {} ({:.0f} seconds)".format(scriptpath, runningfor)) + time.sleep(1) + assert p.poll() != None, (p.poll(), p) + p.wait() + + # Kill the tail commands if they haven't finished. + if echo_cmd_output: + if tailo.poll() != None: + tailo.kill() + tailo.wait() + if taile.poll() != None: + taile.kill() + taile.wait() + + debug.info(2, "Finished {} with {}".format(scriptpath, p.returncode)) + os.chdir(cwd) return (outfile, errfile, resultsfile)