diff --git a/compiler/base/custom_cell_properties.py b/compiler/base/custom_cell_properties.py index a5d40463..2e21c1ec 100644 --- a/compiler/base/custom_cell_properties.py +++ b/compiler/base/custom_cell_properties.py @@ -45,8 +45,7 @@ class _bitcell: cell_s8_6t = _cell({'bl' : 'bl0', 'br' : 'bl1', - 'wl0': 'wl0', - 'wl1': 'wl1'}) + 'wl': 'wl'}) cell_6t = _cell({'bl' : 'bl', 'br' : 'br', @@ -181,4 +180,4 @@ class cell_properties(): if ports == "{}R_{}W_{}RW".format(OPTS.num_r_ports, OPTS.num_w_ports, OPTS.num_rw_ports): use_custom_arrangement = True break - return use_custom_arrangement \ No newline at end of file + return use_custom_arrangement diff --git a/compiler/base/design.py b/compiler/base/design.py index ee985fec..96e5f26b 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -26,6 +26,12 @@ class design(hierarchy_design): self.setup_layer_constants() self.setup_multiport_constants() + def check_pins(self): + for pin_name in self.pins: + pins = self.get_pins(pin_name) + for pin in pins: + print(pin_name, pin) + def setup_layer_constants(self): """ These are some layer constants used diff --git a/compiler/base/graph_util.py b/compiler/base/graph_util.py index 5d1ee692..6d4ee5da 100644 --- a/compiler/base/graph_util.py +++ b/compiler/base/graph_util.py @@ -1,13 +1,7 @@ -import os, copy +import copy from collections import defaultdict - -import gdsMill -import tech -import math -import globals import debug -from vector import vector -from pin_layout import pin_layout + class timing_graph(): """ @@ -33,16 +27,16 @@ class timing_graph(): """Add node to graph with no edges""" node = node.lower() - if not node in self.graph: + if node not in self.graph: self.graph[node] = set() def remove_edges(self, node): """Helper function to remove edges, useful for removing vdd/gnd""" node = node.lower() - self.graph[node] = set() + self.graph[node] = set() - def get_all_paths(self, src_node, dest_node, remove_rail_nodes=True, reduce_paths=True): + def get_all_paths(self, src_node, dest_node, remove_rail_nodes=True, reduce_paths=True): """Traverse all paths from source to destination""" src_node = src_node.lower() @@ -59,44 +53,44 @@ class timing_graph(): visited = set() # Create an array to store paths - path = [] + path = [] self.all_paths = [] # Call the recursive helper function to print all paths - self.get_all_paths_util(src_node, dest_node, visited, path) + self.get_all_paths_util(src_node, dest_node, visited, path) debug.info(2, "Paths found={}".format(len(self.all_paths))) if reduce_paths: self.reduce_paths() - return self.all_paths + return self.all_paths def reduce_paths(self): """ Remove any path that is a subset of another path """ self.all_paths = [p1 for p1 in self.all_paths if not any(set(p1)<=set(p2) for p2 in self.all_paths if p1 is not p2)] - def get_all_paths_util(self, cur_node, dest_node, visited, path): + def get_all_paths_util(self, cur_node, dest_node, visited, path): """Recursive function to find all paths in a Depth First Search manner""" # Mark the current node as visited and store in path visited.add(cur_node) - path.append(cur_node) + path.append(cur_node) # If current vertex is same as destination, then print # current path[] - if cur_node == dest_node: + if cur_node == dest_node: self.all_paths.append(copy.deepcopy(path)) - else: + else: # If current vertex is not destination # Recur for all the vertices adjacent to this vertex - for node in self.graph[cur_node]: - if node not in visited: - self.get_all_paths_util(node, dest_node, visited, path) + for node in self.graph[cur_node]: + if node not in visited: + self.get_all_paths_util(node, dest_node, visited, path) # Remove current vertex from path[] and mark it as unvisited - path.pop() - visited.remove(cur_node) + path.pop() + visited.remove(cur_node) def get_timing(self, path, corner, slew, load): """Returns the analytical delays in the input path""" @@ -106,20 +100,20 @@ class timing_graph(): delays = [] cur_slew = slew - for i in range(len(path)-1): + for i in range(len(path) - 1): - path_edge_mod = self.edge_mods[(path[i], path[i+1])] + path_edge_mod = self.edge_mods[(path[i], path[i + 1])] # On the output of the current stage, get COUT from all other mods connected cout = 0 - for node in self.graph[path[i+1]]: - output_edge_mod = self.edge_mods[(path[i+1], node)] + for node in self.graph[path[i + 1]]: + output_edge_mod = self.edge_mods[(path[i + 1], node)] cout+=output_edge_mod.get_cin() # If at the last output, include the final output load - if i == len(path)-2: - cout+=load + if i == len(path) - 2: + cout += load - delays.append(path_edge_mod.analytical_delay(corner, slew, cout)) + delays.append(path_edge_mod.analytical_delay(corner, cur_slew, cout)) cur_slew = delays[-1].slew return delays @@ -127,4 +121,15 @@ class timing_graph(): def __str__(self): """ override print function output """ - return "Nodes: {}\nEdges:{} ".format(list(self.graph), self.graph) + str = "" + for n in self.graph: + str += n + "\n" + for d in self.graph[n]: + str += "\t\t-> " + d + "\n" + return str + + def __repr__(self): + """ override print function output """ + + return str(self) + diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index 79b5a53a..b4b0ef72 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -12,6 +12,7 @@ import os from globals import OPTS import tech + class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): """ Design Class for all modules to inherit the base features. @@ -137,12 +138,16 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): os.remove(tempgds) def init_graph_params(self): - """Initializes parameters relevant to the graph creation""" + """ + Initializes parameters relevant to the graph creation + """ # Only initializes a set for checking instances which should not be added self.graph_inst_exclude = set() def build_graph(self, graph, inst_name, port_nets): - """Recursively create graph from instances in module.""" + """ + Recursively create graph from instances in module. + """ # Translate port names to external nets if len(port_nets) != len(self.pins): @@ -159,7 +164,9 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): subinst.mod.build_graph(graph, subinst_name, subinst_ports) def build_names(self, name_dict, inst_name, port_nets): - """Collects all the nets and the parent inst of that net.""" + """ + Collects all the nets and the parent inst of that net. + """ # Translate port names to external nets if len(port_nets) != len(self.pins): debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets, @@ -177,64 +184,10 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): name_dict[si_port.lower()] = mod_info subinst.mod.build_names(name_dict, subinst_name, subinst_ports) - def find_aliases(self, inst_name, port_nets, path_nets, alias, alias_mod, exclusion_set=None): - """Given a list of nets, will compare the internal alias of a mod to determine - if the nets have a connection to this mod's net (but not inst). - """ - if not exclusion_set: - exclusion_set = set() - try: - self.name_dict - except AttributeError: - self.name_dict = {} - self.build_names(self.name_dict, inst_name, port_nets) - aliases = [] - for net in path_nets: - net = net.lower() - int_net = self.name_dict[net]['int_net'] - int_mod = self.name_dict[net]['mod'] - if int_mod.is_net_alias(int_net, alias, alias_mod, exclusion_set): - aliases.append(net) - return aliases - - 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).""" - if self in exclusion_set: - return False - # Check ports of this mod - for pin in self.pins: - if self.is_net_alias_name_check(known_net, pin, net_alias, mod): - return True - # Check connections of all other subinsts - mod_set = set() - for subinst, inst_conns in zip(self.insts, self.conns): - 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 - elif inst_conn.lower() == known_net.lower() and subinst.mod not in mod_set: - if subinst.mod.is_net_alias(mod_pin, net_alias, mod, exclusion_set): - return True - mod_set.add(subinst.mod) - return False - - def is_net_alias_name_check(self, parent_net, child_net, alias_net, mod): - """Utility function for checking single net alias.""" - return self == mod and \ - child_net.lower() == alias_net.lower() and \ - parent_net.lower() == alias_net.lower() - - def get_mod_net(self, parent_net, child_inst, child_conns): - """ - Given an instance and net, returns the internal net in the mod - corresponding to input net. - """ - for conn, pin in zip(child_conns, child_inst.mod.pins): - if parent_net.lower() == conn.lower(): - return pin - return None - def translate_nets(self, subinst_ports, port_dict, inst_name): - """Converts connection names to their spice hierarchy equivalent""" + """ + Converts connection names to their spice hierarchy equivalent + """ converted_conns = [] for conn in subinst_ports: if conn in port_dict: @@ -244,8 +197,10 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): return converted_conns def add_graph_edges(self, graph, port_nets): - """For every input, adds an edge to every output. - Only intended to be used for gates and other simple modules.""" + """ + For every input, adds an edge to every output. + Only intended to be used for gates and other simple modules. + """ # 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)} diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index d350c156..e4830226 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -37,6 +37,7 @@ class layout(): self.height = None self.bounding_box = None self.insts = [] # Holds module/cell layout instances + self.inst_names = set() # Set of names to check for duplicates self.objs = [] # Holds all other objects (labels, geometries, etc) self.pin_map = {} # Holds name->pin_layout map for all pins self.visited = [] # List of modules we have already visited @@ -63,6 +64,16 @@ class layout(): self.translate_all(offset) return offset + def offset_x_coordinates(self): + """ + This function is called after everything is placed to + shift the origin to the furthest left point. + Y offset is unchanged. + """ + offset = self.find_lowest_coords() + self.translate_all(offset.scale(1, 0)) + return offset + def get_gate_offset(self, x_offset, height, inv_num): """ Gets the base offset and y orientation of stacked rows of gates @@ -201,6 +212,12 @@ class layout(): def add_inst(self, name, mod, offset=[0, 0], mirror="R0", rotate=0): """ Adds an instance of a mod to this module """ + # Contacts are not really instances, so skip them + if "contact" not in mod.name: + # Check that the instance name is unique + debug.check(name not in self.inst_names, "Duplicate named instance in {0}: {1}".format(self.name, name)) + + self.inst_names.add(name) self.insts.append(geometry.instance(name, mod, offset, mirror, rotate)) debug.info(3, "adding instance {}".format(self.insts[-1])) # This is commented out for runtime reasons @@ -1019,7 +1036,7 @@ class layout(): """ import channel_route cr = channel_route.channel_route(netlist, offset, layer_stack, directions, vertical=True, parent=self) - self.add_inst("vc", cr) + self.add_inst(cr.name, cr) self.connect_inst([]) def create_horizontal_channel_route(self, netlist, offset, layer_stack, directions=None): @@ -1028,7 +1045,7 @@ class layout(): """ import channel_route cr = channel_route.channel_route(netlist, offset, layer_stack, directions, vertical=False, parent=self) - self.add_inst("hc", cr) + self.add_inst(cr.name, cr) self.connect_inst([]) def add_boundary(self, ll=vector(0, 0), ur=None): diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index 30e58809..08e2b474 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -499,3 +499,53 @@ class spice(): def return_power(self, dynamic=0.0, leakage=0.0): return power_data(dynamic, leakage) + def find_aliases(self, inst_name, port_nets, path_nets, alias, alias_mod, exclusion_set=None): + """ + Given a list of nets, will compare the internal alias of a mod to determine + if the nets have a connection to this mod's net (but not inst). + """ + if not exclusion_set: + exclusion_set = set() + try: + self.name_dict + except AttributeError: + self.name_dict = {} + self.build_names(self.name_dict, inst_name, port_nets) + aliases = [] + for net in path_nets: + net = net.lower() + int_net = self.name_dict[net]['int_net'] + int_mod = self.name_dict[net]['mod'] + if int_mod.is_net_alias(int_net, alias, alias_mod, exclusion_set): + aliases.append(net) + return aliases + + 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). + """ + if self in exclusion_set: + return False + # Check ports of this mod + for pin in self.pins: + if self.is_net_alias_name_check(known_net, pin, net_alias, mod): + return True + # Check connections of all other subinsts + mod_set = set() + for subinst, inst_conns in zip(self.insts, self.conns): + 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 + elif inst_conn.lower() == known_net.lower() and subinst.mod not in mod_set: + if subinst.mod.is_net_alias(mod_pin, net_alias, mod, exclusion_set): + return True + mod_set.add(subinst.mod) + return False + + def is_net_alias_name_check(self, parent_net, child_net, alias_net, mod): + """ + Utility function for checking single net alias. + """ + return self == mod and \ + child_net.lower() == alias_net.lower() and \ + parent_net.lower() == alias_net.lower() diff --git a/compiler/base/verilog.py b/compiler/base/verilog.py index 04344738..aadf602c 100644 --- a/compiler/base/verilog.py +++ b/compiler/base/verilog.py @@ -6,6 +6,7 @@ # All rights reserved. # import debug +import math class verilog: """ @@ -53,7 +54,7 @@ class verilog: self.vf.write("\n );\n\n") if self.write_size: - self.num_wmasks = int(self.word_size/self.write_size) + self.num_wmasks = int(math.ceil(self.word_size / self.write_size)) self.vf.write(" parameter NUM_WMASKS = {0} ;\n".format(self.num_wmasks)) self.vf.write(" parameter DATA_WIDTH = {0} ;\n".format(self.word_size)) self.vf.write(" parameter ADDR_WIDTH = {0} ;\n".format(self.addr_size)) @@ -189,9 +190,13 @@ class verilog: self.vf.write(" if (!csb{0}_reg)\n".format(port)) if self.write_size: + remainder_bits = self.word_size % self.write_size for mask in range(0,self.num_wmasks): lower = mask * self.write_size - upper = lower + self.write_size-1 + if (remainder_bits and mask == self.num_wmasks - 1): + upper = lower + remainder_bits - 1 + else: + upper = lower + self.write_size - 1 self.vf.write(" if (wmask{0}_reg[{1}])\n".format(port,mask)) self.vf.write(" mem[addr{0}_reg][{1}:{2}] = din{0}_reg[{1}:{2}];\n".format(port,upper,lower)) self.vf.write(" end\n") diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index da1b8147..6323a351 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -5,7 +5,7 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import sys,re,shutil,copy +import shutil import debug import tech import math @@ -14,13 +14,10 @@ from .trim_spice import * from .charutils import * from .sram_op import * from .bit_polarity import * -import utils from globals import OPTS from .simulation import simulation from .measurements import * -import logical_effort -import graph_util -from sram_factory import factory + class delay(simulation): """ @@ -47,10 +44,10 @@ class delay(simulation): self.targ_write_ports = [] self.period = 0 if self.write_size: - self.num_wmasks = int(self.word_size / self.write_size) + self.num_wmasks = int(math.ceil(self.word_size / self.write_size)) else: self.num_wmasks = 0 - self.set_load_slew(0,0) + self.set_load_slew(0, 0) self.set_corner(corner) self.create_signal_names() self.add_graph_exclusions() @@ -59,8 +56,14 @@ class delay(simulation): """ Create measurement names. The names themselves currently define the type of measurement """ self.delay_meas_names = ["delay_lh", "delay_hl", "slew_lh", "slew_hl"] - self.power_meas_names = ["read0_power", "read1_power", "write0_power", "write1_power", - "disabled_read0_power", "disabled_read1_power", "disabled_write0_power", "disabled_write1_power"] + self.power_meas_names = ["read0_power", + "read1_power", + "write0_power", + "write1_power", + "disabled_read0_power", + "disabled_read1_power", + "disabled_write0_power", + "disabled_write1_power"] # self.voltage_when_names = ["volt_bl", "volt_br"] # self.bitline_delay_names = ["delay_bl", "delay_br"] @@ -69,7 +72,7 @@ class delay(simulation): self.read_meas_lists = self.create_read_port_measurement_objects() self.write_meas_lists = self.create_write_port_measurement_objects() - self.check_meas_names(self.read_meas_lists+self.write_meas_lists) + self.check_meas_names(self.read_meas_lists + self.write_meas_lists) def check_meas_names(self, measures_lists): """ @@ -80,8 +83,8 @@ class delay(simulation): for meas_list in measures_lists: for meas in meas_list: name = meas.name.lower() - debug.check(name not in name_set,("SPICE measurements must have unique names. " - "Duplicate name={}").format(name)) + debug.check(name not in name_set, ("SPICE measurements must have unique names. " + "Duplicate name={}").format(name)) name_set.add(name) def create_read_port_measurement_objects(self): @@ -89,7 +92,7 @@ class delay(simulation): self.read_lib_meas = [] self.clk_frmt = "clk{0}" # Unformatted clock name - targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) # Empty values are the port and probe data bit + targ_name = "{0}{1}_{2}".format(self.dout_name, "{}", self.probe_data) # Empty values are the port and probe data bit self.delay_meas = [] self.delay_meas.append(delay_measure("delay_lh", self.clk_frmt, targ_name, "RISE", "RISE", measure_scale=1e9)) self.delay_meas[-1].meta_str = sram_op.READ_ONE # Used to index time delay values when measurements written to spice file. @@ -136,18 +139,18 @@ class delay(simulation): """ self.bitline_volt_meas = [] - self.bitline_volt_meas.append(voltage_at_measure("v_bl_READ_ZERO", - self.bl_name)) + self.bitline_volt_meas.append(voltage_at_measure("v_bl_READ_ZERO", + self.bl_name)) self.bitline_volt_meas[-1].meta_str = sram_op.READ_ZERO - self.bitline_volt_meas.append(voltage_at_measure("v_br_READ_ZERO", - self.br_name)) + self.bitline_volt_meas.append(voltage_at_measure("v_br_READ_ZERO", + self.br_name)) self.bitline_volt_meas[-1].meta_str = sram_op.READ_ZERO - self.bitline_volt_meas.append(voltage_at_measure("v_bl_READ_ONE", - self.bl_name)) + self.bitline_volt_meas.append(voltage_at_measure("v_bl_READ_ONE", + self.bl_name)) self.bitline_volt_meas[-1].meta_str = sram_op.READ_ONE - self.bitline_volt_meas.append(voltage_at_measure("v_br_READ_ONE", - self.br_name)) + self.bitline_volt_meas.append(voltage_at_measure("v_br_READ_ONE", + self.br_name)) self.bitline_volt_meas[-1].meta_str = sram_op.READ_ONE return self.bitline_volt_meas @@ -177,16 +180,16 @@ class delay(simulation): self.dout_volt_meas = [] for meas in self.delay_meas: # Output voltage measures - self.dout_volt_meas.append(voltage_at_measure("v_{}".format(meas.name), - meas.targ_name_no_port)) + self.dout_volt_meas.append(voltage_at_measure("v_{}".format(meas.name), + meas.targ_name_no_port)) self.dout_volt_meas[-1].meta_str = meas.meta_str if not OPTS.use_pex: - self.sen_meas = delay_measure("delay_sen", self.clk_frmt, self.sen_name+"{}", "FALL", "RISE", measure_scale=1e9) + self.sen_meas = delay_measure("delay_sen", self.clk_frmt, self.sen_name + "{}", "FALL", "RISE", measure_scale=1e9) else: self.sen_meas = delay_measure("delay_sen", self.clk_frmt, self.sen_name, "FALL", "RISE", measure_scale=1e9) - self.sen_meas.meta_str = sram_op.READ_ZERO + self.sen_meas.meta_str = sram_op.READ_ZERO self.sen_meas.meta_add_delay = True return self.dout_volt_meas + [self.sen_meas] @@ -194,30 +197,30 @@ class delay(simulation): def create_read_bit_measures(self): """ Adds bit measurements for read0 and read1 cycles """ - self.read_bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]} + self.read_bit_meas = {bit_polarity.NONINVERTING: [], bit_polarity.INVERTING: []} meas_cycles = (sram_op.READ_ZERO, sram_op.READ_ONE) for cycle in meas_cycles: meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name) single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data) - for polarity,meas in single_bit_meas.items(): + for polarity, meas in single_bit_meas.items(): meas.meta_str = cycle self.read_bit_meas[polarity].append(meas) # Dictionary values are lists, reduce to a single list of measurements - return [meas for meas_list in self.read_bit_meas.values() for meas in meas_list] + return [meas for meas_list in self.read_bit_meas.values() for meas in meas_list] def create_write_bit_measures(self): """ Adds bit measurements for write0 and write1 cycles """ - self.write_bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]} + self.write_bit_meas = {bit_polarity.NONINVERTING: [], bit_polarity.INVERTING: []} meas_cycles = (sram_op.WRITE_ZERO, sram_op.WRITE_ONE) for cycle in meas_cycles: meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name) single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data) - for polarity,meas in single_bit_meas.items(): + for polarity, meas in single_bit_meas.items(): meas.meta_str = cycle self.write_bit_meas[polarity].append(meas) # Dictionary values are lists, reduce to a single list of measurements - return [meas for meas_list in self.write_bit_meas.values() for meas in meas_list] + return [meas for meas_list in self.write_bit_meas.values() for meas in meas_list] def get_bit_measures(self, meas_tag, probe_address, probe_data): """ @@ -231,9 +234,9 @@ class delay(simulation): storage_names = cell_inst.mod.get_storage_net_names() debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes" "supported for characterization. Storage nets={}").format(storage_names)) - if not OPTS.use_pex: - q_name = cell_name+'.'+str(storage_names[0]) - qbar_name = cell_name+'.'+str(storage_names[1]) + if not OPTS.use_pex: + q_name = cell_name + '.' + str(storage_names[0]) + qbar_name = cell_name + '.' + str(storage_names[1]) else: bank_num = self.sram.get_bank_num(self.sram.name, bit_row, bit_col) q_name = "bitcell_Q_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col) @@ -243,179 +246,15 @@ class delay(simulation): # but they is enforced externally. {} added to names to differentiate between ports allow the # measurements are independent of the ports q_meas = voltage_at_measure("v_q_{}".format(meas_tag), q_name) - qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name) + qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name) - return {bit_polarity.NONINVERTING:q_meas, bit_polarity.INVERTING:qbar_meas} + return {bit_polarity.NONINVERTING: q_meas, bit_polarity.INVERTING: qbar_meas} - def set_load_slew(self,load,slew): + def set_load_slew(self, load, slew): """ Set the load and slew """ self.load = load self.slew = slew - - def add_graph_exclusions(self): - """Exclude portions of SRAM from timing graph which are not relevant""" - - # other initializations can only be done during analysis when a bit has been selected - # for testing. - self.sram.bank.graph_exclude_precharge() - self.sram.graph_exclude_addr_dff() - self.sram.graph_exclude_data_dff() - self.sram.graph_exclude_ctrl_dffs() - self.sram.bank.bitcell_array.graph_exclude_replica_col_bits() - - def create_graph(self): - """Creates timing graph to generate the timing paths for the SRAM output.""" - - self.sram.bank.bitcell_array.bitcell_array.init_graph_params() # Removes previous bit exclusions - self.sram.bank.bitcell_array.graph_exclude_bits(self.wordline_row, self.bitline_column) - - # Generate new graph every analysis as edges might change depending on test bit - self.graph = graph_util.timing_graph() - self.sram_spc_name = "X{}".format(self.sram.name) - self.sram.build_graph(self.graph,self.sram_spc_name,self.pins) - - def set_internal_spice_names(self): - """Sets important names for characterization such as Sense amp enable and internal bit nets.""" - - port = self.read_ports[0] - if not OPTS.use_pex: - self.graph.get_all_paths('{}{}'.format("clk", port), - '{}{}_{}'.format(self.dout_name, port, self.probe_data)) - - sen_with_port = self.get_sen_name(self.graph.all_paths) - if sen_with_port.endswith(str(port)): - self.sen_name = sen_with_port[:-len(str(port))] - else: - self.sen_name = sen_with_port - debug.warning("Error occurred while determining SEN name. Can cause faults in simulation.") - - debug.info(2,"s_en name = {}".format(self.sen_name)) - - 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)) - - if bl_name_port.endswith(str(port)+"_"+str(self.probe_data)): - 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 - else: - 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)): - 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 - else: - self.br_name = br_name_port - debug.warning("Error occurred while determining bitline names. Can cause faults in simulation.") - debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name)) - else: - self.graph.get_all_paths('{}{}'.format("clk", port), - '{}{}_{}'.format(self.dout_name, port, self.probe_data)) - - self.sen_name = self.get_sen_name(self.graph.all_paths) - debug.info(2,"s_en name = {}".format(self.sen_name)) - - - self.bl_name = "bl{0}_{1}".format(port, OPTS.word_size-1) - self.br_name = "br{0}_{1}".format(port, OPTS.word_size-1) - debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name)) - - - def get_sen_name(self, paths, assumed_port=None): - """ - Gets the signal name associated with the sense amp enable from input paths. - Only expects a single path to contain the sen signal name. - """ - - sa_mods = factory.get_mods(OPTS.sense_amp) - # Any sense amp instantiated should be identical, any change to that - # will require some identification to determine the mod desired. - debug.check(len(sa_mods) == 1, "Only expected one type of Sense Amp. Cannot perform s_en checks.") - enable_name = sa_mods[0].get_enable_name() - sen_name = self.get_alias_in_path(paths, enable_name, sa_mods[0]) - if OPTS.use_pex: - sen_name = sen_name.split('.')[-1] - return sen_name - - def get_bl_name(self, paths, port): - """Gets the signal name associated with the bitlines in the bank.""" - - cell_mod = factory.create(module_type=OPTS.bitcell) - cell_bl = cell_mod.get_bl_name(port) - cell_br = cell_mod.get_br_name(port) - - bl_found = False - # Only a single path should contain a single s_en name. Anything else is an error. - bl_names = [] - exclude_set = self.get_bl_name_search_exclusions() - for int_net in [cell_bl, cell_br]: - bl_names.append(self.get_alias_in_path(paths, int_net, cell_mod, exclude_set)) - if OPTS.use_pex: - for i in range(len(bl_names)): - bl_names[i] = bl_names[i].split('.')[-1] - return bl_names[0], bl_names[1] - - - def get_bl_name_search_exclusions(self): - """Gets the mods as a set which should be excluded while searching for name.""" - - # Exclude the RBL as it contains bitcells which are not in the main bitcell array - # so it makes the search awkward - return set(factory.get_mods(OPTS.replica_bitline)) - - def get_primary_cell_mod(self, cell_mods): - """ - Distinguish bitcell array mod from replica bitline array. - Assume there are no replica bitcells in the primary array. - """ - if len(cell_mods) == 1: - return cell_mods[0] - rbc_mods = factory.get_mods(OPTS.replica_bitcell) - non_rbc_mods = [] - for bitcell in cell_mods: - has_cell = False - for replica_cell in rbc_mods: - has_cell = has_cell or replica_cell.contains(bitcell, replica_cell.mods) - if not has_cell: - non_rbc_mods.append(bitcell) - if len(non_rbc_mods) != 1: - debug.error('Multiple bitcell mods found. Cannot distinguish for characterization',1) - return non_rbc_mods[0] - - def are_mod_pins_equal(self, mods): - """Determines if there are pins differences in the input mods""" - - if len(mods) == 0: - return True - pins = mods[0].pins - for mod in mods[1:]: - if pins != mod.pins: - return False - return True - - def get_alias_in_path(self, paths, int_net, mod, exclusion_set=None): - """ - Finds a single alias for the int_net in given paths. - More or less hits cause an error - """ - - net_found = False - for path in paths: - aliases = self.sram.find_aliases(self.sram_spc_name, self.pins, path, int_net, mod, exclusion_set) - if net_found and len(aliases) >= 1: - debug.error('Found multiple paths with {} net.'.format(int_net),1) - elif len(aliases) > 1: - debug.error('Found multiple {} nets in single path.'.format(int_net),1) - elif not net_found and len(aliases) == 1: - path_net_name = aliases[0] - net_found = True - if not net_found: - debug.error("Could not find {} net in timing paths.".format(int_net),1) - - return path_net_name def check_arguments(self): """Checks if arguments given for write_stimulus() meets requirements""" @@ -423,19 +262,19 @@ class delay(simulation): try: int(self.probe_address, 2) except ValueError: - debug.error("Probe Address is not of binary form: {0}".format(self.probe_address),1) + debug.error("Probe Address is not of binary form: {0}".format(self.probe_address), 1) if len(self.probe_address) != self.addr_size: - debug.error("Probe Address's number of bits does not correspond to given SRAM",1) + debug.error("Probe Address's number of bits does not correspond to given SRAM", 1) if not isinstance(self.probe_data, int) or self.probe_data>self.word_size or self.probe_data<0: - debug.error("Given probe_data is not an integer to specify a data bit",1) + debug.error("Given probe_data is not an integer to specify a data bit", 1) # Adding port options here which the characterizer cannot handle. Some may be added later like ROM if len(self.read_ports) == 0: - debug.error("Characterizer does not currently support SRAMs without read ports.",1) + debug.error("Characterizer does not currently support SRAMs without read ports.", 1) if len(self.write_ports) == 0: - debug.error("Characterizer does not currently support SRAMs without write ports.",1) + debug.error("Characterizer does not currently support SRAMs without write ports.", 1) def write_generic_stimulus(self): """ Create the instance, supplies, loads, and access transistors. """ @@ -446,19 +285,14 @@ class delay(simulation): # instantiate the sram self.sf.write("\n* Instantiation of the SRAM\n") - if not OPTS.use_pex: - self.stim.inst_model(pins=self.pins, - model_name=self.sram.name) - else: - self.stim.inst_sram_pex(pins=self.pins, - model_name=self.sram.name) + self.stim.inst_model(pins=self.pins, + model_name=self.sram.name) self.sf.write("\n* SRAM output loads\n") for port in self.read_ports: for i in range(self.word_size): - self.sf.write("CD{0}{1} {2}{0}_{1} 0 {3}f\n".format(port,i,self.dout_name,self.load)) + self.sf.write("CD{0}{1} {2}{0}_{1} 0 {3}f\n".format(port, i, self.dout_name, self.load)) - def write_delay_stimulus(self): """ Creates a stimulus file for simulations to probe a bitcell at a given clock period. @@ -488,7 +322,6 @@ class delay(simulation): self.gen_data() self.gen_addr() - # generate control signals self.sf.write("\n* Generation of control signals\n") self.gen_control() @@ -510,7 +343,6 @@ class delay(simulation): self.sf.close() - def write_power_stimulus(self, trim): """ Creates a stimulus file to measure leakage power only. This works on the *untrimmed netlist*. @@ -535,11 +367,11 @@ class delay(simulation): self.sf.write("\n* Generation of data and address signals\n") for write_port in self.write_ports: for i in range(self.word_size): - self.stim.gen_constant(sig_name="{0}{1}_{2} ".format(self.din_name,write_port, i), - v_val=0) + self.stim.gen_constant(sig_name="{0}{1}_{2} ".format(self.din_name, write_port, i), + v_val=0) for port in self.all_ports: for i in range(self.addr_size): - self.stim.gen_constant(sig_name="{0}{1}_{2}".format(self.addr_name,port, i), + self.stim.gen_constant(sig_name="{0}{1}_{2}".format(self.addr_name, port, i), v_val=0) # generate control signals @@ -551,12 +383,12 @@ class delay(simulation): self.sf.write("\n* Generation of global clock signal\n") for port in self.all_ports: - self.stim.gen_constant(sig_name="CLK{0}".format(port), v_val=0) + self.stim.gen_constant(sig_name="CLK{0}".format(port), v_val=0) self.write_power_measures() # run until the end of the cycle time - self.stim.write_control(2*self.period) + self.stim.write_control(2 * self.period) self.sf.close() @@ -602,7 +434,7 @@ class delay(simulation): # These measurements have there time further delayed to the neg. edge of the clock. if delay_obj.meta_add_delay: - meas_cycle_delay += self.period/2 + meas_cycle_delay += self.period / 2 return (meas_cycle_delay, meas_cycle_delay, self.vdd_voltage, port) @@ -611,7 +443,7 @@ class delay(simulation): # Return value is intended to match the power measure format: t_initial, t_final, port t_initial = self.cycle_times[self.measure_cycles[port][power_obj.meta_str]] - t_final = self.cycle_times[self.measure_cycles[port][power_obj.meta_str]+1] + t_final = self.cycle_times[self.measure_cycles[port][power_obj.meta_str] + 1] return (t_initial, t_final, port) @@ -624,7 +456,7 @@ class delay(simulation): # Measurement occurs slightly into the next period so we know that the value # "stuck" after the end of the period -> current period start + 1.25*period - at_time = meas_cycle+1.25*self.period + at_time = meas_cycle + 1.25 * self.period return (at_time, port) @@ -634,7 +466,7 @@ class delay(simulation): """ # Only checking 0 value reads for now. - t_trig = meas_cycle_delay = self.cycle_times[self.measure_cycles[port][sram_op.READ_ZERO]] + t_trig = self.cycle_times[self.measure_cycles[port][sram_op.READ_ZERO]] return (t_trig, self.vdd_voltage, port) @@ -649,7 +481,6 @@ class delay(simulation): measure_variant_inp_tuple = self.get_measure_variants(port, measure, "read") measure.write_measure(self.stim, measure_variant_inp_tuple) - def write_delay_measures_write_port(self, port): """ Write the measure statements to quantify the power results for a write port. @@ -682,7 +513,6 @@ class delay(simulation): self.sf.write("* Write ports {}\n".format(write_port)) self.write_delay_measures_write_port(write_port) - def write_power_measures(self): """ Write the measure statements to quantify the leakage power only. @@ -692,7 +522,7 @@ class delay(simulation): # add measure statements for power t_initial = self.period - t_final = 2*self.period + t_final = 2 * self.period self.stim.gen_meas_power(meas_name="leakage_power", t_initial=t_initial, t_final=t_final) @@ -712,7 +542,7 @@ class delay(simulation): while True: time_out -= 1 if (time_out <= 0): - debug.error("Timed out, could not find a feasible period.",2) + debug.error("Timed out, could not find a feasible period.", 2) # Write ports are assumed non-critical to timing, so the first available is used self.targ_write_ports = [self.write_ports[0]] @@ -758,7 +588,6 @@ class delay(simulation): feasible_delays[self.read_ports[0]] = self.find_feasible_period_one_port(self.read_ports[0]) previous_period = self.period - # Loops through all the ports checks if the feasible period works. Everything restarts it if does not. # Write ports do not produce delays which is why they are not included here. i = 1 @@ -783,7 +612,7 @@ class delay(simulation): include leakage of all cells. """ - debug.check(self.period > 0, "Target simulation period non-positive") + debug.check(self.period > 0, "Target simulation period non-positive") self.write_delay_stimulus() @@ -799,30 +628,29 @@ class delay(simulation): for port in self.targ_write_ports: if not self.check_bit_measures(self.write_bit_meas, port): - return(False,{}) + return(False, {}) - debug.info(2, "Checking write values for port {}".format(port)) + debug.info(2, "Checking write values for port {}".format(port)) write_port_dict = {} for measure in self.write_lib_meas: write_port_dict[measure.name] = measure.retrieve_measure(port=port) if not check_dict_values_is_float(write_port_dict): - debug.error("Failed to Measure Write Port Values:\n\t\t{0}".format(write_port_dict),1) + debug.error("Failed to Measure Write Port Values:\n\t\t{0}".format(write_port_dict), 1) result[port].update(write_port_dict) - for port in self.targ_read_ports: # First, check that the memory has the right values at the right times if not self.check_bit_measures(self.read_bit_meas, port): - return(False,{}) + return(False, {}) debug.info(2, "Checking read delay values for port {}".format(port)) # Check sen timing, then bitlines, then general measurements. if not self.check_sen_measure(port): - return (False,{}) + return (False, {}) if not self.check_read_debug_measures(port): - return (False,{}) + return (False, {}) # Check timing for read ports. Power is only checked if it was read correctly read_port_dict = {} @@ -830,26 +658,25 @@ class delay(simulation): read_port_dict[measure.name] = measure.retrieve_measure(port=port) if not self.check_valid_delays(read_port_dict): - return (False,{}) + return (False, {}) if not check_dict_values_is_float(read_port_dict): - debug.error("Failed to Measure Read Port Values:\n\t\t{0}".format(read_port_dict),1) + debug.error("Failed to Measure Read Port Values:\n\t\t{0}".format(read_port_dict), 1) result[port].update(read_port_dict) - return (True,result) + return (True, result) def check_sen_measure(self, port): """Checks that the sen occurred within a half-period""" sen_val = self.sen_meas.retrieve_measure(port=port) - debug.info(2,"s_en delay={}ns".format(sen_val)) + debug.info(2, "s_en delay={}ns".format(sen_val)) if self.sen_meas.meta_add_delay: - max_delay = self.period/2 + max_delay = self.period / 2 else: max_delay = self.period return not (type(sen_val) != float or sen_val > max_delay) - def check_read_debug_measures(self, port): """Debug measures that indicate special conditions.""" @@ -863,23 +690,23 @@ class delay(simulation): val = meas.retrieve_measure(port=port) if self.bl_name == meas.targ_name_no_port: bl_vals[meas.meta_str] = val - elif self.br_name == meas.targ_name_no_port: + elif self.br_name == meas.targ_name_no_port: br_vals[meas.meta_str] = val - debug.info(2,"{}={}".format(meas.name,val)) + debug.info(2, "{}={}".format(meas.name, val)) dout_success = True bl_success = False for meas in self.dout_volt_meas: val = meas.retrieve_measure(port=port) - debug.info(2,"{}={}".format(meas.name, val)) - debug.check(type(val)==float, "Error retrieving numeric measurement: {0} {1}".format(meas.name,val)) + debug.info(2, "{}={}".format(meas.name, val)) + debug.check(type(val)==float, "Error retrieving numeric measurement: {0} {1}".format(meas.name, val)) - if meas.meta_str == sram_op.READ_ONE and val < self.vdd_voltage*0.1: + if meas.meta_str == sram_op.READ_ONE and val < self.vdd_voltage * 0.1: dout_success = False debug.info(1, "Debug measurement failed. Value {}V was read on read 1 cycle.".format(val)) bl_success = self.check_bitline_meas(bl_vals[sram_op.READ_ONE], br_vals[sram_op.READ_ONE]) - elif meas.meta_str == sram_op.READ_ZERO and val > self.vdd_voltage*0.9: + elif meas.meta_str == sram_op.READ_ZERO and val > self.vdd_voltage * 0.9: dout_success = False debug.info(1, "Debug measurement failed. Value {}V was read on read 0 cycle.".format(val)) bl_success = self.check_bitline_meas(br_vals[sram_op.READ_ONE], bl_vals[sram_op.READ_ONE]) @@ -887,10 +714,9 @@ class delay(simulation): # If the bitlines have a correct value while the output does not then that is a # sen error. FIXME: there are other checks that can be done to solidfy this conclusion. if not dout_success and bl_success: - debug.error("Sense amp enable timing error. Increase the delay chain through the configuration file.",1) + debug.error("Sense amp enable timing error. Increase the delay chain through the configuration file.", 1) return dout_success - def check_bit_measures(self, bit_measures, port): """ @@ -901,29 +727,29 @@ class delay(simulation): for polarity, meas_list in bit_measures.items(): for meas in meas_list: val = meas.retrieve_measure(port=port) - debug.info(2,"{}={}".format(meas.name, val)) + debug.info(2, "{}={}".format(meas.name, val)) if type(val) != float: continue meas_cycle = meas.meta_str # Loose error conditions. Assume it's not metastable but account for noise during reads. if (meas_cycle == sram_op.READ_ZERO and polarity == bit_polarity.NONINVERTING) or\ (meas_cycle == sram_op.READ_ONE and polarity == bit_polarity.INVERTING): - success = val < self.vdd_voltage/2 + success = val < self.vdd_voltage / 2 elif (meas_cycle == sram_op.READ_ZERO and polarity == bit_polarity.INVERTING) or\ (meas_cycle == sram_op.READ_ONE and polarity == bit_polarity.NONINVERTING): - success = val > self.vdd_voltage/2 + success = val > self.vdd_voltage / 2 elif (meas_cycle == sram_op.WRITE_ZERO and polarity == bit_polarity.INVERTING) or\ (meas_cycle == sram_op.WRITE_ONE and polarity == bit_polarity.NONINVERTING): - success = val > self.vdd_voltage/2 + success = val > self.vdd_voltage / 2 elif (meas_cycle == sram_op.WRITE_ONE and polarity == bit_polarity.INVERTING) or\ (meas_cycle == sram_op.WRITE_ZERO and polarity == bit_polarity.NONINVERTING): - success = val < self.vdd_voltage/2 + success = val < self.vdd_voltage / 2 if not success: - debug.info(1,("Wrong value detected on probe bit during read/write cycle. " - "Check writes and control logic for bugs.\n measure={}, op={}, " - "bit_storage={}, V(bit)={}").format(meas.name, meas_cycle.name, polarity.name,val)) + debug.info(1, ("Wrong value detected on probe bit during read/write cycle. " + "Check writes and control logic for bugs.\n measure={}, op={}, " + "bit_storage={}, V(bit)={}").format(meas.name, meas_cycle.name, polarity.name, val)) - return success + return success def check_bitline_meas(self, v_discharged_bl, v_charged_bl): """ @@ -933,11 +759,11 @@ class delay(simulation): # The inputs looks at discharge/charged bitline rather than left or right (bl/br) # Performs two checks, discharging bitline is at least 10% away from vdd and there is a # 10% vdd difference between the bitlines. Both need to fail to be considered a s_en error. - min_dicharge = v_discharged_bl < self.vdd_voltage*0.9 - min_diff = (v_charged_bl - v_discharged_bl) > self.vdd_voltage*0.1 + min_dicharge = v_discharged_bl < self.vdd_voltage * 0.9 + min_diff = (v_charged_bl - v_discharged_bl) > self.vdd_voltage * 0.1 - debug.info(1,"min_dicharge={}, min_diff={}".format(min_dicharge,min_diff)) - return (min_dicharge and min_diff) + debug.info(1, "min_dicharge={}, min_diff={}".format(min_dicharge, min_diff)) + return (min_dicharge and min_diff) def run_power_simulation(self): """ @@ -948,20 +774,20 @@ class delay(simulation): self.write_power_stimulus(trim=False) self.stim.run_sim() leakage_power=parse_spice_list("timing", "leakage_power") - debug.check(leakage_power!="Failed","Could not measure leakage power.") - debug.info(1, "Leakage power of full array is {0} mW".format(leakage_power*1e3)) + debug.check(leakage_power!="Failed", "Could not measure leakage power.") + debug.info(1, "Leakage power of full array is {0} mW".format(leakage_power * 1e3)) # debug # sys.exit(1) self.write_power_stimulus(trim=True) self.stim.run_sim() trim_leakage_power=parse_spice_list("timing", "leakage_power") - debug.check(trim_leakage_power!="Failed","Could not measure leakage power.") - debug.info(1, "Leakage power of trimmed array is {0} mW".format(trim_leakage_power*1e3)) + debug.check(trim_leakage_power!="Failed", "Could not measure leakage power.") + debug.info(1, "Leakage power of trimmed array is {0} mW".format(trim_leakage_power * 1e3)) # For debug, you sometimes want to inspect each simulation. # key=raw_input("press return to continue") - return (leakage_power*1e3, trim_leakage_power*1e3) + return (leakage_power * 1e3, trim_leakage_power * 1e3) def check_valid_delays(self, result_dict): """ Check if the measurements are defined and if they are valid. """ @@ -971,30 +797,31 @@ class delay(simulation): delay_lh = result_dict["delay_lh"] slew_hl = result_dict["slew_hl"] slew_lh = result_dict["slew_lh"] - period_load_slew_str = "period {0} load {1} slew {2}".format(self.period,self.load, self.slew) + period_load_slew_str = "period {0} load {1} slew {2}".format(self.period, self.load, self.slew) # if it failed or the read was longer than a period if type(delay_hl)!=float or type(delay_lh)!=float or type(slew_lh)!=float or type(slew_hl)!=float: delays_str = "delay_hl={0} delay_lh={1}".format(delay_hl, delay_lh) - slews_str = "slew_hl={0} slew_lh={1}".format(slew_hl,slew_lh) - debug.info(2,"Failed simulation (in sec):\n\t\t{0}\n\t\t{1}\n\t\t{2}".format(period_load_slew_str, - delays_str, - slews_str)) + slews_str = "slew_hl={0} slew_lh={1}".format(slew_hl, slew_lh) + debug.info(2, "Failed simulation (in sec):\n\t\t{0}\n\t\t{1}\n\t\t{2}".format(period_load_slew_str, + delays_str, + slews_str)) return False delays_str = "delay_hl={0} delay_lh={1}".format(delay_hl, delay_lh) - slews_str = "slew_hl={0} slew_lh={1}".format(slew_hl,slew_lh) - half_period = self.period/2 # high-to-low delays start at neg. clk edge, so they need to be less than half_period + slews_str = "slew_hl={0} slew_lh={1}".format(slew_hl, slew_lh) + # high-to-low delays start at neg. clk edge, so they need to be less than half_period + half_period = self.period / 2 if abs(delay_hl)>half_period or abs(delay_lh)>self.period or abs(slew_hl)>half_period or abs(slew_lh)>self.period \ or delay_hl<0 or delay_lh<0 or slew_hl<0 or slew_lh<0: - debug.info(2,"UNsuccessful simulation (in ns):\n\t\t{0}\n\t\t{1}\n\t\t{2}".format(period_load_slew_str, - delays_str, - slews_str)) + debug.info(2, "UNsuccessful simulation (in ns):\n\t\t{0}\n\t\t{1}\n\t\t{2}".format(period_load_slew_str, + delays_str, + slews_str)) return False else: - debug.info(2,"Successful simulation (in ns):\n\t\t{0}\n\t\t{1}\n\t\t{2}".format(period_load_slew_str, - delays_str, - slews_str)) + debug.info(2, "Successful simulation (in ns):\n\t\t{0}\n\t\t{1}\n\t\t{2}".format(period_load_slew_str, + delays_str, + slews_str)) return True @@ -1013,12 +840,12 @@ class delay(simulation): target_period = self.find_min_period_one_port(feasible_delays, port, lb_period, ub_period, target_period) # The min period of one port becomes the new lower bound. Reset the upper_bound. lb_period = target_period - ub_period = feasible_period + ub_period = feasible_period # Clear the target ports before leaving self.targ_read_ports = [] self.targ_write_ports = [] - return target_period + return target_period def find_min_period_one_port(self, feasible_delays, port, lb_period, ub_period, target_period): """ @@ -1039,7 +866,7 @@ class delay(simulation): while True: time_out -= 1 if (time_out <= 0): - debug.error("Timed out, could not converge on minimum period.",2) + debug.error("Timed out, could not converge on minimum period.", 2) self.period = target_period debug.info(1, "MinPeriod Search Port {3}: {0}ns (ub: {1} lb: {2})".format(target_period, @@ -1060,7 +887,6 @@ class delay(simulation): target_period = 0.5 * (ub_period + lb_period) # key=input("press return to continue") - def try_period(self, feasible_delays): """ This tries to simulate a period and checks if the result @@ -1083,19 +909,19 @@ class delay(simulation): if self.sram.col_addr_size>0 and "slew" in dname: continue - if not relative_compare(results[port][dname],feasible_delays[port][dname],error_tolerance=0.05): - debug.info(2,"Delay too big {0} vs {1}".format(results[port][dname],feasible_delays[port][dname])) + if not relative_compare(results[port][dname], feasible_delays[port][dname], error_tolerance=0.05): + debug.info(2, "Delay too big {0} vs {1}".format(results[port][dname], feasible_delays[port][dname])) return False # key=raw_input("press return to continue") delay_str = ', '.join("{0}={1}ns".format(mname, results[port][mname]) for mname in self.delay_meas_names) - debug.info(2,"Successful period {0}, Port {2}, {1}".format(self.period, - delay_str, - port)) + debug.info(2, "Successful period {0}, Port {2}, {1}".format(self.period, + delay_str, + port)) return True - def set_probe(self,probe_address, probe_data): + def set_probe(self, probe_address, probe_data): """ Probe address and data can be set separately to utilize other functions in this characterizer besides analyze. @@ -1111,16 +937,16 @@ class delay(simulation): """Calculates bitline column number of data bit under test using bit position and mux size""" if self.sram.col_addr_size>0: - col_address = int(probe_address[0:self.sram.col_addr_size],2) + col_address = int(probe_address[0:self.sram.col_addr_size], 2) else: col_address = 0 - bl_column = int(self.sram.words_per_row*probe_data + col_address) - return bl_column + bl_column = int(self.sram.words_per_row * probe_data + col_address) + return bl_column def get_address_row_number(self, probe_address): """Calculates wordline row number of data bit under test using address and column mux size""" - return int(probe_address[self.sram.col_addr_size:],2) + return int(probe_address[self.sram.col_addr_size:], 2) def prepare_netlist(self): """ Prepare a trimmed netlist and regular netlist. """ @@ -1134,7 +960,7 @@ class delay(simulation): self.num_cols, self.word_size, self.num_spare_rows) - self.trimsp.trim(self.probe_address,self.probe_data) + self.trimsp.trim(self.probe_address, self.probe_data) else: # The non-reduced netlist file when it is disabled self.trim_sp_file = "{}sram.sp".format(OPTS.openram_temp) @@ -1169,9 +995,9 @@ class delay(simulation): feasible_delays = self.find_feasible_period() # 2) Finds the minimum period without degrading the delays by X% - self.set_load_slew(max(loads),max(slews)) + self.set_load_slew(max(loads), max(slews)) min_period = self.find_min_period(feasible_delays) - debug.check(type(min_period)==float,"Couldn't find minimum period.") + debug.check(type(min_period)==float, "Couldn't find minimum period.") debug.info(1, "Min Period Found: {0}ns".format(min_period)) char_sram_data["min_period"] = round_time(min_period) @@ -1205,14 +1031,14 @@ class delay(simulation): self.targ_write_ports = self.write_ports for slew in slews: for load in loads: - self.set_load_slew(load,slew) + self.set_load_slew(load, slew) # Find the delay, dynamic power, and leakage power of the trimmed array. (success, delay_results) = self.run_delay_simulation() - debug.check(success,"Couldn't run a simulation. slew={0} load={1}\n".format(self.slew,self.load)) - debug.info(1, "Simulation Passed: Port {0} slew={1} load={2}".format("All", self.slew,self.load)) + debug.check(success, "Couldn't run a simulation. slew={0} load={1}\n".format(self.slew, self.load)) + debug.info(1, "Simulation Passed: Port {0} slew={1} load={2}".format("All", self.slew, self.load)) # The results has a dict for every port but dicts can be empty (e.g. ports were not targeted). for port in self.all_ports: - for mname,value in delay_results[port].items(): + for mname, value in delay_results[port].items(): if "power" in mname: # Subtract partial array leakage and add full array leakage for the power measures measure_data[port][mname].append(value + leakage_offset) @@ -1233,8 +1059,8 @@ class delay(simulation): elif c=="1": inverse_address += "0" else: - debug.error("Non-binary address string",1) - return inverse_address+column_addr + debug.error("Non-binary address string", 1) + return inverse_address + column_addr def gen_test_cycles_one_port(self, read_port, write_port): """Sets a list of key time-points [ns] of the waveform (each rising edge) @@ -1244,10 +1070,9 @@ class delay(simulation): inverse_address = self.calculate_inverse_address() # For now, ignore data patterns and write ones or zeros - data_ones = "1"*self.word_size - data_zeros = "0"*self.word_size - wmask_ones = "1"*self.num_wmasks - wmask_zeroes = "0"*self.num_wmasks + data_ones = "1" * self.word_size + data_zeros = "0" * self.word_size + wmask_ones = "1" * self.num_wmasks if self.t_current == 0: self.add_noop_all_ports("Idle cycle (no positive clock edge)") @@ -1263,10 +1088,10 @@ class delay(simulation): data_zeros, wmask_ones, write_port) - self.measure_cycles[write_port][sram_op.WRITE_ZERO] = len(self.cycle_times)-1 + self.measure_cycles[write_port][sram_op.WRITE_ZERO] = len(self.cycle_times) - 1 self.add_noop_clock_one_port(write_port) - self.measure_cycles[write_port]["disabled_write0"] = len(self.cycle_times)-1 + self.measure_cycles[write_port]["disabled_write0"] = len(self.cycle_times) - 1 # This also ensures we will have a H->L transition on the next read self.add_read("R data 1 address {} to set dout caps".format(inverse_address), @@ -1276,12 +1101,11 @@ class delay(simulation): self.add_read("R data 0 address {} to check W0 worked".format(self.probe_address), self.probe_address, read_port) - self.measure_cycles[read_port][sram_op.READ_ZERO] = len(self.cycle_times)-1 + self.measure_cycles[read_port][sram_op.READ_ZERO] = len(self.cycle_times) - 1 self.add_noop_clock_one_port(read_port) self.measure_cycles[read_port]["disabled_read0"] = len(self.cycle_times) - 1 - self.add_noop_all_ports("Idle cycle (if read takes >1 cycle)") self.add_write("W data 1 address {} to write value".format(self.probe_address), @@ -1289,10 +1113,10 @@ class delay(simulation): data_ones, wmask_ones, write_port) - self.measure_cycles[write_port][sram_op.WRITE_ONE] = len(self.cycle_times)-1 + self.measure_cycles[write_port][sram_op.WRITE_ONE] = len(self.cycle_times) - 1 self.add_noop_clock_one_port(write_port) - self.measure_cycles[write_port]["disabled_write1"] = len(self.cycle_times)-1 + self.measure_cycles[write_port]["disabled_write1"] = len(self.cycle_times) - 1 self.add_write("W data 0 address {} to clear din caps".format(inverse_address), inverse_address, @@ -1303,7 +1127,6 @@ class delay(simulation): self.add_noop_clock_one_port(read_port) self.measure_cycles[read_port]["disabled_read1"] = len(self.cycle_times) - 1 - # This also ensures we will have a L->H transition on the next read self.add_read("R data 0 address {} to clear dout caps".format(inverse_address), inverse_address, @@ -1312,11 +1135,11 @@ class delay(simulation): self.add_read("R data 1 address {} to check W1 worked".format(self.probe_address), self.probe_address, read_port) - self.measure_cycles[read_port][sram_op.READ_ONE] = len(self.cycle_times)-1 + self.measure_cycles[read_port][sram_op.READ_ONE] = len(self.cycle_times) - 1 self.add_noop_all_ports("Idle cycle (if read takes >1 cycle))") - def get_available_port(self,get_read_port): + def get_available_port(self, get_read_port): """Returns the first accessible read or write port. """ if get_read_port and len(self.read_ports) > 0: @@ -1338,14 +1161,16 @@ class delay(simulation): # Using this requires setting at least one port to target for simulation. if len(self.targ_write_ports) == 0 or len(self.targ_read_ports) == 0: - debug.error("Write and read port must be specified for characterization.",1) + debug.error("Write and read port must be specified for characterization.", 1) self.set_stimulus_variables() # Get any available read/write port in case only a single write or read ports is being characterized. - cur_read_port = self.get_available_port(get_read_port=True) - cur_write_port = self.get_available_port(get_read_port=False) - debug.check(cur_read_port != None, "Characterizer requires at least 1 read port") - debug.check(cur_write_port != None, "Characterizer requires at least 1 write port") + cur_read_port = self.get_available_port(get_read_port=True) + cur_write_port = self.get_available_port(get_read_port=False) + debug.check(cur_read_port != None, + "Characterizer requires at least 1 read port") + debug.check(cur_write_port != None, + "Characterizer requires at least 1 write port") # Create test cycles for specified target ports. write_pos = 0 @@ -1372,7 +1197,7 @@ class delay(simulation): delay = delays[0] for i in range(1, len(delays)): delay+=delays[i] - return delay + return delay def analytical_delay(self, slews, loads): """ @@ -1382,23 +1207,23 @@ class delay(simulation): debug.warning("In analytical mode, all ports have the timing of the first read port.") # Probe set to 0th bit, does not matter for analytical delay. - self.set_probe('0'*self.addr_size, 0) + self.set_probe('0' * self.addr_size, 0) self.create_graph() self.set_internal_spice_names() self.create_measurement_names() port = self.read_ports[0] - self.graph.get_all_paths('{}{}'.format("clk", port), + self.graph.get_all_paths('{}{}'.format("clk", port), '{}{}_{}'.format(self.dout_name, port, self.probe_data)) # Select the path with the bitline (bl) - bl_name,br_name = self.get_bl_name(self.graph.all_paths, port) + bl_name, br_name = self.get_bl_name(self.graph.all_paths, port) bl_path = [path for path in self.graph.all_paths if bl_name in path][0] # Set delay/power for slews and loads port_data = self.get_empty_measure_data_dict() power = self.analytical_power(slews, loads) - debug.info(1,'Slew, Load, Delay(ns), Slew(ns)') + debug.info(1, 'Slew, Load, Delay(ns), Slew(ns)') max_delay = 0.0 for slew in slews: for load in loads: @@ -1407,41 +1232,45 @@ class delay(simulation): total_delay = self.sum_delays(path_delays) max_delay = max(max_delay, total_delay.delay) - debug.info(1,'{}, {}, {}, {}'.format(slew,load,total_delay.delay/1e3, total_delay.slew/1e3)) + debug.info(1, + '{}, {}, {}, {}'.format(slew, + load, + total_delay.delay / 1e3, + total_delay.slew / 1e3)) # Delay is only calculated on a single port and replicated for now. for port in self.all_ports: - for mname in self.delay_meas_names+self.power_meas_names: + for mname in self.delay_meas_names + self.power_meas_names: if "power" in mname: port_data[port][mname].append(power.dynamic) elif "delay" in mname and port in self.read_ports: - port_data[port][mname].append(total_delay.delay/1e3) + port_data[port][mname].append(total_delay.delay / 1e3) elif "slew" in mname and port in self.read_ports: - port_data[port][mname].append(total_delay.slew/1e3) + port_data[port][mname].append(total_delay.slew / 1e3) else: - debug.error("Measurement name not recognized: {}".format(mname),1) + debug.error("Measurement name not recognized: {}".format(mname), 1) # Estimate the period as double the delay with margin period_margin = 0.1 - sram_data = { "min_period":(max_delay/1e3)*2*period_margin, - "leakage_power": power.leakage} + sram_data = {"min_period": (max_delay / 1e3) * 2 * period_margin, + "leakage_power": power.leakage} - debug.info(2,"SRAM Data:\n{}".format(sram_data)) - debug.info(2,"Port Data:\n{}".format(port_data)) + debug.info(2, "SRAM Data:\n{}".format(sram_data)) + debug.info(2, "Port Data:\n{}".format(port_data)) - return (sram_data,port_data) + return (sram_data, port_data) def analytical_power(self, slews, loads): """Get the dynamic and leakage power from the SRAM""" # slews unused, only last load is used load = loads[-1] - power = self.sram.analytical_power(self.corner, load) + power = self.sram.analytical_power(self.corner, load) # convert from nW to mW - power.dynamic /= 1e6 + power.dynamic /= 1e6 power.leakage /= 1e6 - debug.info(1,"Dynamic Power: {0} mW".format(power.dynamic)) - debug.info(1,"Leakage Power: {0} mW".format(power.leakage)) + debug.info(1, "Dynamic Power: {0} mW".format(power.dynamic)) + debug.info(1, "Leakage Power: {0} mW".format(power.leakage)) return power def gen_data(self): @@ -1449,7 +1278,7 @@ class delay(simulation): for write_port in self.write_ports: for i in range(self.word_size): - sig_name="{0}{1}_{2} ".format(self.din_name,write_port, i) + sig_name="{0}{1}_{2} ".format(self.din_name, write_port, i) self.stim.gen_pwl(sig_name, self.cycle_times, self.data_values[write_port][i], self.period, self.slew, 0.05) def gen_addr(self): @@ -1460,7 +1289,7 @@ class delay(simulation): for port in self.all_ports: for i in range(self.addr_size): - sig_name = "{0}{1}_{2}".format(self.addr_name,port,i) + sig_name = "{0}{1}_{2}".format(self.addr_name, port, i) self.stim.gen_pwl(sig_name, self.cycle_times, self.addr_values[port][i], self.period, self.slew, 0.05) def gen_control(self): @@ -1471,11 +1300,10 @@ class delay(simulation): if port in self.readwrite_ports: self.stim.gen_pwl("WEB{0}".format(port), self.cycle_times, self.web_values[port], self.period, self.slew, 0.05) - def get_empty_measure_data_dict(self): """Make a dict of lists for each type of delay and power measurement to append results to""" - measure_names = self.delay_meas_names + self.power_meas_names + measure_names = self.delay_meas_names + self.power_meas_names # Create list of dicts. List lengths is # of ports. Each dict maps the measurement names to lists. - measure_data = [{mname:[] for mname in measure_names} for i in self.all_ports] + measure_data = [{mname: [] for mname in measure_names} for i in self.all_ports] return measure_data diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index 8574c4f6..44c6d278 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -8,13 +8,11 @@ import collections import debug import random +import math from .stimuli import * from .charutils import * from globals import OPTS from .simulation import simulation -# from .delay import delay -import graph_util -from sram_factory import factory class functional(simulation): @@ -23,7 +21,7 @@ class functional(simulation): for successful SRAM operation. """ - def __init__(self, sram, spfile, corner): + def __init__(self, sram, spfile, corner, cycles=15): super().__init__(sram, spfile, corner) # Seed the characterizer with a constant seed for unit tests @@ -31,32 +29,37 @@ class functional(simulation): random.seed(12345) if self.write_size: - self.num_wmasks = int(self.word_size / self.write_size) + self.num_wmasks = int(math.ceil(self.word_size / self.write_size)) else: self.num_wmasks = 0 if not self.num_spare_cols: self.num_spare_cols = 0 + self.probe_address, self.probe_data = '0' * self.addr_size, 0 self.set_corner(corner) self.set_spice_constants() self.set_stimulus_variables() # For the debug signal names + self.wordline_row = 0 + self.bitline_column = 0 self.create_signal_names() self.add_graph_exclusions() self.create_graph() self.set_internal_spice_names() + self.q_name, self.qbar_name = self.get_bit_name() + debug.info(2, "q name={}\nqbar name={}".format(self.q_name, self.qbar_name)) # Number of checks can be changed - self.num_cycles = 15 + self.num_cycles = cycles # This is to have ordered keys for random selection self.stored_words = collections.OrderedDict() self.read_check = [] self.read_results = [] def run(self, feasible_period=None): - if feasible_period: #period defaults to tech.py feasible period otherwise. + if feasible_period: # period defaults to tech.py feasible period otherwise. self.period = feasible_period # Generate a random sequence of reads and writes self.create_random_memory_sequence() @@ -226,17 +229,25 @@ class functional(simulation): sp_read_value = "" for bit in range(self.word_size + self.num_spare_cols): value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(), bit, check)) - if value > self.v_high: - sp_read_value = "1" + sp_read_value - elif value < self.v_low: - sp_read_value = "0" + sp_read_value - else: - error ="FAILED: {0}_{1} value {2} at time {3}n does not fall within noise margins <{4} or >{5}.".format(dout_port, - bit, - value, - eo_period, - self.v_low, - self.v_high) + try: + value = float(value) + if value > self.v_high: + sp_read_value = "1" + sp_read_value + elif value < self.v_low: + sp_read_value = "0" + sp_read_value + else: + error ="FAILED: {0}_{1} value {2} at time {3}n does not fall within noise margins <{4} or >{5}.".format(dout_port, + bit, + value, + eo_period, + self.v_low, + self.v_high) + except ValueError: + error ="FAILED: {0}_{1} value {2} at time {3}n is not a float.".format(dout_port, + bit, + value, + eo_period) + return (0, error) self.read_results.append([sp_read_value, dout_port, eo_period, check]) @@ -245,11 +256,12 @@ class functional(simulation): def check_stim_results(self): for i in range(len(self.read_check)): if self.read_check[i][0] != self.read_results[i][0]: - error = "FAILED: {0} value {1} does not match written value {2} read during cycle {3} at time {4}n".format(self.read_results[i][1], - self.read_results[i][0], - self.read_check[i][0], - int((self.read_results[i][2]-self.period)/self.period), - self.read_results[i][2]) + str = "FAILED: {0} value {1} does not match written value {2} read during cycle {3} at time {4}n" + error = str.format(self.read_results[i][1], + self.read_results[i][0], + self.read_check[i][0], + int((self.read_results[i][2] - self.period) / self.period), + self.read_results[i][2]) return(0, error) return(1, "SUCCESS") @@ -311,7 +323,7 @@ class functional(simulation): else: expected_value = self.word_size + self.num_spare_cols for i in range(expected_value - len(new_value)): - new_value = "0" + new_value + new_value = "0" + new_value # print("Binary Conversion: {} to {}".format(value, new_value)) return new_value @@ -344,8 +356,8 @@ class functional(simulation): # Write important signals to stim file self.sf.write("\n\n* Important signals for debug\n") - self.sf.write("* bl: {}\n".format(self.bl_name)) - self.sf.write("* br: {}\n".format(self.br_name)) + self.sf.write("* bl: {}\n".format(self.bl_name.format(port))) + self.sf.write("* br: {}\n".format(self.br_name.format(port))) self.sf.write("* s_en: {}\n".format(self.sen_name)) self.sf.write("* q: {}\n".format(self.q_name)) self.sf.write("* qbar: {}\n".format(self.qbar_name)) @@ -419,50 +431,8 @@ class functional(simulation): self.stim.write_control(self.cycle_times[-1] + self.period) self.sf.close() - - # FIXME: refactor to share with delay.py - def add_graph_exclusions(self): - """Exclude portions of SRAM from timing graph which are not relevant""" - - # other initializations can only be done during analysis when a bit has been selected - # for testing. - self.sram.bank.graph_exclude_precharge() - self.sram.graph_exclude_addr_dff() - self.sram.graph_exclude_data_dff() - self.sram.graph_exclude_ctrl_dffs() - self.sram.bank.bitcell_array.graph_exclude_replica_col_bits() - - # FIXME: refactor to share with delay.py - def create_graph(self): - """Creates timing graph to generate the timing paths for the SRAM output.""" - - self.sram.bank.bitcell_array.init_graph_params() # Removes previous bit exclusions - # Does wordline=0 and column=0 just for debug names - self.sram.bank.bitcell_array.graph_exclude_bits(0, 0) - - # Generate new graph every analysis as edges might change depending on test bit - self.graph = graph_util.timing_graph() - self.sram_spc_name = "X{}".format(self.sram.name) - self.sram.build_graph(self.graph, self.sram_spc_name, self.pins) - - # FIXME: refactor to share with delay.py - def set_internal_spice_names(self): - """Sets important names for characterization such as Sense amp enable and internal bit nets.""" - - # For now, only testing these using first read port. - port = self.read_ports[0] - self.graph.get_all_paths('{}{}'.format("clk", port), - '{}{}_{}'.format(self.dout_name, port, 0).lower()) - - self.sen_name = self.get_sen_name(self.graph.all_paths) - debug.info(2, "s_en name = {}".format(self.sen_name)) - - self.bl_name, self.br_name = self.get_bl_name(self.graph.all_paths, port) - debug.info(2, "bl name={}, br name={}".format(self.bl_name, self.br_name)) - - self.q_name, self.qbar_name = self.get_bit_name() - debug.info(2, "q name={}\nqbar name={}".format(self.q_name, self.qbar_name)) - + + #FIXME: Similar function to delay.py, refactor this def get_bit_name(self): """ Get a bit cell name """ (cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, 0, 0) @@ -473,63 +443,5 @@ class functional(simulation): qbar_name = cell_name + '.' + str(storage_names[1]) return (q_name, qbar_name) - - # FIXME: refactor to share with delay.py - def get_sen_name(self, paths): - """ - Gets the signal name associated with the sense amp enable from input paths. - Only expects a single path to contain the sen signal name. - """ - - sa_mods = factory.get_mods(OPTS.sense_amp) - # Any sense amp instantiated should be identical, any change to that - # will require some identification to determine the mod desired. - debug.check(len(sa_mods) == 1, "Only expected one type of Sense Amp. Cannot perform s_en checks.") - enable_name = sa_mods[0].get_enable_name() - sen_name = self.get_alias_in_path(paths, enable_name, sa_mods[0]) - return sen_name - - # FIXME: refactor to share with delay.py - def get_bl_name(self, paths, port): - """Gets the signal name associated with the bitlines in the bank.""" - - cell_mod = factory.create(module_type=OPTS.bitcell) - cell_bl = cell_mod.get_bl_name(port) - cell_br = cell_mod.get_br_name(port) - - # Only a single path should contain a single s_en name. Anything else is an error. - bl_names = [] - exclude_set = self.get_bl_name_search_exclusions() - for int_net in [cell_bl, cell_br]: - bl_names.append(self.get_alias_in_path(paths, int_net, cell_mod, exclude_set)) - - return bl_names[0], bl_names[1] - def get_bl_name_search_exclusions(self): - """Gets the mods as a set which should be excluded while searching for name.""" - - # Exclude the RBL as it contains bitcells which are not in the main bitcell array - # so it makes the search awkward - return set(factory.get_mods(OPTS.replica_bitline)) - - def get_alias_in_path(self, paths, int_net, mod, exclusion_set=None): - """ - Finds a single alias for the int_net in given paths. - More or less hits cause an error - """ - - net_found = False - for path in paths: - aliases = self.sram.find_aliases(self.sram_spc_name, self.pins, path, int_net, mod, exclusion_set) - if net_found and len(aliases) >= 1: - debug.error('Found multiple paths with {} net.'.format(int_net), 1) - elif len(aliases) > 1: - debug.error('Found multiple {} nets in single path.'.format(int_net), 1) - elif not net_found and len(aliases) == 1: - path_net_name = aliases[0] - net_found = True - if not net_found: - debug.error("Could not find {} net in timing paths.".format(int_net), 1) - - return path_net_name diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py index 064eb2c5..ecb9adb8 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -5,16 +5,13 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import sys,re,shutil -from design import design import debug import math import tech -from .stimuli import * -from .trim_spice import * -from .charutils import * -import utils from globals import OPTS +from sram_factory import factory +import graph_util + class simulation(): @@ -38,11 +35,11 @@ class simulation(): self.write_ports = self.sram.write_ports self.words_per_row = self.sram.words_per_row if self.write_size: - self.num_wmasks = int(self.word_size/self.write_size) + self.num_wmasks = int(math.ceil(self.word_size / self.write_size)) else: self.num_wmasks = 0 - def set_corner(self,corner): + def set_corner(self, corner): """ Set the corner values """ self.corner = corner (self.process, self.vdd_voltage, self.temperature) = corner @@ -50,27 +47,25 @@ class simulation(): def set_spice_constants(self): """ sets feasible timing parameters """ self.period = tech.spice["feasible_period"] - self.slew = tech.spice["rise_time"]*2 - self.load = tech.spice["dff_in_cap"]*4 + self.slew = tech.spice["rise_time"] * 2 + self.load = tech.spice["dff_in_cap"] * 4 self.v_high = self.vdd_voltage - tech.spice["nom_threshold"] - self.v_low = tech.spice["nom_threshold"] + self.v_low = tech.spice["nom_threshold"] self.gnd_voltage = 0 def create_signal_names(self): self.addr_name = "a" self.din_name = "din" self.dout_name = "dout" - self.pins = self.gen_pin_names(port_signal_names=(self.addr_name,self.din_name,self.dout_name), - port_info=(len(self.all_ports),self.write_ports,self.read_ports), + self.pins = self.gen_pin_names(port_signal_names=(self.addr_name, self.din_name, self.dout_name), + port_info=(len(self.all_ports), self.write_ports, self.read_ports), abits=self.addr_size, dbits=self.word_size + self.num_spare_cols) debug.check(len(self.sram.pins) == len(self.pins), "Number of pins generated for characterization \ do not match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(self.sram.pins, - self.pins)) - #This is TODO once multiport control has been finalized. - #self.control_name = "CSB" + self.pins)) def set_stimulus_variables(self): # Clock signals @@ -78,20 +73,20 @@ class simulation(): self.t_current = 0 # control signals: only one cs_b for entire multiported sram, one we_b for each write port - self.csb_values = {port:[] for port in self.all_ports} - self.web_values = {port:[] for port in self.readwrite_ports} + self.csb_values = {port: [] for port in self.all_ports} + self.web_values = {port: [] for port in self.readwrite_ports} # Raw values added as a bit vector - self.addr_value = {port:[] for port in self.all_ports} - self.data_value = {port:[] for port in self.write_ports} - self.wmask_value = {port:[] for port in self.write_ports} - self.spare_wen_value = {port:[] for port in self.write_ports} + self.addr_value = {port: [] for port in self.all_ports} + self.data_value = {port: [] for port in self.write_ports} + self.wmask_value = {port: [] for port in self.write_ports} + self.spare_wen_value = {port: [] for port in self.write_ports} # Three dimensional list to handle each addr and data bits for each port over the number of checks - self.addr_values = {port:[[] for bit in range(self.addr_size)] for port in self.all_ports} - self.data_values = {port:[[] for bit in range(self.word_size + self.num_spare_cols)] for port in self.write_ports} - self.wmask_values = {port:[[] for bit in range(self.num_wmasks)] for port in self.write_ports} - self.spare_wen_values = {port:[[] for bit in range(self.num_spare_cols)] for port in self.write_ports} + self.addr_values = {port: [[] for bit in range(self.addr_size)] for port in self.all_ports} + self.data_values = {port: [[] for bit in range(self.word_size + self.num_spare_cols)] for port in self.write_ports} + self.wmask_values = {port: [[] for bit in range(self.num_wmasks)] for port in self.write_ports} + self.spare_wen_values = {port: [[] for bit in range(self.num_spare_cols)] for port in self.write_ports} # For generating comments in SPICE stimulus self.cycle_comments = [] @@ -99,7 +94,7 @@ class simulation(): def add_control_one_port(self, port, op): """Appends control signals for operation to a given port""" - #Determine values to write to port + # Determine values to write to port web_val = 1 csb_val = 1 if op == "read": @@ -108,7 +103,7 @@ class simulation(): csb_val = 0 web_val = 0 elif op != "noop": - debug.error("Could not add control signals for port {0}. Command {1} not recognized".format(port,op),1) + debug.error("Could not add control signals for port {0}. Command {1} not recognized".format(port, op), 1) # Append the values depending on the type of port self.csb_values[port].append(csb_val) @@ -128,7 +123,7 @@ class simulation(): elif c=="1": self.data_values[port][bit].append(1) else: - debug.error("Non-binary data string",1) + debug.error("Non-binary data string", 1) bit -= 1 def add_address(self, address, port): @@ -141,12 +136,11 @@ class simulation(): if c=="0": self.addr_values[port][bit].append(0) elif c=="1": - self.addr_values[port][bit].append(1) + self.addr_values[port][bit].append(1) else: - debug.error("Non-binary address string",1) + debug.error("Non-binary address string", 1) bit -= 1 - def add_wmask(self, wmask, port): """ Add the array of address values """ debug.check(len(wmask) == self.num_wmasks, "Invalid wmask size.") @@ -190,9 +184,9 @@ class simulation(): self.t_current += self.period self.add_control_one_port(port, "write") - self.add_data(data,port) - self.add_address(address,port) - self.add_wmask(wmask,port) + self.add_data(data, port) + self.add_address(address, port) + self.add_wmask(wmask, port) self.add_spare_wen("1" * self.num_spare_cols, port) #Add noops to all other ports. @@ -220,11 +214,11 @@ class simulation(): try: self.add_data(self.data_value[port][-1], port) except: - self.add_data("0"*(self.word_size + self.num_spare_cols), port) + self.add_data("0" * (self.word_size + self.num_spare_cols), port) try: self.add_wmask(self.wmask_value[port][-1], port) except: - self.add_wmask("0"*self.num_wmasks, port) + self.add_wmask("0" * self.num_wmasks, port) self.add_spare_wen("0" * self.num_spare_cols, port) #Add noops to all other ports. @@ -275,12 +269,12 @@ class simulation(): try: self.add_data(self.data_value[port][-1], port) except: - self.add_data("0"*(self.word_size + self.num_spare_cols), port) + self.add_data("0" * (self.word_size + self.num_spare_cols), port) try: self.add_wmask(self.wmask_value[port][-1], port) except: - self.add_wmask("0"*self.num_wmasks, port) - self.add_spare_wen("0" * self.num_spare_cols, port) + self.add_wmask("0" * self.num_wmasks, port) + self.add_spare_wen("0" * self.num_spare_cols, port) def add_noop_one_port(self, port): """ Add the control values for a noop to a single port. Does not increment the period. """ @@ -289,7 +283,7 @@ class simulation(): try: self.add_address(self.addr_value[port][-1], port) except: - self.add_address("0"*self.addr_size, port) + self.add_address("0" * self.addr_size, port) # If the port is also a readwrite then add # the same value as previous cycle @@ -297,11 +291,11 @@ class simulation(): try: self.add_data(self.data_value[port][-1], port) except: - self.add_data("0"*(self.word_size + self.num_spare_cols), port) + self.add_data("0" * (self.word_size + self.num_spare_cols), port) try: self.add_wmask(self.wmask_value[port][-1], port) except: - self.add_wmask("0"*self.num_wmasks, port) + self.add_wmask("0" * self.num_wmasks, port) self.add_spare_wen("0" * self.num_spare_cols, port) def add_noop_clock_one_port(self, port): @@ -320,23 +314,23 @@ class simulation(): if unselected_port != port: self.add_noop_one_port(unselected_port) - def append_cycle_comment(self, port, comment): """Add comment to list to be printed in stimulus file""" #Clean up time before appending. Make spacing dynamic as well. time = "{0:.2f} ns:".format(self.t_current) - time_spacing = len(time)+6 + time_spacing = len(time) + 6 self.cycle_comments.append("Cycle {0:<6d} Port {1:<6} {2:<{3}}: {4}".format(len(self.cycle_times), port, time, time_spacing, - comment)) + comment)) def gen_cycle_comment(self, op, word, addr, wmask, port, t_current): if op == "noop": - comment = "\tIdle during cycle {0} ({1}ns - {2}ns)".format(int(t_current/self.period), - t_current, - t_current+self.period) + str = "\tIdle during cycle {0} ({1}ns - {2}ns)" + comment = str.format(int(t_current / self.period), + t_current, + t_current + self.period) elif op == "write": comment = "\tWriting {0} to address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word, addr, @@ -345,40 +339,41 @@ class simulation(): t_current, t_current+self.period) elif op == "partial_write": - comment = "\tWriting (partial) {0} to address {1} with mask bit {2} (from port {3}) during cycle {4} ({5}ns - {6}ns)".format(word, - addr, - wmask, - port, - int(t_current / self.period), - t_current, - t_current + self.period) + str = "\tWriting (partial) {0} to address {1} with mask bit {2} (from port {3}) during cycle {4} ({5}ns - {6}ns)" + comment = str.format(word, + addr, + wmask, + port, + int(t_current / self.period), + t_current, + t_current + self.period) else: - comment = "\tReading {0} from address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word, - addr, - port, - int(t_current/self.period), - t_current, - t_current+self.period) - + str = "\tReading {0} from address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)" + comment = str.format(word, + addr, + port, + int(t_current / self.period), + t_current, + t_current + self.period) return comment def gen_pin_names(self, port_signal_names, port_info, abits, dbits): """Creates the pins names of the SRAM based on the no. of ports.""" - #This may seem redundant as the pin names are already defined in the sram. However, it is difficult - #to extract the functionality from the names, so they are recreated. As the order is static, changing - #the order of the pin names will cause issues here. + # This may seem redundant as the pin names are already defined in the sram. However, it is difficult + # to extract the functionality from the names, so they are recreated. As the order is static, changing + # the order of the pin names will cause issues here. pin_names = [] (addr_name, din_name, dout_name) = port_signal_names (total_ports, write_index, read_index) = port_info for write_input in write_index: for i in range(dbits): - pin_names.append("{0}{1}_{2}".format(din_name,write_input, i)) + pin_names.append("{0}{1}_{2}".format(din_name, write_input, i)) for port in range(total_ports): for i in range(abits): - pin_names.append("{0}{1}_{2}".format(addr_name,port,i)) + pin_names.append("{0}{1}_{2}".format(addr_name, port, i)) #Control signals not finalized. for port in range(total_ports): @@ -393,18 +388,159 @@ class simulation(): if self.write_size: for port in write_index: for bit in range(self.num_wmasks): - pin_names.append("WMASK{0}_{1}".format(port,bit)) + pin_names.append("WMASK{0}_{1}".format(port, bit)) if self.num_spare_cols: for port in write_index: for bit in range(self.num_spare_cols): - pin_names.append("SPARE_WEN{0}_{1}".format(port,bit)) + pin_names.append("SPARE_WEN{0}_{1}".format(port, bit)) for read_output in read_index: for i in range(dbits): - pin_names.append("{0}{1}_{2}".format(dout_name,read_output, i)) + pin_names.append("{0}{1}_{2}".format(dout_name, read_output, i)) pin_names.append("{0}".format("vdd")) pin_names.append("{0}".format("gnd")) return pin_names + def add_graph_exclusions(self): + """ + Exclude portions of SRAM from timing graph which are not relevant + """ + + # other initializations can only be done during analysis when a bit has been selected + # for testing. + self.sram.bank.graph_exclude_precharge() + self.sram.graph_exclude_addr_dff() + self.sram.graph_exclude_data_dff() + self.sram.graph_exclude_ctrl_dffs() + self.sram.bank.bitcell_array.graph_exclude_replica_col_bits() + + def set_internal_spice_names(self): + """ + Sets important names for characterization such as Sense amp enable and internal bit nets. + """ + + port = self.read_ports[0] + if not OPTS.use_pex: + self.graph.get_all_paths('{}{}'.format("clk", port), + '{}{}_{}'.format(self.dout_name, port, self.probe_data)) + + sen_with_port = self.get_sen_name(self.graph.all_paths) + if sen_with_port.endswith(str(port)): + self.sen_name = sen_with_port[:-len(str(port))] + else: + self.sen_name = sen_with_port + debug.warning("Error occurred while determining SEN name. Can cause faults in simulation.") + + debug.info(2, "s_en name = {}".format(self.sen_name)) + + 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)) + + if bl_name_port.endswith(str(port) + "_" + str(self.probe_data)): + 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 + else: + 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)): + 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 + else: + self.br_name = br_name_port + debug.warning("Error occurred while determining bitline names. Can cause faults in simulation.") + debug.info(2, "bl name={}, br name={}".format(self.bl_name, self.br_name)) + else: + self.graph.get_all_paths('{}{}'.format("clk", port), + '{}{}_{}'.format(self.dout_name, port, self.probe_data)) + + self.sen_name = self.get_sen_name(self.graph.all_paths) + debug.info(2, "s_en name = {}".format(self.sen_name)) + + self.bl_name = "bl{0}_{1}".format(port, OPTS.word_size - 1) + self.br_name = "br{0}_{1}".format(port, OPTS.word_size - 1) + debug.info(2, "bl name={}, br name={}".format(self.bl_name, self.br_name)) + + def get_sen_name(self, paths, assumed_port=None): + """ + Gets the signal name associated with the sense amp enable from input paths. + Only expects a single path to contain the sen signal name. + """ + + sa_mods = factory.get_mods(OPTS.sense_amp) + # Any sense amp instantiated should be identical, any change to that + # will require some identification to determine the mod desired. + debug.check(len(sa_mods) == 1, "Only expected one type of Sense Amp. Cannot perform s_en checks.") + enable_name = sa_mods[0].get_enable_name() + sen_name = self.get_alias_in_path(paths, enable_name, sa_mods[0]) + if OPTS.use_pex: + sen_name = sen_name.split('.')[-1] + return sen_name + + def create_graph(self): + """ + Creates timing graph to generate the timing paths for the SRAM output. + """ + + self.sram.clear_exclude_bits() # Removes previous bit exclusions + self.sram.graph_exclude_bits(self.wordline_row, self.bitline_column) + + # Generate new graph every analysis as edges might change depending on test bit + self.graph = graph_util.timing_graph() + self.sram_instance_name = "X{}".format(self.sram.name) + self.sram.build_graph(self.graph, self.sram_instance_name, self.pins) + + def get_bl_name_search_exclusions(self): + """ + Gets the mods as a set which should be excluded while searching for name. + """ + + # Exclude the RBL as it contains bitcells which are not in the main bitcell array + # so it makes the search awkward + return set(factory.get_mods(OPTS.replica_bitline)) + + def get_alias_in_path(self, paths, internal_net, mod, exclusion_set=None): + """ + Finds a single alias for the internal_net in given paths. + More or less hits cause an error + """ + net_found = False + for path in paths: + aliases = self.sram.find_aliases(self.sram_instance_name, self.pins, path, internal_net, mod, exclusion_set) + if net_found and len(aliases) >= 1: + debug.error('Found multiple paths with {} net.'.format(internal_net), 1) + elif len(aliases) > 1: + debug.error('Found multiple {} nets in single path.'.format(internal_net), 1) + elif not net_found and len(aliases) == 1: + path_net_name = aliases[0] + net_found = True + if not net_found: + debug.error("Could not find {} net in timing paths.".format(internal_net), 1) + + return path_net_name + + def get_bl_name(self, paths, port): + """ + Gets the signal name associated with the bitlines in the bank. + """ + + cell_mod = factory.create(module_type=OPTS.bitcell) + cell_bl = cell_mod.get_bl_name(port) + cell_br = cell_mod.get_br_name(port) + + # Only a single path should contain a single s_en name. Anything else is an error. + bl_names = [] + exclude_set = self.get_bl_name_search_exclusions() + for int_net in [cell_bl, cell_br]: + bl_names.append(self.get_alias_in_path(paths, int_net, cell_mod, exclude_set)) + if OPTS.use_pex: + for i in range(len(bl_names)): + bl_names[i] = bl_names[i].split('.')[-1] + return bl_names[0], bl_names[1] + + + diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py index 90fd6213..785b2b3b 100644 --- a/compiler/characterizer/stimuli.py +++ b/compiler/characterizer/stimuli.py @@ -15,7 +15,6 @@ import tech import debug import subprocess import os -import sys import numpy as np from globals import OPTS @@ -40,32 +39,26 @@ class stimuli(): debug.info(2, "Not using spice library") self.device_models = tech.spice["fet_models"][self.process] - self.sram_name = "Xsram" - - def inst_sram(self, pins, inst_name): - """ Function to instatiate an SRAM subckt. """ - - self.sf.write("{} ".format(self.sram_name)) - for pin in self.sram_pins: - self.sf.write("{0} ".format(pin)) - self.sf.write("{0}\n".format(inst_name)) - def inst_model(self, pins, model_name): """ Function to instantiate a generic model with a set of pins """ - self.sf.write("X{0} ".format(model_name)) - for pin in pins: - self.sf.write("{0} ".format(pin)) - self.sf.write("{0}\n".format(model_name)) + + if OPTS.use_pex: + self.inst_pex_model(pins, model_name) + else: + self.sf.write("X{0} ".format(model_name)) + for pin in pins: + self.sf.write("{0} ".format(pin)) + self.sf.write("{0}\n".format(model_name)) - def inst_sram_pex(self, pins, model_name): + def inst_pex_model(self, pins, model_name): self.sf.write("X{0} ".format(model_name)) for pin in pins: self.sf.write("{0} ".format(pin)) for bank in range(OPTS.num_banks): row = int(OPTS.num_words / OPTS.words_per_row) - 1 - col = int(OPTS.word_size * OPTS.words_per_row) - 1 - self.sf.write("bitcell_Q_b{0}_r{1}_c{2} ".format(bank,row,col)) - self.sf.write("bitcell_Q_bar_b{0}_r{1}_c{2} ".format(bank,row,col)) + col = int(OPTS.word_size * OPTS.words_per_row) - 1 + self.sf.write("bitcell_Q_b{0}_r{1}_c{2} ".format(bank, row, col)) + self.sf.write("bitcell_Q_bar_b{0}_r{1}_c{2} ".format(bank, row, col)) # can't add all bitcells to top level due to ngspice max port count of 1005 # for row in range(int(OPTS.num_words / OPTS.words_per_row)): # for col in range(int(OPTS.word_size * OPTS.words_per_row)): @@ -76,7 +69,6 @@ class stimuli(): for port in range(OPTS.num_r_ports + OPTS.num_w_ports + OPTS.num_rw_ports): self.sf.write("bl{0}_{1} ".format(port, col)) self.sf.write("br{0}_{1} ".format(port, col)) - self.sf.write("s_en{0} ".format(bank)) self.sf.write("{0}\n".format(model_name)) @@ -94,14 +86,13 @@ class stimuli(): self.tx_length)) self.sf.write(".ENDS test_inv\n") - - def create_buffer(self, buffer_name, size=[1,3], beta=2.5): + def create_buffer(self, buffer_name, size=[1, 3], beta=2.5): """ Generates buffer for top level signals (only for sim purposes). Size is pair for PMOS, NMOS width multiple. """ - self.sf.write(".SUBCKT test_{2} in out {0} {1}\n".format(self.vdd_name, + self.sf.write(".SUBCKT test_{2} in out {0} {1}\n".format(self.vdd_name, self.gnd_name, buffer_name)) self.sf.write("mpinv1 out_inv in {0} {0} {1} w={2}u l={3}u\n".format(self.vdd_name, @@ -122,8 +113,6 @@ class stimuli(): self.tx_length)) self.sf.write(".ENDS test_{0}\n\n".format(buffer_name)) - - def gen_pulse(self, sig_name, v1, v2, offset, period, t_rise, t_fall): """ Generates a periodic signal with 50% duty cycle and slew rates. Period is measured @@ -140,7 +129,6 @@ class stimuli(): 0.5*period-0.5*t_rise-0.5*t_fall, period)) - def gen_pwl(self, sig_name, clk_times, data_values, period, slew, setup): """ Generate a PWL stimulus given a signal name and data values at each period. @@ -149,18 +137,22 @@ class stimuli(): to the initial value. """ # the initial value is not a clock time - debug.check(len(clk_times)==len(data_values),"Clock and data value lengths don't match. {0} clock values, {1} data values for {2}".format(len(clk_times), len(data_values), sig_name)) + str = "Clock and data value lengths don't match. {0} clock values, {1} data values for {2}" + debug.check(len(clk_times)==len(data_values), + str.format(len(clk_times), + len(data_values), + sig_name)) # shift signal times earlier for setup time - times = np.array(clk_times) - setup*period + times = np.array(clk_times) - setup * period values = np.array(data_values) * self.voltage half_slew = 0.5 * slew self.sf.write("* (time, data): {}\n".format(list(zip(clk_times, data_values)))) self.sf.write("V{0} {0} 0 PWL (0n {1}v ".format(sig_name, values[0])) - for i in range(1,len(times)): - self.sf.write("{0}n {1}v {2}n {3}v ".format(times[i]-half_slew, - values[i-1], - times[i]+half_slew, + for i in range(1, len(times)): + self.sf.write("{0}n {1}v {2}n {3}v ".format(times[i] - half_slew, + values[i - 1], + times[i] + half_slew, values[i])) self.sf.write(")\n") @@ -169,9 +161,9 @@ class stimuli(): self.sf.write("V{0} {0} 0 DC {1}\n".format(sig_name, v_val)) def get_inverse_voltage(self, value): - if value > 0.5*self.voltage: + if value > 0.5 * self.voltage: return 0 - elif value <= 0.5*self.voltage: + elif value <= 0.5 * self.voltage: return self.voltage else: debug.error("Invalid value to get an inverse of: {0}".format(value)) @@ -184,7 +176,6 @@ class stimuli(): else: debug.error("Invalid value to get an inverse of: {0}".format(value)) - def gen_meas_delay(self, meas_name, trig_name, targ_name, trig_val, targ_val, trig_dir, targ_dir, trig_td, targ_td): """ Creates the .meas statement for the measurement of delay """ measure_string=".meas tran {0} TRIG v({1}) VAL={2} {3}=1 TD={4}n TARG v({5}) VAL={6} {7}=1 TD={8}n\n\n" @@ -230,7 +221,7 @@ class stimuli(): def gen_meas_value(self, meas_name, dout, t_intital, t_final): measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name, dout, t_intital, t_final) self.sf.write(measure_string) - + def write_control(self, end_time, runlvl=4): """ Write the control cards to run and end the simulation """ @@ -243,10 +234,10 @@ class stimuli(): reltol = 0.005 # 0.5% else: reltol = 0.001 # 0.1% - timestep = 10 #ps, was 5ps but ngspice was complaining the timestep was too small in certain tests. + timestep = 10 # ps, was 5ps but ngspice was complaining the timestep was too small in certain tests. # UIC is needed for ngspice to converge - self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep,end_time)) + self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep, end_time)) self.sf.write(".TEMP {}\n".format(self.temperature)) if OPTS.spice_name == "ngspice": # ngspice sometimes has convergence problems if not using gear method @@ -260,7 +251,7 @@ class stimuli(): # create plots for all signals self.sf.write("* probe is used for hspice/xa, while plot is used in ngspice\n") if OPTS.debug_level>0: - if OPTS.spice_name in ["hspice","xa"]: + if OPTS.spice_name in ["hspice", "xa"]: self.sf.write(".probe V(*)\n") else: self.sf.write(".plot V(*)\n") @@ -271,7 +262,6 @@ class stimuli(): # end the stimulus file self.sf.write(".end\n\n") - def write_include(self, circuit): """Writes include statements, inputs are lists of model files""" @@ -291,13 +281,12 @@ class stimuli(): else: debug.error("Could not find spice model: {0}\nSet SPICE_MODEL_DIR to over-ride path.\n".format(item)) - def write_supply(self): """ Writes supply voltage statements """ gnd_node_name = "0" self.sf.write("V{0} {0} {1} {2}\n".format(self.vdd_name, gnd_node_name, self.voltage)) - #Adding a commented out supply for simulators where gnd and 0 are not global grounds. + # Adding a commented out supply for simulators where gnd and 0 are not global grounds. self.sf.write("\n*Nodes gnd and 0 are the same global ground node in ngspice/hspice/xa. Otherwise, this source may be needed.\n") self.sf.write("*V{0} {0} {1} {2}\n".format(self.gnd_name, gnd_node_name, 0.0)) @@ -306,7 +295,7 @@ class stimuli(): temp_stim = "{0}stim.sp".format(OPTS.openram_temp) import datetime start_time = datetime.datetime.now() - debug.check(OPTS.spice_exe!="","No spice simulator has been found.") + debug.check(OPTS.spice_exe != "", "No spice simulator has been found.") if OPTS.spice_name == "xa": # Output the xa configurations here. FIXME: Move this to write it once. @@ -314,27 +303,32 @@ class stimuli(): xa_cfg.write("set_sim_level -level 7\n") xa_cfg.write("set_powernet_level 7 -node vdd\n") xa_cfg.close() - cmd = "{0} {1} -c {2}xa.cfg -o {2}xa -mt 2".format(OPTS.spice_exe, - temp_stim, - OPTS.openram_temp) + cmd = "{0} {1} -c {2}xa.cfg -o {2}xa -mt {3}".format(OPTS.spice_exe, + temp_stim, + OPTS.openram_temp, + OPTS.num_threads) valid_retcode=0 elif OPTS.spice_name == "hspice": # TODO: Should make multithreading parameter a configuration option - cmd = "{0} -mt 2 -i {1} -o {2}timing".format(OPTS.spice_exe, - temp_stim, - OPTS.openram_temp) + cmd = "{0} -mt {1} -i {2} -o {3}timing".format(OPTS.spice_exe, + OPTS.num_threads, + temp_stim, + OPTS.openram_temp) valid_retcode=0 else: # ngspice 27+ supports threading with "set num_threads=4" in the stimulus file or a .spiceinit # Measurements can't be made with a raw file set in ngspice # -r {2}timing.raw + ng_cfg = open("{}.spiceinit".format(OPTS.openram_temp), "w") + ng_cfg.write("set num_threads={}\n".format(OPTS.num_threads)) + ng_cfg.close() + cmd = "{0} -b -o {2}timing.lis {1}".format(OPTS.spice_exe, temp_stim, OPTS.openram_temp) # for some reason, ngspice-25 returns 1 when it only has acceptable warnings valid_retcode=1 - spice_stdout = open("{0}spice_stdout.log".format(OPTS.openram_temp), 'w') spice_stderr = open("{0}spice_stderr.log".format(OPTS.openram_temp), 'w') @@ -348,7 +342,7 @@ class stimuli(): debug.error("Spice simulation error: " + cmd, -1) else: end_time = datetime.datetime.now() - delta_time = round((end_time-start_time).total_seconds(),1) - debug.info(2,"*** Spice: {} seconds".format(delta_time)) + delta_time = round((end_time - start_time).total_seconds(), 1) + debug.info(2, "*** Spice: {} seconds".format(delta_time)) diff --git a/compiler/custom/and4_dec.py b/compiler/custom/and4_dec.py index 9c68f78b..211e4ce4 100644 --- a/compiler/custom/and4_dec.py +++ b/compiler/custom/and4_dec.py @@ -122,7 +122,7 @@ class and4_dec(design.design): width=pin.width(), height=pin.height()) - for pin_name in ["A", "B", "C"]: + for pin_name in ["A", "B", "C", "D"]: pin = self.nand_inst.get_pin(pin_name) self.add_layout_pin_rect_center(text=pin_name, layer=pin.layer, diff --git a/compiler/custom/dff.py b/compiler/custom/dff.py index c8fdb4b0..cb703707 100644 --- a/compiler/custom/dff.py +++ b/compiler/custom/dff.py @@ -55,12 +55,6 @@ class dff(design.design): transition_prob = 0.5 return transition_prob*(c_load + c_para) - def get_clk_cin(self): - """Return the total capacitance (in relative units) that the clock is loaded by in the dff""" - #This is a handmade cell so the value must be entered in the tech.py file or estimated. - #Calculated in the tech file by summing the widths of all the gates and dividing by the minimum width. - return parameter["dff_clk_cin"] - 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) diff --git a/compiler/custom/s8_bitcell.py b/compiler/custom/s8_bitcell.py index 2905924a..fff3a6b8 100644 --- a/compiler/custom/s8_bitcell.py +++ b/compiler/custom/s8_bitcell.py @@ -27,11 +27,11 @@ class s8_bitcell(bitcell_base.bitcell_base): pin_names = ["bl0", "bl1", "wl0", "wl1", "vpwr", "vgnd"] type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"] else: - pin_names = [props.bitcell.cell_6t.pin.bl, - props.bitcell.cell_6t.pin.br, - props.bitcell.cell_6t.pin.wl, - props.bitcell.cell_6t.pin.vdd, - props.bitcell.cell_6t.pin.gnd] + pin_names = [props.bitcell.cell_s8_6t.pin.bl, + props.bitcell.cell_s8_6t.pin.br, + props.bitcell.cell_s8_6t.pin.wl, + "vpwr", + "vgnd"] type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] storage_nets = ['Q', 'Q_bar'] @@ -61,15 +61,12 @@ class s8_bitcell(bitcell_base.bitcell_base): layer["mem"], "s8sram_cell\x00") - - #debug.check(OPTS.tech_name != "sky130", "sky130 does not yet support single port cells") - def get_all_wl_names(self): """ Creates a list of all wordline pin names """ if props.compare_ports(props.bitcell.split_wl): row_pins = ["wl0", "wl1"] else: - row_pins = [props.bitcell.s8_sp.pin.wl] + row_pins = [props.bitcell.cell_s8_6t.pin.wl] return row_pins def get_all_bitline_names(self): diff --git a/compiler/custom/s8_col_cap_array.py b/compiler/custom/s8_col_cap_array.py new file mode 100644 index 00000000..3fb00a20 --- /dev/null +++ b/compiler/custom/s8_col_cap_array.py @@ -0,0 +1,137 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +import design +from sram_factory import factory +from globals import OPTS +from tech import cell_properties + + +class s8_col_cap_array(design.design): + """ + Generate a dummy row/column for the replica array. + """ + def __init__(self, rows, cols, location, column_offset=0, mirror=0, name=""): + super().__init__(name) + self.rows = rows + self.cols = cols + self.location = location + self.column_offset = column_offset + self.mirror = mirror + self.no_instances = True + self.all_wordline_names = [] + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + """ Create and connect the netlist """ + self.add_modules() + self.add_pins() + self.create_instances() + + def create_layout(self): + + self.place_array("col_cap_r{0}_c{1}", self.mirror) + self.add_layout_pins() + self.add_boundary() + self.DRC_LVS() + + def add_modules(self): + """ Add the modules used in this design """ + if self.location == "top": + self.colend1 = factory.create(module_type="s8_col_end", version = "colend") + self.add_mod(self.colend1) + self.colend2 = factory.create(module_type="s8_col_end", version = "colend_p_cent") + self.add_mod(self.colend2) + elif self.location == "bottom": + self.colend1 = factory.create(module_type="s8_col_end", version = "colenda") + self.add_mod(self.colend1) + self.colend2 = factory.create(module_type="s8_col_end", version = "colenda_p_cent") + self.add_mod(self.colend2) + + self.cell = factory.create(module_type="s8_bitcell", version = "opt1") + + def create_instances(self): + """ Create the module instances used in this design """ + self.cell_inst = {} + self.array_layout = [] + alternate_bitcell = 0 + for col in range((self.cols * 2 )-1): + row_layout = [] + name="rca_{0}".format(col) + # Top/bottom cell are always dummy cells. + # Regular array cells are replica cells (>left_rbl and left_rbl and 0): + + if alternate_bitcell == 0: + row_layout.append(self.rowend1) + self.cell_inst[row]=self.add_inst(name=name, mod=self.rowend1) + self.connect_inst(["wl_0_{}".format(row-1), "vpwr"]) + alternate_bitcell = 1 + + else: + row_layout.append(self.rowend2) + self.cell_inst[row]=self.add_inst(name=name,mod=self.rowend2) + self.connect_inst(["wl_0_{}".format(row-1), "vpwr"]) + alternate_bitcell = 0 + + elif (row == 0): + row_layout.append(self.bottom_corner) + self.cell_inst[row]=self.add_inst(name=name, mod=self.bottom_corner) + self.connect_inst([]) + + elif (row == self.rows - 1): + row_layout.append(self.top_corner) + self.cell_inst[row]=self.add_inst(name=name, mod=self.top_corner) + self.connect_inst([]) + + + self.array_layout.append(row_layout) + + + def get_bitcell_pins(self, row, col): + """ + Creates a list of connections in the bitcell, + indexed by column and row, for instance use in bitcell_array + """ + + pin_name = cell_properties.bitcell.cell_1rw1r.pin + bitcell_pins = ["{0}_{1}".format(pin_name.wl0, row), + "{0}_{1}".format(pin_name.wl1, row), + "gnd"] + + return bitcell_pins + + def place_array(self, name_template, row_offset=0): + self.width = 0 + self.height = 0 + + for inst in self.insts: + self.height += inst.height + if inst.width > self.width: + self.width = inst.width + yoffset = 0.0 + for row in range(0, len(self.array_layout)): + xoffset = 0.0 + + for col in range(0, len(self.array_layout[row])): + inst = self.insts[col + row*len(self.array_layout[row])] + inst.place(offset=[xoffset, yoffset]) + xoffset += inst.width + yoffset += inst.height + + + + def add_pins(self): + for row in range(self.rows - 2): + for port in self.all_ports: + self.add_pin("wl_{}_{}".format(port, row), "OUTPUT") + self.add_pin("vpwr", "POWER") + self.add_pin("vgnd", "GROUND") + + def add_layout_pins(self): + """ Add the layout pins """ + if self.column_offset == 0: + row_list = self.cell.get_all_wl_names() + + for row in range(1, self.rows-1): + if row > 0 and row < self.rows: + for cell_row in row_list: + wl_pin = self.cell_inst[row].get_pin(cell_row) + self.add_layout_pin(text=cell_row + "_0_{0}".format(row), + layer=wl_pin.layer, + offset=wl_pin.ll().scale(0, 1), + width=self.width, + height=wl_pin.height()) + + # Add vdd/gnd via stacks + for row in range(1, self.rows): + inst = self.cell_inst[row] + for pin_name in ["vpwr", "vgnd"]: + for pin in inst.get_pins(pin_name): + self.add_power_pin(name=pin.name, + loc=pin.center(), + start_layer=pin.layer) + diff --git a/compiler/custom/s8_row_end.py b/compiler/custom/s8_row_end.py new file mode 100644 index 00000000..d02ca709 --- /dev/null +++ b/compiler/custom/s8_row_end.py @@ -0,0 +1,38 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 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. +# + +import debug +import design +import utils +from globals import OPTS +from tech import parameter, drc, layer, GDS + +class s8_row_end(design.design): + + + def __init__(self, version, name=""): + super().__init__(name) + pin_names = ["wl", "vpwr"] + type_list = ["OUTPUT", "POWER"] + + if version == "rowend": + self.name = "s8sram16x16_rowend" + elif version == "rowenda": + self.name = "s8sram16x16_rowenda" + else: + debug.error("Invalid type for row_end", -1) + design.design.__init__(self, name=self.name) + (self.width, self.height) = utils.get_libcell_size(self.name, + GDS["unit"], + layer["mem"], + "s8sram16x16_rowend_ce\x00") + self.pin_map = utils.get_libcell_pins(pin_names, self.name, GDS["unit"]) + + + self.add_pin("wl", "OUTPUT") + self.add_pin("vpwr", "POWER") diff --git a/compiler/example_configs/riscv-freepdk45-8kbyte.py b/compiler/example_configs/riscv-freepdk45-8kbyte.py new file mode 100644 index 00000000..b0ebf764 --- /dev/null +++ b/compiler/example_configs/riscv-freepdk45-8kbyte.py @@ -0,0 +1,26 @@ +word_size = 32 +num_words = 2048 +write_size = 8 + +local_array_size = 32 + +num_rw_ports = 1 +num_r_ports = 1 +num_w_ports = 0 + +tech_name = "freepdk45" +nominal_corners_only = True + +route_supplies = False +check_lvsdrc = False +perimeter_pins = False +#netlist_only = True +#analytical_delay = False +output_path = "macros/sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size, + num_words, + write_size, + tech_name) +output_name = "sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size, + num_words, + write_size, + tech_name) diff --git a/compiler/example_configs/riscv-scn4m_subm-16kbyte.py b/compiler/example_configs/riscv-scn4m_subm-16kbyte.py new file mode 100644 index 00000000..befe49fb --- /dev/null +++ b/compiler/example_configs/riscv-scn4m_subm-16kbyte.py @@ -0,0 +1,26 @@ +word_size = 32 +num_words = 4096 +write_size = 8 + +local_array_size = 32 + +num_rw_ports = 1 +num_r_ports = 1 +num_w_ports = 0 + +tech_name = "scn4m_subm" +nominal_corners_only = True + +route_supplies = False +check_lvsdrc = False +perimeter_pins = False +#netlist_only = True +#analytical_delay = False +output_path = "macros/sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size, + num_words, + write_size, + tech_name) +output_name = "sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size, + num_words, + write_size, + tech_name) diff --git a/compiler/example_configs/riscv-scn4m_subm-1kbyte.py b/compiler/example_configs/riscv-scn4m_subm-1kbyte.py new file mode 100644 index 00000000..1ab9f5fe --- /dev/null +++ b/compiler/example_configs/riscv-scn4m_subm-1kbyte.py @@ -0,0 +1,24 @@ +word_size = 32 +num_words = 256 +write_size = 8 + +num_rw_ports = 1 +num_r_ports = 1 +num_w_ports = 0 + +tech_name = "scn4m_subm" +nominal_corners_only = True + +route_supplies = True +check_lvsdrc = True +perimeter_pins = True +#netlist_only = True +#analytical_delay = False +output_path = "macros/sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size, + num_words, + write_size, + tech_name) +output_name = "sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size, + num_words, + write_size, + tech_name) diff --git a/compiler/example_configs/riscv-scn4m_subm-2kbyte.py b/compiler/example_configs/riscv-scn4m_subm-2kbyte.py new file mode 100644 index 00000000..267e31ef --- /dev/null +++ b/compiler/example_configs/riscv-scn4m_subm-2kbyte.py @@ -0,0 +1,24 @@ +word_size = 32 +num_words = 512 +write_size = 8 + +num_rw_ports = 1 +num_r_ports = 1 +num_w_ports = 0 + +tech_name = "scn4m_subm" +nominal_corners_only = True + +route_supplies = True +check_lvsdrc = True +perimeter_pins = True +#netlist_only = True +#analytical_delay = False +output_path = "macros/sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size, + num_words, + write_size, + tech_name) +output_name = "sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size, + num_words, + write_size, + tech_name) diff --git a/compiler/example_configs/riscv-scn4m_subm-32kbyte.py b/compiler/example_configs/riscv-scn4m_subm-32kbyte.py new file mode 100644 index 00000000..98cb8808 --- /dev/null +++ b/compiler/example_configs/riscv-scn4m_subm-32kbyte.py @@ -0,0 +1,26 @@ +word_size = 32 +num_words = 8192 +write_size = 8 + +local_array_size = 32 + +num_rw_ports = 1 +num_r_ports = 1 +num_w_ports = 0 + +tech_name = "scn4m_subm" +nominal_corners_only = True + +route_supplies = False +check_lvsdrc = False +perimeter_pins = False +#netlist_only = True +#analytical_delay = False +output_path = "macros/sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size, + num_words, + write_size, + tech_name) +output_name = "sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size, + num_words, + write_size, + tech_name) diff --git a/compiler/example_configs/riscv-scn4m_subm-4kbyte.py b/compiler/example_configs/riscv-scn4m_subm-4kbyte.py new file mode 100644 index 00000000..87cf4a71 --- /dev/null +++ b/compiler/example_configs/riscv-scn4m_subm-4kbyte.py @@ -0,0 +1,24 @@ +word_size = 32 +num_words = 1024 +write_size = 8 + +num_rw_ports = 1 +num_r_ports = 1 +num_w_ports = 0 + +tech_name = "scn4m_subm" +nominal_corners_only = True + +route_supplies = True +check_lvsdrc = True +perimeter_pins = True +#netlist_only = True +#analytical_delay = False +output_path = "macros/sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size, + num_words, + write_size, + tech_name) +output_name = "sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size, + num_words, + write_size, + tech_name) diff --git a/compiler/example_configs/riscv-scn4m_subm-8kbyte.py b/compiler/example_configs/riscv-scn4m_subm-8kbyte.py new file mode 100644 index 00000000..8b1715ff --- /dev/null +++ b/compiler/example_configs/riscv-scn4m_subm-8kbyte.py @@ -0,0 +1,26 @@ +word_size = 32 +num_words = 2048 +write_size = 8 + +local_array_size = 32 + +num_rw_ports = 1 +num_r_ports = 1 +num_w_ports = 0 + +tech_name = "scn4m_subm" +nominal_corners_only = True + +route_supplies = False +check_lvsdrc = False +perimeter_pins = False +#netlist_only = True +#analytical_delay = False +output_path = "macros/sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size, + num_words, + write_size, + tech_name) +output_name = "sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size, + num_words, + write_size, + tech_name) diff --git a/compiler/example_configs/riscv-sky130-1kbyte.py b/compiler/example_configs/riscv-sky130-1kbyte.py new file mode 100644 index 00000000..c4a298d2 --- /dev/null +++ b/compiler/example_configs/riscv-sky130-1kbyte.py @@ -0,0 +1,26 @@ +word_size = 32 +num_words = 256 +write_size = 8 + +local_array_size = 16 + +num_rw_ports = 1 +num_r_ports = 1 +num_w_ports = 0 + +tech_name = "sky130" +nominal_corners_only = True + +route_supplies = True +check_lvsdrc = True +perimeter_pins = True +#netlist_only = True +#analytical_delay = False +output_path = "macros/sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size, + num_words, + write_size, + tech_name) +output_name = "sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size, + num_words, + write_size, + tech_name) diff --git a/compiler/example_configs/riscv-sky130-2kbyte.py b/compiler/example_configs/riscv-sky130-2kbyte.py new file mode 100644 index 00000000..d50d532e --- /dev/null +++ b/compiler/example_configs/riscv-sky130-2kbyte.py @@ -0,0 +1,26 @@ +word_size = 32 +num_words = 512 +write_size = 8 + +local_array_size = 16 + +num_rw_ports = 1 +num_r_ports = 1 +num_w_ports = 0 + +tech_name = "sky130" +nominal_corners_only = True + +route_supplies = True +check_lvsdrc = True +perimeter_pins = True +#netlist_only = True +#analytical_delay = False +output_path = "macros/sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size, + num_words, + write_size, + tech_name) +output_name = "sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size, + num_words, + write_size, + tech_name) diff --git a/compiler/example_configs/riscv-sky130-4kbyte.py b/compiler/example_configs/riscv-sky130-4kbyte.py new file mode 100644 index 00000000..2d870e88 --- /dev/null +++ b/compiler/example_configs/riscv-sky130-4kbyte.py @@ -0,0 +1,26 @@ +word_size = 32 +num_words = 1024 +write_size = 8 + +local_array_size = 16 + +num_rw_ports = 1 +num_r_ports = 1 +num_w_ports = 0 + +tech_name = "sky130" +nominal_corners_only = True + +route_supplies = True +check_lvsdrc = True +perimeter_pins = True +#netlist_only = True +#analytical_delay = False +output_path = "macros/sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size, + num_words, + write_size, + tech_name) +output_name = "sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size, + num_words, + write_size, + tech_name) diff --git a/compiler/globals.py b/compiler/globals.py index dd4ac177..e1bb8661 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -55,6 +55,11 @@ def parse_args(): action="store_false", help="Disable all LVS/DRC checks", dest="check_lvsdrc"), + optparse.make_option("-j", "--threads", + action="store", + type="int", + help="Specify the number of threads (default: 2)", + dest="num_threads"), optparse.make_option("-v", "--verbose", action="count", diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 12a3b037..a1c6c251 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -8,7 +8,7 @@ import debug import design from sram_factory import factory -from math import log, ceil +from math import log, ceil, floor from tech import drc, layer from vector import vector from globals import OPTS @@ -27,7 +27,7 @@ class bank(design.design): self.sram_config = sram_config sram_config.set_local_config(self) if self.write_size: - self.num_wmasks = int(self.word_size / self.write_size) + self.num_wmasks = int(ceil(self.word_size / self.write_size)) else: self.num_wmasks = 0 @@ -186,16 +186,26 @@ class bank(design.design): self.bitcell_array_right = self.bitcell_array.width # These are the offsets of the main array (excluding dummy and replica rows/cols) - self.main_bitcell_array_top = self.bitcell_array.bitcell_array_inst.uy() + self.main_bitcell_array_top = self.bitcell_array.get_main_array_top() # Just past the dummy column - self.main_bitcell_array_left = self.bitcell_array.bitcell_array_inst.lx() + self.main_bitcell_array_left = self.bitcell_array.get_main_array_left() + # Just past the dummy column + self.main_bitcell_array_right = self.bitcell_array.get_main_array_right() # Just past the dummy row and replica row - self.main_bitcell_array_bottom = self.bitcell_array.bitcell_array_inst.by() - + self.main_bitcell_array_bottom = self.bitcell_array.get_main_array_bottom() + self.compute_instance_port0_offsets() if len(self.all_ports)==2: self.compute_instance_port1_offsets() - + + def get_column_offsets(self): + """ + Return an array of the x offsets of all the regular bits + """ + # Assumes bitcell_array is at 0,0 + offsets = self.bitcell_array.get_column_offsets() + return offsets + def compute_instance_port0_offsets(self): """ Compute the instance offsets for port0 on the left/bottom of the bank. @@ -209,14 +219,14 @@ class bank(design.design): # LOWER RIGHT QUADRANT # Below the bitcell array - self.port_data_offsets[port] = vector(self.main_bitcell_array_left - self.bitcell_array.cell.width, 0) + self.port_data_offsets[port] = vector(0, 0) # UPPER LEFT QUADRANT # To the left of the bitcell array above the predecoders and control logic - x_offset = self.m2_gap + self.port_address.width + x_offset = self.m2_gap + self.port_address[port].width self.port_address_offsets[port] = vector(-x_offset, self.main_bitcell_array_bottom) - self.predecoder_height = self.port_address.predecoder_height + self.port_address_offsets[port].y + self.predecoder_height = self.port_address[port].predecoder_height + self.port_address_offsets[port].y # LOWER LEFT QUADRANT # Place the col decoder left aligned with wordline driver @@ -224,7 +234,7 @@ class bank(design.design): # control logic to allow control signals to easily pass over in M3 # by placing 1 1/4 a cell pitch down because both power connections and inputs/outputs # may be routed in M3 or M4 - x_offset = self.central_bus_width[port] + self.port_address.wordline_driver.width + x_offset = self.central_bus_width[port] + self.port_address[port].wordline_driver_array.width if self.col_addr_size > 0: x_offset += self.column_decoder.width + self.col_addr_bus_width y_offset = 1.25 * self.dff.height + self.column_decoder.height @@ -253,11 +263,11 @@ class bank(design.design): # UPPER LEFT QUADRANT # Above the bitcell array - self.port_data_offsets[port] = vector(self.main_bitcell_array_left, self.bitcell_array_top) + self.port_data_offsets[port] = vector(0, self.bitcell_array_top) # LOWER RIGHT QUADRANT # To the right of the bitcell array - x_offset = self.bitcell_array_right + self.port_address.width + self.m2_gap + x_offset = self.bitcell_array_right + self.port_address[port].width + self.m2_gap self.port_address_offsets[port] = vector(x_offset, self.main_bitcell_array_bottom) @@ -268,7 +278,7 @@ class bank(design.design): # control logic to allow control signals to easily pass over in M3 # by placing 1 1/4 a cell pitch down because both power connections and inputs/outputs # may be routed in M3 or M4 - x_offset = self.bitcell_array_right + self.central_bus_width[port] + self.port_address.wordline_driver.width + x_offset = self.bitcell_array_right + self.central_bus_width[port] + self.port_address[port].wordline_driver_array.width if self.col_addr_size > 0: x_offset += self.column_decoder.width + self.col_addr_bus_width y_offset = self.bitcell_array_top + 1.25 * self.dff.height + self.column_decoder.height @@ -355,56 +365,66 @@ class bank(design.design): def add_modules(self): """ Add all the modules using the class loader """ - + + self.port_address = [] + for port in self.all_ports: + self.port_address.append(factory.create(module_type="port_address", + cols=self.num_cols + self.num_spare_cols, + rows=self.num_rows, + port=port)) + self.add_mod(self.port_address[port]) + + try: + local_array_size = OPTS.local_array_size + except AttributeError: + local_array_size = 0 + + if local_array_size > 0: + # Find the even multiple that satisfies the fanout with equal sized local arrays + total_cols = self.num_cols + self.num_spare_cols + num_lb = floor(total_cols / local_array_size) + final_size = total_cols - num_lb * local_array_size + cols = [local_array_size] * (num_lb - 1) + # Add the odd bits to the last local array + cols.append(local_array_size + final_size) + self.bitcell_array = factory.create(module_type="global_bitcell_array", + cols=cols, + rows=self.num_rows) + else: + self.bitcell_array = factory.create(module_type="replica_bitcell_array", + cols=self.num_cols + self.num_spare_cols, + rows=self.num_rows) + self.add_mod(self.bitcell_array) + self.port_data = [] + self.bit_offsets = self.get_column_offsets() for port in self.all_ports: temp_pre = factory.create(module_type="port_data", sram_config=self.sram_config, - port=port) + port=port, + bit_offsets=self.bit_offsets) self.port_data.append(temp_pre) self.add_mod(self.port_data[port]) - - self.port_address = factory.create(module_type="port_address", - cols=self.num_cols + self.num_spare_cols, - rows=self.num_rows) - self.add_mod(self.port_address) - - self.num_rbl = len(self.all_ports) - - self.bitcell_array = factory.create(module_type="replica_bitcell_array", - cols=self.num_cols + self.num_spare_cols, - rows=self.num_rows, - rbl=[1, 1 if len(self.all_ports)>1 else 0]) - - self.add_mod(self.bitcell_array) - + if(self.num_banks > 1): self.bank_select = factory.create(module_type="bank_select") self.add_mod(self.bank_select) def create_bitcell_array(self): """ Creating Bitcell Array """ - self.bitcell_array_inst=self.add_inst(name="replica_bitcell_array", + self.bitcell_array_inst=self.add_inst(name="bitcell_array", mod=self.bitcell_array) # Arrays are always: # word lines (bottom to top) # bit lines (left to right) # vdd # gnd + temp = self.bitcell_array.get_inouts() - temp = self.bitcell_array.get_all_bitline_names() - - wordline_names = self.bitcell_array.get_all_wordline_names() - - # Rename the RBL WL to the enable name - for port in self.all_ports: - rbl_wl_name = self.bitcell_array.get_rbl_wordline_names(port) - wordline_names = [x.replace(rbl_wl_name[port], "wl_en{0}".format(port)) for x in wordline_names] - # Connect the other RBL WL to gnd - wordline_names = ["gnd" if x.startswith("rbl_wl") else x for x in wordline_names] - # Connect the dummy WL to gnd - wordline_names = ["gnd" if x.startswith("dummy") else x for x in wordline_names] - temp.extend(wordline_names) + temp.append("rbl_wl0") + temp.extend(self.bitcell_array.get_wordline_names()) + if len(self.all_ports) > 1: + temp.append("rbl_wl1") temp.append("vdd") temp.append("gnd") @@ -464,13 +484,15 @@ class bank(design.design): self.port_address_inst = [None] * len(self.all_ports) for port in self.all_ports: self.port_address_inst[port] = self.add_inst(name="port_address{}".format(port), - mod=self.port_address) + mod=self.port_address[port]) temp = [] for bit in range(self.row_addr_size): temp.append("addr{0}_{1}".format(port, bit + self.col_addr_size)) temp.append("wl_en{}".format(port)) - temp.extend(self.bitcell_array.get_wordline_names(port)) + wordline_names = self.bitcell_array.get_wordline_names(port) + temp.extend(wordline_names) + temp.append("rbl_wl{}".format(port)) temp.extend(["vdd", "gnd"]) self.connect_inst(temp) @@ -511,6 +533,9 @@ class bank(design.design): elif self.col_addr_size == 3: self.column_decoder = factory.create(module_type="hierarchical_predecode3x8", height=self.dff.height) + elif self.col_addr_size == 4: + self.column_decoder = factory.create(module_type="hierarchical_predecode4x16", + height=self.dff.height) else: # No error checking before? debug.error("Invalid column decoder?", -1) @@ -658,7 +683,7 @@ class bank(design.design): # 2 pitches on the right for vias/jogs to access the inputs control_bus_offset = vector(-self.m3_pitch * self.num_control_lines[0] - 2 * self.m3_pitch, self.min_y_offset) # The control bus is routed up to two pitches below the bitcell array - control_bus_length = self.main_bitcell_array_bottom - self.min_y_offset - 2 * self.m1_pitch + control_bus_length = self.port_data_inst[0].uy() - self.min_y_offset self.bus_pins[0] = self.create_bus(layer="m2", offset=control_bus_offset, names=self.control_signals[0], @@ -670,7 +695,7 @@ class bank(design.design): # Port 1 if len(self.all_ports)==2: # The other control bus is routed up to two pitches above the bitcell array - control_bus_length = self.max_y_offset - self.main_bitcell_array_top - 2 * self.m1_pitch + control_bus_length = self.max_y_offset - self.port_data_inst[1].by() control_bus_offset = vector(self.bitcell_array_right + 2.5 * self.m3_pitch, self.max_y_offset - control_bus_length) # The bus for the right port is reversed so that the rbl_wl is closest to the array @@ -690,7 +715,6 @@ class bank(design.design): inst1 = self.bitcell_array_inst inst1_bl_name = [x for x in self.bitcell_array.get_bitline_names(port) if "bl" in x] inst1_br_name = [x for x in self.bitcell_array.get_bitline_names(port) if "br" in x] - inst2_bl_name = [] inst2_br_name = [] for col in range(self.num_cols): @@ -709,8 +733,7 @@ class bank(design.design): # Connect the replica bitlines - rbl_bl_names = self.bitcell_array.get_rbl_bitline_names(port)[2 * port: 2 * port + 2] - for (array_name, data_name) in zip(rbl_bl_names, ["rbl_bl", "rbl_br"]): + 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) def route_port_data_out(self, port): @@ -825,33 +848,51 @@ class bank(design.design): self.route_port_address_in(port) if port % 2: - self.route_port_address_right(port) + self.route_port_address_out(port, "right") else: - self.route_port_address_left(port) + self.route_port_address_out(port, "left") - def route_port_address_left(self, port): + def route_port_address_out(self, port, side="left"): """ Connecting Wordline driver output to Bitcell WL connection """ - driver_names = ["wl_{}".format(x) for x in range(self.num_rows)] - for (driver_name, array_name) in zip(driver_names, self.bitcell_array.get_wordline_names(port)): + driver_names = ["wl_{}".format(x) for x in range(self.num_rows)] + ["rbl_wl"] + rbl_wl_name = self.bitcell_array.get_rbl_wordline_names(port)[port] + for (driver_name, array_name) in zip(driver_names, self.bitcell_array.get_wordline_names(port) + [rbl_wl_name]): # The mid guarantees we exit the input cell to the right. driver_wl_pin = self.port_address_inst[port].get_pin(driver_name) - driver_wl_pos = driver_wl_pin.rc() + if side == "left": + driver_wl_pos = driver_wl_pin.rc() + else: + driver_wl_pos = driver_wl_pin.lc() bitcell_wl_pin = self.bitcell_array_inst.get_pin(array_name) - bitcell_wl_pos = bitcell_wl_pin.lc() - mid1 = driver_wl_pos.scale(0, 1) + vector(0.5 * self.port_address_inst[port].rx() + 0.5 * self.bitcell_array_inst.lx(), 0) - mid2 = mid1.scale(1, 0) + bitcell_wl_pos.scale(0.5, 1) - self.add_path(driver_wl_pin.layer, [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) - self.add_via_stack_center(from_layer=driver_wl_pin.layer, - to_layer=bitcell_wl_pin.layer, - offset=bitcell_wl_pos, - directions=("H", "H")) + + if side == "left": + bitcell_wl_pos = bitcell_wl_pin.lc() + port_address_pos = self.port_address_inst[port].rx() + bitcell_array_pos = self.bitcell_array_inst.lx() + else: + bitcell_wl_pos = bitcell_wl_pin.rc() + port_address_pos = self.port_address_inst[port].lx() + bitcell_array_pos = self.bitcell_array_inst.rx() + + 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_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]) + 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 """ - driver_names = ["wl_{}".format(x) for x in range(self.num_rows)] - for (driver_name, array_name) in zip(driver_names, self.bitcell_array.get_wordline_names(port)): + driver_names = ["wl_{}".format(x) for x in range(self.num_rows)] + ["rbl_wl"] + rbl_wl_name = self.bitcell_array.get_rbl_wordline_names(port)[port] + for (driver_name, array_name) in zip(driver_names, self.bitcell_array.get_wordline_names(port) + [rbl_wl_name]): # The mid guarantees we exit the input cell to the right. driver_wl_pin = self.port_address_inst[port].get_pin(driver_name) driver_wl_pos = driver_wl_pin.lc() @@ -859,11 +900,12 @@ class bank(design.design): bitcell_wl_pos = bitcell_wl_pin.rc() mid1 = driver_wl_pos.scale(0, 1) + vector(0.5 * self.port_address_inst[port].lx() + 0.5 * self.bitcell_array_inst.rx(), 0) mid2 = mid1.scale(1, 0) + bitcell_wl_pos.scale(0, 1) - self.add_path(driver_wl_pin.layer, [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) + self.add_path(driver_wl_pin.layer, [driver_wl_pos, mid1, mid2]) self.add_via_stack_center(from_layer=driver_wl_pin.layer, to_layer=bitcell_wl_pin.layer, - offset=bitcell_wl_pos, - directions=("H", "H")) + offset=mid2) + self.add_path(bitcell_wl_pin.layer, [mid2, bitcell_wl_pos]) + def route_column_address_lines(self, port): """ Connecting the select lines of column mux to the address bus """ @@ -901,7 +943,7 @@ class bank(design.design): offset = self.column_decoder_inst[port].lr() + vector(pitch, 0) decode_pins = [self.column_decoder_inst[port].get_pin(x) for x in decode_names] - + sel_names = ["sel_{}".format(x) for x in range(self.num_col_addr_lines)] column_mux_pins = [self.port_data_inst[port].get_pin(x) for x in sel_names] @@ -961,16 +1003,15 @@ class bank(design.design): def route_unused_wordlines(self): """ Connect the unused RBL and dummy wordlines to gnd """ gnd_wl_names = [] - + return # Connect unused RBL WL to gnd # All RBL WL names array_rbl_names = set(self.bitcell_array.get_rbl_wordline_names()) - dummy_rbl_names = set(self.bitcell_array.get_dummy_wordline_names()) # List of used RBL WL names rbl_wl_names = set() for port in self.all_ports: rbl_wl_names.add(self.bitcell_array.get_rbl_wordline_names(port)[port]) - gnd_wl_names = list((array_rbl_names - rbl_wl_names) | dummy_rbl_names) + gnd_wl_names = list((array_rbl_names - rbl_wl_names)) for wl_name in gnd_wl_names: pin = self.bitcell_array_inst.get_pin(wl_name) @@ -1000,10 +1041,6 @@ class bank(design.design): connection.append((self.prefix + "p_en_bar{}".format(port), self.port_data_inst[port].get_pin("p_en_bar"))) - rbl_wl_name = self.bitcell_array.get_rbl_wordline_names(port) - connection.append((self.prefix + "wl_en{}".format(port), - self.bitcell_array_inst.get_pin(rbl_wl_name[port]))) - if port in self.write_ports: connection.append((self.prefix + "w_en{}".format(port), self.port_data_inst[port].get_pin("w_en"))) @@ -1024,66 +1061,48 @@ class bank(design.design): self.add_via_stack_center(from_layer=pin.layer, to_layer="m2", offset=control_pos) - + # clk to wordline_driver control_signal = self.prefix + "wl_en{}".format(port) if port % 2: pin_pos = self.port_address_inst[port].get_pin("wl_en").uc() - mid_pos = pin_pos + vector(0, 2 * self.m2_gap) # to route down to the top of the bus + control_y_offset = self.bus_pins[port][control_signal].by() + mid_pos = vector(pin_pos.x, control_y_offset + self.m1_pitch) else: pin_pos = self.port_address_inst[port].get_pin("wl_en").bc() - mid_pos = pin_pos - vector(0, 2 * self.m2_gap) # to route down to the top of the bus + control_y_offset = self.bus_pins[port][control_signal].uy() + mid_pos = vector(pin_pos.x, control_y_offset - self.m1_pitch) control_x_offset = self.bus_pins[port][control_signal].cx() control_pos = vector(control_x_offset, mid_pos.y) self.add_wire(self.m1_stack, [pin_pos, mid_pos, control_pos]) self.add_via_center(layers=self.m1_stack, offset=control_pos) - def determine_wordline_stage_efforts(self, external_cout, inp_is_rise=True): - """Get the all the stage efforts for each stage in the path within the bank clk_buf to a wordline""" - # Decoder is assumed to have settled before the negative edge of the clock. - # Delay model relies on this assumption - stage_effort_list = [] - wordline_cout = self.bitcell_array.get_wordline_cin() + external_cout - stage_effort_list += self.port_address.wordline_driver.determine_wordline_stage_efforts(wordline_cout, - inp_is_rise) - - return stage_effort_list - - def get_wl_en_cin(self): - """Get the relative capacitance of all the clk connections in the bank""" - # wl_en only used in the wordline driver. - return self.port_address.wordline_driver.get_wl_en_cin() - - def get_w_en_cin(self): - """Get the relative capacitance of all the clk connections in the bank""" - # wl_en only used in the wordline driver. - port = self.write_ports[0] - return self.port_data[port].write_driver.get_w_en_cin() - - def get_clk_bar_cin(self): - """Get the relative capacitance of all the clk_bar connections in the bank""" - # Current bank only uses clock bar (clk_buf_bar) as an enable for the precharge array. - - # Precharges are the all the same in Mulitport, one is picked - port = self.read_ports[0] - return self.port_data[port].precharge_array.get_en_cin() - - def get_sen_cin(self): - """Get the relative capacitance of all the sense amp enable connections in the bank""" - # Current bank only uses sen as an enable for the sense amps. - port = self.read_ports[0] - return self.port_data[port].sense_amp_array.get_en_cin() - def graph_exclude_precharge(self): - """Precharge adds a loop between bitlines, can be excluded to reduce complexity""" + """ + Precharge adds a loop between bitlines, can be excluded to reduce complexity + """ for port in self.read_ports: if self.port_data[port]: self.port_data[port].graph_exclude_precharge() def get_cell_name(self, inst_name, row, col): - """Gets the spice name of the target bitcell.""" + """ + Gets the spice name of the target bitcell. + """ return self.bitcell_array_inst.mod.get_cell_name(inst_name + '.x' + self.bitcell_array_inst.name, row, col) + def graph_exclude_bits(self, targ_row, targ_col): + """ + Excludes bits in column from being added to graph except target + """ + self.bitcell_array.graph_exclude_bits(targ_row, targ_col) + + def clear_exclude_bits(self): + """ + Clears the bit exclusions + """ + self.bitcell_array.clear_exclude_bits() + diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index 9f908bbe..0a334621 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -5,6 +5,7 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # +import debug from bitcell_base_array import bitcell_base_array from s8_corner import s8_corner from tech import drc, spice @@ -20,11 +21,17 @@ class bitcell_array(bitcell_base_array): """ def __init__(self, rows, cols, column_offset=0, name=""): super().__init__(rows=rows, cols=cols, column_offset=column_offset, name=name) + debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols)) + self.add_comment("rows: {0} cols: {1}".format(rows, cols)) + # This will create a default set of bitline/wordline names + self.create_all_bitline_names() + self.create_all_wordline_names() + self.create_netlist() if not OPTS.netlist_only: self.create_layout() - + # We don't offset this because we need to align # the replica bitcell in the control logic # self.offset_all_coordinates() @@ -58,9 +65,6 @@ class bitcell_array(bitcell_base_array): self.add_mod(factory.create(module_type="s8_internal", version = "wlstrap")) self.add_mod(factory.create(module_type="s8_internal", version = "wlstrap_p")) - - - def create_instances(self): """ Create the module instances used in this design """ @@ -115,15 +119,10 @@ class bitcell_array(bitcell_base_array): bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c return bl_wire - def get_wordline_cin(self): - """Get the relative input capacitance from the wordline connections in all the bitcell""" - # A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns - bitcell_wl_cin = self.cell.get_wl_cin() - total_cin = bitcell_wl_cin * self.column_size - return total_cin - - def graph_exclude_bits(self, targ_row, targ_col): - """Excludes bits in column from being added to graph except target""" + def graph_exclude_bits(self, targ_row=None, targ_col=None): + """ + Excludes bits in column from being added to graph except target + """ # Function is not robust with column mux configurations for row in range(self.row_size): for col in range(self.column_size): diff --git a/compiler/modules/bitcell_base_array.py b/compiler/modules/bitcell_base_array.py index 2181df10..ff2ff5a0 100644 --- a/compiler/modules/bitcell_base_array.py +++ b/compiler/modules/bitcell_base_array.py @@ -19,7 +19,6 @@ class bitcell_base_array(design.design): def __init__(self, name, rows, cols, column_offset): super().__init__(name) debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols)) - self.add_comment("rows: {0} cols: {1}".format(rows, cols)) self.column_size = cols self.row_size = rows @@ -34,14 +33,19 @@ class bitcell_base_array(design.design): self.strap = factory.create(module_type="s8_internal", version="wlstrap") self.strap2 = factory.create(module_type="s8_internal", version="wlstrap_p") - self.create_all_bitline_names() - self.create_all_wordline_names() + self.wordline_names = [[] for port in self.all_ports] + self.all_wordline_names = [] + self.bitline_names = [[] for port in self.all_ports] + self.all_bitline_names = [] + self.rbl_bitline_names = [[] for port in self.all_ports] + self.all_rbl_bitline_names = [] + self.rbl_wordline_names = [[] for port in self.all_ports] + self.all_rbl_wordline_names = [] def get_all_bitline_names(self, prefix=""): return [prefix + x for x in self.all_bitline_names] - + def create_all_bitline_names(self): - self.bitline_names = [[] for port in self.all_ports] for col in range(self.column_size): for port in self.all_ports: self.bitline_names[port].extend(["bl_{0}_{1}".format(port, col), @@ -49,47 +53,32 @@ class bitcell_base_array(design.design): # Make a flat list too self.all_bitline_names = [x for sl in zip(*self.bitline_names) for x in sl] - def get_all_wordline_names(self, prefix=""): - return [prefix + x for x in self.all_wordline_names] + # def get_all_wordline_names(self, prefix=""): + # return [prefix + x for x in self.all_wordline_names] - def create_all_wordline_names(self): - self.wordline_names = [[] for port in self.all_ports] - for row in range(self.row_size): + def create_all_wordline_names(self, remove_wordline = 0): + for row in range(self.row_size - remove_wordline): for port in self.all_ports: if not cell_properties.compare_ports(cell_properties.bitcell.split_wl): self.wordline_names[port].append("wl_{0}_{1}".format(port, row)) else: self.wordline_names[port].append("wl0_{0}_{1}".format(port, row)) self.wordline_names[port].append("wl1_{0}_{1}".format(port, row)) + self.all_wordline_names = [x for sl in zip(*self.wordline_names) for x in sl] - - def get_bitline_names(self, port=None): - if port == None: - return self.all_bitline_names - else: - return self.bitline_names[port] - - def get_wordline_names(self, port=None): - if port == None: - return self.all_wordline_names - else: - return self.wordline_names[port] - + def add_pins(self): + for bl_name in self.get_bitline_names(): + self.add_pin(bl_name, "INOUT") + for wl_name in self.get_wordline_names(): + self.add_pin(wl_name, "INPUT") if not cell_properties.compare_ports(cell_properties.bitcell_array.use_custom_cell_arrangement): - for bl_name in self.get_bitline_names(): - self.add_pin(bl_name, "INOUT") - for wl_name in self.get_wordline_names(): - self.add_pin(wl_name, "INPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") else: - for bl_name in self.get_bitline_names(): - self.add_pin(bl_name, "INOUT") - for wl_name in self.get_wordline_names(): - self.add_pin(wl_name, "INPUT") self.add_pin("vpwr", "POWER") self.add_pin("vgnd", "GROUND") + def get_bitcell_pins(self, row, col): """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ @@ -102,34 +91,89 @@ class bitcell_base_array(design.design): return bitcell_pins + def get_rbl_wordline_names(self, port=None): + """ + Return the WL for the given RBL port. + """ + if port == None: + return self.all_rbl_wordline_names + else: + return self.rbl_wordline_names[port] + + def get_rbl_bitline_names(self, port=None): + """ Return all the BL for the given RBL port """ + if port == None: + return self.all_rbl_bitline_names + else: + return self.rbl_bitline_names[port] + + def get_bitline_names(self, port=None): + """ Return the regular bitlines for the given port or all""" + if port == None: + return self.all_bitline_names + else: + return self.bitline_names[port] + + def get_all_bitline_names(self, port=None): + """ Return ALL the bitline names (including rbl) """ + temp = [] + temp.extend(self.get_rbl_bitline_names(0)) + if port == None: + temp.extend(self.all_bitline_names) + else: + temp.extend(self.bitline_names[port]) + if len(self.all_ports) > 1: + temp.extend(self.get_rbl_bitline_names(1)) + return temp + + def get_wordline_names(self, port=None): + """ Return the regular wordline names """ + if port == None: + return self.all_wordline_names + else: + return self.wordline_names[port] + + def get_all_wordline_names(self, port=None): + """ Return all the wordline names """ + temp = [] + temp.extend(self.get_rbl_wordline_names(0)) + if port == None: + temp.extend(self.all_wordline_names) + else: + temp.extend(self.wordline_names[port]) + if len(self.all_ports) > 1: + temp.extend(self.get_rbl_wordline_names(1)) + return temp + def add_layout_pins(self): """ Add the layout pins """ - if not cell_properties.compare_ports(cell_properties.bitcell_array.use_custom_cell_arrangement): - bitline_names = self.cell.get_all_bitline_names() - for col in range(self.column_size): - for port in self.all_ports: - bl_pin = self.cell_inst[0, col].get_pin(bitline_names[2 * port]) - self.add_layout_pin(text="bl_{0}_{1}".format(port, col), - layer=bl_pin.layer, - offset=bl_pin.ll().scale(1, 0), - width=bl_pin.width(), - height=self.height) - br_pin = self.cell_inst[0, col].get_pin(bitline_names[2 * port + 1]) - self.add_layout_pin(text="br_{0}_{1}".format(port, col), - layer=br_pin.layer, - offset=br_pin.ll().scale(1, 0), - width=br_pin.width(), - height=self.height) + bitline_names = self.cell.get_all_bitline_names() + for col in range(self.column_size): + for port in self.all_ports: + bl_pin = self.cell_inst[0, col].get_pin(bitline_names[2 * port]) + self.add_layout_pin(text="bl_{0}_{1}".format(port, col), + layer=bl_pin.layer, + offset=bl_pin.ll().scale(1, 0), + width=bl_pin.width(), + height=self.height) + br_pin = self.cell_inst[0, col].get_pin(bitline_names[2 * port + 1]) + self.add_layout_pin(text="br_{0}_{1}".format(port, col), + layer=br_pin.layer, + offset=br_pin.ll().scale(1, 0), + width=br_pin.width(), + height=self.height) - wl_names = self.cell.get_all_wl_names() - for row in range(self.row_size): - for port in self.all_ports: - wl_pin = self.cell_inst[row, 0].get_pin(wl_names[port]) - self.add_layout_pin(text="wl_{0}_{1}".format(port, row), - layer=wl_pin.layer, - offset=wl_pin.ll().scale(0, 1), - width=self.width, - height=wl_pin.height()) + wl_names = self.cell.get_all_wl_names() + for row in range(self.row_size): + for port in self.all_ports: + wl_pin = self.cell_inst[row, 0].get_pin(wl_names[port]) + self.add_layout_pin(text="wl_{0}_{1}".format(port, row), + layer=wl_pin.layer, + offset=wl_pin.ll().scale(0, 1), + width=self.width, + height=wl_pin.height()) + + if not cell_properties.compare_ports(cell_properties.bitcell_array.use_custom_cell_arrangement): # Copy a vdd/gnd layout pin from every cell for row in range(self.row_size): @@ -138,37 +182,8 @@ class bitcell_base_array(design.design): for pin_name in ["vdd", "gnd"]: self.copy_layout_pin(inst, pin_name) else: - bitline_names = self.cell.get_all_bitline_names() - for col in range(self.column_size): - for port in self.all_ports: - bl_pin = self.cell_inst[0, col].get_pin(bitline_names[2 * port]) - self.add_layout_pin(text="bl0_{0}_{1}".format(port, col), - layer=bl_pin.layer, - offset=bl_pin.ll().scale(1, 0), - width=bl_pin.width(), - height=self.height) - br_pin = self.cell_inst[0, col].get_pin(bitline_names[2 * port + 1]) - self.add_layout_pin(text="bl1_{0}_{1}".format(port, col), - layer=br_pin.layer, - offset=br_pin.ll().scale(1, 0), - width=br_pin.width(), - height=self.height) - wl_names = self.cell.get_all_wl_names() - for row in range(self.row_size): - for port in self.all_ports: - wl0_pin = self.cell_inst[row, 0].get_pin(wl_names[port]) - self.add_layout_pin(text="wl0_{0}_{1}".format(port, row), - layer=wl0_pin.layer, - offset=wl0_pin.ll().scale(0, 1), - width=self.width, - height=wl0_pin.height()) - wl1_pin = self.cell_inst[row, 0].get_pin(wl_names[port]) - self.add_layout_pin(text="wl1_{0}_{1}".format(port, row), - layer=wl1_pin.layer, - offset=wl1_pin.ll().scale(0, 1), - width=self.width, - height=wl1_pin.height()) + # Copy a vdd/gnd layout pin from every cell for row in range(self.row_size): for col in range(self.column_size): @@ -224,3 +239,10 @@ class bitcell_base_array(design.design): else: from tech import custom_cell_placement custom_cell_placement(self) + + def get_column_offsets(self): + """ + Return an array of the x offsets of all the regular bits + """ + offsets = [self.cell_inst[0, col].lx() for col in range(self.column_size)] + return offsets \ No newline at end of file diff --git a/compiler/modules/single_level_column_mux_array.py b/compiler/modules/column_mux_array.py similarity index 92% rename from compiler/modules/single_level_column_mux_array.py rename to compiler/modules/column_mux_array.py index f57bbb20..87e750cf 100644 --- a/compiler/modules/single_level_column_mux_array.py +++ b/compiler/modules/column_mux_array.py @@ -11,22 +11,23 @@ from tech import layer, preferred_directions from vector import vector from sram_factory import factory from globals import OPTS -import logical_effort +from tech import cell_properties -class single_level_column_mux_array(design.design): +class column_mux_array(design.design): """ Dynamically generated column mux array. Array of column mux to read the bitlines through the 6T. """ - def __init__(self, name, columns, word_size, bitcell_bl="bl", bitcell_br="br", column_offset=0): + def __init__(self, name, columns, word_size, offsets=None, bitcell_bl="bl", bitcell_br="br", column_offset=0): super().__init__(name) debug.info(1, "Creating {0}".format(self.name)) self.add_comment("cols: {0} word_size: {1} bl: {2} br: {3}".format(columns, word_size, bitcell_bl, bitcell_br)) self.columns = columns self.word_size = word_size + self.offsets = offsets self.words_per_row = int(self.columns / self.word_size) self.bitcell_bl = bitcell_bl self.bitcell_br = bitcell_br @@ -88,7 +89,7 @@ class single_level_column_mux_array(design.design): self.add_pin("gnd") def add_modules(self): - self.mux = factory.create(module_type="single_level_column_mux", + self.mux = factory.create(module_type="column_mux", bitcell_bl=self.bitcell_bl, bitcell_br=self.bitcell_br) self.add_mod(self.mux) @@ -116,10 +117,12 @@ class single_level_column_mux_array(design.design): "gnd"]) def place_array(self): - from tech import cell_properties + # Default to single spaced columns + if not self.offsets: + self.offsets = [n * self.mux.width for n in range(self.columns)] + # For every column, add a pass gate - for col_num in range(self.columns): - xoffset = col_num * self.mux.width + for col_num, xoffset in enumerate(self.offsets[0:self.columns]): if cell_properties.bitcell.mirror.y and (col_num + self.column_offset) % 2: mirror = "MY" xoffset = xoffset + self.mux.width @@ -163,7 +166,7 @@ class single_level_column_mux_array(design.design): self.add_layout_pin(text="sel_{}".format(j), layer=self.sel_layer, offset=offset, - width=self.mux.width * self.columns) + width=self.mux_inst[-1].rx()) def add_vertical_poly_rail(self): """ Connect the poly to the address rails """ @@ -230,10 +233,3 @@ class single_level_column_mux_array(design.design): to_layer=self.sel_layer, offset=br_out_offset_begin, directions=self.via_directions) - - def get_drain_cin(self): - """Get the relative capacitance of the drain of the NMOS pass TX""" - from tech import parameter - # Bitcell drain load being used to estimate mux NMOS drain load - drain_load = logical_effort.convert_farad_to_relative_c(parameter['bitcell_drain_cap']) - return drain_load diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 706888de..e99d7b89 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -156,96 +156,12 @@ class control_logic(design.design): self.nand2 = factory.create(module_type="pnand2", height=dff_height) self.add_mod(self.nand2) - - # if (self.port_type == "rw") or (self.port_type == "r"): - # from importlib import reload - # self.delay_chain_resized = False - # c = reload(__import__(OPTS.replica_bitline)) - # replica_bitline = getattr(c, OPTS.replica_bitline) - # bitcell_loads = int(math.ceil(self.num_rows * OPTS.rbl_delay_percentage)) - # #Use a model to determine the delays with that heuristic - # if OPTS.use_tech_delay_chain_size: #Use tech parameters if set. - # fanout_list = OPTS.delay_chain_stages*[OPTS.delay_chain_fanout_per_stage] - # debug.info(1, "Using tech parameters to size delay chain: fanout_list={}".format(fanout_list)) - # self.replica_bitline = factory.create(module_type="replica_bitline", - # delay_fanout_list=fanout_list, - # bitcell_loads=bitcell_loads) - # if self.sram != None: #Calculate model value even for specified sizes - # self.set_sen_wl_delays() - - # else: #Otherwise, use a heuristic and/or model based sizing. - # #First use a heuristic - # delay_stages_heuristic, delay_fanout_heuristic = self.get_heuristic_delay_chain_size() - # self.replica_bitline = factory.create(module_type="replica_bitline", - # delay_fanout_list=[delay_fanout_heuristic]*delay_stages_heuristic, - # bitcell_loads=bitcell_loads) - # #Resize if necessary, condition depends on resizing method - # if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_rise_fall_timing_match(): - # #This resizes to match fall and rise delays, can make the delay chain weird sizes. - # stage_list = self.get_dynamic_delay_fanout_list(delay_stages_heuristic, delay_fanout_heuristic) - # self.replica_bitline = factory.create(module_type="replica_bitline", - # delay_fanout_list=stage_list, - # bitcell_loads=bitcell_loads) - - # #This resizes based on total delay. - # # delay_stages, delay_fanout = self.get_dynamic_delay_chain_size(delay_stages_heuristic, delay_fanout_heuristic) - # # self.replica_bitline = factory.create(module_type="replica_bitline", - # # delay_fanout_list=[delay_fanout]*delay_stages, - # # bitcell_loads=bitcell_loads) - - # self.sen_delay_rise,self.sen_delay_fall = self.get_delays_to_sen() #get the new timing - # self.delay_chain_resized = True debug.check(OPTS.delay_chain_stages % 2, "Must use odd number of delay chain stages for inverting delay chain.") self.delay_chain=factory.create(module_type="delay_chain", fanout_list = OPTS.delay_chain_stages * [ OPTS.delay_chain_fanout_per_stage ]) self.add_mod(self.delay_chain) - - def get_heuristic_delay_chain_size(self): - """Use a basic heuristic to determine the size of the delay chain used for the Sense Amp Enable """ - # FIXME: The minimum was 2 fanout, now it will not pass DRC unless it is 3. Why? - delay_fanout = 3 # This can be anything >=3 - # Model poorly captures delay of the column mux. Be pessismistic for column mux - if self.words_per_row >= 2: - delay_stages = 8 - else: - delay_stages = 2 - - # Read ports have a shorter s_en delay. The model is not accurate enough to catch this difference - # on certain sram configs. - if self.port_type == "r": - delay_stages+=2 - - return (delay_stages, delay_fanout) - - def set_sen_wl_delays(self): - """Set delays for wordline and sense amp enable""" - self.wl_delay_rise, self.wl_delay_fall = self.get_delays_to_wl() - self.sen_delay_rise, self.sen_delay_fall = self.get_delays_to_sen() - self.wl_delay = self.wl_delay_rise + self.wl_delay_fall - self.sen_delay = self.sen_delay_rise + self.sen_delay_fall - - def does_sen_rise_fall_timing_match(self): - """Compare the relative rise/fall delays of the sense amp enable and wordline""" - self.set_sen_wl_delays() - # This is not necessarily more reliable than total delay in some cases. - if (self.wl_delay_rise * self.wl_timing_tolerance >= self.sen_delay_rise or - self.wl_delay_fall * self.wl_timing_tolerance >= self.sen_delay_fall): - return False - else: - return True - - def does_sen_total_timing_match(self): - """Compare the total delays of the sense amp enable and wordline""" - self.set_sen_wl_delays() - # The sen delay must always be bigger than than the wl - # delay. This decides how much larger the sen delay must be - # before a re-size is warranted. - if self.wl_delay * self.wl_timing_tolerance >= self.sen_delay: - return False - else: - return True def get_dynamic_delay_chain_size(self, previous_stages, previous_fanout): """Determine the size of the delay chain used for the Sense Amp Enable using path delays""" @@ -333,17 +249,6 @@ class control_logic(design.design): delay_per_stage = fanout + 1 + self.inv_parasitic_delay delay_stages = ceil(required_delay / delay_per_stage) return delay_stages - - def calculate_stage_list(self, total_stages, fanout_rise, fanout_fall): - """ - Produces a list of fanouts which determine the size of the delay chain. - List length is the number of stages. - Assumes the first stage is falling. - """ - stage_list = [] - for i in range(total_stages): - if i % 2 == 0: - stage_list.append() def setup_signal_busses(self): """ Setup bus names, determine the size of the busses etc """ @@ -869,137 +774,6 @@ class control_logic(design.design): offset=pin.ll(), height=pin.height(), width=pin.width()) - def get_delays_to_wl(self): - """Get the delay (in delay units) of the clk to a wordline in the bitcell array""" - debug.check(self.sram.all_mods_except_control_done, "Cannot calculate sense amp enable delay unless all module have been added.") - self.wl_stage_efforts = self.get_wordline_stage_efforts() - clk_to_wl_rise, clk_to_wl_fall = logical_effort.calculate_relative_rise_fall_delays(self.wl_stage_efforts) - total_delay = clk_to_wl_rise + clk_to_wl_fall - debug.info(1, - "Clock to wl delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_wl_rise, - clk_to_wl_fall, - total_delay)) - return clk_to_wl_rise, clk_to_wl_fall - - def get_wordline_stage_efforts(self): - """Follows the gated_clk_bar -> wl_en -> wordline signal for the total path efforts""" - stage_effort_list = [] - - # Initial direction of gated_clk_bar signal for this path - is_clk_bar_rise = True - - # Calculate the load on wl_en within the module and add it to external load - external_cout = self.sram.get_wl_en_cin() - # First stage is the clock buffer - stage_effort_list += self.clk_buf_driver.get_stage_efforts(external_cout, is_clk_bar_rise) - last_stage_is_rise = stage_effort_list[-1].is_rise - - # Then ask the sram for the other path delays (from the bank) - stage_effort_list += self.sram.get_wordline_stage_efforts(last_stage_is_rise) - - return stage_effort_list - - def get_delays_to_sen(self): - """ - Get the delay (in delay units) of the clk to a sense amp enable. - This does not incorporate the delay of the replica bitline. - """ - debug.check(self.sram.all_mods_except_control_done, "Cannot calculate sense amp enable delay unless all module have been added.") - self.sen_stage_efforts = self.get_sa_enable_stage_efforts() - clk_to_sen_rise, clk_to_sen_fall = logical_effort.calculate_relative_rise_fall_delays(self.sen_stage_efforts) - total_delay = clk_to_sen_rise + clk_to_sen_fall - debug.info(1, - "Clock to s_en delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_sen_rise, - clk_to_sen_fall, - total_delay)) - return clk_to_sen_rise, clk_to_sen_fall - - def get_sa_enable_stage_efforts(self): - """Follows the gated_clk_bar signal to the sense amp enable signal adding each stages stage effort to a list""" - stage_effort_list = [] - - # Initial direction of clock signal for this path - last_stage_rise = True - - # First stage, gated_clk_bar -(and2)-> rbl_in. Only for RW ports. - if self.port_type == "rw": - stage1_cout = self.replica_bitline.get_en_cin() - stage_effort_list += self.and2.get_stage_efforts(stage1_cout, last_stage_rise) - last_stage_rise = stage_effort_list[-1].is_rise - - # Replica bitline stage, rbl_in -(rbl)-> pre_s_en - stage2_cout = self.sen_and2.get_cin() - stage_effort_list += self.replica_bitline.determine_sen_stage_efforts(stage2_cout, last_stage_rise) - last_stage_rise = stage_effort_list[-1].is_rise - - # buffer stage, pre_s_en -(buffer)-> s_en - stage3_cout = self.sram.get_sen_cin() - stage_effort_list += self.s_en_driver.get_stage_efforts(stage3_cout, last_stage_rise) - last_stage_rise = stage_effort_list[-1].is_rise - - return stage_effort_list - - def get_wl_sen_delays(self): - """ Gets a list of the stages and delays in order of their path. """ - - if self.sen_stage_efforts == None or self.wl_stage_efforts == None: - debug.error("Model delays not calculated for SRAM.", 1) - wl_delays = logical_effort.calculate_delays(self.wl_stage_efforts) - sen_delays = logical_effort.calculate_delays(self.sen_stage_efforts) - return wl_delays, sen_delays - - def analytical_delay(self, corner, slew, load): - """ Gets the analytical delay from clk input to wl_en output """ - - stage_effort_list = [] - # Calculate the load on clk_buf_bar - # ext_clk_buf_cout = self.sram.get_clk_bar_cin() - - # Operations logic starts on negative edge - last_stage_rise = False - - # First stage(s), clk -(pdriver)-> clk_buf. - # clk_buf_cout = self.replica_bitline.get_en_cin() - clk_buf_cout = 0 - stage_effort_list += self.clk_buf_driver.get_stage_efforts(clk_buf_cout, last_stage_rise) - last_stage_rise = stage_effort_list[-1].is_rise - - # Second stage, clk_buf -(inv)-> clk_bar - clk_bar_cout = self.and2.get_cin() - stage_effort_list += self.and2.get_stage_efforts(clk_bar_cout, last_stage_rise) - last_stage_rise = stage_effort_list[-1].is_rise - - # Third stage clk_bar -(and)-> gated_clk_bar - gated_clk_bar_cin = self.get_gated_clk_bar_cin() - stage_effort_list.append(self.inv.get_stage_effort(gated_clk_bar_cin, last_stage_rise)) - last_stage_rise = stage_effort_list[-1].is_rise - - # Stages from gated_clk_bar -------> wordline - stage_effort_list += self.get_wordline_stage_efforts() - return stage_effort_list - - def get_clk_buf_cin(self): - """ - Get the loads that are connected to the buffered clock. - Includes all the DFFs and some logic. - """ - - # Control logic internal load - int_clk_buf_cap = self.inv.get_cin() + self.ctrl_dff_array.get_clk_cin() + self.and2.get_cin() - - # Control logic external load (in the other parts of the SRAM) - ext_clk_buf_cap = self.sram.get_clk_bar_cin() - - return int_clk_buf_cap + ext_clk_buf_cap - - def get_gated_clk_bar_cin(self): - """Get intermediates net gated_clk_bar's capacitance""" - - total_cin = 0 - total_cin += self.wl_en_driver.get_cin() - if self.port_type == 'rw': - total_cin += self.and2.get_cin() - return total_cin def graph_exclude_dffs(self): """Exclude dffs from graph as they do not represent critical path""" diff --git a/compiler/modules/delay_chain.py b/compiler/modules/delay_chain.py index 246299c1..30126b63 100644 --- a/compiler/modules/delay_chain.py +++ b/compiler/modules/delay_chain.py @@ -210,25 +210,3 @@ class delay_chain(design.design): layer="m2", start=mid_point, end=mid_point.scale(1, 0)) - - def get_cin(self): - """Get the enable input ralative capacitance""" - # Only 1 input to the delay chain which is connected to an inverter. - dc_cin = self.inv.get_cin() - return dc_cin - - def determine_delayed_en_stage_efforts(self, ext_delayed_en_cout, inp_is_rise=True): - """Get the stage efforts from the en to s_en. Does not compute the delay for the bitline load.""" - stage_effort_list = [] - # Add a stage to the list for every stage in delay chain. - # Stages only differ in fanout except the last which has an external cout. - last_stage_is_rise = inp_is_rise - for stage_fanout in self.fanout_list: - stage_cout = self.inv.get_cin() * (stage_fanout + 1) - if len(stage_effort_list) == len(self.fanout_list) - 1: - stage_cout+=ext_delayed_en_cout - stage = self.inv.get_stage_effort(stage_cout, last_stage_is_rise) - stage_effort_list.append(stage) - last_stage_is_rise = stage.is_rise - - return stage_effort_list diff --git a/compiler/modules/dff_array.py b/compiler/modules/dff_array.py index c4f85a6d..cb82443b 100644 --- a/compiler/modules/dff_array.py +++ b/compiler/modules/dff_array.py @@ -155,9 +155,3 @@ class dff_array(design.design): self.add_via_stack_center(from_layer=clk_pin.layer, to_layer="m3", offset=vector(clk_pin.cx(), clk_ypos)) - - def get_clk_cin(self): - """Return the total capacitance (in relative units) that the clock is loaded by in the dff array""" - dff_clk_cin = self.dff.get_clk_cin() - total_cin = dff_clk_cin * self.rows * self.columns - return total_cin diff --git a/compiler/modules/dff_buf.py b/compiler/modules/dff_buf.py index 1657d7a8..1290bf12 100644 --- a/compiler/modules/dff_buf.py +++ b/compiler/modules/dff_buf.py @@ -196,10 +196,3 @@ class dff_buf(design.design): self.add_via_stack_center(from_layer=a2_pin.layer, to_layer="m2", offset=qb_pos) - - def get_clk_cin(self): - """Return the total capacitance (in relative units) that the clock is loaded by in the dff""" - # This is a handmade cell so the value must be entered in the tech.py file or estimated. - # Calculated in the tech file by summing the widths of all the gates and dividing by the minimum width. - # FIXME: Dff changed in a past commit. The parameter need to be updated. - return parameter["dff_clk_cin"] diff --git a/compiler/modules/dff_buf_array.py b/compiler/modules/dff_buf_array.py index 88852d45..d6382973 100644 --- a/compiler/modules/dff_buf_array.py +++ b/compiler/modules/dff_buf_array.py @@ -227,9 +227,3 @@ class dff_buf_array(design.design): # Drop a via to the M3 pin self.add_via_center(layers=self.m2_stack, offset=vector(clk_pin.cx(), clk_ypos)) - - def get_clk_cin(self): - """Return the total capacitance (in relative units) that the clock is loaded by in the dff array""" - dff_clk_cin = self.dff.get_clk_cin() - total_cin = dff_clk_cin * self.rows * self.columns - return total_cin diff --git a/compiler/modules/dff_inv.py b/compiler/modules/dff_inv.py index 033312ef..8f50f9e8 100644 --- a/compiler/modules/dff_inv.py +++ b/compiler/modules/dff_inv.py @@ -150,7 +150,3 @@ class dff_inv(design.design): offset=dout_pin.center()) self.add_via_center(layers=self.m1_stack, offset=dout_pin.center()) - - def get_clk_cin(self): - """Return the total capacitance (in relative units) that the clock is loaded by in the dff""" - return self.dff.get_clk_cin() diff --git a/compiler/modules/dff_inv_array.py b/compiler/modules/dff_inv_array.py index 6b08bcce..1687e043 100644 --- a/compiler/modules/dff_inv_array.py +++ b/compiler/modules/dff_inv_array.py @@ -188,10 +188,4 @@ class dff_inv_array(design.design): height=self.height) # Drop a via to the M3 pin self.add_via_center(layers=self.m2_stack, - offset=vector(clk_pin.cx(),clk_ypos)) - - def get_clk_cin(self): - """Return the total capacitance (in relative units) that the clock is loaded by in the dff array""" - dff_clk_cin = self.dff.get_clk_cin() - total_cin = dff_clk_cin * self.rows * self.columns - return total_cin + offset=vector(clk_pin.cx(),clk_ypos)) diff --git a/compiler/modules/dummy_array.py b/compiler/modules/dummy_array.py index 9004994b..3a7a2ec0 100644 --- a/compiler/modules/dummy_array.py +++ b/compiler/modules/dummy_array.py @@ -5,6 +5,8 @@ # from bitcell_base_array import bitcell_base_array from sram_factory import factory +from tech import GDS,layer,drc,parameter,cell_properties +from tech import cell_properties as props from globals import OPTS @@ -16,6 +18,10 @@ class dummy_array(bitcell_base_array): super().__init__(rows=rows, cols=cols, column_offset=column_offset, name=name) self.mirror = mirror + # This will create a default set of bitline/wordline names + self.create_all_bitline_names() + self.create_all_wordline_names() + self.create_netlist() if not OPTS.netlist_only: self.create_layout() @@ -38,28 +44,96 @@ class dummy_array(bitcell_base_array): def add_modules(self): """ Add the modules used in this design """ - self.dummy_cell = factory.create(module_type="dummy_{}".format(OPTS.bitcell)) + + if not props.compare_ports(props.bitcell_array.use_custom_cell_arrangement): + self.dummy_cell = factory.create(module_type="dummy_{}".format(OPTS.bitcell)) + self.cell = factory.create(module_type="bitcell") + else: + self.dummy_cell = factory.create(module_type="s8_bitcell", version = "opt1") + self.dummy_cell2 = factory.create(module_type="s8_bitcell", version = "opt1a") + self.add_mod(factory.create(module_type="s8_internal", version = "wlstrap")) + self.add_mod(factory.create(module_type="s8_internal", version = "wlstrap_p")) + self.cell = factory.create(module_type="s8_bitcell", version = "opt1") + self.add_mod(self.dummy_cell2) self.add_mod(self.dummy_cell) - - self.cell = factory.create(module_type="bitcell") def create_instances(self): """ Create the module instances used in this design """ self.cell_inst = {} + if not props.compare_ports(props.bitcell_array.use_custom_cell_arrangement): + for col in range(self.column_size): + for row in range(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) + self.connect_inst(self.get_bitcell_pins(row, col)) + else: + from tech import custom_cell_arrangement + custom_cell_arrangement(self) + + def add_pins(self): + # bitline pins are not added because they are floating + for wl_name in self.get_wordline_names(): + self.add_pin(wl_name, "INPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") + + def add_layout_pins(self): + """ Add the layout pins """ + + # Add the bitline metal, but not as pins since they are going to just be floating + # For some reason, LVS has an issue if we don't add this metal + bitline_names = self.cell.get_all_bitline_names() for col in range(self.column_size): + for port in self.all_ports: + bl_pin = self.cell_inst[0, col].get_pin(bitline_names[2 * port]) + self.add_rect(layer=bl_pin.layer, + offset=bl_pin.ll().scale(1, 0), + width=bl_pin.width(), + height=self.height) + br_pin = self.cell_inst[0, col].get_pin(bitline_names[2 * port + 1]) + self.add_rect(layer=br_pin.layer, + offset=br_pin.ll().scale(1, 0), + width=br_pin.width(), + height=self.height) + + wl_names = self.cell.get_all_wl_names() + if not props.compare_ports(props.bitcell.split_wl): for row in range(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) - self.connect_inst(self.get_bitcell_pins(row, col)) + for port in self.all_ports: + wl_pin = self.cell_inst[row, 0].get_pin(wl_names[port]) + self.add_layout_pin(text="wl_{0}_{1}".format(port, row), + layer=wl_pin.layer, + offset=wl_pin.ll().scale(0, 1), + width=self.width, + height=wl_pin.height()) + else: + for row in range(self.row_size): + for port in self.all_ports: + for wl in range(len(wl_names)): + wl_pin = self.cell_inst[row, 0].get_pin("wl{}".format(wl)) + self.add_layout_pin(text="wl{0}_{1}_{2}".format(wl, port, row), + layer=wl_pin.layer, + offset=wl_pin.ll().scale(0, 1), + width=self.width, + height=wl_pin.height()) + + # Copy a vdd/gnd layout pin from every cell + if not props.compare_ports(props.bitcell_array.use_custom_cell_arrangement): + for row in range(self.row_size): + for col in range(self.column_size): + inst = self.cell_inst[row, col] + for pin_name in ["vdd", "gnd"]: + self.copy_layout_pin(inst, pin_name) + else: + for row in range(self.row_size): + for col in range(self.column_size): + inst = self.cell_inst[row, col] + for pin_name in ["vpwr", "vgnd"]: + self.copy_layout_pin(inst, pin_name) + def input_load(self): + # FIXME: This appears to be old code from previous characterization. Needs to be updated. wl_wire = self.gen_wl_wire() return wl_wire.return_input_cap() - - def get_wordline_cin(self): - """Get the relative input capacitance from the wordline connections in all the bitcell""" - # A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns - bitcell_wl_cin = self.cell.get_wl_cin() - total_cin = bitcell_wl_cin * self.column_size - return total_cin diff --git a/compiler/modules/global_bitcell_array.py b/compiler/modules/global_bitcell_array.py index 264a3500..4cb384b6 100644 --- a/compiler/modules/global_bitcell_array.py +++ b/compiler/modules/global_bitcell_array.py @@ -5,34 +5,28 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import design +import bitcell_base_array from globals import OPTS from sram_factory import factory from vector import vector import debug +from numpy import cumsum -class global_bitcell_array(design.design): +class global_bitcell_array(bitcell_base_array.bitcell_base_array): """ Creates a global bitcell array. Rows is an integer number for all local arrays. Cols is a list of the array widths. - add_left_rbl and add_right_ """ - def __init__(self, rows, cols, ports, name=""): + def __init__(self, rows, cols, name=""): # The total of all columns will be the number of columns - super().__init__(name=name) - self.cols = cols - self.rows = rows - self.all_ports = ports + super().__init__(name=name, rows=rows, cols=sum(cols), column_offset=0) + self.column_sizes = cols + self.col_offsets = [0] + list(cumsum(cols)[:-1]) - debug.check(len(ports)<=2, "Only support dual port or less in global bitcell array.") + debug.check(len(self.all_ports)<=2, "Only support dual port or less in global bitcell array.") self.rbl = [1, 1 if len(self.all_ports)>1 else 0] - self.left_rbl = self.rbl[0] - self.right_rbl = self.rbl[1] - - # Just used for pin names - self.cell = factory.create(module_type="bitcell") self.create_netlist() if not OPTS.netlist_only: @@ -48,6 +42,8 @@ class global_bitcell_array(design.design): self.place() + self.route() + self.add_layout_pins() self.add_boundary() @@ -58,21 +54,46 @@ class global_bitcell_array(design.design): """ Add the modules used in this design """ self.local_mods = [] - for i, cols in enumerate(self.cols): - # Always add the left RBLs to the first subarray and the right RBLs to the last subarray + # Special case of a single local array + # so it should contain the left and possibly right RBL + if len(self.column_sizes) == 1: + la = factory.create(module_type="local_bitcell_array", + rows=self.row_size, + cols=self.column_sizes[0], + rbl=self.rbl, + left_rbl=[0], + right_rbl=[1] if len(self.all_ports) > 1 else []) + self.add_mod(la) + self.local_mods.append(la) + return + + for i, cols in enumerate(self.column_sizes): + # Always add the left RBLs to the first subarray if i == 0: - la = factory.create(module_type="local_bitcell_array", rows=self.rows, cols=cols, rbl=self.rbl, add_rbl=[self.left_rbl, 0]) - elif i == len(self.cols) - 1: - la = factory.create(module_type="local_bitcell_array", rows=self.rows, cols=cols, rbl=self.rbl, add_rbl=[0, self.right_rbl]) + la = factory.create(module_type="local_bitcell_array", + rows=self.row_size, + cols=cols, + rbl=self.rbl, + left_rbl=[0]) + # Add the right RBL to the last subarray + elif i == len(self.column_sizes) - 1 and len(self.all_ports) > 1: + la = factory.create(module_type="local_bitcell_array", + rows=self.row_size, + cols=cols, + rbl=self.rbl, + right_rbl=[1]) + # Middle subarrays do not have any RBLs else: - la = factory.create(module_type="local_bitcell_array", rows=self.rows, cols=cols, rbl=self.rbl, add_rbl=[0, 0]) + la = factory.create(module_type="local_bitcell_array", + rows=self.row_size, + cols=cols, + rbl=self.rbl) self.add_mod(la) self.local_mods.append(la) - def add_pins(self): - return + self.add_bitline_pins() self.add_wordline_pins() @@ -80,60 +101,96 @@ class global_bitcell_array(design.design): self.add_pin("gnd", "GROUND") def add_bitline_pins(self): + self.bitline_names = [[] for x in self.all_ports] + self.rbl_bitline_names = [[] for x in self.all_ports] for port in self.all_ports: - self.add_pin_list(self.replica_bitline_names[port], "INOUT") - self.add_pin_list(self.bitline_names, "INOUT") + self.rbl_bitline_names[0].append("rbl_bl_{}_0".format(port)) + for port in self.all_ports: + self.rbl_bitline_names[0].append("rbl_br_{}_0".format(port)) + + for col in range(self.column_size): + for port in self.all_ports: + self.bitline_names[port].append("bl_{0}_{1}".format(port, col)) + for port in self.all_ports: + self.bitline_names[port].append("br_{0}_{1}".format(port, col)) + + if len(self.all_ports) > 1: + for port in self.all_ports: + self.rbl_bitline_names[1].append("rbl_bl_{}_1".format(port)) + for port in self.all_ports: + self.rbl_bitline_names[1].append("rbl_br_{}_1".format(port)) + + # Make a flat list too + self.all_bitline_names = [x for sl in zip(*self.bitline_names) for x in sl] + # Make a flat list too + self.all_rbl_bitline_names = [x for sl in zip(*self.rbl_bitline_names) for x in sl] + + self.add_pin_list(self.rbl_bitline_names[0], "INOUT") + self.add_pin_list(self.all_bitline_names, "INOUT") + if len(self.all_ports) > 1: + self.add_pin_list(self.rbl_bitline_names[1], "INOUT") def add_wordline_pins(self): - # All wordline names for all ports - self.wordline_names = [] - # Wordline names for each port - self.wordline_names_by_port = [[] for x in self.all_ports] - # Replica wordlines by port - self.replica_wordline_names = [[] for x in self.all_ports] + self.rbl_wordline_names = [[] for x in self.all_ports] + + self.wordline_names = [[] for x in self.all_ports] - # Regular array wordline names - self.bitcell_array_wordline_names = self.bitcell_array.get_all_wordline_names() - - self.wordline_names = [] - - # Left port WLs - for port in range(self.left_rbl): - # Make names for all RBLs - wl_names=["rbl_{0}_{1}".format(x, port) for x in self.cell.get_all_wl_names()] - # Keep track of the pin that is the RBL - self.replica_wordline_names[port] = wl_names - self.wordline_names.extend(wl_names) + for bit in self.all_ports: + for port in self.all_ports: + self.rbl_wordline_names[port].append("rbl_wl_{0}_{1}".format(port, bit)) + self.all_rbl_wordline_names = [x for sl in zip(*self.rbl_wordline_names) for x in sl] + # Regular WLs - self.wordline_names.extend(self.bitcell_array_wordline_names) - - # Right port WLs - for port in range(self.left_rbl, self.left_rbl + self.right_rbl): - # Make names for all RBLs - wl_names=["rbl_{0}_{1}".format(x, port) for x in self.cell.get_all_wl_names()] - # Keep track of the pin that is the RBL - self.replica_wordline_names[port] = wl_names - self.wordline_names.extend(wl_names) - - # Array of all port wl names - for port in range(self.left_rbl + self.right_rbl): - wl_names = ["rbl_{0}_{1}".format(x, port) for x in self.cell.get_all_wl_names()] - self.replica_wordline_names[port] = wl_names + for row in range(self.row_size): + for port in self.all_ports: + self.wordline_names[port].append("wl_{0}_{1}".format(port, row)) - self.add_pin_list(self.wordline_names, "INPUT") + self.all_wordline_names = [x for sl in zip(*self.wordline_names) for x in sl] + + for port in range(self.rbl[0]): + self.add_pin(self.rbl_wordline_names[port][port], "INPUT") + self.add_pin_list(self.all_wordline_names, "INPUT") + for port in range(self.rbl[0], self.rbl[0] + self.rbl[1]): + self.add_pin(self.rbl_wordline_names[port][port], "INPUT") - def create_instances(self): """ Create the module instances used in this design """ self.local_insts = [] - for i, mod in enumerate(self.local_mods): - name = "la_{0}".format(i) + for col, mod in zip(self.col_offsets, self.local_mods): + name = "la_{0}".format(col) self.local_insts.append(self.add_inst(name=name, - mod=mod)) - self.connect_inst(mod.pins) + mod=mod)) + + temp = [] + if col == 0: + temp.extend(self.get_rbl_bitline_names(0)) + + port_inouts = [x for x in mod.get_inouts() if x.startswith("bl") or x.startswith("br")] + for pin_name in port_inouts: + # Offset of the last underscore that defines the bit number + bit_index = pin_name.rindex('_') + # col is the bit offset of the local array, + # while col_value is the offset within this array + col_value = int(pin_name[bit_index + 1:]) + # Name of signal without the bit + base_name = pin_name[:bit_index] + # Strip the bit and add the new one + new_name = "{0}_{1}".format(base_name, col + col_value) + temp.append(new_name) + + if len(self.all_ports) > 1 and mod == self.local_mods[-1]: + temp.extend(self.get_rbl_bitline_names(1)) + + for port in self.all_ports: + port_inputs = [x for x in mod.get_inputs() if "wl_{}".format(port) in x] + temp.extend(port_inputs) + + temp.append("vdd") + temp.append("gnd") + self.connect_inst(temp) def place(self): offset = vector(0, 0) @@ -144,5 +201,136 @@ class global_bitcell_array(design.design): self.height = self.local_mods[0].height self.width = self.local_insts[-1].rx() - def add_layout_pins(self): + def route(self): + pass + + def add_layout_pins(self): + + # Regular bitlines + for col, inst in zip(self.col_offsets, self.local_insts): + for port in self.all_ports: + port_inouts = [x for x in inst.mod.get_inouts() if x.startswith("bl_{}".format(port)) or x.startswith("br_{}".format(port))] + for pin_name in port_inouts: + # Offset of the last underscore that defines the bit number + bit_index = pin_name.rindex('_') + # col is the bit offset of the local array, + # while col_value is the offset within this array + col_value = int(pin_name[bit_index + 1:]) + # Name of signal without the bit + base_name = pin_name[:bit_index] + # Strip the bit and add the new one + new_name = "{0}_{1}".format(base_name, col + col_value) + self.copy_layout_pin(inst, pin_name, new_name) + + for wl_name in self.local_mods[0].get_inputs(): + left_pin = self.local_insts[0].get_pin(wl_name) + right_pin = self.local_insts[-1].get_pin(wl_name) + self.add_layout_pin_segment_center(text=wl_name, + layer=left_pin.layer, + start=left_pin.lc(), + end=right_pin.rc()) + + # Replica bitlines + self.copy_layout_pin(self.local_insts[0], "rbl_bl_0_0") + self.copy_layout_pin(self.local_insts[0], "rbl_br_0_0") + + if len(self.all_ports) > 1: + self.copy_layout_pin(self.local_insts[0], "rbl_bl_1_0") + self.copy_layout_pin(self.local_insts[0], "rbl_br_1_0") + self.copy_layout_pin(self.local_insts[-1], "rbl_bl_0_1") + self.copy_layout_pin(self.local_insts[-1], "rbl_br_0_1") + self.copy_layout_pin(self.local_insts[-1], "rbl_bl_1_1") + self.copy_layout_pin(self.local_insts[-1], "rbl_br_1_1") + + for inst in self.insts: + self.copy_power_pins(inst, "vdd") + self.copy_power_pins(inst, "gnd") + + def get_main_array_top(self): + return self.local_insts[0].offset.y + self.local_mods[0].get_main_array_top() + + def get_main_array_bottom(self): + return self.local_insts[0].offset.y + self.local_mods[0].get_main_array_bottom() + + def get_main_array_left(self): + return self.local_insts[0].offset.x + self.local_mods[0].get_main_array_left() + + def get_main_array_right(self): + return self.local_insts[-1].offset.x + self.local_mods[-1].get_main_array_right() + + def get_column_offsets(self): + """ + Return an array of the x offsets of all the regular bits + """ + offsets = [] + for inst in self.local_insts: + offsets.extend(inst.lx() + x for x in inst.mod.get_column_offsets()) + return offsets + + def graph_exclude_bits(self, targ_row, targ_col): + """ + Excludes bits in column from being added to graph except target + """ + + # This must find which local array includes the specified column + # Find the summation of columns that is large and take the one before + for i, col in enumerate(self.col_offsets): + if col > targ_col: + break + else: + i = len(self.local_mods) + + # This is the array with the column + local_array = self.local_mods[i - 1] + # We must also translate the global array column number to the local array column number + local_col = targ_col - self.col_offsets[i - 1] + + for mod in self.local_mods: + if mod == local_array: + mod.graph_exclude_bits(targ_row, local_col) + else: + # Otherwise, we exclude ALL of the rows/columns + mod.graph_exclude_bits() + + def graph_exclude_replica_col_bits(self): + """ + Exclude all but replica in every local array. + """ + + for mod in self.local_mods: + mod.graph_exclude_replica_col_bits() + + def get_cell_name(self, inst_name, row, col): + """Gets the spice name of the target bitcell.""" + + # This must find which local array includes the specified column + # Find the summation of columns that is large and take the one before + for i, local_col in enumerate(self.col_offsets): + if local_col > col: + break + else: + # In this case, we it should be in the last bitcell array + i = len(self.col_offsets) + + # This is the local instance + local_inst = self.local_insts[i - 1] + # This is the array with the column + local_array = self.local_mods[i - 1] + # We must also translate the global array column number to the local array column number + local_col = col - self.col_offsets[i - 1] + + return local_array.get_cell_name(inst_name + '.x' + local_inst.name, row, local_col) + + def clear_exclude_bits(self): + """ + Clears the bit exclusions + """ + for mod in self.local_mods: + mod.clear_exclude_bits() + + def graph_exclude_dffs(self): + """Exclude dffs from graph as they do not represent critical path""" + + self.graph_inst_exclude.add(self.ctrl_dff_inst) + diff --git a/compiler/modules/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index d9c715f7..f022a048 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -24,13 +24,14 @@ class hierarchical_decoder(design.design): self.pre2x4_inst = [] self.pre3x8_inst = [] + self.pre4x16_inst = [] b = factory.create(module_type="bitcell") self.cell_height = b.height self.num_outputs = num_outputs self.num_inputs = math.ceil(math.log(self.num_outputs, 2)) - (self.no_of_pre2x4, self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs) + (self.no_of_pre2x4, self.no_of_pre3x8, self.no_of_pre4x16)=self.determine_predecodes(self.num_inputs) self.create_netlist() if not OPTS.netlist_only: @@ -55,9 +56,9 @@ class hierarchical_decoder(design.design): self.route_decoder_bus() self.route_vdd_gnd() - self.offset_all_coordinates() + self.offset_x_coordinates() - self.width = self.and_inst[0].rx() + self.m1_space + self.width = self.and_inst[0].rx() + 0.5 * self.m1_width self.add_boundary() self.DRC_LVS() @@ -86,25 +87,37 @@ class hierarchical_decoder(design.design): height=self.cell_height) self.add_mod(self.pre3_8) + self.pre4_16 = factory.create(module_type="hierarchical_predecode4x16", + height=self.cell_height) + self.add_mod(self.pre4_16) + def determine_predecodes(self, num_inputs): - """ Determines the number of 2:4 pre-decoder and 3:8 pre-decoder - needed based on the number of inputs """ + """ + Determines the number of 2:4, 3:8 and 4:16 pre-decoders + needed based on the number of inputs + """ if (num_inputs == 2): - return (1, 0) + return (1, 0, 0) elif (num_inputs == 3): - return(0, 1) + return(0, 1, 0) elif (num_inputs == 4): - return(2, 0) + return(2, 0, 0) elif (num_inputs == 5): - return(1, 1) + return(1, 1, 0) elif (num_inputs == 6): - return(3, 0) + return(3, 0, 0) elif (num_inputs == 7): - return(2, 1) + return(2, 1, 0) elif (num_inputs == 8): - return(1, 2) + return(1, 2, 0) elif (num_inputs == 9): - return(0, 3) + return(0, 3, 0) + elif (num_inputs == 10): + return(0, 2, 1) + elif (num_inputs == 11): + return(0, 1, 2) + elif (num_inputs == 12): + return(0, 0, 3) else: debug.error("Invalid number of inputs for hierarchical decoder", -1) @@ -131,12 +144,19 @@ class hierarchical_decoder(design.design): index = index + 1 self.predec_groups.append(lines) + for i in range(self.no_of_pre4x16): + lines = [] + for j in range(16): + lines.append(index) + index = index + 1 + self.predec_groups.append(lines) + def setup_layout_constants(self): """ Calculate the overall dimensions of the hierarchical decoder """ # If we have 4 or fewer rows, the predecoder is the decoder itself if self.num_inputs>=4: - self.total_number_of_predecoder_outputs = 4 * self.no_of_pre2x4 + 8 * self.no_of_pre3x8 + self.total_number_of_predecoder_outputs = 4 * self.no_of_pre2x4 + 8 * self.no_of_pre3x8 + 16 * self.no_of_pre4x16 else: self.total_number_of_predecoder_outputs = 0 debug.error("Not enough rows ({}) for a hierarchical decoder. Non-hierarchical not supported yet.".format(self.num_inputs), @@ -144,17 +164,20 @@ class hierarchical_decoder(design.design): # Calculates height and width of pre-decoder, # FIXME: Update with 4x16 - if self.no_of_pre3x8 > 0 and self.no_of_pre2x4 > 0: - self.predecoder_width = max(self.pre3_8.width, self.pre2_4.width) - elif self.no_of_pre3x8 > 0: - self.predecoder_width = self.pre3_8.width - else: - self.predecoder_width = self.pre2_4.width + self.predecoder_width = 0 + if self.no_of_pre2x4 > 0: + self.predecoder_width = max(self.predecoder_width, self.pre2_4.width) + if self.no_of_pre3x8 > 0: + self.predecoder_width = max(self.predecoder_width, self.pre3_8.width) + if self.no_of_pre4x16 > 0: + self.predecoder_width = max(self.predecoder_width, self.pre4_16.width) # How much space between each predecoder self.predecoder_spacing = 2 * self.and2.height - self.predecoder_height = self.pre2_4.height * self.no_of_pre2x4 + self.pre3_8.height * self.no_of_pre3x8 \ - + (self.no_of_pre2x4 + self.no_of_pre3x8 - 1) * self.predecoder_spacing + self.predecoder_height = self.pre2_4.height * self.no_of_pre2x4 \ + + self.pre3_8.height * self.no_of_pre3x8 \ + + self.pre4_16.height * self.no_of_pre4x16 \ + + (self.no_of_pre2x4 + self.no_of_pre3x8 + self.no_of_pre4x16 - 1) * self.predecoder_spacing # Inputs to cells are on input layer # Outputs from cells are on output layer @@ -192,6 +215,8 @@ class hierarchical_decoder(design.design): min_x = min(min_x, self.pre2x4_inst[0].lx()) if self.no_of_pre3x8 > 0: min_x = min(min_x, self.pre3x8_inst[0].lx()) + if self.no_of_pre4x16 > 0: + min_x = min(min_x, self.pre4x16_inst[0].lx()) input_offset=vector(min_x - self.input_routing_width, 0) input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)] @@ -232,6 +257,20 @@ class hierarchical_decoder(design.design): self.route_input_bus(decoder_offset, input_offset) + for pre_num in range(self.no_of_pre4x16): + for i in range(4): + index = pre_num * 4 + i + self.no_of_pre3x8 * 3 + self.no_of_pre2x4 * 2 + + input_pos = self.input_bus["addr_{}".format(index)].center() + + in_name = "in_{}".format(i) + decoder_pin = self.pre4x16_inst[pre_num].get_pin(in_name) + + decoder_offset = decoder_pin.center() + input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1) + + self.route_input_bus(decoder_offset, input_offset) + def route_input_bus(self, input_offset, output_offset): """ Route a vertical M2 coordinate to another @@ -267,6 +306,9 @@ class hierarchical_decoder(design.design): for i in range(self.no_of_pre3x8): self.create_pre3x8(i) + for i in range(self.no_of_pre4x16): + self.create_pre4x16(i) + def create_pre2x4(self, num): """ Add a 2x4 predecoder to the left of the origin """ @@ -305,6 +347,24 @@ class hierarchical_decoder(design.design): mod=self.pre3_8)) self.connect_inst(pins) + def create_pre4x16(self, num): + """ Add 4x16 predecoder to the left of the origin and above any 3x8 decoders """ + # If we had 2x4 predecodes, those are used as the lower + # decode output bits + in_index_offset = num * 4 + self.no_of_pre3x8 * 3 + self.no_of_pre2x4 * 2 + out_index_offset = num * 16 + self.no_of_pre3x8 * 8 + self.no_of_pre2x4 * 4 + + pins = [] + for input_index in range(4): + pins.append("addr_{0}".format(input_index + in_index_offset)) + for output_index in range(16): + pins.append("out_{0}".format(output_index + out_index_offset)) + pins.extend(["vdd", "gnd"]) + + self.pre4x16_inst.append(self.add_inst(name="pre4x16_{0}".format(num), + mod=self.pre4_16)) + self.connect_inst(pins) + def place_pre_decoder(self): """ Creates pre-decoder and places labels input address [A] """ @@ -314,11 +374,16 @@ class hierarchical_decoder(design.design): for i in range(self.no_of_pre3x8): self.place_pre3x8(i) + for i in range(self.no_of_pre4x16): + self.place_pre4x16(i) + self.predecode_height = 0 if self.no_of_pre2x4 > 0: self.predecode_height = self.pre2x4_inst[-1].uy() if self.no_of_pre3x8 > 0: self.predecode_height = self.pre3x8_inst[-1].uy() + if self.no_of_pre4x16 > 0: + self.predecode_height = self.pre4x16_inst[-1].uy() def place_pre2x4(self, num): """ Place 2x4 predecoder to the left of the origin """ @@ -333,6 +398,14 @@ class hierarchical_decoder(design.design): offset = vector(-self.pre3_8.width, height) self.pre3x8_inst[num].place(offset) + def place_pre4x16(self, num): + """ Place 3x8 predecoder to the left of the origin and above any 2x4 decoders """ + height = self.no_of_pre2x4 * (self.pre2_4.height + self.predecoder_spacing) \ + + self.no_of_pre3x8 * (self.pre3_8.height + self.predecoder_spacing) \ + + num * (self.pre4_16.height + self.predecoder_spacing) + offset = vector(-self.pre4_16.width, height) + self.pre4x16_inst[num].place(offset) + def create_row_decoder(self): """ Create the row-decoder by placing AND2/AND3 and Inverters and add the primary decoder output pins. """ @@ -468,7 +541,17 @@ class hierarchical_decoder(design.design): x_offset = self.pre3x8_inst[pre_num].rx() + self.output_layer_pitch y_offset = self.pre3x8_inst[pre_num].by() + i * self.cell_height self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset) - + + # FIXME: convert to connect_bus + for pre_num in range(self.no_of_pre4x16): + for i in range(16): + predecode_name = "predecode_{}".format(pre_num * 16 + i + self.no_of_pre3x8 * 8 + self.no_of_pre2x4 * 4) + out_name = "out_{}".format(i) + pin = self.pre4x16_inst[pre_num].get_pin(out_name) + x_offset = self.pre4x16_inst[pre_num].rx() + self.output_layer_pitch + y_offset = self.pre4x16_inst[pre_num].by() + i * self.cell_height + self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset) + def route_bus_to_decoder(self): """ Use the self.predec_groups to determine the connections to the decoder AND gates. @@ -559,7 +642,7 @@ class hierarchical_decoder(design.design): start_layer=supply_pin.layer) # Copy the pins from the predecoders - for pre in self.pre2x4_inst + self.pre3x8_inst: + for pre in self.pre2x4_inst + self.pre3x8_inst + self.pre4x16_inst: for pin_name in ["vdd", "gnd"]: self.copy_layout_pin(pre, pin_name) @@ -609,11 +692,3 @@ class hierarchical_decoder(design.design): to_layer=self.output_layer, offset=rail_pos, directions=self.bus_directions) - - def input_load(self): - if self.determine_predecodes(self.num_inputs)[1]==0: - pre = self.pre2_4 - else: - pre = self.pre3_8 - return pre.input_load() - diff --git a/compiler/modules/hierarchical_predecode.py b/compiler/modules/hierarchical_predecode.py index eefa7563..f5bff640 100644 --- a/compiler/modules/hierarchical_predecode.py +++ b/compiler/modules/hierarchical_predecode.py @@ -45,7 +45,7 @@ class hierarchical_predecode(design.design): def add_modules(self): """ Add the INV and AND gate modules """ - debug.check(self.number_of_inputs < 4, + debug.check(self.number_of_inputs <= 4, "Invalid number of predecode inputs: {}".format(self.number_of_inputs)) if self.column_decoder: @@ -204,6 +204,7 @@ class hierarchical_predecode(design.design): pin = top_and_gate.get_pin("D") else: debug.error("Too many inputs for predecoder.", -1) + y_offset = pin.cy() in_pin = "in_{}".format(num) a_pin = "A_{}".format(num) @@ -288,10 +289,14 @@ class hierarchical_predecode(design.design): if self.number_of_inputs == 2: gate_lst = ["A", "B"] - else: + elif self.number_of_inputs == 3: gate_lst = ["A", "B", "C"] + elif self.number_of_inputs == 4: + gate_lst = ["A", "B", "C", "D"] + else: + debug.error("Invalid number of nand inputs for decode", -1) - # this will connect pins A,B or A,B,C + # this will connect pins A,B or A,B,C or A,B,C,D for rail_pin, gate_pin in zip(index_lst, gate_lst): pin = self.and_inst[k].get_pin(gate_pin) pin_pos = pin.center() diff --git a/compiler/modules/hierarchical_predecode4x16.py b/compiler/modules/hierarchical_predecode4x16.py index 3b423fde..93dbc4ea 100644 --- a/compiler/modules/hierarchical_predecode4x16.py +++ b/compiler/modules/hierarchical_predecode4x16.py @@ -32,14 +32,14 @@ class hierarchical_predecode4x16(hierarchical_predecode): ["in_0", "inbar_1", "in_2", "inbar_3", "out_5", "vdd", "gnd"], ["inbar_0", "in_1", "in_2", "inbar_3", "out_6", "vdd", "gnd"], ["in_0", "in_1", "in_2", "inbar_3", "out_7", "vdd", "gnd"], - ["inbar_0", "inbar_1", "inbar_2", "in_3", "out_0", "vdd", "gnd"], - ["in_0", "inbar_1", "inbar_2", "in_3", "out_1", "vdd", "gnd"], - ["inbar_0", "in_1", "inbar_2", "in_3", "out_2", "vdd", "gnd"], - ["in_0", "in_1", "inbar_2", "in_3", "out_3", "vdd", "gnd"], - ["inbar_0", "inbar_1", "in_2", "in_3", "out_4", "vdd", "gnd"], - ["in_0", "inbar_1", "in_2", "in_3", "out_5", "vdd", "gnd"], - ["inbar_0", "in_1", "in_2", "in_3", "out_6", "vdd", "gnd"], - ["in_0", "in_1", "in_2", "in_3", "out_7", "vdd", "gnd"] ] + ["inbar_0", "inbar_1", "inbar_2", "in_3", "out_8", "vdd", "gnd"], + ["in_0", "inbar_1", "inbar_2", "in_3", "out_9", "vdd", "gnd"], + ["inbar_0", "in_1", "inbar_2", "in_3", "out_10", "vdd", "gnd"], + ["in_0", "in_1", "inbar_2", "in_3", "out_11", "vdd", "gnd"], + ["inbar_0", "inbar_1", "in_2", "in_3", "out_12", "vdd", "gnd"], + ["in_0", "inbar_1", "in_2", "in_3", "out_13", "vdd", "gnd"], + ["inbar_0", "in_1", "in_2", "in_3", "out_14", "vdd", "gnd"], + ["in_0", "in_1", "in_2", "in_3", "out_15", "vdd", "gnd"] ] self.create_and_array(connections) diff --git a/compiler/modules/local_bitcell_array.py b/compiler/modules/local_bitcell_array.py index 7a917390..c07b4f1b 100644 --- a/compiler/modules/local_bitcell_array.py +++ b/compiler/modules/local_bitcell_array.py @@ -5,30 +5,34 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import design +import bitcell_base_array from globals import OPTS from sram_factory import factory from vector import vector import debug -class local_bitcell_array(design.design): + +class local_bitcell_array(bitcell_base_array.bitcell_base_array): """ A local bitcell array is a bitcell array with a wordline driver. This can either be a single aray on its own if there is no hierarchical WL or it can be combined into a larger array with hierarchical WL. """ - def __init__(self, rows, cols, rbl, add_rbl=None, name=""): - super().__init__(name=name) - debug.info(2, "create local array of size {} rows x {} cols words".format(rows, cols)) + def __init__(self, rows, cols, rbl, left_rbl=[], right_rbl=[], name=""): + super().__init__(name=name, rows=rows, cols=cols, column_offset=0) + debug.info(2, "Creating {0} {1}x{2} rbl: {3} left_rbl: {4} right_rbl: {5}".format(name, + rows, + cols, + rbl, + left_rbl, + right_rbl)) self.rows = rows self.cols = cols self.rbl = rbl - if add_rbl == None: - self.add_rbl = rbl - else: - self.add_rbl = add_rbl - + self.left_rbl = left_rbl + self.right_rbl = right_rbl + debug.check(len(self.all_ports) < 3, "Local bitcell array only supports dual port or less.") self.create_netlist() @@ -38,7 +42,7 @@ class local_bitcell_array(design.design): # We don't offset this because we need to align # the replica bitcell in the control logic # self.offset_all_coordinates() - + def create_netlist(self): """ Create and connect the netlist """ self.add_modules() @@ -66,53 +70,51 @@ class local_bitcell_array(design.design): cols=self.cols, rows=self.rows, rbl=self.rbl, - add_rbl=self.add_rbl) + left_rbl=self.left_rbl, + right_rbl=self.right_rbl) self.add_mod(self.bitcell_array) self.wl_array = factory.create(module_type="wordline_buffer_array", rows=self.rows + 1, cols=self.cols) self.add_mod(self.wl_array) - + def add_pins(self): - - # Inputs to the wordline driver (by port) - self.wordline_names = [] # Outputs from the wordline driver (by port) self.driver_wordline_outputs = [] # Inputs to the bitcell array (by port) self.array_wordline_inputs = [] - for port in self.all_ports: - wordline_inputs = [] - if port == 0: - wordline_inputs += [self.bitcell_array.get_rbl_wordline_names(0)[0]] - wordline_inputs += self.bitcell_array.get_wordline_names(port) - if port == 1: - wordline_inputs += [self.bitcell_array.get_rbl_wordline_names(1)[1]] - self.wordline_names.append(wordline_inputs) - self.driver_wordline_outputs.append([x + "i" for x in self.wordline_names[-1]]) - - self.gnd_wl_names = [] - - # Connect unused RBL WL to gnd - array_rbl_names = set([x for x in self.bitcell_array.get_all_wordline_names() if x.startswith("rbl")]) - dummy_rbl_names = set([x for x in self.bitcell_array.get_all_wordline_names() if x.startswith("dummy")]) - rbl_wl_names = set([x for rbl_port_names in self.wordline_names for x in rbl_port_names if x.startswith("rbl")]) - self.gnd_wl_names = list((array_rbl_names - rbl_wl_names) | dummy_rbl_names) + self.wordline_names = self.bitcell_array.wordline_names + self.all_wordline_names = self.bitcell_array.all_wordline_names - self.all_array_wordline_inputs = [x + "i" if x not in self.gnd_wl_names else "gnd" for x in self.bitcell_array.get_all_wordline_names()] - self.bitline_names = self.bitcell_array.bitline_names - self.all_array_bitline_names = self.bitcell_array.get_all_bitline_names() + self.all_bitline_names = self.bitcell_array.all_bitline_names + + self.rbl_wordline_names = self.bitcell_array.rbl_wordline_names + self.all_rbl_wordline_names = self.bitcell_array.all_rbl_wordline_names + + self.rbl_bitline_names = self.bitcell_array.rbl_bitline_names + self.all_rbl_bitline_names = self.bitcell_array.all_rbl_bitline_names + + self.all_array_wordline_inputs = [x + "i" for x in self.bitcell_array.get_all_wordline_names()] + # Arrays are always: # bit lines (left to right) # word lines (bottom to top) # vdd # gnd - self.add_pin_list([x for x in self.all_array_bitline_names if not x.startswith("dummy")], "INOUT") + for port in self.left_rbl: + self.add_pin_list(self.rbl_bitline_names[port], "INOUT") + self.add_pin_list(self.all_bitline_names, "INOUT") + for port in self.right_rbl: + self.add_pin_list(self.rbl_bitline_names[port], "INOUT") + for port in range(self.rbl[0]): + self.add_pin(self.rbl_wordline_names[port][port], "INPUT") for port in self.all_ports: self.add_pin_list(self.wordline_names[port], "INPUT") + for port in range(self.rbl[0], self.rbl[0] + self.rbl[1]): + self.add_pin(self.rbl_wordline_names[port][port], "INPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") @@ -120,15 +122,41 @@ class local_bitcell_array(design.design): """ Create the module instances used in this design """ self.wl_insts = [] + self.driver_wordline_outputs = [] for port in self.all_ports: - self.wl_insts.append(self.add_inst(name="wl_driver", + self.wl_insts.append(self.add_inst(name="wl_driver{}".format(port), mod=self.wl_array)) - self.connect_inst(self.wordline_names[port] + self.driver_wordline_outputs[port] + ["vdd", "gnd"]) + temp = [] + temp += [self.get_rbl_wordline_names(port)[port]] + if port == 0: + temp += self.get_wordline_names(port) + else: + temp += self.get_wordline_names(port)[::-1] + self.driver_wordline_outputs.append([x + "i" for x in temp]) + + temp += self.driver_wordline_outputs[-1] + temp += ["vdd", "gnd"] + + self.connect_inst(temp) self.bitcell_array_inst = self.add_inst(name="array", mod=self.bitcell_array) + temp = [] + for port in self.left_rbl: + temp += self.get_rbl_bitline_names(port) + temp += self.all_bitline_names + for port in self.right_rbl: + temp += self.get_rbl_bitline_names(port) - self.connect_inst(self.all_array_bitline_names + self.all_array_wordline_inputs + ["vdd", "gnd"]) + wl_temp = [] + for port in range(self.rbl[0]): + wl_temp += [self.get_rbl_wordline_names(port)[port]] + wl_temp += self.get_wordline_names() + for port in range(self.rbl[0], sum(self.rbl)): + wl_temp += [self.get_rbl_wordline_names(port)[port]] + temp += [x + "i" for x in wl_temp] + temp += ["vdd", "gnd"] + self.connect_inst(temp) def place(self): """ Place the bitcelll array to the right of the wl driver. """ @@ -142,41 +170,17 @@ class local_bitcell_array(design.design): if len(self.all_ports) > 1: self.wl_insts[1].place(vector(self.bitcell_array_inst.rx() + self.wl_array.width + driver_to_array_spacing, - 2 * self.cell.height), - mirror="MY") + 2 * self.cell.height + self.wl_array.height), + mirror="XY") self.height = self.bitcell_array.height - self.width = self.bitcell_array_inst.rx() + self.width = max(self.bitcell_array_inst.rx(), max([x.rx() for x in self.wl_insts])) - def route_unused_wordlines(self): - """ Connect the unused RBL and dummy wordlines to gnd """ - - for wl_name in self.gnd_wl_names: - pin = self.bitcell_array_inst.get_pin(wl_name) - pin_layer = pin.layer - layer_pitch = 1.5 * getattr(self, "{}_pitch".format(pin_layer)) - left_pin_loc = pin.lc() - right_pin_loc = pin.rc() - - # Place the pins a track outside of the array - left_loc = left_pin_loc - vector(layer_pitch, 0) - right_loc = right_pin_loc + vector(layer_pitch, 0) - self.add_power_pin("gnd", left_loc, directions=("H", "H")) - 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]) - def add_layout_pins(self): for x in self.get_inouts(): self.copy_layout_pin(self.bitcell_array_inst, x) - for port in self.all_ports: - for (x, y) in zip(self.wordline_names[port], self.wl_array.get_inputs()): - self.copy_layout_pin(self.wl_insts[port], y, x) - supply_insts = [*self.wl_insts, self.bitcell_array_inst] for pin_name in ["vdd", "gnd"]: for inst in supply_insts: @@ -188,8 +192,44 @@ class local_bitcell_array(design.design): def route(self): + # Route the global wordlines for port in self.all_ports: - for (driver_name, net_name) in zip(self.wl_insts[port].mod.get_outputs(), self.driver_wordline_outputs[port]): + if port == 0: + wordline_names = [self.get_rbl_wordline_names(port)[port]] + self.get_wordline_names(port) + else: + wordline_names = [self.get_rbl_wordline_names(port)[port]] + self.get_wordline_names(port)[::-1] + + wordline_pins = self.wl_array.get_inputs() + + for (wl_name, in_pin_name) in zip(wordline_names, wordline_pins): + # wl_pin = self.bitcell_array_inst.get_pin(wl_name) + in_pin = self.wl_insts[port].get_pin(in_pin_name) + + y_offset = in_pin.cy() + if port == 0: + y_offset -= 2 * self.m3_pitch + else: + y_offset += 2 * self.m3_pitch + + self.add_layout_pin_segment_center(text=wl_name, + layer="m3", + start=vector(self.wl_insts[port].lx(), y_offset), + end=vector(self.wl_insts[port].lx() + self.wl_array.width, y_offset)) + + mid = vector(in_pin.cx(), y_offset) + self.add_path("m2", [in_pin.center(), mid]) + + self.add_via_stack_center(from_layer=in_pin.layer, + to_layer="m2", + offset=in_pin.center()) + self.add_via_center(self.m2_stack, + offset=mid) + + # Route the buffers + for port in self.all_ports: + driver_outputs = self.driver_wordline_outputs[port] + + for (driver_name, net_name) in zip(self.wl_insts[port].mod.get_outputs(), driver_outputs): array_name = net_name[:-1] out_pin = self.wl_insts[port].get_pin(driver_name) in_pin = self.bitcell_array_inst.get_pin(array_name) @@ -203,5 +243,46 @@ class local_bitcell_array(design.design): in_loc = in_pin.rc() self.add_path(out_pin.layer, [out_loc, mid_loc, in_loc]) - self.route_unused_wordlines() - + def get_main_array_top(self): + return self.bitcell_array_inst.by() + self.bitcell_array.get_main_array_top() + + def get_main_array_bottom(self): + return self.bitcell_array_inst.by() + self.bitcell_array.get_main_array_bottom() + + def get_main_array_left(self): + return self.bitcell_array_inst.lx() + self.bitcell_array.get_main_array_left() + + def get_main_array_right(self): + return self.bitcell_array_inst.lx() + self.bitcell_array.get_main_array_right() + + def get_column_offsets(self): + """ + Return an array of the x offsets of all the regular bits + """ + # must add the offset of the instance + offsets = [self.bitcell_array_inst.lx() + x for x in self.bitcell_array.get_column_offsets()] + return offsets + + def graph_exclude_bits(self, targ_row=None, targ_col=None): + """ + Excludes bits in column from being added to graph except target + """ + self.bitcell_array.graph_exclude_bits(targ_row, targ_col) + + def graph_exclude_replica_col_bits(self): + """ + Exclude all but replica in the local array. + """ + + self.bitcell_array.graph_exclude_replica_col_bits() + + def get_cell_name(self, inst_name, row, col): + """Gets the spice name of the target bitcell.""" + return self.bitcell_array.get_cell_name(inst_name + '.x' + self.bitcell_array_inst.name, row, col) + + def clear_exclude_bits(self): + """ + Clears the bit exclusions + """ + self.bitcell_array.clear_exclude_bits() + diff --git a/compiler/modules/port_address.py b/compiler/modules/port_address.py index 532463a7..e416eb43 100644 --- a/compiler/modules/port_address.py +++ b/compiler/modules/port_address.py @@ -17,10 +17,11 @@ class port_address(design.design): Create the address port (row decoder and wordline driver).. """ - def __init__(self, cols, rows, name=""): + def __init__(self, cols, rows, port, name=""): self.num_cols = cols self.num_rows = rows + self.port = port self.addr_size = ceil(log(self.num_rows, 2)) if name == "": @@ -39,6 +40,7 @@ class port_address(design.design): self.add_modules() self.create_row_decoder() self.create_wordline_driver() + self.create_rbl_driver() def create_layout(self): if "li" in layer: @@ -59,6 +61,8 @@ class port_address(design.design): for bit in range(self.num_rows): self.add_pin("wl_{0}".format(bit), "OUTPUT") + + self.add_pin("rbl_wl", "OUTPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") @@ -71,10 +75,16 @@ class port_address(design.design): def route_supplies(self): """ Propagate all vdd/gnd pins up to this level for all modules """ - for inst in self.insts: + for inst in [self.wordline_driver_array_inst, self.row_decoder_inst]: self.copy_power_pins(inst, "vdd") self.copy_power_pins(inst, "gnd") + for rbl_vdd_pin in self.rbl_driver_inst.get_pins("vdd"): + if OPTS.tech_name == "sky130": + self.add_power_pin("vdd", rbl_vdd_pin.center()) + else: + self.add_power_pin("vdd", rbl_vdd_pin.lc()) + def route_pins(self): for row in range(self.addr_size): decoder_name = "addr_{}".format(row) @@ -82,16 +92,16 @@ class port_address(design.design): for row in range(self.num_rows): driver_name = "wl_{}".format(row) - self.copy_layout_pin(self.wordline_driver_inst, driver_name) + self.copy_layout_pin(self.wordline_driver_array_inst, driver_name) - self.copy_layout_pin(self.wordline_driver_inst, "en", "wl_en") + self.copy_layout_pin(self.rbl_driver_inst, "Z", "rbl_wl") def route_internal(self): for row in range(self.num_rows): # The pre/post is to access the pin from "outside" the cell to avoid DRCs decoder_out_pin = self.row_decoder_inst.get_pin("decode_{}".format(row)) decoder_out_pos = decoder_out_pin.rc() - driver_in_pin = self.wordline_driver_inst.get_pin("in_{}".format(row)) + driver_in_pin = self.wordline_driver_array_inst.get_pin("in_{}".format(row)) driver_in_pos = driver_in_pin.lc() self.add_zjog(self.route_layer, decoder_out_pos, driver_in_pos, var_offset=0.3) @@ -102,6 +112,26 @@ class port_address(design.design): self.add_via_stack_center(from_layer=driver_in_pin.layer, to_layer=self.route_layer, offset=driver_in_pos) + + # Route the RBL from the enable input + en_pin = self.wordline_driver_array_inst.get_pin("en") + if self.port == 0: + en_pos = en_pin.bc() + else: + en_pos = en_pin.uc() + rbl_in_pin = self.rbl_driver_inst.get_pin("A") + rbl_in_pos = rbl_in_pin.center() + + self.add_via_stack_center(from_layer=rbl_in_pin.layer, + to_layer=en_pin.layer, + offset=rbl_in_pos) + self.add_zjog(layer=en_pin.layer, + start=rbl_in_pos, + end=en_pos, + first_direction="V") + self.add_layout_pin_rect_center(text="wl_en", + layer=en_pin.layer, + offset=rbl_in_pos) def add_modules(self): @@ -109,10 +139,33 @@ class port_address(design.design): num_outputs=self.num_rows) self.add_mod(self.row_decoder) - self.wordline_driver = factory.create(module_type="wordline_driver_array", - rows=self.num_rows, - cols=self.num_cols) - self.add_mod(self.wordline_driver) + self.wordline_driver_array = factory.create(module_type="wordline_driver_array", + rows=self.num_rows, + cols=self.num_cols) + self.add_mod(self.wordline_driver_array) + + try: + local_array_size = OPTS.local_array_size + driver_size = max(int(self.num_cols / local_array_size), 1) + except AttributeError: + local_array_size = 0 + # Defautl to FO4 + driver_size = max(int(self.num_cols / 4), 1) + + # The polarity must be switched if we have a hierarchical wordline + # to compensate for the local array inverters + b = factory.create(module_type="bitcell") + + if local_array_size > 0: + 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", + size=driver_size, + height=b.height) + + self.add_mod(self.rbl_driver) def create_row_decoder(self): """ Create the hierarchical row decoder """ @@ -128,11 +181,24 @@ class port_address(design.design): temp.extend(["vdd", "gnd"]) self.connect_inst(temp) + def create_rbl_driver(self): + """ Create the RBL Wordline Driver """ + + self.rbl_driver_inst = self.add_inst(name="rbl_driver", + mod=self.rbl_driver) + + temp = [] + temp.append("wl_en") + temp.append("rbl_wl") + temp.append("vdd") + temp.append("gnd") + self.connect_inst(temp) + def create_wordline_driver(self): """ Create the Wordline Driver """ - - self.wordline_driver_inst = self.add_inst(name="wordline_driver", - mod=self.wordline_driver) + + self.wordline_driver_array_inst = self.add_inst(name="wordline_driver", + mod=self.wordline_driver_array) temp = [] for row in range(self.num_rows): @@ -150,11 +216,23 @@ class port_address(design.design): """ row_decoder_offset = vector(0, 0) - wordline_driver_offset = vector(self.row_decoder.width, 0) - self.wordline_driver_inst.place(wordline_driver_offset) self.row_decoder_inst.place(row_decoder_offset) + + 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 + if self.port == 0: + rbl_driver_offset = vector(x_offset, + 0) + self.rbl_driver_inst.place(rbl_driver_offset, "MX") + else: + rbl_driver_offset = vector(x_offset, + self.wordline_driver_array.height) + self.rbl_driver_inst.place(rbl_driver_offset) + # Pass this up self.predecoder_height = self.row_decoder.predecoder_height self.height = self.row_decoder.height - self.width = self.wordline_driver_inst.rx() + self.width = self.wordline_driver_array_inst.rx() diff --git a/compiler/modules/port_data.py b/compiler/modules/port_data.py index a8e4bdb2..c013bc4a 100644 --- a/compiler/modules/port_data.py +++ b/compiler/modules/port_data.py @@ -6,6 +6,7 @@ from tech import drc import debug import design +import math from sram_factory import factory from collections import namedtuple from vector import vector @@ -18,18 +19,26 @@ class port_data(design.design): Port 0 always has the RBL on the left while port 1 is on the right. """ - def __init__(self, sram_config, port, name=""): + def __init__(self, sram_config, port, bit_offsets=None, name=""): sram_config.set_local_config(self) self.port = port if self.write_size is not None: - self.num_wmasks = int(self.word_size / self.write_size) + self.num_wmasks = int(math.ceil(self.word_size / self.write_size)) else: self.num_wmasks = 0 if self.num_spare_cols is None: self.num_spare_cols = 0 + if not bit_offsets: + bitcell = factory.create(module_type="bitcell") + self.bit_offsets = [] + for i in range(self.num_cols + self.num_spare_cols): + self.bit_offsets.append(i * bitcell.width) + else: + self.bit_offsets = bit_offsets + if name == "": name = "port_data_{0}".format(self.port) super().__init__(name) @@ -179,8 +188,19 @@ class port_data(design.design): # Precharge will be shifted left if needed # Column offset is set to port so extra column can be on left or right # and mirroring happens correctly + + # Used for names/dimensions only + self.cell = factory.create(module_type="bitcell") + + if self.port == 0: + # Append an offset on the left + precharge_bit_offsets = [self.bit_offsets[0] - self.cell.width] + self.bit_offsets + else: + # Append an offset on the right + precharge_bit_offsets = self.bit_offsets + [self.bit_offsets[-1] + self.cell.width] self.precharge_array = factory.create(module_type="precharge_array", columns=self.num_cols + self.num_spare_cols + 1, + offsets=precharge_bit_offsets, bitcell_bl=self.bl_names[self.port], bitcell_br=self.br_names[self.port], column_offset=self.port - 1) @@ -190,6 +210,7 @@ class port_data(design.design): # RBLs don't get a sense amp self.sense_amp_array = factory.create(module_type="sense_amp_array", word_size=self.word_size, + offsets=self.bit_offsets, words_per_row=self.words_per_row, num_spare_cols=self.num_spare_cols) self.add_mod(self.sense_amp_array) @@ -201,6 +222,7 @@ class port_data(design.design): self.column_mux_array = factory.create(module_type="column_mux_array", columns=self.num_cols, word_size=self.word_size, + offsets=self.bit_offsets, bitcell_bl=self.bl_names[self.port], bitcell_br=self.br_names[self.port]) self.add_mod(self.column_mux_array) @@ -212,6 +234,7 @@ class port_data(design.design): self.write_driver_array = factory.create(module_type="write_driver_array", columns=self.num_cols, word_size=self.word_size, + offsets=self.bit_offsets, write_size=self.write_size, num_spare_cols=self.num_spare_cols) self.add_mod(self.write_driver_array) @@ -219,6 +242,7 @@ class port_data(design.design): # RBLs don't get a write mask self.write_mask_and_array = factory.create(module_type="write_mask_and_array", columns=self.num_cols, + offsets=self.bit_offsets, word_size=self.word_size, write_size=self.write_size) self.add_mod(self.write_mask_and_array) @@ -411,21 +435,15 @@ class port_data(design.design): vertical_port_order.append(self.write_driver_array_inst) vertical_port_order.append(self.write_mask_and_array_inst) - # Add one column for the the RBL - if self.port==0: - x_offset = self.bitcell.width - else: - x_offset = 0 - vertical_port_offsets = 5 * [None] - self.width = x_offset + self.width = 0 self.height = 0 for i, p in enumerate(vertical_port_order): if p == None: continue self.height += (p.height + self.m2_gap) self.width = max(self.width, p.width) - vertical_port_offsets[i] = vector(x_offset, self.height) + vertical_port_offsets[i] = vector(0, self.height) # Reversed order self.write_mask_and_offset = vertical_port_offsets[4] @@ -433,9 +451,6 @@ class port_data(design.design): self.sense_amp_offset = vertical_port_offsets[2] self.column_mux_offset = vertical_port_offsets[1] self.precharge_offset = vertical_port_offsets[0] - # Shift the precharge left if port 0 - if self.precharge_offset and self.port == 0: - self.precharge_offset -= vector(x_offset, 0) def place_instances(self): """ Place the instances. """ diff --git a/compiler/modules/precharge_array.py b/compiler/modules/precharge_array.py index c2d3d986..7f101ac9 100644 --- a/compiler/modules/precharge_array.py +++ b/compiler/modules/precharge_array.py @@ -10,7 +10,7 @@ import debug from vector import vector from sram_factory import factory from globals import OPTS -from tech import layer +from tech import cell_properties class precharge_array(design.design): @@ -19,12 +19,13 @@ class precharge_array(design.design): of bit line columns, height is the height of the bit-cell array. """ - def __init__(self, name, columns, size=1, bitcell_bl="bl", bitcell_br="br", column_offset=0): + def __init__(self, name, columns, offsets=None, size=1, bitcell_bl="bl", bitcell_br="br", column_offset=0): super().__init__(name) debug.info(1, "Creating {0}".format(self.name)) self.add_comment("cols: {0} size: {1} bl: {2} br: {3}".format(columns, size, bitcell_bl, bitcell_br)) self.columns = columns + self.offsets = offsets self.size = size self.bitcell_bl = bitcell_bl self.bitcell_br = bitcell_br @@ -62,10 +63,11 @@ class precharge_array(design.design): self.create_insts() def create_layout(self): - self.width = self.columns * self.pc_cell.width + self.place_insts() + + self.width = self.offsets[-1] + self.pc_cell.width self.height = self.pc_cell.height - self.place_insts() self.add_layout_pins() self.add_boundary() self.DRC_LVS() @@ -112,26 +114,20 @@ class precharge_array(design.design): def place_insts(self): """ Places precharge array by horizontally tiling the precharge cell""" - from tech import cell_properties - xoffset = 0 - for i in range(self.columns): - tempx = xoffset + + # Default to single spaced columns + if not self.offsets: + self.offsets = [n * self.pc_cell.width for n in range(self.columns)] + + for i, xoffset in enumerate(self.offsets): if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2: mirror = "MY" - tempx = tempx + self.pc_cell.width + tempx = xoffset + self.pc_cell.width else: mirror = "" + tempx = xoffset offset = vector(tempx, 0) self.local_insts[i].place(offset=offset, mirror=mirror) - xoffset = xoffset + self.pc_cell.width - def get_en_cin(self): - """ - Get the relative capacitance of all the clk connections - in the precharge array - """ - # Assume single port - precharge_en_cin = self.pc_cell.get_en_cin() - return precharge_en_cin * self.columns diff --git a/compiler/modules/replica_bitcell_array.py b/compiler/modules/replica_bitcell_array.py index d3c25ba0..dbd724c7 100644 --- a/compiler/modules/replica_bitcell_array.py +++ b/compiler/modules/replica_bitcell_array.py @@ -21,39 +21,48 @@ class replica_bitcell_array(bitcell_base_array.bitcell_base_array): Requires a regular bitcell array, replica bitcell, and dummy bitcell (Bl/BR disconnected). """ - def __init__(self, rows, cols, rbl, name, add_rbl=None): + def __init__(self, rows, cols, rbl=None, left_rbl=None, right_rbl=None, name=""): super().__init__(name, rows, cols, column_offset=0) - debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols)) + debug.info(1, "Creating {0} {1} x {2} rbls: {3} left_rbl: {4} right_rbl: {5}".format(self.name, + rows, + cols, + rbl, + left_rbl, + right_rbl)) self.add_comment("rows: {0} cols: {1}".format(rows, cols)) + self.add_comment("rbl: {0} left_rbl: {1} right_rbl: {2}".format(rbl, left_rbl, right_rbl)) self.column_size = cols self.row_size = rows # This is how many RBLs are in all the arrays - self.rbl = rbl - self.left_rbl = rbl[0] - self.right_rbl = rbl[1] - # This is how many RBLs are added to THIS array - if add_rbl == None: - self.add_left_rbl = rbl[0] - self.add_right_rbl = rbl[1] + if rbl: + self.rbl = rbl else: - self.add_left_rbl = add_rbl[0] - self.add_right_rbl = add_rbl[1] - for a, b in zip(add_rbl, rbl): - debug.check(a <= b, - "Invalid number of RBLs for port configuration.") - - debug.check(sum(rbl) <= len(self.all_ports), + self.rbl=[1, 1 if len(self.all_ports)>1 else 0] + # This specifies which RBL to put on the left or right + # by port number + # This could be an empty list + if left_rbl != None: + self.left_rbl = left_rbl + else: + self.left_rbl = [0] + # This could be an empty list + if right_rbl != None: + self.right_rbl = right_rbl + else: + self.right_rbl=[1] if len(self.all_ports) > 1 else [] + self.rbls = self.left_rbl + self.right_rbl + + debug.check(sum(self.rbl) == len(self.all_ports), + "Invalid number of RBLs for port configuration.") + debug.check(sum(self.rbl) >= len(self.left_rbl) + len(self.right_rbl), "Invalid number of RBLs for port configuration.") - if not cell_properties.compare_ports(cell_properties.bitcell_array.use_custom_cell_arrangement): - # Two dummy rows plus replica even if we don't add the column - self.extra_rows = 2 + sum(rbl) - # Two dummy cols plus replica if we add the column - self.extra_cols = 2 + self.add_left_rbl + self.add_right_rbl - else: - self.extra_rows = 0 - self.extra_cols = 2 + self.add_left_rbl + self.add_right_rbl + # Two dummy rows plus replica even if we don't add the column + self.extra_rows = 2 + sum(self.rbl) + # Two dummy cols plus replica if we add the column + self.extra_cols = 2 + len(self.left_rbl) + len(self.right_rbl) + self.create_netlist() if not OPTS.netlist_only: self.create_layout() @@ -93,53 +102,60 @@ class replica_bitcell_array(bitcell_base_array.bitcell_base_array): """ # Bitcell array self.bitcell_array = factory.create(module_type="bitcell_array", - column_offset=1 + self.add_left_rbl, + column_offset=1 + len(self.left_rbl), cols=self.column_size, rows=self.row_size) self.add_mod(self.bitcell_array) # Replica bitlines self.replica_columns = {} - for bit in range(self.add_left_rbl + self.add_right_rbl): - # Creating left_rbl - if bit < self.add_left_rbl: + + for port in self.all_ports: + if port in self.left_rbl: + # We will always have self.rbl[0] rows of replica wordlines below + # the array. # These go from the top (where the bitcell array starts ) down - replica_bit = self.left_rbl - bit - # Creating right_rbl - else: + replica_bit = self.rbl[0] - port + 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.left_rbl + self.row_size + 1 + bit + replica_bit = self.rbl[0] + self.row_size + port + else: + continue + # If we have an odd numer on the bottom - column_offset = self.left_rbl + 1 - - self.replica_columns[bit] = factory.create(module_type="replica_column", - rows=self.row_size, - rbl=self.rbl, - column_offset=column_offset, - replica_bit=replica_bit) - self.add_mod(self.replica_columns[bit]) - # If there are bitcell end caps, replace the dummy cells on the edge of the bitcell array with end caps. + column_offset = self.rbl[0] + 1 + + self.replica_columns[port] = factory.create(module_type="replica_column", + rows=self.row_size, + rbl=self.rbl, + column_offset=column_offset, + replica_bit=replica_bit) + self.add_mod(self.replica_columns[port]) try: end_caps_enabled = cell_properties.bitcell.end_caps except AttributeError: end_caps_enabled = False - if not cell_properties.compare_ports(cell_properties.bitcell_array.use_custom_cell_arrangement): - # Dummy row - self.dummy_row = factory.create(module_type="dummy_array", + + # Dummy row + self.dummy_row = factory.create(module_type="dummy_array", cols=self.column_size, rows=1, # dummy column + left replica column - column_offset=1 + self.add_left_rbl, + column_offset=1 + len(self.left_rbl), mirror=0) - self.add_mod(self.dummy_row) + self.add_mod(self.dummy_row) + if not cell_properties.compare_ports(cell_properties.bitcell_array.use_custom_cell_arrangement): # Dummy Row or Col Cap, depending on bitcell array properties col_cap_module_type = ("col_cap_array" if end_caps_enabled else "dummy_array") self.col_cap = factory.create(module_type=col_cap_module_type, cols=self.column_size, rows=1, - # dummy column + left replica column(s) - column_offset=1 + self.add_left_rbl, + # dummy column + left replica column + column_offset=1 + len(self.left_rbl), mirror=0) self.add_mod(self.col_cap) @@ -150,7 +166,7 @@ class replica_bitcell_array(bitcell_base_array.bitcell_base_array): cols=1, column_offset=0, rows=self.row_size + self.extra_rows, - mirror=(self.left_rbl + 1) % 2) + mirror=(self.rbl[0] + 1) % 2) self.add_mod(self.row_cap_left) self.row_cap_right = factory.create(module_type=row_cap_module_type, @@ -159,11 +175,30 @@ class replica_bitcell_array(bitcell_base_array.bitcell_base_array): # + left replica column(s) # + bitcell columns # + right replica column(s) - column_offset = 1 + self.add_left_rbl + self.column_size + self.add_right_rbl, + column_offset = 1 + len(self.left_rbl) + self.column_size + self.rbl[0], rows=self.row_size + self.extra_rows, - mirror=(self.left_rbl + 1) %2) + mirror=(self.rbl[0] + 1) %2) self.add_mod(self.row_cap_right) else: + # Dummy Row or Col Cap, depending on bitcell array properties + col_cap_module_type = ("s8_col_cap_array" if end_caps_enabled else "dummy_array") + self.col_cap_top = factory.create(module_type=col_cap_module_type, + cols=self.column_size, + rows=1, + # dummy column + left replica column(s) + column_offset=1 + len(self.left_rbl), + mirror=0, + location="top") + self.add_mod(self.col_cap_top) + + self.col_cap_bottom = factory.create(module_type=col_cap_module_type, + cols=self.column_size, + rows=1, + # dummy column + left replica column(s) + column_offset=1 + len(self.left_rbl), + mirror=0, + location="bottom") + self.add_mod(self.col_cap_bottom) # Dummy Col or Row Cap, depending on bitcell array properties row_cap_module_type = ("s8_row_cap_array" if end_caps_enabled else "dummy_array") @@ -180,7 +215,7 @@ class replica_bitcell_array(bitcell_base_array.bitcell_base_array): # + left replica column(s) # + bitcell columns # + right replica column(s) - column_offset = 1 + self.add_left_rbl + self.column_size + self.add_right_rbl, + column_offset = 1 + len(self.left_rbl) + self.column_size + self.rbl[0], rows=self.row_size + self.extra_rows, mirror=0) self.add_mod(self.row_cap_right) @@ -206,226 +241,313 @@ class replica_bitcell_array(bitcell_base_array.bitcell_base_array): self.add_pin("gnd", "GROUND") def add_bitline_pins(self): - # Regular bitline names by port - self.bitline_names = [] - # Replica bitlines by port - self.rbl_bitline_names = [] - # Dummy bitlines by left/right - self.dummy_col_bitline_names = [] - if not cell_properties.compare_ports(cell_properties.bitcell_array.use_custom_cell_arrangement): - - for loc in ["left", "right"]: - self.dummy_col_bitline_names.append([]) - for port in self.all_ports: - bitline_names = ["dummy_{0}_{1}".format(x, loc) for x in self.row_cap_left.get_bitline_names(port)] - self.dummy_col_bitline_names[-1].extend(bitline_names) - self.all_dummy_col_bitline_names = [x for sl in self.dummy_col_bitline_names for x in sl] - for port in range(self.add_left_rbl + self.add_right_rbl): - left_names=["rbl_bl_{0}_{1}".format(x, port) for x in self.all_ports] - right_names=["rbl_br_{0}_{1}".format(x, port) for x in self.all_ports] - bitline_names = [x for t in zip(left_names, right_names) for x in t] - self.rbl_bitline_names.append(bitline_names) + # The bit is which port the RBL is for + for bit in self.rbls: + for port in self.all_ports: + self.rbl_bitline_names[bit].append("rbl_bl_{0}_{1}".format(port, bit)) + for port in self.all_ports: + self.rbl_bitline_names[bit].append("rbl_br_{0}_{1}".format(port, bit)) # Make a flat list too self.all_rbl_bitline_names = [x for sl in self.rbl_bitline_names for x in sl] - for port in self.all_ports: - bitline_names = self.bitcell_array.get_bitline_names(port) - self.bitline_names.append(bitline_names) + self.bitline_names = self.bitcell_array.bitline_names # Make a flat list too self.all_bitline_names = [x for sl in zip(*self.bitline_names) for x in sl] - if not cell_properties.compare_ports(cell_properties.bitcell_array.use_custom_cell_arrangement): - self.add_pin_list(self.dummy_col_bitline_names[0], "INOUT") - for port in range(self.add_left_rbl): - self.add_pin_list(self.rbl_bitline_names[port], "INOUT") - self.add_pin_list(self.all_bitline_names, "INOUT") - for port in range(self.add_left_rbl, self.add_left_rbl + self.add_right_rbl): - self.add_pin_list(self.rbl_bitline_names[port], "INOUT") - self.add_pin_list(self.dummy_col_bitline_names[1], "INOUT") + + for port in self.left_rbl: + self.add_pin_list(self.rbl_bitline_names[port], "INOUT") + self.add_pin_list(self.all_bitline_names, "INOUT") + for port in self.right_rbl: + self.add_pin_list(self.rbl_bitline_names[port], "INOUT") def add_wordline_pins(self): - # Regular wordlines by port - self.wordline_names = [] - # Replica wordlines by port - self.rbl_wordline_names = [] - # Dummy wordlines by bot/top - self.dummy_row_wordline_names = [] - if not cell_properties.compare_ports(cell_properties.bitcell_array.use_custom_cell_arrangement): - dummy_row_wordline_names = ["dummy_" + x for x in self.col_cap.get_wordline_names()] - for loc in ["bot", "top"]: - wordline_names = ["{0}_{1}".format(wl_name, loc) for wl_name in dummy_row_wordline_names] - self.dummy_row_wordline_names.append(wordline_names) - self.all_dummy_row_wordline_names = [x for sl in self.dummy_row_wordline_names for x in sl] - - for port in range(self.left_rbl + self.right_rbl): - wordline_names=["rbl_wl_{0}_{1}".format(x, port) for x in self.all_ports] - self.rbl_wordline_names.append(wordline_names) - self.all_rbl_wordline_names = [x for sl in self.rbl_wordline_names for x in sl] + # Wordlines to ground + self.gnd_wordline_names = [] for port in self.all_ports: - wordline_names = self.bitcell_array.get_wordline_names(port) - self.wordline_names.append(wordline_names) - self.all_wordline_names = [x for sl in zip(*self.wordline_names) for x in sl] + for bit in self.all_ports: + #if not cell_properties.compare_ports(cell_properties.bitcell.split_wl): + self.rbl_wordline_names[port].append("rbl_wl_{0}_{1}".format(port, bit)) + if bit != port: + self.gnd_wordline_names.append("rbl_wl_{0}_{1}".format(port, bit)) + #else: + # self.rbl_wordline_names[port].append("rbl_wl0_{0}_{1}".format(port, bit)) + # self.rbl_wordline_names[port].append("rbl_wl1_{0}_{1}".format(port, bit)) + # if bit != port: + # self.gnd_wordline_names.append("rbl0_wl_{0}_{1}".format(port, bit)) + # self.gnd_wordline_names.append("rbl1_wl_{0}_{1}".format(port, bit)) + + self.all_rbl_wordline_names = [x for sl in self.rbl_wordline_names for x in sl] + + self.wordline_names = self.bitcell_array.wordline_names + self.all_wordline_names = self.bitcell_array.all_wordline_names + # All wordlines including dummy and RBL self.replica_array_wordline_names = [] if not cell_properties.compare_ports(cell_properties.bitcell_array.use_custom_cell_arrangement): - self.replica_array_wordline_names.extend(self.dummy_row_wordline_names[0]) - for p in range(self.left_rbl): - self.replica_array_wordline_names.extend(self.rbl_wordline_names[p]) + self.replica_array_wordline_names.extend(["gnd"] * len(self.col_cap.get_wordline_names())) + for bit in range(self.rbl[0]): + self.replica_array_wordline_names.extend([x if x not in self.gnd_wordline_names else "gnd" for x in self.rbl_wordline_names[bit]]) self.replica_array_wordline_names.extend(self.all_wordline_names) - for p in range(self.left_rbl, self.left_rbl + self.right_rbl): - self.replica_array_wordline_names.extend(self.rbl_wordline_names[p]) + for bit in range(self.rbl[1]): + self.replica_array_wordline_names.extend([x if x not in self.gnd_wordline_names else "gnd" for x in self.rbl_wordline_names[self.rbl[0] + bit]]) if not cell_properties.compare_ports(cell_properties.bitcell_array.use_custom_cell_arrangement): - self.replica_array_wordline_names.extend(self.dummy_row_wordline_names[1]) - if not cell_properties.compare_ports(cell_properties.bitcell_array.use_custom_cell_arrangement): - self.add_pin_list(self.dummy_row_wordline_names[0], "INPUT") - for port in range(self.left_rbl): - self.add_pin_list(self.rbl_wordline_names[port], "INPUT") + self.replica_array_wordline_names.extend(["gnd"] * len(self.col_cap.get_wordline_names())) + + for port in range(self.rbl[0]): + self.add_pin(self.rbl_wordline_names[port][port], "INPUT") self.add_pin_list(self.all_wordline_names, "INPUT") - for port in range(self.left_rbl, self.left_rbl + self.right_rbl): - self.add_pin_list(self.rbl_wordline_names[port], "INPUT") - if not cell_properties.compare_ports(cell_properties.bitcell_array.use_custom_cell_arrangement): - self.add_pin_list(self.dummy_row_wordline_names[1], "INPUT") + for port in range(self.rbl[0], self.rbl[0] + self.rbl[1]): + self.add_pin(self.rbl_wordline_names[port][port], "INPUT") def create_instances(self): """ Create the module instances used in this design """ - - supplies = ["vdd", "gnd"] - - # Used for names/dimensions only if not cell_properties.compare_ports(cell_properties.bitcell_array.use_custom_cell_arrangement): - self.cell = factory.create(module_type="bitcell") + + self.supplies = ["vdd", "gnd"] + + # Used for names/dimensions only + self.cell = factory.create(module_type="bitcell") + + # Main array + self.bitcell_array_inst=self.add_inst(name="bitcell_array", + mod=self.bitcell_array) + self.connect_inst(self.all_bitline_names + self.all_wordline_names + self.supplies) + + # Replica columns + self.replica_col_insts = [] + for port in self.all_ports: + if port in self.rbls: + self.replica_col_insts.append(self.add_inst(name="replica_col_{}".format(port), + mod=self.replica_columns[port])) + self.connect_inst(self.rbl_bitline_names[port] + self.replica_array_wordline_names + self.supplies) + else: + self.replica_col_insts.append(None) + + # Dummy rows under the bitcell array (connected with with the replica cell wl) + self.dummy_row_replica_insts = [] + # Note, this is the number of left and right even if we aren't adding the columns to this bitcell array! + for port in self.all_ports: + self.dummy_row_replica_insts.append(self.add_inst(name="dummy_row_{}".format(port), + mod=self.dummy_row)) + self.connect_inst([x if x not in self.gnd_wordline_names else "gnd" for x in self.rbl_wordline_names[port]] + self.supplies) + + # Top/bottom dummy rows or col caps + self.dummy_row_insts = [] + self.dummy_row_insts.append(self.add_inst(name="dummy_row_bot", + mod=self.col_cap)) + self.connect_inst(["gnd"] * len(self.col_cap.get_wordline_names()) + self.supplies) + self.dummy_row_insts.append(self.add_inst(name="dummy_row_top", + mod=self.col_cap)) + self.connect_inst(["gnd"] * len(self.col_cap.get_wordline_names()) + self.supplies) + + # Left/right Dummy columns + self.dummy_col_insts = [] + self.dummy_col_insts.append(self.add_inst(name="dummy_col_left", + mod=self.row_cap_left)) + self.connect_inst(self.replica_array_wordline_names + self.supplies) + self.dummy_col_insts.append(self.add_inst(name="dummy_col_right", + mod=self.row_cap_right)) + self.connect_inst(self.replica_array_wordline_names + self.supplies) else: - self.cell = factory.create(module_type="s8_bitcell", version = "opt1") - - # Main array - self.bitcell_array_inst=self.add_inst(name="bitcell_array", - mod=self.bitcell_array) - self.connect_inst(self.all_bitline_names + self.all_wordline_names + supplies) - - # Replica columns - self.replica_col_insts = [] - for port in range(self.add_left_rbl + self.add_right_rbl): - self.replica_col_insts.append(self.add_inst(name="replica_col_{}".format(port), - mod=self.replica_columns[port])) - self.connect_inst(self.rbl_bitline_names[port] + self.replica_array_wordline_names + supplies) - - # Dummy rows under the bitcell array (connected with with the replica cell wl) - self.dummy_row_replica_insts = [] - # Note, this is the number of left and right even if we aren't adding the columns to this bitcell array! - for port in range(self.left_rbl + self.right_rbl): - self.dummy_row_replica_insts.append(self.add_inst(name="dummy_row_{}".format(port), - mod=self.dummy_row)) - self.connect_inst(self.all_bitline_names + self.rbl_wordline_names[port] + supplies) - - # Top/bottom dummy rows or col caps - self.dummy_row_insts = [] - self.dummy_row_insts.append(self.add_inst(name="dummy_row_bot", - mod=self.col_cap)) - self.connect_inst(self.all_bitline_names - + self.dummy_row_wordline_names[0] - + supplies) - self.dummy_row_insts.append(self.add_inst(name="dummy_row_top", - mod=self.col_cap)) - self.connect_inst(self.all_bitline_names - + self.dummy_row_wordline_names[1] - + supplies) - - # Left/right Dummy columns - self.dummy_col_insts = [] - self.dummy_col_insts.append(self.add_inst(name="dummy_col_left", - mod=self.row_cap_left)) - self.connect_inst(self.dummy_col_bitline_names[0] + self.replica_array_wordline_names + supplies) - self.dummy_col_insts.append(self.add_inst(name="dummy_col_right", - mod=self.row_cap_right)) - self.connect_inst(self.dummy_col_bitline_names[1] + self.replica_array_wordline_names + supplies) + from tech import custom_replica_bitcell_array_arrangement + custom_replica_bitcell_array_arrangement(self) def create_layout(self): - self.height = (self.row_size + self.extra_rows) * self.dummy_row.height - self.width = (self.column_size + self.extra_cols) * self.cell.width + # We will need unused wordlines grounded, so we need to know their layer + 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 + if not cell_properties.compare_ports(cell_properties.bitcell_array.use_custom_cell_arrangement): + 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 + else: + self.width = self.row_cap_left.width + self.row_cap_right.width + self.col_cap_top.width + for rbl in range(self.rbl[0] + self.rbl[1]): + self.width += self.replica_col_insts[rbl].width + self.height = self.row_cap_left.height + # This is a bitcell x bitcell offset to scale self.bitcell_offset = vector(self.cell.width, self.cell.height) + if not cell_properties.compare_ports(cell_properties.bitcell_array.use_custom_cell_arrangement): + 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) + else: + self.strap_offset = vector(self.replica_col_insts[0].mod.strap1.width, self.replica_col_insts[0].mod.strap1.height) + self.col_end_offset = vector(self.dummy_row_insts[0].mod.colend1.width, self.dummy_row_insts[0].mod.colend1.height) + self.row_end_offset = vector(self.dummy_col_insts[0].mod.rowend1.width, self.dummy_col_insts[0].mod.rowend1.height) - # Everything is computed with the main array at (0, 0) to start - self.bitcell_array_inst.place(offset=[0, 0]) + # Everything is computed with the main array at (self.unused_pitch, 0) to start + self.bitcell_array_inst.place(offset=self.unused_offset) self.add_replica_columns() 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 - self.translate_all(self.bitcell_offset.scale(-1 - self.add_left_rbl, -1 - self.left_rbl)) + array_offset = self.bitcell_offset.scale(1 + len(self.left_rbl), 1 + self.rbl[0]) + self.translate_all(array_offset.scale(-1, -1)) self.add_layout_pins() + self.route_unused_wordlines() + self.add_boundary() self.DRC_LVS() + def get_main_array_top(self): + return self.bitcell_array_inst.uy() + + def get_main_array_bottom(self): + return self.bitcell_array_inst.by() + + def get_main_array_left(self): + return self.bitcell_array_inst.lx() + + def get_main_array_right(self): + return self.bitcell_array_inst.rx() + + def get_column_offsets(self): + """ + Return an array of the x offsets of all the regular bits + """ + offsets = [x + self.bitcell_array_inst.lx() for x in self.bitcell_array.get_column_offsets()] + return offsets + def add_replica_columns(self): """ Add replica columns on left and right of array """ + end_caps_enabled = cell_properties.bitcell.end_caps # Grow from left to right, toward the array - for bit in range(self.add_left_rbl): - offset = self.bitcell_offset.scale(-self.add_left_rbl + bit, -self.left_rbl - 1) + for bit, port in enumerate(self.left_rbl): + if not end_caps_enabled: + 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 + self.replica_col_insts[bit].place(offset) # Grow to the right of the bitcell array, array outward - for bit in range(self.add_right_rbl): - offset = self.bitcell_array_inst.lr() + self.bitcell_offset.scale(bit, -self.left_rbl - 1) - self.replica_col_insts[self.add_left_rbl + bit].place(offset) + for bit, port in enumerate(self.right_rbl): + if not end_caps_enabled: + 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) + + self.replica_col_insts[len(self.left_rbl) + bit].place(offset) # Replica dummy rows # Add the dummy rows even if we aren't adding the replica column to this bitcell array # These grow up, toward the array - for bit in range(self.left_rbl): - self.dummy_row_replica_insts[bit].place(offset=self.bitcell_offset.scale(0, -self.left_rbl + bit + (-self.left_rbl + bit) % 2), - mirror="MX" if (-self.left_rbl + bit) % 2 else "R0") + for bit in range(self.rbl[0]): + dummy_offset = self.bitcell_offset.scale(0, -self.rbl[0] + bit + (-self.rbl[0] + bit) % 2) + self.unused_offset + self.dummy_row_replica_insts[bit].place(offset=dummy_offset, + mirror="MX" if (-self.rbl[0] + bit) % 2 else "R0") # These grow up, away from the array - for bit in range(self.right_rbl): - self.dummy_row_replica_insts[self.left_rbl + bit].place(offset=self.bitcell_offset.scale(0, bit + bit % 2) + self.bitcell_array_inst.ul(), - mirror="MX" if bit % 2 else "R0") + for bit in range(self.rbl[1]): + dummy_offset = self.bitcell_offset.scale(0, bit + bit % 2) + self.bitcell_array_inst.ul() + self.dummy_row_replica_insts[self.rbl[0] + bit].place(offset=dummy_offset, + mirror="MX" if bit % 2 else "R0") def add_end_caps(self): """ Add dummy cells or end caps around the array """ + end_caps_enabled = cell_properties.bitcell.end_caps # FIXME: These depend on the array size itself # Far top dummy row (first row above array is NOT flipped) - flip_dummy = self.right_rbl % 2 - dummy_row_offset = self.bitcell_offset.scale(0, self.right_rbl + flip_dummy) + self.bitcell_array_inst.ul() + flip_dummy = self.rbl[1] % 2 + if not end_caps_enabled: + 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() + 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.left_rbl + 1) % 2 - dummy_row_offset = self.bitcell_offset.scale(0, -self.left_rbl - 1 + flip_dummy) + flip_dummy = (self.rbl[0] + 1) % 2 + if not end_caps_enabled: + 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 self.dummy_row_insts[0].place(offset=dummy_row_offset, 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 - dummy_col_offset = self.bitcell_offset.scale(-self.add_left_rbl - 1, -self.left_rbl - 1) + if not end_caps_enabled: + dummy_col_offset = self.bitcell_offset.scale(-len(self.left_rbl) - 1, -len(self.left_rbl) - 1) + 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)) + 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 - dummy_col_offset = self.bitcell_offset.scale(self.add_right_rbl, -self.left_rbl - 1) + self.bitcell_array_inst.lr() + if not end_caps_enabled: + dummy_col_offset = self.bitcell_offset.scale(len(self.right_rbl)*(1+self.strap_offset.x/self.cell.width), -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() + self.dummy_col_insts[1].place(offset=dummy_col_offset) def add_layout_pins(self): """ Add the layout pins """ + + #All wordlines + #Main array wl and bl/br + if not cell_properties.compare_ports(cell_properties.bitcell_array.use_custom_cell_arrangement): - # All wordlines - # Main array wl and bl/br - for pin_name in self.all_wordline_names: - pin_list = self.bitcell_array_inst.get_pins(pin_name) - for pin in pin_list: - self.add_layout_pin(text=pin_name, + for pin_name in self.all_wordline_names: + pin_list = self.bitcell_array_inst.get_pins(pin_name) + for pin in pin_list: + self.add_layout_pin(text=pin_name, + layer=pin.layer, + 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): + for (wl_name, pin_name) in zip(names, self.dummy_row.get_wordline_names()): + if wl_name in self.gnd_wordline_names: + continue + pin = inst.get_pin(pin_name) + self.add_layout_pin(text=wl_name, layer=pin.layer, offset=pin.ll().scale(0, 1), width=self.width, height=pin.height()) + else: + for pin_name in self.all_wordline_names: + pin_list = self.dummy_col_insts[0].get_pins(pin_name) + for pin in pin_list: + self.add_layout_pin(text=pin_name, + layer=pin.layer, + 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): + for (wl_name, pin_name) in zip(names, self.dummy_row.get_wordline_names()): + if wl_name in self.gnd_wordline_names: + continue + pin = inst.get_pin(pin_name) + self.add_layout_pin(text=wl_name, + layer=pin.layer, + offset=pin.ll().scale(0, 1), + width=self.width, + height=pin.height()) + for pin_name in self.all_bitline_names: pin_list = self.bitcell_array_inst.get_pins(pin_name) for pin in pin_list: @@ -435,126 +557,34 @@ class replica_bitcell_array(bitcell_base_array.bitcell_base_array): width=pin.width(), height=self.height) - # Dummy wordlines - for (names, inst) in zip(self.dummy_row_wordline_names, self.dummy_row_insts): - for (wl_name, pin_name) in zip(names, self.dummy_row.get_wordline_names()): - # It's always a single row - pin = inst.get_pin(pin_name) - self.add_layout_pin(text=wl_name, - layer=pin.layer, - 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): - for (wl_name, pin_name) in zip(names, self.dummy_row.get_wordline_names()): - pin = inst.get_pin(pin_name) - self.add_layout_pin(text=wl_name, - layer=pin.layer, - offset=pin.ll().scale(0, 1), - width=self.width, - height=pin.height()) - # Replica bitlines - for (names, inst) in zip(self.rbl_bitline_names, self.replica_col_insts): - for (bl_name, pin_name) in zip(names, self.replica_columns[0].all_bitline_names): - pin = inst.get_pin(pin_name) - self.add_layout_pin(text=bl_name, - layer=pin.layer, - offset=pin.ll().scale(1, 0), - width=pin.width(), - height=self.height) + if len(self.rbls) > 0: + for (names, inst) in zip(self.rbl_bitline_names, self.replica_col_insts): + pin_names = self.replica_columns[self.rbls[0]].all_bitline_names + for (bl_name, pin_name) in zip(names, pin_names): + pin = inst.get_pin(pin_name) + self.add_layout_pin(text=bl_name, + layer=pin.layer, + offset=pin.ll().scale(1, 0), + width=pin.width(), + height=self.height) # vdd/gnd are only connected in the perimeter cells # replica column should only have a vdd/gnd in the dummy cell on top/bottom supply_insts = self.dummy_col_insts + self.dummy_row_insts - for pin_name in ["vdd", "gnd"]: + + for pin_name in self.supplies: for inst in supply_insts: pin_list = inst.get_pins(pin_name) for pin in pin_list: self.add_power_pin(name=pin_name, loc=pin.center(), - directions=("V", "V"), start_layer=pin.layer) for inst in self.replica_col_insts: - self.copy_layout_pin(inst, pin_name) + if inst: + self.copy_layout_pin(inst, pin_name) - def get_rbl_wordline_names(self, port=None): - """ - Return the ACTIVE WL for the given RBL port. - Inactive will be set to gnd. - """ - if port == None: - return self.all_rbl_wordline_names - else: - return self.rbl_wordline_names[port] - - def get_rbl_bitline_names(self, port=None): - """ Return the BL for the given RBL port """ - if port == None: - return self.all_rbl_bitline_names - else: - return self.rbl_bitline_names[port] - - def get_bitline_names(self, port=None): - """ Return the regular bitlines for the given port or all""" - if port == None: - return self.all_bitline_names - else: - return self.bitline_names[port] - - def get_all_bitline_names(self): - """ Return ALL the bitline names (including dummy and rbl) """ - temp = [] - temp.extend(self.get_dummy_bitline_names(0)) - if self.add_left_rbl > 0: - temp.extend(self.get_rbl_bitline_names(0)) - temp.extend(self.get_bitline_names()) - if self.add_right_rbl > 0: - temp.extend(self.get_rbl_bitline_names(self.add_left_rbl)) - temp.extend(self.get_dummy_bitline_names(1)) - return temp - - def get_wordline_names(self, port=None): - """ Return the regular wordline names """ - if port == None: - return self.all_wordline_names - else: - return self.wordline_names[port] - - def get_all_wordline_names(self, port=None): - """ Return all the wordline names """ - temp = [] - temp.extend(self.get_dummy_wordline_names(0)) - temp.extend(self.get_rbl_wordline_names(0)) - if port == None: - temp.extend(self.all_wordline_names) - else: - temp.extend(self.wordline_names[port]) - if len(self.all_ports) > 1: - temp.extend(self.get_rbl_wordline_names(1)) - temp.extend(self.get_dummy_wordline_names(1)) - return temp - - def get_dummy_wordline_names(self, port=None): - """ - Return the ACTIVE WL for the given dummy port. - """ - if port == None: - return self.all_dummy_row_wordline_names - else: - return self.dummy_row_wordline_names[port] - - def get_dummy_bitline_names(self, port=None): - """ Return the BL for the given dummy port """ - if port == None: - return self.all_dummy_col_bitline_names - else: - return self.dummy_col_bitline_names[port] - def analytical_power(self, corner, load): """Power of Bitcell array and bitline in nW.""" # Dynamic Power from Bitline @@ -572,6 +602,37 @@ class replica_bitcell_array(bitcell_base_array.bitcell_base_array): cell_power.leakage * self.column_size * self.row_size) return total_power + def route_unused_wordlines(self): + """ Connect the unused RBL and dummy wordlines to gnd """ + if not cell_properties.compare_ports(cell_properties.bitcell_array.use_custom_cell_arrangement): + # This grounds all the dummy row word lines + for inst in self.dummy_row_insts: + for wl_name in self.col_cap.get_wordline_names(): + self.ground_pin(inst, wl_name) + + # Ground the unused replica wordlines + for (names, inst) in zip(self.rbl_wordline_names, self.dummy_row_replica_insts): + for (wl_name, pin_name) in zip(names, self.dummy_row.get_wordline_names()): + if wl_name in self.gnd_wordline_names: + self.ground_pin(inst, pin_name) + + def ground_pin(self, inst, name): + pin = inst.get_pin(name) + pin_layer = pin.layer + + left_pin_loc = vector(self.dummy_col_insts[0].lx(), pin.cy()) + right_pin_loc = vector(self.dummy_col_insts[1].rx(), pin.cy()) + + # Place the pins a track outside of the array + left_loc = left_pin_loc - vector(self.unused_pitch, 0) + right_loc = right_pin_loc + vector(self.unused_pitch, 0) + self.add_power_pin("gnd", left_loc, directions=("H", "H")) + 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]) + def gen_bl_wire(self): if OPTS.netlist_only: height = 0 @@ -582,23 +643,28 @@ class replica_bitcell_array(bitcell_base_array.bitcell_base_array): bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell return bl_wire - def get_wordline_cin(self): - """Get the relative input capacitance from the wordline connections in all the bitcell""" - # A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns - bitcell_wl_cin = self.cell.get_wl_cin() - total_cin = bitcell_wl_cin * self.column_size - return total_cin - - def graph_exclude_bits(self, targ_row, targ_col): - """Excludes bits in column from being added to graph except target""" + def graph_exclude_bits(self, targ_row=None, targ_col=None): + """ + Excludes bits in column from being added to graph except target + """ self.bitcell_array.graph_exclude_bits(targ_row, targ_col) def graph_exclude_replica_col_bits(self): - """Exclude all replica/dummy cells in the replica columns except the replica bit.""" + """ + Exclude all replica/dummy cells in the replica columns except the replica bit. + """ - for port in range(self.left_rbl + self.right_rbl): + for port in self.left_rbl + self.right_rbl: self.replica_columns[port].exclude_all_but_replica() def get_cell_name(self, inst_name, row, col): - """Gets the spice name of the target bitcell.""" + """ + Gets the spice name of the target bitcell. + """ return self.bitcell_array.get_cell_name(inst_name + '.x' + self.bitcell_array_inst.name, row, col) + + def clear_exclude_bits(self): + """ + Clears the bit exclusions + """ + self.bitcell_array.init_graph_params() diff --git a/compiler/modules/replica_column.py b/compiler/modules/replica_column.py index a28aebf9..9988096c 100644 --- a/compiler/modules/replica_column.py +++ b/compiler/modules/replica_column.py @@ -4,14 +4,14 @@ # All rights reserved. # import debug -import design +from bitcell_base_array import bitcell_base_array from tech import cell_properties from sram_factory import factory from vector import vector from globals import OPTS -class replica_column(design.design): +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. @@ -21,14 +21,17 @@ class replica_column(design.design): """ def __init__(self, name, rows, rbl, replica_bit, column_offset=0): - super().__init__(name) + super().__init__(rows=sum(rbl) + rows + 2, 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 + 2 + if not cell_properties.compare_ports(cell_properties.bitcell_array.use_custom_cell_arrangement): + self.total_size = self.left_rbl + rows + self.right_rbl + 2 + else: + 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, @@ -57,37 +60,16 @@ class replica_column(design.design): self.DRC_LVS() def add_pins(self): - self.bitline_names = [[] for port in self.all_ports] - col = 0 - for port in self.all_ports: - self.bitline_names[port].append("bl_{0}_{1}".format(port, col)) - self.bitline_names[port].append("br_{0}_{1}".format(port, col)) - self.all_bitline_names = [x for sl in self.bitline_names for x in sl] - self.add_pin_list(self.all_bitline_names, "OUTPUT") - if not cell_properties.compare_ports(cell_properties.bitcell_array.use_custom_cell_arrangement): - self.wordline_names = [[] for port in self.all_ports] - for row in range(self.total_size): - for port in self.all_ports: - if not cell_properties.compare_ports(cell_properties.bitcell.split_wl): - self.wordline_names[port].append("wl_{0}_{1}".format(port, row)) - else: - self.wordline_names[port].append("wl0_{0}_{1}".format(port, row)) - self.wordline_names[port].append("wl1_{0}_{1}".format(port, row)) - self.all_wordline_names = [x for sl in zip(*self.wordline_names) for x in sl] - self.add_pin_list(self.all_wordline_names, "INPUT") + self.create_all_bitline_names() + #remove 2 wordlines to account for top/bot + if not cell_properties.bitcell.end_caps: + self.create_all_wordline_names() else: - self.wordline_names = [[] for port in self.all_ports] - for row in range(self.rows): - for port in self.all_ports: - if not cell_properties.compare_ports(cell_properties.bitcell.split_wl): - self.wordline_names[port].append("wl_{0}_{1}".format(port, row)) - else: - self.wordline_names[port].append("wl0_{0}_{1}".format(port, row)) - self.wordline_names[port].append("wl1_{0}_{1}".format(port, row)) + self.create_all_wordline_names(2) + self.add_pin_list(self.all_bitline_names, "OUTPUT") + self.add_pin_list(self.all_wordline_names, "INPUT") - self.all_wordline_names = [x for sl in zip(*self.wordline_names) for x in sl] - self.add_pin_list(self.all_wordline_names, "INPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") @@ -276,13 +258,13 @@ class replica_column(design.design): width=self.width, height=wl_pin.height()) - # # Supplies are only connected in the ends - # for (index, inst) in self.cell_inst.items(): - # 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) - # else: - # self.copy_layout_pin(inst, pin_name) + # Supplies are only connected in the ends + for (index, inst) in self.cell_inst.items(): + for pin_name in ["vpwr", "vgnd"]: + if inst in [self.cell_inst[0], self.cell_inst[self.total_size - 1]]: + self.copy_power_pins(inst, pin_name) + else: + self.copy_layout_pin(inst, pin_name) def get_bitline_names(self, port=None): if port == None: @@ -291,8 +273,10 @@ class replica_column(design.design): return self.bitline_names[port] def get_bitcell_pins(self, row, col): - """ Creates a list of connections in the bitcell, - indexed by column and row, for instance use in bitcell_array """ + """ + Creates a list of connections in the bitcell, + indexed by column and row, for instance use in bitcell_array + """ bitcell_pins = [] for port in self.all_ports: bitcell_pins.extend([x for x in self.get_bitline_names(port) if x.endswith("_{0}".format(col))]) @@ -303,18 +287,24 @@ class replica_column(design.design): return bitcell_pins def get_bitcell_pins_col_cap(self, row, col): - """ Creates a list of connections in the bitcell, - indexed by column and row, for instance use in bitcell_array """ + """ + Creates a list of connections in the bitcell, + indexed by column and row, for instance use in bitcell_array + """ bitcell_pins = [] 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.append("vdd") - bitcell_pins.append("gnd") + if len(self.edge_cell.get_pins("vdd")) > 0: + bitcell_pins.append("vdd") + if len(self.edge_cell.get_pins("gnd")) > 0: + bitcell_pins.append("gnd") return bitcell_pins def exclude_all_but_replica(self): - """Excludes all bits except the replica cell (self.replica_bit).""" + """ + Excludes all bits except the replica cell (self.replica_bit). + """ for row, cell in self.cell_inst.items(): if row != self.replica_bit: diff --git a/compiler/modules/sense_amp.py b/compiler/modules/sense_amp.py index 67703903..f1d5de92 100644 --- a/compiler/modules/sense_amp.py +++ b/compiler/modules/sense_amp.py @@ -82,13 +82,6 @@ class sense_amp(design.design): # Power in this module currently not defined. Returns 0 nW (leakage and dynamic). total_power = self.return_power() return total_power - - def get_en_cin(self): - """Get the relative capacitance of sense amp enable gate cin""" - pmos_cin = parameter["sa_en_pmos_size"] / drc("minwidth_tx") - nmos_cin = parameter["sa_en_nmos_size"] / drc("minwidth_tx") - # sen is connected to 2 pmos isolation TX and 1 nmos per sense amp. - return 2 * pmos_cin + nmos_cin def get_enable_name(self): """Returns name used for enable net""" diff --git a/compiler/modules/sense_amp_array.py b/compiler/modules/sense_amp_array.py index 20f6e06f..a46ccebf 100644 --- a/compiler/modules/sense_amp_array.py +++ b/compiler/modules/sense_amp_array.py @@ -6,12 +6,11 @@ # All rights reserved. # import design -from tech import drc from vector import vector from sram_factory import factory import debug from globals import OPTS -import logical_effort +from tech import cell_properties class sense_amp_array(design.design): @@ -20,7 +19,7 @@ class sense_amp_array(design.design): Dynamically generated sense amp array for all bitlines. """ - def __init__(self, name, word_size, words_per_row, num_spare_cols=None, column_offset=0): + def __init__(self, name, word_size, words_per_row, offsets=None, num_spare_cols=None, column_offset=0): super().__init__(name) debug.info(1, "Creating {0}".format(self.name)) @@ -29,6 +28,8 @@ class sense_amp_array(design.design): self.word_size = word_size self.words_per_row = words_per_row + self.num_cols = word_size * words_per_row + self.offsets = offsets if not num_spare_cols: self.num_spare_cols = 0 else: @@ -68,16 +69,15 @@ class sense_amp_array(design.design): self.create_sense_amp_array() def create_layout(self): - self.height = self.amp.height - - if self.bitcell.width > self.amp.width: - self.width = self.bitcell.width * (self.word_size * self.words_per_row + self.num_spare_cols) - else: - self.width = self.amp.width * (self.word_size * self.words_per_row + self.num_spare_cols) self.place_sense_amp_array() + + self.height = self.amp.height + self.width = self.local_insts[-1].rx() + self.add_layout_pins() self.route_rails() + self.add_boundary() self.DRC_LVS() @@ -111,29 +111,32 @@ class sense_amp_array(design.design): self.en_name, "vdd", "gnd"]) def place_sense_amp_array(self): - from tech import cell_properties + if self.bitcell.width > self.amp.width: + self.amp_spacing = self.bitcell.width + else: + self.amp_spacing = self.amp.width + + if not self.offsets: + self.offsets = [] + for i in range(self.num_cols + self.num_spare_cols): + self.offsets.append(i * self.bitcell.width) - for i in range(0, self.row_size, self.words_per_row): - index = int(i / self.words_per_row) - xoffset = i * self.bitcell.width - - if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2: + for i, xoffset in enumerate(self.offsets[0:self.num_cols:self.words_per_row]): + if cell_properties.bitcell.mirror.y and (i * self.words_per_row + self.column_offset) % 2: mirror = "MY" - xoffset = xoffset + self.amp.width + xoffset = xoffset + self.amp_spacing else: mirror = "" amp_position = vector(xoffset, 0) - self.local_insts[index].place(offset=amp_position, mirror=mirror) + self.local_insts[i].place(offset=amp_position, mirror=mirror) # place spare sense amps (will share the same enable as regular sense amps) - for i in range(0, self.num_spare_cols): + for i, xoffset in enumerate(self.offsets[self.num_cols:]): index = self.word_size + i - xoffset = ((self.word_size * self.words_per_row) + i) * self.bitcell.width - - if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2: + if cell_properties.bitcell.mirror.y and (index + self.column_offset) % 2: mirror = "MY" - xoffset = xoffset + self.amp.width + xoffset = xoffset + self.amp_width else: mirror = "" @@ -190,18 +193,3 @@ class sense_amp_array(design.design): self.add_via_stack_center(from_layer=en_pin.layer, to_layer=self.en_layer, offset=inst.get_pin(self.amp.en_name).center()) - - def input_load(self): - return self.amp.input_load() - - def get_en_cin(self): - """Get the relative capacitance of all the sense amp enable connections in the array""" - sense_amp_en_cin = self.amp.get_en_cin() - return sense_amp_en_cin * self.word_size - - def get_drain_cin(self): - """Get the relative capacitance of the drain of the PMOS isolation TX""" - from tech import parameter - # Bitcell drain load being used to estimate PMOS drain load - drain_load = logical_effort.convert_farad_to_relative_c(parameter['bitcell_drain_cap']) - return drain_load diff --git a/compiler/modules/wordline_buffer_array.py b/compiler/modules/wordline_buffer_array.py index 7a1bf8d1..45f3ef33 100644 --- a/compiler/modules/wordline_buffer_array.py +++ b/compiler/modules/wordline_buffer_array.py @@ -109,12 +109,13 @@ class wordline_buffer_array(design.design): def place_drivers(self): for row in range(self.rows): + # These are flipped since we always start with an RBL on the bottom if (row % 2): - y_offset = self.wl_driver.height * (row + 1) - inst_mirror = "MX" - else: y_offset = self.wl_driver.height * row inst_mirror = "R0" + else: + y_offset = self.wl_driver.height * (row + 1) + inst_mirror = "MX" offset = [0, y_offset] diff --git a/compiler/modules/wordline_driver_array.py b/compiler/modules/wordline_driver_array.py index f82938de..c334404e 100644 --- a/compiler/modules/wordline_driver_array.py +++ b/compiler/modules/wordline_driver_array.py @@ -44,7 +44,7 @@ class wordline_driver_array(design.design): self.place_drivers() self.route_layout() self.route_vdd_gnd() - self.offset_all_coordinates() + self.offset_x_coordinates() self.add_boundary() self.DRC_LVS() @@ -60,8 +60,10 @@ class wordline_driver_array(design.design): self.add_pin("gnd", "GROUND") def add_modules(self): + self.wl_driver = factory.create(module_type="wordline_driver", - size=self.cols) + cols=self.cols) + self.add_mod(self.wl_driver) def route_vdd_gnd(self): @@ -159,24 +161,3 @@ class wordline_driver_array(design.design): layer=self.route_layer, start=wl_offset, end=wl_offset - vector(self.m1_width, 0)) - - def determine_wordline_stage_efforts(self, external_cout, inp_is_rise=True): - """ - Follows the clk_buf to a wordline signal adding - each stages stage effort to a list. - """ - stage_effort_list = [] - - stage1 = self.wl_driver.get_stage_effort(external_cout, inp_is_rise) - stage_effort_list.append(stage1) - - return stage_effort_list - - def get_wl_en_cin(self): - """ - Get the relative capacitance of all - the enable connections in the bank - """ - # The enable is connected to a and2 for every row. - total_cin = self.wl_driver.get_cin() * self.rows - return total_cin diff --git a/compiler/modules/write_driver_array.py b/compiler/modules/write_driver_array.py index 665142ec..a2458f42 100644 --- a/compiler/modules/write_driver_array.py +++ b/compiler/modules/write_driver_array.py @@ -7,10 +7,12 @@ # import design import debug +import math from tech import drc from sram_factory import factory from vector import vector from globals import OPTS +from tech import cell_properties class write_driver_array(design.design): @@ -19,7 +21,7 @@ class write_driver_array(design.design): Dynamically generated write driver array of all bitlines. """ - def __init__(self, name, columns, word_size, num_spare_cols=None, write_size=None, column_offset=0): + def __init__(self, name, columns, word_size, offsets=None, num_spare_cols=None, write_size=None, column_offset=0): super().__init__(name) debug.info(1, "Creating {0}".format(self.name)) @@ -29,6 +31,7 @@ class write_driver_array(design.design): self.columns = columns self.word_size = word_size self.write_size = write_size + self.offsets = offsets self.column_offset = column_offset self.words_per_row = int(columns / word_size) if not num_spare_cols: @@ -37,7 +40,7 @@ class write_driver_array(design.design): self.num_spare_cols = num_spare_cols if self.write_size: - self.num_wmasks = int(self.word_size / self.write_size) + self.num_wmasks = int(math.ceil(self.word_size / self.write_size)) self.create_netlist() if not OPTS.netlist_only: @@ -66,17 +69,9 @@ class write_driver_array(design.design): def create_layout(self): - if self.bitcell.width > self.driver.width: - self.width = (self.columns + self.num_spare_cols) * self.bitcell.width - self.width_regular_cols = self.columns * self.bitcell.width - self.single_col_width = self.bitcell.width - else: - self.width = (self.columns + self.num_spare_cols) * self.driver.width - self.width_regular_cols = self.columns * self.driver.width - self.single_col_width = self.driver.width - self.height = self.driver.height - self.place_write_array() + self.width = self.driver_insts[-1].rx() + self.height = self.driver.height self.add_layout_pins() self.add_boundary() self.DRC_LVS() @@ -107,14 +102,14 @@ class write_driver_array(design.design): self.bitcell = factory.create(module_type="bitcell") def create_write_array(self): - self.driver_insts = {} + self.driver_insts = [] w = 0 windex=0 for i in range(0, self.columns, self.words_per_row): name = "write_driver{}".format(i) index = int(i / self.words_per_row) - self.driver_insts[index]=self.add_inst(name=name, - mod=self.driver) + self.driver_insts.append(self.add_inst(name=name, + mod=self.driver)) if self.write_size: self.connect_inst([self.data_name + "_{0}".format(index), @@ -146,8 +141,8 @@ class write_driver_array(design.design): else: offset = 1 name = "write_driver{}".format(self.columns + i) - self.driver_insts[index]=self.add_inst(name=name, - mod=self.driver) + self.driver_insts.append(self.add_inst(name=name, + mod=self.driver)) self.connect_inst([self.data_name + "_{0}".format(index), self.get_bl_name() + "_{0}".format(index), @@ -155,30 +150,31 @@ class write_driver_array(design.design): self.en_name + "_{0}".format(i + offset), "vdd", "gnd"]) def place_write_array(self): - from tech import cell_properties if self.bitcell.width > self.driver.width: self.driver_spacing = self.bitcell.width else: self.driver_spacing = self.driver.width - for i in range(0, self.columns, self.words_per_row): - index = int(i / self.words_per_row) - xoffset = i * self.driver_spacing - if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2: + if not self.offsets: + self.offsets = [] + for i in range(self.columns + self.num_spare_cols): + self.offsets.append(i * self.driver_spacing) + + for i, xoffset in enumerate(self.offsets[0:self.columns:self.words_per_row]): + if cell_properties.bitcell.mirror.y and (i * self.words_per_row + self.column_offset) % 2: mirror = "MY" xoffset = xoffset + self.driver.width else: mirror = "" base = vector(xoffset, 0) - self.driver_insts[index].place(offset=base, mirror=mirror) + self.driver_insts[i].place(offset=base, mirror=mirror) # place spare write drivers (if spare columns are specified) - for i in range(self.num_spare_cols): + for i, xoffset in enumerate(self.offsets[self.columns:]): index = self.word_size + i - xoffset = (self.columns + i) * self.driver_spacing - - if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2: + + if cell_properties.bitcell.mirror.y and (index + self.column_offset) % 2: mirror = "MY" xoffset = xoffset + self.driver.width else: @@ -243,12 +239,14 @@ class write_driver_array(design.design): elif self.num_spare_cols and not self.write_size: # shorten enable rail to accomodate those for spare write drivers - inst = self.driver_insts[0] - en_pin = inst.get_pin(inst.mod.en_name) + left_inst = self.driver_insts[0] + left_en_pin = left_inst.get_pin(inst.mod.en_name) + right_inst = self.driver_insts[-self.num_spare_cols - 1] + right_en_pin = right_inst.get_pin(inst.mod.en_name) self.add_layout_pin(text=self.en_name + "_{0}".format(0), layer="m1", - offset=en_pin.ll(), - width=self.width_regular_cols - self.words_per_row * en_pin.width()) + offset=left_en_pin.ll(), + width=right_en_pin.rx() - left_en_pin.lx()) # individual enables for every spare write driver for i in range(self.num_spare_cols): @@ -256,7 +254,7 @@ class write_driver_array(design.design): en_pin = inst.get_pin(inst.mod.en_name) self.add_layout_pin(text=self.en_name + "_{0}".format(i + 1), layer="m1", - offset=en_pin.lr() + vector(-drc("minwidth_m1"),0)) + offset=en_pin.lr() + vector(-drc("minwidth_m1"), 0)) else: inst = self.driver_insts[0] @@ -265,7 +263,3 @@ class write_driver_array(design.design): offset=inst.get_pin(inst.mod.en_name).ll().scale(0, 1), width=self.width) - def get_w_en_cin(self): - """Get the relative capacitance of all the enable connections in the bank""" - # The enable is connected to a nand2 for every row. - return self.driver.get_w_en_cin() * len(self.driver_insts) diff --git a/compiler/modules/write_mask_and_array.py b/compiler/modules/write_mask_and_array.py index 9b083512..94446755 100644 --- a/compiler/modules/write_mask_and_array.py +++ b/compiler/modules/write_mask_and_array.py @@ -7,6 +7,7 @@ # import design import debug +import math from sram_factory import factory from vector import vector from globals import OPTS @@ -18,7 +19,7 @@ class write_mask_and_array(design.design): The write mask AND array goes between the write driver array and the sense amp array. """ - def __init__(self, name, columns, word_size, write_size, column_offset=0): + def __init__(self, name, columns, word_size, write_size, offsets=None, column_offset=0): super().__init__(name) debug.info(1, "Creating {0}".format(self.name)) self.add_comment("columns: {0}".format(columns)) @@ -28,9 +29,10 @@ class write_mask_and_array(design.design): self.columns = columns self.word_size = word_size self.write_size = write_size + self.offsets = offsets self.column_offset = column_offset self.words_per_row = int(columns / word_size) - self.num_wmasks = int(word_size / write_size) + self.num_wmasks = int(math.ceil(word_size / write_size)) self.create_netlist() if not OPTS.netlist_only: @@ -90,12 +92,17 @@ class write_mask_and_array(design.design): debug.check(self.wmask_en_len >= self.and2.width, "Write mask AND is wider than the corresponding write drivers {0} vs {1}.".format(self.and2.width, self.wmask_en_len)) - - self.width = self.bitcell.width * self.columns + if not self.offsets: + self.offsets = [] + for i in range(self.columns): + self.offsets.append(i * self.driver_spacing) + + self.width = self.offsets[-1] + self.driver_spacing self.height = self.and2.height + write_bits = self.columns / self.num_wmasks for i in range(self.num_wmasks): - base = vector(i * self.wmask_en_len, 0) + base = vector(self.offsets[int(i * write_bits)], 0) self.and2_insts[i].place(base) def add_layout_pins(self): @@ -140,8 +147,3 @@ class write_mask_and_array(design.design): supply_pin_left = self.and2_insts[0].get_pin(supply) supply_pin_right = self.and2_insts[self.num_wmasks - 1].get_pin(supply) self.add_path(supply_pin_left.layer, [supply_pin_left.lc(), supply_pin_right.rc()]) - - def get_cin(self): - """Get the relative capacitance of all the input connections in the bank""" - # The enable is connected to an and2 for every row. - return self.and2.get_cin() * len(self.and2_insts) diff --git a/compiler/options.py b/compiler/options.py index d97ea300..2221f85f 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -9,6 +9,7 @@ import optparse import getpass import os + class options(optparse.Values): """ Class for holding all of the OpenRAM options. All @@ -30,6 +31,9 @@ class options(optparse.Values): num_r_ports = 0 num_w_ports = 0 + # By default, use local arrays with a max fanout of 16 + #local_array_size = 16 + # Write mask size, default will be overwritten with word_size if not user specified write_size = None @@ -93,6 +97,8 @@ class options(optparse.Values): trim_netlist = False # Run with extracted parasitics use_pex = False + # Output config with all options + output_extended_config = False ################### @@ -114,6 +120,9 @@ class options(optparse.Values): # For sky130, we need magic for filtering. magic_exe = None + # Number of threads to use + num_threads = 2 + # Should we print out the banner at startup print_banner = True @@ -137,7 +146,8 @@ class options(optparse.Values): bank_select = "bank_select" bitcell_array = "bitcell_array" bitcell = "bitcell" - column_mux_array = "single_level_column_mux_array" + buf_dec = "pbuf" + column_mux_array = "column_mux_array" control_logic = "control_logic" decoder = "hierarchical_decoder" delay_chain = "delay_chain" @@ -146,7 +156,7 @@ class options(optparse.Values): inv_dec = "pinv" nand2_dec = "pnand2" nand3_dec = "pnand3" - nand4_dec = "pnand4" # Not available right now + nand4_dec = "pnand4" precharge_array = "precharge_array" ptx = "ptx" replica_bitline = "replica_bitline" diff --git a/compiler/pgates/single_level_column_mux.py b/compiler/pgates/column_mux.py similarity index 92% rename from compiler/pgates/single_level_column_mux.py rename to compiler/pgates/column_mux.py index cd9be887..20616115 100644 --- a/compiler/pgates/single_level_column_mux.py +++ b/compiler/pgates/column_mux.py @@ -10,14 +10,13 @@ import debug from tech import drc, layer from vector import vector from sram_factory import factory -import logical_effort from globals import OPTS -class single_level_column_mux(pgate.pgate): +class column_mux(pgate.pgate): """ This module implements the columnmux bitline cell used in the design. - Creates a single columnmux cell with the given integer size relative + Creates a single column mux cell with the given integer size relative to minimum size. Default is 8x. Per Samira and Hodges-Jackson book: Column-mux transistors driven by the decoder must be sized for optimal speed @@ -241,18 +240,3 @@ class single_level_column_mux(pgate.pgate): offset=vector(0, 0), width=self.bitcell.width, height=self.height) - - def get_stage_effort(self, corner, slew, load): - """ - Returns relative delay that the column mux. - Difficult to convert to LE model. - """ - parasitic_delay = 1 - # This is not CMOS, so using this may be incorrect. - cin = 2 * self.tx_size - return logical_effort.logical_effort("column_mux", - self.tx_size, - cin, - load, - parasitic_delay, - False) diff --git a/compiler/pgates/pand2.py b/compiler/pgates/pand2.py index a46485d0..21241056 100644 --- a/compiler/pgates/pand2.py +++ b/compiler/pgates/pand2.py @@ -146,21 +146,4 @@ class pand2(pgate.pgate): offset=pin.center(), width=pin.width(), height=pin.height()) - - def get_stage_efforts(self, external_cout, inp_is_rise=False): - """Get the stage efforts of the A or B -> Z path""" - stage_effort_list = [] - stage1_cout = self.inv.get_cin() - stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise) - stage_effort_list.append(stage1) - last_stage_is_rise = stage1.is_rise - - stage2 = self.inv.get_stage_effort(external_cout, last_stage_is_rise) - stage_effort_list.append(stage2) - - return stage_effort_list - - def get_cin(self): - """Return the relative input capacitance of a single input""" - return self.nand.get_cin() - + \ No newline at end of file diff --git a/compiler/pgates/pand3.py b/compiler/pgates/pand3.py index 72a57f74..63d1cd0f 100644 --- a/compiler/pgates/pand3.py +++ b/compiler/pgates/pand3.py @@ -161,21 +161,4 @@ class pand3(pgate.pgate): slew=nand_delay.slew, load=load) return nand_delay + inv_delay - - def get_stage_efforts(self, external_cout, inp_is_rise=False): - """Get the stage efforts of the A or B -> Z path""" - stage_effort_list = [] - stage1_cout = self.inv.get_cin() - stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise) - stage_effort_list.append(stage1) - last_stage_is_rise = stage1.is_rise - - stage2 = self.inv.get_stage_effort(external_cout, last_stage_is_rise) - stage_effort_list.append(stage2) - - return stage_effort_list - - def get_cin(self): - """Return the relative input capacitance of a single input""" - return self.nand.get_cin() - + \ No newline at end of file diff --git a/compiler/pgates/pand4.py b/compiler/pgates/pand4.py new file mode 100644 index 00000000..021ccf6c --- /dev/null +++ b/compiler/pgates/pand4.py @@ -0,0 +1,165 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 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. +# +import debug +from vector import vector +import pgate +from sram_factory import factory + + +class pand4(pgate.pgate): + """ + This is a simple buffer used for driving loads. + """ + def __init__(self, name, size=1, height=None, vertical=False, add_wells=True): + debug.info(1, "Creating pand4 {}".format(name)) + self.add_comment("size: {}".format(size)) + + self.vertical = vertical + self.size = size + + # Creates the netlist and layout + super().__init__(name, height, add_wells) + + def create_netlist(self): + self.add_pins() + self.create_modules() + self.create_insts() + + def create_modules(self): + # Shield the cap, but have at least a stage effort of 4 + self.nand = factory.create(module_type="pnand4", + height=self.height, + add_wells=self.vertical) + + # Add the well tap to the inverter because when stacked + # vertically it is sometimes narrower + self.inv = factory.create(module_type="pdriver", + size_list=[self.size], + height=self.height, + add_wells=self.add_wells) + + self.add_mod(self.nand) + self.add_mod(self.inv) + + def create_layout(self): + if self.vertical: + self.height = 2 * self.nand.height + self.width = max(self.nand.width, self.inv.width) + else: + self.width = self.nand.width + self.inv.width + + self.place_insts() + self.add_wires() + self.add_layout_pins() + self.route_supply_rails() + self.add_boundary() + self.DRC_LVS() + + def add_pins(self): + self.add_pin("A", "INPUT") + self.add_pin("B", "INPUT") + self.add_pin("C", "INPUT") + self.add_pin("D", "INPUT") + self.add_pin("Z", "OUTPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") + + def create_insts(self): + self.nand_inst = self.add_inst(name="pand4_nand", + mod=self.nand) + self.connect_inst(["A", "B", "C", "D", "zb_int", "vdd", "gnd"]) + + self.inv_inst = self.add_inst(name="pand4_inv", + mod=self.inv) + self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) + + def place_insts(self): + # Add NAND to the right + self.nand_inst.place(offset=vector(0, 0)) + + if self.vertical: + # Add INV above + self.inv_inst.place(offset=vector(self.inv.width, + 2 * self.nand.height), + mirror="XY") + else: + # Add INV to the right + self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top, (middle), and bottom. """ + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, 0), + width=self.width) + + # Second gnd of the inverter gate + if self.vertical: + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, self.height), + width=self.width) + + if self.vertical: + # Shared between two gates + y_offset = 0.5 * self.height + else: + y_offset = self.height + self.add_layout_pin_rect_center(text="vdd", + layer=self.route_layer, + offset=vector(0.5 * self.width, y_offset), + width=self.width) + + def add_wires(self): + # nand Z to inv A + z1_pin = self.nand_inst.get_pin("Z") + a2_pin = self.inv_inst.get_pin("A") + if self.vertical: + route_layer = "m2" + self.add_via_stack_center(offset=z1_pin.center(), + from_layer=z1_pin.layer, + to_layer=route_layer) + self.add_zjog(route_layer, + z1_pin.uc(), + a2_pin.bc(), + "V") + self.add_via_stack_center(offset=a2_pin.center(), + from_layer=a2_pin.layer, + to_layer=route_layer) + else: + route_layer = self.route_layer + mid1_point = vector(z1_pin.cx(), a2_pin.cy()) + self.add_path(route_layer, + [z1_pin.center(), mid1_point, a2_pin.center()]) + + def add_layout_pins(self): + pin = self.inv_inst.get_pin("Z") + self.add_layout_pin_rect_center(text="Z", + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + for pin_name in ["A", "B", "C", "D"]: + pin = self.nand_inst.get_pin(pin_name) + self.add_layout_pin_rect_center(text=pin_name, + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + def analytical_delay(self, corner, slew, load=0.0): + """ Calculate the analytical delay of DFF-> INV -> INV """ + nand_delay = self.nand.analytical_delay(corner, + slew=slew, + load=self.inv.input_load()) + inv_delay = self.inv.analytical_delay(corner, + slew=nand_delay.slew, + load=load) + return nand_delay + inv_delay + diff --git a/compiler/pgates/pbuf.py b/compiler/pgates/pbuf.py index d82e2091..c2398d5f 100644 --- a/compiler/pgates/pbuf.py +++ b/compiler/pgates/pbuf.py @@ -96,21 +96,4 @@ class pbuf(pgate.pgate): offset=a_pin.center(), width=a_pin.width(), height=a_pin.height()) - - def get_stage_efforts(self, external_cout, inp_is_rise=False): - """Get the stage efforts of the A -> Z path""" - stage_effort_list = [] - stage1_cout = self.inv2.get_cin() - stage1 = self.inv1.get_stage_effort(stage1_cout, inp_is_rise) - stage_effort_list.append(stage1) - last_stage_is_rise = stage1.is_rise - - stage2 = self.inv2.get_stage_effort(external_cout, last_stage_is_rise) - stage_effort_list.append(stage2) - - return stage_effort_list - - def get_cin(self): - """Returns the relative capacitance of the input""" - input_cin = self.inv1.get_cin() - return input_cin + diff --git a/compiler/pgates/pdriver.py b/compiler/pgates/pdriver.py index 8916f0fa..e48f9f6c 100644 --- a/compiler/pgates/pdriver.py +++ b/compiler/pgates/pdriver.py @@ -168,24 +168,4 @@ class pdriver(pgate.pgate): def get_sizes(self): """ Return the relative sizes of the buffers """ return self.size_list - - def get_stage_efforts(self, external_cout, inp_is_rise=False): - """ Get the stage efforts of the A -> Z path """ - cout_list = [] - for prev_inv, inv in zip(self.inv_list, self.inv_list[1:]): - cout_list.append(inv.get_cin()) - - cout_list.append(external_cout) - - stage_effort_list = [] - last_inp_is_rise = inp_is_rise - for inv, cout in zip(self.inv_list, cout_list): - stage = inv.get_stage_effort(cout, last_inp_is_rise) - stage_effort_list.append(stage) - last_inp_is_rise = stage.is_rise - - return stage_effort_list - - def get_cin(self): - """ Returns the relative capacitance of the input """ - return self.inv_list[0].get_cin() + \ No newline at end of file diff --git a/compiler/pgates/pinvbuf.py b/compiler/pgates/pinvbuf.py index f746736c..497bd3df 100644 --- a/compiler/pgates/pinvbuf.py +++ b/compiler/pgates/pinvbuf.py @@ -184,36 +184,3 @@ class pinvbuf(pgate.pgate): self.add_layout_pin_rect_center(text="A", layer=a_pin.layer, offset=a_pin.center()) - - def determine_clk_buf_stage_efforts(self, external_cout, inp_is_rise=False): - """Get the stage efforts of the clk -> clk_buf path""" - stage_effort_list = [] - stage1_cout = self.inv1.get_cin() + self.inv2.get_cin() - stage1 = self.inv.get_stage_effort(stage1_cout, inp_is_rise) - stage_effort_list.append(stage1) - last_stage_is_rise = stage1.is_rise - - stage2 = self.inv2.get_stage_effort(external_cout, last_stage_is_rise) - stage_effort_list.append(stage2) - - return stage_effort_list - - def determine_clk_buf_bar_stage_efforts(self, external_cout, inp_is_rise=False): - """Get the stage efforts of the clk -> clk_buf path""" - - # After (almost) every stage, the direction of the signal inverts. - stage_effort_list = [] - stage1_cout = self.inv1.get_cin() + self.inv2.get_cin() - stage1 = self.inv.get_stage_effort(stage1_cout, inp_is_rise) - stage_effort_list.append(stage1) - last_stage_is_rise = stage_effort_list[-1].is_rise - - stage2_cout = self.inv2.get_cin() - stage2 = self.inv1.get_stage_effort(stage2_cout, last_stage_is_rise) - stage_effort_list.append(stage2) - last_stage_is_rise = stage_effort_list[-1].is_rise - - stage3 = self.inv2.get_stage_effort(external_cout, last_stage_is_rise) - stage_effort_list.append(stage3) - - return stage_effort_list diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index efcbe369..db5a1f28 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -133,7 +133,6 @@ class pnand3(pgate.pgate): # This is the extra space needed to ensure DRC rules # to the active contacts nmos = factory.create(module_type="ptx", tx_type="nmos") - extra_contact_space = max(-nmos.get_pin("D").by(), 0) def create_ptx(self): """ diff --git a/compiler/pgates/pnand4.py b/compiler/pgates/pnand4.py new file mode 100644 index 00000000..5a812799 --- /dev/null +++ b/compiler/pgates/pnand4.py @@ -0,0 +1,371 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 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. +# +import pgate +import debug +from tech import drc, parameter, spice +from vector import vector +import logical_effort +from sram_factory import factory +from globals import OPTS +import contact + + +class pnand4(pgate.pgate): + """ + This module generates gds of a parametrically sized 4-input nand. + This model use ptx to generate a 4-input nand within a cetrain height. + """ + def __init__(self, name, size=1, height=None, add_wells=True): + """ Creates a cell for a simple 3 input nand """ + + debug.info(2, + "creating pnand4 structure {0} with size of {1}".format(name, + size)) + self.add_comment("size: {}".format(size)) + + # We have trouble pitch matching a 3x sizes to the bitcell... + # If we relax this, we could size this better. + self.size = size + self.nmos_size = 2 * size + self.pmos_size = parameter["beta"] * size + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") + + # FIXME: Allow these to be sized + debug.check(size == 1, + "Size 1 pnand4 is only supported now.") + self.tx_mults = 1 + + if OPTS.tech_name == "sky130": + self.nmos_width = self.nearest_bin("nmos", self.nmos_width) + self.pmos_width = self.nearest_bin("pmos", self.pmos_width) + + # Creates the netlist and layout + super().__init__(name, height, add_wells) + + def add_pins(self): + """ Adds pins for spice netlist """ + pin_list = ["A", "B", "C", "D", "Z", "vdd", "gnd"] + dir_list = ["INPUT", "INPUT", "INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"] + self.add_pin_list(pin_list, dir_list) + + def create_netlist(self): + self.add_pins() + self.add_ptx() + self.create_ptx() + + def create_layout(self): + """ Calls all functions related to the generation of the layout """ + + self.setup_layout_constants() + self.place_ptx() + if self.add_wells: + self.add_well_contacts() + self.route_inputs() + self.route_output() + self.determine_width() + self.route_supply_rails() + self.connect_rails() + self.extend_wells() + self.add_boundary() + + def add_ptx(self): + """ Create the PMOS and NMOS transistors. """ + self.nmos_center = factory.create(module_type="ptx", + width=self.nmos_width, + mults=self.tx_mults, + tx_type="nmos", + add_source_contact="active", + add_drain_contact="active") + self.add_mod(self.nmos_center) + + self.nmos_right = factory.create(module_type="ptx", + width=self.nmos_width, + mults=self.tx_mults, + tx_type="nmos", + add_source_contact="active", + add_drain_contact=self.route_layer) + self.add_mod(self.nmos_right) + + self.nmos_left = factory.create(module_type="ptx", + width=self.nmos_width, + mults=self.tx_mults, + tx_type="nmos", + add_source_contact=self.route_layer, + add_drain_contact="active") + self.add_mod(self.nmos_left) + + self.pmos_left = factory.create(module_type="ptx", + width=self.pmos_width, + mults=self.tx_mults, + tx_type="pmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer) + self.add_mod(self.pmos_left) + + self.pmos_center = factory.create(module_type="ptx", + width=self.pmos_width, + mults=self.tx_mults, + tx_type="pmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer) + self.add_mod(self.pmos_center) + + self.pmos_right = factory.create(module_type="ptx", + width=self.pmos_width, + mults=self.tx_mults, + tx_type="pmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer) + self.add_mod(self.pmos_right) + + def setup_layout_constants(self): + """ Pre-compute some handy layout parameters. """ + + # Compute the overlap of the source and drain pins + self.ptx_offset = self.pmos_left.get_pin("D").center() - self.pmos_left.get_pin("S").center() + + # This is the extra space needed to ensure DRC rules + # to the active contacts + nmos = factory.create(module_type="ptx", tx_type="nmos") + + def create_ptx(self): + """ + Create the PMOS and NMOS in the netlist. + """ + + self.pmos1_inst = self.add_inst(name="pnand4_pmos1", + mod=self.pmos_left) + self.connect_inst(["vdd", "A", "Z", "vdd"]) + + self.pmos2_inst = self.add_inst(name="pnand4_pmos2", + mod=self.pmos_center) + self.connect_inst(["Z", "B", "vdd", "vdd"]) + + self.pmos3_inst = self.add_inst(name="pnand4_pmos3", + mod=self.pmos_center) + self.connect_inst(["Z", "C", "vdd", "vdd"]) + + self.pmos4_inst = self.add_inst(name="pnand4_pmos4", + mod=self.pmos_right) + self.connect_inst(["Z", "D", "vdd", "vdd"]) + + self.nmos1_inst = self.add_inst(name="pnand4_nmos1", + mod=self.nmos_left) + self.connect_inst(["Z", "D", "net1", "gnd"]) + + self.nmos2_inst = self.add_inst(name="pnand4_nmos2", + mod=self.nmos_center) + self.connect_inst(["net1", "C", "net2", "gnd"]) + + self.nmos3_inst = self.add_inst(name="pnand4_nmos3", + mod=self.nmos_center) + self.connect_inst(["net2", "B", "net3", "gnd"]) + + self.nmos4_inst = self.add_inst(name="pnand4_nmos4", + mod=self.nmos_right) + self.connect_inst(["net3", "A", "gnd", "gnd"]) + + def place_ptx(self): + """ + Place the PMOS and NMOS in the layout at the upper-most + and lowest position to provide maximum routing in channel + """ + + pmos1_pos = vector(self.pmos_left.active_offset.x, + self.height - self.pmos_left.active_height - self.top_bottom_space) + self.pmos1_inst.place(pmos1_pos) + + pmos2_pos = pmos1_pos + self.ptx_offset + self.pmos2_inst.place(pmos2_pos) + + pmos3_pos = pmos2_pos + self.ptx_offset + self.pmos3_inst.place(pmos3_pos) + + self.pmos4_pos = pmos3_pos + self.ptx_offset + self.pmos4_inst.place(self.pmos4_pos) + + nmos1_pos = vector(self.pmos_left.active_offset.x, + self.top_bottom_space) + self.nmos1_inst.place(nmos1_pos) + + nmos2_pos = nmos1_pos + self.ptx_offset + self.nmos2_inst.place(nmos2_pos) + + nmos3_pos = nmos2_pos + self.ptx_offset + self.nmos3_inst.place(nmos3_pos) + + self.nmos4_pos = nmos3_pos + self.ptx_offset + self.nmos4_inst.place(self.nmos4_pos) + + def add_well_contacts(self): + """ Add n/p well taps to the layout and connect to supplies """ + + self.add_nwell_contact(self.pmos_right, + self.pmos4_pos + vector(self.m1_pitch, 0)) + self.add_pwell_contact(self.nmos_right, + self.nmos4_pos + vector(self.m1_pitch, 0)) + + def connect_rails(self): + """ Connect the nmos and pmos to its respective power rails """ + + self.connect_pin_to_rail(self.nmos1_inst, "S", "gnd") + + self.connect_pin_to_rail(self.pmos1_inst, "S", "vdd") + + self.connect_pin_to_rail(self.pmos2_inst, "D", "vdd") + + self.connect_pin_to_rail(self.pmos4_inst, "D", "vdd") + + def route_inputs(self): + """ Route the A and B and C inputs """ + + # We can use this pitch because the contacts and overlap won't be adjacent + pmos_drain_bottom = self.pmos1_inst.get_pin("D").by() + self.output_yoffset = pmos_drain_bottom - 0.5 * self.route_layer_width - self.route_layer_space + + bottom_pin = self.nmos1_inst.get_pin("D") + # active contact metal to poly contact metal spacing + active_contact_to_poly_contact = bottom_pin.uy() + self.m1_space + 0.5 * contact.poly_contact.second_layer_height + # active diffusion to poly contact spacing + # doesn't use nmos uy because that is calculated using offset + poly height + active_top = self.nmos1_inst.by() + self.nmos1_inst.mod.active_height + active_to_poly_contact = active_top + self.poly_to_active + 0.5 * contact.poly_contact.first_layer_height + active_to_poly_contact2 = active_top + self.poly_contact_to_gate + 0.5 * self.route_layer_width + self.inputA_yoffset = max(active_contact_to_poly_contact, + active_to_poly_contact, + active_to_poly_contact2) + + apin = self.route_input_gate(self.pmos1_inst, + self.nmos1_inst, + self.inputA_yoffset, + "A", + position="left") + + self.inputB_yoffset = self.inputA_yoffset + self.m1_pitch + bpin = self.route_input_gate(self.pmos2_inst, + self.nmos2_inst, + self.inputB_yoffset, + "B", + position="center") + + self.inputC_yoffset = self.inputB_yoffset + self.m1_pitch + cpin = self.route_input_gate(self.pmos3_inst, + self.nmos3_inst, + self.inputC_yoffset, + "C", + position="right") + + self.inputD_yoffset = self.inputC_yoffset + self.m1_pitch + dpin = self.route_input_gate(self.pmos4_inst, + self.nmos4_inst, + self.inputD_yoffset, + "D", + position="right") + + if OPTS.tech_name == "sky130": + self.add_enclosure([apin, bpin, cpin, dpin], "npc", drc("npc_enclose_poly")) + + def route_output(self): + """ Route the Z output """ + + # PMOS1 drain + pmos1_pin = self.pmos1_inst.get_pin("D") + # PMOS3 drain + pmos3_pin = self.pmos3_inst.get_pin("D") + # NMOS3 drain + nmos4_pin = self.nmos4_inst.get_pin("D") + + out_offset = vector(nmos4_pin.cx() + self.route_layer_pitch, + self.output_yoffset) + + # Go up to metal2 for ease on all output pins + # self.add_via_center(layers=self.m1_stack, + # offset=pmos1_pin.center(), + # directions=("V", "V")) + # self.add_via_center(layers=self.m1_stack, + # offset=pmos3_pin.center(), + # directions=("V", "V")) + # self.add_via_center(layers=self.m1_stack, + # offset=nmos3_pin.center(), + # directions=("V", "V")) + + # # Route in the A input track (top track) + # mid_offset = vector(nmos3_pin.center().x, self.inputA_yoffset) + # self.add_path("m1", [pmos1_pin.center(), mid_offset, nmos3_pin.uc()]) + + # This extends the output to the edge of the cell + # self.add_via_center(layers=self.m1_stack, + # offset=mid_offset) + + top_left_pin_offset = pmos1_pin.center() + top_right_pin_offset = pmos3_pin.center() + bottom_pin_offset = nmos4_pin.center() + + # PMOS1 to output + self.add_path(self.route_layer, [top_left_pin_offset, + vector(top_left_pin_offset.x, out_offset.y), + out_offset]) + # PMOS4 to output + self.add_path(self.route_layer, [top_right_pin_offset, + vector(top_right_pin_offset.x, out_offset.y), + out_offset]) + # NMOS4 to output + mid2_offset = vector(out_offset.x, bottom_pin_offset.y) + self.add_path(self.route_layer, + [bottom_pin_offset, mid2_offset], + width=nmos4_pin.height()) + mid3_offset = vector(out_offset.x, nmos4_pin.by()) + self.add_path(self.route_layer, [mid3_offset, out_offset]) + + self.add_layout_pin_rect_center(text="Z", + layer=self.route_layer, + offset=out_offset) + + def analytical_power(self, corner, load): + """Returns dynamic and leakage power. Results in nW""" + c_eff = self.calculate_effective_capacitance(load) + freq = spice["default_event_frequency"] + power_dyn = self.calc_dynamic_power(corner, c_eff, freq) + power_leak = spice["nand4_leakage"] + + total_power = self.return_power(power_dyn, power_leak) + return total_power + + def calculate_effective_capacitance(self, load): + """Computes effective capacitance. Results in fF""" + c_load = load + # In fF + c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"]) + transition_prob = 0.1094 + return transition_prob * (c_load + c_para) + + def input_load(self): + """Return the relative input capacitance of a single input""" + return self.nmos_size + self.pmos_size + + def get_stage_effort(self, cout, inp_is_rise=True): + """ + Returns an object representing the parameters for delay in tau units. + Optional is_rise refers to the input direction rise/fall. + Input inverted by this stage. + """ + parasitic_delay = 3 + return logical_effort.logical_effort(self.name, + self.size, + self.input_load(), + cout, + parasitic_delay, + not inp_is_rise) + + 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) diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index aefdbb86..4ae48167 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -300,11 +300,4 @@ class precharge(design.design): self.add_path(self.bitline_layer, [left_pos, right_pos], width=pmos_pin.height()) - - def get_en_cin(self): - """Get the relative capacitance of the enable in the precharge cell""" - # The enable connect to three pmos gates - # They all use the same size pmos. - pmos_cin = self.pmos.get_cin() - return 3 * pmos_cin - + \ No newline at end of file diff --git a/compiler/pgates/wordline_driver.py b/compiler/pgates/wordline_driver.py index a8ca76f8..6abf7b20 100644 --- a/compiler/pgates/wordline_driver.py +++ b/compiler/pgates/wordline_driver.py @@ -18,9 +18,9 @@ class wordline_driver(design.design): This is an AND (or NAND) with configurable drive strength to drive the wordlines. It is matched to the bitcell height. """ - def __init__(self, name, size=1, height=None): + def __init__(self, name, cols, height=None): debug.info(1, "Creating wordline_driver {}".format(name)) - self.add_comment("size: {}".format(size)) + self.add_comment("cols: {}".format(cols)) super().__init__(name) if height is None: @@ -28,7 +28,7 @@ class wordline_driver(design.design): self.height = b.height else: self.height = height - self.size = size + self.cols = cols self.create_netlist() if not OPTS.netlist_only: @@ -42,10 +42,25 @@ class wordline_driver(design.design): def create_modules(self): self.nand = factory.create(module_type="nand2_dec", height=self.height) - - self.driver = factory.create(module_type="inv_dec", - size=self.size, - height=self.nand.height) + + try: + local_array_size = OPTS.local_array_size + driver_size = max(int(self.cols / local_array_size), 1) + except AttributeError: + local_array_size = 0 + # Defautl to FO4 + driver_size = max(int(self.cols / 4), 1) + + # The polarity must be switched if we have a hierarchical wordline + # to compensate for the local array inverters + if local_array_size > 0: + self.driver = factory.create(module_type="buf_dec", + size=driver_size, + height=self.nand.height) + else: + self.driver = factory.create(module_type="inv_dec", + size=driver_size, + height=self.nand.height) self.add_mod(self.nand) self.add_mod(self.driver) diff --git a/compiler/sram/sram.py b/compiler/sram/sram.py index 8cf926c6..63d971f3 100644 --- a/compiler/sram/sram.py +++ b/compiler/sram/sram.py @@ -63,17 +63,22 @@ class sram(): def verilog_write(self, name): self.s.verilog_write(name) + def extended_config_write(self, name): + """Dump config file with all options. + Include defaults and anything changed by input config.""" + f = open(name, "w") + var_dict = dict((name, getattr(OPTS, name)) for name in dir(OPTS) if not name.startswith('__') and not callable(getattr(OPTS, name))) + for var_name, var_value in var_dict.items(): + if isinstance(var_value, str): + f.write(str(var_name) + " = " + "\"" + str(var_value) + "\"\n") + else: + f.write(str(var_name) + " = " + str(var_value)+ "\n") + f.close() + def save(self): """ Save all the output files while reporting time to do it as well. """ if not OPTS.netlist_only: - # Create a LEF physical model - start_time = datetime.datetime.now() - lefname = OPTS.output_path + self.s.name + ".lef" - debug.print_raw("LEF: Writing to {0}".format(lefname)) - self.lef_write(lefname) - print_time("LEF", datetime.datetime.now(), start_time) - # Write the layout start_time = datetime.datetime.now() gdsname = OPTS.output_path + self.s.name + ".gds" @@ -81,6 +86,13 @@ class sram(): self.gds_write(gdsname) print_time("GDS", datetime.datetime.now(), start_time) + # Create a LEF physical model + start_time = datetime.datetime.now() + lefname = OPTS.output_path + self.s.name + ".lef" + debug.print_raw("LEF: Writing to {0}".format(lefname)) + self.lef_write(lefname) + print_time("LEF", datetime.datetime.now(), start_time) + # Save the spice file start_time = datetime.datetime.now() spname = OPTS.output_path + self.s.name + ".sp" @@ -137,3 +149,11 @@ class sram(): debug.print_raw("Verilog: Writing to {0}".format(vname)) self.verilog_write(vname) print_time("Verilog", datetime.datetime.now(), start_time) + + # Write out options if specified + if OPTS.output_extended_config: + start_time = datetime.datetime.now() + oname = OPTS.output_path + OPTS.output_name + "_extended.py" + debug.print_raw("Extended Config: Writing to {0}".format(oname)) + self.extended_config_write(oname) + print_time("Extended Config", datetime.datetime.now(), start_time) diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index 3f254b73..2dfd8096 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -414,7 +414,7 @@ class sram_1bank(sram_base): layer_stack=self.m1_stack, parent=self) if add_routes: - self.add_inst("hc", cr) + self.add_inst(cr.name, cr) self.connect_inst([]) else: self.col_addr_bus_size[port] = cr.height @@ -470,7 +470,7 @@ class sram_1bank(sram_base): layer_stack=layer_stack, parent=self) if add_routes: - self.add_inst("hc", cr) + self.add_inst(cr.name, cr) self.connect_inst([]) else: self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap @@ -482,7 +482,7 @@ class sram_1bank(sram_base): layer_stack=layer_stack, parent=self) if add_routes: - self.add_inst("hc", cr) + self.add_inst(cr.name, cr) self.connect_inst([]) else: self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap @@ -542,7 +542,9 @@ class sram_1bank(sram_base): [data_dff_clk_pos, mid_pos, clk_steiner_pos]) def route_control_logic(self): - """ Route the control logic pins that are not inputs """ + """ + Route the control logic pins that are not inputs + """ for port in self.all_ports: for signal in self.control_logic_outputs[port]: @@ -567,7 +569,9 @@ class sram_1bank(sram_base): offset=dest_pin.center()) def route_row_addr_dff(self): - """ Connect the output of the row flops to the bank pins """ + """ + Connect the output of the row flops to the bank pins + """ for port in self.all_ports: for bit in range(self.row_addr_size): flop_name = "dout_{}".format(bit) @@ -600,7 +604,9 @@ class sram_1bank(sram_base): offset=pin.center()) def graph_exclude_data_dff(self): - """Removes data dff and wmask dff (if applicable) from search graph. """ + """ + Removes data dff and wmask dff (if applicable) from search graph. + """ # Data dffs and wmask dffs are only for writing so are not useful for evaluating read delay. for inst in self.data_dff_insts: self.graph_inst_exclude.add(inst) @@ -612,7 +618,9 @@ class sram_1bank(sram_base): self.graph_inst_exclude.add(inst) def graph_exclude_addr_dff(self): - """Removes data dff from search graph. """ + """ + Removes data dff from search graph. + """ # Address is considered not part of the critical path, subjectively removed for inst in self.row_addr_dff_insts: self.graph_inst_exclude.add(inst) @@ -622,31 +630,21 @@ class sram_1bank(sram_base): self.graph_inst_exclude.add(inst) def graph_exclude_ctrl_dffs(self): - """Exclude dffs for CSB, WEB, etc from graph""" + """ + Exclude dffs for CSB, WEB, etc from graph + """ # Insts located in control logic, exclusion function called here for inst in self.control_logic_insts: inst.mod.graph_exclude_dffs() - - def get_sen_name(self, sram_name, port=0): - """Returns the s_en spice name.""" - # Naming scheme is hardcoded using this function, should be built into the - # graph in someway. - sen_name = "s_en{}".format(port) - control_conns = self.get_conns(self.control_logic_insts[port]) - # Sanity checks - if sen_name not in control_conns: - debug.error("Signal={} not contained in control logic connections={}".format(sen_name, - control_conns)) - if sen_name in self.pins: - debug.error("Internal signal={} contained in port list. Name defined by the parent.".format(sen_name)) - return "X{}.{}".format(sram_name, sen_name) def get_cell_name(self, inst_name, row, col): - """Gets the spice name of the target bitcell.""" + """ + Gets the spice name of the target bitcell. + """ # Sanity check in case it was forgotten - if inst_name.find('x') != 0: - inst_name = 'x'+inst_name - return self.bank_inst.mod.get_cell_name(inst_name+'.x'+self.bank_inst.name, row, col) + if inst_name.find("x") != 0: + inst_name = "x" + inst_name + return self.bank_inst.mod.get_cell_name(inst_name + ".x" + self.bank_inst.name, row, col) def get_bank_num(self, inst_name, row, col): - return 0; + return 0 diff --git a/compiler/sram/sram_base.py b/compiler/sram/sram_base.py index ffaca694..351619bf 100644 --- a/compiler/sram/sram_base.py +++ b/compiler/sram/sram_base.py @@ -7,7 +7,7 @@ # import datetime import debug -from math import log +from math import log, ceil from importlib import reload from vector import vector from globals import OPTS, print_time @@ -15,9 +15,6 @@ from design import design from verilog import verilog from lef import lef from sram_factory import factory -from tech import drc -import numpy as np -import logical_effort class sram_base(design, verilog, lef): @@ -36,16 +33,13 @@ class sram_base(design, verilog, lef): self.bank_insts = [] if self.write_size: - self.num_wmasks = int(self.word_size / self.write_size) + self.num_wmasks = int(ceil(self.word_size / self.write_size)) else: self.num_wmasks = 0 if not self.num_spare_cols: self.num_spare_cols = 0 - # For logical effort delay calculations. - self.all_mods_except_control_done = False - def add_pins(self): """ Add pins for entire SRAM. """ @@ -87,18 +81,20 @@ class sram_base(design, verilog, lef): for bit in range(self.word_size + self.num_spare_cols): self.add_pin("dout{0}[{1}]".format(port, bit), "OUTPUT") - self.add_pin("vdd","POWER") - self.add_pin("gnd","GROUND") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def add_global_pex_labels(self): """ Add pex labels at the sram level for spice analysis """ + + # add pex labels for bitcells for bank_num in range(len(self.bank_insts)): bank = self.bank_insts[bank_num] - pex_data = bank.reverse_transformation_bitcell(bank.mod.bitcell.name) + pex_data = bank.reverse_transformation_bitcell(self.bitcell.name) bank_offset = pex_data[0] # offset bank relative to sram Q_offset = pex_data[1] # offset of storage relative to bank @@ -112,46 +108,61 @@ class sram_base(design, verilog, lef): br = [] storage_layer_name = "m1" - bitline_layer_name = "m2" + bitline_layer_name = self.bitcell.get_pin("bl").layer for cell in range(len(bank_offset)): - Q = [bank_offset[cell][0] + Q_offset[cell][0], bank_offset[cell][1] + Q_offset[cell][1]] - Q_bar = [bank_offset[cell][0] + Q_bar_offset[cell][0], bank_offset[cell][1] + Q_bar_offset[cell][1]] + Q = [bank_offset[cell][0] + Q_offset[cell][0], + bank_offset[cell][1] + Q_offset[cell][1]] + Q_bar = [bank_offset[cell][0] + Q_bar_offset[cell][0], + bank_offset[cell][1] + Q_bar_offset[cell][1]] OPTS.words_per_row = self.words_per_row - self.add_layout_pin_rect_center("bitcell_Q_b{}_r{}_c{}".format(bank_num, int(cell % (OPTS.num_words / self.words_per_row)), int(cell / (OPTS.num_words))) , storage_layer_name, Q) - self.add_layout_pin_rect_center("bitcell_Q_bar_b{}_r{}_c{}".format(bank_num, int(cell % (OPTS.num_words / self.words_per_row)), int(cell / (OPTS.num_words))), storage_layer_name, Q_bar) + row = int(cell % (OPTS.num_words / self.words_per_row)) + col = int(cell / (OPTS.num_words)) + self.add_layout_pin_rect_center("bitcell_Q_b{}_r{}_c{}".format(bank_num, + row, + col), + storage_layer_name, + Q) + self.add_layout_pin_rect_center("bitcell_Q_bar_b{}_r{}_c{}".format(bank_num, + row, + col), + storage_layer_name, + Q_bar) for cell in range(len(bl_offsets)): col = bl_meta[cell][0][2] for bitline in range(len(bl_offsets[cell])): - bitline_location = [float(bank_offset[cell][0]) + bl_offsets[cell][bitline][0], float(bank_offset[cell][1]) + bl_offsets[cell][bitline][1]] + bitline_location = [float(bank_offset[cell][0]) + bl_offsets[cell][bitline][0], + float(bank_offset[cell][1]) + bl_offsets[cell][bitline][1]] bl.append([bitline_location, bl_meta[cell][bitline][3], col]) for cell in range(len(br_offsets)): col = br_meta[cell][0][2] for bitline in range(len(br_offsets[cell])): - bitline_location = [float(bank_offset[cell][0]) + br_offsets[cell][bitline][0], float(bank_offset[cell][1]) + br_offsets[cell][bitline][1]] - br.append([bitline_location, br_meta[cell][bitline][3], col]) + bitline_location = [float(bank_offset[cell][0]) + br_offsets[cell][bitline][0], + float(bank_offset[cell][1]) + br_offsets[cell][bitline][1]] + br.append([bitline_location, br_meta[cell][bitline][3], col]) for i in range(len(bl)): - self.add_layout_pin_rect_center("bl{0}_{1}".format(bl[i][1], bl[i][2]), bitline_layer_name, bl[i][0]) + self.add_layout_pin_rect_center("bl{0}_{1}".format(bl[i][1], bl[i][2]), + bitline_layer_name, bl[i][0]) for i in range(len(br)): - self.add_layout_pin_rect_center("br{0}_{1}".format(br[i][1], br[i][2]), bitline_layer_name, br[i][0]) + self.add_layout_pin_rect_center("br{0}_{1}".format(br[i][1], br[i][2]), + bitline_layer_name, br[i][0]) # add pex labels for control logic - for i in range (len(self.control_logic_insts)): + for i in range(len(self.control_logic_insts)): instance = self.control_logic_insts[i] control_logic_offset = instance.offset for output in instance.mod.output_list: pin = instance.mod.get_pin(output) - pin.transform([0,0], instance.mirror, instance.rotate) - offset = [control_logic_offset[0] + pin.center()[0], control_logic_offset[1] + pin.center()[1]] - self.add_layout_pin_rect_center("{0}{1}".format(pin.name,i), storage_layer_name, offset) - - - - + pin.transform([0, 0], instance.mirror, instance.rotate) + offset = [control_logic_offset[0] + pin.center()[0], + control_logic_offset[1] + pin.center()[1]] + self.add_layout_pin_rect_center("{0}{1}".format(pin.name, i), + storage_layer_name, + offset) def create_netlist(self): """ Netlist creation """ @@ -370,10 +381,6 @@ class sram_base(design, verilog, lef): self.bank_count = 0 - # The control logic can resize itself based on the other modules. - # Requires all other modules added before control logic. - self.all_mods_except_control_done = True - c = reload(__import__(OPTS.control_logic)) self.mod_control_logic = getattr(c, OPTS.control_logic) @@ -619,6 +626,7 @@ class sram_base(design, verilog, lef): sp.write("* Column mux: {}:1\n".format(self.words_per_row)) sp.write("**************************************************\n") # This causes unit test mismatch + # sp.write("* Created: {0}\n".format(datetime.datetime.now())) # sp.write("* User: {0}\n".format(getpass.getuser())) # sp.write(".global {0} {1}\n".format(spice["vdd_name"], @@ -630,50 +638,15 @@ class sram_base(design, verilog, lef): def lvs_write(self, sp_name): self.sp_write(sp_name, lvs_netlist=True) - - def get_wordline_stage_efforts(self, inp_is_rise=True): - """Get the all the stage efforts for each stage in the path from clk_buf to a wordline""" - stage_effort_list = [] - # Clk_buf originates from the control logic so only the bank is related to the wordline path - # No loading on the wordline other than in the bank. - external_wordline_cout = 0 - stage_effort_list += self.bank.determine_wordline_stage_efforts(external_wordline_cout, inp_is_rise) + def graph_exclude_bits(self, targ_row, targ_col): + """ + Excludes bits in column from being added to graph except target + """ + self.bank.graph_exclude_bits(targ_row, targ_col) - return stage_effort_list - - def get_wl_en_cin(self): - """Gets the capacitive load the of clock (clk_buf) for the sram""" - # Only the wordline drivers within the bank use this signal - return self.bank.get_wl_en_cin() - - def get_w_en_cin(self): - """Gets the capacitive load the of write enable (w_en) for the sram""" - # Only the write drivers within the bank use this signal - return self.bank.get_w_en_cin() - - def get_p_en_bar_cin(self): - """Gets the capacitive load the of precharge enable (p_en_bar) for the sram""" - # Only the precharges within the bank use this signal - return self.bank.get_p_en_bar_cin() - - def get_clk_bar_cin(self): - """Gets the capacitive load the of clock (clk_buf_bar) for the sram""" - # As clk_buf_bar is an output of the control logic. The cap for that module is not determined here. - # Only the precharge cells use this signal (other than the control logic) - return self.bank.get_clk_bar_cin() - - def get_sen_cin(self): - """Gets the capacitive load the of sense amp enable for the sram""" - # Only the sense_amps use this signal (other than the control logic) - return self.bank.get_sen_cin() - - def get_dff_clk_buf_cin(self): - """Get the relative capacitance of the clk_buf signal. - Does not get the control logic loading but everything else""" - total_cin = 0 - total_cin += self.row_addr_dff.get_clk_cin() - total_cin += self.data_dff.get_clk_cin() - if self.col_addr_size > 0: - total_cin += self.col_addr_dff.get_clk_cin() - return total_cin + def clear_exclude_bits(self): + """ + Clears the bit exclusions + """ + self.bank.clear_exclude_bits() diff --git a/compiler/sram/sram_config.py b/compiler/sram/sram_config.py index fa70d730..573e7514 100644 --- a/compiler/sram/sram_config.py +++ b/compiler/sram/sram_config.py @@ -6,15 +6,15 @@ # All rights reserved. # import debug -from math import log,sqrt,ceil -from importlib import reload +from math import log, sqrt, ceil from globals import OPTS from sram_factory import factory + class sram_config: """ This is a structure that is used to hold the SRAM configuration options. """ - def __init__(self, word_size, num_words, write_size = None, num_banks=1, words_per_row=None, num_spare_rows=0, num_spare_cols=0): + def __init__(self, word_size, num_words, write_size=None, num_banks=1, words_per_row=None, num_spare_rows=0, num_spare_cols=0): self.word_size = word_size self.num_words = num_words self.write_size = write_size @@ -25,7 +25,7 @@ class sram_config: # This will get over-written when we determine the organization self.words_per_row = words_per_row - self.compute_sizes() + self.compute_sizes() def set_local_config(self, module): """ Copy all of the member variables to the given module for convenience """ @@ -34,31 +34,31 @@ class sram_config: # Copy all the variables to the local module for member in members: - setattr(module,member,getattr(self,member)) + setattr(module, member, getattr(self, member)) def compute_sizes(self): """ Computes the organization of the memory using bitcell size by trying to make it square.""" bitcell = factory.create(module_type="bitcell") - - debug.check(self.num_banks in [1,2,4], "Valid number of banks are 1 , 2 and 4.") + debug.check(self.num_banks in [1, 2, 4], + "Valid number of banks are 1 , 2 and 4.") - self.num_words_per_bank = self.num_words/self.num_banks - self.num_bits_per_bank = self.word_size*self.num_words_per_bank + self.num_words_per_bank = self.num_words / self.num_banks + self.num_bits_per_bank = self.word_size * self.num_words_per_bank # If this was hard coded, don't dynamically compute it! if not self.words_per_row: # Compute the area of the bitcells and estimate a square bank (excluding auxiliary circuitry) - self.bank_area = bitcell.width*bitcell.height*self.num_bits_per_bank + self.bank_area = bitcell.width * bitcell.height * self.num_bits_per_bank self.bank_side_length = sqrt(self.bank_area) # Estimate the words per row given the height of the bitcell and the square side length - self.tentative_num_cols = int(self.bank_side_length/bitcell.width) + self.tentative_num_cols = int(self.bank_side_length / bitcell.width) self.words_per_row = self.estimate_words_per_row(self.tentative_num_cols, self.word_size) # Estimate the number of rows given the tentative words per row - self.tentative_num_rows = self.num_bits_per_bank / (self.words_per_row*self.word_size) + self.tentative_num_rows = self.num_bits_per_bank / (self.words_per_row * self.word_size) self.words_per_row = self.amend_words_per_row(self.tentative_num_rows, self.words_per_row) self.recompute_sizes() @@ -70,57 +70,57 @@ class sram_config: SRAM for testing. """ - debug.info(1,"Recomputing with words per row: {}".format(self.words_per_row)) + debug.info(1, "Recomputing with words per row: {}".format(self.words_per_row)) # If the banks changed - self.num_words_per_bank = self.num_words/self.num_banks - self.num_bits_per_bank = self.word_size*self.num_words_per_bank + self.num_words_per_bank = self.num_words / self.num_banks + self.num_bits_per_bank = self.word_size * self.num_words_per_bank # Fix the number of columns and rows - self.num_cols = int(self.words_per_row*self.word_size) - self.num_rows_temp = int(self.num_words_per_bank/self.words_per_row) + self.num_cols = int(self.words_per_row * self.word_size) + self.num_rows_temp = int(self.num_words_per_bank / self.words_per_row) self.num_rows = self.num_rows_temp + self.num_spare_rows - debug.info(1,"Rows: {} Cols: {}".format(self.num_rows_temp,self.num_cols)) + debug.info(1, "Rows: {} Cols: {}".format(self.num_rows_temp, self.num_cols)) # Compute the address and bank sizes self.row_addr_size = ceil(log(self.num_rows, 2)) self.col_addr_size = int(log(self.words_per_row, 2)) self.bank_addr_size = self.col_addr_size + self.row_addr_size self.addr_size = self.bank_addr_size + int(log(self.num_banks, 2)) - debug.info(1,"Row addr size: {}".format(self.row_addr_size) + debug.info(1, "Row addr size: {}".format(self.row_addr_size) + " Col addr size: {}".format(self.col_addr_size) + " Bank addr size: {}".format(self.bank_addr_size)) - - def estimate_words_per_row(self,tentative_num_cols, word_size): + def estimate_words_per_row(self, tentative_num_cols, word_size): """ This provides a heuristic rounded estimate for the number of words per row. """ + tentative_column_ways = tentative_num_cols / word_size + column_mux_sizes = [1, 2, 4, 8, 16] + # If we are double, we may want a larger column mux + if tentative_column_ways > 2 * column_mux_sizes[-1]: + debug.warning("Extremely large number of columns for 16-way maximum column mux.") - if tentative_num_cols < 1.5*word_size: - return 1 - elif tentative_num_cols < 3*word_size: - return 2 - elif tentative_num_cols < 6*word_size: - return 4 - else: - if tentative_num_cols > 16*word_size: - debug.warning("Reaching column mux size limit. Consider increasing above 8-way.") - return 8 + closest_way = min(column_mux_sizes, key=lambda x: abs(x - tentative_column_ways)) - def amend_words_per_row(self,tentative_num_rows, words_per_row): + return closest_way + + def amend_words_per_row(self, tentative_num_rows, words_per_row): """ This picks the number of words per row more accurately by limiting it to a minimum and maximum. """ # Recompute the words per row given a hard max if(not OPTS.is_unit_test and tentative_num_rows > 512): - debug.check(tentative_num_rows*words_per_row <= 2048, "Number of words exceeds 2048") - return int(words_per_row*tentative_num_rows/512) + debug.check(tentative_num_rows * words_per_row <= 4096, + "Number of words exceeds 2048") + return int(words_per_row * tentative_num_rows / 512) + # Recompute the words per row given a hard min - if(not OPTS.is_unit_test and tentative_num_rows < 16): - debug.check(tentative_num_rows*words_per_row >= 16, "Minimum number of rows is 16, but given {0}".format(tentative_num_rows)) - return int(words_per_row*tentative_num_rows/16) + if (not OPTS.is_unit_test and tentative_num_rows < 16): + debug.check(tentative_num_rows * words_per_row >= 16, + "Minimum number of rows is 16, but given {0}".format(tentative_num_rows)) + return int(words_per_row * tentative_num_rows / 16) return words_per_row diff --git a/compiler/tests/04_and4_dec_test.py b/compiler/tests/04_and4_dec_test.py index ffd7788a..aa163160 100755 --- a/compiler/tests/04_and4_dec_test.py +++ b/compiler/tests/04_and4_dec_test.py @@ -8,7 +8,7 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS @@ -16,7 +16,7 @@ from sram_factory import factory import debug -@unittest.skip("SKIPPING 04_and4_dec_test") +# @unittest.skip("SKIPPING 04_and4_dec_test") class and4_dec_test(openram_test): def runTest(self): diff --git a/compiler/tests/04_single_level_column_mux_1rw_1r_test.py b/compiler/tests/04_column_mux_1rw_1r_test.py similarity index 79% rename from compiler/tests/04_single_level_column_mux_1rw_1r_test.py rename to compiler/tests/04_column_mux_1rw_1r_test.py index a7e79e9b..7825e081 100755 --- a/compiler/tests/04_single_level_column_mux_1rw_1r_test.py +++ b/compiler/tests/04_column_mux_1rw_1r_test.py @@ -8,7 +8,7 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS @@ -16,7 +16,7 @@ from sram_factory import factory import debug -class single_level_column_mux_1rw_1r_test(openram_test): +class column_mux_1rw_1r_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) @@ -28,11 +28,11 @@ class single_level_column_mux_1rw_1r_test(openram_test): globals.setup_bitcell() debug.info(2, "Checking column mux port 0") - tx = factory.create(module_type="single_level_column_mux", tx_size=8, bitcell_bl="bl0", bitcell_br="br0") + tx = factory.create(module_type="column_mux", tx_size=8, bitcell_bl="bl0", bitcell_br="br0") self.local_check(tx) debug.info(2, "Checking column mux port 1") - tx = factory.create(module_type="single_level_column_mux", tx_size=8, bitcell_bl="bl1", bitcell_br="br1") + tx = factory.create(module_type="column_mux", tx_size=8, bitcell_bl="bl1", bitcell_br="br1") self.local_check(tx) globals.end_openram() diff --git a/compiler/tests/04_single_level_column_mux_pbitcell_test.py b/compiler/tests/04_column_mux_pbitcell_test.py similarity index 79% rename from compiler/tests/04_single_level_column_mux_pbitcell_test.py rename to compiler/tests/04_column_mux_pbitcell_test.py index 18ab631f..a7a93403 100755 --- a/compiler/tests/04_single_level_column_mux_pbitcell_test.py +++ b/compiler/tests/04_column_mux_pbitcell_test.py @@ -8,16 +8,15 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS from sram_factory import factory import debug -#@unittest.skip("SKIPPING 04_driver_test") -class single_level_column_mux_pbitcell_test(openram_test): +class column_mux_pbitcell_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) @@ -31,12 +30,12 @@ class single_level_column_mux_pbitcell_test(openram_test): factory.reset() debug.info(2, "Checking column mux for pbitcell (innermost connections)") - tx = factory.create(module_type="single_level_column_mux", tx_size=8, bitcell_bl="bl0", bitcell_br="br0") + tx = factory.create(module_type="column_mux", tx_size=8, bitcell_bl="bl0", bitcell_br="br0") self.local_check(tx) factory.reset() debug.info(2, "Checking column mux for pbitcell (outermost connections)") - tx = factory.create(module_type="single_level_column_mux",tx_size=8, bitcell_bl="bl2", bitcell_br="br2") + tx = factory.create(module_type="column_mux",tx_size=8, bitcell_bl="bl2", bitcell_br="br2") self.local_check(tx) globals.end_openram() diff --git a/compiler/tests/04_single_level_column_mux_test.py b/compiler/tests/04_column_mux_test.py similarity index 87% rename from compiler/tests/04_single_level_column_mux_test.py rename to compiler/tests/04_column_mux_test.py index 20dfe968..a10603ee 100755 --- a/compiler/tests/04_single_level_column_mux_test.py +++ b/compiler/tests/04_column_mux_test.py @@ -8,7 +8,7 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS @@ -16,7 +16,7 @@ from sram_factory import factory import debug -class single_level_column_mux_test(openram_test): +class column_mux_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) @@ -24,7 +24,7 @@ class single_level_column_mux_test(openram_test): # check single level column mux in single port debug.info(2, "Checking column mux") - tx = factory.create(module_type="single_level_column_mux", tx_size=8) + tx = factory.create(module_type="column_mux", tx_size=8) self.local_check(tx) globals.end_openram() diff --git a/compiler/tests/04_pand2_test.py b/compiler/tests/04_pand2_test.py index f7e5f304..077c180e 100755 --- a/compiler/tests/04_pand2_test.py +++ b/compiler/tests/04_pand2_test.py @@ -8,13 +8,13 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS -from sram_factory import factory import debug + class pand2_test(openram_test): def runTest(self): diff --git a/compiler/tests/04_pand3_test.py b/compiler/tests/04_pand3_test.py index e58f1ee9..4817601e 100755 --- a/compiler/tests/04_pand3_test.py +++ b/compiler/tests/04_pand3_test.py @@ -8,13 +8,13 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS -from sram_factory import factory import debug + class pand3_test(openram_test): def runTest(self): diff --git a/compiler/tests/04_pand4_test.py b/compiler/tests/04_pand4_test.py new file mode 100755 index 00000000..f7dd329d --- /dev/null +++ b/compiler/tests/04_pand4_test.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 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. +# +import unittest +from testutils import * +import sys, os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +import debug + + +class pand4_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + global verify + import verify + + import pand4 + + debug.info(2, "Testing pand4 gate 4x") + a = pand4.pand4(name="pand4x4", size=4) + self.local_check(a) + + globals.end_openram() + +# instantiate a copdsay of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_pnand4_test.py b/compiler/tests/04_pnand4_test.py new file mode 100755 index 00000000..8eb1b4ba --- /dev/null +++ b/compiler/tests/04_pnand4_test.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 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. +# +import unittest +from testutils import * +import sys, os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class pnand4_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + debug.info(2, "Checking 4-input nand gate") + tx = factory.create(module_type="pnand4", size=1) + self.local_check(tx) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_wordline_driver_test.py b/compiler/tests/04_wordline_driver_test.py index ada65db7..74cfb7a7 100755 --- a/compiler/tests/04_wordline_driver_test.py +++ b/compiler/tests/04_wordline_driver_test.py @@ -25,7 +25,7 @@ class wordline_driver_test(openram_test): # check wordline driver for single port debug.info(2, "Checking driver") - tx = factory.create(module_type="wordline_driver") + tx = factory.create(module_type="wordline_driver", cols=8) self.local_check(tx) globals.end_openram() diff --git a/compiler/tests/06_hierarchical_decoder_test.py b/compiler/tests/06_hierarchical_decoder_test.py index ce186598..42bc38cd 100755 --- a/compiler/tests/06_hierarchical_decoder_test.py +++ b/compiler/tests/06_hierarchical_decoder_test.py @@ -8,33 +8,40 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS from sram_factory import factory import debug + class hierarchical_decoder_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) + # Use the 2 port cell since it is usually bigger/easier + OPTS.bitcell = "bitcell_1rw_1r" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + # Checks 2x4 and 2-input NAND decoder - #debug.info(1, "Testing 16 row sample for hierarchical_decoder") - #a = factory.create(module_type="hierarchical_decoder", num_outputs=16) - #self.local_check(a) + debug.info(1, "Testing 16 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=16) + self.local_check(a) # Checks 2x4 and 2-input NAND decoder with non-power-of-two - #debug.info(1, "Testing 17 row sample for hierarchical_decoder") - #a = factory.create(module_type="hierarchical_decoder", num_outputs=17) - #self.local_check(a) + debug.info(1, "Testing 17 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=17) + self.local_check(a) # Checks 2x4 with 3x8 and 2-input NAND decoder - #debug.info(1, "Testing 32 row sample for hierarchical_decoder") - #a = factory.create(module_type="hierarchical_decoder", num_outputs=32) - #self.local_check(a) + debug.info(1, "Testing 32 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=32) + self.local_check(a) # Checks 3 x 2x4 and 3-input NAND decoder debug.info(1, "Testing 64 row sample for hierarchical_decoder") @@ -42,18 +49,20 @@ class hierarchical_decoder_test(openram_test): self.local_check(a) # Checks 2x4 and 2 x 3x8 and 3-input NAND with non-power-of-two - #debug.info(1, "Testing 132 row sample for hierarchical_decoder") - #a = factory.create(module_type="hierarchical_decoder", num_outputs=132) - #self.local_check(a) - - # Checks 3 x 3x8 and 3-input NAND decoder - #debug.info(1, "Testing 512 row sample for hierarchical_decoder") - #a = factory.create(module_type="hierarchical_decoder", num_outputs=512) - #self.local_check(a) - + debug.info(1, "Testing 132 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=132) self.local_check(a) - + # Checks 3 x 3x8 and 3-input NAND decoder + debug.info(1, "Testing 512 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=512) + self.local_check(a) + + # Checks 3 x 4x16 and 4-input NAND decoder + debug.info(1, "Testing 4096 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=4096) + self.local_check(a) + globals.end_openram() # run the test from the command line diff --git a/compiler/tests/06_hierarchical_predecode4x16_test.py b/compiler/tests/06_hierarchical_predecode4x16_test.py index b4ebda38..7ded6144 100755 --- a/compiler/tests/06_hierarchical_predecode4x16_test.py +++ b/compiler/tests/06_hierarchical_predecode4x16_test.py @@ -8,14 +8,15 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS from sram_factory import factory import debug -@unittest.skip("SKIPPING hierarchical_predecode4x16_test") + +# @unittest.skip("SKIPPING hierarchical_predecode4x16_test") class hierarchical_predecode4x16_test(openram_test): def runTest(self): diff --git a/compiler/tests/07_single_level_column_mux_array_1rw_1r_test.py b/compiler/tests/07_column_mux_array_1rw_1r_test.py similarity index 65% rename from compiler/tests/07_single_level_column_mux_array_1rw_1r_test.py rename to compiler/tests/07_column_mux_array_1rw_1r_test.py index 209133aa..10c96092 100755 --- a/compiler/tests/07_single_level_column_mux_array_1rw_1r_test.py +++ b/compiler/tests/07_column_mux_array_1rw_1r_test.py @@ -14,7 +14,8 @@ from globals import OPTS from sram_factory import factory import debug -class single_level_column_mux_test(openram_test): + +class column_mux_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) @@ -26,27 +27,27 @@ class single_level_column_mux_test(openram_test): globals.setup_bitcell() debug.info(1, "Testing sample for 2-way column_mux_array port 0") - a = factory.create(module_type="single_level_column_mux_array", columns=8, word_size=4, bitcell_bl="bl0", bitcell_br="br0") + a = factory.create(module_type="column_mux_array", columns=8, word_size=4, bitcell_bl="bl0", bitcell_br="br0") self.local_check(a) debug.info(1, "Testing sample for 2-way column_mux_array port 1") - a = factory.create(module_type="single_level_column_mux_array", columns=8, word_size=4, bitcell_bl="bl1", bitcell_br="br1") + a = factory.create(module_type="column_mux_array", columns=8, word_size=4, bitcell_bl="bl1", bitcell_br="br1") self.local_check(a) debug.info(1, "Testing sample for 4-way column_mux_array port 0") - a = factory.create(module_type="single_level_column_mux_array", columns=8, word_size=2, bitcell_bl="bl0", bitcell_br="br0") + a = factory.create(module_type="column_mux_array", columns=8, word_size=2, bitcell_bl="bl0", bitcell_br="br0") self.local_check(a) debug.info(1, "Testing sample for 4-way column_mux_array port 1") - a = factory.create(module_type="single_level_column_mux_array", columns=8, word_size=2, bitcell_bl="bl1", bitcell_br="br1") + a = factory.create(module_type="column_mux_array", columns=8, word_size=2, bitcell_bl="bl1", bitcell_br="br1") self.local_check(a) debug.info(1, "Testing sample for 8-way column_mux_array port 0") - a = factory.create(module_type="single_level_column_mux_array", columns=16, word_size=2, bitcell_bl="bl0", bitcell_br="br0") + a = factory.create(module_type="column_mux_array", columns=16, word_size=2, bitcell_bl="bl0", bitcell_br="br0") self.local_check(a) debug.info(1, "Testing sample for 8-way column_mux_array port 1") - a = factory.create(module_type="single_level_column_mux_array", columns=16, word_size=2, bitcell_bl="bl1", bitcell_br="br1") + a = factory.create(module_type="column_mux_array", columns=16, word_size=2, bitcell_bl="bl1", bitcell_br="br1") self.local_check(a) globals.end_openram() diff --git a/compiler/tests/07_single_level_column_mux_array_pbitcell_test.py b/compiler/tests/07_column_mux_array_pbitcell_test.py similarity index 69% rename from compiler/tests/07_single_level_column_mux_array_pbitcell_test.py rename to compiler/tests/07_column_mux_array_pbitcell_test.py index 663ff075..0a089bb9 100755 --- a/compiler/tests/07_single_level_column_mux_array_pbitcell_test.py +++ b/compiler/tests/07_column_mux_array_pbitcell_test.py @@ -7,19 +7,19 @@ # All rights reserved. # from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS from sram_factory import factory import debug -class single_level_column_mux_pbitcell_test(openram_test): + +class column_mux_pbitcell_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) - import single_level_column_mux_array # check single level column mux array in multi-port OPTS.bitcell = "pbitcell" @@ -29,19 +29,19 @@ class single_level_column_mux_pbitcell_test(openram_test): factory.reset() debug.info(1, "Testing sample for 2-way column_mux_array in multi-port") - a = factory.create(module_type="single_level_column_mux_array", columns=16, word_size=8, bitcell_bl="bl0", bitcell_br="br0") + a = factory.create(module_type="column_mux_array", columns=16, word_size=8, bitcell_bl="bl0", bitcell_br="br0") self.local_check(a) debug.info(1, "Testing sample for 4-way column_mux_array in multi-port") - a = factory.create(module_type="single_level_column_mux_array", columns=16, word_size=4, bitcell_bl="bl0", bitcell_br="br0") + a = factory.create(module_type="column_mux_array", columns=16, word_size=4, bitcell_bl="bl0", bitcell_br="br0") self.local_check(a) debug.info(1, "Testing sample for 8-way column_mux_array in multi-port (innermost connections)") - a = factory.create(module_type="single_level_column_mux_array", columns=32, word_size=4, bitcell_bl="bl0", bitcell_br="br0") + a = factory.create(module_type="column_mux_array", columns=32, word_size=4, bitcell_bl="bl0", bitcell_br="br0") self.local_check(a) debug.info(1, "Testing sample for 8-way column_mux_array in multi-port (outermost connections)") - a = factory.create(module_type="single_level_column_mux_array", columns=32, word_size=4, bitcell_bl="bl2", bitcell_br="br2", column_offset=3) + a = factory.create(module_type="column_mux_array", columns=32, word_size=4, bitcell_bl="bl2", bitcell_br="br2", column_offset=3) self.local_check(a) globals.end_openram() diff --git a/compiler/tests/07_single_level_column_mux_array_test.py b/compiler/tests/07_column_mux_array_test.py similarity index 77% rename from compiler/tests/07_single_level_column_mux_array_test.py rename to compiler/tests/07_column_mux_array_test.py index c0476a4f..f32c2773 100755 --- a/compiler/tests/07_single_level_column_mux_array_test.py +++ b/compiler/tests/07_column_mux_array_test.py @@ -14,22 +14,23 @@ from globals import OPTS from sram_factory import factory import debug -class single_level_column_mux_test(openram_test): + +class column_mux_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) debug.info(1, "Testing sample for 2-way column_mux_array") - a = factory.create(module_type="single_level_column_mux_array", columns=16, word_size=8) + a = factory.create(module_type="column_mux_array", columns=16, word_size=8) self.local_check(a) debug.info(1, "Testing sample for 4-way column_mux_array") - a = factory.create(module_type="single_level_column_mux_array", columns=16, word_size=4) + a = factory.create(module_type="column_mux_array", columns=16, word_size=4) self.local_check(a) debug.info(1, "Testing sample for 8-way column_mux_array") - a = factory.create(module_type="single_level_column_mux_array", columns=32, word_size=4) + a = factory.create(module_type="column_mux_array", columns=32, word_size=4) self.local_check(a) globals.end_openram() 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 b57d65a7..65bc7848 100755 --- a/compiler/tests/14_replica_bitcell_array_1rw_1r_test.py +++ b/compiler/tests/14_replica_bitcell_array_1rw_1r_test.py @@ -28,8 +28,7 @@ class replica_bitcell_array_1rw_1r_test(openram_test): a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, - rbl=[1, 1], - add_rbl=[0, 0]) + rbl=[1, 1]) self.local_check(a) debug.info(2, "Testing 4x4 left replica array for cell_1rw_1r") @@ -37,14 +36,16 @@ class replica_bitcell_array_1rw_1r_test(openram_test): cols=4, rows=4, rbl=[1, 1], - add_rbl=[1, 0]) + left_rbl=[0]) self.local_check(a) debug.info(2, "Testing 4x4 array left and right replica for cell_1rw_1r") a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, - rbl=[1, 1]) + rbl=[1, 1], + left_rbl=[0], + right_rbl=[1]) self.local_check(a) @@ -55,7 +56,7 @@ class replica_bitcell_array_1rw_1r_test(openram_test): cols=4, rows=4, rbl=[1, 1], - add_rbl=[0, 1]) + right_rbl=[1]) self.local_check(a) globals.end_openram() diff --git a/compiler/tests/14_replica_pbitcell_array_test.py b/compiler/tests/14_replica_pbitcell_array_test.py index 8376241c..1cd2d9fc 100755 --- a/compiler/tests/14_replica_pbitcell_array_test.py +++ b/compiler/tests/14_replica_pbitcell_array_test.py @@ -27,7 +27,7 @@ class replica_pbitcell_array_test(openram_test): OPTS.num_w_ports = 0 debug.info(2, "Testing 4x4 array for pbitcell") - a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, rbl=[1, 1], add_rbl=[1, 1]) + a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, rbl=[1, 1], left_rbl=[0], right_rbl=[1]) self.local_check(a) OPTS.bitcell = "pbitcell" @@ -39,7 +39,7 @@ class replica_pbitcell_array_test(openram_test): factory.reset() debug.info(2, "Testing 4x4 array for pbitcell") - a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, rbl=[1, 0], add_rbl=[1, 0]) + a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, rbl=[1, 0], left_rbl=[0]) self.local_check(a) globals.end_openram() diff --git a/compiler/tests/15_global_bitcell_array_1rw_1r_test.py b/compiler/tests/15_global_bitcell_array_1rw_1r_test.py new file mode 100755 index 00000000..dd17d82e --- /dev/null +++ b/compiler/tests/15_global_bitcell_array_1rw_1r_test.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 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. +# +import unittest +from testutils import * +import sys, os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from sram_factory import factory +import debug + + +# @unittest.skip("SKIPPING 05_global_bitcell_array_test") +class global_bitcell_array_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Testing 2 x 4x4 global bitcell array for cell_1rw_1r") + a = factory.create(module_type="global_bitcell_array", cols=[4, 4], rows=4) + self.local_check(a) + + # debug.info(2, "Testing 4x4 local bitcell array for 6t_cell with replica column") + # a = factory.create(module_type="local_bitcell_array", cols=4, left_rbl=1, rows=4, ports=[0]) + # self.local_check(a) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/15_global_bitcell_array_test.py b/compiler/tests/15_global_bitcell_array_test.py index 18c25c37..ed7e282a 100755 --- a/compiler/tests/15_global_bitcell_array_test.py +++ b/compiler/tests/15_global_bitcell_array_test.py @@ -15,20 +15,20 @@ from sram_factory import factory import debug -@unittest.skip("SKIPPING 05_global_bitcell_array_test") +# @unittest.skip("SKIPPING 05_global_bitcell_array_test") class global_bitcell_array_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) - debug.info(2, "Testing 2 x 4x4 global bitcell array for 6t_cell without replica") - a = factory.create(module_type="global_bitcell_array", cols=[4, 4], rows=4, ports=[0]) - self.local_check(a) - - # debug.info(2, "Testing 4x4 local bitcell array for 6t_cell with replica column") - # a = factory.create(module_type="local_bitcell_array", cols=4, left_rbl=1, rows=4, ports=[0]) + # debug.info(2, "Testing 2 x 4x4 global bitcell array for 6t_cell") + # a = factory.create(module_type="global_bitcell_array", cols=[4, 4], rows=4) # self.local_check(a) + + debug.info(2, "Testing 2 x 4x4 global bitcell array for 6t_cell") + a = factory.create(module_type="global_bitcell_array", cols=[10, 6], rows=4) + self.local_check(a) globals.end_openram() diff --git a/compiler/tests/15_local_bitcell_array_1rw_1r_test.py b/compiler/tests/15_local_bitcell_array_1rw_1r_test.py index 778d4b5f..01a5c8ee 100755 --- a/compiler/tests/15_local_bitcell_array_1rw_1r_test.py +++ b/compiler/tests/15_local_bitcell_array_1rw_1r_test.py @@ -28,20 +28,21 @@ class local_bitcell_array_1rw_1r_test(openram_test): globals.setup_bitcell() debug.info(2, "Testing 4x4 local bitcell array for cell_1rw_1r without replica") - a = factory.create(module_type="local_bitcell_array", cols=4, rows=4, rbl=[1, 1], add_rbl=[0, 0]) + a = factory.create(module_type="local_bitcell_array", cols=4, rows=4, rbl=[1, 1]) self.local_check(a) debug.info(2, "Testing 4x4 local bitcell array for cell_1rw_1r with replica column") - a = factory.create(module_type="local_bitcell_array", cols=4, rows=4, rbl=[1, 1], add_rbl=[1, 0]) - self.local_check(a) - - debug.info(2, "Testing 4x4 local bitcell array for cell_1rw_1r with replica column") - a = factory.create(module_type="local_bitcell_array", cols=4, rows=4, rbl=[1, 1], add_rbl=[0, 1]) + a = factory.create(module_type="local_bitcell_array", cols=4, rows=4, rbl=[1, 1], right_rbl=[1]) self.local_check(a) debug.info(2, "Testing 4x4 local bitcell array for cell_1rw_1r with replica column") - a = factory.create(module_type="local_bitcell_array", cols=4, rows=4, rbl=[1, 1], add_rbl=[1, 1]) + a = factory.create(module_type="local_bitcell_array", cols=4, rows=4, rbl=[1, 1], left_rbl=[0]) self.local_check(a) + + debug.info(2, "Testing 4x4 local bitcell array for cell_1rw_1r with replica column") + a = factory.create(module_type="local_bitcell_array", cols=4, rows=4, rbl=[1, 1], left_rbl=[0], right_rbl=[1]) + self.local_check(a) + globals.end_openram() diff --git a/compiler/tests/15_local_bitcell_array_test.py b/compiler/tests/15_local_bitcell_array_test.py index 591b0607..45dae1a4 100755 --- a/compiler/tests/15_local_bitcell_array_test.py +++ b/compiler/tests/15_local_bitcell_array_test.py @@ -23,11 +23,11 @@ class local_bitcell_array_test(openram_test): globals.init_openram(config_file) debug.info(2, "Testing 4x4 local bitcell array for 6t_cell without replica") - a = factory.create(module_type="local_bitcell_array", cols=4, rows=4, rbl=[1, 0], add_rbl=[0, 0]) + a = factory.create(module_type="local_bitcell_array", cols=4, rows=4, rbl=[1, 0]) self.local_check(a) debug.info(2, "Testing 4x4 local bitcell array for 6t_cell with replica column") - a = factory.create(module_type="local_bitcell_array", cols=4, rows=4, rbl=[1, 0], add_rbl=[1, 0]) + a = factory.create(module_type="local_bitcell_array", cols=4, rows=4, rbl=[1, 0], left_rbl=[0]) self.local_check(a) globals.end_openram() diff --git a/compiler/tests/18_port_address_1rw_1r_test.py b/compiler/tests/18_port_address_1rw_1r_test.py index caf2cb96..42a46614 100755 --- a/compiler/tests/18_port_address_1rw_1r_test.py +++ b/compiler/tests/18_port_address_1rw_1r_test.py @@ -27,11 +27,11 @@ class port_address_1rw_1r_test(openram_test): globals.setup_bitcell() debug.info(1, "Port address 16 rows") - a = factory.create("port_address", cols=16, rows=16) + a = factory.create("port_address", cols=16, rows=16, port=0) self.local_check(a) debug.info(1, "Port address 256 rows") - a = factory.create("port_address", cols=256, rows=256) + a = factory.create("port_address", cols=256, rows=256, port=1) self.local_check(a) globals.end_openram() diff --git a/compiler/tests/18_port_address_test.py b/compiler/tests/18_port_address_test.py index 11da333e..94feedb2 100755 --- a/compiler/tests/18_port_address_test.py +++ b/compiler/tests/18_port_address_test.py @@ -21,11 +21,11 @@ class port_address_test(openram_test): globals.init_openram(config_file) debug.info(1, "Port address 16 rows") - a = factory.create("port_address", cols=16, rows=16) + a = factory.create("port_address", cols=16, rows=16, port=0) self.local_check(a) debug.info(1, "Port address 512 rows") - a = factory.create("port_address", cols=256, rows=512) + a = factory.create("port_address", cols=256, rows=512, port=0) self.local_check(a) globals.end_openram() diff --git a/compiler/tests/19_single_bank_1rw_1r_test.py b/compiler/tests/19_single_bank_1rw_1r_test.py index 22f83f29..b60e7c98 100755 --- a/compiler/tests/19_single_bank_1rw_1r_test.py +++ b/compiler/tests/19_single_bank_1rw_1r_test.py @@ -8,13 +8,14 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS from sram_factory import factory import debug + class single_bank_1rw_1r_test(openram_test): def runTest(self): diff --git a/compiler/tests/19_single_bank_global_bitline.py b/compiler/tests/19_single_bank_global_bitline.py new file mode 100755 index 00000000..ffaea6e6 --- /dev/null +++ b/compiler/tests/19_single_bank_global_bitline.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 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. +# +import unittest +from testutils import * +import sys, os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class single_bank_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + OPTS.local_array_size = 2 + c = sram_config(word_size=4, + num_words=16) + + c.words_per_row=1 + factory.reset() + c.recompute_sizes() + debug.info(1, "No column mux") + a = factory.create(module_type="bank", sram_config=c) + self.local_check(a) + + c.num_words=32 + c.words_per_row=2 + factory.reset() + c.recompute_sizes() + debug.info(1, "Two way column mux") + a = factory.create(module_type="bank", sram_config=c) + self.local_check(a) + + c.num_words=64 + c.words_per_row=4 + factory.reset() + c.recompute_sizes() + debug.info(1, "Four way column mux") + a = factory.create(module_type="bank", sram_config=c) + self.local_check(a) + + c.word_size=2 + c.num_words=128 + c.words_per_row=8 + factory.reset() + c.recompute_sizes() + debug.info(1, "Eight way column mux") + a = factory.create(module_type="bank", sram_config=c) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/20_sram_1bank_2mux_global_test.py b/compiler/tests/20_sram_1bank_2mux_global_test.py new file mode 100755 index 00000000..4020dff9 --- /dev/null +++ b/compiler/tests/20_sram_1bank_2mux_global_test.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 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. +# +import unittest +from testutils import * +import sys, os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +#@unittest.skip("SKIPPING 20_sram_1bank_4mux_test") +class sram_1bank_2mux_global_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + OPTS.local_array_size = 8 + OPTS.route_supplies = False + c = sram_config(word_size=8, + num_words=32, + num_banks=1) + + c.words_per_row=2 + c.recompute_sizes() + debug.info(1, "Layout test for {}rw,{}r,{}w sram " + "with {} bit words, {} words, {} words per " + "row, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.words_per_row, + c.num_banks)) + a = factory.create(module_type="sram", sram_config=c) + self.local_check(a, final_verification=True) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/21_ngspice_delay_global_test.py b/compiler/tests/21_ngspice_delay_global_test.py new file mode 100755 index 00000000..ac463883 --- /dev/null +++ b/compiler/tests/21_ngspice_delay_global_test.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 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. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class timing_sram_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + OPTS.spice_name="ngspice" + OPTS.analytical_delay = False + OPTS.netlist_only = True + + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + from characterizer import delay + from sram_config import sram_config + OPTS.local_array_size = 2 + c = sram_config(word_size=4, + num_words=16, + num_banks=1) + c.words_per_row=1 + c.recompute_sizes() + # c = sram_config(word_size=8, + # num_words=32, + # num_banks=1) + + # c.words_per_row=2 + # c.recompute_sizes() + debug.info(1, "Testing timing for global hierarchical array") + s = factory.create(module_type="sram", sram_config=c) + + tempspice = OPTS.openram_temp + "temp.sp" + s.sp_write(tempspice) + + probe_address = "1" * s.s.addr_size + probe_data = s.s.word_size - 1 + debug.info(1, "Probe address {0} probe data bit {1}".format(probe_address, probe_data)) + + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + d = delay(s.s, tempspice, corner) + import tech + loads = [tech.spice["dff_in_cap"]*4] + slews = [tech.spice["rise_time"]*2] + data, port_data = d.analyze(probe_address, probe_data, slews, loads) + #Combine info about port into all data + data.update(port_data[0]) + + if OPTS.tech_name == "freepdk45": + golden_data = {'delay_hl': [0.26308339999999997], + 'delay_lh': [0.26308339999999997], + 'disabled_read0_power': [0.1829355], + 'disabled_read1_power': [0.1962055], + 'disabled_write0_power': [0.2130763], + 'disabled_write1_power': [0.2349011], + 'leakage_power': 0.002509793, + 'min_period': 0.977, + 'read0_power': [0.4028693], + 'read1_power': [0.4055884], + 'slew_hl': [0.27116019999999996], + 'slew_lh': [0.27116019999999996], + 'write0_power': [0.44159149999999997], + 'write1_power': [0.3856132]} + elif OPTS.tech_name == "scn4m_subm": + golden_data = {'delay_hl': [2.0149939999999997], + 'delay_lh': [2.0149939999999997], + 'disabled_read0_power': [7.751129], + 'disabled_read1_power': [9.025803], + 'disabled_write0_power': [9.546656], + 'disabled_write1_power': [10.2449], + 'leakage_power': 0.004770704, + 'min_period': 7.188, + 'read0_power': [17.68452], + 'read1_power': [18.24353], + 'slew_hl': [1.942796], + 'slew_lh': [1.942796], + 'write0_power': [20.02101], + 'write1_power': [15.389470000000001]} + else: + self.assertTrue(False) # other techs fail + + # Check if no too many or too few results + self.assertTrue(len(data.keys())==len(golden_data.keys())) + + self.assertTrue(self.check_golden_data(data,golden_data,0.25)) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/22_psram_1bank_2mux_func_test.py b/compiler/tests/22_psram_1bank_2mux_func_test.py index 7a6da149..5e4040d6 100755 --- a/compiler/tests/22_psram_1bank_2mux_func_test.py +++ b/compiler/tests/22_psram_1bank_2mux_func_test.py @@ -8,13 +8,14 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS from sram_factory import factory import debug + class psram_1bank_2mux_func_test(openram_test): def runTest(self): @@ -36,7 +37,7 @@ class psram_1bank_2mux_func_test(openram_test): from importlib import reload import characterizer reload(characterizer) - from characterizer import functional, delay + from characterizer import functional from sram_config import sram_config c = sram_config(word_size=2, num_words=32, @@ -52,13 +53,13 @@ class psram_1bank_2mux_func_test(openram_test): c.words_per_row, c.num_banks)) s = factory.create(module_type="sram", sram_config=c) - tempspice = OPTS.openram_temp + "sram.sp" + tempspice = OPTS.openram_temp + "sram.sp" s.sp_write(tempspice) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) f = functional(s.s, tempspice, corner) (fail, error) = f.run() - self.assertTrue(fail,error) + self.assertTrue(fail, error) globals.end_openram() diff --git a/compiler/tests/22_psram_1bank_4mux_func_test.py b/compiler/tests/22_psram_1bank_4mux_func_test.py index facd3874..2c51f2f3 100755 --- a/compiler/tests/22_psram_1bank_4mux_func_test.py +++ b/compiler/tests/22_psram_1bank_4mux_func_test.py @@ -8,13 +8,14 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS from sram_factory import factory import debug + #@unittest.skip("SKIPPING 22_psram_1bank_4mux_func_test, third port reads are broken?") class psram_1bank_4mux_func_test(openram_test): @@ -37,7 +38,7 @@ class psram_1bank_4mux_func_test(openram_test): from importlib import reload import characterizer reload(characterizer) - from characterizer import functional, delay + from characterizer import functional from sram_config import sram_config c = sram_config(word_size=2, num_words=256, @@ -53,13 +54,13 @@ class psram_1bank_4mux_func_test(openram_test): c.words_per_row, c.num_banks)) s = factory.create(module_type="sram", sram_config=c) - tempspice = OPTS.openram_temp + "sram.sp" + tempspice = OPTS.openram_temp + "sram.sp" s.sp_write(tempspice) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) f = functional(s.s, tempspice, corner) (fail, error) = f.run() - self.assertTrue(fail,error) + self.assertTrue(fail, error) globals.end_openram() diff --git a/compiler/tests/22_psram_1bank_8mux_func_test.py b/compiler/tests/22_psram_1bank_8mux_func_test.py index acf0c3a4..447b7512 100755 --- a/compiler/tests/22_psram_1bank_8mux_func_test.py +++ b/compiler/tests/22_psram_1bank_8mux_func_test.py @@ -8,13 +8,14 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS from sram_factory import factory import debug + #@unittest.skip("SKIPPING 22_psram_1bank_8mux_func_test") class psram_1bank_8mux_func_test(openram_test): @@ -37,7 +38,7 @@ class psram_1bank_8mux_func_test(openram_test): from importlib import reload import characterizer reload(characterizer) - from characterizer import functional, delay + from characterizer import functional from sram_config import sram_config c = sram_config(word_size=4, num_words=256, @@ -53,13 +54,13 @@ class psram_1bank_8mux_func_test(openram_test): c.words_per_row, c.num_banks)) s = factory.create(module_type="sram", sram_config=c) - tempspice = OPTS.openram_temp + "sram.sp" + tempspice = OPTS.openram_temp + "sram.sp" s.sp_write(tempspice) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) f = functional(s.s, tempspice, corner) (fail, error) = f.run() - self.assertTrue(fail,error) + self.assertTrue(fail, error) globals.end_openram() diff --git a/compiler/tests/22_psram_1bank_nomux_func_test.py b/compiler/tests/22_psram_1bank_nomux_func_test.py index b7d6cf78..5f2e81c8 100755 --- a/compiler/tests/22_psram_1bank_nomux_func_test.py +++ b/compiler/tests/22_psram_1bank_nomux_func_test.py @@ -8,7 +8,7 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS @@ -37,7 +37,7 @@ class psram_1bank_nomux_func_test(openram_test): from importlib import reload import characterizer reload(characterizer) - from characterizer import functional, delay + from characterizer import functional from sram_config import sram_config c = sram_config(word_size=2, num_words=32, @@ -53,13 +53,13 @@ class psram_1bank_nomux_func_test(openram_test): c.words_per_row, c.num_banks)) s = factory.create(module_type="sram", sram_config=c) - tempspice = OPTS.openram_temp + "sram.sp" + tempspice = OPTS.openram_temp + "sram.sp" s.sp_write(tempspice) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) f = functional(s.s, tempspice, corner) (fail, error) = f.run() - self.assertTrue(fail,error) + self.assertTrue(fail, error) globals.end_openram() diff --git a/compiler/tests/22_sram_1bank_2mux_func_test.py b/compiler/tests/22_sram_1bank_2mux_func_test.py index 10d19c1c..20ac52f0 100755 --- a/compiler/tests/22_sram_1bank_2mux_func_test.py +++ b/compiler/tests/22_sram_1bank_2mux_func_test.py @@ -8,13 +8,14 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS from sram_factory import factory import debug + #@unittest.skip("SKIPPING 22_sram_1bank_2mux_func_test") class sram_1bank_2mux_func_test(openram_test): @@ -29,7 +30,7 @@ class sram_1bank_2mux_func_test(openram_test): from importlib import reload import characterizer reload(characterizer) - from characterizer import functional, delay + from characterizer import functional from sram_config import sram_config c = sram_config(word_size=4, num_words=32, @@ -42,13 +43,13 @@ class sram_1bank_2mux_func_test(openram_test): c.words_per_row, c.num_banks)) s = factory.create(module_type="sram", sram_config=c) - tempspice = OPTS.openram_temp + "sram.sp" + tempspice = OPTS.openram_temp + "sram.sp" s.sp_write(tempspice) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) f = functional(s.s, tempspice, corner) (fail, error) = f.run() - self.assertTrue(fail,error) + self.assertTrue(fail, error) globals.end_openram() diff --git a/compiler/tests/22_sram_1bank_2mux_global_func_test.py b/compiler/tests/22_sram_1bank_2mux_global_func_test.py new file mode 100755 index 00000000..e5b8853a --- /dev/null +++ b/compiler/tests/22_sram_1bank_2mux_global_func_test.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 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. +# +import unittest +from testutils import * +import sys, os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +#@unittest.skip("SKIPPING 22_sram_1bank_2mux_func_test") +class sram_1bank_2mux_func_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + OPTS.analytical_delay = False + OPTS.netlist_only = True + OPTS.trim_netlist = False + + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + from characterizer import functional + from sram_config import sram_config + OPTS.local_array_size = 8 + OPTS.route_supplies = False + c = sram_config(word_size=8, + num_words=32, + num_banks=1) + c.words_per_row=2 + c.recompute_sizes() + debug.info(1, "Functional test for sram with " + "{} bit words, {} words, {} words per row, {} banks".format(c.word_size, + c.num_words, + c.words_per_row, + c.num_banks)) + s = factory.create(module_type="sram", sram_config=c) + tempspice = OPTS.openram_temp + "sram.sp" + s.sp_write(tempspice) + + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + f = functional(s.s, tempspice, corner) + (fail, error) = f.run() + self.assertTrue(fail, error) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/22_sram_1bank_2mux_sparecols_func_test.py b/compiler/tests/22_sram_1bank_2mux_sparecols_func_test.py index 902cacdd..a09ab17e 100755 --- a/compiler/tests/22_sram_1bank_2mux_sparecols_func_test.py +++ b/compiler/tests/22_sram_1bank_2mux_sparecols_func_test.py @@ -8,13 +8,14 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS from sram_factory import factory import debug + #@unittest.skip("SKIPPING 22_sram_1bank_2mux_sparecols_func_test") class sram_1bank_2mux_sparecols_func_test(openram_test): @@ -29,7 +30,7 @@ class sram_1bank_2mux_sparecols_func_test(openram_test): from importlib import reload import characterizer reload(characterizer) - from characterizer import functional, delay + from characterizer import functional from sram_config import sram_config c = sram_config(word_size=4, num_words=32, @@ -44,13 +45,13 @@ class sram_1bank_2mux_sparecols_func_test(openram_test): c.num_spare_cols, c.num_banks)) s = factory.create(module_type="sram", sram_config=c) - tempspice = OPTS.openram_temp + "sram.sp" + tempspice = OPTS.openram_temp + "sram.sp" s.sp_write(tempspice) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) f = functional(s.s, tempspice, corner) (fail, error) = f.run() - self.assertTrue(fail,error) + self.assertTrue(fail, error) globals.end_openram() diff --git a/compiler/tests/22_sram_1bank_4mux_func_test.py b/compiler/tests/22_sram_1bank_4mux_func_test.py index d2bf7886..baa25906 100755 --- a/compiler/tests/22_sram_1bank_4mux_func_test.py +++ b/compiler/tests/22_sram_1bank_4mux_func_test.py @@ -8,13 +8,14 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS from sram_factory import factory import debug + #@unittest.skip("SKIPPING 22_sram_1bank_4mux_func_test") class sram_1bank_4mux_func_test(openram_test): @@ -29,7 +30,7 @@ class sram_1bank_4mux_func_test(openram_test): from importlib import reload import characterizer reload(characterizer) - from characterizer import functional, delay + from characterizer import functional from sram_config import sram_config c = sram_config(word_size=4, num_words=128, @@ -42,13 +43,13 @@ class sram_1bank_4mux_func_test(openram_test): c.words_per_row, c.num_banks)) s = factory.create(module_type="sram", sram_config=c) - tempspice = OPTS.openram_temp + "sram.sp" + tempspice = OPTS.openram_temp + "sram.sp" s.sp_write(tempspice) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) f = functional(s.s, tempspice, corner) (fail, error) = f.run() - self.assertTrue(fail,error) + self.assertTrue(fail, error) globals.end_openram() diff --git a/compiler/tests/22_sram_1bank_8mux_func_test.py b/compiler/tests/22_sram_1bank_8mux_func_test.py index 3f6ff55f..d9cd794a 100755 --- a/compiler/tests/22_sram_1bank_8mux_func_test.py +++ b/compiler/tests/22_sram_1bank_8mux_func_test.py @@ -8,13 +8,14 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS from sram_factory import factory import debug + #@unittest.skip("SKIPPING 22_sram_1bank_8mux_func_test") class sram_1bank_8mux_func_test(openram_test): @@ -29,7 +30,7 @@ class sram_1bank_8mux_func_test(openram_test): from importlib import reload import characterizer reload(characterizer) - from characterizer import functional, delay + from characterizer import functional if not OPTS.spice_exe: debug.error("Could not find {} simulator.".format(OPTS.spice_name),-1) @@ -45,13 +46,13 @@ class sram_1bank_8mux_func_test(openram_test): c.words_per_row, c.num_banks)) s = factory.create(module_type="sram", sram_config=c) - tempspice = OPTS.openram_temp + "sram.sp" + tempspice = OPTS.openram_temp + "sram.sp" s.sp_write(tempspice) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) f = functional(s.s, tempspice, corner) (fail, error) = f.run() - self.assertTrue(fail,error) + self.assertTrue(fail, error) globals.end_openram() diff --git a/compiler/tests/22_sram_1bank_nomux_1rw_1r_func_test.py b/compiler/tests/22_sram_1bank_nomux_1rw_1r_func_test.py index f2958f9f..50906e62 100755 --- a/compiler/tests/22_sram_1bank_nomux_1rw_1r_func_test.py +++ b/compiler/tests/22_sram_1bank_nomux_1rw_1r_func_test.py @@ -8,13 +8,14 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS from sram_factory import factory import debug + #@unittest.skip("SKIPPING 22_sram_1rw_1r_1bank_nomux_func_test") class psram_1bank_nomux_func_test(openram_test): @@ -33,7 +34,7 @@ class psram_1bank_nomux_func_test(openram_test): from importlib import reload import characterizer reload(characterizer) - from characterizer import functional, delay + from characterizer import functional from sram_config import sram_config c = sram_config(word_size=4, num_words=32, @@ -46,13 +47,13 @@ class psram_1bank_nomux_func_test(openram_test): c.words_per_row, c.num_banks)) s = factory.create(module_type="sram", sram_config=c) - tempspice = OPTS.openram_temp + "sram.sp" + tempspice = OPTS.openram_temp + "sram.sp" s.sp_write(tempspice) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) f = functional(s.s, tempspice, corner) (fail, error) = f.run() - self.assertTrue(fail,error) + self.assertTrue(fail, error) globals.end_openram() diff --git a/compiler/tests/22_sram_1bank_nomux_func_test.py b/compiler/tests/22_sram_1bank_nomux_func_test.py index 2aa20e99..0af55ec2 100755 --- a/compiler/tests/22_sram_1bank_nomux_func_test.py +++ b/compiler/tests/22_sram_1bank_nomux_func_test.py @@ -8,13 +8,14 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS from sram_factory import factory import debug + #@unittest.skip("SKIPPING 22_sram_func_test") class sram_1bank_nomux_func_test(openram_test): @@ -47,7 +48,7 @@ class sram_1bank_nomux_func_test(openram_test): corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) f = functional(s.s, tempspice, corner) (fail, error) = f.run() - self.assertTrue(fail,error) + self.assertTrue(fail, error) globals.end_openram() diff --git a/compiler/tests/22_sram_1bank_nomux_sparecols_func_test.py b/compiler/tests/22_sram_1bank_nomux_sparecols_func_test.py index 347d15d0..dbb9d7b2 100755 --- a/compiler/tests/22_sram_1bank_nomux_sparecols_func_test.py +++ b/compiler/tests/22_sram_1bank_nomux_sparecols_func_test.py @@ -8,13 +8,14 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS from sram_factory import factory import debug + #@unittest.skip("SKIPPING 22_sram_func_test") class sram_1bank_nomux_sparecols_func_test(openram_test): @@ -48,7 +49,7 @@ class sram_1bank_nomux_sparecols_func_test(openram_test): corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) f = functional(s.s, tempspice, corner) (fail, error) = f.run() - self.assertTrue(fail,error) + self.assertTrue(fail, error) globals.end_openram() diff --git a/compiler/tests/22_sram_1bank_wmask_1rw_1r_func_test.py b/compiler/tests/22_sram_1bank_wmask_1rw_1r_func_test.py index 07cff70e..7ab35824 100755 --- a/compiler/tests/22_sram_1bank_wmask_1rw_1r_func_test.py +++ b/compiler/tests/22_sram_1bank_wmask_1rw_1r_func_test.py @@ -35,7 +35,7 @@ class sram_wmask_1w_1r_func_test(openram_test): from importlib import reload import characterizer reload(characterizer) - from characterizer import functional, delay + from characterizer import functional from sram_config import sram_config c = sram_config(word_size=8, num_words=16, diff --git a/compiler/tests/22_sram_wmask_func_test.py b/compiler/tests/22_sram_wmask_func_test.py index d29795a9..b59c3b95 100755 --- a/compiler/tests/22_sram_wmask_func_test.py +++ b/compiler/tests/22_sram_wmask_func_test.py @@ -8,13 +8,14 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS from sram_factory import factory import debug + #@unittest.skip("SKIPPING sram_wmask_func_test") class sram_wmask_func_test(openram_test): @@ -29,7 +30,7 @@ class sram_wmask_func_test(openram_test): from importlib import reload import characterizer reload(characterizer) - from characterizer import functional, delay + from characterizer import functional from sram_config import sram_config c = sram_config(word_size=8, num_words=16, diff --git a/compiler/tests/26_hspice_pex_pinv_test.py b/compiler/tests/26_hspice_pex_pinv_test.py index b4b55cdb..96b2d108 100755 --- a/compiler/tests/26_hspice_pex_pinv_test.py +++ b/compiler/tests/26_hspice_pex_pinv_test.py @@ -9,8 +9,8 @@ Run regression tests/pex test on an extracted pinv to ensure pex functionality with HSPICE. """ import unittest -from testutils import header,openram_test -import sys,os +from testutils import header, openram_test +import sys, os sys.path.append(os.path.join(sys.path[0],"..")) import globals from globals import OPTS @@ -35,43 +35,43 @@ class hspice_pex_pinv_test(openram_test): # generate the pinv prev_purge_value = OPTS.purge_temp - OPTS.purge_temp = False # force set purge to false to save the sp file + # force set purge to false to save the sp file + OPTS.purge_temp = False debug.info(2, "Checking 1x size inverter") tx = pinv.pinv(name="pinv", size=1) - tempgds = "{0}{1}.gds".format(OPTS.openram_temp,tx.name) + tempgds = "{0}{1}.gds".format(OPTS.openram_temp, tx.name) tx.gds_write(tempgds) - tempsp = "{0}{1}.sp".format(OPTS.openram_temp,tx.name) + tempsp = "{0}{1}.sp".format(OPTS.openram_temp, tx.name) tx.sp_write(tempsp) - # make sure that the library simulation is successful\ - sp_delay = self.simulate_delay(test_module = tempsp, - top_level_name = tx.name) - if sp_delay is "Failed": + # make sure that the library simulation is successful + sp_delay = self.simulate_delay(test_module=tempsp, + top_level_name=tx.name) + if sp_delay == "Failed": self.fail('Library Spice module did not behave as expected') # now generate its pex file pex_file = self.run_pex(tx) - OPTS.purge_temp = prev_purge_value # restore the old purge value + OPTS.purge_temp = prev_purge_value # restore the old purge value # generate simulation for pex, make sure the simulation is successful - pex_delay = self.simulate_delay(test_module = pex_file, - top_level_name = tx.name) + pex_delay = self.simulate_delay(test_module=pex_file, + top_level_name=tx.name) # make sure the extracted spice simulated - if pex_delay is "Failed": + if pex_delay == "Failed": self.fail('Pex file did not behave as expected') # if pex data is bigger than original spice file then result is ok # However this may not always be true depending on the netlist provided # comment out for now - #debug.info(2,"pex_delay: {0}".format(pex_delay)) - #debug.info(2,"sp_delay: {0}".format(sp_delay)) + # debug.info(2,"pex_delay: {0}".format(pex_delay)) + # debug.info(2,"sp_delay: {0}".format(sp_delay)) - #assert pex_delay > sp_delay, "pex delay {0} is smaller than sp_delay {1}"\ - #.format(pex_delay,sp_delay) + # assert pex_delay > sp_delay, "pex delay {0} is smaller than sp_delay {1}"\ + # .format(pex_delay,sp_delay) globals.end_openram() def simulate_delay(self, test_module, top_level_name): - from characterizer import charutils from charutils import parse_spice_list # setup simulation sim_file = OPTS.openram_temp + "stim.sp" @@ -87,43 +87,43 @@ class hspice_pex_pinv_test(openram_test): from characterizer import measurements, stimuli corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) sim_file = open(sim_file, "w") - simulation = stimuli(sim_file,corner) + simulation = stimuli(sim_file, corner) # library files simulation.write_include(cir_file) # supply voltages - simulation.gen_constant(sig_name ="vdd", - v_val = tech.spice["nom_supply_voltage"]) - simulation.gen_constant(sig_name = "gnd", - v_val = "0v") + simulation.gen_constant(sig_name="vdd", + v_val=tech.spice["nom_supply_voltage"]) + simulation.gen_constant(sig_name="gnd", + v_val="0v") run_time = tech.spice["feasible_period"] * 4 # input voltage clk_period = tech.spice["feasible_period"] - simulation.gen_pwl(sig_name ="input", - clk_times = [clk_period,clk_period], - data_values = [1,0], - period = clk_period, - slew = 0.001*tech.spice["feasible_period"], - setup = 0) + simulation.gen_pwl(sig_name="input", + clk_times=[clk_period, clk_period], + data_values=[1, 0], + period=clk_period, + slew=0.001 * tech.spice["feasible_period"], + setup=0) # instantiation of simulated pinv - simulation.inst_model(pins = ["input", "output", "vdd", "gnd"], - model_name = top_module_name) + simulation.inst_model(pins=["input", "output", "vdd", "gnd"], + model_name=top_module_name) # delay measurement - delay_measure = measurements.delay_measure(measure_name = "pinv_delay", - trig_name = "input", - targ_name = "output", - trig_dir_str = "FALL", - targ_dir_str = "RISE", - has_port = False) + delay_measure = measurements.delay_measure(measure_name="pinv_delay", + trig_name="input", + targ_name="output", + trig_dir_str="FALL", + targ_dir_str="RISE", + has_port=False) trig_td = trag_td = 0.01 * run_time - rest_info = trig_td,trag_td,tech.spice["nom_supply_voltage"] + rest_info = trig_td, trag_td, tech.spice["nom_supply_voltage"] delay_measure.write_measure(simulation, rest_info) - simulation.write_control(end_time = run_time) + simulation.write_control(end_time=run_time) sim_file.close() return simulation diff --git a/compiler/tests/26_ngspice_pex_pinv_test.py b/compiler/tests/26_ngspice_pex_pinv_test.py index e6e0cfb2..e5dbe5db 100755 --- a/compiler/tests/26_ngspice_pex_pinv_test.py +++ b/compiler/tests/26_ngspice_pex_pinv_test.py @@ -34,43 +34,43 @@ class ngspice_pex_pinv_test(openram_test): # generate the pinv module prev_purge_value = OPTS.purge_temp - OPTS.purge_temp = False # force set purge to false to save the sp file + OPTS.purge_temp = False # force set purge to false to save the sp file debug.info(2, "Checking 1x size inverter") tx = pinv.pinv(name="pinv", size=1) - tempgds = "{0}{1}.gds".format(OPTS.openram_temp,tx.name) + tempgds = "{0}{1}.gds".format(OPTS.openram_temp, tx.name) tx.gds_write(tempgds) - tempsp = "{0}{1}.sp".format(OPTS.openram_temp,tx.name) + tempsp = "{0}{1}.sp".format(OPTS.openram_temp, tx.name) tx.sp_write(tempsp) # make sure that the library simulation is successful - sp_delay = self.simulate_delay(test_module = tempsp, - top_level_name = tx.name) - if sp_delay is "Failed": + sp_delay = self.simulate_delay(test_module=tempsp, + top_level_name=tx.name) + if sp_delay == "Failed": self.fail('Library Spice module did not behave as expected') # now generate its pex file pex_file = self.run_pex(tx) - OPTS.purge_temp = prev_purge_value # restore the old purge value + # restore the old purge value + OPTS.purge_temp = prev_purge_value # generate simulation for pex, make sure the simulation is successful - pex_delay = self.simulate_delay(test_module = pex_file, - top_level_name = tx.name) + pex_delay = self.simulate_delay(test_module=pex_file, + top_level_name=tx.name) # make sure the extracted spice simulated - if pex_delay is "Failed": + if pex_delay == "Failed": self.fail('Pex file did not behave as expected') # if pex data is bigger than original spice file then result is ok # However this may not always be true depending on the netlist provided # comment out for now - #debug.info(2,"pex_delay: {0}".format(pex_delay)) - #debug.info(2,"sp_delay: {0}".format(sp_delay)) + # debug.info(2,"pex_delay: {0}".format(pex_delay)) + # debug.info(2,"sp_delay: {0}".format(sp_delay)) - #assert pex_delay > sp_delay, "pex delay {0} is smaller than sp_delay {1}"\ - #.format(pex_delay,sp_delay) + # assert pex_delay > sp_delay, "pex delay {0} is smaller than sp_delay {1}"\ + # .format(pex_delay,sp_delay) globals.end_openram() def simulate_delay(self, test_module, top_level_name): - from characterizer import charutils from charutils import parse_spice_list # setup simulation sim_file = OPTS.openram_temp + "stim.sp" @@ -86,47 +86,46 @@ class ngspice_pex_pinv_test(openram_test): from characterizer import measurements, stimuli corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) sim_file = open(sim_file, "w") - simulation = stimuli(sim_file,corner) + simulation = stimuli(sim_file, corner) # library files simulation.write_include(cir_file) # supply voltages - simulation.gen_constant(sig_name ="vdd", - v_val = tech.spice["nom_supply_voltage"]) + simulation.gen_constant(sig_name="vdd", + v_val=tech.spice["nom_supply_voltage"]) # The scn4m_subm and ngspice combination will have a gnd source error: # "Fatal error: instance vgnd is a shorted VSRC" # However, remove gnd power for all techa pass for this test # simulation.gen_constant(sig_name = "gnd", # v_val = "0v") - run_time = tech.spice["feasible_period"] * 4 # input voltage clk_period = tech.spice["feasible_period"] - simulation.gen_pwl(sig_name ="input", - clk_times = [clk_period,clk_period], - data_values = [1,0], - period = clk_period, - slew = 0.001*tech.spice["feasible_period"], - setup = 0) + simulation.gen_pwl(sig_name="input", + clk_times=[clk_period, clk_period], + data_values=[1, 0], + period=clk_period, + slew=0.001 * tech.spice["feasible_period"], + setup=0) # instantiation of simulated pinv - simulation.inst_model(pins = ["input", "output", "vdd", "gnd"], - model_name = top_module_name) + simulation.inst_model(pins=["input", "output", "vdd", "gnd"], + model_name=top_module_name) # delay measurement - delay_measure = measurements.delay_measure(measure_name = "pinv_delay", - trig_name = "input", - targ_name = "output", - trig_dir_str = "FALL", - targ_dir_str = "RISE", - has_port = False) + delay_measure = measurements.delay_measure(measure_name="pinv_delay", + trig_name="input", + targ_name="output", + trig_dir_str="FALL", + targ_dir_str="RISE", + has_port=False) trig_td = trag_td = 0.01 * run_time - rest_info = trig_td,trag_td,tech.spice["nom_supply_voltage"] + rest_info = trig_td, trag_td, tech.spice["nom_supply_voltage"] delay_measure.write_measure(simulation, rest_info) - simulation.write_control(end_time = run_time) + simulation.write_control(end_time=run_time) sim_file.close() return simulation diff --git a/compiler/tests/26_pex_test.py b/compiler/tests/26_pex_test.py deleted file mode 100755 index 4eff7db5..00000000 --- a/compiler/tests/26_pex_test.py +++ /dev/null @@ -1,319 +0,0 @@ -#!/usr/bin/env python3 -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2019 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. -# -import unittest -from testutils import * -import sys,os -sys.path.append(os.getenv("OPENRAM_HOME")) -import globals -from globals import OPTS -from sram_factory import factory -import debug - -@unittest.skip("SKIPPING 26_pex_test") -class sram_func_test(openram_test): - - def runTest(self): - config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) - globals.init_openram(config_file) - - OPTS.use_pex = True - - # This is a hack to reload the characterizer __init__ with the spice version - from importlib import reload - import characterizer - reload(characterizer) - from characterizer import setup_hold - if not OPTS.spice_exe: - debug.error("Could not find {} simulator.".format(OPTS.spice_name),-1) - - self.func_test(bank_num=1) - self.func_test(bank_num=2) - self.func_test(bank_num=4) - - globals.end_openram() - - def func_test(self, bank_num): - - import sram - import tech - - debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank") - s = sram.sram(word_size=OPTS.word_size, - num_words=OPTS.num_words, - num_banks=OPTS.num_banks, - name="test_sram1") - - tempspice = OPTS.openram_temp + "temp.sp" - tempgds = OPTS.openram_temp + "temp.gds" - - s.sp_write(tempspice) - s.gds_write(tempgds) - - self.assertFalse(verify.run_drc(s.name, tempgds)) - self.assertFalse(verify.run_lvs(s.name, tempgds, tempspice)) - self.assertFalse(verify.run_pex(s.name, tempgds, - tempspice, output=OPTS.openram_temp + "temp_pex.sp")) - - import sp_file - stimulus_file = OPTS.openram_temp + "stimulus.sp" - a_stimulus = sp_file.sp_file(stimulus_file) - self.write_stimulus(a_stimulus) - - simulator_file = OPTS.openram_temp + "simulator.sp" - a_simulator = sp_file.sp_file(simulator_file) - self.write_simulator(a_simulator) - - result_file = OPTS.openram_temp + "result" - - import os - - if OPTS.spice_name == "hspice": - cmd = "hspice -mt 2 -i {0} > {1} ".format( - simulator_file, result_file) - else: - cmd = "ngspice -b -i {0} > {1} ".format( - simulator_file, result_file) - os.system(cmd) - - import re - sp_result = open(result_file, "r") - contents = sp_result.read() - key = "vr1" - val = re.search( - r"{0}(\s*)=(\s*)(\d*(.).*)(\s*)(from)".format(key), contents) - val = val.group(3) - value1 = float(self.convert_voltage_unit(val)) - - key = "vr2" - val = re.search( - r"{0}(\s*)=(\s*)(\d*(.).*)(\s*)(from)".format(key), contents) - val = val.group(3) - value2 = float(self.convert_voltage_unit(val)) - - self.assertTrue(round(value1) > 0.5 * tech.spice["supply_voltage"]) - self.assertTrue(round(value2) < 0.5 * tech.spice["supply_voltage"]) - - - - def convert_voltage_unit(self, string): - newstring = "" - for letter in string: - if letter == "m": - letter = "10e-3" - elif letter == "u": - letter = "10e-6" - else: - letter = letter - newstring = str(newstring) + str(letter) - return newstring - - def convert_time_unit(self, string): - newstring = "" - for letter in string: - if letter == "f": - letter = "10e-15" - elif letter == "p": - letter = "10e-12" - elif letter == "n": - letter = "10e-9" - elif letter == "u": - letter = "10e-6" - elif letter == "m": - letter = "10e-3" - else: - letter = letter - newstring = str(newstring) + str(letter) - return newstring - - def write_simulator(self, sim_file): - sim_file.write("\n") - import tech - time_step = tech.spice["clock_period"] - for model in tech.spice["fet_models"]: - sim_file.write(".inc " + str(model) + "\n") - sim_file.write(".inc stimulus.sp\n") - sim_file.write(".inc temp_pex.sp\n") - sim_file.write(".options post runlvl=6\n") - sim_file.write("\n") - - sim_file.write( - "Xsource DATA[0] ADDR[0] ADDR[1] ADDR[2] ADDR[3] CSb WEb WEb_inv OEb clk vdd vss source\n") - sim_file.write( - "Xsram DATA[0] ADDR[0] ADDR[1] ADDR[2] ADDR[3] CSb WEb OEb clk vdd vss test_sram1\n") - sim_file.write("\n") - - sim_file.write(".MEASURE TRAN vr1 AVG V(DATA[0]) FROM ={0}ns TO ={1}ns\n".format( - 4.5 * tech.spice["clock_period"], 5 * tech.spice["clock_period"])) - sim_file.write(".MEASURE TRAN vr2 AVG V(DATA[0]) FROM ={0}ns TO ={1}ns\n".format( - 9.5 * tech.spice["clock_period"], 10 * tech.spice["clock_period"])) - sim_file.write("\n") - - if OPTS.spice_name in ["hspice","xa"]: - sim_file.write(".probe v(x*.*)\n") - sim_file.write(".tran 0.1ns {0}ns\n".format( - 10 * tech.spice["clock_period"])) - sim_file.write(".end\n") - else: - sim_file.write( - ".meas tran DELAY1.0 TRIG v(clk) VAL=0.5 RISE=6 TARG v(DATA[0]) VAL=0.5 TD=0.5n RISE=1\n") - sim_file.write(".tran 0.1ns {0}ns\n".format( - 10 * tech.spice["clock_period"])) - sim_file.write(".control\n") - sim_file.write("run\n") - #sim_file.write("plot CSb WEb OEb \n") - #sim_file.write("plot clk DATA0 \n") - sim_file.write("quit\n") - sim_file.write(".endc\n") - sim_file.write(".end\n") - sim_file.file.close() - - def write_stimulus(self, sti_file): - import tech - import sp_file - sti_file.write( - ".subckt source DATA[0] ADDR[0] ADDR[1] ADDR[2] ADDR[3] CSb WEb WEb_inv OEb clk vdd vss\n") - - time_step = tech.spice["clock_period"] - - clk = sp_file.PWL(name="clk", port=["clk", "0"]) - for i in range(0, 11): - clk.write_pulse(i * time_step, time_step, "UP") - clk.write_to_sp(sti_file) - - WEB_inv = sp_file.PWL(name="WEb_inv", port=["WEb_inv", "0"]) - WEB = sp_file.PWL(name="WEB", port=["WEb", "0"]) - OEb = sp_file.PWL(name="OEb", port=["OEb", "0"]) - CSb = sp_file.PWL(name="CSb", port=["CSb", "0"]) - - # write - CSb.write_pulse(0.75 * time_step, time_step, "DN") - WEB.write_pulse(0.75 * time_step, time_step, "DN") - WEB_inv.write_pulse(0.75 * time_step, time_step, "UP") - CSb.write_pulse(1.75 * time_step, time_step, "DN") - WEB.write_pulse(1.75 * time_step, time_step, "DN") - WEB_inv.write_pulse(1.75 * time_step, time_step, "UP") - - # read - OEb.write_pulse(3.75 * time_step, time_step, "DN") - CSb.write_pulse(3.75 * time_step, time_step, "DN") - - # write - CSb.write_pulse(5.75 * time_step, time_step, "DN") - WEB.write_pulse(5.75 * time_step, time_step, "DN") - WEB_inv.write_pulse(5.75 * time_step, time_step, "UP") - CSb.write_pulse(6.75 * time_step, time_step, "DN") - WEB.write_pulse(6.75 * time_step, time_step, "DN") - WEB_inv.write_pulse(6.75 * time_step, time_step, "UP") - - # read - OEb.write_pulse(8.75 * time_step, time_step, "DN") - CSb.write_pulse(8.75 * time_step, time_step, "DN") - - CSb.write_to_sp(sti_file) - WEB.write_to_sp(sti_file) - WEB_inv.write_to_sp(sti_file) - OEb.write_to_sp(sti_file) - - sti_file.write("VA[0] A[0] 0 PWL(0n {0} {1}n {0} {2}n 0 {3}n 0 {4}n {0})\n".format(tech.spice["supply_voltage"], 8.875 * tech.spice[ - "clock_period"], 13.875 * tech.spice["clock_period"], 14.5 * tech.spice["clock_period"], 14.501 * tech.spice["clock_period"])) - sti_file.write("VA[1] A[1] 0 PWL(0n {0} {1}n {0} {2}n 0 {3}n 0 {4}n {0})\n".format(tech.spice["supply_voltage"], 8.875 * tech.spice[ - "clock_period"], 13.875 * tech.spice["clock_period"], 14.5 * tech.spice["clock_period"], 14.501 * tech.spice["clock_period"])) - sti_file.write("VA[2] A[2] 0 PWL(0n {0} {1}n {0} {2}n 0 {3}n 0 {4}n {0})\n".format(tech.spice["supply_voltage"], 8.875 * tech.spice[ - "clock_period"], 13.875 * tech.spice["clock_period"], 14.5 * tech.spice["clock_period"], 14.501 * tech.spice["clock_period"])) - sti_file.write("VA[3] A[3] 0 PWL(0n {0} {1}n {0} {2}n 0 {3}n 0 {4}n {0})\n".format(tech.spice["supply_voltage"], 8.875 * tech.spice[ - "clock_period"], 13.875 * tech.spice["clock_period"], 14.5 * tech.spice["clock_period"], 14.501 * tech.spice["clock_period"])) - - sti_file.write( - "xA[0]_buff A[0] ADDR[0]_inv ADDR[0] vdd vss test_buf\n") - sti_file.write( - "xA[1]_buff A[1] ADDR[1]_inv ADDR[1] vdd vss test_buf\n") - sti_file.write( - "xA[2]_buff A[2] ADDR[2]_inv ADDR[2] vdd vss test_buf\n") - sti_file.write( - "xA[3]_buff A[3] ADDR[3]_inv ADDR[3] vdd vss test_buf\n") - - VD_0 = sp_file.PWL(name="VD[0]", port=["D[0]", "0"]) - VD_0.write_pulse(0, 5 * time_step, "S1") - VD_0.write_pulse(5 * time_step, 5 * time_step, "S0") - VD_0.write_to_sp(sti_file) - - sti_file.write( - "xD[0]_buff D[0] DATA[0]_inv DATA[0]s vdd vss test_buf\n") - sti_file.write( - "xD[0]_gate DATA[0]s WEb WEb_inv DATA[0] vdd vss tran_gate\n") - sti_file.write("mp[0]_gate_vdd vdd write_v DATA[0] vdd " + str(tech.spice["pmos"]) + - " w=" + str(2 * tech.parameter["min_tx_size"]) + "u" + - " l=" + str(tech.drc["minlength_channel"]) + "u" + - "\n") - sti_file.write("mn[0]_gate_vss vss write_g DATA[0] vss " + str(tech.spice["nmos"]) + - " w=" + str(tech.parameter["min_tx_size"]) + "u" + - " l=" + str(tech.drc["minlength_channel"]) + "u" + - "\n") - - Vwrite_v = sp_file.PWL(name="write_v", port=["write_vs", "0"]) - Vwrite_v.write_pulse(0, 0.5 * time_step, "S1") - Vwrite_v.write_pulse(7.5 * time_step, time_step, "DN") - Vwrite_v.write_to_sp(sti_file) - sti_file.write( - "xwrite_v write_vs write_v_inv write_v vdd vss test_buf\n") - - Vwrite_g = sp_file.PWL(name="write_g", port=["write_gs", "0"]) - Vwrite_g.write_pulse(0, 0.5 * time_step, "S0") - Vwrite_g.write_pulse(3 * time_step, time_step, "UP") - Vwrite_g.write_to_sp(sti_file) - sti_file.write( - "xwrite_g write_gs write_g_inv write_g vdd vss test_buf\n") - - sti_file.write("Vdd vdd 0 DC " + - str(tech.spice["supply_voltage"]) + "\n") - sti_file.write("Vvss vss 0 DC 0\n") - sti_file.write(".ENDS source\n") - sti_file.write("\n") - - sti_file.write(".SUBCKT tran_gate in gate gate_inv out vdd vss\n") - sti_file.write("mp0 in gate out vdd " + str(tech.spice["pmos"]) + - " w=" + str(2 * tech.parameter["min_tx_size"]) + "u" + - " l=" + str(tech.drc["minlength_channel"]) + "u" + - "\n") - sti_file.write("mn0 in gate_inv out vss " + str(tech.spice["nmos"]) + - " w=" + str(tech.parameter["min_tx_size"]) + "u" + - " l=" + str(tech.drc["minlength_channel"]) + "u" + - "\n") - sti_file.write(".ENDS tran_gate\n") - sti_file.write("\n") - - sti_file.write(".SUBCKT test_buf in out_inv out_buf vdd vss\n") - sti_file.write("mpinv1 out_inv in vdd vdd " + str(tech.spice["pmos"]) + - " w=" + str(2 * tech.parameter["min_tx_size"]) + "u" + - " l=" + str(tech.drc["minlength_channel"]) + "u" + - "\n") - sti_file.write("mninv1 out_inv in vss vss " + str(tech.spice["nmos"]) + - " w=" + str(tech.parameter["min_tx_size"]) + "u" + - " l=" + str(tech.drc["minlength_channel"]) + "u" + - "\n") - sti_file.write("mpinv2 out_buf out_inv vdd vdd " + str(tech.spice["pmos"]) + - " w=" + str(2 * tech.parameter["min_tx_size"]) + "u" + - " l=" + str(tech.drc["minlength_channel"]) + "u" + - "\n") - sti_file.write("mninv2 out_buf out_inv vss vss " + str(tech.spice["nmos"]) + - " w=" + str(tech.parameter["min_tx_size"]) + "u" + - " l=" + str(tech.drc["minlength_channel"]) + "u" + - "\n") - sti_file.write(".ENDS test_buf\n") - sti_file.write("\n") - - sti_file.file.close() - - -# run the test from the command line -if __name__ == "__main__": - (OPTS, args) = globals.parse_args() - del sys.argv[1:] - header(__file__, OPTS.tech_name) - unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/26_sram_pex_test.py b/compiler/tests/26_sram_pex_test.py new file mode 100755 index 00000000..5a3253de --- /dev/null +++ b/compiler/tests/26_sram_pex_test.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 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. +# +import unittest +from testutils import * +import sys, os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +@unittest.skip("SKIPPING 26_sram_pex_test") +class sram_pex_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + OPTS.analytical_delay = False + OPTS.use_pex = True + + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + from characterizer import functional + from sram_config import sram_config + c = sram_config(word_size=4, + num_words=32, + num_banks=1) + c.words_per_row=2 + c.recompute_sizes() + debug.info(1, "Functional test for sram with " + "{} bit words, {} words, {} words per row, {} banks".format(c.word_size, + c.num_words, + c.words_per_row, + c.num_banks)) + s = factory.create(module_type="sram", sram_config=c) + tempspice = self.run_pex(s) + + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + f = functional(s.s, tempspice, corner) + (fail, error) = f.run() + self.assertTrue(fail, error) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/50_riscv_func_test.py b/compiler/tests/50_riscv_func_test.py index 1d5720f7..0d9825e6 100755 --- a/compiler/tests/50_riscv_func_test.py +++ b/compiler/tests/50_riscv_func_test.py @@ -8,13 +8,14 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS from sram_factory import factory import debug + @unittest.skip("SKIPPING 50_riscv_func_test") class riscv_func_test(openram_test): @@ -24,6 +25,7 @@ class riscv_func_test(openram_test): OPTS.analytical_delay = False OPTS.netlist_only = True OPTS.trim_netlist = False + OPTS.local_array_size = 16 OPTS.num_rw_ports = 1 OPTS.num_w_ports = 0 OPTS.num_r_ports = 1 @@ -33,7 +35,7 @@ class riscv_func_test(openram_test): from importlib import reload import characterizer reload(characterizer) - from characterizer import functional, delay + from characterizer import functional from sram_config import sram_config c = sram_config(word_size=32, write_size=8, @@ -53,7 +55,7 @@ class riscv_func_test(openram_test): corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) f = functional(s.s, tempspice, corner) (fail, error) = f.run() - self.assertTrue(fail,error) + self.assertTrue(fail, error) globals.end_openram() diff --git a/compiler/tests/50_riscv_phys_test.py b/compiler/tests/50_riscv_phys_test.py index 0ae11025..724131dd 100755 --- a/compiler/tests/50_riscv_phys_test.py +++ b/compiler/tests/50_riscv_phys_test.py @@ -8,14 +8,15 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS from sram_factory import factory import debug -@unittest.skip("SKIPPING 50_riscv_phys_test") + +#@unittest.skip("SKIPPING 50_riscv_phys_test") class riscv_phys_test(openram_test): def runTest(self): @@ -26,10 +27,11 @@ class riscv_phys_test(openram_test): OPTS.num_rw_ports = 1 OPTS.num_r_ports = 1 OPTS.num_w_ports = 0 + OPTS.local_array_size = 16 globals.setup_bitcell() - OPTS.route_supplies=False - OPTS.perimeter_pins=False - + OPTS.route_supplies = False + OPTS.perimeter_pins = False + c = sram_config(word_size=32, write_size=8, num_words=256, diff --git a/compiler/tests/regress.py b/compiler/tests/regress.py index 3fd5d6eb..383ef8fa 100755 --- a/compiler/tests/regress.py +++ b/compiler/tests/regress.py @@ -9,7 +9,7 @@ import re import unittest -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals @@ -47,12 +47,12 @@ suite = unittest.TestSuite() load = unittest.defaultTestLoader.loadTestsFromModule suite.addTests(map(load, modules)) -test_runner = unittest.TextTestRunner(verbosity=2,stream=sys.stderr) +test_runner = unittest.TextTestRunner(verbosity=2, stream=sys.stderr) test_result = test_runner.run(suite) import verify verify.print_drc_stats() verify.print_lvs_stats() -verify.print_pex_stats() +verify.print_pex_stats() sys.exit(not test_result.wasSuccessful()) diff --git a/compiler/tests/testutils.py b/compiler/tests/testutils.py index a8da9fb4..9188a4f9 100644 --- a/compiler/tests/testutils.py +++ b/compiler/tests/testutils.py @@ -82,6 +82,8 @@ class openram_test(unittest.TestCase): output = OPTS.openram_temp + a.name + ".pex.netlist" tempspice = "{0}{1}.sp".format(OPTS.openram_temp, a.name) tempgds = "{0}{1}.gds".format(OPTS.openram_temp, a.name) + + a.gds_write(tempgds) import verify result=verify.run_pex(a.name, tempgds, tempspice, output=output, final_verification=False) diff --git a/compiler/verify/calibre.py b/compiler/verify/calibre.py index 965e3557..d51e71b6 100644 --- a/compiler/verify/calibre.py +++ b/compiler/verify/calibre.py @@ -101,7 +101,7 @@ def write_calibre_lvs_script(cell_name, final_verification, gds_name, sp_name): # FIXME: Remove when vdd/gnd connected #'lvsAbortOnSupplyError' : 0 - if not final_verification: + if not final_verification or not OPTS.route_supplies: lvs_runset['cmnVConnectReport']=1 lvs_runset['cmnVConnectNamesState']='SOME' lvs_runset['cmnVConnectNames']='vdd gnd' diff --git a/compiler/verify/magic.py b/compiler/verify/magic.py index 930316ff..ece741cd 100644 --- a/compiler/verify/magic.py +++ b/compiler/verify/magic.py @@ -100,7 +100,7 @@ def write_magic_script(cell_name, extract=False, final_verification=False): pre = "#" else: pre = "" - if final_verification: + if final_verification and OPTS.route_supplies: f.write(pre + "extract unique all\n".format(cell_name)) # Hack to work around unit scales in SkyWater if OPTS.tech_name=="sky130": @@ -295,11 +295,8 @@ def run_pex(name, gds_name, sp_name, output=None, final_verification=False): global num_pex_runs num_pex_runs += 1 - #debug.warning("PEX using magic not implemented.") - #return 1 os.chdir(OPTS.openram_temp) - from tech import drc if output == None: output = name + ".pex.netlist" @@ -312,17 +309,11 @@ def run_pex(name, gds_name, sp_name, output=None, final_verification=False): # pex_fix did run the pex using a script while dev orignial method # use batch mode. # the dev old code using batch mode does not run and is split into functions - #pex_runset = write_batch_pex_rule(gds_name,name,sp_name,output) - pex_runset = write_script_pex_rule(gds_name,name,output) + pex_runset = write_script_pex_rule(gds_name, name, output) errfile = "{0}{1}.pex.err".format(OPTS.openram_temp, name) outfile = "{0}{1}.pex.out".format(OPTS.openram_temp, name) - # bash mode command from dev branch - #batch_cmd = "{0} -gui -pex {1}pex_runset -batch 2> {2} 1> {3}".format(OPTS.pex_exe, - # OPTS.openram_temp, - # errfile, - # outfile) script_cmd = "{0} 2> {1} 1> {2}".format(pex_runset, errfile, outfile) @@ -334,8 +325,8 @@ def run_pex(name, gds_name, sp_name, output=None, final_verification=False): pex_nelist = open(output, 'r') s = pex_nelist.read() pex_nelist.close() - s = s.replace('pfet','p') - s = s.replace('nfet','n') + s = s.replace('pfet', 'p') + s = s.replace('nfet', 'n') f = open(output, 'w') f.write(s) f.close() @@ -345,12 +336,13 @@ def run_pex(name, gds_name, sp_name, output=None, final_verification=False): results = f.readlines() f.close() out_errors = find_error(results) - debug.check(os.path.isfile(output),"Couldn't find PEX extracted output.") + debug.check(os.path.isfile(output), "Couldn't find PEX extracted output.") - correct_port(name,output,sp_name) + correct_port(name, output, sp_name) return out_errors -def write_batch_pex_rule(gds_name,name,sp_name,output): + +def write_batch_pex_rule(gds_name, name, sp_name, output): """ The dev branch old batch mode runset 2. magic can perform extraction with the following: @@ -394,7 +386,8 @@ def write_batch_pex_rule(gds_name,name,sp_name,output): f.close() return file -def write_script_pex_rule(gds_name,cell_name,output): + +def write_script_pex_rule(gds_name, cell_name, output): global OPTS run_file = OPTS.openram_temp + "run_pex.sh" f = open(run_file, "w") @@ -412,23 +405,29 @@ def write_script_pex_rule(gds_name,cell_name,output): pre = "#" else: pre = "" - f.write(pre+"extract\n".format(cell_name)) - f.write(pre+"ext2spice hierarchy off\n") - f.write(pre+"ext2spice format ngspice\n") - f.write(pre+"ext2spice renumber off\n") - f.write(pre+"ext2spice scale off\n") - f.write(pre+"ext2spice blackbox on\n") - f.write(pre+"ext2spice subcircuit top on\n") - f.write(pre+"ext2spice global off\n") - f.write(pre+"ext2spice {}\n".format(cell_name)) + f.write(pre + "extract\n") + f.write(pre + "ext2sim labels on\n") + f.write(pre + "ext2sim\n") + f.write(pre + "extresist simplify off\n") + f.write(pre + "extresist all\n") + f.write(pre + "ext2spice hierarchy off\n") + f.write(pre + "ext2spice format ngspice\n") + f.write(pre + "ext2spice renumber off\n") + f.write(pre + "ext2spice scale off\n") + f.write(pre + "ext2spice blackbox on\n") + f.write(pre + "ext2spice subcircuit top on\n") + f.write(pre + "ext2spice global off\n") + f.write(pre + "ext2spice extresist on\n") + f.write(pre + "ext2spice {}\n".format(cell_name)) f.write("quit -noprompt\n") f.write("eof\n") - f.write("mv {0}.spice {1}\n".format(cell_name,output)) + f.write("mv {0}.spice {1}\n".format(cell_name, output)) f.close() os.system("chmod u+x {}".format(run_file)) return run_file + def find_error(results): # Errors begin with "ERROR:" test = re.compile("ERROR:") @@ -438,6 +437,7 @@ def find_error(results): out_errors = len(stdouterrors) return out_errors + def correct_port(name, output_file_name, ref_file_name): pex_file = open(output_file_name, "r") contents = pex_file.read() @@ -456,9 +456,9 @@ def correct_port(name, output_file_name, ref_file_name): for bank in range(OPTS.num_banks): for bank in range(OPTS.num_banks): row = int(OPTS.num_words / OPTS.words_per_row) - 1 - col = int(OPTS.word_size * OPTS.words_per_row) - 1 - bitcell_list += "bitcell_Q_b{0}_r{1}_c{2} ".format(bank,row,col) - bitcell_list += "bitcell_Q_bar_b{0}_r{1}_c{2} ".format(bank,row,col) + col = int(OPTS.word_size * OPTS.words_per_row) - 1 + bitcell_list += "bitcell_Q_b{0}_r{1}_c{2} ".format(bank, row, col) + bitcell_list += "bitcell_Q_bar_b{0}_r{1}_c{2} ".format(bank, row, col) for col in range(OPTS.word_size * OPTS.words_per_row): for port in range(OPTS.num_r_ports + OPTS.num_w_ports + OPTS.num_rw_ports): bitcell_list += "bl{0}_{1} ".format(bank, col) @@ -484,13 +484,18 @@ def correct_port(name, output_file_name, ref_file_name): # write the new pex file with info in the memory output_file = open(output_file_name, "w") output_file.write(part1) - output_file.write(circuit_title+'\n') + output_file.write(circuit_title + '\n') output_file.write(part2) output_file.close() + def print_drc_stats(): - debug.info(1,"DRC runs: {0}".format(num_drc_runs)) + debug.info(1, "DRC runs: {0}".format(num_drc_runs)) + + def print_lvs_stats(): - debug.info(1,"LVS runs: {0}".format(num_lvs_runs)) + debug.info(1, "LVS runs: {0}".format(num_lvs_runs)) + + def print_pex_stats(): - debug.info(1,"PEX runs: {0}".format(num_pex_runs)) + debug.info(1, "PEX runs: {0}".format(num_pex_runs)) diff --git a/technology/scn4m_subm/tech/setup.tcl b/technology/scn4m_subm/tech/setup.tcl index 09bbea27..40de5ac1 100644 --- a/technology/scn4m_subm/tech/setup.tcl +++ b/technology/scn4m_subm/tech/setup.tcl @@ -6,6 +6,9 @@ equate class {-circuit1 pfet} {-circuit2 p} flatten class {-circuit1 dummy_cell_6t} flatten class {-circuit1 dummy_cell_1rw_1r} flatten class {-circuit1 dummy_cell_1w_1r} +flatten class {-circuit1 dummy_pbitcell} +flatten class {-circuit1 dummy_pbitcell_0} +flatten class {-circuit1 dummy_pbitcell_1} flatten class {-circuit1 pbitcell} flatten class {-circuit1 pbitcell_0} flatten class {-circuit1 pbitcell_1}