From 4f28295e20fc24f74c5f4432e69594d84388243f Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Fri, 19 Apr 2019 01:27:06 -0700 Subject: [PATCH 01/22] Added initial graph for correct naming --- compiler/base/design.py | 6 +-- compiler/base/graph_util.py | 66 +++++++++++++++++++++++++++++++ compiler/base/hierarchy_design.py | 34 ++++++++++++++++ compiler/pgates/pinv.py | 1 - compiler/pgates/ptx.py | 13 ++++++ 5 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 compiler/base/graph_util.py diff --git a/compiler/base/design.py b/compiler/base/design.py index 4e76b40a..243304fe 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -18,7 +18,7 @@ class design(hierarchy_design): self.setup_drc_constants() self.setup_multiport_constants() - + self.m1_pitch = max(contact.m1m2.width,contact.m1m2.height) + max(self.m1_space, self.m2_space) self.m2_pitch = max(contact.m2m3.width,contact.m2m3.height) + max(self.m2_space, self.m3_space) self.m3_pitch = max(contact.m3m4.width,contact.m3m4.height) + max(self.m3_space, self.m4_space) @@ -86,8 +86,8 @@ class design(hierarchy_design): for port in range(OPTS.num_r_ports): self.read_ports.append(port_number) self.readonly_ports.append(port_number) - port_number += 1 - + port_number += 1 + def analytical_power(self, corner, load): """ Get total power of a module """ total_module_power = self.return_power() diff --git a/compiler/base/graph_util.py b/compiler/base/graph_util.py new file mode 100644 index 00000000..b795e67f --- /dev/null +++ b/compiler/base/graph_util.py @@ -0,0 +1,66 @@ +import os +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 graph(): + """Implements a directed graph""" + + def __init__(self): + self.graph = defaultdict(list) + + def add_edge(self, u, v): + """Adds edge to graph. Nodes added as well if they do not exist.""" + if v not in self.graph[u]: + self.graph[u].append(v) + + def add_node(self, u): + """Add node to graph with no edges""" + if not u in self.graph: + self.graph[u] = [] + + def remove_edges(self, node): + """Helper function to remove edges, useful for removing vdd/gnd""" + self.graph[node] = [] + + def printAllPaths(self,s, d): + + # Mark all the vertices as not visited + visited = set() + + # Create an array to store paths + path = [] + + # Call the recursive helper function to print all paths + self.printAllPathsUtil(s, d,visited, path) + + def printAllPathsUtil(self, u, d, visited, path): + + # Mark the current node as visited and store in path + visited.add(u) + path.append(u) + + # If current vertex is same as destination, then print + # current path[] + if u == d: + debug.info(1,"{}".format(path)) + else: + # If current vertex is not destination + #Recur for all the vertices adjacent to this vertex + for i in self.graph[u]: + if i not in visited: + self.printAllPathsUtil(i, d, visited, path) + + # Remove current vertex from path[] and mark it as unvisited + path.pop() + visited.remove(u) + + def __str__(self): + """ override print function output """ + return "Nodes: {}\nEdges:{} ".format(list(self.graph), self.graph) \ No newline at end of file diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index 6a6003a2..607859a4 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -5,6 +5,7 @@ import verify import debug import os from globals import OPTS +import graph_util total_drc_errors = 0 total_lvs_errors = 0 @@ -98,6 +99,39 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): os.remove(tempspice) os.remove(tempgds) + #Example graph run + # graph = graph_util.graph() + # pins = ['A','Z','vdd','gnd'] + # d.build_graph(graph,"Xpdriver",pins) + # graph.remove_edges('vdd') + # graph.remove_edges('gnd') + # debug.info(1,"{}".format(graph)) + # graph.printAllPaths('A', 'Z') + + def build_graph(self, graph, inst_name, port_nets): + """Recursively create graph from instances in module.""" + + #Translate port names to external nets + if len(port_nets) != len(self.pins): + debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,self.pins),1) + port_dict = {i:j for i,j in zip(self.pins, port_nets)} + debug.info(1, "Instance name={}".format(inst_name)) + for subinst, conns in zip(self.insts, self.conns): + debug.info(1, "Sub-Instance={}".format(subinst)) + subinst_name = inst_name+'.X'+subinst.name + subinst_ports = self.translate_nets(conns, port_dict, inst_name) + subinst.mod.build_graph(graph, subinst_name, subinst_ports) + + def translate_nets(self, subinst_ports, port_dict, inst_name): + """Converts connection names to their spice hierarchy equivalent""" + converted_conns = [] + for conn in subinst_ports: + if conn in port_dict: + converted_conns.append(port_dict[conn]) + else: + converted_conns.append("{}.{}".format(inst_name, conn)) + return converted_conns + def __str__(self): """ override print function output """ return "design: " + self.name diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index f00e9af7..c66c944c 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -38,7 +38,6 @@ class pinv(pgate.pgate): self.create_netlist() if not OPTS.netlist_only: self.create_layout() - # for run-time, we won't check every transitor DRC/LVS independently # but this may be uncommented for debug purposes #self.DRC_LVS() diff --git a/compiler/pgates/ptx.py b/compiler/pgates/ptx.py index 3a127454..7e80a7c9 100644 --- a/compiler/pgates/ptx.py +++ b/compiler/pgates/ptx.py @@ -356,3 +356,16 @@ class ptx(design.design): def get_cin(self): """Returns the relative gate cin of the tx""" return self.tx_width/drc("minwidth_tx") + + def build_graph(self, graph, inst_name, port_nets): + """Adds ptx edges to graph. Lowest graph level.""" + #The ptx has four connections: (S)ource, (G)ate, (D)rain, (B)ody. The positions in spice + #are hardcoded which is represented here as well. + #Edges are connected as follows: G->S, G->D, D<->S. Body not represented in graph. + if len(port_nets) != 4: + debug.error("Transistor has non-standard connections.",1) + graph.add_edge(port_nets[1],port_nets[0]) + graph.add_edge(port_nets[1],port_nets[2]) + graph.add_edge(port_nets[0],port_nets[2]) + graph.add_edge(port_nets[2],port_nets[0]) + \ No newline at end of file From e292767166b7f44a41b4dfe0d93d58dc91d59acc Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Wed, 24 Apr 2019 14:23:22 -0700 Subject: [PATCH 02/22] Added graph creation and functions in base class and lower level modules. --- compiler/base/hierarchy_design.py | 13 ++++++++++--- compiler/bitcells/bitcell.py | 11 ++++++++++- compiler/bitcells/bitcell_1rw_1r.py | 12 ++++++++++++ compiler/bitcells/bitcell_1w_1r.py | 11 +++++++++++ compiler/bitcells/pbitcell.py | 12 ++++++++++++ compiler/bitcells/replica_bitcell.py | 9 +++++++++ compiler/bitcells/replica_bitcell_1rw_1r.py | 11 +++++++++++ compiler/bitcells/replica_bitcell_1w_1r.py | 11 +++++++++++ compiler/{base => characterizer}/graph_util.py | 5 ++++- compiler/modules/bank.py | 6 ++++++ compiler/modules/bitcell_array.py | 10 ++++++++++ compiler/modules/dff.py | 8 ++++++++ compiler/modules/hierarchical_decoder.py | 1 + compiler/modules/precharge_array.py | 1 + compiler/modules/sense_amp.py | 13 ++++++++++++- compiler/modules/tri_gate.py | 12 +++++++++++- compiler/modules/write_driver.py | 12 ++++++++++++ compiler/pgates/pnand2.py | 9 +++++++++ compiler/sram_1bank.py | 16 ++++++++++++++++ compiler/tests/05_bitcell_1rw_1r_array_test.py | 2 +- compiler/tests/05_bitcell_array_test.py | 2 +- compiler/tests/12_tri_gate_array_test.py | 2 +- compiler/tests/21_hspice_delay_test.py | 17 +++++++++++++++++ 23 files changed, 196 insertions(+), 10 deletions(-) rename compiler/{base => characterizer}/graph_util.py (90%) diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index 607859a4..a8d0f5bf 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -24,7 +24,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): self.name = name hierarchy_layout.layout.__init__(self, name) hierarchy_spice.spice.__init__(self, name) - + self.init_graph_params() def get_layout_pins(self,inst): """ Return a map of pin locations of the instance offset """ @@ -100,6 +100,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): os.remove(tempgds) #Example graph run + # import graph_util # graph = graph_util.graph() # pins = ['A','Z','vdd','gnd'] # d.build_graph(graph,"Xpdriver",pins) @@ -108,6 +109,11 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): # debug.info(1,"{}".format(graph)) # graph.printAllPaths('A', 'Z') + def init_graph_params(self): + """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.""" @@ -115,9 +121,10 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): if len(port_nets) != len(self.pins): debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,self.pins),1) port_dict = {i:j for i,j in zip(self.pins, port_nets)} - debug.info(1, "Instance name={}".format(inst_name)) + debug.info(3, "Instance name={}".format(inst_name)) for subinst, conns in zip(self.insts, self.conns): - debug.info(1, "Sub-Instance={}".format(subinst)) + if subinst in self.graph_inst_exclude: + continue subinst_name = inst_name+'.X'+subinst.name subinst_ports = self.translate_nets(conns, port_dict, inst_name) subinst.mod.build_graph(graph, subinst_name, subinst_ports) diff --git a/compiler/bitcells/bitcell.py b/compiler/bitcells/bitcell.py index 3f1d02d3..798ee3f9 100644 --- a/compiler/bitcells/bitcell.py +++ b/compiler/bitcells/bitcell.py @@ -24,7 +24,7 @@ class bitcell(design.design): self.width = bitcell.width self.height = bitcell.height self.pin_map = bitcell.pin_map - + def analytical_delay(self, corner, slew, load=0, swing = 0.5): parasitic_delay = 1 size = 0.5 #This accounts for bitline being drained thought the access TX and internal node @@ -74,3 +74,12 @@ class bitcell(design.design): #Calculated in the tech file by summing the widths of all the related gates and dividing by the minimum width. access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] return 2*access_tx_cin + + def build_graph(self, graph, inst_name, port_nets): + """Adds edges to graph. Handmade cells must implement this manually.""" + #The bitcell has 5 net ports hard-coded in self.pin_names. The edges + #are based on the hard-coded name positions. + # The edges added are: wl->bl, wl->br. + # Internal nodes of the handmade cell not considered, only ports. vdd/gnd ignored for graph. + graph.add_edge(port_nets[2],port_nets[0]) + graph.add_edge(port_nets[2],port_nets[1]) \ No newline at end of file diff --git a/compiler/bitcells/bitcell_1rw_1r.py b/compiler/bitcells/bitcell_1rw_1r.py index e597167f..58b0ca98 100644 --- a/compiler/bitcells/bitcell_1rw_1r.py +++ b/compiler/bitcells/bitcell_1rw_1r.py @@ -99,3 +99,15 @@ class bitcell_1rw_1r(design.design): #FIXME: sizing is not accurate with the handmade cell. Change once cell widths are fixed. access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] return 2*access_tx_cin + + def build_graph(self, graph, inst_name, port_nets): + """Adds edges to graph. Handmade cells must implement this manually.""" + #The bitcell has 8 net ports hard-coded in self.pin_names. The edges + #are based on the hard-coded name positions. + # The edges added are: wl0->bl0, wl0->br0, wl1->bl1, wl1->br1. + # Internal nodes of the handmade cell not considered, only ports. vdd/gnd ignored for graph. + graph.add_edge(port_nets[4],port_nets[0]) + graph.add_edge(port_nets[4],port_nets[1]) + graph.add_edge(port_nets[5],port_nets[2]) + graph.add_edge(port_nets[5],port_nets[3]) + diff --git a/compiler/bitcells/bitcell_1w_1r.py b/compiler/bitcells/bitcell_1w_1r.py index cf487fba..3d1febdd 100644 --- a/compiler/bitcells/bitcell_1w_1r.py +++ b/compiler/bitcells/bitcell_1w_1r.py @@ -99,3 +99,14 @@ class bitcell_1w_1r(design.design): #FIXME: sizing is not accurate with the handmade cell. Change once cell widths are fixed. access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] return 2*access_tx_cin + + def build_graph(self, graph, inst_name, port_nets): + """Adds edges to graph. Handmade cells must implement this manually.""" + #The bitcell has 8 net ports hard-coded in self.pin_names. The edges + #are based on the hard-coded name positions. + # The edges added are: wl0->bl0, wl0->br0, wl1->bl1, wl1->br1. + # Internal nodes of the handmade cell not considered, only ports. vdd/gnd ignored for graph. + graph.add_edge(port_nets[4],port_nets[0]) + graph.add_edge(port_nets[4],port_nets[1]) + graph.add_edge(port_nets[5],port_nets[2]) + graph.add_edge(port_nets[5],port_nets[3]) diff --git a/compiler/bitcells/pbitcell.py b/compiler/bitcells/pbitcell.py index 94c294db..cff7b5cf 100644 --- a/compiler/bitcells/pbitcell.py +++ b/compiler/bitcells/pbitcell.py @@ -893,3 +893,15 @@ class pbitcell(design.design): access_tx_cin = self.readwrite_nmos.get_cin() return 2*access_tx_cin + def build_graph(self, graph, inst_name, port_nets): + """Adds edges to graph. Done manually to make the graph acyclic.""" + #The base graph function can make this but it would contain loops and path + #searches would essentially be useless. + # Edges added wl->bl, wl->br for every port + + num_wl = len(self.rw_wl_names) + len(self.w_wl_names) + len(self.r_wl_names) + wl_pos = 2*num_wl #there are this many bitlines nets before the wls in the port list + for i in range(num_wl): + bl_pos = i*2 + graph.add_edge(port_nets[wl_pos+i],port_nets[bl_pos]) + graph.add_edge(port_nets[wl_pos+i],port_nets[bl_pos+1]) diff --git a/compiler/bitcells/replica_bitcell.py b/compiler/bitcells/replica_bitcell.py index d8b8b76e..8d77ff7b 100644 --- a/compiler/bitcells/replica_bitcell.py +++ b/compiler/bitcells/replica_bitcell.py @@ -29,3 +29,12 @@ class replica_bitcell(design.design): #Calculated in the tech file by summing the widths of all the related gates and dividing by the minimum width. access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] return 2*access_tx_cin + + def build_graph(self, graph, inst_name, port_nets): + """Adds edges to graph. Handmade cells must implement this manually.""" + #The bitcell has 5 net ports hard-coded in self.pin_names. The edges + #are based on the hard-coded name positions. + # The edges added are: wl->bl, wl->br. + # Internal nodes of the handmade cell not considered, only ports. vdd/gnd ignored for graph. + graph.add_edge(port_nets[2],port_nets[0]) + graph.add_edge(port_nets[2],port_nets[1]) \ No newline at end of file diff --git a/compiler/bitcells/replica_bitcell_1rw_1r.py b/compiler/bitcells/replica_bitcell_1rw_1r.py index 8f7b3b38..67871525 100644 --- a/compiler/bitcells/replica_bitcell_1rw_1r.py +++ b/compiler/bitcells/replica_bitcell_1rw_1r.py @@ -30,3 +30,14 @@ class replica_bitcell_1rw_1r(design.design): #FIXME: sizing is not accurate with the handmade cell. Change once cell widths are fixed. access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] return 2*access_tx_cin + + def build_graph(self, graph, inst_name, port_nets): + """Adds edges to graph. Handmade cells must implement this manually.""" + #The bitcell has 8 net ports hard-coded in self.pin_names. The edges + #are based on the hard-coded name positions. + # The edges added are: wl0->bl0, wl0->br0, wl1->bl1, wl1->br1. + # Internal nodes of the handmade cell not considered, only ports. vdd/gnd ignored for graph. + graph.add_edge(port_nets[4],port_nets[0]) + graph.add_edge(port_nets[4],port_nets[1]) + graph.add_edge(port_nets[5],port_nets[2]) + graph.add_edge(port_nets[5],port_nets[3]) \ No newline at end of file diff --git a/compiler/bitcells/replica_bitcell_1w_1r.py b/compiler/bitcells/replica_bitcell_1w_1r.py index 6f59adec..1f5c3266 100644 --- a/compiler/bitcells/replica_bitcell_1w_1r.py +++ b/compiler/bitcells/replica_bitcell_1w_1r.py @@ -30,3 +30,14 @@ class replica_bitcell_1w_1r(design.design): #FIXME: sizing is not accurate with the handmade cell. Change once cell widths are fixed. access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] return 2*access_tx_cin + + def build_graph(self, graph, inst_name, port_nets): + """Adds edges to graph. Handmade cells must implement this manually.""" + #The bitcell has 8 net ports hard-coded in self.pin_names. The edges + #are based on the hard-coded name positions. + # The edges added are: wl0->bl0, wl0->br0, wl1->bl1, wl1->br1. + # Internal nodes of the handmade cell not considered, only ports. vdd/gnd ignored for graph. + graph.add_edge(port_nets[4],port_nets[0]) + graph.add_edge(port_nets[4],port_nets[1]) + graph.add_edge(port_nets[5],port_nets[2]) + graph.add_edge(port_nets[5],port_nets[3]) \ No newline at end of file diff --git a/compiler/base/graph_util.py b/compiler/characterizer/graph_util.py similarity index 90% rename from compiler/base/graph_util.py rename to compiler/characterizer/graph_util.py index b795e67f..c20f3c91 100644 --- a/compiler/base/graph_util.py +++ b/compiler/characterizer/graph_util.py @@ -36,9 +36,11 @@ class graph(): # Create an array to store paths path = [] + self.path_count = 0 # Call the recursive helper function to print all paths - self.printAllPathsUtil(s, d,visited, path) + self.printAllPathsUtil(s, d,visited, path) + debug.info(1, "Paths found={}".format(self.path_count)) def printAllPathsUtil(self, u, d, visited, path): @@ -50,6 +52,7 @@ class graph(): # current path[] if u == d: debug.info(1,"{}".format(path)) + self.path_count+=1 else: # If current vertex is not destination #Recur for all the vertices adjacent to this vertex diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 5b556660..aaa44b08 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -1279,3 +1279,9 @@ class bank(design.design): """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. return self.sense_amp_array.get_en_cin() + + def graph_exclude_precharge(self): + """Precharge adds a loop between bitlines, can be excluded to reduce complexity""" + for inst in self.precharge_array_inst: + if inst != None: + self.graph_inst_exclude.add(inst) \ No newline at end of file diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index 79c68241..a06a7e90 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -193,3 +193,13 @@ class bitcell_array(design.design): 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""" + #Function is not robust with column mux configurations + for row in range(self.row_size): + if row == targ_row: + continue + self.graph_inst_exclude.add(self.cell_inst[row,targ_col]) + + \ No newline at end of file diff --git a/compiler/modules/dff.py b/compiler/modules/dff.py index 753ae41a..9b9dd0fe 100644 --- a/compiler/modules/dff.py +++ b/compiler/modules/dff.py @@ -50,3 +50,11 @@ class dff(design.design): #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 to graph. Handmade cells must implement this manually.""" + #The cell has 5 net ports hard-coded in self.pin_names. The edges + #are based on the hard-coded name positions. + # The edges added are: clk->Q. + # Internal nodes of the handmade cell not considered, only ports. vdd/gnd ignored for graph. + graph.add_edge(port_nets[2],port_nets[1]) + \ No newline at end of file diff --git a/compiler/modules/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index bd17b37e..6b5e6050 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -621,3 +621,4 @@ class hierarchical_decoder(design.design): else: pre = self.pre3_8 return pre.input_load() + diff --git a/compiler/modules/precharge_array.py b/compiler/modules/precharge_array.py index 67581e1a..04f212fb 100644 --- a/compiler/modules/precharge_array.py +++ b/compiler/modules/precharge_array.py @@ -107,3 +107,4 @@ class precharge_array(design.design): #Assume single port precharge_en_cin = self.pc_cell.get_en_cin() return precharge_en_cin*self.columns + \ No newline at end of file diff --git a/compiler/modules/sense_amp.py b/compiler/modules/sense_amp.py index 8d739041..431fc1b5 100644 --- a/compiler/modules/sense_amp.py +++ b/compiler/modules/sense_amp.py @@ -51,4 +51,15 @@ class sense_amp(design.design): 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 \ No newline at end of file + return 2*pmos_cin + nmos_cin + + def build_graph(self, graph, inst_name, port_nets): + """Adds edges to graph. Handmade cells must implement this manually.""" + #The cell has 6 net ports hard-coded in self.pin_names. The edges + #are based on the hard-coded name positions. + # The edges added are: en->dout, bl->dout + # br->dout not included to reduce complexity + # Internal nodes of the handmade cell not considered, only ports. vdd/gnd ignored for graph. + graph.add_edge(port_nets[0],port_nets[2]) + graph.add_edge(port_nets[3],port_nets[2]) + \ No newline at end of file diff --git a/compiler/modules/tri_gate.py b/compiler/modules/tri_gate.py index 688b3644..6a1efdea 100644 --- a/compiler/modules/tri_gate.py +++ b/compiler/modules/tri_gate.py @@ -10,7 +10,7 @@ class tri_gate(design.design): netlist should be available in the technology library. """ - pin_names = ["in", "en", "en_bar", "out", "gnd", "vdd"] + pin_names = ["in", "out", "en", "en_bar", "gnd", "vdd"] (width,height) = utils.get_libcell_size("tri_gate", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "tri_gate", GDS["unit"]) @@ -42,3 +42,13 @@ class tri_gate(design.design): def input_load(self): return 9*spice["min_tx_gate_c"] + def build_graph(self, graph, inst_name, port_nets): + """Adds edges to graph. Handmade cells must implement this manually.""" + #The cell has 6 net ports hard-coded in self.pin_names. The edges + #are based on the hard-coded name positions. + # The edges added are: en->out, en_bar->out, in->out. + # A liberal amount of edges were added, may be reduced later for complexity. + # Internal nodes of the handmade cell not considered, only ports. vdd/gnd ignored for graph. + graph.add_edge(port_nets[0],port_nets[1]) + graph.add_edge(port_nets[2],port_nets[1]) + graph.add_edge(port_nets[3],port_nets[1]) \ No newline at end of file diff --git a/compiler/modules/write_driver.py b/compiler/modules/write_driver.py index 34c51245..d20a0135 100644 --- a/compiler/modules/write_driver.py +++ b/compiler/modules/write_driver.py @@ -28,3 +28,15 @@ class write_driver(design.design): """Get the relative capacitance of a single input""" # This is approximated from SCMOS. It has roughly 5 3x transistor gates. return 5*3 + + def build_graph(self, graph, inst_name, port_nets): + """Adds edges to graph. Handmade cells must implement this manually.""" + #The cell has 6 net ports hard-coded in self.pin_names. The edges + #are based on the hard-coded name positions. + # The edges added are: din->bl, din->br, en->bl, en->br + # A liberal amount of edges were added, may be reduced later for complexity. + # Internal nodes of the handmade cell not considered, only ports. vdd/gnd ignored for graph. + graph.add_edge(port_nets[0],port_nets[1]) + graph.add_edge(port_nets[0],port_nets[2]) + graph.add_edge(port_nets[3],port_nets[1]) + graph.add_edge(port_nets[3],port_nets[2]) \ No newline at end of file diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index 95a9bc28..eb2484af 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -32,6 +32,8 @@ class pnand2(pgate.pgate): if not OPTS.netlist_only: self.create_layout() + #For characterization purposes only + self.exclude_nmos_from_graph() def create_netlist(self): self.add_pins() @@ -259,3 +261,10 @@ class pnand2(pgate.pgate): """ parasitic_delay = 2 return logical_effort.logical_effort(self.name, self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise) + + def exclude_nmos_from_graph(self): + """Exclude the nmos TXs from the graph to reduce complexity""" + #The pull-down network has an internal net which causes 2 different in->out paths + #Removing them simplifies generic path searching. + self.graph_inst_exclude.add(self.nmos1_inst) + self.graph_inst_exclude.add(self.nmos2_inst) diff --git a/compiler/sram_1bank.py b/compiler/sram_1bank.py index cbfec653..abe08f40 100644 --- a/compiler/sram_1bank.py +++ b/compiler/sram_1bank.py @@ -305,3 +305,19 @@ class sram_1bank(sram_base): self.add_label(text=n, layer=pin.layer, offset=pin.center()) + + def graph_exclude_data_dff(self): + """Removes data dff from search graph. """ + #Data 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) + + def graph_exclude_addr_dff(self): + """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) + + if self.col_addr_dff: + for inst in self.col_addr_dff_insts: + self.graph_inst_exclude.add(inst) diff --git a/compiler/tests/05_bitcell_1rw_1r_array_test.py b/compiler/tests/05_bitcell_1rw_1r_array_test.py index 9ea0cc32..865b10b9 100755 --- a/compiler/tests/05_bitcell_1rw_1r_array_test.py +++ b/compiler/tests/05_bitcell_1rw_1r_array_test.py @@ -26,7 +26,7 @@ class bitcell_1rw_1r_array_test(openram_test): OPTS.num_w_ports = 0 a = factory.create(module_type="bitcell_array", cols=4, rows=4) self.local_check(a) - + globals.end_openram() # run the test from the command line diff --git a/compiler/tests/05_bitcell_array_test.py b/compiler/tests/05_bitcell_array_test.py index 82eaf256..eef69fd6 100755 --- a/compiler/tests/05_bitcell_array_test.py +++ b/compiler/tests/05_bitcell_array_test.py @@ -22,7 +22,7 @@ class array_test(openram_test): debug.info(2, "Testing 4x4 array for 6t_cell") a = factory.create(module_type="bitcell_array", cols=4, rows=4) self.local_check(a) - + globals.end_openram() # run the test from the command line diff --git a/compiler/tests/12_tri_gate_array_test.py b/compiler/tests/12_tri_gate_array_test.py index 04ef1369..cff8595a 100755 --- a/compiler/tests/12_tri_gate_array_test.py +++ b/compiler/tests/12_tri_gate_array_test.py @@ -20,7 +20,7 @@ class tri_gate_array_test(openram_test): debug.info(1, "Testing tri_gate_array for columns=8, word_size=8") a = factory.create(module_type="tri_gate_array", columns=8, word_size=8) self.local_check(a) - + debug.info(1, "Testing tri_gate_array for columns=16, word_size=8") a = factory.create(module_type="tri_gate_array", columns=16, word_size=8) self.local_check(a) diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py index abe65b02..fe9c4951 100755 --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -33,6 +33,23 @@ class timing_sram_test(openram_test): c.recompute_sizes() debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank") s = factory.create(module_type="sram", sram_config=c) + + #Exclude things known not to be in critical path. + #Intended for characterizing read paths. Somewhat hacky implementation + s.s.bank.bitcell_array.graph_exclude_bits(15,0) + s.s.bank.graph_exclude_precharge() + s.s.graph_exclude_addr_dff() + s.s.graph_exclude_data_dff() + + debug.info(1,'pins={}'.format(s.s.pins)) + import graph_util + graph = graph_util.graph() + pins=['DIN0[0]', 'ADDR0[0]', 'ADDR0[1]', 'ADDR0[2]', 'ADDR0[3]', 'csb0', 'web0', 'clk0', 'DOUT0[0]', 'vdd', 'gnd'] + s.s.build_graph(graph,"Xsram",pins) + graph.remove_edges('vdd') + graph.remove_edges('gnd') + debug.info(1,"{}".format(graph)) + graph.printAllPaths('clk0', 'DOUT0[0]') tempspice = OPTS.openram_temp + "temp.sp" s.sp_write(tempspice) From f35385f42ac6c440a618828b2459d6e73ef84e83 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Wed, 24 Apr 2019 23:51:09 -0700 Subject: [PATCH 03/22] Cleaned up names, added exclusions to narrow paths for analysis. --- compiler/{characterizer => base}/graph_util.py | 8 ++++---- compiler/base/hierarchy_design.py | 2 +- compiler/modules/control_logic.py | 6 +++++- compiler/sram_1bank.py | 8 +++++++- compiler/tests/21_hspice_delay_test.py | 5 +++-- 5 files changed, 20 insertions(+), 9 deletions(-) rename compiler/{characterizer => base}/graph_util.py (89%) diff --git a/compiler/characterizer/graph_util.py b/compiler/base/graph_util.py similarity index 89% rename from compiler/characterizer/graph_util.py rename to compiler/base/graph_util.py index c20f3c91..eb40d55b 100644 --- a/compiler/characterizer/graph_util.py +++ b/compiler/base/graph_util.py @@ -29,7 +29,7 @@ class graph(): """Helper function to remove edges, useful for removing vdd/gnd""" self.graph[node] = [] - def printAllPaths(self,s, d): + def print_all_paths(self,s, d): # Mark all the vertices as not visited visited = set() @@ -39,10 +39,10 @@ class graph(): self.path_count = 0 # Call the recursive helper function to print all paths - self.printAllPathsUtil(s, d,visited, path) + self.print_all_paths_util(s, d,visited, path) debug.info(1, "Paths found={}".format(self.path_count)) - def printAllPathsUtil(self, u, d, visited, path): + def print_all_paths_util(self, u, d, visited, path): # Mark the current node as visited and store in path visited.add(u) @@ -58,7 +58,7 @@ class graph(): #Recur for all the vertices adjacent to this vertex for i in self.graph[u]: if i not in visited: - self.printAllPathsUtil(i, d, visited, path) + self.print_all_paths_util(i, d, visited, path) # Remove current vertex from path[] and mark it as unvisited path.pop() diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index a8d0f5bf..6d9de53c 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -107,7 +107,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): # graph.remove_edges('vdd') # graph.remove_edges('gnd') # debug.info(1,"{}".format(graph)) - # graph.printAllPaths('A', 'Z') + # graph.print_all_paths('A', 'Z') def init_graph_params(self): """Initializes parameters relevant to the graph creation""" diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 4f654c5f..80537f2a 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -965,4 +965,8 @@ class control_logic(design.design): total_cin += self.wl_en_driver.get_cin() if self.port_type == 'rw': total_cin +=self.and2.get_cin() - return total_cin \ No newline at end of file + return total_cin + + 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) \ No newline at end of file diff --git a/compiler/sram_1bank.py b/compiler/sram_1bank.py index abe08f40..ca10d827 100644 --- a/compiler/sram_1bank.py +++ b/compiler/sram_1bank.py @@ -320,4 +320,10 @@ class sram_1bank(sram_base): if self.col_addr_dff: for inst in self.col_addr_dff_insts: - self.graph_inst_exclude.add(inst) + self.graph_inst_exclude.add(inst) + + def graph_exclude_ctrl_dffs(self): + """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() diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py index fe9c4951..7ca8c4b5 100755 --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -40,6 +40,7 @@ class timing_sram_test(openram_test): s.s.bank.graph_exclude_precharge() s.s.graph_exclude_addr_dff() s.s.graph_exclude_data_dff() + s.s.graph_exclude_ctrl_dffs() debug.info(1,'pins={}'.format(s.s.pins)) import graph_util @@ -48,8 +49,8 @@ class timing_sram_test(openram_test): s.s.build_graph(graph,"Xsram",pins) graph.remove_edges('vdd') graph.remove_edges('gnd') - debug.info(1,"{}".format(graph)) - graph.printAllPaths('clk0', 'DOUT0[0]') + #debug.info(1,"{}".format(graph)) + graph.print_all_paths('clk0', 'DOUT0[0]') tempspice = OPTS.openram_temp + "temp.sp" s.sp_write(tempspice) From 5bfc42fdbb1fc4ecc970012ffff199658cc352bc Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Mon, 29 Apr 2019 23:57:25 -0700 Subject: [PATCH 04/22] Added quality improvements to graph: improved naming, auto vdd/gnd removal --- compiler/base/graph_util.py | 59 ++++++++++++++++---------- compiler/tests/21_hspice_delay_test.py | 6 +-- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/compiler/base/graph_util.py b/compiler/base/graph_util.py index eb40d55b..d2759930 100644 --- a/compiler/base/graph_util.py +++ b/compiler/base/graph_util.py @@ -9,28 +9,43 @@ import debug from vector import vector from pin_layout import pin_layout -class graph(): - """Implements a directed graph""" +class timing_graph(): + """Implements a directed graph + Nodes are currently just Strings. + """ def __init__(self): - self.graph = defaultdict(list) + self.graph = defaultdict(set) - def add_edge(self, u, v): + def add_edge(self, src_node, dest_node): """Adds edge to graph. Nodes added as well if they do not exist.""" - if v not in self.graph[u]: - self.graph[u].append(v) + src_node = src_node.lower() + dest_node = dest_node.lower() + self.graph[src_node].add(dest_node) - def add_node(self, u): + def add_node(self, node): """Add node to graph with no edges""" - if not u in self.graph: - self.graph[u] = [] + node = node.lower() + if not node in self.graph: + self.graph[node] = set() def remove_edges(self, node): """Helper function to remove edges, useful for removing vdd/gnd""" - self.graph[node] = [] + node = node.lower() + self.graph[node] = set() - def print_all_paths(self,s, d): - + def print_all_paths(self, src_node, dest_node, rmv_rail_nodes=True): + """Traverse all paths from source to destination""" + src_node = src_node.lower() + dest_node = dest_node.lower() + + #Remove vdd and gnd by default + #Will require edits if separate supplies are implemented. + if rmv_rail_nodes: + #Names are also assumed. + self.remove_edges('vdd') + self.remove_edges('gnd') + # Mark all the vertices as not visited visited = set() @@ -39,30 +54,30 @@ class graph(): self.path_count = 0 # Call the recursive helper function to print all paths - self.print_all_paths_util(s, d,visited, path) + self.print_all_paths_util(src_node, dest_node, visited, path) debug.info(1, "Paths found={}".format(self.path_count)) - def print_all_paths_util(self, u, d, visited, path): - + def print_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(u) - path.append(u) + visited.add(cur_node) + path.append(cur_node) # If current vertex is same as destination, then print # current path[] - if u == d: + if cur_node == dest_node: debug.info(1,"{}".format(path)) self.path_count+=1 else: # If current vertex is not destination #Recur for all the vertices adjacent to this vertex - for i in self.graph[u]: - if i not in visited: - self.print_all_paths_util(i, d, visited, path) + for node in self.graph[cur_node]: + if node not in visited: + self.print_all_paths_util(node, dest_node, visited, path) # Remove current vertex from path[] and mark it as unvisited path.pop() - visited.remove(u) + visited.remove(cur_node) def __str__(self): """ override print function output """ diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py index 7ca8c4b5..b7ccd72c 100755 --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -44,13 +44,13 @@ class timing_sram_test(openram_test): debug.info(1,'pins={}'.format(s.s.pins)) import graph_util - graph = graph_util.graph() + graph = graph_util.timing_graph() pins=['DIN0[0]', 'ADDR0[0]', 'ADDR0[1]', 'ADDR0[2]', 'ADDR0[3]', 'csb0', 'web0', 'clk0', 'DOUT0[0]', 'vdd', 'gnd'] s.s.build_graph(graph,"Xsram",pins) - graph.remove_edges('vdd') - graph.remove_edges('gnd') #debug.info(1,"{}".format(graph)) graph.print_all_paths('clk0', 'DOUT0[0]') + # import sys + # sys.exit(1) tempspice = OPTS.openram_temp + "temp.sp" s.sp_write(tempspice) From d54074d68ef021237e2a1638e7ac83192efbf04c Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Tue, 7 May 2019 00:52:27 -0700 Subject: [PATCH 05/22] Made timing graph more gate-level. Changed edges to be defined by inputs/ouputs and name based. --- compiler/base/hierarchy_design.py | 16 +++++- compiler/base/hierarchy_spice.py | 19 +++++++ compiler/bitcells/bitcell.py | 11 ++--- compiler/bitcells/bitcell_1rw_1r.py | 24 ++++----- compiler/bitcells/bitcell_1w_1r.py | 19 +++---- compiler/bitcells/pbitcell.py | 43 ++++++++-------- compiler/bitcells/replica_bitcell.py | 11 ++--- compiler/bitcells/replica_bitcell_1rw_1r.py | 21 ++++---- compiler/bitcells/replica_bitcell_1w_1r.py | 19 +++---- compiler/characterizer/model_check.py | 55 +++++++++++++-------- compiler/modules/dff.py | 10 ++-- compiler/modules/sense_amp.py | 14 ++---- compiler/modules/tri_gate.py | 13 ++--- compiler/modules/write_driver.py | 17 ++----- compiler/pgates/pgate.py | 1 - compiler/pgates/pinv.py | 18 +++++-- compiler/pgates/pnand2.py | 10 +++- compiler/pgates/pnand3.py | 8 ++- compiler/pgates/pnor2.py | 7 ++- compiler/pgates/ptx.py | 20 ++++---- 20 files changed, 205 insertions(+), 151 deletions(-) diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index 6d9de53c..898ac918 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -120,7 +120,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): #Translate port names to external nets if len(port_nets) != len(self.pins): debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,self.pins),1) - port_dict = {i:j for i,j in zip(self.pins, port_nets)} + port_dict = {pin:port for pin,port in zip(self.pins, port_nets)} debug.info(3, "Instance name={}".format(inst_name)) for subinst, conns in zip(self.insts, self.conns): if subinst in self.graph_inst_exclude: @@ -139,6 +139,20 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): converted_conns.append("{}.{}".format(inst_name, conn)) 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.""" + #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)} + input_pins = self.get_inputs() + output_pins = self.get_outputs() + inout_pins = self.get_inouts() + for inp in input_pins+inout_pins: + for out in output_pins+inout_pins: + if inp != out: #do not add self loops + graph.add_edge(pin_dict[inp], pin_dict[out]) + def __str__(self): """ override print function output """ return "design: " + self.name diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index cb2799f3..f2535f86 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -58,6 +58,17 @@ class spice(): else: debug.error("Mismatch in type and pin list lengths.", -1) + def add_pin_types(self, type_list): + """Add pin types for all the cell's pins. + Typically, should only be used for handmade cells.""" + #This only works if self.pins == bitcell.pin_names + if self.pin_names != self.pins: + debug.error("{} spice subcircuit port names do not match pin_names\ + \n SPICE names={}\ + \n Module names={}\ + ".format(self.name, self.pin_names, self.pins),1) + self.pin_type = {pin:type for pin,type in zip(self.pin_names, type_list)} + def get_pin_type(self, name): """ Returns the type of the signal pin. """ return self.pin_type[name] @@ -87,6 +98,14 @@ class spice(): output_list.append(pin) return output_list + def get_inouts(self): + """ These use pin types to determine pin lists. These + may be over-ridden by submodules that didn't use pin directions yet.""" + inout_list = [] + for pin in self.pins: + if self.pin_type[pin]=="INOUT": + inout_list.append(pin) + return inout_list def add_mod(self, mod): """Adds a subckt/submodule to the subckt hierarchy""" diff --git a/compiler/bitcells/bitcell.py b/compiler/bitcells/bitcell.py index 798ee3f9..76ecfb0a 100644 --- a/compiler/bitcells/bitcell.py +++ b/compiler/bitcells/bitcell.py @@ -13,6 +13,7 @@ class bitcell(design.design): """ pin_names = ["bl", "br", "wl", "vdd", "gnd"] + type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("cell_6t", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "cell_6t", GDS["unit"]) @@ -24,6 +25,7 @@ class bitcell(design.design): self.width = bitcell.width self.height = bitcell.height self.pin_map = bitcell.pin_map + self.add_pin_types(self.type_list) def analytical_delay(self, corner, slew, load=0, swing = 0.5): parasitic_delay = 1 @@ -76,10 +78,5 @@ class bitcell(design.design): return 2*access_tx_cin def build_graph(self, graph, inst_name, port_nets): - """Adds edges to graph. Handmade cells must implement this manually.""" - #The bitcell has 5 net ports hard-coded in self.pin_names. The edges - #are based on the hard-coded name positions. - # The edges added are: wl->bl, wl->br. - # Internal nodes of the handmade cell not considered, only ports. vdd/gnd ignored for graph. - graph.add_edge(port_nets[2],port_nets[0]) - graph.add_edge(port_nets[2],port_nets[1]) \ No newline at end of file + """Adds edges based on inputs/outputs. Overrides base class function.""" + self.add_graph_edges(graph, port_nets) \ No newline at end of file diff --git a/compiler/bitcells/bitcell_1rw_1r.py b/compiler/bitcells/bitcell_1rw_1r.py index 58b0ca98..d10d03b6 100644 --- a/compiler/bitcells/bitcell_1rw_1r.py +++ b/compiler/bitcells/bitcell_1rw_1r.py @@ -13,6 +13,7 @@ class bitcell_1rw_1r(design.design): """ pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("cell_1rw_1r", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "cell_1rw_1r", GDS["unit"]) @@ -24,7 +25,8 @@ class bitcell_1rw_1r(design.design): self.width = bitcell_1rw_1r.width self.height = bitcell_1rw_1r.height self.pin_map = bitcell_1rw_1r.pin_map - + self.add_pin_types(self.type_list) + def analytical_delay(self, corner, slew, load=0, swing = 0.5): parasitic_delay = 1 size = 0.5 #This accounts for bitline being drained thought the access TX and internal node @@ -101,13 +103,13 @@ class bitcell_1rw_1r(design.design): return 2*access_tx_cin def build_graph(self, graph, inst_name, port_nets): - """Adds edges to graph. Handmade cells must implement this manually.""" - #The bitcell has 8 net ports hard-coded in self.pin_names. The edges - #are based on the hard-coded name positions. - # The edges added are: wl0->bl0, wl0->br0, wl1->bl1, wl1->br1. - # Internal nodes of the handmade cell not considered, only ports. vdd/gnd ignored for graph. - graph.add_edge(port_nets[4],port_nets[0]) - graph.add_edge(port_nets[4],port_nets[1]) - graph.add_edge(port_nets[5],port_nets[2]) - graph.add_edge(port_nets[5],port_nets[3]) - + """Adds edges to graph. Multiport bitcell timing graph is too complex + to use the add_graph_edges function.""" + pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + #Edges hardcoded here. Essentially wl->bl/br for both ports. + # Port 0 edges + graph.add_edge(pin_dict["wl0"], pin_dict["bl0"]) + graph.add_edge(pin_dict["wl0"], pin_dict["br0"]) + # Port 1 edges + graph.add_edge(pin_dict["wl1"], pin_dict["bl1"]) + graph.add_edge(pin_dict["wl1"], pin_dict["br1"]) diff --git a/compiler/bitcells/bitcell_1w_1r.py b/compiler/bitcells/bitcell_1w_1r.py index 3d1febdd..e8865dcf 100644 --- a/compiler/bitcells/bitcell_1w_1r.py +++ b/compiler/bitcells/bitcell_1w_1r.py @@ -13,6 +13,7 @@ class bitcell_1w_1r(design.design): """ pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("cell_1w_1r", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "cell_1w_1r", GDS["unit"]) @@ -24,6 +25,7 @@ class bitcell_1w_1r(design.design): self.width = bitcell_1w_1r.width self.height = bitcell_1w_1r.height self.pin_map = bitcell_1w_1r.pin_map + self.add_pin_types(self.type_list) def analytical_delay(self, corner, slew, load=0, swing = 0.5): parasitic_delay = 1 @@ -101,12 +103,11 @@ class bitcell_1w_1r(design.design): return 2*access_tx_cin def build_graph(self, graph, inst_name, port_nets): - """Adds edges to graph. Handmade cells must implement this manually.""" - #The bitcell has 8 net ports hard-coded in self.pin_names. The edges - #are based on the hard-coded name positions. - # The edges added are: wl0->bl0, wl0->br0, wl1->bl1, wl1->br1. - # Internal nodes of the handmade cell not considered, only ports. vdd/gnd ignored for graph. - graph.add_edge(port_nets[4],port_nets[0]) - graph.add_edge(port_nets[4],port_nets[1]) - graph.add_edge(port_nets[5],port_nets[2]) - graph.add_edge(port_nets[5],port_nets[3]) + """Adds edges to graph. Multiport bitcell timing graph is too complex + to use the add_graph_edges function.""" + pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + #Edges hardcoded here. Essentially wl->bl/br for both ports. + # Port 0 edges + graph.add_edge(pin_dict["wl0"], pin_dict["bl0"]) + graph.add_edge(pin_dict["wl0"], pin_dict["br0"]) + # Port 1 is a write port, so its timing is not considered here. diff --git a/compiler/bitcells/pbitcell.py b/compiler/bitcells/pbitcell.py index cff7b5cf..f9c49709 100644 --- a/compiler/bitcells/pbitcell.py +++ b/compiler/bitcells/pbitcell.py @@ -91,40 +91,40 @@ class pbitcell(design.design): port = 0 for k in range(self.num_rw_ports): - self.add_pin("bl{}".format(port)) - self.add_pin("br{}".format(port)) + self.add_pin("bl{}".format(port), "OUTPUT") + self.add_pin("br{}".format(port), "OUTPUT") self.rw_bl_names.append("bl{}".format(port)) self.rw_br_names.append("br{}".format(port)) port += 1 for k in range(self.num_w_ports): - self.add_pin("bl{}".format(port)) - self.add_pin("br{}".format(port)) + self.add_pin("bl{}".format(port), "INPUT") + self.add_pin("br{}".format(port), "INPUT") self.w_bl_names.append("bl{}".format(port)) self.w_br_names.append("br{}".format(port)) port += 1 for k in range(self.num_r_ports): - self.add_pin("bl{}".format(port)) - self.add_pin("br{}".format(port)) + self.add_pin("bl{}".format(port), "OUTPUT") + self.add_pin("br{}".format(port), "OUTPUT") self.r_bl_names.append("bl{}".format(port)) self.r_br_names.append("br{}".format(port)) port += 1 port = 0 for k in range(self.num_rw_ports): - self.add_pin("wl{}".format(port)) + self.add_pin("wl{}".format(port), "INPUT") self.rw_wl_names.append("wl{}".format(port)) port += 1 for k in range(self.num_w_ports): - self.add_pin("wl{}".format(port)) + self.add_pin("wl{}".format(port), "INPUT") self.w_wl_names.append("wl{}".format(port)) port += 1 for k in range(self.num_r_ports): - self.add_pin("wl{}".format(port)) + self.add_pin("wl{}".format(port), "INPUT") self.r_wl_names.append("wl{}".format(port)) port += 1 - self.add_pin("vdd") - self.add_pin("gnd") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") # if this is a replica bitcell, replace the instances of Q_bar with vdd if self.replica_bitcell: @@ -894,14 +894,13 @@ class pbitcell(design.design): return 2*access_tx_cin def build_graph(self, graph, inst_name, port_nets): - """Adds edges to graph. Done manually to make the graph acyclic.""" - #The base graph function can make this but it would contain loops and path - #searches would essentially be useless. - # Edges added wl->bl, wl->br for every port - - num_wl = len(self.rw_wl_names) + len(self.w_wl_names) + len(self.r_wl_names) - wl_pos = 2*num_wl #there are this many bitlines nets before the wls in the port list - for i in range(num_wl): - bl_pos = i*2 - graph.add_edge(port_nets[wl_pos+i],port_nets[bl_pos]) - graph.add_edge(port_nets[wl_pos+i],port_nets[bl_pos+1]) + """Adds edges to graph for pbitcell. Only readwrite and read ports.""" + pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + # Edges added wl->bl, wl->br for every port except write ports + rw_pin_names = zip(self.r_wl_names, self.r_bl_names, self.r_br_names) + r_pin_names = zip(self.rw_wl_names, self.rw_bl_names, self.rw_br_names) + for pin_zip in zip(rw_pin_names, r_pin_names): + for wl,bl,br in pin_zip: + graph.add_edge(pin_dict[wl],pin_dict[bl]) + graph.add_edge(pin_dict[wl],pin_dict[br]) + diff --git a/compiler/bitcells/replica_bitcell.py b/compiler/bitcells/replica_bitcell.py index 8d77ff7b..deb71e40 100644 --- a/compiler/bitcells/replica_bitcell.py +++ b/compiler/bitcells/replica_bitcell.py @@ -11,6 +11,7 @@ class replica_bitcell(design.design): the technology library. """ pin_names = ["bl", "br", "wl", "vdd", "gnd"] + type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("replica_cell_6t", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "replica_cell_6t", GDS["unit"]) @@ -22,6 +23,7 @@ class replica_bitcell(design.design): self.width = replica_bitcell.width self.height = replica_bitcell.height self.pin_map = replica_bitcell.pin_map + self.add_pin_types(self.type_list) def get_wl_cin(self): """Return the relative capacitance of the access transistor gates""" @@ -31,10 +33,5 @@ class replica_bitcell(design.design): return 2*access_tx_cin def build_graph(self, graph, inst_name, port_nets): - """Adds edges to graph. Handmade cells must implement this manually.""" - #The bitcell has 5 net ports hard-coded in self.pin_names. The edges - #are based on the hard-coded name positions. - # The edges added are: wl->bl, wl->br. - # Internal nodes of the handmade cell not considered, only ports. vdd/gnd ignored for graph. - graph.add_edge(port_nets[2],port_nets[0]) - graph.add_edge(port_nets[2],port_nets[1]) \ No newline at end of file + """Adds edges based on inputs/outputs. Overrides base class function.""" + self.add_graph_edges(graph, port_nets) \ No newline at end of file diff --git a/compiler/bitcells/replica_bitcell_1rw_1r.py b/compiler/bitcells/replica_bitcell_1rw_1r.py index 67871525..14e10ee6 100644 --- a/compiler/bitcells/replica_bitcell_1rw_1r.py +++ b/compiler/bitcells/replica_bitcell_1rw_1r.py @@ -11,6 +11,7 @@ class replica_bitcell_1rw_1r(design.design): the technology library. """ pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("replica_cell_1rw_1r", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "replica_cell_1rw_1r", GDS["unit"]) @@ -22,6 +23,7 @@ class replica_bitcell_1rw_1r(design.design): self.width = replica_bitcell_1rw_1r.width self.height = replica_bitcell_1rw_1r.height self.pin_map = replica_bitcell_1rw_1r.pin_map + self.add_pin_types(self.type_list) def get_wl_cin(self): """Return the relative capacitance of the access transistor gates""" @@ -32,12 +34,13 @@ class replica_bitcell_1rw_1r(design.design): return 2*access_tx_cin def build_graph(self, graph, inst_name, port_nets): - """Adds edges to graph. Handmade cells must implement this manually.""" - #The bitcell has 8 net ports hard-coded in self.pin_names. The edges - #are based on the hard-coded name positions. - # The edges added are: wl0->bl0, wl0->br0, wl1->bl1, wl1->br1. - # Internal nodes of the handmade cell not considered, only ports. vdd/gnd ignored for graph. - graph.add_edge(port_nets[4],port_nets[0]) - graph.add_edge(port_nets[4],port_nets[1]) - graph.add_edge(port_nets[5],port_nets[2]) - graph.add_edge(port_nets[5],port_nets[3]) \ No newline at end of file + """Adds edges to graph. Multiport bitcell timing graph is too complex + to use the add_graph_edges function.""" + pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + #Edges hardcoded here. Essentially wl->bl/br for both ports. + # Port 0 edges + graph.add_edge(pin_dict["wl0"], pin_dict["bl0"]) + graph.add_edge(pin_dict["wl0"], pin_dict["br0"]) + # Port 1 edges + graph.add_edge(pin_dict["wl1"], pin_dict["bl1"]) + graph.add_edge(pin_dict["wl1"], pin_dict["br1"]) \ No newline at end of file diff --git a/compiler/bitcells/replica_bitcell_1w_1r.py b/compiler/bitcells/replica_bitcell_1w_1r.py index 1f5c3266..0b1b598f 100644 --- a/compiler/bitcells/replica_bitcell_1w_1r.py +++ b/compiler/bitcells/replica_bitcell_1w_1r.py @@ -11,6 +11,7 @@ class replica_bitcell_1w_1r(design.design): the technology library. """ pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("replica_cell_1w_1r", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "replica_cell_1w_1r", GDS["unit"]) @@ -22,6 +23,7 @@ class replica_bitcell_1w_1r(design.design): self.width = replica_bitcell_1w_1r.width self.height = replica_bitcell_1w_1r.height self.pin_map = replica_bitcell_1w_1r.pin_map + self.add_pin_types(self.type_list) def get_wl_cin(self): """Return the relative capacitance of the access transistor gates""" @@ -32,12 +34,11 @@ class replica_bitcell_1w_1r(design.design): return 2*access_tx_cin def build_graph(self, graph, inst_name, port_nets): - """Adds edges to graph. Handmade cells must implement this manually.""" - #The bitcell has 8 net ports hard-coded in self.pin_names. The edges - #are based on the hard-coded name positions. - # The edges added are: wl0->bl0, wl0->br0, wl1->bl1, wl1->br1. - # Internal nodes of the handmade cell not considered, only ports. vdd/gnd ignored for graph. - graph.add_edge(port_nets[4],port_nets[0]) - graph.add_edge(port_nets[4],port_nets[1]) - graph.add_edge(port_nets[5],port_nets[2]) - graph.add_edge(port_nets[5],port_nets[3]) \ No newline at end of file + """Adds edges to graph. Multiport bitcell timing graph is too complex + to use the add_graph_edges function.""" + pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + #Edges hardcoded here. Essentially wl->bl/br for both ports. + # Port 0 edges + graph.add_edge(pin_dict["wl0"], pin_dict["bl0"]) + graph.add_edge(pin_dict["wl0"], pin_dict["br0"]) + # Port 1 is a write port, so its timing is not considered here. \ No newline at end of file diff --git a/compiler/characterizer/model_check.py b/compiler/characterizer/model_check.py index ff8bbdfa..b9446639 100644 --- a/compiler/characterizer/model_check.py +++ b/compiler/characterizer/model_check.py @@ -31,7 +31,7 @@ class model_check(delay): self.bl_meas_name, self.bl_slew_name = "bl_measures", "bl_slews" self.power_name = "total_power" - def create_measurement_names(self): + def create_measurement_names(self, port): """Create measurement names. The names themselves currently define the type of measurement""" #Create delay measurement names wl_en_driver_delay_names = ["delay_wl_en_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())] @@ -42,7 +42,10 @@ class model_check(delay): else: dc_delay_names = ["delay_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)] self.wl_delay_meas_names = wl_en_driver_delay_names+["delay_wl_en", "delay_wl_bar"]+wl_driver_delay_names+["delay_wl"] - self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in"]+dc_delay_names + if port not in self.sram.readonly_ports: + self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in"]+dc_delay_names + else: + self.rbl_delay_meas_names = ["delay_gated_clk_nand"]+dc_delay_names self.sae_delay_meas_names = ["delay_pre_sen"]+sen_driver_delay_names+["delay_sen"] # if self.custom_delaychain: @@ -58,45 +61,54 @@ class model_check(delay): else: dc_slew_names = ["slew_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)] self.wl_slew_meas_names = ["slew_wl_gated_clk_bar"]+wl_en_driver_slew_names+["slew_wl_en", "slew_wl_bar"]+wl_driver_slew_names+["slew_wl"] - self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar","slew_gated_clk_nand", "slew_delay_chain_in"]+dc_slew_names + if port not in self.sram.readonly_ports: + self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar","slew_gated_clk_nand", "slew_delay_chain_in"]+dc_slew_names + else: + self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar"]+dc_slew_names self.sae_slew_meas_names = ["slew_replica_bl0", "slew_pre_sen"]+sen_driver_slew_names+["slew_sen"] self.bitline_meas_names = ["delay_wl_to_bl", "delay_bl_to_dout"] self.power_meas_names = ['read0_power'] - def create_signal_names(self): + def create_signal_names(self, port): """Creates list of the signal names used in the spice file along the wl and sen paths. Names are re-harded coded here; i.e. the names are hardcoded in most of OpenRAM and are replicated here. """ delay.create_signal_names(self) #Signal names are all hardcoded, need to update to make it work for probe address and different configurations. - wl_en_driver_signals = ["Xsram.Xcontrol0.Xbuf_wl_en.Zb{}_int".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())] - wl_driver_signals = ["Xsram.Xbank0.Xwordline_driver0.Xwl_driver_inv{}.Zb{}_int".format(self.wordline_row, stage) for stage in range(1,self.get_num_wl_driver_stages())] - sen_driver_signals = ["Xsram.Xcontrol0.Xbuf_s_en.Zb{}_int".format(stage) for stage in range(1,self.get_num_sen_driver_stages())] + wl_en_driver_signals = ["Xsram.Xcontrol{}.Xbuf_wl_en.Zb{}_int".format('{}', stage) for stage in range(1,self.get_num_wl_en_driver_stages())] + wl_driver_signals = ["Xsram.Xbank0.Xwordline_driver{}.Xwl_driver_inv{}.Zb{}_int".format('{}', self.wordline_row, stage) for stage in range(1,self.get_num_wl_driver_stages())] + sen_driver_signals = ["Xsram.Xcontrol{}.Xbuf_s_en.Zb{}_int".format('{}',stage) for stage in range(1,self.get_num_sen_driver_stages())] if self.custom_delaychain: delay_chain_signal_names = [] else: - delay_chain_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_{}".format(stage) for stage in range(1,self.get_num_delay_stages())] - - self.wl_signal_names = ["Xsram.Xcontrol0.gated_clk_bar"]+\ + delay_chain_signal_names = ["Xsram.Xcontrol{}.Xreplica_bitline.Xdelay_chain.dout_{}".format('{}', stage) for stage in range(1,self.get_num_delay_stages())] + if len(self.sram.all_ports) > 1: + port_format = '{}' + else: + port_format = '' + self.wl_signal_names = ["Xsram.Xcontrol{}.gated_clk_bar".format('{}')]+\ wl_en_driver_signals+\ - ["Xsram.wl_en0", "Xsram.Xbank0.Xwordline_driver0.wl_bar_{}".format(self.wordline_row)]+\ + ["Xsram.wl_en{}".format('{}'), "Xsram.Xbank0.Xwordline_driver{}.wl_bar_{}".format('{}',self.wordline_row)]+\ wl_driver_signals+\ - ["Xsram.Xbank0.wl_{}".format(self.wordline_row)] - pre_delay_chain_names = ["Xsram.Xcontrol0.gated_clk_bar", "Xsram.Xcontrol0.Xand2_rbl_in.zb_int", "Xsram.Xcontrol0.rbl_in"] + ["Xsram.Xbank0.wl{}_{}".format(port_format, self.wordline_row)] + pre_delay_chain_names = ["Xsram.Xcontrol{}.gated_clk_bar".format('{}')] + if port not in self.sram.readonly_ports: + pre_delay_chain_names+= ["Xsram.Xcontrol{}.Xand2_rbl_in.zb_int".format('{}'), "Xsram.Xcontrol{}.rbl_in".format('{}')] + self.rbl_en_signal_names = pre_delay_chain_names+\ delay_chain_signal_names+\ - ["Xsram.Xcontrol0.Xreplica_bitline.delayed_en"] + ["Xsram.Xcontrol{}.Xreplica_bitline.delayed_en".format('{}')] - self.sae_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.bl0_0", "Xsram.Xcontrol0.pre_s_en"]+\ + self.sae_signal_names = ["Xsram.Xcontrol{}.Xreplica_bitline.bl0_0".format('{}'), "Xsram.Xcontrol{}.pre_s_en".format('{}')]+\ sen_driver_signals+\ - ["Xsram.s_en0"] + ["Xsram.s_en{}".format('{}')] dout_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit - self.bl_signal_names = ["Xsram.Xbank0.wl_{}".format(self.wordline_row),\ - "Xsram.Xbank0.bl_{}".format(self.bitline_column),\ + self.bl_signal_names = ["Xsram.Xbank0.wl{}_{}".format(port_format, self.wordline_row),\ + "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column),\ dout_name] def create_measurement_objects(self): @@ -362,17 +374,18 @@ class model_check(delay): errors = self.calculate_error_l2_norm(scaled_meas, scaled_model) debug.info(1, "Errors:\n{}\n".format(errors)) - def analyze(self, probe_address, probe_data, slews, loads): + def analyze(self, probe_address, probe_data, slews, loads, port): """Measures entire delay path along the wordline and sense amp enable and compare it to the model delays.""" self.load=max(loads) self.slew=max(slews) self.set_probe(probe_address, probe_data) - self.create_signal_names() - self.create_measurement_names() + self.create_signal_names(port) + self.create_measurement_names(port) self.create_measurement_objects() data_dict = {} read_port = self.read_ports[0] #only test the first read port + read_port = port self.targ_read_ports = [read_port] self.targ_write_ports = [self.write_ports[0]] debug.info(1,"Model test: corner {}".format(self.corner)) diff --git a/compiler/modules/dff.py b/compiler/modules/dff.py index 9b9dd0fe..3ba3f5c8 100644 --- a/compiler/modules/dff.py +++ b/compiler/modules/dff.py @@ -11,6 +11,7 @@ class dff(design.design): """ pin_names = ["D", "Q", "clk", "vdd", "gnd"] + type_list = ["INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("dff", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "dff", GDS["unit"]) @@ -20,6 +21,7 @@ class dff(design.design): self.width = dff.width self.height = dff.height self.pin_map = dff.pin_map + self.add_pin_types(self.type_list) def analytical_power(self, corner, load): """Returns dynamic and leakage power. Results in nW""" @@ -51,10 +53,6 @@ class dff(design.design): return parameter["dff_clk_cin"] def build_graph(self, graph, inst_name, port_nets): - """Adds edges to graph. Handmade cells must implement this manually.""" - #The cell has 5 net ports hard-coded in self.pin_names. The edges - #are based on the hard-coded name positions. - # The edges added are: clk->Q. - # Internal nodes of the handmade cell not considered, only ports. vdd/gnd ignored for graph. - graph.add_edge(port_nets[2],port_nets[1]) + """Adds edges based on inputs/outputs. Overrides base class function.""" + self.add_graph_edges(graph, port_nets) \ No newline at end of file diff --git a/compiler/modules/sense_amp.py b/compiler/modules/sense_amp.py index 431fc1b5..5ee208cd 100644 --- a/compiler/modules/sense_amp.py +++ b/compiler/modules/sense_amp.py @@ -13,6 +13,7 @@ class sense_amp(design.design): """ pin_names = ["bl", "br", "dout", "en", "vdd", "gnd"] + type_list = ["INPUT", "INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"]) @@ -23,7 +24,8 @@ class sense_amp(design.design): self.width = sense_amp.width self.height = sense_amp.height self.pin_map = sense_amp.pin_map - + self.add_pin_types(self.type_list) + def input_load(self): #Input load for the bitlines which are connected to the source/drain of a TX. Not the selects. from tech import spice, parameter @@ -54,12 +56,6 @@ class sense_amp(design.design): return 2*pmos_cin + nmos_cin def build_graph(self, graph, inst_name, port_nets): - """Adds edges to graph. Handmade cells must implement this manually.""" - #The cell has 6 net ports hard-coded in self.pin_names. The edges - #are based on the hard-coded name positions. - # The edges added are: en->dout, bl->dout - # br->dout not included to reduce complexity - # Internal nodes of the handmade cell not considered, only ports. vdd/gnd ignored for graph. - graph.add_edge(port_nets[0],port_nets[2]) - graph.add_edge(port_nets[3],port_nets[2]) + """Adds edges based on inputs/outputs. Overrides base class function.""" + self.add_graph_edges(graph, port_nets) \ No newline at end of file diff --git a/compiler/modules/tri_gate.py b/compiler/modules/tri_gate.py index 6a1efdea..4b7501dd 100644 --- a/compiler/modules/tri_gate.py +++ b/compiler/modules/tri_gate.py @@ -11,6 +11,7 @@ class tri_gate(design.design): """ pin_names = ["in", "out", "en", "en_bar", "gnd", "vdd"] + type_list = ["INPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("tri_gate", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "tri_gate", GDS["unit"]) @@ -26,6 +27,7 @@ class tri_gate(design.design): self.width = tri_gate.width self.height = tri_gate.height self.pin_map = tri_gate.pin_map + self.add_pin_types(self.type_list) def analytical_delay(self, corner, slew, load=0.0): from tech import spice @@ -43,12 +45,5 @@ class tri_gate(design.design): return 9*spice["min_tx_gate_c"] def build_graph(self, graph, inst_name, port_nets): - """Adds edges to graph. Handmade cells must implement this manually.""" - #The cell has 6 net ports hard-coded in self.pin_names. The edges - #are based on the hard-coded name positions. - # The edges added are: en->out, en_bar->out, in->out. - # A liberal amount of edges were added, may be reduced later for complexity. - # Internal nodes of the handmade cell not considered, only ports. vdd/gnd ignored for graph. - graph.add_edge(port_nets[0],port_nets[1]) - graph.add_edge(port_nets[2],port_nets[1]) - graph.add_edge(port_nets[3],port_nets[1]) \ No newline at end of file + """Adds edges based on inputs/outputs. Overrides base class function.""" + self.add_graph_edges(graph, port_nets) \ No newline at end of file diff --git a/compiler/modules/write_driver.py b/compiler/modules/write_driver.py index d20a0135..c31e8a4b 100644 --- a/compiler/modules/write_driver.py +++ b/compiler/modules/write_driver.py @@ -11,7 +11,8 @@ class write_driver(design.design): the technology library. """ - pin_names = ["din", "bl", "br", "en", "gnd", "vdd"] + pin_names = ["din", "bl", "br", "en", "vdd", "gnd"] + type_list = ["INPUT", "OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("write_driver", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "write_driver", GDS["unit"]) @@ -22,7 +23,7 @@ class write_driver(design.design): self.width = write_driver.width self.height = write_driver.height self.pin_map = write_driver.pin_map - + self.add_pin_types(self.type_list) def get_w_en_cin(self): """Get the relative capacitance of a single input""" @@ -30,13 +31,5 @@ class write_driver(design.design): return 5*3 def build_graph(self, graph, inst_name, port_nets): - """Adds edges to graph. Handmade cells must implement this manually.""" - #The cell has 6 net ports hard-coded in self.pin_names. The edges - #are based on the hard-coded name positions. - # The edges added are: din->bl, din->br, en->bl, en->br - # A liberal amount of edges were added, may be reduced later for complexity. - # Internal nodes of the handmade cell not considered, only ports. vdd/gnd ignored for graph. - graph.add_edge(port_nets[0],port_nets[1]) - graph.add_edge(port_nets[0],port_nets[2]) - graph.add_edge(port_nets[3],port_nets[1]) - graph.add_edge(port_nets[3],port_nets[2]) \ No newline at end of file + """Adds edges based on inputs/outputs. Overrides base class function.""" + self.add_graph_edges(graph, port_nets) \ No newline at end of file diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index 2f641da9..eaabdfe5 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -216,4 +216,3 @@ class pgate(design.design): # offset=implant_offset, # width=implant_width, # height=implant_height) - diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index c66c944c..4a7e0820 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -62,7 +62,9 @@ class pinv(pgate.pgate): def add_pins(self): """ Adds pins for spice netlist """ - self.add_pin_list(["A", "Z", "vdd", "gnd"]) + pin_list = ["A", "Z", "vdd", "gnd"] + dir_list = ['INPUT', 'OUTPUT', 'POWER', 'GROUND'] + self.add_pin_list(pin_list, dir_list) def determine_tx_mults(self): @@ -283,7 +285,8 @@ class pinv(pgate.pgate): return transition_prob*(c_load + c_para) def get_cin(self): - """Return the capacitance of the gate connection in generic capacitive units relative to the minimum width of a transistor""" + """Return the capacitance of the gate connection in generic capacitive + units relative to the minimum width of a transistor""" return self.nmos_size + self.pmos_size def get_stage_effort(self, cout, inp_is_rise=True): @@ -291,4 +294,13 @@ class pinv(pgate.pgate): Optional is_rise refers to the input direction rise/fall. Input inverted by this stage. """ parasitic_delay = 1 - return logical_effort.logical_effort(self.name, self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise) + return logical_effort.logical_effort(self.name, + self.size, + self.get_cin(), + 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) \ No newline at end of file diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index eb2484af..e1dccf7e 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -33,7 +33,7 @@ class pnand2(pgate.pgate): self.create_layout() #For characterization purposes only - self.exclude_nmos_from_graph() + #self.exclude_nmos_from_graph() def create_netlist(self): self.add_pins() @@ -54,7 +54,9 @@ class pnand2(pgate.pgate): def add_pins(self): """ Adds pins for spice netlist """ - self.add_pin_list(["A", "B", "Z", "vdd", "gnd"]) + pin_list = ["A", "B", "Z", "vdd", "gnd"] + dir_list = ['INPUT', 'INPUT', 'OUTPUT', 'POWER', 'GROUND'] + self.add_pin_list(pin_list, dir_list) def add_ptx(self): @@ -268,3 +270,7 @@ class pnand2(pgate.pgate): #Removing them simplifies generic path searching. self.graph_inst_exclude.add(self.nmos1_inst) self.graph_inst_exclude.add(self.nmos2_inst) + + 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) \ No newline at end of file diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index d0c37b55..18fc2ed0 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -37,7 +37,9 @@ class pnand3(pgate.pgate): def add_pins(self): """ Adds pins for spice netlist """ - self.add_pin_list(["A", "B", "C", "Z", "vdd", "gnd"]) + pin_list = ["A", "B", "C", "Z", "vdd", "gnd"] + dir_list = ['INPUT', 'INPUT', 'INPUT', 'OUTPUT', 'POWER', 'GROUND'] + self.add_pin_list(pin_list, dir_list) def create_netlist(self): self.add_pins() @@ -272,3 +274,7 @@ class pnand3(pgate.pgate): """ parasitic_delay = 3 return logical_effort.logical_effort(self.name, self.size, self.get_cin(), 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) \ No newline at end of file diff --git a/compiler/pgates/pnor2.py b/compiler/pgates/pnor2.py index 8a5c80d4..64a75b44 100644 --- a/compiler/pgates/pnor2.py +++ b/compiler/pgates/pnor2.py @@ -34,7 +34,9 @@ class pnor2(pgate.pgate): def add_pins(self): """ Adds pins for spice netlist """ - self.add_pin_list(["A", "B", "Z", "vdd", "gnd"]) + pin_list = ["A", "B", "Z", "vdd", "gnd"] + dir_list = ['INPUT', 'INPUT', 'OUTPUT', 'INOUT', 'INOUT'] + self.add_pin_list(pin_list, dir_list) def create_netlist(self): self.add_pins() @@ -234,3 +236,6 @@ class pnor2(pgate.pgate): transition_prob = spice["nor2_transition_prob"] return transition_prob*(c_load + c_para) + 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) \ No newline at end of file diff --git a/compiler/pgates/ptx.py b/compiler/pgates/ptx.py index 7e80a7c9..81ff54a3 100644 --- a/compiler/pgates/ptx.py +++ b/compiler/pgates/ptx.py @@ -60,7 +60,13 @@ class ptx(design.design): #self.DRC() def create_netlist(self): - self.add_pin_list(["D", "G", "S", "B"]) + pin_list = ["D", "G", "S", "B"] + if self.tx_type=="nmos": + body_dir = 'GROUND' + else: #Assumed that the check for either pmos or nmos is done elsewhere. + body_dir = 'POWER' + dir_list = ['INOUT', 'INPUT', 'INOUT', body_dir] + self.add_pin_list(pin_list, dir_list) # self.spice.append("\n.SUBCKT {0} {1}".format(self.name, # " ".join(self.pins))) @@ -358,14 +364,6 @@ class ptx(design.design): return self.tx_width/drc("minwidth_tx") def build_graph(self, graph, inst_name, port_nets): - """Adds ptx edges to graph. Lowest graph level.""" - #The ptx has four connections: (S)ource, (G)ate, (D)rain, (B)ody. The positions in spice - #are hardcoded which is represented here as well. - #Edges are connected as follows: G->S, G->D, D<->S. Body not represented in graph. - if len(port_nets) != 4: - debug.error("Transistor has non-standard connections.",1) - graph.add_edge(port_nets[1],port_nets[0]) - graph.add_edge(port_nets[1],port_nets[2]) - graph.add_edge(port_nets[0],port_nets[2]) - graph.add_edge(port_nets[2],port_nets[0]) + """Adds edges based on inputs/outputs. Overrides base class function.""" + self.add_graph_edges(graph, port_nets) \ No newline at end of file From b4cce6588989714f5d70f35db3722507423883c6 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Mon, 13 May 2019 19:38:46 -0700 Subject: [PATCH 06/22] Added incorrect read checking in characterizer. --- compiler/characterizer/delay.py | 193 +++++++++++++++++++------ compiler/characterizer/measurements.py | 30 +++- compiler/characterizer/stimuli.py | 7 + compiler/tests/21_hspice_delay_test.py | 25 ++-- 4 files changed, 196 insertions(+), 59 deletions(-) diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index a80fc4dc..4b8dae7a 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -1,4 +1,4 @@ -import sys,re,shutil +import sys,re,shutil,copy import debug import tech import math @@ -48,32 +48,33 @@ class delay(simulation): def create_measurement_objects(self): """Create the measurements used for read and write ports""" - self.create_read_port_measurement_objects() - self.create_write_port_measurement_objects() + self.read_meas_lists = self.create_read_port_measurement_objects() + self.write_meas_lists = self.create_write_port_measurement_objects() def create_read_port_measurement_objects(self): """Create the measurements used for read ports: delays, slews, powers""" - self.read_meas_objs = [] + self.read_lib_meas = [] trig_delay_name = "clk{0}" targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit - self.read_meas_objs.append(delay_measure("delay_lh", trig_delay_name, targ_name, "RISE", "RISE", measure_scale=1e9)) - self.read_meas_objs[-1].meta_str = "read1" #Used to index time delay values when measurements written to spice file. - self.read_meas_objs.append(delay_measure("delay_hl", trig_delay_name, targ_name, "FALL", "FALL", measure_scale=1e9)) - self.read_meas_objs[-1].meta_str = "read0" + self.read_lib_meas.append(delay_measure("delay_lh", trig_delay_name, targ_name, "RISE", "RISE", measure_scale=1e9)) + self.read_lib_meas[-1].meta_str = "read1" #Used to index time delay values when measurements written to spice file. + self.read_lib_meas.append(delay_measure("delay_hl", trig_delay_name, targ_name, "FALL", "FALL", measure_scale=1e9)) + self.read_lib_meas[-1].meta_str = "read0" + self.delay_meas = self.read_lib_meas[:] #For debugging, kept separated - self.read_meas_objs.append(slew_measure("slew_lh", targ_name, "RISE", measure_scale=1e9)) - self.read_meas_objs[-1].meta_str = "read1" - self.read_meas_objs.append(slew_measure("slew_hl", targ_name, "FALL", measure_scale=1e9)) - self.read_meas_objs[-1].meta_str = "read0" + self.read_lib_meas.append(slew_measure("slew_lh", targ_name, "RISE", measure_scale=1e9)) + self.read_lib_meas[-1].meta_str = "read1" + self.read_lib_meas.append(slew_measure("slew_hl", targ_name, "FALL", measure_scale=1e9)) + self.read_lib_meas[-1].meta_str = "read0" - self.read_meas_objs.append(power_measure("read1_power", "RISE", measure_scale=1e3)) - self.read_meas_objs[-1].meta_str = "read1" - self.read_meas_objs.append(power_measure("read0_power", "FALL", measure_scale=1e3)) - self.read_meas_objs[-1].meta_str = "read0" + self.read_lib_meas.append(power_measure("read1_power", "RISE", measure_scale=1e3)) + self.read_lib_meas[-1].meta_str = "read1" + self.read_lib_meas.append(power_measure("read0_power", "FALL", measure_scale=1e3)) + self.read_lib_meas[-1].meta_str = "read0" #This will later add a half-period to the spice time delay. Only for reading 0. - for obj in self.read_meas_objs: + for obj in self.read_lib_meas: if obj.meta_str is "read0": obj.meta_add_delay = True @@ -85,17 +86,22 @@ class delay(simulation): bl_name = "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column) br_name = "Xsram.Xbank0.br{}_{}".format(port_format, self.bitline_column) - # self.read_meas_objs.append(voltage_when_measure(self.voltage_when_names[0], trig_name, bl_name, "RISE", .5)) - # self.read_meas_objs.append(voltage_when_measure(self.voltage_when_names[1], trig_name, br_name, "RISE", .5)) + # self.read_lib_meas.append(voltage_when_measure(self.voltage_when_names[0], trig_name, bl_name, "RISE", .5)) + # self.read_lib_meas.append(voltage_when_measure(self.voltage_when_names[1], trig_name, br_name, "RISE", .5)) - #These are read values but need to be separated for unique error checking. - self.create_bitline_delay_measurement_objects() + read_measures = [] + read_measures.append(self.read_lib_meas) + #Other measurements associated with the read port not included in the liberty file + read_measures.append(self.create_bitline_delay_measurement_objects()) + read_measures.append(self.create_debug_measurement_objects()) + + return read_measures def create_bitline_delay_measurement_objects(self): """Create the measurements used for bitline delay values. Due to unique error checking, these are separated from other measurements. These measurements are only associated with read values """ - self.bitline_delay_objs = [] + self.bitline_delay_meas = [] trig_name = "clk{0}" if len(self.all_ports) == 1: #special naming case for single port sram bitlines which does not include the port in name port_format = "" @@ -106,23 +112,51 @@ class delay(simulation): targ_val = (self.vdd_voltage - tech.spice["v_threshold_typical"])/self.vdd_voltage #Calculate as a percentage of vdd targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit - # self.bitline_delay_objs.append(delay_measure(self.bitline_delay_names[0], trig_name, bl_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9)) - # self.bitline_delay_objs[-1].meta_str = "read0" - # self.bitline_delay_objs.append(delay_measure(self.bitline_delay_names[1], trig_name, br_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9)) - # self.bitline_delay_objs[-1].meta_str = "read1" + # self.bitline_delay_meas.append(delay_measure(self.bitline_delay_names[0], trig_name, bl_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9)) + # self.bitline_delay_meas[-1].meta_str = "read0" + # self.bitline_delay_meas.append(delay_measure(self.bitline_delay_names[1], trig_name, br_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9)) + # self.bitline_delay_meas[-1].meta_str = "read1" #Enforces the time delay on the bitline measurements for read0 or read1 - for obj in self.bitline_delay_objs: + for obj in self.bitline_delay_meas: obj.meta_add_delay = True + return self.bitline_delay_meas + def create_write_port_measurement_objects(self): """Create the measurements used for read ports: delays, slews, powers""" - self.write_meas_objs = [] + self.write_lib_meas = [] - self.write_meas_objs.append(power_measure("write1_power", "RISE", measure_scale=1e3)) - self.write_meas_objs[-1].meta_str = "write1" - self.write_meas_objs.append(power_measure("write0_power", "FALL", measure_scale=1e3)) - self.write_meas_objs[-1].meta_str = "write0" - + self.write_lib_meas.append(power_measure("write1_power", "RISE", measure_scale=1e3)) + self.write_lib_meas[-1].meta_str = "write1" + self.write_lib_meas.append(power_measure("write0_power", "FALL", measure_scale=1e3)) + self.write_lib_meas[-1].meta_str = "write0" + + write_measures = [] + write_measures.append(self.write_lib_meas) + return write_measures + + def create_debug_measurement_objects(self): + """Create debug measurement to help identify failures.""" + + self.debug_delay_meas = [] + self.debug_volt_meas = [] + for meas in self.delay_meas: + debug_meas = copy.deepcopy(meas) + debug_meas.name = debug_meas.name+'_debug' + #This particular debugs check if output value is flipped, so TARG_DIR is flipped + if meas.targ_dir_str == 'FALL': + debug_meas.targ_dir_str = 'RISE' + else: + debug_meas.targ_dir_str = 'FALL' + #inserting debug member variable + debug_meas.parent = meas.name + self.debug_delay_meas.append(debug_meas) + + self.debug_volt_meas.append(voltage_at_measure("v_{}".format(debug_meas.meta_str), + debug_meas.targ_name_no_port)) + self.debug_volt_meas[-1].meta_str = debug_meas.meta_str + return self.debug_delay_meas+self.debug_volt_meas + def create_signal_names(self): self.addr_name = "A" self.din_name = "DIN" @@ -284,6 +318,8 @@ class delay(simulation): return self.get_power_measure_variants(port, measure_obj, "read") elif meas_type is voltage_when_measure: return self.get_volt_when_measure_variants(port, measure_obj) + elif meas_type is voltage_at_measure: + return self.get_volt_at_measure_variants(port, measure_obj) else: debug.error("Input function not defined for measurement type={}".format(meas_type)) @@ -299,6 +335,7 @@ class delay(simulation): else: debug.error("Unrecognised delay Index={}".format(delay_obj.meta_str),1) + #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 @@ -312,7 +349,21 @@ class delay(simulation): return (t_initial, t_final, port) - def get_volt_when_measure_variants(self, port, power_obj): + def get_volt_at_measure_variants(self, port, volt_meas): + """Get the measurement values that can either vary port to port (time delays)""" + #Only checking 0 value reads for now. + if volt_meas.meta_str == "read0": + #Falling delay are measured starting from neg. clk edge. Delay adjusted to that. + meas_cycle = self.cycle_times[self.measure_cycles[port][volt_meas.meta_str]] + elif volt_meas.meta_str == "read1": + meas_cycle = self.cycle_times[self.measure_cycles[port][volt_meas.meta_str]] + else: + debug.error("Unrecognised delay Index={}".format(volt_meas.meta_str),1) + #Measurement occurs at the end of the period -> current period start + period + at_time = meas_cycle+self.period + return (at_time, port) + + def get_volt_when_measure_variants(self, port, volt_meas): """Get the measurement values that can either vary port to port (time delays)""" #Only checking 0 value reads for now. t_trig = meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]] @@ -324,9 +375,10 @@ class delay(simulation): Write the measure statements to quantify the delay and power results for a read port. """ # add measure statements for delays/slews - for measure in self.read_meas_objs+self.bitline_delay_objs: - measure_variant_inp_tuple = self.get_read_measure_variants(port, measure) - measure.write_measure(self.stim, measure_variant_inp_tuple) + for meas_list in self.read_meas_lists: + for measure in meas_list: + measure_variant_inp_tuple = self.get_read_measure_variants(port, measure) + measure.write_measure(self.stim, measure_variant_inp_tuple) def get_write_measure_variants(self, port, measure_obj): """Checks the measurement object and calls respective function for related measurement inputs.""" @@ -341,9 +393,10 @@ class delay(simulation): Write the measure statements to quantify the power results for a write port. """ # add measure statements for power - for measure in self.write_meas_objs: - measure_variant_inp_tuple = self.get_write_measure_variants(port, measure) - measure.write_measure(self.stim, measure_variant_inp_tuple) + for meas_list in self.write_meas_lists: + for measure in meas_list: + measure_variant_inp_tuple = self.get_write_measure_variants(port, measure) + measure.write_measure(self.stim, measure_variant_inp_tuple) def write_delay_measures(self): """ @@ -462,6 +515,7 @@ class delay(simulation): #Sanity Check debug.check(self.period > 0, "Target simulation period non-positive") + sim_passed = True result = [{} for i in self.all_ports] # Checking from not data_value to data_value self.write_delay_stimulus() @@ -471,17 +525,18 @@ class delay(simulation): #Loop through all targeted ports and collect delays and powers. #Too much duplicate code here. Try reducing for port in self.targ_read_ports: - debug.info(2, "Check delay values for port {}".format(port)) + debug.info(2, "Checking delay values for port {}".format(port)) read_port_dict = {} #Get measurements from output file - for measure in self.read_meas_objs: + for measure in self.read_lib_meas: read_port_dict[measure.name] = measure.retrieve_measure(port=port) + debug_passed = self.check_debug_measures(port, read_port_dict) #Check timing for read ports. Power is only checked if it was read correctly - if not self.check_valid_delays(read_port_dict): + if not self.check_valid_delays(read_port_dict) or not debug_passed: 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) #Printing the entire dict looks bad. + debug.error("Failed to Measure Read Port Values:\n\t\t{0}".format(read_port_dict),1) #Printing the entire dict looks bad. result[port].update(read_port_dict) @@ -490,7 +545,7 @@ class delay(simulation): for port in self.targ_write_ports: write_port_dict = {} - for measure in self.write_meas_objs: + 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): @@ -498,13 +553,54 @@ class delay(simulation): result[port].update(write_port_dict) # The delay is from the negative edge for our SRAM - return (True,result) + return (sim_passed,result) + def check_debug_measures(self, port, read_measures): + """Debug measures that indicate special conditions.""" + #Currently, only check if the opposite than intended value was read during + # the read cycles i.e. neither of these measurements should pass. + success = True + for meas in self.debug_delay_meas: + val = meas.retrieve_measure(port=port) + debug.info(2,"{}={}".format(meas.name, val)) + if type(val) != float: + continue + + if meas.meta_add_delay: + max_delay = self.period/2 + else: + max_delay = self.period + + #If the debug measurement occurs after the original (and passes other conditions) + #then it fails i.e. the debug value represents the final (but failing) state of the output + parent_compare = type(read_measures[meas.parent]) != float or val > read_measures[meas.parent] + if 0 < val < max_delay and parent_compare: + success = False + debug.info(1, "Debug measurement failed. Incorrect Value found on output.") + break + + for meas in self.debug_volt_meas: + val = meas.retrieve_measure(port=port) + debug.info(2,"{}={}".format(meas.name, val)) + if type(val) != float: + continue + + if meas.meta_str == 'read1' and val < tech.spice["v_threshold_typical"]: + success = False + debug.info(1, "Debug measurement failed. Value {}v was read on read 1 cycle.".format(val)) + break + elif meas.meta_str == 'read0' and val > self.vdd_voltage-tech.spice["v_threshold_typical"]: + success = False + debug.info(1, "Debug measurement failed. Value {}v was read on read 0 cycle.".format(val)) + break + + return success + def evaluate_bitline_delay(self, port): """Parse and check the bitline delay. One of the measurements is expected to fail which warrants its own function.""" bl_delay_meas_dict = {} values_added = 0 #For error checking - for measure in self.bitline_delay_objs: + for measure in self.bitline_delay_meas: bl_delay_val = measure.retrieve_measure(port=port) if type(bl_delay_val) != float or 0 > bl_delay_val or bl_delay_val > self.period/2: #Only add if value is valid, do not error. debug.error("Bitline delay measurement failed: half-period={}, {}={}".format(self.period/2, measure.name, bl_delay_val),1) @@ -556,7 +652,8 @@ class delay(simulation): 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 - if abs(delay_hl)>half_period or abs(delay_lh)>self.period or abs(slew_hl)>half_period or abs(slew_lh)>self.period: + 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)) diff --git a/compiler/characterizer/measurements.py b/compiler/characterizer/measurements.py index e3d16584..7c568697 100644 --- a/compiler/characterizer/measurements.py +++ b/compiler/characterizer/measurements.py @@ -156,4 +156,32 @@ class voltage_when_measure(spice_measurement): trig_voltage = self.trig_val_of_vdd*vdd_voltage - return (meas_name,trig_name,targ_name,trig_voltage,self.trig_dir_str,trig_td) \ No newline at end of file + return (meas_name,trig_name,targ_name,trig_voltage,self.trig_dir_str,trig_td) + +class voltage_at_measure(spice_measurement): + """Generates a spice measurement to measure the voltage at a specific time. + The time is considered variant with different periods.""" + + def __init__(self, measure_name, targ_name, measure_scale=None): + spice_measurement.__init__(self, measure_name, measure_scale) + self.set_meas_constants(targ_name) + + def get_measure_function(self): + return stimuli.gen_meas_find_voltage_at_time + + def set_meas_constants(self, targ_name): + """Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)""" + self.targ_name_no_port = targ_name + + def get_measure_values(self, time_at, port=None): + """Constructs inputs to stimulus measurement function. Variant values are inputs here.""" + if port != None: + #For dictionary indexing reasons, the name is formatted differently than the signals + meas_name = "{}{}".format(self.name, port) + targ_name = self.targ_name_no_port.format(port) + else: + meas_name = self.name + targ_name = self.targ_name_no_port + + return (meas_name,targ_name,time_at) + \ No newline at end of file diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py index 3b569b2a..01111abd 100644 --- a/compiler/characterizer/stimuli.py +++ b/compiler/characterizer/stimuli.py @@ -226,6 +226,13 @@ class stimuli(): trig_dir, trig_td)) + def gen_meas_find_voltage_at_time(self, meas_name, targ_name, time_at): + """ Creates the .meas statement for voltage at time""" + measure_string=".meas tran {0} FIND v({1}) AT={2}n \n\n" + self.sf.write(measure_string.format(meas_name, + targ_name, + time_at)) + def gen_meas_power(self, meas_name, t_initial, t_final): """ Creates the .meas statement for the measurement of avg power """ # power mea cmd is different in different spice: diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py index b7ccd72c..96a8db84 100755 --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -26,10 +26,15 @@ class timing_sram_test(openram_test): reload(characterizer) from characterizer import delay from sram_config import sram_config - c = sram_config(word_size=1, - num_words=16, + # c = sram_config(word_size=1, + # num_words=16, + # num_banks=1) + # c.words_per_row=1 + c = sram_config(word_size=32, + num_words=256, num_banks=1) - c.words_per_row=1 + c.words_per_row=2 + OPTS.use_tech_delay_chain_size = True c.recompute_sizes() debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank") s = factory.create(module_type="sram", sram_config=c) @@ -42,13 +47,13 @@ class timing_sram_test(openram_test): s.s.graph_exclude_data_dff() s.s.graph_exclude_ctrl_dffs() - debug.info(1,'pins={}'.format(s.s.pins)) - import graph_util - graph = graph_util.timing_graph() - pins=['DIN0[0]', 'ADDR0[0]', 'ADDR0[1]', 'ADDR0[2]', 'ADDR0[3]', 'csb0', 'web0', 'clk0', 'DOUT0[0]', 'vdd', 'gnd'] - s.s.build_graph(graph,"Xsram",pins) - #debug.info(1,"{}".format(graph)) - graph.print_all_paths('clk0', 'DOUT0[0]') + # debug.info(1,'pins={}'.format(s.s.pins)) + # import graph_util + # graph = graph_util.timing_graph() + # pins=['DIN0[0]', 'ADDR0[0]', 'ADDR0[1]', 'ADDR0[2]', 'ADDR0[3]', 'csb0', 'web0', 'clk0', 'DOUT0[0]', 'vdd', 'gnd'] + # s.s.build_graph(graph,"Xsram",pins) + # #debug.info(1,"{}".format(graph)) + # graph.print_all_paths('clk0', 'DOUT0[0]') # import sys # sys.exit(1) From b30c20ffb515754098556172b7403845f02645de Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Tue, 14 May 2019 01:15:50 -0700 Subject: [PATCH 07/22] Added graph creation to characterizer, re-arranged pin creation. --- compiler/characterizer/delay.py | 42 +++++++++++++--------- compiler/characterizer/functional.py | 15 ++------ compiler/characterizer/simulation.py | 48 +++++++++++++++++++++++++ compiler/characterizer/stimuli.py | 50 ++++---------------------- compiler/tests/21_hspice_delay_test.py | 26 +++++++------- 5 files changed, 97 insertions(+), 84 deletions(-) diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 4b8dae7a..3939511f 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -10,6 +10,7 @@ from globals import OPTS from .simulation import simulation from .measurements import * import logical_effort +import graph_util class delay(simulation): """Functions to measure the delay and power of an SRAM at a given address and @@ -37,6 +38,8 @@ class delay(simulation): self.period = 0 self.set_load_slew(0,0) self.set_corner(corner) + self.create_signal_names() + self.add_graph_exclusions() def create_measurement_names(self): """Create measurement names. The names themselves currently define the type of measurement""" @@ -157,19 +160,31 @@ class delay(simulation): self.debug_volt_meas[-1].meta_str = debug_meas.meta_str return self.debug_delay_meas+self.debug_volt_meas - def create_signal_names(self): - self.addr_name = "A" - self.din_name = "DIN" - self.dout_name = "DOUT" - - #This is TODO once multiport control has been finalized. - #self.control_name = "CSB" - 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() + + 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 + 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.build_graph(self.graph,"X{}".format(self.sram.name),self.pins) + #debug.info(1,"{}".format(graph)) + #graph.print_all_paths('clk0', 'DOUT0[0]') + def check_arguments(self): """Checks if arguments given for write_stimulus() meets requirements""" try: @@ -198,12 +213,8 @@ class delay(simulation): # instantiate the sram self.sf.write("\n* Instantiation of the SRAM\n") - self.stim.inst_sram(sram=self.sram, - 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, - sram_name=self.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): @@ -812,7 +823,7 @@ class delay(simulation): char_sram_data = {} self.set_probe(probe_address, probe_data) - self.create_signal_names() + self.create_graph() self.create_measurement_names() self.create_measurement_objects() @@ -998,7 +1009,6 @@ class delay(simulation): """ if OPTS.num_rw_ports > 1 or OPTS.num_w_ports > 0 and OPTS.num_r_ports > 0: debug.warning("Analytical characterization results are not supported for multiport.") - self.create_signal_names() self.create_measurement_names() power = self.analytical_power(slews, loads) port_data = self.get_empty_measure_data_dict() diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index ca05648a..d1471d2f 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -194,12 +194,7 @@ class functional(simulation): new_value = "0" + new_value #print("Binary Conversion: {} to {}".format(value, new_value)) - return new_value - - def create_signal_names(self): - self.addr_name = "A" - self.din_name = "DIN" - self.dout_name = "DOUT" + return new_value def write_functional_stimulus(self): """ Writes SPICE stimulus. """ @@ -219,12 +214,8 @@ class functional(simulation): #Instantiate the SRAM self.sf.write("\n* Instantiation of the SRAM\n") - self.stim.inst_sram(sram=self.sram, - 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, - sram_name=self.name) + self.stim.inst_model(pins=self.pins, + model_name=self.sram.name) # Add load capacitance to each of the read ports self.sf.write("\n* SRAM output loads\n") diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py index beca0502..e14ee635 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -42,6 +42,19 @@ class simulation(): self.v_low = tech.spice["v_threshold_typical"] 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), + abits=self.addr_size, + dbits=self.word_size) + debug.check(len(self.sram.pins) == len(self.pins), "Number of pins generated for characterization \ + do 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" + def set_stimulus_variables(self): # Clock signals self.cycle_times = [] @@ -223,3 +236,38 @@ class simulation(): 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. + 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)) + + for port in range(total_ports): + for i in range(abits): + pin_names.append("{0}{1}_{2}".format(addr_name,port,i)) + + #Control signals not finalized. + for port in range(total_ports): + pin_names.append("CSB{0}".format(port)) + for port in range(total_ports): + if (port in read_index) and (port in write_index): + pin_names.append("WEB{0}".format(port)) + + for port in range(total_ports): + pin_names.append("{0}{1}".format(tech.spice["clk"], port)) + + 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}".format(tech.spice["vdd_name"])) + pin_names.append("{0}".format(tech.spice["gnd_name"])) + return pin_names + diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py index 01111abd..6da966cc 100644 --- a/compiler/characterizer/stimuli.py +++ b/compiler/characterizer/stimuli.py @@ -29,51 +29,15 @@ class stimuli(): (self.process, self.voltage, self.temperature) = corner self.device_models = tech.spice["fet_models"][self.process] - def inst_sram(self, sram, port_signal_names, port_info, abits, dbits, sram_name): + self.sram_name = "Xsram" + + def inst_sram(self, pins, inst_name): """ Function to instatiate an SRAM subckt. """ - pin_names = self.gen_pin_names(port_signal_names, port_info, abits, dbits) - #Only checking length. This should check functionality as well (TODO) and/or import that information from the SRAM - debug.check(len(sram.pins) == len(pin_names), "Number of pins generated for characterization do match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(sram.pins,pin_names)) - - self.sf.write("Xsram ") - for pin in pin_names: + + 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(sram_name)) - - 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. - 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)) - - for port in range(total_ports): - for i in range(abits): - pin_names.append("{0}{1}_{2}".format(addr_name,port,i)) - - #Control signals not finalized. - for port in range(total_ports): - pin_names.append("CSB{0}".format(port)) - for port in range(total_ports): - if (port in read_index) and (port in write_index): - pin_names.append("WEB{0}".format(port)) - - for port in range(total_ports): - pin_names.append("{0}{1}".format(tech.spice["clk"], port)) - - 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}".format(self.vdd_name)) - pin_names.append("{0}".format(self.gnd_name)) - return pin_names + 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 """ diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py index 96a8db84..2cdd3414 100755 --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -26,26 +26,26 @@ class timing_sram_test(openram_test): reload(characterizer) from characterizer import delay from sram_config import sram_config - # c = sram_config(word_size=1, - # num_words=16, - # num_banks=1) - # c.words_per_row=1 - c = sram_config(word_size=32, - num_words=256, + c = sram_config(word_size=1, + num_words=16, num_banks=1) - c.words_per_row=2 - OPTS.use_tech_delay_chain_size = True + c.words_per_row=1 + # c = sram_config(word_size=32, + # num_words=256, + # num_banks=1) + # c.words_per_row=2 + #OPTS.use_tech_delay_chain_size = True c.recompute_sizes() debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank") s = factory.create(module_type="sram", sram_config=c) #Exclude things known not to be in critical path. #Intended for characterizing read paths. Somewhat hacky implementation - s.s.bank.bitcell_array.graph_exclude_bits(15,0) - s.s.bank.graph_exclude_precharge() - s.s.graph_exclude_addr_dff() - s.s.graph_exclude_data_dff() - s.s.graph_exclude_ctrl_dffs() + # s.s.bank.bitcell_array.graph_exclude_bits(15,0) + # s.s.bank.graph_exclude_precharge() + # s.s.graph_exclude_addr_dff() + # s.s.graph_exclude_data_dff() + # s.s.graph_exclude_ctrl_dffs() # debug.info(1,'pins={}'.format(s.s.pins)) # import graph_util From 178d3df5f5964c1e0ce54750d5fcabd8eb92b612 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Tue, 14 May 2019 14:44:49 -0700 Subject: [PATCH 08/22] Added graph to characterizer to get net names and perform s_en checks. Graph not working with column mux. --- compiler/base/graph_util.py | 33 ++++++-- compiler/base/hierarchy_spice.py | 8 +- compiler/characterizer/delay.py | 106 +++++++++++++++---------- compiler/modules/tri_gate.py | 2 +- compiler/sram_1bank.py | 18 +++++ compiler/tests/21_hspice_delay_test.py | 16 ++-- 6 files changed, 121 insertions(+), 62 deletions(-) diff --git a/compiler/base/graph_util.py b/compiler/base/graph_util.py index d2759930..25c79d3b 100644 --- a/compiler/base/graph_util.py +++ b/compiler/base/graph_util.py @@ -1,4 +1,4 @@ -import os +import os, copy from collections import defaultdict import gdsMill @@ -16,6 +16,7 @@ class timing_graph(): def __init__(self): self.graph = defaultdict(set) + self.all_paths = [] def add_edge(self, src_node, dest_node): """Adds edge to graph. Nodes added as well if they do not exist.""" @@ -34,7 +35,7 @@ class timing_graph(): node = node.lower() self.graph[node] = set() - def print_all_paths(self, src_node, dest_node, rmv_rail_nodes=True): + def get_all_paths(self, src_node, dest_node, rmv_rail_nodes=True): """Traverse all paths from source to destination""" src_node = src_node.lower() dest_node = dest_node.lower() @@ -51,13 +52,15 @@ class timing_graph(): # Create an array to store paths path = [] - self.path_count = 0 + self.all_paths = [] # Call the recursive helper function to print all paths - self.print_all_paths_util(src_node, dest_node, visited, path) - debug.info(1, "Paths found={}".format(self.path_count)) + self.get_all_paths_util(src_node, dest_node, visited, path) + debug.info(1, "Paths found={}".format(len(self.all_paths))) - def print_all_paths_util(self, cur_node, dest_node, visited, path): + return self.all_paths + + 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) @@ -67,18 +70,32 @@ class timing_graph(): # current path[] if cur_node == dest_node: debug.info(1,"{}".format(path)) - self.path_count+=1 + self.all_paths.append(copy.deepcopy(path)) 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.print_all_paths_util(node, dest_node, visited, path) + 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) + def get_path_preconvergence_point(self, path1, path2): + """Assuming the inputs paths have the same starting point and end point, the + paths should split and converge at some point before/at the last stage. Finds the + point before convergence.""" + debug.check(path1[0] == path2[0], "Paths must start from the same point.") + debug.check(path1[-1] == path2[-1], "Paths must end from the same point.") + #Paths must end at the same point, so the paths are traversed backwards to find + #point of convergence. There could be multiple points, only finds first. + for point1,point2 in zip(reversed(path1), reversed(path2)): + if point1 != point2: + return (point1,point2) + debug.info(1,"Pre-convergence point not found, paths are equals.") + return path1[0],path2[0] + def __str__(self): """ override print function output """ return "Nodes: {}\nEdges:{} ".format(list(self.graph), self.graph) \ No newline at end of file diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index f2535f86..6dddf4c5 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -140,7 +140,13 @@ class spice(): debug.error("-----") debug.error("Connections: \n"+str(conns_string),1) - + def get_conns(self, inst): + """Returns the connections of a given instance.""" + for i in range(len(self.insts)): + if inst is self.insts[i]: + return self.conns[i] + #If not found, returns None + return None def sp_read(self): """Reads the sp file (and parse the pins) from the library diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 3939511f..b7169a0d 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -81,49 +81,39 @@ class delay(simulation): if obj.meta_str is "read0": obj.meta_add_delay = True - trig_name = "Xsram.s_en{}" #Sense amp enable - if len(self.all_ports) == 1: #special naming case for single port sram bitlines which does not include the port in name - port_format = "" - else: - port_format = "{}" + # trig_name = "Xsram.s_en{}" #Sense amp enable + # if len(self.all_ports) == 1: #special naming case for single port sram bitlines which does not include the port in name + # port_format = "" + # else: + # port_format = "{}" - bl_name = "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column) - br_name = "Xsram.Xbank0.br{}_{}".format(port_format, self.bitline_column) + # bl_name = "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column) + # br_name = "Xsram.Xbank0.br{}_{}".format(port_format, self.bitline_column) # self.read_lib_meas.append(voltage_when_measure(self.voltage_when_names[0], trig_name, bl_name, "RISE", .5)) # self.read_lib_meas.append(voltage_when_measure(self.voltage_when_names[1], trig_name, br_name, "RISE", .5)) read_measures = [] read_measures.append(self.read_lib_meas) #Other measurements associated with the read port not included in the liberty file - read_measures.append(self.create_bitline_delay_measurement_objects()) + read_measures.append(self.create_bitline_measurement_objects()) read_measures.append(self.create_debug_measurement_objects()) return read_measures - def create_bitline_delay_measurement_objects(self): + def create_bitline_measurement_objects(self): """Create the measurements used for bitline delay values. Due to unique error checking, these are separated from other measurements. These measurements are only associated with read values """ - self.bitline_delay_meas = [] - trig_name = "clk{0}" - if len(self.all_ports) == 1: #special naming case for single port sram bitlines which does not include the port in name - port_format = "" - else: - port_format = "{}" - bl_name = "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column) - br_name = "Xsram.Xbank0.br{}_{}".format(port_format, self.bitline_column) - targ_val = (self.vdd_voltage - tech.spice["v_threshold_typical"])/self.vdd_voltage #Calculate as a percentage of vdd + self.bitline_volt_meas = [] + #Bitline voltage measures + self.bitline_volt_meas.append(voltage_at_measure("v_bl_name", + self.bl_name)) + self.bitline_volt_meas[-1].meta_str = 'read0' - targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit - # self.bitline_delay_meas.append(delay_measure(self.bitline_delay_names[0], trig_name, bl_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9)) - # self.bitline_delay_meas[-1].meta_str = "read0" - # self.bitline_delay_meas.append(delay_measure(self.bitline_delay_names[1], trig_name, br_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9)) - # self.bitline_delay_meas[-1].meta_str = "read1" - #Enforces the time delay on the bitline measurements for read0 or read1 - for obj in self.bitline_delay_meas: - obj.meta_add_delay = True - - return self.bitline_delay_meas + self.bitline_volt_meas.append(voltage_at_measure("v_br_name", + self.br_name)) + self.bitline_volt_meas[-1].meta_str = 'read1' + return self.bitline_volt_meas def create_write_port_measurement_objects(self): """Create the measurements used for read ports: delays, slews, powers""" @@ -155,9 +145,11 @@ class delay(simulation): debug_meas.parent = meas.name self.debug_delay_meas.append(debug_meas) + #Output voltage measures self.debug_volt_meas.append(voltage_at_measure("v_{}".format(debug_meas.meta_str), debug_meas.targ_name_no_port)) self.debug_volt_meas[-1].meta_str = debug_meas.meta_str + return self.debug_delay_meas+self.debug_volt_meas def set_load_slew(self,load,slew): @@ -183,7 +175,28 @@ class delay(simulation): self.graph = graph_util.timing_graph() self.sram.build_graph(self.graph,"X{}".format(self.sram.name),self.pins) #debug.info(1,"{}".format(graph)) - #graph.print_all_paths('clk0', 'DOUT0[0]') + port = 0 + self.graph.get_all_paths('{}{}'.format(tech.spice["clk"], port), \ + '{}{}_{}'.format(self.dout_name, port, self.probe_data)) + sen_name = self.sram.get_sen_name(self.sram.name, port).lower() + debug.info(1, "Sen name={}".format(sen_name)) + sen_path = [] + for path in self.graph.all_paths: + if sen_name in path: + sen_path = path + break + debug.check(len(sen_path)!=0, "Could not find s_en timing path.") + preconv_names = [] + for path in self.graph.all_paths: + if not path is sen_path: + sen_preconv = self.graph.get_path_preconvergence_point(path, sen_path) + if sen_preconv not in preconv_names: + preconv_names.append(sen_preconv[0]) #only save non-sen_path names + + #Not an good way to separate inverting and non-inverting bitlines... + self.bl_name = [bl for bl in preconv_names if 'bl' in bl][0] + self.br_name = [bl for bl in preconv_names if 'br' in bl][0] + debug.info(1,"bl_name={}".format(self.bl_name)) def check_arguments(self): """Checks if arguments given for write_stimulus() meets requirements""" @@ -551,9 +564,6 @@ class delay(simulation): result[port].update(read_port_dict) - bitline_delay_dict = self.evaluate_bitline_delay(port) - result[port].update(bitline_delay_dict) - for port in self.targ_write_ports: write_port_dict = {} for measure in self.write_lib_meas: @@ -589,7 +599,14 @@ class delay(simulation): success = False debug.info(1, "Debug measurement failed. Incorrect Value found on output.") break - + + #FIXME: these checks need to be re-done to be more robust against possible errors + bitline_results = {} + for meas in self.bitline_volt_meas: + val = meas.retrieve_measure(port=port) + bitline_results[meas.meta_str] = val + + for meas in self.debug_volt_meas: val = meas.retrieve_measure(port=port) debug.info(2,"{}={}".format(meas.name, val)) @@ -604,19 +621,20 @@ class delay(simulation): success = False debug.info(1, "Debug measurement failed. Value {}v was read on read 0 cycle.".format(val)) break - + + #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 success and self.check_bitline_meas(bitline_results[meas.meta_str]): + debug.error("Sense amp enable timing error. Increase the delay chain through the configuration file.",1) + return success - def evaluate_bitline_delay(self, port): - """Parse and check the bitline delay. One of the measurements is expected to fail which warrants its own function.""" - bl_delay_meas_dict = {} - values_added = 0 #For error checking - for measure in self.bitline_delay_meas: - bl_delay_val = measure.retrieve_measure(port=port) - if type(bl_delay_val) != float or 0 > bl_delay_val or bl_delay_val > self.period/2: #Only add if value is valid, do not error. - debug.error("Bitline delay measurement failed: half-period={}, {}={}".format(self.period/2, measure.name, bl_delay_val),1) - bl_delay_meas_dict[measure.name] = bl_delay_val - return bl_delay_meas_dict + + def check_bitline_meas(self, v_bitline): + """Checks the value of the discharging bitline""" + + return v_bitline < self.vdd_voltage*0.9 + def run_power_simulation(self): """ diff --git a/compiler/modules/tri_gate.py b/compiler/modules/tri_gate.py index 4b7501dd..633715af 100644 --- a/compiler/modules/tri_gate.py +++ b/compiler/modules/tri_gate.py @@ -10,7 +10,7 @@ class tri_gate(design.design): netlist should be available in the technology library. """ - pin_names = ["in", "out", "en", "en_bar", "gnd", "vdd"] + pin_names = ["in", "out", "en", "en_bar", "vdd", "gnd"] type_list = ["INPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("tri_gate", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "tri_gate", GDS["unit"]) diff --git a/compiler/sram_1bank.py b/compiler/sram_1bank.py index ca10d827..cf822861 100644 --- a/compiler/sram_1bank.py +++ b/compiler/sram_1bank.py @@ -327,3 +327,21 @@ class sram_1bank(sram_base): #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.") + debug.info(1,"pins={}".format(self.pins)) + debug.info(1,"cl conns={}".format(control_conns)) + return "X{}.{}".format(sram_name, sen_name) + + diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py index 2cdd3414..16ef6de1 100755 --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -26,15 +26,15 @@ class timing_sram_test(openram_test): reload(characterizer) from characterizer import delay from sram_config import sram_config - c = sram_config(word_size=1, - num_words=16, - num_banks=1) - c.words_per_row=1 - # c = sram_config(word_size=32, - # num_words=256, + # c = sram_config(word_size=1, + # num_words=16, # num_banks=1) - # c.words_per_row=2 - #OPTS.use_tech_delay_chain_size = True + # c.words_per_row=1 + c = sram_config(word_size=32, + num_words=256, + num_banks=1) + c.words_per_row=2 + OPTS.use_tech_delay_chain_size = True c.recompute_sizes() debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank") s = factory.create(module_type="sram", sram_config=c) From a80698918b3b65eaa1a4861c214bd3dc364fcb6b Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Wed, 15 May 2019 17:17:26 -0700 Subject: [PATCH 09/22] Fixed test issues, removed all bitcells not relevant for timing graph. --- compiler/characterizer/delay.py | 33 +++++++++++-------------------- compiler/modules/bitcell_array.py | 7 ++++--- compiler/tests/testutils.py | 5 +---- 3 files changed, 17 insertions(+), 28 deletions(-) diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index b7169a0d..87f0259b 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -174,7 +174,7 @@ class delay(simulation): #Generate new graph every analysis as edges might change depending on test bit self.graph = graph_util.timing_graph() self.sram.build_graph(self.graph,"X{}".format(self.sram.name),self.pins) - #debug.info(1,"{}".format(graph)) + #debug.info(1,"{}".format(self.graph)) port = 0 self.graph.get_all_paths('{}{}'.format(tech.spice["clk"], port), \ '{}{}_{}'.format(self.dout_name, port, self.probe_data)) @@ -605,6 +605,7 @@ class delay(simulation): for meas in self.bitline_volt_meas: val = meas.retrieve_measure(port=port) bitline_results[meas.meta_str] = val + debug.info(1,"{}={}".format(meas.name,val)) for meas in self.debug_volt_meas: @@ -616,11 +617,9 @@ class delay(simulation): if meas.meta_str == 'read1' and val < tech.spice["v_threshold_typical"]: success = False debug.info(1, "Debug measurement failed. Value {}v was read on read 1 cycle.".format(val)) - break elif meas.meta_str == 'read0' and val > self.vdd_voltage-tech.spice["v_threshold_typical"]: success = False debug.info(1, "Debug measurement failed. Value {}v was read on read 0 cycle.".format(val)) - break #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. @@ -831,31 +830,23 @@ class delay(simulation): # Make a copy in temp for debugging shutil.copy(self.sp_file, self.sim_sp_file) - - - def analyze(self,probe_address, probe_data, slews, loads): - """ - Main function to characterize an SRAM for a table. Computes both delay and power characterization. - """ - #Dict to hold all characterization values - char_sram_data = {} - + def analysis_init(self, probe_address, probe_data): + """Sets values which are dependent on the data address/bit being tested.""" self.set_probe(probe_address, probe_data) self.create_graph() self.create_measurement_names() self.create_measurement_objects() + def analyze(self, probe_address, probe_data, slews, loads): + """ + Main function to characterize an SRAM for a table. Computes both delay and power characterization. + """ + #Dict to hold all characterization values + char_sram_data = {} + self.analysis_init(probe_address, probe_data) + self.load=max(loads) self.slew=max(slews) - # This is for debugging a full simulation - # debug.info(0,"Debug simulation running...") - # target_period=50.0 - # feasible_delay_lh=0.059083183 - # feasible_delay_hl=0.17953789 - # load=1.6728 - # slew=0.04 - # self.try_period(target_period, feasible_delay_lh, feasible_delay_hl) - # sys.exit(1) # 1) Find a feasible period and it's corresponding delays using the trimmed array. feasible_delays = self.find_feasible_period() diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index a06a7e90..b049d341 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -198,8 +198,9 @@ class bitcell_array(design.design): """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): - if row == targ_row: - continue - self.graph_inst_exclude.add(self.cell_inst[row,targ_col]) + for col in range(self.column_size): + if row == targ_row and col == targ_col: + continue + self.graph_inst_exclude.add(self.cell_inst[row,col]) \ No newline at end of file diff --git a/compiler/tests/testutils.py b/compiler/tests/testutils.py index 799c223e..e9e35ee6 100755 --- a/compiler/tests/testutils.py +++ b/compiler/tests/testutils.py @@ -62,11 +62,8 @@ class openram_test(unittest.TestCase): """ debug.info(1, "Finding feasible period for current test.") delay_obj.set_load_slew(load, slew) - delay_obj.set_probe(probe_address="1"*sram.addr_size, probe_data=(sram.word_size-1)) test_port = delay_obj.read_ports[0] #Only test one port, assumes other ports have similar period. - delay_obj.create_signal_names() - delay_obj.create_measurement_names() - delay_obj.create_measurement_objects() + delay_obj.analysis_init(probe_address="1"*sram.addr_size, probe_data=(sram.word_size-1)) delay_obj.find_feasible_period_one_port(test_port) return delay_obj.period From 03a762d311e3a62d8b35179b2a955e9af57e1fce Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Thu, 16 May 2019 14:18:33 -0700 Subject: [PATCH 10/22] Replaced constant string comparisons with enums --- compiler/characterizer/charutils.py | 6 ++++ compiler/characterizer/delay.py | 50 ++++++++++++++--------------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/compiler/characterizer/charutils.py b/compiler/characterizer/charutils.py index 7081ff85..87e5adbe 100644 --- a/compiler/characterizer/charutils.py +++ b/compiler/characterizer/charutils.py @@ -8,7 +8,13 @@ import re import debug from globals import OPTS +from enum import Enum +class sram_op(Enum): + READ_ZERO = 0 + READ_ONE = 1 + WRITE_ZERO = 2 + WRITE_ONE = 3 def relative_compare(value1,value2,error_tolerance=0.001): """ This is used to compare relative values for convergence. """ diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 082ef99b..98db7177 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -68,24 +68,24 @@ class delay(simulation): trig_delay_name = "clk{0}" targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit self.read_lib_meas.append(delay_measure("delay_lh", trig_delay_name, targ_name, "RISE", "RISE", measure_scale=1e9)) - self.read_lib_meas[-1].meta_str = "read1" #Used to index time delay values when measurements written to spice file. + self.read_lib_meas[-1].meta_str = sram_op.READ_ONE #Used to index time delay values when measurements written to spice file. self.read_lib_meas.append(delay_measure("delay_hl", trig_delay_name, targ_name, "FALL", "FALL", measure_scale=1e9)) - self.read_lib_meas[-1].meta_str = "read0" + self.read_lib_meas[-1].meta_str = sram_op.READ_ZERO self.delay_meas = self.read_lib_meas[:] #For debugging, kept separated self.read_lib_meas.append(slew_measure("slew_lh", targ_name, "RISE", measure_scale=1e9)) - self.read_lib_meas[-1].meta_str = "read1" + self.read_lib_meas[-1].meta_str = sram_op.READ_ONE self.read_lib_meas.append(slew_measure("slew_hl", targ_name, "FALL", measure_scale=1e9)) - self.read_lib_meas[-1].meta_str = "read0" + self.read_lib_meas[-1].meta_str = sram_op.READ_ZERO self.read_lib_meas.append(power_measure("read1_power", "RISE", measure_scale=1e3)) - self.read_lib_meas[-1].meta_str = "read1" + self.read_lib_meas[-1].meta_str = sram_op.READ_ONE self.read_lib_meas.append(power_measure("read0_power", "FALL", measure_scale=1e3)) - self.read_lib_meas[-1].meta_str = "read0" + self.read_lib_meas[-1].meta_str = sram_op.READ_ZERO #This will later add a half-period to the spice time delay. Only for reading 0. for obj in self.read_lib_meas: - if obj.meta_str is "read0": + if obj.meta_str is sram_op.READ_ZERO: obj.meta_add_delay = True # trig_name = "Xsram.s_en{}" #Sense amp enable @@ -113,13 +113,13 @@ class delay(simulation): """ self.bitline_volt_meas = [] #Bitline voltage measures - self.bitline_volt_meas.append(voltage_at_measure("v_bl_name", - self.bl_name)) - self.bitline_volt_meas[-1].meta_str = 'read0' + self.bitline_volt_meas.append(voltage_at_measure("v_bl", + self.bl_name)) + self.bitline_volt_meas[-1].meta_str = sram_op.READ_ZERO - self.bitline_volt_meas.append(voltage_at_measure("v_br_name", + self.bitline_volt_meas.append(voltage_at_measure("v_br", self.br_name)) - self.bitline_volt_meas[-1].meta_str = 'read1' + self.bitline_volt_meas[-1].meta_str = sram_op.READ_ONE return self.bitline_volt_meas def create_write_port_measurement_objects(self): @@ -127,9 +127,9 @@ class delay(simulation): self.write_lib_meas = [] self.write_lib_meas.append(power_measure("write1_power", "RISE", measure_scale=1e3)) - self.write_lib_meas[-1].meta_str = "write1" + self.write_lib_meas[-1].meta_str = sram_op.WRITE_ONE self.write_lib_meas.append(power_measure("write0_power", "FALL", measure_scale=1e3)) - self.write_lib_meas[-1].meta_str = "write0" + self.write_lib_meas[-1].meta_str = sram_op.WRITE_ZERO write_measures = [] write_measures.append(self.write_lib_meas) @@ -358,10 +358,10 @@ class delay(simulation): """Get the measurement values that can either vary from simulation to simulation (vdd, address) or port to port (time delays)""" #Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port #vdd is arguably constant as that is true for a single lib file. - if delay_obj.meta_str == "read0": + if delay_obj.meta_str == sram_op.READ_ZERO: #Falling delay are measured starting from neg. clk edge. Delay adjusted to that. meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]] - elif delay_obj.meta_str == "read1": + elif delay_obj.meta_str == sram_op.READ_ONE: meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]] else: debug.error("Unrecognised delay Index={}".format(delay_obj.meta_str),1) @@ -383,10 +383,10 @@ class delay(simulation): def get_volt_at_measure_variants(self, port, volt_meas): """Get the measurement values that can either vary port to port (time delays)""" #Only checking 0 value reads for now. - if volt_meas.meta_str == "read0": + if volt_meas.meta_str == sram_op.READ_ZERO: #Falling delay are measured starting from neg. clk edge. Delay adjusted to that. meas_cycle = self.cycle_times[self.measure_cycles[port][volt_meas.meta_str]] - elif volt_meas.meta_str == "read1": + elif volt_meas.meta_str == sram_op.READ_ONE: meas_cycle = self.cycle_times[self.measure_cycles[port][volt_meas.meta_str]] else: debug.error("Unrecognised delay Index={}".format(volt_meas.meta_str),1) @@ -397,7 +397,7 @@ class delay(simulation): def get_volt_when_measure_variants(self, port, volt_meas): """Get the measurement values that can either vary port to port (time delays)""" #Only checking 0 value reads for now. - t_trig = meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]] + t_trig = meas_cycle_delay = self.cycle_times[self.measure_cycles[port][sram_op.READ_ZERO]] return (t_trig, self.vdd_voltage, port) @@ -621,10 +621,10 @@ class delay(simulation): if type(val) != float: continue - if meas.meta_str == 'read1' and val < tech.spice["v_threshold_typical"]: + if meas.meta_str == sram_op.READ_ONE and val < tech.spice["v_threshold_typical"]: success = False debug.info(1, "Debug measurement failed. Value {}v was read on read 1 cycle.".format(val)) - elif meas.meta_str == 'read0' and val > self.vdd_voltage-tech.spice["v_threshold_typical"]: + elif meas.meta_str == sram_op.READ_ZERO and val > self.vdd_voltage-tech.spice["v_threshold_typical"]: success = False debug.info(1, "Debug measurement failed. Value {}v was read on read 0 cycle.".format(val)) @@ -943,7 +943,7 @@ class delay(simulation): self.add_write("W data 0 address {} to write value".format(self.probe_address), self.probe_address,data_zeros,write_port) - self.measure_cycles[write_port]["write0"] = len(self.cycle_times)-1 + self.measure_cycles[write_port][sram_op.WRITE_ZERO] = 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), @@ -951,14 +951,14 @@ class delay(simulation): self.add_read("R data 0 address {} to check W0 worked".format(self.probe_address), self.probe_address,data_zeros,read_port) - self.measure_cycles[read_port]["read0"] = len(self.cycle_times)-1 + self.measure_cycles[read_port][sram_op.READ_ZERO] = len(self.cycle_times)-1 self.add_noop_all_ports("Idle cycle (if read takes >1 cycle)", inverse_address,data_zeros) self.add_write("W data 1 address {} to write value".format(self.probe_address), self.probe_address,data_ones,write_port) - self.measure_cycles[write_port]["write1"] = len(self.cycle_times)-1 + self.measure_cycles[write_port][sram_op.WRITE_ONE] = len(self.cycle_times)-1 self.add_write("W data 0 address {} to clear DIN caps".format(inverse_address), inverse_address,data_zeros,write_port) @@ -969,7 +969,7 @@ class delay(simulation): self.add_read("R data 1 address {} to check W1 worked".format(self.probe_address), self.probe_address,data_zeros,read_port) - self.measure_cycles[read_port]["read1"] = 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))", self.probe_address,data_zeros) From 412f9bb4632a10550558c348af56b4d5da7578b6 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Fri, 17 May 2019 01:56:22 -0700 Subject: [PATCH 11/22] Added additional check to bitline to reduce false positives. --- compiler/characterizer/delay.py | 43 ++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 98db7177..01cb8948 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -113,11 +113,17 @@ class delay(simulation): """ self.bitline_volt_meas = [] #Bitline voltage measures - self.bitline_volt_meas.append(voltage_at_measure("v_bl", + 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[-1].meta_str = sram_op.READ_ZERO - self.bitline_volt_meas.append(voltage_at_measure("v_br", + 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[-1].meta_str = sram_op.READ_ONE return self.bitline_volt_meas @@ -608,39 +614,52 @@ class delay(simulation): break #FIXME: these checks need to be re-done to be more robust against possible errors - bitline_results = {} + bl_vals = {} + br_vals = {} for meas in self.bitline_volt_meas: val = meas.retrieve_measure(port=port) - bitline_results[meas.meta_str] = val + if self.bl_name == meas.targ_name_no_port: + bl_vals[meas.meta_str] = val + elif self.br_name == meas.targ_name_no_port: + br_vals[meas.meta_str] = val + debug.info(1,"{}={}".format(meas.name,val)) - + bl_check = False for meas in self.debug_volt_meas: val = meas.retrieve_measure(port=port) debug.info(2,"{}={}".format(meas.name, val)) if type(val) != float: continue - if meas.meta_str == sram_op.READ_ONE and val < tech.spice["v_threshold_typical"]: + if meas.meta_str == sram_op.READ_ONE and val < self.vdd_voltage*.1: success = False debug.info(1, "Debug measurement failed. Value {}v was read on read 1 cycle.".format(val)) - elif meas.meta_str == sram_op.READ_ZERO and val > self.vdd_voltage-tech.spice["v_threshold_typical"]: + bl_check = 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*.9: success = False debug.info(1, "Debug measurement failed. Value {}v was read on read 0 cycle.".format(val)) + bl_check = self.check_bitline_meas(br_vals[sram_op.READ_ONE], bl_vals[sram_op.READ_ONE]) #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 success and self.check_bitline_meas(bitline_results[meas.meta_str]): + if bl_check: debug.error("Sense amp enable timing error. Increase the delay chain through the configuration file.",1) return success - def check_bitline_meas(self, v_bitline): - """Checks the value of the discharging bitline""" - - return v_bitline < self.vdd_voltage*0.9 + def check_bitline_meas(self, v_discharged_bl, v_charged_bl): + """Checks the value of the discharging bitline. Confirms s_en timing errors. + Returns true if the bitlines are at there expected value.""" + #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 + debug.info(1,"min_dicharge={}, min_diff={}".format(min_dicharge,min_diff)) + return (min_dicharge and min_diff) def run_power_simulation(self): """ From 099bc4e2584c9b2238e67d560dda2e4443ab02a0 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Mon, 20 May 2019 18:35:52 -0700 Subject: [PATCH 12/22] Added bitcell check to storage nodes. --- compiler/base/hierarchy_spice.py | 17 +++++ compiler/bitcells/bitcell.py | 18 +++++ compiler/characterizer/charutils.py | 7 +- compiler/characterizer/delay.py | 100 ++++++++++++++++++++++--- compiler/characterizer/measurements.py | 59 ++++++++------- compiler/modules/bank.py | 6 +- compiler/modules/bitcell_array.py | 4 +- compiler/sram_1bank.py | 11 ++- compiler/tests/21_hspice_delay_test.py | 4 +- 9 files changed, 181 insertions(+), 45 deletions(-) diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index 2efed1da..eeabb61c 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -182,6 +182,23 @@ class spice(): else: self.spice = [] + def check_net_in_spice(self, net_name): + """Checks if a net name exists in the current. Intended to be check nets in hand-made cells.""" + #Remove spaces and lower case then add spaces. Nets are separated by spaces. + net_formatted = ' '+net_name.lstrip().rstrip().lower()+' ' + for line in self.spice: + #Lowercase the line and remove any part of the line that is a comment. + line = line.lower().split('*')[0] + + #Skip .subckt or .ENDS lines + if line.find('.') == 0: + continue + if net_formatted in line: + return True + return False + + + def contains(self, mod, modlist): for x in modlist: if x.name == mod.name: diff --git a/compiler/bitcells/bitcell.py b/compiler/bitcells/bitcell.py index 42b8112b..30766f06 100644 --- a/compiler/bitcells/bitcell.py +++ b/compiler/bitcells/bitcell.py @@ -20,6 +20,7 @@ class bitcell(design.design): """ pin_names = ["bl", "br", "wl", "vdd", "gnd"] + internal_nets = ['Q', 'Qbar'] type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("cell_6t", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "cell_6t", GDS["unit"]) @@ -33,6 +34,7 @@ class bitcell(design.design): self.height = bitcell.height self.pin_map = bitcell.pin_map self.add_pin_types(self.type_list) + self.nets_match = self.check_internal_nets() def analytical_delay(self, corner, slew, load=0, swing = 0.5): parasitic_delay = 1 @@ -77,6 +79,22 @@ class bitcell(design.design): total_power = self.return_power(dynamic, leakage) return total_power + def check_internal_nets(self): + """For handmade cell, checks sp file contains the storage nodes.""" + nets_match = True + for net in self.internal_nets: + nets_match = nets_match and self.check_net_in_spice(net) + return nets_match + + def get_storage_net_names(self): + """Returns names of storage nodes in bitcell in [non-inverting, inverting] format.""" + #Checks that they do exist + if self.nets_match: + return self.internal_nets + else: + debug.info(1,"Storage nodes={} not found in spice file.".format(self.internal_nets)) + return None + def get_wl_cin(self): """Return the relative capacitance of the access transistor gates""" #This is a handmade cell so the value must be entered in the tech.py file or estimated. diff --git a/compiler/characterizer/charutils.py b/compiler/characterizer/charutils.py index 87e5adbe..6b965bc1 100644 --- a/compiler/characterizer/charutils.py +++ b/compiler/characterizer/charutils.py @@ -15,7 +15,11 @@ class sram_op(Enum): READ_ONE = 1 WRITE_ZERO = 2 WRITE_ONE = 3 - + +class bit_polarity(Enum): + NONINVERTING = 0 + INVERTING = 1 + def relative_compare(value1,value2,error_tolerance=0.001): """ This is used to compare relative values for convergence. """ return (abs(value1 - value2) / abs(max(value1,value2)) <= error_tolerance) @@ -38,7 +42,6 @@ def parse_spice_list(filename, key): f.close() # val = re.search(r"{0}\s*=\s*(-?\d+.?\d*\S*)\s+.*".format(key), contents) val = re.search(r"{0}\s*=\s*(-?\d+.?\d*[e]?[-+]?[0-9]*\S*)\s+.*".format(key), contents) - if val != None: debug.info(4, "Key = " + key + " Val = " + val.group(1)) return convert_to_float(val.group(1)) diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 01cb8948..e92663d9 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -60,7 +60,19 @@ class delay(simulation): """Create the measurements used for read and write ports""" 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) + + def check_meas_names(self, measures_lists): + """Given measurements (in 2d list), checks that their names are unique. + Spice sim will fail otherwise.""" + name_set = set() + 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)) + name_set.add(name) + def create_read_port_measurement_objects(self): """Create the measurements used for read ports: delays, slews, powers""" @@ -104,6 +116,7 @@ class delay(simulation): #Other measurements associated with the read port not included in the liberty file read_measures.append(self.create_bitline_measurement_objects()) read_measures.append(self.create_debug_measurement_objects()) + read_measures.append(self.create_read_bit_measures()) return read_measures @@ -164,6 +177,37 @@ class delay(simulation): self.debug_volt_meas[-1].meta_str = debug_meas.meta_str return self.debug_delay_meas+self.debug_volt_meas + + def create_read_bit_measures(self): + """Adds bit measurements for read0 and read1 cycles""" + self.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(): + meas.meta_str = cycle + self.bit_meas[polarity].append(meas) + #Dictionary values are lists, reduce to a single list of measurements + return [meas for meas_list in self.bit_meas.values() for meas in meas_list] + + def get_bit_measures(self, meas_tag, probe_address, probe_data): + """Creates measurements for the q/qbar of input bit position. + meas_tag is a unique identifier for the measurement.""" + bit_col = self.get_data_bit_column_number(probe_address, probe_data) + bit_row = self.get_address_row_number(probe_address) + (cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, bit_row, bit_col) + 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)) + q_name = cell_name+'.'+str(storage_names[0]) + qbar_name = cell_name+'.'+str(storage_names[1]) + #Bit measures, measurements times to be defined later. The measurement names must be unique + # but they is enforced externally + q_meas = voltage_at_measure("v_q_{}".format(meas_tag), q_name, has_port=False) + qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name, has_port=False) + + return {bit_polarity.NONINVERTING:q_meas, bit_polarity.INVERTING:qbar_meas} def set_load_slew(self,load,slew): """ Set the load and slew """ @@ -211,6 +255,11 @@ class delay(simulation): self.br_name = [bl for bl in preconv_names if 'br' in bl][0] debug.info(1,"bl_name={}".format(self.bl_name)) + (cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, self.wordline_row, self.bitline_column) + debug.info(1, "cell_name={}".format(cell_name)) + + + def check_arguments(self): """Checks if arguments given for write_stimulus() meets requirements""" try: @@ -350,16 +399,22 @@ class delay(simulation): """Checks the measurement object and calls respective function for related measurement inputs.""" meas_type = type(measure_obj) if meas_type is delay_measure or meas_type is slew_measure: - return self.get_delay_measure_variants(port, measure_obj) + variant_tuple = self.get_delay_measure_variants(port, measure_obj) elif meas_type is power_measure: - return self.get_power_measure_variants(port, measure_obj, "read") + variant_tuple = self.get_power_measure_variants(port, measure_obj, "read") elif meas_type is voltage_when_measure: - return self.get_volt_when_measure_variants(port, measure_obj) + variant_tuple = self.get_volt_when_measure_variants(port, measure_obj) elif meas_type is voltage_at_measure: - return self.get_volt_at_measure_variants(port, measure_obj) + variant_tuple = self.get_volt_at_measure_variants(port, measure_obj) else: debug.error("Input function not defined for measurement type={}".format(meas_type)) - + #Removes port input from any object which does not use it. This shorthand only works if + #the measurement has port as the last input. Could be implemented by measurement type or + #remove entirely from measurement classes. + if not measure_obj.has_port: + variant_tuple = variant_tuple[:-1] + return variant_tuple + def get_delay_measure_variants(self, port, delay_obj): """Get the measurement values that can either vary from simulation to simulation (vdd, address) or port to port (time delays)""" #Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port @@ -567,10 +622,10 @@ class delay(simulation): #Get measurements from output file for measure in self.read_lib_meas: read_port_dict[measure.name] = measure.retrieve_measure(port=port) - debug_passed = self.check_debug_measures(port, read_port_dict) - + success = self.check_debug_measures(port, read_port_dict) + success = success and self.check_bit_measures() #Check timing for read ports. Power is only checked if it was read correctly - if not self.check_valid_delays(read_port_dict) or not debug_passed: + if not self.check_valid_delays(read_port_dict) or not success: 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) #Printing the entire dict looks bad. @@ -648,7 +703,32 @@ class delay(simulation): return success - + + def check_bit_measures(self): + """Checks the measurements which represent the internal storage voltages + at the end of the read cycle.""" + success = True + for polarity, meas_list in self.bit_meas.items(): + for meas in meas_list: + val = meas.retrieve_measure() + debug.info(1,"{}={}".format(meas.name, val)) + if type(val) != float: + continue + meas_cycle = meas.meta_str + 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*.1 + 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*.9 + if not success: + debug.info(1,("Wrong value detected on probe bit during read cycle. " + "Check writes and control logic for bugs.\n measure={}, op={}, " + "bit_storage={}").format(meas.name, meas_cycle.name, polarity.name)) + return success + + + def check_bitline_meas(self, v_discharged_bl, v_charged_bl): """Checks the value of the discharging bitline. Confirms s_en timing errors. Returns true if the bitlines are at there expected value.""" diff --git a/compiler/characterizer/measurements.py b/compiler/characterizer/measurements.py index 6cd03469..228f6839 100644 --- a/compiler/characterizer/measurements.py +++ b/compiler/characterizer/measurements.py @@ -13,10 +13,11 @@ from .charutils import * class spice_measurement(ABC): """Base class for spice stimulus measurements.""" - def __init__(self, measure_name, measure_scale=None): + def __init__(self, measure_name, measure_scale=None, has_port=True): #Names must be unique for correct spice simulation, but not enforced here. self.name = measure_name self.measure_scale = measure_scale + self.has_port = has_port #Needed for error checking #Some meta values used externally. variables are added here for consistency accross the objects self.meta_str = None self.meta_add_delay = False @@ -35,18 +36,29 @@ class spice_measurement(ABC): measure_vals = self.get_measure_values(*input_tuple) measure_func(stim_obj, *measure_vals) - def retrieve_measure(self, port=""): - value = parse_spice_list("timing", "{0}{1}".format(self.name.lower(), port)) + def retrieve_measure(self, port=None): + self.port_error_check(port) + if port != None: + value = parse_spice_list("timing", "{0}{1}".format(self.name.lower(), port)) + else: + value = parse_spice_list("timing", "{0}".format(self.name.lower())) if type(value)!=float or self.measure_scale == None: return value else: return value*self.measure_scale - + + def port_error_check(self, port): + if self.has_port and port == None: + debug.error("Cannot retrieve measurement, port input was expected.",1) + elif not self.has_port and port != None: + debug.error("Unexpected port input received during measure retrieval.",1) + class delay_measure(spice_measurement): """Generates a spice measurement for the delay of 50%-to-50% points of two signals.""" - def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd=0.5, targ_vdd=0.5, measure_scale=None): - spice_measurement.__init__(self, measure_name, measure_scale) + def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, targ_dir_str,\ + trig_vdd=0.5, targ_vdd=0.5, measure_scale=None, has_port=True): + spice_measurement.__init__(self, measure_name, measure_scale, has_port) self.set_meas_constants(trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd) def get_measure_function(self): @@ -56,10 +68,8 @@ class delay_measure(spice_measurement): """Set the constants for this measurement: signal names, directions, and trigger scales""" self.trig_dir_str = trig_dir_str self.targ_dir_str = targ_dir_str - self.trig_val_of_vdd = trig_vdd self.targ_val_of_vdd = targ_vdd - self.trig_name_no_port = trig_name self.targ_name_no_port = targ_name @@ -67,9 +77,10 @@ class delay_measure(spice_measurement): def get_measure_values(self, trig_td, targ_td, vdd_voltage, port=None): """Constructs inputs to stimulus measurement function. Variant values are inputs here.""" + self.port_error_check(port) trig_val = self.trig_val_of_vdd * vdd_voltage targ_val = self.targ_val_of_vdd * vdd_voltage - + if port != None: #For dictionary indexing reasons, the name is formatted differently than the signals meas_name = "{}{}".format(self.name, port) @@ -79,13 +90,12 @@ class delay_measure(spice_measurement): meas_name = self.name trig_name = self.trig_name_no_port targ_name = self.targ_name_no_port - return (meas_name,trig_name,targ_name,trig_val,targ_val,self.trig_dir_str,self.targ_dir_str,trig_td,targ_td) class slew_measure(delay_measure): - def __init__(self, measure_name, signal_name, slew_dir_str, measure_scale=None): - spice_measurement.__init__(self, measure_name, measure_scale) + def __init__(self, measure_name, signal_name, slew_dir_str, measure_scale=None, has_port=True): + spice_measurement.__init__(self, measure_name, measure_scale, has_port) self.set_meas_constants(signal_name, slew_dir_str) def set_meas_constants(self, signal_name, slew_dir_str): @@ -101,7 +111,6 @@ class slew_measure(delay_measure): self.targ_val_of_vdd = 0.1 else: debug.error("Unrecognised slew measurement direction={}".format(slew_dir_str),1) - self.trig_name_no_port = signal_name self.targ_name_no_port = signal_name @@ -110,8 +119,8 @@ class slew_measure(delay_measure): class power_measure(spice_measurement): """Generates a spice measurement for the average power between two time points.""" - def __init__(self, measure_name, power_type="", measure_scale=None): - spice_measurement.__init__(self, measure_name, measure_scale) + def __init__(self, measure_name, power_type="", measure_scale=None, has_port=True): + spice_measurement.__init__(self, measure_name, measure_scale, has_port) self.set_meas_constants(power_type) def get_measure_function(self): @@ -124,6 +133,7 @@ class power_measure(spice_measurement): def get_measure_values(self, t_initial, t_final, port=None): """Constructs inputs to stimulus measurement function. Variant values are inputs here.""" + self.port_error_check(port) if port != None: meas_name = "{}{}".format(self.name, port) else: @@ -133,8 +143,8 @@ class power_measure(spice_measurement): class voltage_when_measure(spice_measurement): """Generates a spice measurement to measure the voltage of a signal based on the voltage of another.""" - def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, trig_vdd, measure_scale=None): - spice_measurement.__init__(self, measure_name, measure_scale) + def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, trig_vdd, measure_scale=None, has_port=True): + spice_measurement.__init__(self, measure_name, measure_scale, has_port) self.set_meas_constants(trig_name, targ_name, trig_dir_str, trig_vdd) def get_measure_function(self): @@ -144,13 +154,12 @@ class voltage_when_measure(spice_measurement): """Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)""" self.trig_dir_str = trig_dir_str self.trig_val_of_vdd = trig_vdd - self.trig_name_no_port = trig_name self.targ_name_no_port = targ_name def get_measure_values(self, trig_td, vdd_voltage, port=None): """Constructs inputs to stimulus measurement function. Variant values are inputs here.""" - + self.port_error_check(port) if port != None: #For dictionary indexing reasons, the name is formatted differently than the signals meas_name = "{}{}".format(self.name, port) @@ -160,17 +169,15 @@ class voltage_when_measure(spice_measurement): meas_name = self.name trig_name = self.trig_name_no_port targ_name = self.targ_name_no_port - - trig_voltage = self.trig_val_of_vdd*vdd_voltage - + trig_voltage = self.trig_val_of_vdd*vdd_voltage return (meas_name,trig_name,targ_name,trig_voltage,self.trig_dir_str,trig_td) class voltage_at_measure(spice_measurement): """Generates a spice measurement to measure the voltage at a specific time. The time is considered variant with different periods.""" - def __init__(self, measure_name, targ_name, measure_scale=None): - spice_measurement.__init__(self, measure_name, measure_scale) + def __init__(self, measure_name, targ_name, measure_scale=None, has_port=True): + spice_measurement.__init__(self, measure_name, measure_scale, has_port) self.set_meas_constants(targ_name) def get_measure_function(self): @@ -182,13 +189,13 @@ class voltage_at_measure(spice_measurement): def get_measure_values(self, time_at, port=None): """Constructs inputs to stimulus measurement function. Variant values are inputs here.""" + self.port_error_check(port) if port != None: #For dictionary indexing reasons, the name is formatted differently than the signals meas_name = "{}{}".format(self.name, port) targ_name = self.targ_name_no_port.format(port) else: meas_name = self.name - targ_name = self.targ_name_no_port - + targ_name = self.targ_name_no_port return (meas_name,targ_name,time_at) \ No newline at end of file diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 72702069..efdf228d 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -1286,4 +1286,8 @@ class bank(design.design): """Precharge adds a loop between bitlines, can be excluded to reduce complexity""" for inst in self.precharge_array_inst: if inst != None: - self.graph_inst_exclude.add(inst) \ No newline at end of file + self.graph_inst_exclude.add(inst) + + def get_cell_name(self, inst_name, row, col): + """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) \ No newline at end of file diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index e19c7636..2380ee4b 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -210,4 +210,6 @@ class bitcell_array(design.design): continue self.graph_inst_exclude.add(self.cell_inst[row,col]) - \ No newline at end of file + def get_cell_name(self, inst_name, row, col): + """Gets the spice name of the target bitcell.""" + return inst_name+'.x'+self.cell_inst[row,col].name, self.cell_inst[row,col] \ No newline at end of file diff --git a/compiler/sram_1bank.py b/compiler/sram_1bank.py index fdd41e8b..8d8ff65f 100644 --- a/compiler/sram_1bank.py +++ b/compiler/sram_1bank.py @@ -343,9 +343,12 @@ class sram_1bank(sram_base): 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.") - debug.info(1,"pins={}".format(self.pins)) - debug.info(1,"cl conns={}".format(control_conns)) + debug.error("Internal signal={} contained in port list. Name defined by the parent.") return "X{}.{}".format(sram_name, sen_name) - + def get_cell_name(self, inst_name, row, col): + """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) \ No newline at end of file diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py index 456dd915..d0fa88da 100755 --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -45,7 +45,9 @@ class timing_sram_test(openram_test): c.recompute_sizes() debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank") s = factory.create(module_type="sram", sram_config=c) - + #import sys + #sys.exit(1) + tempspice = OPTS.openram_temp + "temp.sp" s.sp_write(tempspice) From d08181455c436aef8996a86da24fcaea5996fbe6 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Mon, 20 May 2019 22:50:03 -0700 Subject: [PATCH 13/22] Added multiport bitcell support for storage node checks --- compiler/base/hierarchy_spice.py | 7 ++++++- compiler/bitcells/bitcell.py | 17 +++++------------ compiler/bitcells/bitcell_1rw_1r.py | 13 ++++++++++++- compiler/bitcells/bitcell_1w_1r.py | 13 ++++++++++++- compiler/bitcells/pbitcell.py | 23 +++++++++++++++-------- 5 files changed, 50 insertions(+), 23 deletions(-) diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index eeabb61c..e668f535 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -197,7 +197,12 @@ class spice(): return True return False - + def do_nets_exist(self, nets): + """For handmade cell, checks sp file contains the storage nodes.""" + nets_match = True + for net in nets: + nets_match = nets_match and self.check_net_in_spice(net) + return nets_match def contains(self, mod, modlist): for x in modlist: diff --git a/compiler/bitcells/bitcell.py b/compiler/bitcells/bitcell.py index 30766f06..0de11375 100644 --- a/compiler/bitcells/bitcell.py +++ b/compiler/bitcells/bitcell.py @@ -20,7 +20,7 @@ class bitcell(design.design): """ pin_names = ["bl", "br", "wl", "vdd", "gnd"] - internal_nets = ['Q', 'Qbar'] + storage_nets = ['Q', 'Qbar'] type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("cell_6t", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "cell_6t", GDS["unit"]) @@ -34,7 +34,7 @@ class bitcell(design.design): self.height = bitcell.height self.pin_map = bitcell.pin_map self.add_pin_types(self.type_list) - self.nets_match = self.check_internal_nets() + self.nets_match = self.do_nets_exist(self.storage_nets) def analytical_delay(self, corner, slew, load=0, swing = 0.5): parasitic_delay = 1 @@ -78,21 +78,14 @@ class bitcell(design.design): dynamic = 0 #temporary total_power = self.return_power(dynamic, leakage) return total_power - - def check_internal_nets(self): - """For handmade cell, checks sp file contains the storage nodes.""" - nets_match = True - for net in self.internal_nets: - nets_match = nets_match and self.check_net_in_spice(net) - return nets_match - + def get_storage_net_names(self): """Returns names of storage nodes in bitcell in [non-inverting, inverting] format.""" #Checks that they do exist if self.nets_match: - return self.internal_nets + return self.storage_nets else: - debug.info(1,"Storage nodes={} not found in spice file.".format(self.internal_nets)) + debug.info(1,"Storage nodes={} not found in spice file.".format(self.storage_nets)) return None def get_wl_cin(self): diff --git a/compiler/bitcells/bitcell_1rw_1r.py b/compiler/bitcells/bitcell_1rw_1r.py index 67373010..6a6ebd92 100644 --- a/compiler/bitcells/bitcell_1rw_1r.py +++ b/compiler/bitcells/bitcell_1rw_1r.py @@ -20,7 +20,8 @@ class bitcell_1rw_1r(design.design): """ pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] - type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"] + type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"] + storage_nets = ['Q', 'Q_bar'] (width,height) = utils.get_libcell_size("cell_1rw_1r", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "cell_1rw_1r", GDS["unit"]) @@ -33,6 +34,7 @@ class bitcell_1rw_1r(design.design): self.height = bitcell_1rw_1r.height self.pin_map = bitcell_1rw_1r.pin_map self.add_pin_types(self.type_list) + self.nets_match = self.do_nets_exist(self.storage_nets) def analytical_delay(self, corner, slew, load=0, swing = 0.5): parasitic_delay = 1 @@ -109,6 +111,15 @@ class bitcell_1rw_1r(design.design): access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] return 2*access_tx_cin + def get_storage_net_names(self): + """Returns names of storage nodes in bitcell in [non-inverting, inverting] format.""" + #Checks that they do exist + if self.nets_match: + return self.storage_nets + else: + debug.info(1,"Storage nodes={} not found in spice file.".format(self.storage_nets)) + return None + def build_graph(self, graph, inst_name, port_nets): """Adds edges to graph. Multiport bitcell timing graph is too complex to use the add_graph_edges function.""" diff --git a/compiler/bitcells/bitcell_1w_1r.py b/compiler/bitcells/bitcell_1w_1r.py index b3c2a5bd..dded6b30 100644 --- a/compiler/bitcells/bitcell_1w_1r.py +++ b/compiler/bitcells/bitcell_1w_1r.py @@ -20,7 +20,8 @@ class bitcell_1w_1r(design.design): """ pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] - type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"] + type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"] + storage_nets = ['Q', 'Q_bar'] (width,height) = utils.get_libcell_size("cell_1w_1r", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "cell_1w_1r", GDS["unit"]) @@ -33,6 +34,7 @@ class bitcell_1w_1r(design.design): self.height = bitcell_1w_1r.height self.pin_map = bitcell_1w_1r.pin_map self.add_pin_types(self.type_list) + self.nets_match = self.do_nets_exist(self.storage_nets) def analytical_delay(self, corner, slew, load=0, swing = 0.5): parasitic_delay = 1 @@ -109,6 +111,15 @@ class bitcell_1w_1r(design.design): access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] return 2*access_tx_cin + def get_storage_net_names(self): + """Returns names of storage nodes in bitcell in [non-inverting, inverting] format.""" + #Checks that they do exist + if self.nets_match: + return self.storage_nets + else: + debug.info(1,"Storage nodes={} not found in spice file.".format(self.storage_nets)) + return None + def build_graph(self, graph, inst_name, port_nets): """Adds edges to graph. Multiport bitcell timing graph is too complex to use the add_graph_edges function.""" diff --git a/compiler/bitcells/pbitcell.py b/compiler/bitcells/pbitcell.py index f0f85fe6..899da398 100644 --- a/compiler/bitcells/pbitcell.py +++ b/compiler/bitcells/pbitcell.py @@ -138,7 +138,9 @@ class pbitcell(design.design): self.Q_bar = "vdd" else: self.Q_bar = "Q_bar" - + self.Q = "Q" + self.storage_nets = [self.Q, self.Q_bar] + def add_modules(self): """ Determine size of transistors and add ptx modules """ # if there are any read/write ports, then the inverter nmos is sized based the number of read/write ports @@ -258,20 +260,20 @@ class pbitcell(design.design): # create active for nmos self.inverter_nmos_left = self.add_inst(name="inverter_nmos_left", mod=self.inverter_nmos) - self.connect_inst(["Q", self.Q_bar, "gnd", "gnd"]) + self.connect_inst([self.Q, self.Q_bar, "gnd", "gnd"]) self.inverter_nmos_right = self.add_inst(name="inverter_nmos_right", mod=self.inverter_nmos) - self.connect_inst(["gnd", "Q", self.Q_bar, "gnd"]) + self.connect_inst(["gnd", self.Q, self.Q_bar, "gnd"]) # create active for pmos self.inverter_pmos_left = self.add_inst(name="inverter_pmos_left", mod=self.inverter_pmos) - self.connect_inst(["Q", self.Q_bar, "vdd", "vdd"]) + self.connect_inst([self.Q, self.Q_bar, "vdd", "vdd"]) self.inverter_pmos_right = self.add_inst(name="inverter_pmos_right", mod=self.inverter_pmos) - self.connect_inst(["vdd", "Q", self.Q_bar, "vdd"]) + self.connect_inst(["vdd", self.Q, self.Q_bar, "vdd"]) def place_storage(self): """ Places the transistors for the crossed coupled inverters in the bitcell """ @@ -363,7 +365,7 @@ class pbitcell(design.design): # add read/write transistors self.readwrite_nmos_left[k] = self.add_inst(name="readwrite_nmos_left{}".format(k), mod=self.readwrite_nmos) - self.connect_inst([self.rw_bl_names[k], self.rw_wl_names[k], "Q", "gnd"]) + self.connect_inst([self.rw_bl_names[k], self.rw_wl_names[k], self.Q, "gnd"]) self.readwrite_nmos_right[k] = self.add_inst(name="readwrite_nmos_right{}".format(k), mod=self.readwrite_nmos) @@ -437,7 +439,7 @@ class pbitcell(design.design): # add write transistors self.write_nmos_left[k] = self.add_inst(name="write_nmos_left{}".format(k), mod=self.write_nmos) - self.connect_inst([self.w_bl_names[k], self.w_wl_names[k], "Q", "gnd"]) + self.connect_inst([self.w_bl_names[k], self.w_wl_names[k], self.Q, "gnd"]) self.write_nmos_right[k] = self.add_inst(name="write_nmos_right{}".format(k), mod=self.write_nmos) @@ -522,7 +524,7 @@ class pbitcell(design.design): self.read_access_nmos_right[k] = self.add_inst(name="read_access_nmos_right{}".format(k), mod=self.read_nmos) - self.connect_inst(["gnd", "Q", "RA_to_R_right{}".format(k), "gnd"]) + self.connect_inst(["gnd", self.Q, "RA_to_R_right{}".format(k), "gnd"]) # add read transistors self.read_nmos_left[k] = self.add_inst(name="read_nmos_left{}".format(k), @@ -866,6 +868,11 @@ class pbitcell(design.design): Q_bar_pos = self.inverter_pmos_right.get_pin("S").center() vdd_pos = self.inverter_pmos_right.get_pin("D").center() self.add_path("metal1", [Q_bar_pos, vdd_pos]) + + def get_storage_net_names(self): + """Returns names of storage nodes in bitcell in [non-inverting, inverting] format.""" + return self.storage_nets + def analytical_delay(self, corner, slew, load=0, swing = 0.5): parasitic_delay = 1 From e2d1f7ab0ae66b798f7f8814dd972ec576330ed4 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Mon, 27 May 2019 13:08:59 -0700 Subject: [PATCH 14/22] Added smarter name checking for the characterizer. --- compiler/base/graph_util.py | 2 +- compiler/base/hierarchy_design.py | 79 ++++++++++++++++++++++++++----- compiler/characterizer/delay.py | 78 ++++++++++++++++++++++-------- compiler/modules/sense_amp.py | 9 +++- compiler/sram_factory.py | 10 +++- 5 files changed, 142 insertions(+), 36 deletions(-) diff --git a/compiler/base/graph_util.py b/compiler/base/graph_util.py index 25c79d3b..af4d0888 100644 --- a/compiler/base/graph_util.py +++ b/compiler/base/graph_util.py @@ -8,7 +8,7 @@ import globals import debug from vector import vector from pin_layout import pin_layout - + class timing_graph(): """Implements a directed graph Nodes are currently just Strings. diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index 1bb9f44e..f6b10530 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -104,17 +104,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): total_lvs_errors += num_errors debug.check(num_errors == 0,"LVS failed for {0} with {1} error(s)".format(self.name,num_errors)) os.remove(tempspice) - os.remove(tempgds) - - #Example graph run - # import graph_util - # graph = graph_util.graph() - # pins = ['A','Z','vdd','gnd'] - # d.build_graph(graph,"Xpdriver",pins) - # graph.remove_edges('vdd') - # graph.remove_edges('gnd') - # debug.info(1,"{}".format(graph)) - # graph.print_all_paths('A', 'Z') + os.remove(tempgds) def init_graph_params(self): """Initializes parameters relevant to the graph creation""" @@ -136,6 +126,71 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): subinst_ports = self.translate_nets(conns, port_dict, inst_name) 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.""" + #Translate port names to external nets + if len(port_nets) != len(self.pins): + debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,self.pins),1) + port_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + debug.info(3, "Instance name={}".format(inst_name)) + for subinst, conns in zip(self.insts, self.conns): + subinst_name = inst_name+'.X'+subinst.name + subinst_ports = self.translate_nets(conns, port_dict, inst_name) + for si_port, conn in zip(subinst_ports, conns): + #Only add for first occurrence + if si_port.lower() not in name_dict: + mod_info = {'mod':self, 'int_net':conn} + 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): + """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). + """ + 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): + aliases.append(net) + debug.info(1,"Aliases Found={}".format(aliases)) + return aliases + + def is_net_alias(self, known_net, net_alias, mod): + """Checks if the alias_net in mod is the same as the input net.""" + #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 + 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(): + return subinst.mod.is_net_alias(mod_pin, net_alias, 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""" converted_conns = [] @@ -159,7 +214,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): for out in output_pins+inout_pins: if inp != out: #do not add self loops graph.add_edge(pin_dict[inp], pin_dict[out]) - + def __str__(self): """ override print function output """ pins = ",".join(self.pins) diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index e92663d9..3f97f885 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -18,6 +18,7 @@ from .simulation import simulation from .measurements import * import logical_effort import graph_util +from sram_factory import factory class delay(simulation): """Functions to measure the delay and power of an SRAM at a given address and @@ -99,17 +100,6 @@ class delay(simulation): for obj in self.read_lib_meas: if obj.meta_str is sram_op.READ_ZERO: obj.meta_add_delay = True - - # trig_name = "Xsram.s_en{}" #Sense amp enable - # if len(self.all_ports) == 1: #special naming case for single port sram bitlines which does not include the port in name - # port_format = "" - # else: - # port_format = "{}" - - # bl_name = "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column) - # br_name = "Xsram.Xbank0.br{}_{}".format(port_format, self.bitline_column) - # self.read_lib_meas.append(voltage_when_measure(self.voltage_when_names[0], trig_name, bl_name, "RISE", .5)) - # self.read_lib_meas.append(voltage_when_measure(self.voltage_when_names[1], trig_name, br_name, "RISE", .5)) read_measures = [] read_measures.append(self.read_lib_meas) @@ -230,19 +220,20 @@ class delay(simulation): #Generate new graph every analysis as edges might change depending on test bit self.graph = graph_util.timing_graph() - self.sram.build_graph(self.graph,"X{}".format(self.sram.name),self.pins) + self.sram_spc_name = "X{}".format(self.sram.name) + self.sram.build_graph(self.graph,self.sram_spc_name,self.pins) #debug.info(1,"{}".format(self.graph)) port = 0 self.graph.get_all_paths('{}{}'.format(tech.spice["clk"], port), \ '{}{}_{}'.format(self.dout_name, port, self.probe_data)) - sen_name = self.sram.get_sen_name(self.sram.name, port).lower() - debug.info(1, "Sen name={}".format(sen_name)) - sen_path = [] - for path in self.graph.all_paths: - if sen_name in path: - sen_path = path - break - debug.check(len(sen_path)!=0, "Could not find s_en timing path.") + + sen_name = self.get_sen_name(self.graph.all_paths) + debug.info(1,"s_en name = {}".format(sen_name)) + + self.bl_name,self.br_name = self.get_bl_name(self.graph.all_paths) + import sys + sys.exit(1) + preconv_names = [] for path in self.graph.all_paths: if not path is sen_path: @@ -258,7 +249,52 @@ class delay(simulation): (cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, self.wordline_row, self.bitline_column) debug.info(1, "cell_name={}".format(cell_name)) - + 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_found = False + #Only a single path should contain a single s_en name. Anything else is an error. + for path in paths: + aliases = self.sram.find_aliases(self.sram_spc_name, self.pins, path, enable_name, sa_mods[0]) + if sen_found and len(aliases) >= 1: + debug.error('Found multiple paths with SA enable.',1) + elif len(aliases) > 1: + debug.error('Found multiple S_EN points in single path. Cannot distinguish between them.',1) + elif not sen_found and len(aliases) == 1: + sen_name = aliases[0] + sen_found = True + if not sen_found: + debug.error("Could not find S_EN name.",1) + + return sen_name + + def get_bl_name(self, paths): + """Gets the signal name associated with the bitlines in the bank.""" + 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_found = False + #Only a single path should contain a single s_en name. Anything else is an error. + for path in paths: + aliases = self.sram.find_aliases(self.sram_spc_name, self.pins, path, enable_name, sa_mods[0]) + if sen_found and len(aliases) >= 1: + debug.error('Found multiple paths with SA enable.',1) + elif len(aliases) > 1: + debug.error('Found multiple S_EN points in single path. Cannot distinguish between them.',1) + elif not sen_found and len(aliases) == 1: + sen_name = aliases[0] + sen_found = True + if not sen_found: + debug.error("Could not find S_EN name.",1) + + return sen_name def check_arguments(self): """Checks if arguments given for write_stimulus() meets requirements""" diff --git a/compiler/modules/sense_amp.py b/compiler/modules/sense_amp.py index e28f3efa..8e0ff112 100644 --- a/compiler/modules/sense_amp.py +++ b/compiler/modules/sense_amp.py @@ -61,7 +61,14 @@ class sense_amp(design.design): 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""" + #FIXME: A better programmatic solution to designate pins + enable_name = "en" + debug.check(enable_name in self.pin_names, "Enable name {} not found in pin list".format(enable_name)) + return enable_name + 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/sram_factory.py b/compiler/sram_factory.py index 325261a0..90478f4c 100644 --- a/compiler/sram_factory.py +++ b/compiler/sram_factory.py @@ -83,7 +83,15 @@ class sram_factory: self.objects[module_type].append((kwargs,obj)) return obj - + def get_mods(self, module_type): + """Returns list of all objects of module name's type.""" + try: + mod_tuples = self.objects[module_type] + mods = [mod for kwargs,mod in mod_tuples] + except KeyError: + mods = [] + return mods + # Make a factory factory = sram_factory() From ad229b1504b5a3d77a113cbdcd08e63610ce37c3 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Tue, 28 May 2019 16:55:09 -0700 Subject: [PATCH 15/22] Altered indexing of objects in SRAM factory to remove duplications of items using OPTS names. Added smarter bitline name checking. --- compiler/base/hierarchy_design.py | 35 +++++- compiler/bitcells/bitcell.py | 8 ++ compiler/bitcells/bitcell_1rw_1r.py | 8 ++ compiler/bitcells/bitcell_1w_1r.py | 8 ++ compiler/bitcells/pbitcell.py | 7 ++ compiler/bitcells/replica_pbitcell.py | 1 + compiler/characterizer/delay.py | 171 ++++++++++++++++---------- compiler/modules/bitcell_array.py | 1 + compiler/sram_factory.py | 18 +-- 9 files changed, 182 insertions(+), 75 deletions(-) diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index f6b10530..a75e5a3c 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -143,38 +143,61 @@ 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): + 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). """ + #path_nets = ['xsram_0.xbank0.bl0_7'] + if exclusion_set == None: + exclusion_set = set() try: self.name_dict except AttributeError: self.name_dict = {} self.build_names(self.name_dict, inst_name, port_nets) + #debug.info(1,"names={}".format(list(self.name_dict))) 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): + # debug.info(1,"int_net={}".format(int_net)) + # debug.info(1,"int_mod={}".format(int_mod.name)) + # debug.info(1,"alias_net={}".format(alias)) + # debug.info(1,"alias_mod={}".format(alias_mod.name)) + # debug.info(1,"mod id={}".format(id(alias_mod))) + if int_mod.is_net_alias(int_net, alias, alias_mod, exclusion_set): aliases.append(net) + # debug.info(1,"Alias found\n") + # else: + # debug.info(1,"Alias not found\n") debug.info(1,"Aliases Found={}".format(aliases)) return aliases - def is_net_alias(self, known_net, net_alias, mod): + def is_net_alias(self, known_net, net_alias, mod, exclusion_set): """Checks if the alias_net in mod is the same as the input net.""" + # debug.info(1,"self name={}".format(self.name)) + # debug.info(1,"self mod id={}".format(id(self))) + # debug.info(1,"self pins={}".format(self.pins)) + # debug.info(1,"known_net={}".format(known_net)) + 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 + #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(): - return subinst.mod.is_net_alias(mod_pin, net_alias, mod) + elif inst_conn.lower() == known_net.lower() and subinst.mod not in mod_set: + # debug.info(1,"found matching conn={}".format(inst_conn)) + # debug.info(1,"Setting known pin={}".format(mod_pin)) + 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): diff --git a/compiler/bitcells/bitcell.py b/compiler/bitcells/bitcell.py index 0de11375..b3f8dea1 100644 --- a/compiler/bitcells/bitcell.py +++ b/compiler/bitcells/bitcell.py @@ -71,6 +71,14 @@ class bitcell(design.design): column_pins = ["br"] return column_pins + def get_bl_name(self): + """Get bl name""" + return "bl" + + def get_br_name(self): + """Get bl name""" + return "br" + def analytical_power(self, corner, load): """Bitcell power in nW. Only characterizes leakage.""" from tech import spice diff --git a/compiler/bitcells/bitcell_1rw_1r.py b/compiler/bitcells/bitcell_1rw_1r.py index 6a6ebd92..f5aafa21 100644 --- a/compiler/bitcells/bitcell_1rw_1r.py +++ b/compiler/bitcells/bitcell_1rw_1r.py @@ -95,6 +95,14 @@ class bitcell_1rw_1r(design.design): column_pins = ["br0"] return column_pins + def get_bl_name(self, port=0): + """Get bl name by port""" + return "bl{}".format(port) + + def get_br_name(self, port=0): + """Get bl name by port""" + return "br{}".format(port) + def analytical_power(self, corner, load): """Bitcell power in nW. Only characterizes leakage.""" from tech import spice diff --git a/compiler/bitcells/bitcell_1w_1r.py b/compiler/bitcells/bitcell_1w_1r.py index dded6b30..6046020c 100644 --- a/compiler/bitcells/bitcell_1w_1r.py +++ b/compiler/bitcells/bitcell_1w_1r.py @@ -95,6 +95,14 @@ class bitcell_1w_1r(design.design): column_pins = ["br0"] return column_pins + def get_bl_name(self, port=0): + """Get bl name by port""" + return "bl{}".format(port) + + def get_br_name(self, port=0): + """Get bl name by port""" + return "br{}".format(port) + def analytical_power(self, corner, load): """Bitcell power in nW. Only characterizes leakage.""" from tech import spice diff --git a/compiler/bitcells/pbitcell.py b/compiler/bitcells/pbitcell.py index 899da398..23d019cb 100644 --- a/compiler/bitcells/pbitcell.py +++ b/compiler/bitcells/pbitcell.py @@ -873,6 +873,13 @@ class pbitcell(design.design): """Returns names of storage nodes in bitcell in [non-inverting, inverting] format.""" return self.storage_nets + def get_bl_name(self, port=0): + """Get bl name by port""" + return "bl{}".format(port) + + def get_br_name(self, port=0): + """Get bl name by port""" + return "br{}".format(port) def analytical_delay(self, corner, slew, load=0, swing = 0.5): parasitic_delay = 1 diff --git a/compiler/bitcells/replica_pbitcell.py b/compiler/bitcells/replica_pbitcell.py index 30898d82..d39d361a 100644 --- a/compiler/bitcells/replica_pbitcell.py +++ b/compiler/bitcells/replica_pbitcell.py @@ -54,6 +54,7 @@ class replica_pbitcell(design.design): def add_modules(self): self.prbc = factory.create(module_type="pbitcell",replica_bitcell=True) + debug.info(1,"rbl bitcell name={}".format(self.prbc.name)) self.add_mod(self.prbc) self.height = self.prbc.height diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 3f97f885..3da84ede 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -78,18 +78,21 @@ class delay(simulation): """Create the measurements used for read ports: delays, slews, powers""" self.read_lib_meas = [] - trig_delay_name = "clk{0}" + 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 - self.read_lib_meas.append(delay_measure("delay_lh", trig_delay_name, targ_name, "RISE", "RISE", measure_scale=1e9)) - self.read_lib_meas[-1].meta_str = sram_op.READ_ONE #Used to index time delay values when measurements written to spice file. - self.read_lib_meas.append(delay_measure("delay_hl", trig_delay_name, targ_name, "FALL", "FALL", measure_scale=1e9)) - self.read_lib_meas[-1].meta_str = sram_op.READ_ZERO - self.delay_meas = self.read_lib_meas[:] #For debugging, kept separated + 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. + self.delay_meas.append(delay_measure("delay_hl", self.clk_frmt, targ_name, "FALL", "FALL", measure_scale=1e9)) + self.delay_meas[-1].meta_str = sram_op.READ_ZERO + self.read_lib_meas+=self.delay_meas - self.read_lib_meas.append(slew_measure("slew_lh", targ_name, "RISE", measure_scale=1e9)) - self.read_lib_meas[-1].meta_str = sram_op.READ_ONE - self.read_lib_meas.append(slew_measure("slew_hl", targ_name, "FALL", measure_scale=1e9)) - self.read_lib_meas[-1].meta_str = sram_op.READ_ZERO + self.slew_meas = [] + self.slew_meas.append(slew_measure("slew_lh", targ_name, "RISE", measure_scale=1e9)) + self.slew_meas[-1].meta_str = sram_op.READ_ONE + self.slew_meas.append(slew_measure("slew_hl", targ_name, "FALL", measure_scale=1e9)) + self.slew_meas[-1].meta_str = sram_op.READ_ZERO + self.read_lib_meas+=self.slew_meas self.read_lib_meas.append(power_measure("read1_power", "RISE", measure_scale=1e3)) self.read_lib_meas[-1].meta_str = sram_op.READ_ONE @@ -162,11 +165,15 @@ class delay(simulation): self.debug_delay_meas.append(debug_meas) #Output voltage measures - self.debug_volt_meas.append(voltage_at_measure("v_{}".format(debug_meas.meta_str), + self.debug_volt_meas.append(voltage_at_measure("v_{}".format(debug_meas.name), debug_meas.targ_name_no_port)) self.debug_volt_meas[-1].meta_str = debug_meas.meta_str - - return self.debug_delay_meas+self.debug_volt_meas + + 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_add_delay = True + + return self.debug_delay_meas+self.debug_volt_meas+[self.sen_meas] def create_read_bit_measures(self): """Adds bit measurements for read0 and read1 cycles""" @@ -227,27 +234,11 @@ class delay(simulation): self.graph.get_all_paths('{}{}'.format(tech.spice["clk"], port), \ '{}{}_{}'.format(self.dout_name, port, self.probe_data)) - sen_name = self.get_sen_name(self.graph.all_paths) - debug.info(1,"s_en name = {}".format(sen_name)) + self.sen_name = self.get_sen_name(self.graph.all_paths) + debug.info(1,"s_en name = {}".format(self.sen_name)) self.bl_name,self.br_name = self.get_bl_name(self.graph.all_paths) - import sys - sys.exit(1) - - preconv_names = [] - for path in self.graph.all_paths: - if not path is sen_path: - sen_preconv = self.graph.get_path_preconvergence_point(path, sen_path) - if sen_preconv not in preconv_names: - preconv_names.append(sen_preconv[0]) #only save non-sen_path names - - #Not an good way to separate inverting and non-inverting bitlines... - self.bl_name = [bl for bl in preconv_names if 'bl' in bl][0] - self.br_name = [bl for bl in preconv_names if 'br' in bl][0] - debug.info(1,"bl_name={}".format(self.bl_name)) - - (cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, self.wordline_row, self.bitline_column) - debug.info(1, "cell_name={}".format(cell_name)) + debug.info(1,"bl name={}, br name={}".format(self.bl_name,self.br_name)) def get_sen_name(self, paths): """Gets the signal name associated with the sense amp enable from input paths. @@ -257,44 +248,85 @@ class delay(simulation): #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_found = False - #Only a single path should contain a single s_en name. Anything else is an error. - for path in paths: - aliases = self.sram.find_aliases(self.sram_spc_name, self.pins, path, enable_name, sa_mods[0]) - if sen_found and len(aliases) >= 1: - debug.error('Found multiple paths with SA enable.',1) - elif len(aliases) > 1: - debug.error('Found multiple S_EN points in single path. Cannot distinguish between them.',1) - elif not sen_found and len(aliases) == 1: - sen_name = aliases[0] - sen_found = True - if not sen_found: - debug.error("Could not find S_EN name.",1) - + sen_name = self.get_alias_in_path(paths, enable_name, sa_mods[0]) return sen_name def get_bl_name(self, paths): """Gets the signal name associated with the bitlines in the bank.""" - sa_mods = factory.get_mods(OPTS.sense_amp) + cell_mods = factory.get_mods(OPTS.bitcell) + if len(cell_mods)>=1: + cell_mod = self.get_primary_cell_mod(cell_mods) + elif len(cell_mods)==0: + debug.error("No bitcells found. Cannot determine bitline names.", 1) #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_found = False + # debug.check(self.are_mod_pins_equal(cell_mods), "Only expected one type of bitcell. Cannot perform bitline checks") + # debug.info(1,"num pbitcells={}".format(len(cell_mods))) + # debug.info(1,"cell ids={}".format([id(i) for i in cell_mods])) + + #cell_mods = cell_mods[1:] + cell_bl = cell_mod.get_bl_name() + cell_br = cell_mod.get_br_name() + + bl_found = False #Only a single path should contain a single s_en name. Anything else is an error. - for path in paths: - aliases = self.sram.find_aliases(self.sram_spc_name, self.pins, path, enable_name, sa_mods[0]) - if sen_found and len(aliases) >= 1: - debug.error('Found multiple paths with SA enable.',1) - elif len(aliases) > 1: - debug.error('Found multiple S_EN points in single path. Cannot distinguish between them.',1) - elif not sen_found and len(aliases) == 1: - sen_name = aliases[0] - sen_found = True - if not sen_found: - debug.error("Could not find S_EN name.",1) + 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 sen_name + 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""" @@ -658,6 +690,10 @@ class delay(simulation): #Get measurements from output file for measure in self.read_lib_meas: read_port_dict[measure.name] = measure.retrieve_measure(port=port) + + #Check sen timing, then bitlines, then general measurements. + if not self.check_sen_measure(port): + return (False,{}) success = self.check_debug_measures(port, read_port_dict) success = success and self.check_bit_measures() #Check timing for read ports. Power is only checked if it was read correctly @@ -680,6 +716,17 @@ class delay(simulation): # The delay is from the negative edge for our SRAM return (sim_passed,result) + def check_sen_measure(self, port): + """Checks that the sen occurred within a half-period""" + self.sen_meas + sen_val = self.sen_meas.retrieve_measure(port=port) + debug.info(1,"S_EN delay={} ns".format(sen_val)) + if self.sen_meas.meta_add_delay: + max_delay = self.period/2 + else: + max_delay = self.period + return not (type(sen_val) != float or sen_val > max_delay) + def check_debug_measures(self, port, read_measures): """Debug measures that indicate special conditions.""" #Currently, only check if the opposite than intended value was read during diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index 2380ee4b..361b922a 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -85,6 +85,7 @@ class bitcell_array(design.design): def add_modules(self): """ Add the modules used in this design """ self.cell = factory.create(module_type="bitcell") + debug.info(1,"Cell mod created, id={}".format(id(self.cell))) self.add_mod(self.cell) def create_instances(self): diff --git a/compiler/sram_factory.py b/compiler/sram_factory.py index 90478f4c..0b079780 100644 --- a/compiler/sram_factory.py +++ b/compiler/sram_factory.py @@ -46,16 +46,14 @@ class sram_factory: if hasattr(OPTS, module_type): # Retrieve the name from OPTS if it exists, # otherwise just use the name - module_name = getattr(OPTS, module_type) - else: - module_name = module_type - + module_type = getattr(OPTS, module_type) + # Either retrieve the already loaded module or load it try: mod = self.modules[module_type] except KeyError: - c = reload(__import__(module_name)) - mod = getattr(c, module_name) + c = reload(__import__(module_type)) + mod = getattr(c, module_type) self.modules[module_type] = mod self.module_indices[module_type] = 0 self.objects[module_type] = [] @@ -75,8 +73,10 @@ class sram_factory: # This is especially for library cells so that the spice and gds files can be found. if len(kwargs)>0: # Create a unique name and increment the index - module_name = "{0}_{1}".format(module_name, self.module_indices[module_type]) + module_name = "{0}_{1}".format(module_type, self.module_indices[module_type]) self.module_indices[module_type] += 1 + else: + module_name = module_type #debug.info(0, "New module: type={0} name={1} kwargs={2}".format(module_type,module_name,str(kwargs))) obj = mod(name=module_name,**kwargs) @@ -85,6 +85,10 @@ class sram_factory: def get_mods(self, module_type): """Returns list of all objects of module name's type.""" + if hasattr(OPTS, module_type): + # Retrieve the name from OPTS if it exists, + # otherwise just use the input + module_type = getattr(OPTS, module_type) try: mod_tuples = self.objects[module_type] mods = [mod for kwargs,mod in mod_tuples] From 36214792ebfcb178b971a7e65869470260194b57 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Tue, 28 May 2019 17:04:27 -0700 Subject: [PATCH 16/22] Removed some debug measurements that were causing failures. --- compiler/characterizer/delay.py | 46 +++++---------------------------- 1 file changed, 7 insertions(+), 39 deletions(-) diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 3da84ede..a927ef69 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -149,31 +149,18 @@ class delay(simulation): def create_debug_measurement_objects(self): """Create debug measurement to help identify failures.""" - - self.debug_delay_meas = [] self.debug_volt_meas = [] for meas in self.delay_meas: - debug_meas = copy.deepcopy(meas) - debug_meas.name = debug_meas.name+'_debug' - #This particular debugs check if output value is flipped, so TARG_DIR is flipped - if meas.targ_dir_str == 'FALL': - debug_meas.targ_dir_str = 'RISE' - else: - debug_meas.targ_dir_str = 'FALL' - #inserting debug member variable - debug_meas.parent = meas.name - self.debug_delay_meas.append(debug_meas) - #Output voltage measures - self.debug_volt_meas.append(voltage_at_measure("v_{}".format(debug_meas.name), - debug_meas.targ_name_no_port)) - self.debug_volt_meas[-1].meta_str = debug_meas.meta_str + self.debug_volt_meas.append(voltage_at_measure("v_{}".format(meas.name), + meas.targ_name_no_port)) + self.debug_volt_meas[-1].meta_str = meas.meta_str 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_add_delay = True - return self.debug_delay_meas+self.debug_volt_meas+[self.sen_meas] + return self.debug_volt_meas+[self.sen_meas] def create_read_bit_measures(self): """Adds bit measurements for read0 and read1 cycles""" @@ -694,7 +681,7 @@ class delay(simulation): #Check sen timing, then bitlines, then general measurements. if not self.check_sen_measure(port): return (False,{}) - success = self.check_debug_measures(port, read_port_dict) + success = self.check_debug_measures(port) success = success and self.check_bit_measures() #Check timing for read ports. Power is only checked if it was read correctly if not self.check_valid_delays(read_port_dict) or not success: @@ -727,30 +714,11 @@ class delay(simulation): max_delay = self.period return not (type(sen_val) != float or sen_val > max_delay) - def check_debug_measures(self, port, read_measures): + def check_debug_measures(self, port): """Debug measures that indicate special conditions.""" #Currently, only check if the opposite than intended value was read during # the read cycles i.e. neither of these measurements should pass. - success = True - for meas in self.debug_delay_meas: - val = meas.retrieve_measure(port=port) - debug.info(2,"{}={}".format(meas.name, val)) - if type(val) != float: - continue - - if meas.meta_add_delay: - max_delay = self.period/2 - else: - max_delay = self.period - - #If the debug measurement occurs after the original (and passes other conditions) - #then it fails i.e. the debug value represents the final (but failing) state of the output - parent_compare = type(read_measures[meas.parent]) != float or val > read_measures[meas.parent] - if 0 < val < max_delay and parent_compare: - success = False - debug.info(1, "Debug measurement failed. Incorrect Value found on output.") - break - + success = True #FIXME: these checks need to be re-done to be more robust against possible errors bl_vals = {} br_vals = {} From 2b07db33c8c13905df2e848ab202d2a6b517dcb7 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Mon, 17 Jun 2019 15:31:16 -0700 Subject: [PATCH 17/22] Added bitcell as input to array, but there are DRC errors now. --- compiler/modules/bank.py | 12 +++--- compiler/modules/bitcell_array.py | 5 +-- compiler/modules/replica_bitline.py | 4 +- compiler/sram_factory.py | 2 + compiler/tests/16_control_logic_test.py | 51 +++++++++++++------------ 5 files changed, 39 insertions(+), 35 deletions(-) diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index efdf228d..557297bd 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -401,18 +401,18 @@ class bank(design.design): def add_modules(self): """ Add all the modules using the class loader """ - - self.bitcell_array = factory.create(module_type="bitcell_array", - cols=self.num_cols, - rows=self.num_rows) - self.add_mod(self.bitcell_array) - # create arrays of bitline and bitline_bar names for read, write, or all ports self.bitcell = factory.create(module_type="bitcell") self.bl_names = self.bitcell.list_all_bl_names() self.br_names = self.bitcell.list_all_br_names() self.wl_names = self.bitcell.list_all_wl_names() self.bitline_names = self.bitcell.list_all_bitline_names() + + self.bitcell_array = factory.create(module_type="bitcell_array", + cols=self.num_cols, + rows=self.num_rows, + bitcell=self.bitcell) + self.add_mod(self.bitcell_array) self.precharge_array = [] for port in self.all_ports: diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index 361b922a..17b82199 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -19,13 +19,14 @@ class bitcell_array(design.design): and word line is connected by abutment. Connects the word lines and bit lines. """ - def __init__(self, cols, rows, name): + def __init__(self, cols, rows, name, bitcell): design.design.__init__(self, 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 + self.cell = bitcell self.create_netlist() if not OPTS.netlist_only: @@ -84,8 +85,6 @@ class bitcell_array(design.design): def add_modules(self): """ Add the modules used in this design """ - self.cell = factory.create(module_type="bitcell") - debug.info(1,"Cell mod created, id={}".format(id(self.cell))) self.add_mod(self.cell) def create_instances(self): diff --git a/compiler/modules/replica_bitline.py b/compiler/modules/replica_bitline.py index acd1f5d0..8bfaf0f2 100644 --- a/compiler/modules/replica_bitline.py +++ b/compiler/modules/replica_bitline.py @@ -86,10 +86,12 @@ class replica_bitline(design.design): self.replica_bitcell = factory.create(module_type="replica_bitcell") self.add_mod(self.replica_bitcell) + bitcell = factory.create(module_type="bitcell") # This is the replica bitline load column that is the height of our array self.rbl = factory.create(module_type="bitcell_array", cols=1, - rows=self.bitcell_loads) + rows=self.bitcell_loads, + bitcell=bitcell) self.add_mod(self.rbl) # FIXME: The FO and depth of this should be tuned diff --git a/compiler/sram_factory.py b/compiler/sram_factory.py index 0b079780..f53a72d5 100644 --- a/compiler/sram_factory.py +++ b/compiler/sram_factory.py @@ -65,6 +65,8 @@ class sram_factory: # Must have the same dictionary exactly (conservative) if obj_kwargs == kwargs: #debug.info(0, "Existing module: type={0} name={1} kwargs={2}".format(module_type, obj_item.name, str(kwargs))) + if module_type == 'bitcell_array': + debug.info(1,'Returning existing mod!') return obj_item #else: # print("obj",obj_kwargs) diff --git a/compiler/tests/16_control_logic_test.py b/compiler/tests/16_control_logic_test.py index ed8a0088..78866d7b 100755 --- a/compiler/tests/16_control_logic_test.py +++ b/compiler/tests/16_control_logic_test.py @@ -26,36 +26,37 @@ class control_logic_test(openram_test): import control_logic import tech - # check control logic for single port - debug.info(1, "Testing sample for control_logic") - a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32) - self.local_check(a) + # # check control logic for single port + # debug.info(1, "Testing sample for control_logic") + # a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32) + # self.local_check(a) # check control logic for multi-port OPTS.bitcell = "pbitcell" OPTS.replica_bitcell = "replica_pbitcell" - OPTS.num_rw_ports = 1 - OPTS.num_w_ports = 0 - OPTS.num_r_ports = 0 - - debug.info(1, "Testing sample for control_logic for multiport") - a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8) - self.local_check(a) - - # Check port specific control logic - OPTS.num_rw_ports = 1 - OPTS.num_w_ports = 0 - OPTS.num_r_ports = 0 - - debug.info(1, "Testing sample for control_logic for multiport, only write control logic") - a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="rw") - self.local_check(a) - + #OPTS.num_rw_ports = 1 OPTS.num_rw_ports = 0 - OPTS.num_w_ports = 1 - debug.info(1, "Testing sample for control_logic for multiport, only write control logic") - a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="w") - self.local_check(a) + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 0 + + # debug.info(1, "Testing sample for control_logic for multiport") + # a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8) + # self.local_check(a) + + # # Check port specific control logic + # OPTS.num_rw_ports = 1 + # OPTS.num_w_ports = 0 + # OPTS.num_r_ports = 0 + + # debug.info(1, "Testing sample for control_logic for multiport, only write control logic") + # a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="rw") + # self.local_check(a) + + # OPTS.num_rw_ports = 0 + # OPTS.num_w_ports = 1 + # debug.info(1, "Testing sample for control_logic for multiport, only write control logic") + # a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="w") + # self.local_check(a) OPTS.num_w_ports = 0 OPTS.num_r_ports = 1 From 04ce3d5f457f6da81465c30bf3a79e9b3e01bc01 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Tue, 25 Jun 2019 14:55:28 -0700 Subject: [PATCH 18/22] Split control logic into different tests to avoid factory errors. --- compiler/bitcells/replica_pbitcell.py | 1 - compiler/sram_factory.py | 2 - .../tests/16_control_logic_multiport_test.py | 59 +++++++++++++++++++ compiler/tests/16_control_logic_test.py | 41 +------------ 4 files changed, 62 insertions(+), 41 deletions(-) create mode 100755 compiler/tests/16_control_logic_multiport_test.py diff --git a/compiler/bitcells/replica_pbitcell.py b/compiler/bitcells/replica_pbitcell.py index d39d361a..30898d82 100644 --- a/compiler/bitcells/replica_pbitcell.py +++ b/compiler/bitcells/replica_pbitcell.py @@ -54,7 +54,6 @@ class replica_pbitcell(design.design): def add_modules(self): self.prbc = factory.create(module_type="pbitcell",replica_bitcell=True) - debug.info(1,"rbl bitcell name={}".format(self.prbc.name)) self.add_mod(self.prbc) self.height = self.prbc.height diff --git a/compiler/sram_factory.py b/compiler/sram_factory.py index f53a72d5..0b079780 100644 --- a/compiler/sram_factory.py +++ b/compiler/sram_factory.py @@ -65,8 +65,6 @@ class sram_factory: # Must have the same dictionary exactly (conservative) if obj_kwargs == kwargs: #debug.info(0, "Existing module: type={0} name={1} kwargs={2}".format(module_type, obj_item.name, str(kwargs))) - if module_type == 'bitcell_array': - debug.info(1,'Returning existing mod!') return obj_item #else: # print("obj",obj_kwargs) diff --git a/compiler/tests/16_control_logic_multiport_test.py b/compiler/tests/16_control_logic_multiport_test.py new file mode 100755 index 00000000..66c34d24 --- /dev/null +++ b/compiler/tests/16_control_logic_multiport_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. +# +""" +Run a regression test on a control_logic +""" + +import unittest +from testutils import header,openram_test +import sys,os +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class control_logic_test(openram_test): + + def runTest(self): + globals.init_openram("config_{0}".format(OPTS.tech_name)) + import control_logic + import tech + + # check control logic for multi-port + OPTS.bitcell = "pbitcell" + OPTS.replica_bitcell = "replica_pbitcell" + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 1 + OPTS.num_r_ports = 1 + + debug.info(1, "Testing sample for control_logic for multiport, only write control logic") + a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="rw") + self.local_check(a) + + # OPTS.num_rw_ports = 0 + # OPTS.num_w_ports = 1 + debug.info(1, "Testing sample for control_logic for multiport, only write control logic") + a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="w") + self.local_check(a) + + # OPTS.num_w_ports = 0 + # OPTS.num_r_ports = 1 + debug.info(1, "Testing sample for control_logic for multiport, only read control logic") + a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="r") + 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() diff --git a/compiler/tests/16_control_logic_test.py b/compiler/tests/16_control_logic_test.py index 78866d7b..21409132 100755 --- a/compiler/tests/16_control_logic_test.py +++ b/compiler/tests/16_control_logic_test.py @@ -26,45 +26,10 @@ class control_logic_test(openram_test): import control_logic import tech - # # check control logic for single port - # debug.info(1, "Testing sample for control_logic") - # a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32) - # self.local_check(a) - - # check control logic for multi-port - OPTS.bitcell = "pbitcell" - OPTS.replica_bitcell = "replica_pbitcell" - #OPTS.num_rw_ports = 1 - OPTS.num_rw_ports = 0 - OPTS.num_w_ports = 0 - OPTS.num_r_ports = 0 - - # debug.info(1, "Testing sample for control_logic for multiport") - # a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8) - # self.local_check(a) - - # # Check port specific control logic - # OPTS.num_rw_ports = 1 - # OPTS.num_w_ports = 0 - # OPTS.num_r_ports = 0 - - # debug.info(1, "Testing sample for control_logic for multiport, only write control logic") - # a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="rw") - # self.local_check(a) - - # OPTS.num_rw_ports = 0 - # OPTS.num_w_ports = 1 - # debug.info(1, "Testing sample for control_logic for multiport, only write control logic") - # a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="w") - # self.local_check(a) - - OPTS.num_w_ports = 0 - OPTS.num_r_ports = 1 - debug.info(1, "Testing sample for control_logic for multiport, only read control logic") - a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="r") + # check control logic for single port + debug.info(1, "Testing sample for control_logic") + a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32) self.local_check(a) - - globals.end_openram() # run the test from the command line if __name__ == "__main__": From 33c17ac41c847c3bc5a48467583e86b8d71ee255 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Tue, 25 Jun 2019 15:45:02 -0700 Subject: [PATCH 19/22] Moved manual delay chain declarations from tech files to options. --- compiler/characterizer/__init__.py | 1 - compiler/characterizer/bitline_delay.py | 248 ------------------------ compiler/modules/bitcell_array.py | 2 +- compiler/modules/control_logic.py | 4 +- compiler/modules/replica_bitline.py | 2 + compiler/options.py | 11 +- technology/freepdk45/tech/tech.py | 4 - technology/scn4m_subm/tech/tech.py | 4 - 8 files changed, 13 insertions(+), 263 deletions(-) delete mode 100644 compiler/characterizer/bitline_delay.py diff --git a/compiler/characterizer/__init__.py b/compiler/characterizer/__init__.py index 4c3cd910..649d6bb2 100644 --- a/compiler/characterizer/__init__.py +++ b/compiler/characterizer/__init__.py @@ -15,7 +15,6 @@ from .setup_hold import * from .functional import * from .worst_case import * from .simulation import * -from .bitline_delay import * from .measurements import * from .model_check import * diff --git a/compiler/characterizer/bitline_delay.py b/compiler/characterizer/bitline_delay.py deleted file mode 100644 index 90197619..00000000 --- a/compiler/characterizer/bitline_delay.py +++ /dev/null @@ -1,248 +0,0 @@ -# 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 sys,re,shutil -import debug -import tech -import math -from .stimuli import * -from .trim_spice import * -from .charutils import * -import utils -from globals import OPTS -from .delay import delay - -class bitline_delay(delay): - """Functions to test for the worst case delay in a target SRAM - - The current worst case determines a feasible period for the SRAM then tests - several bits and record the delay and differences between the bits. - - """ - - def __init__(self, sram, spfile, corner): - delay.__init__(self,sram,spfile,corner) - self.period = tech.spice["feasible_period"] - self.is_bitline_measure = True - - def create_signal_names(self): - delay.create_signal_names(self) - self.bl_signal_names = ["Xsram.Xbank0.bl", "Xsram.Xbank0.br"] - self.sen_name = "Xsram.s_en" - - def create_measurement_names(self): - """Create measurement names. The names themselves currently define the type of measurement""" - #Altering the names will crash the characterizer. TODO: object orientated approach to the measurements. - self.bl_volt_meas_names = ["volt_bl", "volt_br"] - self.bl_delay_meas_names = ["delay_bl", "delay_br"] #only used in SPICE simulation - self.bl_delay_result_name = "delay_bl_vth" #Used in the return value - - 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.""" - delay.set_probe(self,probe_address, probe_data) - self.bitline_column = self.get_data_bit_column_number(probe_address, probe_data) - - def write_delay_measures(self): - """ - Write the measure statements to quantify the bitline voltage at sense amp enable 50%. - """ - self.sf.write("\n* Measure statements for delay and power\n") - - # Output some comments to aid where cycles start and - for comment in self.cycle_comments: - self.sf.write("* {}\n".format(comment)) - - for read_port in self.targ_read_ports: - self.write_bitline_voltage_measures(read_port) - self.write_bitline_delay_measures(read_port) - - def write_bitline_voltage_measures(self, port): - """ - Add measurments to capture the bitline voltages at 50% Sense amp enable - """ - debug.info(2, "Measuring bitline column={}, port={}".format(self.bitline_column,port)) - if len(self.all_ports) == 1: #special naming case for single port sram bitlines - bitline_port = "" - else: - bitline_port = str(port) - - sen_port_name = "{}{}".format(self.sen_name,port) - for (measure_name, bl_signal_name) in zip(self.bl_volt_meas_names, self.bl_signal_names): - bl_port_name = "{}{}_{}".format(bl_signal_name, bitline_port, self.bitline_column) - measure_port_name = "{}{}".format(measure_name,port) - self.stim.gen_meas_find_voltage(measure_port_name, sen_port_name, bl_port_name, .5, "RISE", self.cycle_times[self.measure_cycles[port]["read0"]]) - - def write_bitline_delay_measures(self, port): - """ - Write the measure statements to quantify the delay and power results for a read port. - """ - # add measure statements for delays/slews - for (measure_name, bl_signal_name) in zip(self.bl_delay_meas_names, self.bl_signal_names): - meas_values = self.get_delay_meas_values(measure_name, bl_signal_name, port) - self.stim.gen_meas_delay(*meas_values) - - def get_delay_meas_values(self, delay_name, bitline_name, port): - """Get the values needed to generate a Spice measurement statement based on the name of the measurement.""" - if len(self.all_ports) == 1: #special naming case for single port sram bitlines - bitline_port = "" - else: - bitline_port = str(port) - - meas_name="{0}{1}".format(delay_name, port) - targ_name = "{0}{1}_{2}".format(bitline_name,bitline_port,self.bitline_column) - half_vdd = 0.5 * self.vdd_voltage - trig_val = half_vdd - targ_val = self.vdd_voltage-tech.spice["v_threshold_typical"] - trig_name = "clk{0}".format(port) - trig_dir="FALL" - targ_dir="FALL" - #Half period added to delay measurement to negative clock edge - trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read0"]] + self.period/2 - return (meas_name,trig_name,targ_name,trig_val,targ_val,trig_dir,targ_dir,trig_td,targ_td) - - 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) - of the cycles to do a timing evaluation of a single port """ - - # Create the inverse address for a scratch address - 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 - - if self.t_current == 0: - self.add_noop_all_ports("Idle cycle (no positive clock edge)", - inverse_address, data_zeros) - - self.add_write("W data 1 address {}".format(inverse_address), - inverse_address,data_ones,write_port) - - self.add_write("W data 0 address {} to write value".format(self.probe_address), - self.probe_address,data_zeros,write_port) - self.measure_cycles[write_port]["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), - inverse_address,data_zeros,read_port) - self.measure_cycles[read_port]["read1"] = len(self.cycle_times)-1 - - self.add_read("R data 0 address {} to check W0 worked".format(self.probe_address), - self.probe_address,data_zeros,read_port) - self.measure_cycles[read_port]["read0"] = len(self.cycle_times)-1 - - def get_data_bit_column_number(self, probe_address, probe_data): - """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) - else: - col_address = 0 - bl_column = int(self.sram.words_per_row*probe_data + col_address) - return bl_column - - def run_delay_simulation(self): - """ - This tries to simulate a period and checks if the result works. If - so, it returns True and the delays, slews, and powers. It - works on the trimmed netlist by default, so powers do not - include leakage of all cells. - """ - #Sanity Check - debug.check(self.period > 0, "Target simulation period non-positive") - - result = [{} for i in self.all_ports] - # Checking from not data_value to data_value - self.write_delay_stimulus() - - self.stim.run_sim() #running sim prodoces spice output file. - - for port in self.targ_read_ports: - #Parse and check the voltage measurements - bl_volt_meas_dict = {} - for mname in self.bl_volt_meas_names: - mname_port = "{}{}".format(mname,port) - volt_meas_val = parse_spice_list("timing", mname_port) - if type(volt_meas_val)!=float: - debug.error("Failed to Parse Bitline Voltage:\n\t\t{0}={1}".format(mname,volt_meas_val),1) - bl_volt_meas_dict[mname] = volt_meas_val - result[port].update(bl_volt_meas_dict) - - #Parse and check the delay measurements. Intended that one measurement will fail, save the delay that did not fail. - bl_delay_meas_dict = {} - values_added = 0 #For error checking - for mname in self.bl_delay_meas_names: #Parse - mname_port = "{}{}".format(mname,port) - delay_meas_val = parse_spice_list("timing", mname_port) - if type(delay_meas_val)==float: #Only add if value is float, do not error. - bl_delay_meas_dict[self.bl_delay_result_name] = delay_meas_val * 1e9 #convert to ns - values_added+=1 - debug.check(values_added>0, "Bitline delay measurements failed in SPICE simulation.") - debug.check(values_added<2, "Both bitlines experienced a Vth drop, check simulation results.") - result[port].update(bl_delay_meas_dict) - - # The delay is from the negative edge for our SRAM - return (True,result) - - def check_bitline_all_results(self, results): - """Checks the bitline values measured for each tested port""" - for port in self.targ_read_ports: - self.check_bitline_port_results(results[port]) - - def check_bitline_port_results(self, port_results): - """Performs three different checks for the bitline values: functionality, bitline swing from vdd, and differential bit swing""" - bl_volt, br_volt = port_results["volt_bl"], port_results["volt_br"] - self.check_functionality(bl_volt,br_volt) - self.check_swing_from_vdd(bl_volt,br_volt) - self.check_differential_swing(bl_volt,br_volt) - - def check_functionality(self, bl_volt, br_volt): - """Checks whether the read failed or not. Measured values are hardcoded with the intention of reading a 0.""" - if bl_volt > br_volt: - debug.error("Read failure. Value 1 was read instead of 0.",1) - - def check_swing_from_vdd(self, bl_volt, br_volt): - """Checks difference on discharging bitline from VDD to see if it is within margin of the RBL height parameter.""" - if bl_volt < br_volt: - discharge_volt = bl_volt - else: - discharge_volt = br_volt - desired_bl_volt = tech.parameter["rbl_height_percentage"]*self.vdd_voltage - debug.info(1, "Active bitline={:.3f}v, Desired bitline={:.3f}v".format(discharge_volt,desired_bl_volt)) - vdd_error_margin = .2 #20% of vdd margin for bitline, a little high for now. - if abs(discharge_volt - desired_bl_volt) > vdd_error_margin*self.vdd_voltage: - debug.warning("Bitline voltage is not within {}% Vdd margin. Delay chain/RBL could need resizing.".format(vdd_error_margin*100)) - - def check_differential_swing(self, bl_volt, br_volt): - """This check looks at the difference between the bitline voltages. This needs to be large enough to prevent - sensing errors.""" - bitline_swing = abs(bl_volt-br_volt) - debug.info(1,"Bitline swing={:.3f}v".format(bitline_swing)) - vdd_error_margin = .2 #20% of vdd margin for bitline, a little high for now. - if bitline_swing < vdd_error_margin*self.vdd_voltage: - debug.warning("Bitline swing less than {}% Vdd margin. Sensing errors more likely to occur.".format(vdd_error_margin)) - - def analyze(self, probe_address, probe_data, slews, loads): - """Measures the bitline swing of the differential bitlines (bl/br) at 50% s_en """ - self.set_probe(probe_address, probe_data) - self.load=max(loads) - self.slew=max(slews) - - read_port = self.read_ports[0] #only test the first read port - bitline_swings = {} - self.targ_read_ports = [read_port] - self.targ_write_ports = [self.write_ports[0]] - debug.info(1,"Bitline swing test: corner {}".format(self.corner)) - (success, results)=self.run_delay_simulation() - debug.check(success, "Bitline Failed: period {}".format(self.period)) - debug.info(1,"Bitline values (voltages/delays):\n\t {}".format(results[read_port])) - self.check_bitline_all_results(results) - - return results - - - diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index 17b82199..f9d62e0e 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -154,7 +154,7 @@ class bitcell_array(design.design): # Dynamic Power from Bitline bl_wire = self.gen_bl_wire() cell_load = 2 * bl_wire.return_input_cap() - bl_swing = parameter["rbl_height_percentage"] + bl_swing = OPTS.rbl_delay_percentage freq = spice["default_event_rate"] bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing) diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 35795972..64aea76d 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -143,10 +143,10 @@ class control_logic(design.design): 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 * parameter["rbl_height_percentage"])) + 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 = parameter["static_fanout_list"] + 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, diff --git a/compiler/modules/replica_bitline.py b/compiler/modules/replica_bitline.py index 8bfaf0f2..720802c3 100644 --- a/compiler/modules/replica_bitline.py +++ b/compiler/modules/replica_bitline.py @@ -25,6 +25,8 @@ class replica_bitline(design.design): self.bitcell_loads = bitcell_loads self.delay_fanout_list = delay_fanout_list + if len(delay_fanout_list) == 0 or len(delay_fanout_list)%2 == 1: + debug.error('Delay chain must contain an even amount of stages to maintain polarity.',1) self.create_netlist() if not OPTS.netlist_only: diff --git a/compiler/options.py b/compiler/options.py index b3ac813a..bb3811c6 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -42,10 +42,15 @@ class options(optparse.Values): ################### # Optimization options - ################### - # Uses the delay chain size in the tech.py file rather automatic sizing. + ################### + rbl_delay_percentage = .5 #Approximate percentage of delay compared to bitlines + + # Allow manual adjustment of the delay chain over automatic use_tech_delay_chain_size = False - + delay_chain_stages = 4 + delay_chain_fanout_per_stage = 3 + + ################### # Debug options. diff --git a/technology/freepdk45/tech/tech.py b/technology/freepdk45/tech/tech.py index 12c314cf..4654770e 100644 --- a/technology/freepdk45/tech/tech.py +++ b/technology/freepdk45/tech/tech.py @@ -354,9 +354,6 @@ spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input no #Parameters related to sense amp enable timing and delay chain/RBL sizing parameter['le_tau'] = 2.25 #In pico-seconds. parameter['cap_relative_per_ff'] = 7.5 #Units of Relative Capacitance/ Femto-Farad -parameter["static_delay_stages"] = 4 -parameter["static_fanout_per_stage"] = 3 -parameter["static_fanout_list"] = parameter["static_delay_stages"]*[parameter["static_fanout_per_stage"]] parameter["dff_clk_cin"] = 30.6 #relative capacitance parameter["6tcell_wl_cin"] = 3 #relative capacitance parameter["min_inv_para_delay"] = 2.4 #Tau delay units @@ -364,7 +361,6 @@ parameter["sa_en_pmos_size"] = .72 #micro-meters parameter["sa_en_nmos_size"] = .27 #micro-meters parameter["sa_inv_pmos_size"] = .54 #micro-meters parameter["sa_inv_nmos_size"] = .27 #micro-meters -parameter["rbl_height_percentage"] = .5 #Height of RBL compared to bitcell array parameter['bitcell_drain_cap'] = 0.1 #In Femto-Farad, approximation of drain capacitance ################################################### diff --git a/technology/scn4m_subm/tech/tech.py b/technology/scn4m_subm/tech/tech.py index 15278c7b..bf982b48 100644 --- a/technology/scn4m_subm/tech/tech.py +++ b/technology/scn4m_subm/tech/tech.py @@ -321,16 +321,12 @@ spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input no parameter['le_tau'] = 23 #In pico-seconds. parameter["min_inv_para_delay"] = .73 #In relative delay units parameter['cap_relative_per_ff'] = .91 #Units of Relative Capacitance/ Femto-Farad -parameter["static_delay_stages"] = 4 -parameter["static_fanout_per_stage"] = 3 -parameter["static_fanout_list"] = parameter["static_delay_stages"]*[parameter["static_fanout_per_stage"]] parameter["dff_clk_cin"] = 27.5 #In relative capacitance units parameter["6tcell_wl_cin"] = 2 #In relative capacitance units parameter["sa_en_pmos_size"] = 24*_lambda_ parameter["sa_en_nmos_size"] = 9*_lambda_ parameter["sa_inv_pmos_size"] = 18*_lambda_ parameter["sa_inv_nmos_size"] = 9*_lambda_ -parameter["rbl_height_percentage"] = .5 #Height of RBL compared to bitcell array parameter['bitcell_drain_cap'] = 0.2 #In Femto-Farad, approximation of drain capacitance ################################################### From 4f3340e973ee87a8c84d913cff0a816713aab5f3 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Tue, 25 Jun 2019 16:37:35 -0700 Subject: [PATCH 20/22] Cleaned up graph additions to characterizer. --- compiler/base/graph_util.py | 17 +-------------- compiler/base/hierarchy_design.py | 19 +---------------- compiler/characterizer/delay.py | 35 +++++++++++++------------------ compiler/pgates/pand2.py | 2 +- 4 files changed, 18 insertions(+), 55 deletions(-) diff --git a/compiler/base/graph_util.py b/compiler/base/graph_util.py index af4d0888..b6ac4677 100644 --- a/compiler/base/graph_util.py +++ b/compiler/base/graph_util.py @@ -56,7 +56,7 @@ class timing_graph(): # Call the recursive helper function to print all paths self.get_all_paths_util(src_node, dest_node, visited, path) - debug.info(1, "Paths found={}".format(len(self.all_paths))) + debug.info(2, "Paths found={}".format(len(self.all_paths))) return self.all_paths @@ -69,7 +69,6 @@ class timing_graph(): # If current vertex is same as destination, then print # current path[] if cur_node == dest_node: - debug.info(1,"{}".format(path)) self.all_paths.append(copy.deepcopy(path)) else: # If current vertex is not destination @@ -82,20 +81,6 @@ class timing_graph(): path.pop() visited.remove(cur_node) - def get_path_preconvergence_point(self, path1, path2): - """Assuming the inputs paths have the same starting point and end point, the - paths should split and converge at some point before/at the last stage. Finds the - point before convergence.""" - debug.check(path1[0] == path2[0], "Paths must start from the same point.") - debug.check(path1[-1] == path2[-1], "Paths must end from the same point.") - #Paths must end at the same point, so the paths are traversed backwards to find - #point of convergence. There could be multiple points, only finds first. - for point1,point2 in zip(reversed(path1), reversed(path2)): - if point1 != point2: - return (point1,point2) - debug.info(1,"Pre-convergence point not found, paths are equals.") - return path1[0],path2[0] - def __str__(self): """ override print function output """ return "Nodes: {}\nEdges:{} ".format(list(self.graph), self.graph) \ No newline at end of file diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index a75e5a3c..a8a191af 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -147,7 +147,6 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): """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). """ - #path_nets = ['xsram_0.xbank0.bl0_7'] if exclusion_set == None: exclusion_set = set() try: @@ -155,31 +154,17 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): except AttributeError: self.name_dict = {} self.build_names(self.name_dict, inst_name, port_nets) - #debug.info(1,"names={}".format(list(self.name_dict))) aliases = [] for net in path_nets: net = net.lower() int_net = self.name_dict[net]['int_net'] int_mod = self.name_dict[net]['mod'] - # debug.info(1,"int_net={}".format(int_net)) - # debug.info(1,"int_mod={}".format(int_mod.name)) - # debug.info(1,"alias_net={}".format(alias)) - # debug.info(1,"alias_mod={}".format(alias_mod.name)) - # debug.info(1,"mod id={}".format(id(alias_mod))) if int_mod.is_net_alias(int_net, alias, alias_mod, exclusion_set): aliases.append(net) - # debug.info(1,"Alias found\n") - # else: - # debug.info(1,"Alias not found\n") - debug.info(1,"Aliases Found={}".format(aliases)) return aliases def is_net_alias(self, known_net, net_alias, mod, exclusion_set): - """Checks if the alias_net in mod is the same as the input net.""" - # debug.info(1,"self name={}".format(self.name)) - # debug.info(1,"self mod id={}".format(id(self))) - # debug.info(1,"self pins={}".format(self.pins)) - # debug.info(1,"known_net={}".format(known_net)) + """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 @@ -193,8 +178,6 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): 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: - # debug.info(1,"found matching conn={}".format(inst_conn)) - # debug.info(1,"Setting known pin={}".format(mod_pin)) if subinst.mod.is_net_alias(mod_pin, net_alias, mod, exclusion_set): return True mod_set.add(subinst.mod) diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index a927ef69..6c5be96c 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -216,17 +216,19 @@ class delay(simulation): 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) - #debug.info(1,"{}".format(self.graph)) + + def set_internal_spice_names(self): + """Sets important names for characterization such as Sense amp enable and internal bit nets.""" port = 0 self.graph.get_all_paths('{}{}'.format(tech.spice["clk"], port), \ '{}{}_{}'.format(self.dout_name, port, self.probe_data)) self.sen_name = self.get_sen_name(self.graph.all_paths) - debug.info(1,"s_en name = {}".format(self.sen_name)) + debug.info(2,"s_en name = {}".format(self.sen_name)) self.bl_name,self.br_name = self.get_bl_name(self.graph.all_paths) - debug.info(1,"bl name={}, br name={}".format(self.bl_name,self.br_name)) - + debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name)) + 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.""" @@ -245,13 +247,7 @@ class delay(simulation): cell_mod = self.get_primary_cell_mod(cell_mods) elif len(cell_mods)==0: debug.error("No bitcells found. Cannot determine bitline names.", 1) - #Any sense amp instantiated should be identical, any change to that - #will require some identification to determine the mod desired. - # debug.check(self.are_mod_pins_equal(cell_mods), "Only expected one type of bitcell. Cannot perform bitline checks") - # debug.info(1,"num pbitcells={}".format(len(cell_mods))) - # debug.info(1,"cell ids={}".format([id(i) for i in cell_mods])) - - #cell_mods = cell_mods[1:] + cell_bl = cell_mod.get_bl_name() cell_br = cell_mod.get_br_name() @@ -705,9 +701,8 @@ class delay(simulation): def check_sen_measure(self, port): """Checks that the sen occurred within a half-period""" - self.sen_meas sen_val = self.sen_meas.retrieve_measure(port=port) - debug.info(1,"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 else: @@ -729,7 +724,7 @@ class delay(simulation): elif self.br_name == meas.targ_name_no_port: br_vals[meas.meta_str] = val - debug.info(1,"{}={}".format(meas.name,val)) + debug.info(2,"{}={}".format(meas.name,val)) bl_check = False for meas in self.debug_volt_meas: @@ -762,24 +757,23 @@ class delay(simulation): for polarity, meas_list in self.bit_meas.items(): for meas in meas_list: val = meas.retrieve_measure() - debug.info(1,"{}={}".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*.1 + 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*.9 + success = val > self.vdd_voltage/2 if not success: debug.info(1,("Wrong value detected on probe bit during read cycle. " "Check writes and control logic for bugs.\n measure={}, op={}, " - "bit_storage={}").format(meas.name, meas_cycle.name, polarity.name)) + "bit_storage={}, V(bit)={}").format(meas.name, meas_cycle.name, polarity.name,val)) return success - - def check_bitline_meas(self, v_discharged_bl, v_charged_bl): """Checks the value of the discharging bitline. Confirms s_en timing errors. Returns true if the bitlines are at there expected value.""" @@ -991,6 +985,7 @@ class delay(simulation): """Sets values which are dependent on the data address/bit being tested.""" self.set_probe(probe_address, probe_data) self.create_graph() + self.set_internal_spice_names() self.create_measurement_names() self.create_measurement_objects() diff --git a/compiler/pgates/pand2.py b/compiler/pgates/pand2.py index add59565..f7c26032 100644 --- a/compiler/pgates/pand2.py +++ b/compiler/pgates/pand2.py @@ -18,7 +18,7 @@ class pand2(pgate.pgate): This is a simple buffer used for driving loads. """ def __init__(self, name, size=1, height=None): - debug.info(1, "reating pnand2 {}".format(name)) + debug.info(1, "Creating pnand2 {}".format(name)) self.add_comment("size: {}".format(size)) self.size = size From ce7e3205053e43a4da3a1bb4707ec31f011262e6 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Tue, 25 Jun 2019 18:26:13 -0700 Subject: [PATCH 21/22] Undid change to add bitcell as input to array mod. --- compiler/characterizer/bitline_delay.py | 248 ------------------------ compiler/modules/bank.py | 3 +- compiler/modules/bitcell_array.py | 4 +- compiler/modules/replica_bitline.py | 6 +- 4 files changed, 5 insertions(+), 256 deletions(-) delete mode 100644 compiler/characterizer/bitline_delay.py diff --git a/compiler/characterizer/bitline_delay.py b/compiler/characterizer/bitline_delay.py deleted file mode 100644 index 68985b2a..00000000 --- a/compiler/characterizer/bitline_delay.py +++ /dev/null @@ -1,248 +0,0 @@ -# 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 sys,re,shutil -import debug -import tech -import math -from .stimuli import * -from .trim_spice import * -from .charutils import * -import utils -from globals import OPTS -from .delay import delay - -class bitline_delay(delay): - """Functions to test for the worst case delay in a target SRAM - - The current worst case determines a feasible period for the SRAM then tests - several bits and record the delay and differences between the bits. - - """ - - def __init__(self, sram, spfile, corner): - delay.__init__(self,sram,spfile,corner) - self.period = tech.spice["feasible_period"] - self.is_bitline_measure = True - - def create_signal_names(self): - delay.create_signal_names(self) - self.bl_signal_names = ["Xsram.Xbank0.bl", "Xsram.Xbank0.br"] - self.sen_name = "Xsram.s_en" - - def create_measurement_names(self): - """Create measurement names. The names themselves currently define the type of measurement""" - #Altering the names will crash the characterizer. TODO: object orientated approach to the measurements. - self.bl_volt_meas_names = ["volt_bl", "volt_br"] - self.bl_delay_meas_names = ["delay_bl", "delay_br"] #only used in SPICE simulation - self.bl_delay_result_name = "delay_bl_vth" #Used in the return value - - 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.""" - delay.set_probe(self,probe_address, probe_data) - self.bitline_column = self.get_data_bit_column_number(probe_address, probe_data) - - def write_delay_measures(self): - """ - Write the measure statements to quantify the bitline voltage at sense amp enable 50%. - """ - self.sf.write("\n* Measure statements for delay and power\n") - - # Output some comments to aid where cycles start and - for comment in self.cycle_comments: - self.sf.write("* {}\n".format(comment)) - - for read_port in self.targ_read_ports: - self.write_bitline_voltage_measures(read_port) - self.write_bitline_delay_measures(read_port) - - def write_bitline_voltage_measures(self, port): - """ - Add measurments to capture the bitline voltages at 50% Sense amp enable - """ - debug.info(2, "Measuring bitline column={}, port={}".format(self.bitline_column,port)) - if len(self.all_ports) == 1: #special naming case for single port sram bitlines - bitline_port = "" - else: - bitline_port = str(port) - - sen_port_name = "{}{}".format(self.sen_name,port) - for (measure_name, bl_signal_name) in zip(self.bl_volt_meas_names, self.bl_signal_names): - bl_port_name = "{}{}_{}".format(bl_signal_name, bitline_port, self.bitline_column) - measure_port_name = "{}{}".format(measure_name,port) - self.stim.gen_meas_find_voltage(measure_port_name, sen_port_name, bl_port_name, .5, "RISE", self.cycle_times[self.measure_cycles[port]["read0"]]) - - def write_bitline_delay_measures(self, port): - """ - Write the measure statements to quantify the delay and power results for a read port. - """ - # add measure statements for delays/slews - for (measure_name, bl_signal_name) in zip(self.bl_delay_meas_names, self.bl_signal_names): - meas_values = self.get_delay_meas_values(measure_name, bl_signal_name, port) - self.stim.gen_meas_delay(*meas_values) - - def get_delay_meas_values(self, delay_name, bitline_name, port): - """Get the values needed to generate a Spice measurement statement based on the name of the measurement.""" - if len(self.all_ports) == 1: #special naming case for single port sram bitlines - bitline_port = "" - else: - bitline_port = str(port) - - meas_name="{0}{1}".format(delay_name, port) - targ_name = "{0}{1}_{2}".format(bitline_name,bitline_port,self.bitline_column) - half_vdd = 0.5 * self.vdd_voltage - trig_val = half_vdd - targ_val = self.vdd_voltage-tech.spice["v_threshold_typical"] - trig_name = "clk{0}".format(port) - trig_dir="FALL" - targ_dir="FALL" - #Half period added to delay measurement to negative clock edge - trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read0"]] + self.period/2 - return (meas_name,trig_name,targ_name,trig_val,targ_val,trig_dir,targ_dir,trig_td,targ_td) - - 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) - of the cycles to do a timing evaluation of a single port """ - - # Create the inverse address for a scratch address - 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 - - if self.t_current == 0: - self.add_noop_all_ports("Idle cycle (no positive clock edge)", - inverse_address, data_zeros) - - self.add_write("W data 1 address {}".format(inverse_address), - inverse_address,data_ones,write_port) - - self.add_write("W data 0 address {} to write value".format(self.probe_address), - self.probe_address,data_zeros,write_port) - self.measure_cycles[write_port]["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), - inverse_address,data_zeros,read_port) - self.measure_cycles[read_port]["read1"] = len(self.cycle_times)-1 - - self.add_read("R data 0 address {} to check W0 worked".format(self.probe_address), - self.probe_address,data_zeros,read_port) - self.measure_cycles[read_port]["read0"] = len(self.cycle_times)-1 - - def get_data_bit_column_number(self, probe_address, probe_data): - """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) - else: - col_address = 0 - bl_column = int(self.sram.words_per_row*probe_data + col_address) - return bl_column - - def run_delay_simulation(self): - """ - This tries to simulate a period and checks if the result works. If - so, it returns True and the delays, slews, and powers. It - works on the trimmed netlist by default, so powers do not - include leakage of all cells. - """ - #Sanity Check - debug.check(self.period > 0, "Target simulation period non-positive") - - result = [{} for i in self.all_ports] - # Checking from not data_value to data_value - self.write_delay_stimulus() - - self.stim.run_sim() #running sim prodoces spice output file. - - for port in self.targ_read_ports: - #Parse and check the voltage measurements - bl_volt_meas_dict = {} - for mname in self.bl_volt_meas_names: - mname_port = "{}{}".format(mname,port) - volt_meas_val = parse_spice_list("timing", mname_port) - if type(volt_meas_val)!=float: - debug.error("Failed to Parse Bitline Voltage:\n\t\t{0}={1}".format(mname,volt_meas_val),1) - bl_volt_meas_dict[mname] = volt_meas_val - result[port].update(bl_volt_meas_dict) - - #Parse and check the delay measurements. Intended that one measurement will fail, save the delay that did not fail. - bl_delay_meas_dict = {} - values_added = 0 #For error checking - for mname in self.bl_delay_meas_names: #Parse - mname_port = "{}{}".format(mname,port) - delay_meas_val = parse_spice_list("timing", mname_port) - if type(delay_meas_val)==float: #Only add if value is float, do not error. - bl_delay_meas_dict[self.bl_delay_result_name] = delay_meas_val * 1e9 #convert to ns - values_added+=1 - debug.check(values_added>0, "Bitline delay measurements failed in SPICE simulation.") - debug.check(values_added<2, "Both bitlines experienced a Vth drop, check simulation results.") - result[port].update(bl_delay_meas_dict) - - # The delay is from the negative edge for our SRAM - return (True,result) - - def check_bitline_all_results(self, results): - """Checks the bitline values measured for each tested port""" - for port in self.targ_read_ports: - self.check_bitline_port_results(results[port]) - - def check_bitline_port_results(self, port_results): - """Performs three different checks for the bitline values: functionality, bitline swing from vdd, and differential bit swing""" - bl_volt, br_volt = port_results["volt_bl"], port_results["volt_br"] - self.check_functionality(bl_volt,br_volt) - self.check_swing_from_vdd(bl_volt,br_volt) - self.check_differential_swing(bl_volt,br_volt) - - def check_functionality(self, bl_volt, br_volt): - """Checks whether the read failed or not. Measured values are hardcoded with the intention of reading a 0.""" - if bl_volt > br_volt: - debug.error("Read failure. Value 1 was read instead of 0.",1) - - def check_swing_from_vdd(self, bl_volt, br_volt): - """Checks difference on discharging bitline from VDD to see if it is within margin of the RBL height parameter.""" - if bl_volt < br_volt: - discharge_volt = bl_volt - else: - discharge_volt = br_volt - desired_bl_volt = tech.parameter["rbl_height_percentage"]*self.vdd_voltage - debug.info(1, "Active bitline={:.3f}v, Desired bitline={:.3f}v".format(discharge_volt,desired_bl_volt)) - vdd_error_margin = .2 #20% of vdd margin for bitline, a little high for now. - if abs(discharge_volt - desired_bl_volt) > vdd_error_margin*self.vdd_voltage: - debug.warning("Bitline voltage is not within {}% Vdd margin. Delay chain/RBL could need resizing.".format(vdd_error_margin*100)) - - def check_differential_swing(self, bl_volt, br_volt): - """This check looks at the difference between the bitline voltages. This needs to be large enough to prevent - sensing errors.""" - bitline_swing = abs(bl_volt-br_volt) - debug.info(1,"Bitline swing={:.3f}v".format(bitline_swing)) - vdd_error_margin = .2 #20% of vdd margin for bitline, a little high for now. - if bitline_swing < vdd_error_margin*self.vdd_voltage: - debug.warning("Bitline swing less than {}% Vdd margin. Sensing errors more likely to occur.".format(vdd_error_margin)) - - def analyze(self, probe_address, probe_data, slews, loads): - """Measures the bitline swing of the differential bitlines (bl/br) at 50% s_en """ - self.set_probe(probe_address, probe_data) - self.load=max(loads) - self.slew=max(slews) - - read_port = self.read_ports[0] #only test the first read port - bitline_swings = {} - self.targ_read_ports = [read_port] - self.targ_write_ports = [self.write_ports[0]] - debug.info(1,"Bitline swing test: corner {}".format(self.corner)) - (success, results)=self.run_delay_simulation() - debug.check(success, "Bitline Failed: period {}".format(self.period)) - debug.info(1,"Bitline values (voltages/delays):\n\t {}".format(results[read_port])) - self.check_bitline_all_results(results) - - return results - - - diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index e5a1693f..1126c0d7 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -411,8 +411,7 @@ class bank(design.design): self.bitcell_array = factory.create(module_type="bitcell_array", cols=self.num_cols, - rows=self.num_rows, - bitcell=self.bitcell) + rows=self.num_rows) self.add_mod(self.bitcell_array) self.precharge_array = [] diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index 2f3fbdc3..15141bb3 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -19,14 +19,13 @@ class bitcell_array(design.design): and word line is connected by abutment. Connects the word lines and bit lines. """ - def __init__(self, cols, rows, name, bitcell): + def __init__(self, cols, rows, name): design.design.__init__(self, 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 - self.cell = bitcell self.create_netlist() if not OPTS.netlist_only: @@ -87,6 +86,7 @@ class bitcell_array(design.design): def add_modules(self): """ Add the modules used in this design """ + self.cell = factory.create(module_type="bitcell") self.add_mod(self.cell) def create_instances(self): diff --git a/compiler/modules/replica_bitline.py b/compiler/modules/replica_bitline.py index f0bcbdc6..2a441f02 100644 --- a/compiler/modules/replica_bitline.py +++ b/compiler/modules/replica_bitline.py @@ -88,13 +88,11 @@ class replica_bitline(design.design): self.replica_bitcell = factory.create(module_type="replica_bitcell") self.add_mod(self.replica_bitcell) - - bitcell = factory.create(module_type="bitcell") + # This is the replica bitline load column that is the height of our array self.rbl = factory.create(module_type="bitcell_array", cols=1, - rows=self.bitcell_loads, - bitcell=bitcell) + rows=self.bitcell_loads) self.add_mod(self.rbl) # FIXME: The FO and depth of this should be tuned From 3f5b60856a004d97bac702fb6c8ff2490f851bdd Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Fri, 28 Jun 2019 13:49:04 -0700 Subject: [PATCH 22/22] Fixed key error with analytical delay of write ports. --- compiler/sram/sram_base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/sram/sram_base.py b/compiler/sram/sram_base.py index 547bc222..ff41e9f2 100644 --- a/compiler/sram/sram_base.py +++ b/compiler/sram/sram_base.py @@ -529,6 +529,7 @@ class sram_base(design, verilog, lef): elif port in self.readwrite_ports: control_logic = self.control_logic_rw else: + delays[port] = self.return_delay(0,0) #Write ports do not have a lib defined delay, marked as 0 continue clk_to_wlen_delays = control_logic.analytical_delay(corner, slew, load) wlen_to_dout_delays = self.bank.analytical_delay(corner,slew,load,port) #port should probably be specified...