Fixed conflicts in delay and elmore modules on merge with dev.

This commit is contained in:
Hunter Nichols 2021-05-25 15:25:43 -07:00
commit 2f4f8ca912
61 changed files with 1523 additions and 545 deletions

3
.gitattributes vendored
View File

@ -1 +1,2 @@
*.sp linguist-vendored *.sp linguist-language=Spice
*.tf linquist-language=Tech File

View File

@ -1,12 +1,9 @@
![](./images/OpenRAM_logo_yellow_transparent.svg)
# OpenRAM # OpenRAM
[![Python 3.5](https://img.shields.io/badge/Python-3.5-green.svg)](https://www.python.org/) [![Python 3.5](https://img.shields.io/badge/Python-3.5-green.svg)](https://www.python.org/)
[![License: BSD 3-clause](./images/license_badge.svg)](./LICENSE) [![License: BSD 3-clause](./images/license_badge.svg)](./LICENSE)
[![Download](./images/download-stable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/stable.zip)
Master:
[![Download](./images/download-stable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/master.zip)
Dev:
[![Download](./images/download-unstable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/dev.zip) [![Download](./images/download-unstable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/dev.zip)
An open-source static random access memory (SRAM) compiler. An open-source static random access memory (SRAM) compiler.
@ -32,10 +29,9 @@ things that need to be fixed.
## Dependencies ## Dependencies
The OpenRAM compiler has very few dependencies: The OpenRAM compiler has very few dependencies:
+ [Ngspice] 26 (or later) or HSpice I-2013.12-1 (or later) or CustomSim 2017 (or later) + [Ngspice] 26 (or later) or HSpice I-2013.12-1 (or later) or CustomSim 2017 (or later) or [Xyce] 7.2 (or later)
+ Python 3.5 or higher + Python 3.5 or higher
+ Python numpy (pip3 install numpy to install) + Various Python packages (pip install -r requirements.txt)
+ Python scipy (pip3 install scipy to install)
If you want to perform DRC and LVS, you will need either: If you want to perform DRC and LVS, you will need either:
+ Calibre (for [FreePDK45]) + Calibre (for [FreePDK45])
@ -218,6 +214,7 @@ If I forgot to add you, please let me know!
[Netgen]: http://opencircuitdesign.com/netgen/ [Netgen]: http://opencircuitdesign.com/netgen/
[Qflow]: http://opencircuitdesign.com/qflow/history.html [Qflow]: http://opencircuitdesign.com/qflow/history.html
[Ngspice]: http://ngspice.sourceforge.net/ [Ngspice]: http://ngspice.sourceforge.net/
[Xyce]: http://xyce.sandia.gov/
[OSUPDK]: https://vlsiarch.ecen.okstate.edu/flow/ [OSUPDK]: https://vlsiarch.ecen.okstate.edu/flow/
[FreePDK45]: https://www.eda.ncsu.edu/wiki/FreePDK45:Contents [FreePDK45]: https://www.eda.ncsu.edu/wiki/FreePDK45:Contents

View File

@ -123,6 +123,14 @@ class _wordline_driver:
self.vertical_supply = vertical_supply self.vertical_supply = vertical_supply
class _bitcell_array:
def __init__(self,
wordline_layer,
wordline_pitch_factor=2):
self.wordline_layer = wordline_layer
self.wordline_pitch_factor = wordline_pitch_factor
class layer_properties(): class layer_properties():
""" """
This contains meta information about the module routing layers. These This contains meta information about the module routing layers. These
@ -159,6 +167,10 @@ class layer_properties():
self._wordline_driver = _wordline_driver(vertical_supply=False) self._wordline_driver = _wordline_driver(vertical_supply=False)
self._local_bitcell_array = _bitcell_array(wordline_layer="m2")
self._global_bitcell_array = _bitcell_array(wordline_layer="m3")
@property @property
def bank(self): def bank(self):
return self._bank return self._bank
@ -191,3 +203,11 @@ class layer_properties():
def wordline_driver(self): def wordline_driver(self):
return self._wordline_driver return self._wordline_driver
@property
def global_bitcell_array(self):
return self._global_bitcell_array
@property
def local_bitcell_array(self):
return self._local_bitcell_array

View File

@ -132,7 +132,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
for subinst, conns in zip(self.insts, self.conns): for subinst, conns in zip(self.insts, self.conns):
if subinst in self.graph_inst_exclude: if subinst in self.graph_inst_exclude:
continue continue
subinst_name = inst_name + '.X' + subinst.name subinst_name = inst_name + "{}x".format(OPTS.hier_seperator) + subinst.name
subinst_ports = self.translate_nets(conns, port_dict, inst_name) subinst_ports = self.translate_nets(conns, port_dict, inst_name)
subinst.mod.build_graph(graph, subinst_name, subinst_ports) subinst.mod.build_graph(graph, subinst_name, subinst_ports)
@ -148,7 +148,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
port_dict = {pin: port for pin, port 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)) debug.info(3, "Instance name={}".format(inst_name))
for subinst, conns in zip(self.insts, self.conns): for subinst, conns in zip(self.insts, self.conns):
subinst_name = inst_name + '.X' + subinst.name subinst_name = inst_name + "{}x".format(OPTS.hier_seperator) + subinst.name
subinst_ports = self.translate_nets(conns, port_dict, inst_name) subinst_ports = self.translate_nets(conns, port_dict, inst_name)
for si_port, conn in zip(subinst_ports, conns): for si_port, conn in zip(subinst_ports, conns):
# Only add for first occurrence # Only add for first occurrence
@ -166,7 +166,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
if conn in port_dict: if conn in port_dict:
converted_conns.append(port_dict[conn]) converted_conns.append(port_dict[conn])
else: else:
converted_conns.append("{}.{}".format(inst_name, conn)) converted_conns.append("{0}{2}{1}".format(inst_name, conn, OPTS.hier_seperator))
return converted_conns return converted_conns
def add_graph_edges(self, graph, port_nets): def add_graph_edges(self, graph, port_nets):

View File

@ -674,7 +674,8 @@ class layout():
directions=None, directions=None,
size=[1, 1], size=[1, 1],
implant_type=None, implant_type=None,
well_type=None): well_type=None,
min_area=False):
""" """
Punch a stack of vias from a start layer to a target layer by the center. Punch a stack of vias from a start layer to a target layer by the center.
""" """
@ -708,7 +709,7 @@ class layout():
implant_type=implant_type, implant_type=implant_type,
well_type=well_type) well_type=well_type)
if cur_layer != from_layer: if cur_layer != from_layer or min_area:
self.add_min_area_rect_center(cur_layer, self.add_min_area_rect_center(cur_layer,
offset, offset,
via.mod.first_layer_width, via.mod.first_layer_width,

View File

@ -10,6 +10,8 @@ from tech import layer_names
import os import os
import shutil import shutil
from globals import OPTS from globals import OPTS
from vector import vector
from pin_layout import pin_layout
class lef: class lef:
@ -67,23 +69,66 @@ class lef:
def lef_write(self, lef_name): def lef_write(self, lef_name):
""" Write the entire lef of the object to the file. """ """ Write the entire lef of the object to the file. """
# Can possibly use magic lef write to create the LEF
# if OPTS.drc_exe and OPTS.drc_exe[0] == "magic":
# self.magic_lef_write(lef_name)
# return
if OPTS.drc_exe and OPTS.drc_exe[0] == "magic": # To maintain the indent level easily
self.magic_lef_write(lef_name) self.indent = ""
return
debug.info(3, "Writing detailed LEF to {0}".format(lef_name)) if OPTS.detailed_lef:
debug.info(3, "Writing detailed LEF to {0}".format(lef_name))
self.indent = "" # To maintain the indent level easily else:
debug.info(3, "Writing abstract LEF to {0}".format(lef_name))
self.compute_abstract_blockages()
self.lef = open(lef_name, "w") self.lef = open(lef_name, "w")
self.lef_write_header() self.lef_write_header()
for pin in self.pins:
self.lef_write_pin(pin) for pin_name in self.pins:
self.lef_write_obstructions() self.lef_write_pin(pin_name)
self.lef_write_obstructions(OPTS.detailed_lef)
self.lef_write_footer() self.lef_write_footer()
self.lef.close() self.lef.close()
def compute_abstract_blockages(self):
# Start with blockages on all layers the size of the block
# minus the pin escape margin (hard coded to 4 x m3 pitch)
# These are a pin_layout to use their geometric functions
perimeter_margin = self.m3_pitch
self.blockages = {}
for layer_name in self.lef_layers:
self.blockages[layer_name]=[]
for layer_name in self.lef_layers:
ll = vector(perimeter_margin, perimeter_margin)
ur = vector(self.width - perimeter_margin, self.height - perimeter_margin)
self.blockages[layer_name].append(pin_layout("",
[ll, ur],
layer_name))
# For each pin, remove the blockage and add the pin
for pin_name in self.pins:
pin = self.get_pin(pin_name)
inflated_pin = pin.inflated_pin(multiple=1)
another_iteration_needed = True
while another_iteration_needed:
another_iteration_needed = False
old_blockages = list(self.blockages[pin.layer])
for blockage in old_blockages:
if blockage.overlaps(inflated_pin):
intersection_shape = blockage.intersection(inflated_pin)
# If it is zero area, don't add the pin
if intersection_shape[0][0]==intersection_shape[1][0] or intersection_shape[0][1]==intersection_shape[1][1]:
continue
another_iteration_needed = True
# Remove the old blockage and add the new ones
self.blockages[pin.layer].remove(blockage)
intersection_pin = pin_layout("", intersection_shape, inflated_pin.layer)
new_blockages = blockage.cut(intersection_pin)
self.blockages[pin.layer].extend(new_blockages)
def lef_write_header(self): def lef_write_header(self):
""" Header of LEF file """ """ Header of LEF file """
self.lef.write("VERSION 5.4 ;\n") self.lef.write("VERSION 5.4 ;\n")
@ -103,8 +148,8 @@ class lef:
self.lef.write("{0}SYMMETRY X Y R90 ;\n".format(self.indent)) self.lef.write("{0}SYMMETRY X Y R90 ;\n".format(self.indent))
def lef_write_footer(self): def lef_write_footer(self):
self.lef.write("{0}END {1}\n".format(self.indent, self.name))
self.indent = self.indent[:-3] self.indent = self.indent[:-3]
self.lef.write("{0}END {1}\n".format(self.indent, self.name))
self.lef.write("END LIBRARY\n") self.lef.write("END LIBRARY\n")
def lef_write_pin(self, name): def lef_write_pin(self, name):
@ -136,24 +181,29 @@ class lef:
self.indent = self.indent[:-3] self.indent = self.indent[:-3]
self.lef.write("{0}END {1}\n".format(self.indent, name)) self.lef.write("{0}END {1}\n".format(self.indent, name))
def lef_write_obstructions(self): def lef_write_obstructions(self, detailed=False):
""" Write all the obstructions on each layer """ """ Write all the obstructions on each layer """
self.lef.write("{0}OBS\n".format(self.indent)) self.lef.write("{0}OBS\n".format(self.indent))
for layer in self.lef_layers: for layer in self.lef_layers:
self.lef.write("{0}LAYER {1} ;\n".format(self.indent, layer_names[layer])) self.lef.write("{0}LAYER {1} ;\n".format(self.indent, layer_names[layer]))
self.indent += " " self.indent += " "
blockages = self.get_blockages(layer, True) if detailed:
for b in blockages: blockages = self.get_blockages(layer, True)
self.lef_write_shape(b) for b in blockages:
self.lef_write_shape(b)
else:
blockages = self.blockages[layer]
for b in blockages:
self.lef_write_shape(b.rect)
self.indent = self.indent[:-3] self.indent = self.indent[:-3]
self.lef.write("{0}END\n".format(self.indent)) self.lef.write("{0}END\n".format(self.indent))
def lef_write_shape(self, rect): def lef_write_shape(self, obj):
if len(rect) == 2: if len(obj) == 2:
""" Write a LEF rectangle """ """ Write a LEF rectangle """
self.lef.write("{0}RECT ".format(self.indent)) self.lef.write("{0}RECT ".format(self.indent))
for item in rect: for item in obj:
# print(rect) # print(obj)
self.lef.write(" {0} {1}".format(round(item[0], self.lef.write(" {0} {1}".format(round(item[0],
self.round_grid), self.round_grid),
round(item[1], round(item[1],
@ -162,12 +212,10 @@ class lef:
else: else:
""" Write a LEF polygon """ """ Write a LEF polygon """
self.lef.write("{0}POLYGON ".format(self.indent)) self.lef.write("{0}POLYGON ".format(self.indent))
for item in rect: for item in obj:
self.lef.write(" {0} {1}".format(round(item[0], self.lef.write(" {0} {1}".format(round(item[0],
self.round_grid), self.round_grid),
round(item[1], round(item[1],
self.round_grid))) self.round_grid)))
# for i in range(0,len(rect)):
# self.lef.write(" {0} {1}".format(round(rect[i][0],self.round_grid), round(rect[i][1],self.round_grid)))
self.lef.write(" ;\n") self.lef.write(" ;\n")

View File

@ -139,13 +139,13 @@ class pin_layout:
min_area = drc("{}_minarea".format(self.layer)) min_area = drc("{}_minarea".format(self.layer))
pass pass
def inflate(self, spacing=None): def inflate(self, spacing=None, multiple=0.5):
""" """
Inflate the rectangle by the spacing (or other rule) Inflate the rectangle by the spacing (or other rule)
and return the new rectangle. and return the new rectangle.
""" """
if not spacing: if not spacing:
spacing = 0.5*drc("{0}_to_{0}".format(self.layer)) spacing = multiple*drc("{0}_to_{0}".format(self.layer))
(ll, ur) = self.rect (ll, ur) = self.rect
spacing = vector(spacing, spacing) spacing = vector(spacing, spacing)
@ -154,15 +154,23 @@ class pin_layout:
return (newll, newur) return (newll, newur)
def inflated_pin(self, spacing=None, multiple=0.5):
"""
Inflate the rectangle by the spacing (or other rule)
and return the new rectangle.
"""
inflated_area = self.inflate(spacing, multiple)
return pin_layout(self.name, inflated_area, self.layer)
def intersection(self, other): def intersection(self, other):
""" Check if a shape overlaps with a rectangle """ """ Check if a shape overlaps with a rectangle """
(ll, ur) = self.rect (ll, ur) = self.rect
(oll, our) = other.rect (oll, our) = other.rect
min_x = max(ll.x, oll.x) min_x = max(ll.x, oll.x)
max_x = min(ll.x, oll.x) max_x = min(ur.x, our.x)
min_y = max(ll.y, oll.y) min_y = max(ll.y, oll.y)
max_y = min(ll.y, oll.y) max_y = min(ur.y, our.y)
return [vector(min_x, min_y), vector(max_x, max_y)] return [vector(min_x, min_y), vector(max_x, max_y)]
@ -578,6 +586,30 @@ class pin_layout:
return None return None
def cut(self, shape):
"""
Return a set of shapes that are this shape minus the argument shape.
"""
# Make the unique coordinates in X and Y directions
x_offsets = sorted([self.lx(), self.rx(), shape.lx(), shape.rx()])
y_offsets = sorted([self.by(), self.uy(), shape.by(), shape.uy()])
new_shapes = []
# Create all of the shapes
for x1, x2 in zip(x_offsets[0:], x_offsets[1:]):
if x1==x2:
continue
for y1, y2 in zip(y_offsets[0:], y_offsets[1:]):
if y1==y2:
continue
new_shape = pin_layout("", [vector(x1, y1), vector(x2, y2)], self.lpp)
# Don't add the existing shape in if it overlaps the pin shape
if new_shape.contains(shape):
continue
new_shapes.append(new_shape)
return new_shapes
def same_lpp(self, lpp1, lpp2): def same_lpp(self, lpp1, lpp2):
""" """
Check if the layers and purposes are the same. Check if the layers and purposes are the same.

View File

@ -29,6 +29,11 @@ class verilog:
self.vf.write("\n") self.vf.write("\n")
self.vf.write("module {0}(\n".format(self.name)) self.vf.write("module {0}(\n".format(self.name))
self.vf.write("`ifdef USE_POWER_PINS\n")
self.vf.write(" vdd,\n")
self.vf.write(" gnd,\n")
self.vf.write("`endif\n")
for port in self.all_ports: for port in self.all_ports:
if port in self.readwrite_ports: if port in self.readwrite_ports:
self.vf.write("// Port {0}: RW\n".format(port)) self.vf.write("// Port {0}: RW\n".format(port))
@ -65,6 +70,11 @@ class verilog:
self.vf.write(" parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary\n") self.vf.write(" parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary\n")
self.vf.write("\n") self.vf.write("\n")
self.vf.write("`ifdef USE_POWER_PINS\n")
self.vf.write(" inout vdd;\n")
self.vf.write(" inout gnd;\n")
self.vf.write("`endif\n")
for port in self.all_ports: for port in self.all_ports:
self.add_inputs_outputs(port) self.add_inputs_outputs(port)

View File

@ -24,14 +24,22 @@ debug.info(1, "Initializing characterizer...")
OPTS.spice_exe = "" OPTS.spice_exe = ""
if not OPTS.analytical_delay: if not OPTS.analytical_delay:
debug.info(1, "Finding spice simulator.")
if OPTS.spice_name != "": if OPTS.spice_name != "":
# Capitalize Xyce
if OPTS.spice_name == "xyce":
OPTS.spice_name = "Xyce"
OPTS.spice_exe=find_exe(OPTS.spice_name) OPTS.spice_exe=find_exe(OPTS.spice_name)
if OPTS.spice_exe=="" or OPTS.spice_exe==None: if OPTS.spice_exe=="" or OPTS.spice_exe==None:
debug.error("{0} not found. Unable to perform characterization.".format(OPTS.spice_name), 1) debug.error("{0} not found. Unable to perform characterization.".format(OPTS.spice_name), 1)
else: else:
(OPTS.spice_name, OPTS.spice_exe) = get_tool("spice", ["ngspice", "ngspice.exe", "hspice", "xa"]) (OPTS.spice_name, OPTS.spice_exe) = get_tool("spice", ["Xyce", "ngspice", "ngspice.exe", "hspice", "xa"])
if OPTS.spice_name in ["Xyce", "xyce"]:
(OPTS.mpi_name, OPTS.mpi_exe) = get_tool("mpi", ["mpirun"])
OPTS.hier_seperator = ":"
else:
OPTS.mpi_name = None
OPTS.mpi_exe = ""
# set the input dir for spice files if using ngspice # set the input dir for spice files if using ngspice
if OPTS.spice_name == "ngspice": if OPTS.spice_name == "ngspice":
@ -39,6 +47,12 @@ if not OPTS.analytical_delay:
if OPTS.spice_exe == "": if OPTS.spice_exe == "":
debug.error("No recognizable spice version found. Unable to perform characterization.", 1) debug.error("No recognizable spice version found. Unable to perform characterization.", 1)
else:
debug.info(1, "Finding spice simulator: {} ({})".format(OPTS.spice_name, OPTS.spice_exe))
if OPTS.mpi_name:
debug.info(1, "MPI for spice simulator: {} ({})".format(OPTS.mpi_name, OPTS.mpi_exe))
debug.info(1, "Simulation threads: {}".format(OPTS.num_sim_threads))
else: else:
debug.info(1, "Analytical model enabled.") debug.info(1, "Analytical model enabled.")

View File

@ -11,21 +11,26 @@ import debug
from globals import OPTS from globals import OPTS
def relative_compare(value1,value2,error_tolerance=0.001): def relative_compare(value1, value2, error_tolerance=0.001):
""" This is used to compare relative values for convergence. """ """ This is used to compare relative values for convergence. """
return (abs(value1 - value2) / abs(max(value1,value2)) <= error_tolerance) return (abs(value1 - value2) / abs(max(value1, value2)) <= error_tolerance)
def parse_spice_list(filename, key): def parse_spice_list(filename, key):
"""Parses a hspice output.lis file for a key value""" """Parses a hspice output.lis file for a key value"""
lower_key = key.lower()
if OPTS.spice_name == "xa" : if OPTS.spice_name == "xa" :
# customsim has a different output file name # customsim has a different output file name
full_filename="{0}xa.meas".format(OPTS.openram_temp) full_filename="{0}xa.meas".format(OPTS.openram_temp)
elif OPTS.spice_name == "spectre": elif OPTS.spice_name == "spectre":
full_filename = os.path.join(OPTS.openram_temp, "delay_stim.measure") full_filename = os.path.join(OPTS.openram_temp, "delay_stim.measure")
elif OPTS.spice_name in ["Xyce", "xyce"]:
full_filename = os.path.join(OPTS.openram_temp, "spice_stdout.log")
else: else:
# ngspice/hspice using a .lis file # ngspice/hspice using a .lis file
full_filename="{0}{1}.lis".format(OPTS.openram_temp, filename) full_filename = "{0}{1}.lis".format(OPTS.openram_temp, filename)
try: try:
f = open(full_filename, "r") f = open(full_filename, "r")
@ -33,31 +38,34 @@ def parse_spice_list(filename, key):
debug.error("Unable to open spice output file: {0}".format(full_filename),1) debug.error("Unable to open spice output file: {0}".format(full_filename),1)
debug.archive() debug.archive()
contents = f.read() contents = f.read().lower()
f.close() 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*\S*)\s+.*".format(key), contents)
val = re.search(r"{0}\s*=\s*(-?\d+.?\d*[e]?[-+]?[0-9]*\S*)\s+.*".format(key), contents) val = re.search(r"{0}\s*=\s*(-?\d+.?\d*[e]?[-+]?[0-9]*\S*)\s+.*".format(lower_key), contents)
if val != None: if val != None:
debug.info(4, "Key = " + key + " Val = " + val.group(1)) debug.info(4, "Key = " + lower_key + " Val = " + val.group(1))
return convert_to_float(val.group(1)) return convert_to_float(val.group(1))
else: else:
return "Failed" return "Failed"
def round_time(time,time_precision=3):
def round_time(time, time_precision=3):
# times are in ns, so this is how many digits of precision # times are in ns, so this is how many digits of precision
# 3 digits = 1ps # 3 digits = 1ps
# 4 digits = 0.1ps # 4 digits = 0.1ps
# etc. # etc.
return round(time,time_precision) return round(time, time_precision)
def round_voltage(voltage,voltag_precision=5):
def round_voltage(voltage, voltage_precision=5):
# voltages are in volts # voltages are in volts
# 3 digits = 1mv # 3 digits = 1mv
# 4 digits = 0.1mv # 4 digits = 0.1mv
# 5 digits = 0.01mv # 5 digits = 0.01mv
# 6 digits = 1uv # 6 digits = 1uv
# etc # etc
return round(voltage,voltage_precision) return round(voltage, voltage_precision)
def convert_to_float(number): def convert_to_float(number):
"""Converts a string into a (float) number; also converts units(m,u,n,p)""" """Converts a string into a (float) number; also converts units(m,u,n,p)"""
@ -84,7 +92,7 @@ def convert_to_float(number):
'n': lambda x: x * 0.000000001, # nano 'n': lambda x: x * 0.000000001, # nano
'p': lambda x: x * 0.000000000001, # pico 'p': lambda x: x * 0.000000000001, # pico
'f': lambda x: x * 0.000000000000001 # femto 'f': lambda x: x * 0.000000000000001 # femto
}[unit.group(2)](float(unit.group(1))) }[unit.group(2)](float(unit.group(1)))
# if we weren't able to convert it to a float then error out # if we weren't able to convert it to a float then error out
if not type(float_value)==float: if not type(float_value)==float:
@ -92,9 +100,10 @@ def convert_to_float(number):
return float_value return float_value
def check_dict_values_is_float(dict): def check_dict_values_is_float(dict):
"""Checks if all the values are floats. Useful for checking failed Spice measurements.""" """Checks if all the values are floats. Useful for checking failed Spice measurements."""
for key, value in dict.items(): for key, value in dict.items():
if type(value)!=float: if type(value)!=float:
return False return False
return True return True

View File

@ -69,7 +69,7 @@ class delay(simulation):
for meas in meas_list: for meas in meas_list:
name = meas.name.lower() name = meas.name.lower()
debug.check(name not in name_set, ("SPICE measurements must have unique names. " debug.check(name not in name_set, ("SPICE measurements must have unique names. "
"Duplicate name={}").format(name)) "Duplicate name={0}").format(name))
name_set.add(name) name_set.add(name)
def create_read_port_measurement_objects(self): def create_read_port_measurement_objects(self):
@ -77,7 +77,7 @@ class delay(simulation):
self.read_lib_meas = [] self.read_lib_meas = []
self.clk_frmt = "clk{0}" # Unformatted clock name self.clk_frmt = "clk{0}" # Unformatted clock name
targ_name = "{0}{1}_{2}".format(self.dout_name, "{}", self.probe_data) # Empty values are the port and probe data bit targ_name = "{0}{{}}_{1}".format(self.dout_name, self.probe_data) # Empty values are the port and probe data bit
self.delay_meas = [] self.delay_meas = []
self.delay_meas.append(delay_measure("delay_lh", self.clk_frmt, targ_name, "RISE", "RISE", measure_scale=1e9)) 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[-1].meta_str = sram_op.READ_ONE # Used to index time delay values when measurements written to spice file.
@ -166,7 +166,7 @@ class delay(simulation):
self.dout_volt_meas = [] self.dout_volt_meas = []
for meas in self.delay_meas: for meas in self.delay_meas:
# Output voltage measures # Output voltage measures
self.dout_volt_meas.append(voltage_at_measure("v_{}".format(meas.name), self.dout_volt_meas.append(voltage_at_measure("v_{0}".format(meas.name),
meas.targ_name_no_port)) meas.targ_name_no_port))
self.dout_volt_meas[-1].meta_str = meas.meta_str self.dout_volt_meas[-1].meta_str = meas.meta_str
@ -186,7 +186,7 @@ class delay(simulation):
self.read_bit_meas = {bit_polarity.NONINVERTING: [], bit_polarity.INVERTING: []} self.read_bit_meas = {bit_polarity.NONINVERTING: [], bit_polarity.INVERTING: []}
meas_cycles = (sram_op.READ_ZERO, sram_op.READ_ONE) meas_cycles = (sram_op.READ_ZERO, sram_op.READ_ONE)
for cycle in meas_cycles: for cycle in meas_cycles:
meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name) meas_tag = "a{0}_b{1}_{2}".format(self.probe_address, self.probe_data, cycle.name)
single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data) single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data)
for polarity, meas in single_bit_meas.items(): for polarity, meas in single_bit_meas.items():
meas.meta_str = cycle meas.meta_str = cycle
@ -200,7 +200,7 @@ class delay(simulation):
self.write_bit_meas = {bit_polarity.NONINVERTING: [], bit_polarity.INVERTING: []} self.write_bit_meas = {bit_polarity.NONINVERTING: [], bit_polarity.INVERTING: []}
meas_cycles = (sram_op.WRITE_ZERO, sram_op.WRITE_ONE) meas_cycles = (sram_op.WRITE_ZERO, sram_op.WRITE_ONE)
for cycle in meas_cycles: for cycle in meas_cycles:
meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name) meas_tag = "a{0}_b{1}_{2}".format(self.probe_address, self.probe_data, cycle.name)
single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data) single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data)
for polarity, meas in single_bit_meas.items(): for polarity, meas in single_bit_meas.items():
meas.meta_str = cycle meas.meta_str = cycle
@ -219,20 +219,20 @@ class delay(simulation):
(cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, bit_row, bit_col) (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() storage_names = cell_inst.mod.get_storage_net_names()
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes" debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
"supported for characterization. Storage nets={}").format(storage_names)) "supported for characterization. Storage nets={0}").format(storage_names))
if OPTS.use_pex and OPTS.pex_exe[0] != "calibre": if OPTS.use_pex and OPTS.pex_exe[0] != "calibre":
bank_num = self.sram.get_bank_num(self.sram.name, bit_row, bit_col) bank_num = self.sram.get_bank_num(self.sram.name, bit_row, bit_col)
q_name = "bitcell_Q_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col) q_name = "bitcell_Q_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col)
qbar_name = "bitcell_Q_bar_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col) qbar_name = "bitcell_Q_bar_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col)
else: else:
q_name = cell_name + '.' + str(storage_names[0]) q_name = cell_name + OPTS.hier_seperator + str(storage_names[0])
qbar_name = cell_name + '.' + str(storage_names[1]) qbar_name = cell_name + OPTS.hier_seperator + str(storage_names[1])
# Bit measures, measurements times to be defined later. The measurement names must be unique # Bit measures, measurements times to be defined later. The measurement names must be unique
# but they is enforced externally. {} added to names to differentiate between ports allow the # but they is enforced externally. {} added to names to differentiate between ports allow the
# measurements are independent of the ports # measurements are independent of the ports
q_meas = voltage_at_measure("v_q_{}".format(meas_tag), q_name) q_meas = voltage_at_measure("v_q_{0}".format(meas_tag), q_name)
qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name) qbar_meas = voltage_at_measure("v_qbar_{0}".format(meas_tag), qbar_name)
return {bit_polarity.NONINVERTING: q_meas, bit_polarity.INVERTING: qbar_meas} return {bit_polarity.NONINVERTING: q_meas, bit_polarity.INVERTING: qbar_meas}
@ -242,15 +242,15 @@ class delay(simulation):
# FIXME: There should be a default_read_port variable in this case, pathing is done with this # FIXME: There should be a default_read_port variable in this case, pathing is done with this
# but is never mentioned otherwise # but is never mentioned otherwise
port = self.read_ports[0] port = self.read_ports[0]
sen_and_port = self.sen_name+str(port) sen_and_port = self.sen_name + str(port)
bl_and_port = self.bl_name.format(port) # bl_name contains a '{}' for the port bl_and_port = self.bl_name.format(port) # bl_name contains a '{}' for the port
# Isolate the s_en and bitline paths # Isolate the s_en and bitline paths
debug.info(1, "self.bl_name = {}".format(self.bl_name)) debug.info(1, "self.bl_name = {0}".format(self.bl_name))
debug.info(1, "self.graph.all_paths = {}".format(self.graph.all_paths)) debug.info(1, "self.graph.all_paths = {0}".format(self.graph.all_paths))
sen_paths = [path for path in self.graph.all_paths if sen_and_port in path] sen_paths = [path for path in self.graph.all_paths if sen_and_port in path]
bl_paths = [path for path in self.graph.all_paths if bl_and_port in path] bl_paths = [path for path in self.graph.all_paths if bl_and_port in path]
debug.check(len(sen_paths)==1, 'Found {} paths which contain the s_en net.'.format(len(sen_paths))) debug.check(len(sen_paths)==1, 'Found {0} paths which contain the s_en net.'.format(len(sen_paths)))
debug.check(len(bl_paths)==1, 'Found {} paths which contain the bitline net.'.format(len(bl_paths))) debug.check(len(bl_paths)==1, 'Found {0} paths which contain the bitline net.'.format(len(bl_paths)))
sen_path = sen_paths[0] sen_path = sen_paths[0]
bitline_path = bl_paths[0] bitline_path = bl_paths[0]
@ -286,11 +286,11 @@ class delay(simulation):
# Create the measurements # Create the measurements
path_meas = [] path_meas = []
for i in range(len(path)-1): for i in range(len(path) - 1):
cur_net, next_net = path[i], path[i+1] cur_net, next_net = path[i], path[i + 1]
cur_dir, next_dir = path_dirs[i], path_dirs[i+1] cur_dir, next_dir = path_dirs[i], path_dirs[i + 1]
meas_name = "delay_{}_to_{}".format(cur_net, next_net) meas_name = "delay_{0}_to_{1}".format(cur_net, next_net)
if i+1 != len(path)-1: if i + 1 != len(path) - 1:
path_meas.append(delay_measure(meas_name, cur_net, next_net, cur_dir, next_dir, measure_scale=1e9, has_port=False)) path_meas.append(delay_measure(meas_name, cur_net, next_net, cur_dir, next_dir, measure_scale=1e9, has_port=False))
else: # Make the last measurement always measure on FALL because is a read 0 else: # Make the last measurement always measure on FALL because is a read 0
path_meas.append(delay_measure(meas_name, cur_net, next_net, cur_dir, "FALL", measure_scale=1e9, has_port=False)) path_meas.append(delay_measure(meas_name, cur_net, next_net, cur_dir, "FALL", measure_scale=1e9, has_port=False))
@ -309,13 +309,13 @@ class delay(simulation):
# Convert to booleans based on function of modules (inverting/non-inverting) # Convert to booleans based on function of modules (inverting/non-inverting)
mod_type_bools = [mod.is_non_inverting() for mod in edge_mods] mod_type_bools = [mod.is_non_inverting() for mod in edge_mods]
#FIXME: obtuse hack to differentiate s_en input from bitline in sense amps # FIXME: obtuse hack to differentiate s_en input from bitline in sense amps
if self.sen_name in path: if self.sen_name in path:
# Force the sense amp to be inverting for s_en->DOUT. # Force the sense amp to be inverting for s_en->DOUT.
# bitline->DOUT is non-inverting, but the module cannot differentiate inputs. # bitline->DOUT is non-inverting, but the module cannot differentiate inputs.
s_en_index = path.index(self.sen_name) s_en_index = path.index(self.sen_name)
mod_type_bools[s_en_index] = False mod_type_bools[s_en_index] = False
debug.info(2,'Forcing sen->dout to be inverting.') debug.info(2, 'Forcing sen->dout to be inverting.')
# Use these to determine direction list assuming delay start on neg. edge of clock (FALL) # Use these to determine direction list assuming delay start on neg. edge of clock (FALL)
# Also, use shorthand that 'FALL' == False, 'RISE' == True to simplify logic # Also, use shorthand that 'FALL' == False, 'RISE' == True to simplify logic
@ -493,7 +493,7 @@ class delay(simulation):
elif meas_type is voltage_at_measure: elif meas_type is voltage_at_measure:
variant_tuple = self.get_volt_at_measure_variants(port, measure_obj) variant_tuple = self.get_volt_at_measure_variants(port, measure_obj)
else: else:
debug.error("Input function not defined for measurement type={}".format(meas_type)) debug.error("Input function not defined for measurement type={0}".format(meas_type))
# Removes port input from any object which does not use it. This shorthand only works if # 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 # the measurement has port as the last input. Could be implemented by measurement type or
# remove entirely from measurement classes. # remove entirely from measurement classes.
@ -515,7 +515,7 @@ class delay(simulation):
elif delay_obj.meta_str == sram_op.READ_ONE: elif delay_obj.meta_str == sram_op.READ_ONE:
meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]] meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]]
else: else:
debug.error("Unrecognized delay Index={}".format(delay_obj.meta_str),1) debug.error("Unrecognized delay Index={0}".format(delay_obj.meta_str), 1)
# These measurements have there time further delayed to the neg. edge of the clock. # These measurements have there time further delayed to the neg. edge of the clock.
if delay_obj.meta_add_delay: if delay_obj.meta_add_delay:
@ -587,20 +587,20 @@ class delay(simulation):
# Output some comments to aid where cycles start and # Output some comments to aid where cycles start and
# what is happening # what is happening
for comment in self.cycle_comments: for comment in self.cycle_comments:
self.sf.write("* {}\n".format(comment)) self.sf.write("* {0}\n".format(comment))
self.sf.write("\n") self.sf.write("\n")
for read_port in self.targ_read_ports: for read_port in self.targ_read_ports:
self.sf.write("* Read ports {}\n".format(read_port)) self.sf.write("* Read ports {0}\n".format(read_port))
self.write_delay_measures_read_port(read_port) self.write_delay_measures_read_port(read_port)
for write_port in self.targ_write_ports: for write_port in self.targ_write_ports:
self.sf.write("* Write ports {}\n".format(write_port)) self.sf.write("* Write ports {0}\n".format(write_port))
self.write_delay_measures_write_port(write_port) self.write_delay_measures_write_port(write_port)
def load_pex_net(self, net: str): def load_pex_net(self, net: str):
from subprocess import check_output, CalledProcessError from subprocess import check_output, CalledProcessError
prefix = (self.sram_instance_name + ".").lower() prefix = (self.sram_instance_name + OPTS.hier_seperator).lower()
if not net.lower().startswith(prefix) or not OPTS.use_pex or not OPTS.calibre_pex: if not net.lower().startswith(prefix) or not OPTS.use_pex or not OPTS.calibre_pex:
return net return net
original_net = net original_net = net
@ -640,26 +640,41 @@ class delay(simulation):
col = self.bitline_column col = self.bitline_column
row = self.wordline_row row = self.wordline_row
for port in set(self.targ_read_ports + self.targ_write_ports): for port in set(self.targ_read_ports + self.targ_write_ports):
probe_nets.add("WEB{}".format(port)) probe_nets.add("WEB{0}".format(port))
probe_nets.add("{}.w_en{}".format(self.sram_instance_name, port)) probe_nets.add("{0}{2}w_en{1}".format(self.sram_instance_name, port, OPTS.hier_seperator))
probe_nets.add("{0}.Xbank0.Xport_data{1}.Xwrite_driver_array{1}.Xwrite_driver{2}.en_bar".format( probe_nets.add("{0}{3}Xbank0{3}Xport_data{1}{3}Xwrite_driver_array{1}{3}Xwrite_driver{2}{3}en_bar".format(self.sram_instance_name,
self.sram_instance_name, port, self.bitline_column)) port,
probe_nets.add("{}.Xbank0.br_{}_{}".format(self.sram_instance_name, port, self.bitline_column,
self.bitline_column)) OPTS.hier_seperator))
probe_nets.add("{0}{3}Xbank0{3}br_{1}_{2}".format(self.sram_instance_name,
port,
self.bitline_column,
OPTS.hier_seperator))
if not OPTS.use_pex: if not OPTS.use_pex:
continue continue
probe_nets.add( probe_nets.add(
"{0}.vdd_Xbank0_Xbitcell_array_xbitcell_array_xbit_r{1}_c{2}".format(sram_name, row, col - 1)) "{0}{3}vdd_Xbank0_Xbitcell_array_xbitcell_array_xbit_r{1}_c{2}".format(sram_name,
row,
col - 1,
OPTS.hier_seperator))
probe_nets.add( probe_nets.add(
"{0}.p_en_bar{1}_Xbank0_Xport_data{1}_Xprecharge_array{1}_Xpre_column_{2}".format(sram_name, port, col)) "{0}{3}p_en_bar{1}_Xbank0_Xport_data{1}_Xprecharge_array{1}_Xpre_column_{2}".format(sram_name,
port,
col,
OPTS.hier_seperator))
probe_nets.add( probe_nets.add(
"{0}.vdd_Xbank0_Xport_data{1}_Xprecharge_array{1}_xpre_column_{2}".format(sram_name, port, col)) "{0}{3}vdd_Xbank0_Xport_data{1}_Xprecharge_array{1}_xpre_column_{2}".format(sram_name,
probe_nets.add("{0}.vdd_Xbank0_Xport_data{1}_Xwrite_driver_array{1}_xwrite_driver{2}".format(sram_name, port,
port, col)) col,
OPTS.hier_seperator))
probe_nets.add("{0}{3}vdd_Xbank0_Xport_data{1}_Xwrite_driver_array{1}_xwrite_driver{2}".format(sram_name,
port,
col,
OPTS.hier_seperator))
probe_nets.update(self.measurement_nets) probe_nets.update(self.measurement_nets)
for net in probe_nets: for net in probe_nets:
debug.info(2, "Probe: {}".format(net)) debug.info(2, "Probe: {0}".format(net))
self.sf.write(".plot V({}) \n".format(self.load_pex_net(net))) self.sf.write(".plot V({0}) \n".format(self.load_pex_net(net)))
def write_power_measures(self): def write_power_measures(self):
""" """
@ -778,7 +793,7 @@ class delay(simulation):
if not self.check_bit_measures(self.write_bit_meas, port): if not self.check_bit_measures(self.write_bit_meas, port):
return(False, {}) return(False, {})
debug.info(2, "Checking write values for port {}".format(port)) debug.info(2, "Checking write values for port {0}".format(port))
write_port_dict = {} write_port_dict = {}
for measure in self.write_lib_meas: for measure in self.write_lib_meas:
write_port_dict[measure.name] = measure.retrieve_measure(port=port) write_port_dict[measure.name] = measure.retrieve_measure(port=port)
@ -792,7 +807,7 @@ class delay(simulation):
if not self.check_bit_measures(self.read_bit_meas, port): if not self.check_bit_measures(self.read_bit_meas, port):
return(False, {}) return(False, {})
debug.info(2, "Checking read delay values for port {}".format(port)) debug.info(2, "Checking read delay values for port {0}".format(port))
# Check sen timing, then bitlines, then general measurements. # Check sen timing, then bitlines, then general measurements.
if not self.check_sen_measure(port): if not self.check_sen_measure(port):
return (False, {}) return (False, {})
@ -821,7 +836,7 @@ class delay(simulation):
"""Checks that the sen occurred within a half-period""" """Checks that the sen occurred within a half-period"""
sen_val = self.sen_meas.retrieve_measure(port=port) sen_val = self.sen_meas.retrieve_measure(port=port)
debug.info(2, "s_en delay={}ns".format(sen_val)) debug.info(2, "s_en delay={0}ns".format(sen_val))
if self.sen_meas.meta_add_delay: if self.sen_meas.meta_add_delay:
max_delay = self.period / 2 max_delay = self.period / 2
else: else:
@ -843,22 +858,22 @@ class delay(simulation):
elif self.br_name == meas.targ_name_no_port: elif self.br_name == meas.targ_name_no_port:
br_vals[meas.meta_str] = val br_vals[meas.meta_str] = val
debug.info(2, "{}={}".format(meas.name, val)) debug.info(2, "{0}={1}".format(meas.name, val))
dout_success = True dout_success = True
bl_success = False bl_success = False
for meas in self.dout_volt_meas: for meas in self.dout_volt_meas:
val = meas.retrieve_measure(port=port) val = meas.retrieve_measure(port=port)
debug.info(2, "{}={}".format(meas.name, val)) debug.info(2, "{0}={1}".format(meas.name, val))
debug.check(type(val)==float, "Error retrieving numeric measurement: {0} {1}".format(meas.name, val)) debug.check(type(val)==float, "Error retrieving numeric measurement: {0} {1}".format(meas.name, val))
if meas.meta_str == sram_op.READ_ONE and val < self.vdd_voltage * 0.1: if meas.meta_str == sram_op.READ_ONE and val < self.vdd_voltage * 0.1:
dout_success = False dout_success = False
debug.info(1, "Debug measurement failed. Value {}V was read on read 1 cycle.".format(val)) debug.info(1, "Debug measurement failed. Value {0}V was read on read 1 cycle.".format(val))
bl_success = self.check_bitline_meas(bl_vals[sram_op.READ_ONE], br_vals[sram_op.READ_ONE]) bl_success = self.check_bitline_meas(bl_vals[sram_op.READ_ONE], br_vals[sram_op.READ_ONE])
elif meas.meta_str == sram_op.READ_ZERO and val > self.vdd_voltage * 0.9: elif meas.meta_str == sram_op.READ_ZERO and val > self.vdd_voltage * 0.9:
dout_success = False dout_success = False
debug.info(1, "Debug measurement failed. Value {}V was read on read 0 cycle.".format(val)) debug.info(1, "Debug measurement failed. Value {0}V was read on read 0 cycle.".format(val))
bl_success = self.check_bitline_meas(br_vals[sram_op.READ_ONE], bl_vals[sram_op.READ_ONE]) bl_success = 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 # If the bitlines have a correct value while the output does not then that is a
@ -877,7 +892,7 @@ class delay(simulation):
for polarity, meas_list in bit_measures.items(): for polarity, meas_list in bit_measures.items():
for meas in meas_list: for meas in meas_list:
val = meas.retrieve_measure(port=port) val = meas.retrieve_measure(port=port)
debug.info(2, "{}={}".format(meas.name, val)) debug.info(2, "{0}={1}".format(meas.name, val))
if type(val) != float: if type(val) != float:
continue continue
meas_cycle = meas.meta_str meas_cycle = meas.meta_str
@ -896,8 +911,8 @@ class delay(simulation):
success = val < self.vdd_voltage / 2 success = val < self.vdd_voltage / 2
if not success: if not success:
debug.info(1, ("Wrong value detected on probe bit during read/write cycle. " debug.info(1, ("Wrong value detected on probe bit during read/write cycle. "
"Check writes and control logic for bugs.\n measure={}, op={}, " "Check writes and control logic for bugs.\n measure={0}, op={1}, "
"bit_storage={}, V(bit)={}").format(meas.name, meas_cycle.name, polarity.name, val)) "bit_storage={2}, V(bit)={3}").format(meas.name, meas_cycle.name, polarity.name, val))
return success return success
@ -912,7 +927,7 @@ class delay(simulation):
min_dicharge = v_discharged_bl < self.vdd_voltage * 0.9 min_dicharge = v_discharged_bl < self.vdd_voltage * 0.9
min_diff = (v_charged_bl - v_discharged_bl) > self.vdd_voltage * 0.1 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)) debug.info(1, "min_dicharge={0}, min_diff={1}".format(min_dicharge, min_diff))
return (min_dicharge and min_diff) return (min_dicharge and min_diff)
def check_path_measures(self): def check_path_measures(self):
@ -921,11 +936,11 @@ class delay(simulation):
# Get and set measurement, no error checking done other than prints. # Get and set measurement, no error checking done other than prints.
debug.info(2, "Checking measures in Delay Path") debug.info(2, "Checking measures in Delay Path")
value_dict = {} value_dict = {}
for meas in self.sen_path_meas+self.bl_path_meas: for meas in self.sen_path_meas + self.bl_path_meas:
val = meas.retrieve_measure() val = meas.retrieve_measure()
debug.info(2, '{}={}'.format(meas.name, val)) debug.info(2, '{0}={1}'.format(meas.name, val))
if type(val) != float or val > self.period/2: if type(val) != float or val > self.period / 2:
debug.info(1,'Failed measurement:{}={}'.format(meas.name, val)) debug.info(1, 'Failed measurement:{}={}'.format(meas.name, val))
value_dict[meas.name] = val value_dict[meas.name] = val
#debug.info(0, "value_dict={}".format(value_dict)) #debug.info(0, "value_dict={}".format(value_dict))
return value_dict return value_dict
@ -1100,14 +1115,14 @@ class delay(simulation):
# Set up to trim the netlist here if that is enabled # Set up to trim the netlist here if that is enabled
if OPTS.trim_netlist: if OPTS.trim_netlist:
self.trim_sp_file = "{}trimmed.sp".format(OPTS.openram_temp) self.trim_sp_file = "{0}trimmed.sp".format(OPTS.openram_temp)
self.sram.sp_write(self.trim_sp_file, lvs=False, trim=True) self.sram.sp_write(self.trim_sp_file, lvs=False, trim=True)
else: else:
# The non-reduced netlist file when it is disabled # The non-reduced netlist file when it is disabled
self.trim_sp_file = "{}sram.sp".format(OPTS.openram_temp) self.trim_sp_file = "{0}sram.sp".format(OPTS.openram_temp)
# The non-reduced netlist file for power simulation # The non-reduced netlist file for power simulation
self.sim_sp_file = "{}sram.sp".format(OPTS.openram_temp) self.sim_sp_file = "{0}sram.sp".format(OPTS.openram_temp)
# Make a copy in temp for debugging # Make a copy in temp for debugging
shutil.copy(self.sp_file, self.sim_sp_file) shutil.copy(self.sp_file, self.sim_sp_file)
@ -1194,6 +1209,7 @@ class delay(simulation):
for mname, value in delay_results[port].items(): for mname, value in delay_results[port].items():
if "power" in mname: if "power" in mname:
# Subtract partial array leakage and add full array leakage for the power measures # Subtract partial array leakage and add full array leakage for the power measures
debug.info(1, "Adding leakage offset to {0} {1} + {2} = {3}".format(mname, value, leakage_offset, value + leakage_offset))
measure_data[port][mname].append(value + leakage_offset) measure_data[port][mname].append(value + leakage_offset)
else: else:
measure_data[port][mname].append(value) measure_data[port][mname].append(value)
@ -1246,13 +1262,13 @@ class delay(simulation):
if self.t_current == 0: if self.t_current == 0:
self.add_noop_all_ports("Idle cycle (no positive clock edge)") self.add_noop_all_ports("Idle cycle (no positive clock edge)")
self.add_write("W data 1 address {}".format(inverse_address), self.add_write("W data 1 address {0}".format(inverse_address),
inverse_address, inverse_address,
data_ones, data_ones,
wmask_ones, wmask_ones,
write_port) write_port)
self.add_write("W data 0 address {} to write value".format(self.probe_address), self.add_write("W data 0 address {0} to write value".format(self.probe_address),
self.probe_address, self.probe_address,
data_zeros, data_zeros,
wmask_ones, wmask_ones,
@ -1263,11 +1279,11 @@ class delay(simulation):
self.measure_cycles[write_port]["disabled_write0"] = len(self.cycle_times) - 1 self.measure_cycles[write_port]["disabled_write0"] = len(self.cycle_times) - 1
# This also ensures we will have a H->L transition on the next read # 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), self.add_read("R data 1 address {0} to set dout caps".format(inverse_address),
inverse_address, inverse_address,
read_port) read_port)
self.add_read("R data 0 address {} to check W0 worked".format(self.probe_address), self.add_read("R data 0 address {0} to check W0 worked".format(self.probe_address),
self.probe_address, self.probe_address,
read_port) read_port)
self.measure_cycles[read_port][sram_op.READ_ZERO] = len(self.cycle_times) - 1 self.measure_cycles[read_port][sram_op.READ_ZERO] = len(self.cycle_times) - 1
@ -1277,7 +1293,7 @@ class delay(simulation):
self.add_noop_all_ports("Idle cycle (if read takes >1 cycle)") self.add_noop_all_ports("Idle cycle (if read takes >1 cycle)")
self.add_write("W data 1 address {} to write value".format(self.probe_address), self.add_write("W data 1 address {0} to write value".format(self.probe_address),
self.probe_address, self.probe_address,
data_ones, data_ones,
wmask_ones, wmask_ones,
@ -1287,7 +1303,7 @@ class delay(simulation):
self.add_noop_clock_one_port(write_port) self.add_noop_clock_one_port(write_port)
self.measure_cycles[write_port]["disabled_write1"] = len(self.cycle_times) - 1 self.measure_cycles[write_port]["disabled_write1"] = len(self.cycle_times) - 1
self.add_write("W data 0 address {} to clear din caps".format(inverse_address), self.add_write("W data 0 address {0} to clear din caps".format(inverse_address),
inverse_address, inverse_address,
data_zeros, data_zeros,
wmask_ones, wmask_ones,
@ -1297,11 +1313,11 @@ class delay(simulation):
self.measure_cycles[read_port]["disabled_read1"] = len(self.cycle_times) - 1 self.measure_cycles[read_port]["disabled_read1"] = len(self.cycle_times) - 1
# This also ensures we will have a L->H transition on the next read # This also ensures we will have a L->H transition on the next read
self.add_read("R data 0 address {} to clear dout caps".format(inverse_address), self.add_read("R data 0 address {0} to clear dout caps".format(inverse_address),
inverse_address, inverse_address,
read_port) read_port)
self.add_read("R data 1 address {} to check W1 worked".format(self.probe_address), self.add_read("R data 1 address {0} to check W1 worked".format(self.probe_address),
self.probe_address, self.probe_address,
read_port) read_port)
self.measure_cycles[read_port][sram_op.READ_ONE] = len(self.cycle_times) - 1 self.measure_cycles[read_port][sram_op.READ_ONE] = len(self.cycle_times) - 1

View File

@ -67,18 +67,15 @@ class elmore(simulation):
load, load,
total_delay.delay / 1e3, total_delay.delay / 1e3,
total_delay.slew / 1e3)) total_delay.slew / 1e3))
# Delay is only calculated on a single port and replicated for now.
# Delay is only calculated on a single port and replicated for now. for port in self.all_ports:
for port in self.all_ports: for mname in self.delay_meas_names + self.power_meas_names:
for mname in self.delay_meas_names + self.power_meas_names: if "power" in mname:
if "power" in mname: port_data[port][mname].append(power.dynamic)
port_data[port][mname].append(power.dynamic) elif "delay" in mname and port in self.read_ports:
elif "delay" in mname and port in self.read_ports: port_data[port][mname].append(total_delay.delay / 1e3)
port_data[port][mname].append(total_delay.delay / 1e3) elif "slew" in mname and port in self.read_ports:
elif "slew" in mname and port in self.read_ports: port_data[port][mname].append(total_delay.slew / 1e3)
port_data[port][mname].append(total_delay.slew / 1e3)
else:
debug.error("Measurement name not recognized: {}".format(mname), 1)
# Margin for error in period. Calculated by averaging required margin for a small and large # Margin for error in period. Calculated by averaging required margin for a small and large
# memory. FIXME: margin is quite large, should be looked into. # memory. FIXME: margin is quite large, should be looked into.

View File

@ -81,7 +81,7 @@ class functional(simulation):
self.create_graph() self.create_graph()
self.set_internal_spice_names() self.set_internal_spice_names()
self.q_name, self.qbar_name = self.get_bit_name() self.q_name, self.qbar_name = self.get_bit_name()
debug.info(2, "q name={}\nqbar name={}".format(self.q_name, self.qbar_name)) debug.info(2, "q name={0}\nqbar name={1}".format(self.q_name, self.qbar_name))
# Number of checks can be changed # Number of checks can be changed
self.num_cycles = cycles self.num_cycles = cycles
@ -144,7 +144,7 @@ class functional(simulation):
for port in self.write_ports: for port in self.write_ports:
addr = self.gen_addr() addr = self.gen_addr()
(word, spare) = self.gen_data() (word, spare) = self.gen_data()
combined_word = "{}+{}".format(word, spare) combined_word = "{0}+{1}".format(word, spare)
comment = self.gen_cycle_comment("write", combined_word, addr, "1" * self.num_wmasks, port, self.t_current) comment = self.gen_cycle_comment("write", combined_word, addr, "1" * self.num_wmasks, port, self.t_current)
self.add_write_one_port(comment, addr, word + spare, "1" * self.num_wmasks, port) self.add_write_one_port(comment, addr, word + spare, "1" * self.num_wmasks, port)
self.stored_words[addr] = word self.stored_words[addr] = word
@ -167,7 +167,7 @@ class functional(simulation):
self.add_noop_one_port(port) self.add_noop_one_port(port)
else: else:
(addr, word, spare) = self.get_data() (addr, word, spare) = self.get_data()
combined_word = "{}+{}".format(word, spare) combined_word = "{0}+{1}".format(word, spare)
comment = self.gen_cycle_comment("read", combined_word, addr, "0" * self.num_wmasks, port, self.t_current) comment = self.gen_cycle_comment("read", combined_word, addr, "0" * self.num_wmasks, port, self.t_current)
self.add_read_one_port(comment, addr, port) self.add_read_one_port(comment, addr, port)
self.add_read_check(word, port) self.add_read_check(word, port)
@ -197,7 +197,7 @@ class functional(simulation):
self.add_noop_one_port(port) self.add_noop_one_port(port)
else: else:
(word, spare) = self.gen_data() (word, spare) = self.gen_data()
combined_word = "{}+{}".format(word, spare) combined_word = "{0}+{1}".format(word, spare)
comment = self.gen_cycle_comment("write", combined_word, addr, "1" * self.num_wmasks, port, self.t_current) comment = self.gen_cycle_comment("write", combined_word, addr, "1" * self.num_wmasks, port, self.t_current)
self.add_write_one_port(comment, addr, word + spare, "1" * self.num_wmasks, port) self.add_write_one_port(comment, addr, word + spare, "1" * self.num_wmasks, port)
self.stored_words[addr] = word self.stored_words[addr] = word
@ -213,7 +213,7 @@ class functional(simulation):
(word, spare) = self.gen_data() (word, spare) = self.gen_data()
wmask = self.gen_wmask() wmask = self.gen_wmask()
new_word = self.gen_masked_data(old_word, word, wmask) new_word = self.gen_masked_data(old_word, word, wmask)
combined_word = "{}+{}".format(word, spare) combined_word = "{0}+{1}".format(word, spare)
comment = self.gen_cycle_comment("partial_write", combined_word, addr, wmask, port, self.t_current) comment = self.gen_cycle_comment("partial_write", combined_word, addr, wmask, port, self.t_current)
self.add_write_one_port(comment, addr, word + spare, wmask, port) self.add_write_one_port(comment, addr, word + spare, wmask, port)
self.stored_words[addr] = new_word self.stored_words[addr] = new_word
@ -222,7 +222,7 @@ class functional(simulation):
else: else:
(addr, word) = random.choice(list(self.stored_words.items())) (addr, word) = random.choice(list(self.stored_words.items()))
spare = self.stored_spares[addr[:self.addr_spare_index]] spare = self.stored_spares[addr[:self.addr_spare_index]]
combined_word = "{}+{}".format(word, spare) combined_word = "{0}+{1}".format(word, spare)
# The write driver is not sized sufficiently to drive through the two # The write driver is not sized sufficiently to drive through the two
# bitcell access transistors to the read port. So, for now, we do not allow # bitcell access transistors to the read port. So, for now, we do not allow
# a simultaneous write and read to the same address on different ports. This # a simultaneous write and read to the same address on different ports. This
@ -363,7 +363,7 @@ class functional(simulation):
self.stim_sp = "functional_stim.sp" self.stim_sp = "functional_stim.sp"
temp_stim = "{0}/{1}".format(self.output_path, self.stim_sp) temp_stim = "{0}/{1}".format(self.output_path, self.stim_sp)
self.sf = open(temp_stim, "w") self.sf = open(temp_stim, "w")
self.sf.write("* Functional test stimulus file for {}ns period\n\n".format(self.period)) self.sf.write("* Functional test stimulus file for {0}ns period\n\n".format(self.period))
self.stim = stimuli(self.sf, self.corner) self.stim = stimuli(self.sf, self.corner)
# Write include statements # Write include statements
@ -387,16 +387,16 @@ class functional(simulation):
# Write important signals to stim file # Write important signals to stim file
self.sf.write("\n\n* Important signals for debug\n") self.sf.write("\n\n* Important signals for debug\n")
self.sf.write("* bl: {}\n".format(self.bl_name.format(port))) self.sf.write("* bl: {0}\n".format(self.bl_name.format(port)))
self.sf.write("* br: {}\n".format(self.br_name.format(port))) self.sf.write("* br: {0}\n".format(self.br_name.format(port)))
self.sf.write("* s_en: {}\n".format(self.sen_name)) self.sf.write("* s_en: {0}\n".format(self.sen_name))
self.sf.write("* q: {}\n".format(self.q_name)) self.sf.write("* q: {0}\n".format(self.q_name))
self.sf.write("* qbar: {}\n".format(self.qbar_name)) self.sf.write("* qbar: {0}\n".format(self.qbar_name))
# Write debug comments to stim file # Write debug comments to stim file
self.sf.write("\n\n* Sequence of operations\n") self.sf.write("\n\n* Sequence of operations\n")
for comment in self.fn_cycle_comments: for comment in self.fn_cycle_comments:
self.sf.write("*{}\n".format(comment)) self.sf.write("*{0}\n".format(comment))
# Generate data input bits # Generate data input bits
self.sf.write("\n* Generation of data and address signals\n") self.sf.write("\n* Generation of data and address signals\n")
@ -414,10 +414,10 @@ class functional(simulation):
# Generate control signals # Generate control signals
self.sf.write("\n * Generation of control signals\n") self.sf.write("\n * Generation of control signals\n")
for port in self.all_ports: for port in self.all_ports:
self.stim.gen_pwl("CSB{}".format(port), self.cycle_times, self.csb_values[port], self.period, self.slew, 0.05) self.stim.gen_pwl("CSB{0}".format(port), self.cycle_times, self.csb_values[port], self.period, self.slew, 0.05)
for port in self.readwrite_ports: for port in self.readwrite_ports:
self.stim.gen_pwl("WEB{}".format(port), self.cycle_times, self.web_values[port], self.period, self.slew, 0.05) self.stim.gen_pwl("WEB{0}".format(port), self.cycle_times, self.web_values[port], self.period, self.slew, 0.05)
# Generate wmask bits # Generate wmask bits
for port in self.write_ports: for port in self.write_ports:
@ -472,15 +472,15 @@ class functional(simulation):
self.stim.write_control(self.cycle_times[-1] + self.period) self.stim.write_control(self.cycle_times[-1] + self.period)
self.sf.close() self.sf.close()
#FIXME: Similar function to delay.py, refactor this # FIXME: Similar function to delay.py, refactor this
def get_bit_name(self): def get_bit_name(self):
""" Get a bit cell name """ """ Get a bit cell name """
(cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, 0, 0) (cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, 0, 0)
storage_names = cell_inst.mod.get_storage_net_names() storage_names = cell_inst.mod.get_storage_net_names()
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes" debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
"supported for characterization. Storage nets={}").format(storage_names)) "supported for characterization. Storage nets={0}").format(storage_names))
q_name = cell_name + '.' + str(storage_names[0]) q_name = cell_name + OPTS.hier_seperator + str(storage_names[0])
qbar_name = cell_name + '.' + str(storage_names[1]) qbar_name = cell_name + OPTS.hier_seperator + str(storage_names[1])
return (q_name, qbar_name) return (q_name, qbar_name)

View File

@ -53,11 +53,20 @@ class spice_measurement(ABC):
elif not self.has_port and port != None: elif not self.has_port and port != None:
debug.error("Unexpected port input received during measure retrieval.",1) debug.error("Unexpected port input received during measure retrieval.",1)
class delay_measure(spice_measurement): class delay_measure(spice_measurement):
"""Generates a spice measurement for the delay of 50%-to-50% points of two signals.""" """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,\ def __init__(self,
trig_vdd=0.5, targ_vdd=0.5, measure_scale=None, has_port=True): 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) 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) self.set_meas_constants(trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd)
@ -73,7 +82,7 @@ class delay_measure(spice_measurement):
self.trig_name_no_port = trig_name self.trig_name_no_port = trig_name
self.targ_name_no_port = targ_name self.targ_name_no_port = targ_name
#Time delays and ports are variant and needed as inputs when writing the measurement # Time delays and ports are variant and needed as inputs when writing the measurement
def get_measure_values(self, trig_td, targ_td, vdd_voltage, port=None): def get_measure_values(self, trig_td, targ_td, vdd_voltage, port=None):
"""Constructs inputs to stimulus measurement function. Variant values are inputs here.""" """Constructs inputs to stimulus measurement function. Variant values are inputs here."""
@ -82,7 +91,7 @@ class delay_measure(spice_measurement):
targ_val = self.targ_val_of_vdd * vdd_voltage targ_val = self.targ_val_of_vdd * vdd_voltage
if port != None: if port != None:
#For dictionary indexing reasons, the name is formatted differently than the signals # For dictionary indexing reasons, the name is formatted differently than the signals
meas_name = "{}{}".format(self.name, port) meas_name = "{}{}".format(self.name, port)
trig_name = self.trig_name_no_port.format(port) trig_name = self.trig_name_no_port.format(port)
targ_name = self.targ_name_no_port.format(port) targ_name = self.targ_name_no_port.format(port)
@ -90,7 +99,8 @@ class delay_measure(spice_measurement):
meas_name = self.name meas_name = self.name
trig_name = self.trig_name_no_port trig_name = self.trig_name_no_port
targ_name = self.targ_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) 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): class slew_measure(delay_measure):
@ -114,7 +124,8 @@ class slew_measure(delay_measure):
self.trig_name_no_port = signal_name self.trig_name_no_port = signal_name
self.targ_name_no_port = signal_name self.targ_name_no_port = signal_name
#Time delays and ports are variant and needed as inputs when writing the measurement # Time delays and ports are variant and needed as inputs when writing the measurement
class power_measure(spice_measurement): class power_measure(spice_measurement):
"""Generates a spice measurement for the average power between two time points.""" """Generates a spice measurement for the average power between two time points."""
@ -128,8 +139,8 @@ class power_measure(spice_measurement):
def set_meas_constants(self, power_type): def set_meas_constants(self, power_type):
"""Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)""" """Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)"""
#Not needed for power simulation # Not needed for power simulation
self.power_type = power_type #Expected to be "RISE"/"FALL" self.power_type = power_type # Expected to be "RISE"/"FALL"
def get_measure_values(self, t_initial, t_final, port=None): def get_measure_values(self, t_initial, t_final, port=None):
"""Constructs inputs to stimulus measurement function. Variant values are inputs here.""" """Constructs inputs to stimulus measurement function. Variant values are inputs here."""
@ -138,7 +149,8 @@ class power_measure(spice_measurement):
meas_name = "{}{}".format(self.name, port) meas_name = "{}{}".format(self.name, port)
else: else:
meas_name = self.name meas_name = self.name
return (meas_name,t_initial,t_final) return (meas_name, t_initial, t_final)
class voltage_when_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.""" """Generates a spice measurement to measure the voltage of a signal based on the voltage of another."""
@ -161,7 +173,7 @@ class voltage_when_measure(spice_measurement):
"""Constructs inputs to stimulus measurement function. Variant values are inputs here.""" """Constructs inputs to stimulus measurement function. Variant values are inputs here."""
self.port_error_check(port) self.port_error_check(port)
if port != None: if port != None:
#For dictionary indexing reasons, the name is formatted differently than the signals # For dictionary indexing reasons, the name is formatted differently than the signals
meas_name = "{}{}".format(self.name, port) meas_name = "{}{}".format(self.name, port)
trig_name = self.trig_name_no_port.format(port) trig_name = self.trig_name_no_port.format(port)
targ_name = self.targ_name_no_port.format(port) targ_name = self.targ_name_no_port.format(port)
@ -169,8 +181,9 @@ class voltage_when_measure(spice_measurement):
meas_name = self.name meas_name = self.name
trig_name = self.trig_name_no_port trig_name = self.trig_name_no_port
targ_name = self.targ_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) return (meas_name, trig_name, targ_name, trig_voltage, self.trig_dir_str, trig_td)
class voltage_at_measure(spice_measurement): class voltage_at_measure(spice_measurement):
"""Generates a spice measurement to measure the voltage at a specific time. """Generates a spice measurement to measure the voltage at a specific time.
@ -191,11 +204,11 @@ class voltage_at_measure(spice_measurement):
"""Constructs inputs to stimulus measurement function. Variant values are inputs here.""" """Constructs inputs to stimulus measurement function. Variant values are inputs here."""
self.port_error_check(port) self.port_error_check(port)
if port != None: if port != None:
#For dictionary indexing reasons, the name is formatted differently than the signals # For dictionary indexing reasons, the name is formatted differently than the signals
meas_name = "{}{}".format(self.name, port) meas_name = "{}{}".format(self.name, port)
targ_name = self.targ_name_no_port.format(port) targ_name = self.targ_name_no_port.format(port)
else: else:
meas_name = self.name 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) return (meas_name, targ_name, time_at)

View File

@ -5,18 +5,16 @@
# (acting for and on behalf of Oklahoma State University) # (acting for and on behalf of Oklahoma State University)
# All rights reserved. # All rights reserved.
# #
import sys,re,shutil
import debug import debug
import tech import tech
import math
from .stimuli import * from .stimuli import *
from .trim_spice import * from .trim_spice import *
from .charutils import * from .charutils import *
import utils
from globals import OPTS from globals import OPTS
from .delay import delay from .delay import delay
from .measurements import * from .measurements import *
class model_check(delay): class model_check(delay):
"""Functions to test for the worst case delay in a target SRAM """Functions to test for the worst case delay in a target SRAM
@ -39,43 +37,44 @@ class model_check(delay):
self.power_name = "total_power" self.power_name = "total_power"
def create_measurement_names(self, port): def create_measurement_names(self, port):
"""Create measurement names. The names themselves currently define the type of measurement""" """
#Create delay measurement names Create measurement names. The names themselves currently define the type of measurement
wl_en_driver_delay_names = ["delay_wl_en_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())] """
wl_driver_delay_names = ["delay_wl_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_driver_stages())] wl_en_driver_delay_names = ["delay_wl_en_dvr_{0}".format(stage) for stage in range(1, self.get_num_wl_en_driver_stages())]
sen_driver_delay_names = ["delay_sen_dvr_{}".format(stage) for stage in range(1,self.get_num_sen_driver_stages())] wl_driver_delay_names = ["delay_wl_dvr_{0}".format(stage) for stage in range(1, self.get_num_wl_driver_stages())]
sen_driver_delay_names = ["delay_sen_dvr_{0}".format(stage) for stage in range(1, self.get_num_sen_driver_stages())]
if self.custom_delaychain: if self.custom_delaychain:
dc_delay_names = ['delay_dc_out_final'] dc_delay_names = ["delay_dc_out_final"]
else: else:
dc_delay_names = ["delay_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)] dc_delay_names = ["delay_delay_chain_stage_{0}".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.wl_delay_meas_names = wl_en_driver_delay_names + ["delay_wl_en", "delay_wl_bar"] + wl_driver_delay_names + ["delay_wl"]
if port not in self.sram.readonly_ports: if port not in self.sram.readonly_ports:
self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in"]+dc_delay_names self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in"] + dc_delay_names
else: else:
self.rbl_delay_meas_names = ["delay_gated_clk_nand"]+dc_delay_names 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"] self.sae_delay_meas_names = ["delay_pre_sen"] + sen_driver_delay_names + ["delay_sen"]
# if self.custom_delaychain: # if self.custom_delaychain:
# self.delay_chain_indices = (len(self.rbl_delay_meas_names), len(self.rbl_delay_meas_names)+1) # self.delay_chain_indices = (len(self.rbl_delay_meas_names), len(self.rbl_delay_meas_names)+1)
# else: # else:
self.delay_chain_indices = (len(self.rbl_delay_meas_names)-len(dc_delay_names), len(self.rbl_delay_meas_names)) self.delay_chain_indices = (len(self.rbl_delay_meas_names) - len(dc_delay_names), len(self.rbl_delay_meas_names))
#Create slew measurement names # Create slew measurement names
wl_en_driver_slew_names = ["slew_wl_en_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())] wl_en_driver_slew_names = ["slew_wl_en_dvr_{0}".format(stage) for stage in range(1, self.get_num_wl_en_driver_stages())]
wl_driver_slew_names = ["slew_wl_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_driver_stages())] wl_driver_slew_names = ["slew_wl_dvr_{0}".format(stage) for stage in range(1, self.get_num_wl_driver_stages())]
sen_driver_slew_names = ["slew_sen_dvr_{}".format(stage) for stage in range(1,self.get_num_sen_driver_stages())] sen_driver_slew_names = ["slew_sen_dvr_{0}".format(stage) for stage in range(1, self.get_num_sen_driver_stages())]
if self.custom_delaychain: if self.custom_delaychain:
dc_slew_names = ['slew_dc_out_final'] dc_slew_names = ["slew_dc_out_final"]
else: else:
dc_slew_names = ["slew_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)] dc_slew_names = ["slew_delay_chain_stage_{0}".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.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"]
if port not in self.sram.readonly_ports: 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 self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar", "slew_gated_clk_nand", "slew_delay_chain_in"] + dc_slew_names
else: else:
self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar"]+dc_slew_names 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.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.bitline_meas_names = ["delay_wl_to_bl", "delay_bl_to_dout"]
self.power_meas_names = ['read0_power'] self.power_meas_names = ["read0_power"]
def create_signal_names(self, port): def create_signal_names(self, port):
"""Creates list of the signal names used in the spice file along the wl and sen paths. """Creates list of the signal names used in the spice file along the wl and sen paths.
@ -83,40 +82,45 @@ class model_check(delay):
replicated here. replicated here.
""" """
delay.create_signal_names(self) 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.Xcontrol{}.Xbuf_wl_en.Zb{}_int".format('{}', stage) for stage in range(1,self.get_num_wl_en_driver_stages())] # Signal names are all hardcoded, need to update to make it work for probe address and different configurations.
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())] wl_en_driver_signals = ["Xsram{1}Xcontrol{{}}.Xbuf_wl_en.Zb{0}_int".format(stage, OPTS.hier_seperator) for stage in range(1, self.get_num_wl_en_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())] wl_driver_signals = ["Xsram{2}Xbank0{2}Xwordline_driver{{}}{2}Xwl_driver_inv{0}{2}Zb{1}_int".format(self.wordline_row, stage, OPTS.hier_seperator) for stage in range(1, self.get_num_wl_driver_stages())]
sen_driver_signals = ["Xsram{1}Xcontrol{{}}{1}Xbuf_s_en{1}Zb{0}_int".format(stage, OPTS.hier_seperator) for stage in range(1, self.get_num_sen_driver_stages())]
if self.custom_delaychain: if self.custom_delaychain:
delay_chain_signal_names = [] delay_chain_signal_names = []
else: else:
delay_chain_signal_names = ["Xsram.Xcontrol{}.Xreplica_bitline.Xdelay_chain.dout_{}".format('{}', stage) for stage in range(1,self.get_num_delay_stages())] delay_chain_signal_names = ["Xsram{1}Xcontrol{{}}{1}Xreplica_bitline{1}Xdelay_chain{1}dout_{0}".format(stage, OPTS.hier_seperator) for stage in range(1, self.get_num_delay_stages())]
if len(self.sram.all_ports) > 1: if len(self.sram.all_ports) > 1:
port_format = '{}' port_format = '{}'
else: else:
port_format = '' port_format = ''
self.wl_signal_names = ["Xsram.Xcontrol{}.gated_clk_bar".format('{}')]+\ self.wl_signal_names = ["Xsram{0}Xcontrol{{}}{0}gated_clk_bar".format(OPTS.hier_seperator)] + \
wl_en_driver_signals+\ wl_en_driver_signals + \
["Xsram.wl_en{}".format('{}'), "Xsram.Xbank0.Xwordline_driver{}.wl_bar_{}".format('{}',self.wordline_row)]+\ ["Xsram{0}wl_en{{}}".format(OPTS.hier_seperator),
wl_driver_signals+\ "Xsram{1}Xbank0{1}Xwordline_driver{{}}{1}wl_bar_{0}".format(self.wordline_row,
["Xsram.Xbank0.wl{}_{}".format(port_format, self.wordline_row)] OPTS.hier_seperator)] + \
pre_delay_chain_names = ["Xsram.Xcontrol{}.gated_clk_bar".format('{}')] wl_driver_signals + \
["Xsram{2}Xbank0{2}wl{0}_{1}".format(port_format,
self.wordline_row,
OPTS.hier_seperator)]
pre_delay_chain_names = ["Xsram.Xcontrol{{}}{0}gated_clk_bar".format(OPTS.hier_seperator)]
if port not in self.sram.readonly_ports: 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('{}')] pre_delay_chain_names+= ["Xsram{0}Xcontrol{{}}{0}Xand2_rbl_in{0}zb_int".format(OPTS.hier_seperator),
"Xsram{0}Xcontrol{{}}{0}rbl_in".format(OPTS.hier_seperator)]
self.rbl_en_signal_names = pre_delay_chain_names+\ self.rbl_en_signal_names = pre_delay_chain_names + \
delay_chain_signal_names+\ delay_chain_signal_names + \
["Xsram.Xcontrol{}.Xreplica_bitline.delayed_en".format('{}')] ["Xsram{0}Xcontrol{{}}{0}Xreplica_bitline{0}delayed_en".format(OPTS.hier_seperator)]
self.sae_signal_names = ["Xsram{0}Xcontrol{{}}{0}Xreplica_bitline{0}bl0_0".format(OPTS.hier_seperator),
"Xsram{0}Xcontrol{{}}{0}pre_s_en".format(OPTS.hier_seperator)] + \
sen_driver_signals + \
["Xsram{0}s_en{{}}".format(OPTS.hier_seperator)]
self.sae_signal_names = ["Xsram.Xcontrol{}.Xreplica_bitline.bl0_0".format('{}'), "Xsram.Xcontrol{}.pre_s_en".format('{}')]+\ self.bl_signal_names = ["Xsram{2}Xbank0{2}wl{0}_{1}".format(port_format, self.wordline_row, OPTS.hier_seperator),
sen_driver_signals+\ "Xsram{2}Xbank0{2}bl{0}_{1}".format(port_format, self.bitline_column, OPTS.hier_seperator),
["Xsram.s_en{}".format('{}')] "{0}{{}}_{1}".format(self.dout_name, self.probe_data)] # Empty values are the port and probe data bit
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(port_format, self.wordline_row),\
"Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column),\
dout_name]
def create_measurement_objects(self): def create_measurement_objects(self):
"""Create the measurements used for read and write ports""" """Create the measurements used for read and write ports"""
@ -124,7 +128,7 @@ class model_check(delay):
self.create_sae_meas_objs() self.create_sae_meas_objs()
self.create_bl_meas_objs() self.create_bl_meas_objs()
self.create_power_meas_objs() self.create_power_meas_objs()
self.all_measures = self.wl_meas_objs+self.sae_meas_objs+self.bl_meas_objs+self.power_meas_objs self.all_measures = self.wl_meas_objs + self.sae_meas_objs + self.bl_meas_objs + self.power_meas_objs
def create_power_meas_objs(self): def create_power_meas_objs(self):
"""Create power measurement object. Only one.""" """Create power measurement object. Only one."""
@ -138,14 +142,14 @@ class model_check(delay):
targ_dir = "FALL" targ_dir = "FALL"
for i in range(1, len(self.wl_signal_names)): for i in range(1, len(self.wl_signal_names)):
self.wl_meas_objs.append(delay_measure(self.wl_delay_meas_names[i-1], self.wl_meas_objs.append(delay_measure(self.wl_delay_meas_names[i - 1],
self.wl_signal_names[i-1], self.wl_signal_names[i - 1],
self.wl_signal_names[i], self.wl_signal_names[i],
trig_dir, trig_dir,
targ_dir, targ_dir,
measure_scale=1e9)) measure_scale=1e9))
self.wl_meas_objs.append(slew_measure(self.wl_slew_meas_names[i-1], self.wl_meas_objs.append(slew_measure(self.wl_slew_meas_names[i - 1],
self.wl_signal_names[i-1], self.wl_signal_names[i - 1],
trig_dir, trig_dir,
measure_scale=1e9)) measure_scale=1e9))
temp_dir = trig_dir temp_dir = trig_dir
@ -155,9 +159,9 @@ class model_check(delay):
def create_bl_meas_objs(self): def create_bl_meas_objs(self):
"""Create the measurements to measure the bitline to dout, static stages""" """Create the measurements to measure the bitline to dout, static stages"""
#Bitline has slightly different measurements, objects appends hardcoded. # Bitline has slightly different measurements, objects appends hardcoded.
self.bl_meas_objs = [] self.bl_meas_objs = []
trig_dir, targ_dir = "RISE", "FALL" #Only check read 0 trig_dir, targ_dir = "RISE", "FALL" # Only check read 0
self.bl_meas_objs.append(delay_measure(self.bitline_meas_names[0], self.bl_meas_objs.append(delay_measure(self.bitline_meas_names[0],
self.bl_signal_names[0], self.bl_signal_names[0],
self.bl_signal_names[-1], self.bl_signal_names[-1],
@ -171,22 +175,22 @@ class model_check(delay):
self.sae_meas_objs = [] self.sae_meas_objs = []
trig_dir = "RISE" trig_dir = "RISE"
targ_dir = "FALL" targ_dir = "FALL"
#Add measurements from gated_clk_bar to RBL # Add measurements from gated_clk_bar to RBL
for i in range(1, len(self.rbl_en_signal_names)): for i in range(1, len(self.rbl_en_signal_names)):
self.sae_meas_objs.append(delay_measure(self.rbl_delay_meas_names[i-1], self.sae_meas_objs.append(delay_measure(self.rbl_delay_meas_names[i - 1],
self.rbl_en_signal_names[i-1], self.rbl_en_signal_names[i - 1],
self.rbl_en_signal_names[i], self.rbl_en_signal_names[i],
trig_dir, trig_dir,
targ_dir, targ_dir,
measure_scale=1e9)) measure_scale=1e9))
self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[i-1], self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[i - 1],
self.rbl_en_signal_names[i-1], self.rbl_en_signal_names[i - 1],
trig_dir, trig_dir,
measure_scale=1e9)) measure_scale=1e9))
temp_dir = trig_dir temp_dir = trig_dir
trig_dir = targ_dir trig_dir = targ_dir
targ_dir = temp_dir targ_dir = temp_dir
if self.custom_delaychain: #Hack for custom delay chains if self.custom_delaychain: # Hack for custom delay chains
self.sae_meas_objs[-2] = delay_measure(self.rbl_delay_meas_names[-1], self.sae_meas_objs[-2] = delay_measure(self.rbl_delay_meas_names[-1],
self.rbl_en_signal_names[-2], self.rbl_en_signal_names[-2],
self.rbl_en_signal_names[-1], self.rbl_en_signal_names[-1],
@ -198,18 +202,18 @@ class model_check(delay):
trig_dir, trig_dir,
measure_scale=1e9)) measure_scale=1e9))
#Add measurements from rbl_out to sae. Trigger directions do not invert from previous stage due to RBL. # Add measurements from rbl_out to sae. Trigger directions do not invert from previous stage due to RBL.
trig_dir = "FALL" trig_dir = "FALL"
targ_dir = "RISE" targ_dir = "RISE"
for i in range(1, len(self.sae_signal_names)): for i in range(1, len(self.sae_signal_names)):
self.sae_meas_objs.append(delay_measure(self.sae_delay_meas_names[i-1], self.sae_meas_objs.append(delay_measure(self.sae_delay_meas_names[i - 1],
self.sae_signal_names[i-1], self.sae_signal_names[i - 1],
self.sae_signal_names[i], self.sae_signal_names[i],
trig_dir, trig_dir,
targ_dir, targ_dir,
measure_scale=1e9)) measure_scale=1e9))
self.sae_meas_objs.append(slew_measure(self.sae_slew_meas_names[i-1], self.sae_meas_objs.append(slew_measure(self.sae_slew_meas_names[i - 1],
self.sae_signal_names[i-1], self.sae_signal_names[i - 1],
trig_dir, trig_dir,
measure_scale=1e9)) measure_scale=1e9))
temp_dir = trig_dir temp_dir = trig_dir
@ -231,16 +235,16 @@ class model_check(delay):
self.sf.write("* {}\n".format(comment)) self.sf.write("* {}\n".format(comment))
for read_port in self.targ_read_ports: for read_port in self.targ_read_ports:
self.write_measures_read_port(read_port) self.write_measures_read_port(read_port)
def get_delay_measure_variants(self, port, measure_obj): def get_delay_measure_variants(self, port, measure_obj):
"""Get the measurement values that can either vary from simulation to simulation (vdd, address) """Get the measurement values that can either vary from simulation to simulation (vdd, address)
or port to port (time delays)""" or port to port (time delays)"""
#Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port # Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port
#Assuming only read 0 for now # Assuming only read 0 for now
debug.info(3,"Power measurement={}".format(measure_obj)) debug.info(3, "Power measurement={}".format(measure_obj))
if (type(measure_obj) is delay_measure or type(measure_obj) is slew_measure): if (type(measure_obj) is delay_measure or type(measure_obj) is slew_measure):
meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]] + self.period/2 meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]] + self.period / 2
return (meas_cycle_delay, meas_cycle_delay, self.vdd_voltage, port) return (meas_cycle_delay, meas_cycle_delay, self.vdd_voltage, port)
elif type(measure_obj) is power_measure: elif type(measure_obj) is power_measure:
return self.get_power_measure_variants(port, measure_obj, "read") return self.get_power_measure_variants(port, measure_obj, "read")
@ -249,9 +253,9 @@ class model_check(delay):
def get_power_measure_variants(self, port, power_obj, operation): def get_power_measure_variants(self, port, power_obj, operation):
"""Get the measurement values that can either vary port to port (time delays)""" """Get the measurement values that can either vary port to port (time delays)"""
#Return value is intended to match the power measure format: t_initial, t_final, port # Return value is intended to match the power measure format: t_initial, t_final, port
t_initial = self.cycle_times[self.measure_cycles[port]["read0"]] t_initial = self.cycle_times[self.measure_cycles[port]["read0"]]
t_final = self.cycle_times[self.measure_cycles[port]["read0"]+1] t_final = self.cycle_times[self.measure_cycles[port]["read0"] + 1]
return (t_initial, t_final, port) return (t_initial, t_final, port)
@ -280,8 +284,8 @@ class model_check(delay):
elif type(measure)is power_measure: elif type(measure)is power_measure:
power_meas_list.append(measure_value) power_meas_list.append(measure_value)
else: else:
debug.error("Measurement object not recognized.",1) debug.error("Measurement object not recognized.", 1)
return delay_meas_list, slew_meas_list,power_meas_list return delay_meas_list, slew_meas_list, power_meas_list
def run_delay_simulation(self): def run_delay_simulation(self):
""" """
@ -290,7 +294,7 @@ class model_check(delay):
works on the trimmed netlist by default, so powers do not works on the trimmed netlist by default, so powers do not
include leakage of all cells. include leakage of all cells.
""" """
#Sanity Check # Sanity Check
debug.check(self.period > 0, "Target simulation period non-positive") debug.check(self.period > 0, "Target simulation period non-positive")
wl_delay_result = [[] for i in self.all_ports] wl_delay_result = [[] for i in self.all_ports]
@ -303,16 +307,16 @@ class model_check(delay):
# Checking from not data_value to data_value # Checking from not data_value to data_value
self.write_delay_stimulus() self.write_delay_stimulus()
self.stim.run_sim() #running sim prodoces spice output file. self.stim.run_sim() # running sim prodoces spice output file.
#Retrieve the results from the output file # Retrieve the results from the output file
for port in self.targ_read_ports: for port in self.targ_read_ports:
#Parse and check the voltage measurements # Parse and check the voltage measurements
wl_delay_result[port], wl_slew_result[port],_ = self.get_measurement_values(self.wl_meas_objs, port) wl_delay_result[port], wl_slew_result[port], _ = self.get_measurement_values(self.wl_meas_objs, port)
sae_delay_result[port], sae_slew_result[port],_ = self.get_measurement_values(self.sae_meas_objs, port) sae_delay_result[port], sae_slew_result[port], _ = self.get_measurement_values(self.sae_meas_objs, port)
bl_delay_result[port], bl_slew_result[port],_ = self.get_measurement_values(self.bl_meas_objs, port) bl_delay_result[port], bl_slew_result[port], _ = self.get_measurement_values(self.bl_meas_objs, port)
_,__,power_result[port] = self.get_measurement_values(self.power_meas_objs, port) _, __, power_result[port] = self.get_measurement_values(self.power_meas_objs, port)
return (True,wl_delay_result, sae_delay_result, wl_slew_result, sae_slew_result, bl_delay_result, bl_slew_result, power_result) return (True, wl_delay_result, sae_delay_result, wl_slew_result, sae_slew_result, bl_delay_result, bl_slew_result, power_result)
def get_model_delays(self, port): def get_model_delays(self, port):
"""Get model delays based on port. Currently assumes single RW port.""" """Get model delays based on port. Currently assumes single RW port."""
@ -345,41 +349,41 @@ class model_check(delay):
def scale_delays(self, delay_list): def scale_delays(self, delay_list):
"""Takes in a list of measured delays and convert it to simple units to easily compare to model values.""" """Takes in a list of measured delays and convert it to simple units to easily compare to model values."""
converted_values = [] converted_values = []
#Calculate average # Calculate average
total = 0 total = 0
for meas_value in delay_list: for meas_value in delay_list:
total+=meas_value total+=meas_value
average = total/len(delay_list) average = total / len(delay_list)
#Convert values # Convert values
for meas_value in delay_list: for meas_value in delay_list:
converted_values.append(meas_value/average) converted_values.append(meas_value / average)
return converted_values return converted_values
def min_max_normalization(self, value_list): def min_max_normalization(self, value_list):
"""Re-scales input values on a range from 0-1 where min(list)=0, max(list)=1""" """Re-scales input values on a range from 0-1 where min(list)=0, max(list)=1"""
scaled_values = [] scaled_values = []
min_max_diff = max(value_list) - min(value_list) min_max_diff = max(value_list) - min(value_list)
average = sum(value_list)/len(value_list) average = sum(value_list) / len(value_list)
for value in value_list: for value in value_list:
scaled_values.append((value-average)/(min_max_diff)) scaled_values.append((value - average) / (min_max_diff))
return scaled_values return scaled_values
def calculate_error_l2_norm(self, list_a, list_b): def calculate_error_l2_norm(self, list_a, list_b):
"""Calculates error between two lists using the l2 norm""" """Calculates error between two lists using the l2 norm"""
error_list = [] error_list = []
for val_a, val_b in zip(list_a, list_b): for val_a, val_b in zip(list_a, list_b):
error_list.append((val_a-val_b)**2) error_list.append((val_a - val_b)**2)
return error_list return error_list
def compare_measured_and_model(self, measured_vals, model_vals): def compare_measured_and_model(self, measured_vals, model_vals):
"""First scales both inputs into similar ranges and then compares the error between both.""" """First scales both inputs into similar ranges and then compares the error between both."""
scaled_meas = self.min_max_normalization(measured_vals) scaled_meas = self.min_max_normalization(measured_vals)
debug.info(1, "Scaled measurements:\n{}".format(scaled_meas)) debug.info(1, "Scaled measurements:\n{0}".format(scaled_meas))
scaled_model = self.min_max_normalization(model_vals) scaled_model = self.min_max_normalization(model_vals)
debug.info(1, "Scaled model:\n{}".format(scaled_model)) debug.info(1, "Scaled model:\n{0}".format(scaled_model))
errors = self.calculate_error_l2_norm(scaled_meas, scaled_model) errors = self.calculate_error_l2_norm(scaled_meas, scaled_model)
debug.info(1, "Errors:\n{}\n".format(errors)) debug.info(1, "Errors:\n{0}\n".format(errors))
def analyze(self, probe_address, probe_data, slews, loads, port): 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.""" """Measures entire delay path along the wordline and sense amp enable and compare it to the model delays."""
@ -391,19 +395,19 @@ class model_check(delay):
self.create_measurement_objects() self.create_measurement_objects()
data_dict = {} data_dict = {}
read_port = self.read_ports[0] #only test the first read port read_port = self.read_ports[0] # only test the first read port
read_port = port read_port = port
self.targ_read_ports = [read_port] self.targ_read_ports = [read_port]
self.targ_write_ports = [self.write_ports[0]] self.targ_write_ports = [self.write_ports[0]]
debug.info(1,"Model test: corner {}".format(self.corner)) debug.info(1, "Model test: corner {0}".format(self.corner))
(success, wl_delays, sae_delays, wl_slews, sae_slews, bl_delays, bl_slews, powers)=self.run_delay_simulation() (success, wl_delays, sae_delays, wl_slews, sae_slews, bl_delays, bl_slews, powers)=self.run_delay_simulation()
debug.check(success, "Model measurements Failed: period={}".format(self.period)) debug.check(success, "Model measurements Failed: period={0}".format(self.period))
debug.info(1,"Measured Wordline delays (ns):\n\t {}".format(wl_delays[read_port])) debug.info(1, "Measured Wordline delays (ns):\n\t {0}".format(wl_delays[read_port]))
debug.info(1,"Measured Wordline slews:\n\t {}".format(wl_slews[read_port])) debug.info(1, "Measured Wordline slews:\n\t {0}".format(wl_slews[read_port]))
debug.info(1,"Measured SAE delays (ns):\n\t {}".format(sae_delays[read_port])) debug.info(1, "Measured SAE delays (ns):\n\t {0}".format(sae_delays[read_port]))
debug.info(1,"Measured SAE slews:\n\t {}".format(sae_slews[read_port])) debug.info(1, "Measured SAE slews:\n\t {0}".format(sae_slews[read_port]))
debug.info(1,"Measured Bitline delays (ns):\n\t {}".format(bl_delays[read_port])) debug.info(1, "Measured Bitline delays (ns):\n\t {0}".format(bl_delays[read_port]))
data_dict[self.wl_meas_name] = wl_delays[read_port] data_dict[self.wl_meas_name] = wl_delays[read_port]
data_dict[self.sae_meas_name] = sae_delays[read_port] data_dict[self.sae_meas_name] = sae_delays[read_port]
@ -412,14 +416,14 @@ class model_check(delay):
data_dict[self.bl_meas_name] = bl_delays[read_port] data_dict[self.bl_meas_name] = bl_delays[read_port]
data_dict[self.power_name] = powers[read_port] data_dict[self.power_name] = powers[read_port]
if OPTS.auto_delay_chain_sizing: #Model is not used in this case if OPTS.auto_delay_chain_sizing: # Model is not used in this case
wl_model_delays, sae_model_delays = self.get_model_delays(read_port) wl_model_delays, sae_model_delays = self.get_model_delays(read_port)
debug.info(1,"Wordline model delays:\n\t {}".format(wl_model_delays)) debug.info(1, "Wordline model delays:\n\t {0}".format(wl_model_delays))
debug.info(1,"SAE model delays:\n\t {}".format(sae_model_delays)) debug.info(1, "SAE model delays:\n\t {0}".format(sae_model_delays))
data_dict[self.wl_model_name] = wl_model_delays data_dict[self.wl_model_name] = wl_model_delays
data_dict[self.sae_model_name] = sae_model_delays data_dict[self.sae_model_name] = sae_model_delays
#Some evaluations of the model and measured values # Some evaluations of the model and measured values
# debug.info(1, "Comparing wordline measurements and model.") # debug.info(1, "Comparing wordline measurements and model.")
# self.compare_measured_and_model(wl_delays[read_port], wl_model_delays) # self.compare_measured_and_model(wl_delays[read_port], wl_model_delays)
# debug.info(1, "Comparing SAE measurements and model") # debug.info(1, "Comparing SAE measurements and model")
@ -430,17 +434,17 @@ class model_check(delay):
def get_all_signal_names(self): def get_all_signal_names(self):
"""Returns all signals names as a dict indexed by hardcoded names. Useful for writing the head of the CSV.""" """Returns all signals names as a dict indexed by hardcoded names. Useful for writing the head of the CSV."""
name_dict = {} name_dict = {}
#Signal names are more descriptive than the measurement names, first value trimmed to match size of measurements names. # Signal names are more descriptive than the measurement names, first value trimmed to match size of measurements names.
name_dict[self.wl_meas_name] = self.wl_signal_names[1:] name_dict[self.wl_meas_name] = self.wl_signal_names[1:]
name_dict[self.sae_meas_name] = self.rbl_en_signal_names[1:]+self.sae_signal_names[1:] name_dict[self.sae_meas_name] = self.rbl_en_signal_names[1:] + self.sae_signal_names[1:]
name_dict[self.wl_slew_name] = self.wl_slew_meas_names name_dict[self.wl_slew_name] = self.wl_slew_meas_names
name_dict[self.sae_slew_name] = self.rbl_slew_meas_names+self.sae_slew_meas_names name_dict[self.sae_slew_name] = self.rbl_slew_meas_names + self.sae_slew_meas_names
name_dict[self.bl_meas_name] = self.bitline_meas_names[0:1] name_dict[self.bl_meas_name] = self.bitline_meas_names[0:1]
name_dict[self.power_name] = self.power_meas_names name_dict[self.power_name] = self.power_meas_names
#name_dict[self.wl_slew_name] = self.wl_slew_meas_names # pname_dict[self.wl_slew_name] = self.wl_slew_meas_names
if OPTS.auto_delay_chain_sizing: if OPTS.auto_delay_chain_sizing:
name_dict[self.wl_model_name] = name_dict["wl_measures"] #model uses same names as measured. name_dict[self.wl_model_name] = name_dict["wl_measures"] # model uses same names as measured.
name_dict[self.sae_model_name] = name_dict["sae_measures"] name_dict[self.sae_model_name] = name_dict["sae_measures"]
return name_dict return name_dict

View File

@ -76,10 +76,10 @@ class setup_hold():
self.stim.write_supply() self.stim.write_supply()
def write_data(self, mode, target_time, correct_value): def write_data(self, mode, target_time, correct_value):
"""Create the data signals for setup/hold analysis. First period is to """
Create the data signals for setup/hold analysis. First period is to
initialize it to the opposite polarity. Second period is used for initialize it to the opposite polarity. Second period is used for
characterization. characterization.
""" """
self.sf.write("\n* Generation of the data and clk signals\n") self.sf.write("\n* Generation of the data and clk signals\n")
if correct_value == 1: if correct_value == 1:
@ -106,8 +106,11 @@ class setup_hold():
setup=0) setup=0)
def write_clock(self): def write_clock(self):
""" Create the clock signal for setup/hold analysis. First period initializes the FF """
while the second is used for characterization.""" Create the clock signal for setup/hold analysis.
First period initializes the FF
while the second is used for characterization.
"""
self.stim.gen_pwl(sig_name="clk", self.stim.gen_pwl(sig_name="clk",
# initial clk edge is right after the 0 time to initialize a flop # initial clk edge is right after the 0 time to initialize a flop
@ -128,16 +131,6 @@ class setup_hold():
else: else:
dout_rise_or_fall = "FALL" dout_rise_or_fall = "FALL"
# in SETUP mode, the input mirrors what the output should be
if mode == "SETUP":
din_rise_or_fall = dout_rise_or_fall
else:
# in HOLD mode, however, the input should be opposite of the output
if correct_value == 1:
din_rise_or_fall = "FALL"
else:
din_rise_or_fall = "RISE"
self.sf.write("\n* Measure statements for pass/fail verification\n") self.sf.write("\n* Measure statements for pass/fail verification\n")
trig_name = "clk" trig_name = "clk"
targ_name = "Q" targ_name = "Q"
@ -153,19 +146,6 @@ class setup_hold():
trig_td=1.9 * self.period, trig_td=1.9 * self.period,
targ_td=1.9 * self.period) targ_td=1.9 * self.period)
targ_name = "D"
# Start triggers right after initialize value is returned to normal
# at one period
self.stim.gen_meas_delay(meas_name="setup_hold_time",
trig_name=trig_name,
targ_name=targ_name,
trig_val=trig_val,
targ_val=targ_val,
trig_dir="RISE",
targ_dir=din_rise_or_fall,
trig_td=1.2 * self.period,
targ_td=1.2 * self.period)
def bidir_search(self, correct_value, mode): def bidir_search(self, correct_value, mode):
""" This will perform a bidirectional search for either setup or hold times. """ This will perform a bidirectional search for either setup or hold times.
It starts with the feasible priod and looks a half period beyond or before it It starts with the feasible priod and looks a half period beyond or before it
@ -189,26 +169,28 @@ class setup_hold():
correct_value=correct_value) correct_value=correct_value)
self.stim.run_sim(self.stim_sp) self.stim.run_sim(self.stim_sp)
ideal_clk_to_q = convert_to_float(parse_spice_list("timing", "clk2q_delay")) ideal_clk_to_q = convert_to_float(parse_spice_list("timing", "clk2q_delay"))
setuphold_time = convert_to_float(parse_spice_list("timing", "setup_hold_time")) # We use a 1/2 speed clock for some reason...
debug.info(2,"*** {0} CHECK: {1} Ideal Clk-to-Q: {2} Setup/Hold: {3}".format(mode, correct_value,ideal_clk_to_q,setuphold_time)) setuphold_time = (feasible_bound - 2 * self.period)
if mode == "SETUP": # SETUP is clk-din, not din-clk
passing_setuphold_time = -1 * setuphold_time
else:
passing_setuphold_time = setuphold_time
debug.info(2, "*** {0} CHECK: {1} Ideal Clk-to-Q: {2} Setup/Hold: {3}".format(mode,
correct_value,
ideal_clk_to_q,
setuphold_time))
if type(ideal_clk_to_q)!=float or type(setuphold_time)!=float: if type(ideal_clk_to_q)!=float:
debug.error("Initial hold time fails for data value feasible bound {0} Clk-to-Q {1} Setup/Hold {2}".format(feasible_bound, debug.error("Initial hold time fails for data value feasible "
ideal_clk_to_q, "bound {0} Clk-to-Q {1} Setup/Hold {2}".format(feasible_bound,
setuphold_time), ideal_clk_to_q,
setuphold_time),
2) 2)
if mode == "SETUP": # SETUP is clk-din, not din-clk
setuphold_time *= -1e9
else:
setuphold_time *= 1e9
passing_setuphold_time = setuphold_time
debug.info(2, "Checked initial {0} time {1}, data at {2}, clock at {3} ".format(mode, debug.info(2, "Checked initial {0} time {1}, data at {2}, clock at {3} ".format(mode,
setuphold_time, setuphold_time,
feasible_bound, feasible_bound,
2 * self.period)) 2 * self.period))
#raw_input("Press Enter to continue...")
while True: while True:
target_time = (feasible_bound + infeasible_bound) / 2 target_time = (feasible_bound + infeasible_bound) / 2
@ -224,15 +206,14 @@ class setup_hold():
self.stim.run_sim(self.stim_sp) self.stim.run_sim(self.stim_sp)
clk_to_q = convert_to_float(parse_spice_list("timing", "clk2q_delay")) clk_to_q = convert_to_float(parse_spice_list("timing", "clk2q_delay"))
setuphold_time = convert_to_float(parse_spice_list("timing", "setup_hold_time")) # We use a 1/2 speed clock for some reason...
if type(clk_to_q) == float and (clk_to_q < 1.1 * ideal_clk_to_q) and type(setuphold_time)==float: setuphold_time = (target_time - 2 * self.period)
if mode == "SETUP": # SETUP is clk-din, not din-clk if mode == "SETUP": # SETUP is clk-din, not din-clk
setuphold_time *= -1e9 passing_setuphold_time = -1 * setuphold_time
else: else:
setuphold_time *= 1e9
debug.info(2, "PASS Clk-to-Q: {0} Setup/Hold: {1}".format(clk_to_q, setuphold_time))
passing_setuphold_time = setuphold_time passing_setuphold_time = setuphold_time
if type(clk_to_q) == float and (clk_to_q < 1.1 * ideal_clk_to_q):
debug.info(2, "PASS Clk-to-Q: {0} Setup/Hold: {1}".format(clk_to_q, setuphold_time))
feasible_bound = target_time feasible_bound = target_time
else: else:
debug.info(2, "FAIL Clk-to-Q: {0} Setup/Hold: {1}".format(clk_to_q, setuphold_time)) debug.info(2, "FAIL Clk-to-Q: {0} Setup/Hold: {1}".format(clk_to_q, setuphold_time))
@ -242,7 +223,6 @@ class setup_hold():
debug.info(3, "CONVERGE {0} vs {1}".format(feasible_bound, infeasible_bound)) debug.info(3, "CONVERGE {0} vs {1}".format(feasible_bound, infeasible_bound))
break break
debug.info(2, "Converged on {0} time {1}.".format(mode, passing_setuphold_time)) debug.info(2, "Converged on {0} time {1}.".format(mode, passing_setuphold_time))
return passing_setuphold_time return passing_setuphold_time

View File

@ -586,7 +586,7 @@ class simulation():
bl_names.append(self.get_alias_in_path(paths, int_net, cell_mod, exclude_set)) bl_names.append(self.get_alias_in_path(paths, int_net, cell_mod, exclude_set))
if OPTS.use_pex and OPTS.pex_exe[0] != "calibre": if OPTS.use_pex and OPTS.pex_exe[0] != "calibre":
for i in range(len(bl_names)): for i in range(len(bl_names)):
bl_names[i] = bl_names[i].split('.')[-1] bl_names[i] = bl_names[i].split(OPTS.hier_seperator)[-1]
return bl_names[0], bl_names[1] return bl_names[0], bl_names[1]
def get_empty_measure_data_dict(self): def get_empty_measure_data_dict(self):

View File

@ -146,7 +146,7 @@ class stimuli():
edge. The first clk_time should be 0 and is the initial time that corresponds edge. The first clk_time should be 0 and is the initial time that corresponds
to the initial value. to the initial value.
""" """
# the initial value is not a clock time
str = "Clock and data value lengths don't match. {0} clock values, {1} data values for {2}" str = "Clock and data value lengths don't match. {0} clock values, {1} data values for {2}"
debug.check(len(clk_times)==len(data_values), debug.check(len(clk_times)==len(data_values),
str.format(len(clk_times), str.format(len(clk_times),
@ -181,7 +181,7 @@ class stimuli():
def gen_meas_delay(self, meas_name, trig_name, targ_name, trig_val, targ_val, trig_dir, targ_dir, trig_td, targ_td): def gen_meas_delay(self, meas_name, trig_name, targ_name, trig_val, targ_val, trig_dir, targ_dir, trig_td, targ_td):
""" Creates the .meas statement for the measurement of delay """ """ Creates the .meas statement for the measurement of delay """
measure_string=".meas tran {0} TRIG v({1}) VAL={2} {3}=1 TD={4}n TARG v({5}) VAL={6} {7}=1 TD={8}n\n\n" measure_string=".meas tran {0} TRIG v({1}) VAL={2} {3}=1 TD={4}n TARG v({5}) VAL={6} {7}=1 TD={8}n\n\n"
self.sf.write(measure_string.format(meas_name, self.sf.write(measure_string.format(meas_name.lower(),
trig_name, trig_name,
trig_val, trig_val,
trig_dir, trig_dir,
@ -194,7 +194,7 @@ class stimuli():
def gen_meas_find_voltage(self, meas_name, trig_name, targ_name, trig_val, trig_dir, trig_td): def gen_meas_find_voltage(self, meas_name, trig_name, targ_name, trig_val, trig_dir, trig_td):
""" Creates the .meas statement for the measurement of delay """ """ Creates the .meas statement for the measurement of delay """
measure_string=".meas tran {0} FIND v({1}) WHEN v({2})={3}v {4}=1 TD={5}n \n\n" measure_string=".meas tran {0} FIND v({1}) WHEN v({2})={3}v {4}=1 TD={5}n \n\n"
self.sf.write(measure_string.format(meas_name, self.sf.write(measure_string.format(meas_name.lower(),
targ_name, targ_name,
trig_name, trig_name,
trig_val, trig_val,
@ -204,7 +204,7 @@ class stimuli():
def gen_meas_find_voltage_at_time(self, meas_name, targ_name, time_at): def gen_meas_find_voltage_at_time(self, meas_name, targ_name, time_at):
""" Creates the .meas statement for voltage at time""" """ Creates the .meas statement for voltage at time"""
measure_string=".meas tran {0} FIND v({1}) AT={2}n \n\n" measure_string=".meas tran {0} FIND v({1}) AT={2}n \n\n"
self.sf.write(measure_string.format(meas_name, self.sf.write(measure_string.format(meas_name.lower(),
targ_name, targ_name,
time_at)) time_at))
@ -215,13 +215,13 @@ class stimuli():
power_exp = "power" power_exp = "power"
else: else:
power_exp = "par('(-1*v(" + str(self.vdd_name) + ")*I(v" + str(self.vdd_name) + "))')" power_exp = "par('(-1*v(" + str(self.vdd_name) + ")*I(v" + str(self.vdd_name) + "))')"
self.sf.write(".meas tran {0} avg {1} from={2}n to={3}n\n\n".format(meas_name, self.sf.write(".meas tran {0} avg {1} from={2}n to={3}n\n\n".format(meas_name.lower(),
power_exp, power_exp,
t_initial, t_initial,
t_final)) t_final))
def gen_meas_value(self, meas_name, dout, t_initial, t_final): def gen_meas_value(self, meas_name, dout, t_initial, t_final):
measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name, dout, t_initial, t_final) measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name.lower(), dout, t_initial, t_final)
self.sf.write(measure_string) self.sf.write(measure_string)
def write_control(self, end_time, runlvl=4): def write_control(self, end_time, runlvl=4):
@ -238,8 +238,8 @@ class stimuli():
reltol = 0.001 # 0.1% reltol = 0.001 # 0.1%
timestep = 10 # ps, was 5ps but ngspice was complaining the timestep was too small in certain tests. timestep = 10 # ps, was 5ps but ngspice was complaining the timestep was too small in certain tests.
self.sf.write(".TEMP {}\n".format(self.temperature))
if OPTS.spice_name == "ngspice": if OPTS.spice_name == "ngspice":
self.sf.write(".TEMP {}\n".format(self.temperature))
# UIC is needed for ngspice to converge # UIC is needed for ngspice to converge
self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep, end_time)) self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep, end_time))
# ngspice sometimes has convergence problems if not using gear method # ngspice sometimes has convergence problems if not using gear method
@ -248,6 +248,7 @@ class stimuli():
# unless you figure out what these are. # unless you figure out what these are.
self.sf.write(".OPTIONS POST=1 RELTOL={0} PROBE method=gear ACCT\n".format(reltol)) self.sf.write(".OPTIONS POST=1 RELTOL={0} PROBE method=gear ACCT\n".format(reltol))
elif OPTS.spice_name == "spectre": elif OPTS.spice_name == "spectre":
self.sf.write(".TEMP {}\n".format(self.temperature))
self.sf.write("simulator lang=spectre\n") self.sf.write("simulator lang=spectre\n")
if OPTS.use_pex: if OPTS.use_pex:
nestlvl = 1 nestlvl = 1
@ -255,8 +256,7 @@ class stimuli():
else: else:
nestlvl = 10 nestlvl = 10
spectre_save = "lvlpub" spectre_save = "lvlpub"
self.sf.write('saveOptions options save={} nestlvl={} pwr=total \n'.format( self.sf.write('saveOptions options save={} nestlvl={} pwr=total \n'.format(spectre_save, nestlvl))
spectre_save, nestlvl))
self.sf.write("simulatorOptions options reltol=1e-3 vabstol=1e-6 iabstol=1e-12 temp={0} try_fast_op=no " self.sf.write("simulatorOptions options reltol=1e-3 vabstol=1e-6 iabstol=1e-12 temp={0} try_fast_op=no "
"rforce=10m maxnotes=10 maxwarns=10 " "rforce=10m maxnotes=10 maxwarns=10 "
" preservenode=all topcheck=fixall " " preservenode=all topcheck=fixall "
@ -265,12 +265,19 @@ class stimuli():
self.sf.write('tran tran step={} stop={}n ic=node write=spectre.dc errpreset=moderate ' self.sf.write('tran tran step={} stop={}n ic=node write=spectre.dc errpreset=moderate '
' annotate=status maxiters=5 \n'.format("5p", end_time)) ' annotate=status maxiters=5 \n'.format("5p", end_time))
self.sf.write("simulator lang=spice\n") self.sf.write("simulator lang=spice\n")
else: elif OPTS.spice_name in ["hspice", "xa"]:
self.sf.write(".TEMP {}\n".format(self.temperature))
self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep, end_time)) self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep, end_time))
self.sf.write(".OPTIONS POST=1 RUNLVL={0} PROBE\n".format(runlvl)) self.sf.write(".OPTIONS POST=1 RUNLVL={0} PROBE\n".format(runlvl))
if OPTS.spice_name == "hspice": # for cadence plots self.sf.write(".OPTIONS PSF=1 \n")
self.sf.write(".OPTIONS PSF=1 \n") self.sf.write(".OPTIONS HIER_DELIM=1 \n")
self.sf.write(".OPTIONS HIER_DELIM=1 \n") elif OPTS.spice_name in ["Xyce", "xyce"]:
self.sf.write(".OPTIONS DEVICE TEMP={}\n".format(self.temperature))
self.sf.write(".OPTIONS MEASURE MEASFAIL=1\n")
self.sf.write(".OPTIONS LINSOL type=klu\n")
self.sf.write(".TRAN {0}p {1}n\n".format(timestep, end_time))
else:
debug.error("Unkown spice simulator {}".format(OPTS.spice_name))
# create plots for all signals # create plots for all signals
if not OPTS.use_pex: # Don't save all for extracted simulations if not OPTS.use_pex: # Don't save all for extracted simulations
@ -278,7 +285,7 @@ class stimuli():
if OPTS.verbose_level>0: if OPTS.verbose_level>0:
if OPTS.spice_name in ["hspice", "xa"]: if OPTS.spice_name in ["hspice", "xa"]:
self.sf.write(".probe V(*)\n") self.sf.write(".probe V(*)\n")
else: elif OPTS.spice_name != "Xyce":
self.sf.write(".plot V(*)\n") self.sf.write(".plot V(*)\n")
else: else:
self.sf.write("*.probe V(*)\n") self.sf.write("*.probe V(*)\n")
@ -312,7 +319,10 @@ class stimuli():
# Adding a commented out supply for simulators where gnd and 0 are not global grounds. # Adding a commented out supply for simulators where gnd and 0 are not global grounds.
self.sf.write("\n*Nodes gnd and 0 are the same global ground node in ngspice/hspice/xa. Otherwise, this source may be needed.\n") self.sf.write("\n*Nodes gnd and 0 are the same global ground node in ngspice/hspice/xa. Otherwise, this source may be needed.\n")
self.sf.write("*V{0} {0} {1} {2}\n".format(self.gnd_name, gnd_node_name, 0.0)) if OPTS.spice_name in ["Xyce", "xyce"]:
self.sf.write("V{0} {0} {1} {2}\n".format(self.gnd_name, gnd_node_name, 0.0))
else:
self.sf.write("*V{0} {0} {1} {2}\n".format(self.gnd_name, gnd_node_name, 0.0))
def run_sim(self, name): def run_sim(self, name):
""" Run hspice in batch mode and output rawfile to parse. """ """ Run hspice in batch mode and output rawfile to parse. """
@ -349,6 +359,19 @@ class stimuli():
temp_stim, temp_stim,
OPTS.openram_temp) OPTS.openram_temp)
valid_retcode=0 valid_retcode=0
elif OPTS.spice_name in ["Xyce", "xyce"]:
if OPTS.num_sim_threads > 1 and OPTS.mpi_name:
mpi_cmd = "{0} -np {1}".format(OPTS.mpi_exe,
OPTS.num_sim_threads)
else:
mpi_cmd = ""
cmd = "{0} {1} -o {3}timing.lis {2}".format(mpi_cmd,
OPTS.spice_exe,
temp_stim,
OPTS.openram_temp)
valid_retcode=0
else: else:
# ngspice 27+ supports threading with "set num_threads=4" in the stimulus file or a .spiceinit # ngspice 27+ supports threading with "set num_threads=4" in the stimulus file or a .spiceinit
# Measurements can't be made with a raw file set in ngspice # Measurements can't be made with a raw file set in ngspice

View File

@ -70,3 +70,7 @@ class nand2_dec(design.design):
""" """
self.add_graph_edges(graph, port_nets) self.add_graph_edges(graph, port_nets)
def is_non_inverting(self):
"""Return input to output polarity for module"""
return False

View File

@ -70,3 +70,7 @@ class nand3_dec(design.design):
""" """
self.add_graph_edges(graph, port_nets) self.add_graph_edges(graph, port_nets)
def is_non_inverting(self):
"""Return input to output polarity for module"""
return False

View File

@ -70,3 +70,7 @@ class nand4_dec(design.design):
""" """
self.add_graph_edges(graph, port_nets) self.add_graph_edges(graph, port_nets)
def is_non_inverting(self):
"""Return input to output polarity for module"""
return False

View File

@ -11,7 +11,7 @@ process_corners = ["TT"]
supply_voltages = [5.0] supply_voltages = [5.0]
temperatures = [25] temperatures = [25]
route_supplies = True route_supplies = "side"
check_lvsdrc = True check_lvsdrc = True
output_name = "sram_{0}rw{1}r{2}w_{3}_{4}_{5}".format(num_rw_ports, output_name = "sram_{0}rw{1}r{2}w_{3}_{4}_{5}".format(num_rw_ports,

View File

@ -0,0 +1,20 @@
"""
Pseudo-dual port (independent read and write ports), 8bit word, 1 kbyte SRAM.
Useful as a byte FIFO between two devices (the reader and the writer).
"""
word_size = 8 # Bits
num_words = 1024
human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8)
# Allow byte writes
write_size = 8 # Bits
# Dual port
num_rw_ports = 0
num_r_ports = 1
num_w_ports = 1
ports_human = '1r1w'
import os
exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read())

View File

@ -0,0 +1,21 @@
"""
Dual port (1 read/write + 1 read only) 1 kbytes SRAM with byte write.
FIXME: What is this useful for?
FIXME: Why would you want byte write on this?
"""
word_size = 32 # Bits
num_words = 256
human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8)
# Allow byte writes
write_size = 8 # Bits
# Dual port
num_rw_ports = 1
num_r_ports = 1
num_w_ports = 0
ports_human = '1rw1r'
import os
exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read())

View File

@ -0,0 +1,21 @@
"""
Dual port (1 read/write + 1 read only) 1 kbytes SRAM with byte write.
FIXME: What is this useful for?
FIXME: Why would you want byte write on this?
"""
word_size = 8 # Bits
num_words = 1024
human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8)
# Allow byte writes
write_size = 8 # Bits
# Dual port
num_rw_ports = 1
num_r_ports = 1
num_w_ports = 0
ports_human = '1rw1r'
import os
exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read())

View File

@ -0,0 +1,19 @@
"""
Single port, 1 kbytes SRAM, with byte write, useful for RISC-V processor main
memory.
"""
word_size = 32 # Bits
num_words = 256
human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8)
# Allow byte writes
write_size = 8 # Bits
# Single port
num_rw_ports = 1
num_r_ports = 0
num_w_ports = 0
ports_human = '1rw'
import os
exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read())

View File

@ -0,0 +1,21 @@
"""
Dual port (1 read/write + 1 read only), 2 kbytes SRAM (with byte write).
FIXME: What is this useful for?
FIXME: Why would you want byte write on this?
"""
word_size = 32 # Bits
num_words = 512
human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8)
# Allow byte writes
write_size = 8 # Bits
# Dual port
num_rw_ports = 1
num_r_ports = 1
num_w_ports = 0
ports_human = '1rw1r'
import os
exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read())

View File

@ -0,0 +1,19 @@
"""
Single port, 2 kbytes SRAM, with byte write, useful for RISC-V processor main
memory.
"""
word_size = 32 # Bits
num_words = 512
human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8)
# Allow byte writes
write_size = 8 # Bits
# Single port
num_rw_ports = 1
num_r_ports = 0
num_w_ports = 0
ports_human = '1rw'
import os
exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read())

View File

@ -0,0 +1,22 @@
"""
Dual port (1 read/write + 1 read only), 4 kbytes SRAM (with byte write).
FIXME: What is this useful for?
FIXME: Why would you want byte write on this?
"""
word_size = 32 # Bits
num_words = 1024
human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8)
# Allow byte writes
write_size = 8 # Bits
# Dual port
num_rw_ports = 1
num_r_ports = 1
num_w_ports = 0
ports_human = '1rw1r'
import os
exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read())

View File

@ -0,0 +1,20 @@
"""
Single port, 4 kbytes SRAM, with byte write, useful for RISC-V processor main
memory.
"""
word_size = 32 # Bits
num_words = 1024
human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8)
# Allow byte writes
write_size = 8 # Bits
# Single port
num_rw_ports = 1
num_r_ports = 0
num_w_ports = 0
ports_human = '1rw'
import os
exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read())

View File

@ -0,0 +1,19 @@
# Include with
# import os
# exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read())
tech_name = "sky130"
nominal_corner_only = True
# Local wordlines have issues with met3 power routing for now
#local_array_size = 16
#route_supplies = False
check_lvsdrc = True
#perimeter_pins = False
#netlist_only = True
#analytical_delay = False
output_name = "{tech_name}_sram_{human_byte_size}_{ports_human}_{word_size}x{num_words}_{write_size}".format(**locals())
output_path = "macro/{output_name}".format(**locals())

View File

@ -422,7 +422,8 @@ class VlsiLayout:
self.structures[self.rootStructureName].texts.append(textToAdd) self.structures[self.rootStructureName].texts.append(textToAdd)
def padText(self, text): def padText(self, text):
if(len(text)%2 == 1): debug.check(len(text) > 0, "Cannot have zero length text string.")
if(len(text) % 2 == 1):
return text + '\x00' return text + '\x00'
else: else:
return text return text
@ -696,7 +697,6 @@ class VlsiLayout:
return max_pins return max_pins
def getAllPinShapes(self, pin_name): def getAllPinShapes(self, pin_name):
""" """
Search for a pin label and return ALL the enclosing rectangles on the same layer Search for a pin label and return ALL the enclosing rectangles on the same layer

View File

@ -22,7 +22,7 @@ import getpass
import subprocess import subprocess
VERSION = "1.1.14" VERSION = "1.1.15"
NAME = "OpenRAM v{}".format(VERSION) NAME = "OpenRAM v{}".format(VERSION)
USAGE = "openram.py [options] <config file>\nUse -h for help.\n" USAGE = "openram.py [options] <config file>\nUse -h for help.\n"
@ -66,7 +66,7 @@ def parse_args():
optparse.make_option("-m", "--sim_threads", optparse.make_option("-m", "--sim_threads",
action="store", action="store",
type="int", type="int",
help="Specify the number of spice simulation threads (default: 2)", help="Specify the number of spice simulation threads (default: 3)",
dest="num_sim_threads"), dest="num_sim_threads"),
optparse.make_option("-v", optparse.make_option("-v",
"--verbose", "--verbose",
@ -329,7 +329,7 @@ def read_config(config_file, is_unit_test=True):
debug.info(1, "Configuration file is " + config_file + ".py") debug.info(1, "Configuration file is " + config_file + ".py")
try: try:
config = importlib.import_module(module_name) config = importlib.import_module(module_name)
except: except ImportError:
debug.error("Unable to read configuration file: {0}".format(config_file), 2) debug.error("Unable to read configuration file: {0}".format(config_file), 2)
OPTS.overridden = {} OPTS.overridden = {}

View File

@ -75,6 +75,11 @@ class bank(design.design):
self.bank_array_ll = self.offset_all_coordinates().scale(-1, -1) self.bank_array_ll = self.offset_all_coordinates().scale(-1, -1)
self.bank_array_ur = self.bitcell_array_inst.ur() self.bank_array_ur = self.bitcell_array_inst.ur()
self.bank_array_ul = self.bitcell_array_inst.ul() self.bank_array_ul = self.bitcell_array_inst.ul()
# These are used for other placements (e.g. address flops)
self.predecoder_top = self.port_address[0].predecoder_height + self.port_address_inst[0].by()
self.predecoder_bottom = self.port_address_inst[0].by()
self.DRC_LVS() self.DRC_LVS()
def add_pins(self): def add_pins(self):
@ -227,7 +232,6 @@ class bank(design.design):
x_offset = self.m2_gap + self.port_address[port].width x_offset = self.m2_gap + self.port_address[port].width
self.port_address_offsets[port] = vector(-x_offset, self.port_address_offsets[port] = vector(-x_offset,
self.main_bitcell_array_bottom) self.main_bitcell_array_bottom)
self.predecoder_height = self.port_address[port].predecoder_height + self.port_address_offsets[port].y
# LOWER LEFT QUADRANT # LOWER LEFT QUADRANT
# Place the col decoder left aligned with wordline driver # Place the col decoder left aligned with wordline driver
@ -1071,7 +1075,7 @@ class bank(design.design):
""" """
Gets the spice name of the target bitcell. Gets the spice name of the target bitcell.
""" """
return self.bitcell_array_inst.mod.get_cell_name(inst_name + '.x' + self.bitcell_array_inst.name, return self.bitcell_array_inst.mod.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bitcell_array_inst.name,
row, row,
col) col)

View File

@ -121,4 +121,4 @@ class bitcell_array(bitcell_base_array):
def get_cell_name(self, inst_name, row, col): def get_cell_name(self, inst_name, row, col):
"""Gets the spice name of the target bitcell.""" """Gets the spice name of the target bitcell."""
return inst_name + '.x' + self.cell_inst[row, col].name, self.cell_inst[row, col] return inst_name + "{}x".format(OPTS.hier_seperator) + self.cell_inst[row, col].name, self.cell_inst[row, col]

View File

@ -346,9 +346,12 @@ class control_logic(design.design):
row += 1 row += 1
self.place_wlen_row(row) self.place_wlen_row(row)
row += 1 row += 1
self.place_delay(row)
control_center_y = self.wl_en_inst.uy() + self.m3_pitch
# Delay chain always gets placed at row 4
self.place_delay(4)
height = self.delay_inst.uy() height = self.delay_inst.uy()
control_center_y = self.delay_inst.by()
# This offset is used for placement of the control logic in the SRAM level. # This offset is used for placement of the control logic in the SRAM level.
self.control_logic_center = vector(self.ctrl_dff_inst.rx(), control_center_y) self.control_logic_center = vector(self.ctrl_dff_inst.rx(), control_center_y)
@ -387,19 +390,22 @@ class control_logic(design.design):
def place_delay(self, row): def place_delay(self, row):
""" Place the replica bitline """ """ Place the replica bitline """
y_off = row * self.and2.height + 2 * self.m1_pitch debug.check(row % 2 == 0, "Must place delay chain at even row for supply alignment.")
# It is flipped on X axis
y_off = row * self.and2.height + self.delay_chain.height
# Add the RBL above the rows # Add the RBL above the rows
# Add to the right of the control rows and routing channel # Add to the right of the control rows and routing channel
offset = vector(self.delay_chain.width, y_off) offset = vector(0, y_off)
self.delay_inst.place(offset, mirror="MY") self.delay_inst.place(offset, mirror="MX")
def route_delay(self): def route_delay(self):
out_pos = self.delay_inst.get_pin("out").bc() out_pos = self.delay_inst.get_pin("out").center()
# Connect to the rail level with the vdd rail # Connect to the rail level with the vdd rail
# Use pen since it is in every type of control logic # Use gated clock since it is in every type of control logic
vdd_ypos = self.p_en_bar_nand_inst.get_pin("vdd").by() vdd_ypos = self.gated_clk_buf_inst.get_pin("vdd").cy() + self.m1_pitch
in_pos = vector(self.input_bus["rbl_bl_delay"].cx(), vdd_ypos) in_pos = vector(self.input_bus["rbl_bl_delay"].cx(), vdd_ypos)
mid1 = vector(out_pos.x, in_pos.y) mid1 = vector(out_pos.x, in_pos.y)
self.add_wire(self.m1_stack, [out_pos, mid1, in_pos]) self.add_wire(self.m1_stack, [out_pos, mid1, in_pos])
@ -676,7 +682,7 @@ class control_logic(design.design):
# Connect the clock rail to the other clock rail # Connect the clock rail to the other clock rail
# by routing in the supply rail track to avoid channel conflicts # by routing in the supply rail track to avoid channel conflicts
in_pos = self.ctrl_dff_inst.get_pin("clk").uc() in_pos = self.ctrl_dff_inst.get_pin("clk").uc()
mid_pos = in_pos + vector(0, self.and2.height) mid_pos = vector(in_pos.x, self.gated_clk_buf_inst.get_pin("vdd").cy() - self.m1_pitch)
rail_pos = vector(self.input_bus["clk_buf"].cx(), mid_pos.y) rail_pos = vector(self.input_bus["clk_buf"].cx(), mid_pos.y)
self.add_wire(self.m1_stack, [in_pos, mid_pos, rail_pos]) self.add_wire(self.m1_stack, [in_pos, mid_pos, rail_pos])
self.add_via_center(layers=self.m1_stack, self.add_via_center(layers=self.m1_stack,
@ -794,3 +800,8 @@ class control_logic(design.design):
to_layer="m2", to_layer="m2",
offset=out_pos) offset=out_pos)
def get_left_pins(self, name):
"""
Return the left side supply pins to connect to a vertical stripe.
"""
return(self.cntrl_dff_inst.get_pins(name) + self.delay_inst.get_pins(name))

View File

@ -31,6 +31,7 @@ class delay_chain(design.design):
# number of inverters including any fanout loads. # number of inverters including any fanout loads.
self.fanout_list = fanout_list self.fanout_list = fanout_list
self.rows = len(self.fanout_list)
self.create_netlist() self.create_netlist()
if not OPTS.netlist_only: if not OPTS.netlist_only:
@ -43,7 +44,7 @@ class delay_chain(design.design):
def create_layout(self): def create_layout(self):
# Each stage is a a row # Each stage is a a row
self.height = len(self.fanout_list) * self.inv.height self.height = self.rows * self.inv.height
# The width is determined by the largest fanout plus the driver # The width is determined by the largest fanout plus the driver
self.width = (max(self.fanout_list) + 1) * self.inv.width self.width = (max(self.fanout_list) + 1) * self.inv.width
@ -62,14 +63,19 @@ class delay_chain(design.design):
self.add_pin("gnd", "GROUND") self.add_pin("gnd", "GROUND")
def add_modules(self): def add_modules(self):
self.inv = factory.create(module_type="pinv")
self.dff = factory.create(module_type="dff_buf")
dff_height = self.dff.height
self.inv = factory.create(module_type="pinv",
height=dff_height)
self.add_mod(self.inv) self.add_mod(self.inv)
def create_inverters(self): def create_inverters(self):
""" Create the inverters and connect them based on the stage list """ """ Create the inverters and connect them based on the stage list """
self.driver_inst_list = [] self.driver_inst_list = []
self.load_inst_map = {} self.load_inst_map = {}
for stage_num, fanout_size in zip(range(len(self.fanout_list)), self.fanout_list): for stage_num, fanout_size in zip(range(self.rows), self.fanout_list):
# Add the inverter # Add the inverter
cur_driver=self.add_inst(name="dinv{}".format(stage_num), cur_driver=self.add_inst(name="dinv{}".format(stage_num),
mod=self.inv) mod=self.inv)
@ -77,7 +83,7 @@ class delay_chain(design.design):
self.driver_inst_list.append(cur_driver) self.driver_inst_list.append(cur_driver)
# Hook up the driver # Hook up the driver
if stage_num + 1 == len(self.fanout_list): if stage_num + 1 == self.rows:
stageout_name = "out" stageout_name = "out"
else: else:
stageout_name = "dout_{}".format(stage_num + 1) stageout_name = "dout_{}".format(stage_num + 1)
@ -101,7 +107,7 @@ class delay_chain(design.design):
def place_inverters(self): def place_inverters(self):
""" Place the inverters and connect them based on the stage list """ """ Place the inverters and connect them based on the stage list """
for stage_num, fanout_size in zip(range(len(self.fanout_list)), self.fanout_list): for stage_num, fanout_size in zip(range(self.rows), self.fanout_list):
if stage_num % 2: if stage_num % 2:
inv_mirror = "MX" inv_mirror = "MX"
inv_offset = vector(0, (stage_num + 1) * self.inv.height) inv_offset = vector(0, (stage_num + 1) * self.inv.height)
@ -185,24 +191,26 @@ class delay_chain(design.design):
def add_layout_pins(self): def add_layout_pins(self):
# input is A pin of first inverter # input is A pin of first inverter
# It gets routed to the left a bit to prevent pin access errors
# due to the output pin when going up to M3
a_pin = self.driver_inst_list[0].get_pin("A") a_pin = self.driver_inst_list[0].get_pin("A")
mid_loc = vector(a_pin.cx() - self.m3_pitch, a_pin.cy())
self.add_via_stack_center(from_layer=a_pin.layer, self.add_via_stack_center(from_layer=a_pin.layer,
to_layer="m2", to_layer="m2",
offset=a_pin.center()) offset=mid_loc)
self.add_layout_pin(text="in", self.add_path(a_pin.layer, [a_pin.center(), mid_loc])
layer="m2",
offset=a_pin.ll().scale(1, 0),
height=a_pin.cy())
# output is A pin of last load inverter self.add_layout_pin_rect_center(text="in",
layer="m2",
offset=mid_loc)
# output is A pin of last load/fanout inverter
last_driver_inst = self.driver_inst_list[-1] last_driver_inst = self.driver_inst_list[-1]
a_pin = self.load_inst_map[last_driver_inst][-1].get_pin("A") a_pin = self.load_inst_map[last_driver_inst][-1].get_pin("A")
self.add_via_stack_center(from_layer=a_pin.layer, self.add_via_stack_center(from_layer=a_pin.layer,
to_layer="m2", to_layer="m1",
offset=a_pin.center()) offset=a_pin.center())
mid_point = vector(a_pin.cx() + 3 * self.m2_width, a_pin.cy()) self.add_layout_pin_rect_center(text="out",
self.add_path("m2", [a_pin.center(), mid_point, mid_point.scale(1, 0)]) layer="m1",
self.add_layout_pin_segment_center(text="out", offset=a_pin.center())
layer="m2",
start=mid_point,
end=mid_point.scale(1, 0))

View File

@ -11,6 +11,7 @@ from sram_factory import factory
from vector import vector from vector import vector
import debug import debug
from numpy import cumsum from numpy import cumsum
from tech import layer_properties as layer_props
class global_bitcell_array(bitcell_base_array.bitcell_base_array): class global_bitcell_array(bitcell_base_array.bitcell_base_array):
@ -223,11 +224,20 @@ class global_bitcell_array(bitcell_base_array.bitcell_base_array):
new_name = "{0}_{1}".format(base_name, col + col_value) new_name = "{0}_{1}".format(base_name, col + col_value)
self.copy_layout_pin(inst, pin_name, new_name) self.copy_layout_pin(inst, pin_name, new_name)
# Add the global word lines
wl_layer = layer_props.global_bitcell_array.wordline_layer
for wl_name in self.local_mods[0].get_inputs(): for wl_name in self.local_mods[0].get_inputs():
for local_inst in self.local_insts:
wl_pin = local_inst.get_pin(wl_name)
self.add_via_stack_center(from_layer=wl_pin.layer,
to_layer=wl_layer,
offset=wl_pin.center())
left_pin = self.local_insts[0].get_pin(wl_name) left_pin = self.local_insts[0].get_pin(wl_name)
right_pin = self.local_insts[-1].get_pin(wl_name) right_pin = self.local_insts[-1].get_pin(wl_name)
self.add_layout_pin_segment_center(text=wl_name, self.add_layout_pin_segment_center(text=wl_name,
layer=left_pin.layer, layer=wl_layer,
start=left_pin.lc(), start=left_pin.lc(),
end=right_pin.rc()) end=right_pin.rc())
@ -320,7 +330,7 @@ class global_bitcell_array(bitcell_base_array.bitcell_base_array):
# We must also translate the global array column number to the local array column number # We must also translate the global array column number to the local array column number
local_col = col - self.col_offsets[i - 1] local_col = col - self.col_offsets[i - 1]
return local_array.get_cell_name(inst_name + '.x' + local_inst.name, row, local_col) return local_array.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + local_inst.name, row, local_col)
def clear_exclude_bits(self): def clear_exclude_bits(self):
""" """

View File

@ -85,7 +85,6 @@ class hierarchical_predecode(design.design):
self.bus_layer = layer_props.hierarchical_predecode.bus_layer self.bus_layer = layer_props.hierarchical_predecode.bus_layer
self.bus_directions = layer_props.hierarchical_predecode.bus_directions self.bus_directions = layer_props.hierarchical_predecode.bus_directions
if self.column_decoder: if self.column_decoder:
# Column decoders may be routed on M2/M3 if there's a write mask # Column decoders may be routed on M2/M3 if there's a write mask
self.bus_pitch = self.m3_pitch self.bus_pitch = self.m3_pitch
@ -119,7 +118,8 @@ class hierarchical_predecode(design.design):
self.input_rails = self.create_vertical_bus(layer=self.bus_layer, self.input_rails = self.create_vertical_bus(layer=self.bus_layer,
offset=offset, offset=offset,
names=input_names, names=input_names,
length=self.height - 2 * self.bus_pitch) length=self.height - 2 * self.bus_pitch,
pitch=self.bus_pitch)
invert_names = ["Abar_{}".format(x) for x in range(self.number_of_inputs)] invert_names = ["Abar_{}".format(x) for x in range(self.number_of_inputs)]
non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)] non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)]
@ -128,7 +128,8 @@ class hierarchical_predecode(design.design):
self.decode_rails = self.create_vertical_bus(layer=self.bus_layer, self.decode_rails = self.create_vertical_bus(layer=self.bus_layer,
offset=offset, offset=offset,
names=decode_names, names=decode_names,
length=self.height - 2 * self.bus_pitch) length=self.height - 2 * self.bus_pitch,
pitch=self.bus_pitch)
def create_input_inverters(self): def create_input_inverters(self):
""" Create the input inverters to invert input signals for the decode stage. """ """ Create the input inverters to invert input signals for the decode stage. """
@ -180,10 +181,12 @@ class hierarchical_predecode(design.design):
mirror=mirror) mirror=mirror)
def route(self): def route(self):
self.route_input_inverters() self.route_input_inverters()
self.route_output_inverters()
self.route_inputs_to_rails() self.route_inputs_to_rails()
self.route_and_to_rails() self.route_input_ands()
self.route_output_and() self.route_output_ands()
self.route_vdd_gnd() self.route_vdd_gnd()
def route_inputs_to_rails(self): def route_inputs_to_rails(self):
@ -215,7 +218,7 @@ class hierarchical_predecode(design.design):
to_layer=self.bus_layer, to_layer=self.bus_layer,
offset=[self.decode_rails[a_pin].cx(), y_offset]) offset=[self.decode_rails[a_pin].cx(), y_offset])
def route_output_and(self): def route_output_ands(self):
""" """
Route all conections of the outputs and gates Route all conections of the outputs and gates
""" """
@ -230,12 +233,40 @@ class hierarchical_predecode(design.design):
def route_input_inverters(self): def route_input_inverters(self):
""" """
Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd] Route all conections of the inverter inputs
"""
for inv_num in range(self.number_of_inputs):
in_pin = "in_{}".format(inv_num)
# route input
pin = self.inv_inst[inv_num].get_pin("A")
inv_in_pos = pin.center()
in_pos = vector(self.input_rails[in_pin].cx(), inv_in_pos.y)
self.add_path(self.input_layer, [in_pos, inv_in_pos])
# Inverter input pin
self.add_via_stack_center(from_layer=pin.layer,
to_layer=self.input_layer,
offset=inv_in_pos)
# Input rail pin position
via=self.add_via_stack_center(from_layer=self.input_layer,
to_layer=self.bus_layer,
offset=in_pos,
directions=self.bus_directions)
# Create the input pin at this location on the rail
self.add_layout_pin_rect_center(text=in_pin,
layer=self.bus_layer,
offset=in_pos,
height=via.mod.second_layer_height,
width=via.mod.second_layer_width)
def route_output_inverters(self):
"""
Route all conections of the inverter outputs
""" """
for inv_num in range(self.number_of_inputs): for inv_num in range(self.number_of_inputs):
out_pin = "Abar_{}".format(inv_num) out_pin = "Abar_{}".format(inv_num)
in_pin = "in_{}".format(inv_num)
inv_out_pin = self.inv_inst[inv_num].get_pin("Z") inv_out_pin = self.inv_inst[inv_num].get_pin("Z")
# add output so that it is just below the vdd or gnd rail # add output so that it is just below the vdd or gnd rail
@ -255,31 +286,11 @@ class hierarchical_predecode(design.design):
offset=rail_pos, offset=rail_pos,
directions=self.bus_directions) directions=self.bus_directions)
# route input def route_input_ands(self):
pin = self.inv_inst[inv_num].get_pin("A") """
inv_in_pos = pin.center() Route the different permutations of the NAND/AND decocer cells.
in_pos = vector(self.input_rails[in_pin].cx(), inv_in_pos.y) """
self.add_path(self.input_layer, [in_pos, inv_in_pos])
self.add_via_stack_center(from_layer=pin.layer,
to_layer=self.input_layer,
offset=inv_in_pos)
via=self.add_via_stack_center(from_layer=self.input_layer,
to_layer=self.bus_layer,
offset=in_pos)
# Create the input pin at this location on the rail
self.add_layout_pin_rect_center(text=in_pin,
layer=self.bus_layer,
offset=in_pos,
height=via.mod.second_layer_height,
width=via.mod.second_layer_width)
# This is a hack to fix via-to-via spacing issues, but it is currently
# causing its own DRC problems.
# if layer_props.hierarchical_predecode.vertical_supply:
# below_rail = vector(self.decode_rails[out_pin].cx(), y_offset - (self.cell_height / 2))
# self.add_path(self.bus_layer, [rail_pos, below_rail], width=self.li_width + self.m1_enclose_mcon * 2)
def route_and_to_rails(self):
# This 2D array defines the connection mapping # This 2D array defines the connection mapping
and_input_line_combination = self.get_and_input_line_combination() and_input_line_combination = self.get_and_input_line_combination()
for k in range(self.number_of_outputs): for k in range(self.number_of_outputs):

View File

@ -10,6 +10,7 @@ from globals import OPTS
from sram_factory import factory from sram_factory import factory
from vector import vector from vector import vector
import debug import debug
from tech import layer_properties as layer_props
class local_bitcell_array(bitcell_base_array.bitcell_base_array): class local_bitcell_array(bitcell_base_array.bitcell_base_array):
@ -190,6 +191,11 @@ class local_bitcell_array(bitcell_base_array.bitcell_base_array):
def route(self): def route(self):
global_wl_layer = layer_props.global_bitcell_array.wordline_layer
global_wl_pitch = getattr(self, "{}_pitch".format(global_wl_layer))
global_wl_pitch_factor = layer_props.global_bitcell_array.wordline_pitch_factor
local_wl_layer = layer_props.local_bitcell_array.wordline_layer
# Route the global wordlines # Route the global wordlines
for port in self.all_ports: for port in self.all_ports:
if port == 0: if port == 0:
@ -204,25 +210,33 @@ class local_bitcell_array(bitcell_base_array.bitcell_base_array):
in_pin = self.wl_insts[port].get_pin(in_pin_name) in_pin = self.wl_insts[port].get_pin(in_pin_name)
y_offset = in_pin.cy() y_offset = in_pin.cy()
if port == 0: if port == 0:
y_offset -= 2 * self.m3_pitch y_offset -= global_wl_pitch_factor * global_wl_pitch
else: else:
y_offset += 2 * self.m3_pitch y_offset += global_wl_pitch_factor * global_wl_pitch
self.add_layout_pin_segment_center(text=wl_name,
layer="m3",
start=vector(self.wl_insts[port].lx(), y_offset),
end=vector(self.wl_insts[port].lx() + self.wl_array.width, y_offset))
mid = vector(in_pin.cx(), y_offset) mid = vector(in_pin.cx(), y_offset)
self.add_path("m2", [in_pin.center(), mid])
self.add_layout_pin_rect_center(text=wl_name,
layer=global_wl_layer,
offset=mid)
self.add_path(local_wl_layer, [in_pin.center(), mid])
# A short jog to the global line
self.add_via_stack_center(from_layer=in_pin.layer, self.add_via_stack_center(from_layer=in_pin.layer,
to_layer="m2", to_layer=local_wl_layer,
offset=in_pin.center()) offset=in_pin.center(),
self.add_via_center(self.m2_stack, min_area=True)
offset=mid) self.add_path(local_wl_layer, [in_pin.center(), mid])
self.add_via_stack_center(from_layer=local_wl_layer,
to_layer=global_wl_layer,
offset=mid,
min_area=True)
# Add the global WL pin
self.add_layout_pin_rect_center(text=wl_name,
layer=global_wl_layer,
offset=mid)
# Route the buffers # Route the buffers
for port in self.all_ports: for port in self.all_ports:
driver_outputs = self.driver_wordline_outputs[port] driver_outputs = self.driver_wordline_outputs[port]
@ -281,7 +295,7 @@ class local_bitcell_array(bitcell_base_array.bitcell_base_array):
def get_cell_name(self, inst_name, row, col): def get_cell_name(self, inst_name, row, col):
"""Gets the spice name of the target bitcell.""" """Gets the spice name of the target bitcell."""
return self.bitcell_array.get_cell_name(inst_name + '.x' + self.bitcell_array_inst.name, row, col) return self.bitcell_array.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bitcell_array_inst.name, row, col)
def clear_exclude_bits(self): def clear_exclude_bits(self):
""" """

View File

@ -114,4 +114,4 @@ class bitcell_array(bitcell_base_array):
def get_cell_name(self, inst_name, row, col): def get_cell_name(self, inst_name, row, col):
"""Gets the spice name of the target bitcell.""" """Gets the spice name of the target bitcell."""
return inst_name + '.x' + self.cell_inst[row, col].name, self.cell_inst[row, col] return inst_name + "{}x".format(OPTS.hier_seperator) + self.cell_inst[row, col].name, self.cell_inst[row, col]

View File

@ -553,7 +553,7 @@ class replica_bitcell_array(bitcell_base_array):
""" """
Gets the spice name of the target bitcell. Gets the spice name of the target bitcell.
""" """
return self.bitcell_array.get_cell_name(inst_name + '.x' + self.bitcell_array_inst.name, row, col) return self.bitcell_array.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bitcell_array_inst.name, row, col)
def clear_exclude_bits(self): def clear_exclude_bits(self):
""" """

View File

@ -117,9 +117,10 @@ class write_mask_and_array(design.design):
for i in range(self.num_wmasks): for i in range(self.num_wmasks):
# Route the A pin over to the left so that it doesn't conflict with the sense # Route the A pin over to the left so that it doesn't conflict with the sense
# amp output which is usually in the center # amp output which is usually in the center
a_pin = self.and2_insts[i].get_pin("A") inst = self.and2_insts[i]
a_pin = inst.get_pin("A")
a_pos = a_pin.center() a_pos = a_pin.center()
in_pos = vector(self.and2_insts[i].lx(), in_pos = vector(inst.lx(),
a_pos.y) a_pos.y)
self.add_via_stack_center(from_layer=a_pin.layer, self.add_via_stack_center(from_layer=a_pin.layer,
to_layer="m2", to_layer="m2",
@ -130,21 +131,31 @@ class write_mask_and_array(design.design):
self.add_path(a_pin.layer, [in_pos, a_pos]) self.add_path(a_pin.layer, [in_pos, a_pos])
# Copy remaining layout pins # Copy remaining layout pins
self.copy_layout_pin(self.and2_insts[i], "Z", "wmask_out_{0}".format(i)) self.copy_layout_pin(inst, "Z", "wmask_out_{0}".format(i))
# Add via connections to metal3 for AND array's B pin # Add via connections to metal3 for AND array's B pin
en_pin = self.and2_insts[i].get_pin("B") en_pin = inst.get_pin("B")
en_pos = en_pin.center() en_pos = en_pin.center()
self.add_via_stack_center(from_layer=en_pin.layer, self.add_via_stack_center(from_layer=en_pin.layer,
to_layer="m3", to_layer="m3",
offset=en_pos) offset=en_pos)
# Add connection to the supply
for supply_name in ["gnd", "vdd"]:
supply_pin = inst.get_pin(supply_name)
self.add_via_stack_center(from_layer=supply_pin.layer,
to_layer="m1",
offset=supply_pin.center())
for supply in ["gnd", "vdd"]: for supply in ["gnd", "vdd"]:
supply_pin = self.and2_insts[0].get_pin(supply) supply_pin = self.and2_insts[0].get_pin(supply)
supply_pin_yoffset = supply_pin.cy() supply_pin_yoffset = supply_pin.cy()
left_loc = vector(0, supply_pin_yoffset) left_loc = vector(0, supply_pin_yoffset)
right_loc = vector(self.width, supply_pin_yoffset) right_loc = vector(self.width, supply_pin_yoffset)
self.add_path(supply_pin.layer, [left_loc, right_loc]) self.add_path("m1", [left_loc, right_loc])
self.copy_power_pin(supply_pin, loc=left_loc) for loc in [left_loc, right_loc]:
self.copy_power_pin(supply_pin, loc=right_loc) self.add_via_stack_center(from_layer=supply_pin.layer,
to_layer="m1",
offset=loc)
self.copy_power_pin(supply_pin, loc=loc)

View File

@ -6,9 +6,9 @@
# All rights reserved. # All rights reserved.
# #
import optparse import optparse
import getpass
import os import os
class options(optparse.Values): class options(optparse.Values):
""" """
Class for holding all of the OpenRAM options. All Class for holding all of the OpenRAM options. All
@ -137,7 +137,10 @@ class options(optparse.Values):
# Number of threads to use # Number of threads to use
num_threads = 1 num_threads = 1
# Number of threads to use in ngspice/hspice # Number of threads to use in ngspice/hspice
num_sim_threads = 2 num_sim_threads = 3
# Some tools (e.g. Xyce) use other separators like ":"
hier_seperator = "."
# Should we print out the banner at startup # Should we print out the banner at startup
print_banner = True print_banner = True
@ -155,6 +158,9 @@ class options(optparse.Values):
# Route the input/output pins to the perimeter # Route the input/output pins to the perimeter
perimeter_pins = True perimeter_pins = True
# Detailed or abstract LEF view
detailed_lef = False
keep_temp = False keep_temp = False

View File

@ -122,34 +122,44 @@ class grid:
self.set_target(n) self.set_target(n)
# self.set_blocked(n, False) # self.set_blocked(n, False)
def add_perimeter_target(self, side="all"): def get_perimeter_list(self, side="left", layers=[0, 1], width=1, margin=0, offset=0):
debug.info(3, "Adding perimeter target") """
Side specifies which side.
Layer specifies horizontal (0) or vertical (1)
Width specifies how wide the perimter "stripe" should be.
"""
perimeter_list = [] perimeter_list = []
# Add the left/right columns # Add the left/right columns
if side=="all" or side=="left": if side=="all" or side=="left":
x = self.ll.x for x in range(self.ll.x + offset, self.ll.x + width + offset, 1):
for y in range(self.ll.y, self.ur.y, 1): for y in range(self.ll.y + margin, self.ur.y - margin, 1):
perimeter_list.append(vector3d(x, y, 0)) for layer in layers:
perimeter_list.append(vector3d(x, y, 1)) perimeter_list.append(vector3d(x, y, layer))
if side=="all" or side=="right": if side=="all" or side=="right":
x = self.ur.x for x in range(self.ur.x - width - offset, self.ur.x - offset, 1):
for y in range(self.ll.y, self.ur.y, 1): for y in range(self.ll.y + margin, self.ur.y - margin, 1):
perimeter_list.append(vector3d(x, y, 0)) for layer in layers:
perimeter_list.append(vector3d(x, y, 1)) perimeter_list.append(vector3d(x, y, layer))
if side=="all" or side=="bottom": if side=="all" or side=="bottom":
y = self.ll.y for y in range(self.ll.y + offset, self.ll.y + width + offset, 1):
for x in range(self.ll.x, self.ur.x, 1): for x in range(self.ll.x + margin, self.ur.x - margin, 1):
perimeter_list.append(vector3d(x, y, 0)) for layer in layers:
perimeter_list.append(vector3d(x, y, 1)) perimeter_list.append(vector3d(x, y, layer))
if side=="all" or side=="top": if side=="all" or side=="top":
y = self.ur.y for y in range(self.ur.y - width - offset, self.ur.y - offset, 1):
for x in range(self.ll.x, self.ur.x, 1): for x in range(self.ll.x + margin, self.ur.x - margin, 1):
perimeter_list.append(vector3d(x, y, 0)) for layer in layers:
perimeter_list.append(vector3d(x, y, 1)) perimeter_list.append(vector3d(x, y, layer))
return perimeter_list
def add_perimeter_target(self, side="all", layers=[0, 1]):
debug.info(3, "Adding perimeter target")
perimeter_list = self.get_perimeter_list(side, layers)
self.set_target(perimeter_list) self.set_target(perimeter_list)

View File

@ -155,6 +155,10 @@ class pin_group:
# Now simplify the enclosure list # Now simplify the enclosure list
new_pin_list = self.remove_redundant_shapes(pin_list) new_pin_list = self.remove_redundant_shapes(pin_list)
# Now add the right name
for pin in new_pin_list:
pin.name = self.name
debug.check(len(new_pin_list) > 0, debug.check(len(new_pin_list) > 0,
"Did not find any enclosures.") "Did not find any enclosures.")

View File

@ -28,7 +28,7 @@ class router(router_tech):
route on a given layer. This is limited to two layer routes. route on a given layer. This is limited to two layer routes.
It populates blockages on a grid class. It populates blockages on a grid class.
""" """
def __init__(self, layers, design, gds_filename=None, bbox=None, margin=0, route_track_width=1): def __init__(self, layers, design, bbox=None, margin=0, route_track_width=1):
""" """
This will instantiate a copy of the gds file or the module at (0,0) and This will instantiate a copy of the gds file or the module at (0,0) and
route on top of this. The blockages from the gds/module will be route on top of this. The blockages from the gds/module will be
@ -39,19 +39,7 @@ class router(router_tech):
self.cell = design self.cell = design
# If didn't specify a gds blockage file, write it out to read the gds self.gds_filename = OPTS.openram_temp + "temp.gds"
# This isn't efficient, but easy for now
# start_time = datetime.now()
if not gds_filename:
gds_filename = OPTS.openram_temp+"temp.gds"
self.cell.gds_write(gds_filename)
# Load the gds file and read in all the shapes
self.layout = gdsMill.VlsiLayout(units=GDS["unit"])
self.reader = gdsMill.Gds2reader(self.layout)
self.reader.loadFromFile(gds_filename)
self.top_name = self.layout.rootStructureName
# print_time("GDS read",datetime.now(), start_time)
# The pin data structures # The pin data structures
# A map of pin names to a set of pin_layout structures # A map of pin names to a set of pin_layout structures
@ -91,6 +79,16 @@ class router(router_tech):
""" """
Initialize the ll,ur values with the paramter or using the layout boundary. Initialize the ll,ur values with the paramter or using the layout boundary.
""" """
# If didn't specify a gds blockage file, write it out to read the gds
# This isn't efficient, but easy for now
# Load the gds file and read in all the shapes
self.cell.gds_write(self.gds_filename)
self.layout = gdsMill.VlsiLayout(units=GDS["unit"])
self.reader = gdsMill.Gds2reader(self.layout)
self.reader.loadFromFile(self.gds_filename)
self.top_name = self.layout.rootStructureName
if not bbox: if not bbox:
# The boundary will determine the limits to the size # The boundary will determine the limits to the size
# of the routing grid # of the routing grid
@ -178,6 +176,17 @@ class router(router_tech):
""" """
Find the pins and blockages in the design Find the pins and blockages in the design
""" """
# If didn't specify a gds blockage file, write it out to read the gds
# This isn't efficient, but easy for now
# Load the gds file and read in all the shapes
self.cell.gds_write(self.gds_filename)
self.layout = gdsMill.VlsiLayout(units=GDS["unit"])
self.reader = gdsMill.Gds2reader(self.layout)
self.reader.loadFromFile(self.gds_filename)
self.top_name = self.layout.rootStructureName
# print_time("GDS read",datetime.now(), start_time)
# This finds the pin shapes and sorts them into "groups" that # This finds the pin shapes and sorts them into "groups" that
# are connected. This must come before the blockages, so we # are connected. This must come before the blockages, so we
# can not count the pins themselves # can not count the pins themselves
@ -881,6 +890,26 @@ class router(router_tech):
# Clearing the blockage of this pin requires the inflated pins # Clearing the blockage of this pin requires the inflated pins
self.clear_blockages(pin_name) self.clear_blockages(pin_name)
def add_side_supply_pin(self, name, side="left", width=2):
"""
Adds a supply pin to the perimeter and resizes the bounding box.
"""
pg = pin_group(name, [], self)
if name == "vdd":
offset = width
else:
offset = 0
pg.grids = set(self.rg.get_perimeter_list(side=side,
width=width,
margin=self.margin,
offset=offset,
layers=[1]))
pg.enclosures = pg.compute_enclosures()
pg.pins = set(pg.enclosures)
self.cell.pin_map[name].update(pg.pins)
self.pin_groups[name].append(pg)
def add_perimeter_target(self, side="all"): def add_perimeter_target(self, side="all"):
""" """
This will mark all the cells on the perimeter of the original layout as a target. This will mark all the cells on the perimeter of the original layout as a target.
@ -1219,7 +1248,7 @@ class router(router_tech):
""" Return the lowest, leftest pin group """ """ Return the lowest, leftest pin group """
keep_pin = None keep_pin = None
for index,pg in enumerate(self.pin_groups[pin_name]): for index, pg in enumerate(self.pin_groups[pin_name]):
for pin in pg.enclosures: for pin in pg.enclosures:
if not keep_pin: if not keep_pin:
keep_pin = pin keep_pin = pin

View File

@ -123,7 +123,7 @@ class router_tech:
min_wire_width = drc("minwidth_{0}".format(layer_name), 0, math.inf) min_wire_width = drc("minwidth_{0}".format(layer_name), 0, math.inf)
min_width = drc("minwidth_{0}".format(layer_name), self.route_track_width * min_wire_width, math.inf) min_width = self.route_track_width * drc("minwidth_{0}".format(layer_name), self.route_track_width * min_wire_width, math.inf)
min_spacing = drc(str(layer_name)+"_to_"+str(layer_name), self.route_track_width * min_wire_width, math.inf) min_spacing = drc(str(layer_name)+"_to_"+str(layer_name), self.route_track_width * min_wire_width, math.inf)
return (min_width, min_spacing) return (min_width, min_spacing)

View File

@ -17,7 +17,7 @@ class signal_escape_router(router):
A router that routes signals to perimeter and makes pins. A router that routes signals to perimeter and makes pins.
""" """
def __init__(self, layers, design, bbox=None, margin=0, gds_filename=None): def __init__(self, layers, design, bbox=None, margin=0):
""" """
This will route on layers in design. It will get the blockages from This will route on layers in design. It will get the blockages from
either the gds file name or the design itself (by saving to a gds file). either the gds file name or the design itself (by saving to a gds file).
@ -25,7 +25,6 @@ class signal_escape_router(router):
router.__init__(self, router.__init__(self,
layers=layers, layers=layers,
design=design, design=design,
gds_filename=gds_filename,
bbox=bbox, bbox=bbox,
margin=margin) margin=margin)

View File

@ -15,12 +15,12 @@ class signal_router(router):
route on a given layer. This is limited to two layer routes. route on a given layer. This is limited to two layer routes.
""" """
def __init__(self, layers, design, gds_filename=None, bbox=None): def __init__(self, layers, design, bbox=None):
""" """
This will route on layers in design. It will get the blockages from This will route on layers in design. It will get the blockages from
either the gds file name or the design itself (by saving to a gds file). either the gds file name or the design itself (by saving to a gds file).
""" """
router.__init__(self, layers, design, gds_filename, bbox) router.__init__(self, layers, design, bbox)
def route(self, src, dest, detour_scale=5): def route(self, src, dest, detour_scale=5):
""" """

View File

@ -21,7 +21,7 @@ class supply_grid_router(router):
routes a grid to connect the supply on the two layers. routes a grid to connect the supply on the two layers.
""" """
def __init__(self, layers, design, gds_filename=None, bbox=None): def __init__(self, layers, design, margin=0, bbox=None):
""" """
This will route on layers in design. It will get the blockages from This will route on layers in design. It will get the blockages from
either the gds file name or the design itself (by saving to a gds file). either the gds file name or the design itself (by saving to a gds file).
@ -29,9 +29,9 @@ class supply_grid_router(router):
start_time = datetime.now() start_time = datetime.now()
# Power rail width in minimum wire widths # Power rail width in minimum wire widths
self.route_track_width = 2 self.route_track_width = 1
router.__init__(self, layers, design, gds_filename, bbox, self.route_track_width) router.__init__(self, layers, design, bbox=bbox, margin=margin, route_track_width=self.route_track_width)
# The list of supply rails (grid sets) that may be routed # The list of supply rails (grid sets) that may be routed
self.supply_rails = {} self.supply_rails = {}
@ -357,7 +357,8 @@ class supply_grid_router(router):
# This is inefficient since it is non-incremental, but it was # This is inefficient since it is non-incremental, but it was
# easier to debug. # easier to debug.
self.prepare_blockages(pin_name) self.prepare_blockages()
self.clear_blockages(self.vdd_name)
# Add the single component of the pin as the source # Add the single component of the pin as the source
# which unmarks it as a blockage too # which unmarks it as a blockage too
@ -369,7 +370,7 @@ class supply_grid_router(router):
# Actually run the A* router # Actually run the A* router
if not self.run_router(detour_scale=5): if not self.run_router(detour_scale=5):
self.write_debug_gds("debug_route.gds", False) self.write_debug_gds("debug_route.gds")
# if index==3 and pin_name=="vdd": # if index==3 and pin_name=="vdd":
# self.write_debug_gds("route.gds",False) # self.write_debug_gds("route.gds",False)

View File

@ -21,7 +21,7 @@ class supply_tree_router(router):
routes a grid to connect the supply on the two layers. routes a grid to connect the supply on the two layers.
""" """
def __init__(self, layers, design, gds_filename=None, bbox=None): def __init__(self, layers, design, bbox=None, side_pin=None):
""" """
This will route on layers in design. It will get the blockages from This will route on layers in design. It will get the blockages from
either the gds file name or the design itself (by saving to a gds file). either the gds file name or the design itself (by saving to a gds file).
@ -31,11 +31,19 @@ class supply_tree_router(router):
# for prettier routes. # for prettier routes.
self.route_track_width = 1 self.route_track_width = 1
router.__init__(self, layers, design, gds_filename, bbox, self.route_track_width) # The pin escape router already made the bounding box big enough,
# so we can use the regular bbox here.
self.side_pin = side_pin
router.__init__(self,
layers,
design,
bbox=bbox,
route_track_width=self.route_track_width)
def route(self, vdd_name="vdd", gnd_name="gnd"): def route(self, vdd_name="vdd", gnd_name="gnd"):
""" """
Route the two nets in a single layer) Route the two nets in a single layer.
Setting pin stripe will make a power rail on the left side.
""" """
debug.info(1, "Running supply router on {0} and {1}...".format(vdd_name, gnd_name)) debug.info(1, "Running supply router on {0} and {1}...".format(vdd_name, gnd_name))
self.vdd_name = vdd_name self.vdd_name = vdd_name
@ -50,11 +58,17 @@ class supply_tree_router(router):
# but this is simplest for now. # but this is simplest for now.
self.create_routing_grid(signal_grid) self.create_routing_grid(signal_grid)
# Get the pin shapes
start_time = datetime.now() start_time = datetime.now()
# Get the pin shapes
self.find_pins_and_blockages([self.vdd_name, self.gnd_name]) self.find_pins_and_blockages([self.vdd_name, self.gnd_name])
print_time("Finding pins and blockages", datetime.now(), start_time, 3) print_time("Finding pins and blockages", datetime.now(), start_time, 3)
# Add side pins if enabled
if self.side_pin:
self.add_side_supply_pin(self.vdd_name)
self.add_side_supply_pin(self.gnd_name)
# Route the supply pins to the supply rails # Route the supply pins to the supply rails
# Route vdd first since we want it to be shorter # Route vdd first since we want it to be shorter
start_time = datetime.now() start_time = datetime.now()
@ -87,16 +101,21 @@ class supply_tree_router(router):
pin_size = len(self.pin_groups[pin_name]) pin_size = len(self.pin_groups[pin_name])
adj_matrix = [[0] * pin_size for i in range(pin_size)] adj_matrix = [[0] * pin_size for i in range(pin_size)]
for index1,pg1 in enumerate(self.pin_groups[pin_name]): for index1, pg1 in enumerate(self.pin_groups[pin_name]):
for index2,pg2 in enumerate(self.pin_groups[pin_name]): for index2, pg2 in enumerate(self.pin_groups[pin_name]):
if index1>=index2: if index1>=index2:
continue continue
dist = int(grid_utils.distance_set(list(pg1.grids)[0], pg2.grids)) dist = int(grid_utils.distance_set(list(pg1.grids)[0], pg2.grids))
adj_matrix[index1][index2] = dist adj_matrix[index1][index2] = dist
# Find MST # Find MST
debug.info(2, "Finding MinimumSpanning Tree") debug.info(2, "Finding Minimum Spanning Tree")
X = csr_matrix(adj_matrix) X = csr_matrix(adj_matrix)
from scipy.sparse import save_npz
#print("Saving {}.npz".format(self.cell.name))
#save_npz("{}.npz".format(self.cell.name), X)
#exit(1)
Tcsr = minimum_spanning_tree(X) Tcsr = minimum_spanning_tree(X)
mst = Tcsr.toarray().astype(int) mst = Tcsr.toarray().astype(int)
connections = [] connections = []

View File

@ -120,8 +120,9 @@ class sram_1bank(sram_base):
port = 0 port = 0
# The row address bits are placed above the control logic aligned on the right. # The row address bits are placed above the control logic aligned on the right.
x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width
# It is above the control logic but below the top of the bitcell array # It is above the control logic and the predecoder array
y_offset = max(self.control_logic_insts[port].uy(), self.bank.predecoder_height) y_offset = max(self.control_logic_insts[port].uy(), self.bank.predecoder_top)
self.row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_pos[port] = vector(x_offset, y_offset)
self.row_addr_dff_insts[port].place(self.row_addr_pos[port]) self.row_addr_dff_insts[port].place(self.row_addr_pos[port])
@ -130,7 +131,7 @@ class sram_1bank(sram_base):
# The row address bits are placed above the control logic aligned on the left. # The row address bits are placed above the control logic aligned on the left.
x_offset = self.control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width x_offset = self.control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width
# If it can be placed above the predecoder and below the control logic, do it # If it can be placed above the predecoder and below the control logic, do it
y_offset = self.bank.bank_array_ll.y y_offset = self.bank.predecoder_bottom
self.row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_pos[port] = vector(x_offset, y_offset)
self.row_addr_dff_insts[port].place(self.row_addr_pos[port], mirror="XY") self.row_addr_dff_insts[port].place(self.row_addr_pos[port], mirror="XY")
@ -419,11 +420,11 @@ class sram_1bank(sram_base):
if len(route_map) > 0: if len(route_map) > 0:
# The write masks will have blockages on M1 # The write masks will have blockages on M1
if self.num_wmasks > 0 and port in self.write_ports: # if self.num_wmasks > 0 and port in self.write_ports:
layer_stack = self.m3_stack # layer_stack = self.m3_stack
else: # else:
layer_stack = self.m1_stack # layer_stack = self.m1_stack
layer_stack = self.m3_stack
if port == 0: if port == 0:
offset = vector(self.control_logic_insts[port].rx() + self.dff.width, offset = vector(self.control_logic_insts[port].rx() + self.dff.width,
- self.data_bus_size[port] + 2 * self.m3_pitch) - self.data_bus_size[port] + 2 * self.m3_pitch)
@ -527,13 +528,13 @@ class sram_1bank(sram_base):
# Only input (besides pins) is the replica bitline # Only input (besides pins) is the replica bitline
src_pin = self.control_logic_insts[port].get_pin("rbl_bl") src_pin = self.control_logic_insts[port].get_pin("rbl_bl")
dest_pin = self.bank_inst.get_pin("rbl_bl_{0}_{0}".format(port)) dest_pin = self.bank_inst.get_pin("rbl_bl_{0}_{0}".format(port))
self.add_wire(self.m2_stack[::-1], self.add_wire(self.m3_stack,
[src_pin.center(), vector(src_pin.cx(), dest_pin.cy()), dest_pin.rc()]) [src_pin.center(), vector(src_pin.cx(), dest_pin.cy()), dest_pin.rc()])
self.add_via_stack_center(from_layer=src_pin.layer, self.add_via_stack_center(from_layer=src_pin.layer,
to_layer="m2", to_layer="m4",
offset=src_pin.center()) offset=src_pin.center())
self.add_via_stack_center(from_layer=dest_pin.layer, self.add_via_stack_center(from_layer=dest_pin.layer,
to_layer="m2", to_layer="m3",
offset=dest_pin.center()) offset=dest_pin.center())
def route_row_addr_dff(self): def route_row_addr_dff(self):
@ -612,7 +613,7 @@ class sram_1bank(sram_base):
# Sanity check in case it was forgotten # Sanity check in case it was forgotten
if inst_name.find("x") != 0: if inst_name.find("x") != 0:
inst_name = "x" + inst_name inst_name = "x" + inst_name
return self.bank_inst.mod.get_cell_name(inst_name + ".x" + self.bank_inst.name, row, col) return self.bank_inst.mod.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bank_inst.name, row, col)
def get_bank_num(self, inst_name, row, col): def get_bank_num(self, inst_name, row, col):
return 0 return 0

View File

@ -226,10 +226,6 @@ class sram_base(design, verilog, lef):
for inst in self.insts: for inst in self.insts:
self.copy_power_pins(inst, pin_name) self.copy_power_pins(inst, pin_name)
if not OPTS.route_supplies:
# Do not route the power supply (leave as must-connect pins)
return
try: try:
from tech import power_grid from tech import power_grid
grid_stack = power_grid grid_stack = power_grid
@ -238,38 +234,99 @@ class sram_base(design, verilog, lef):
# Route a M3/M4 grid # Route a M3/M4 grid
grid_stack = self.m3_stack grid_stack = self.m3_stack
if OPTS.route_supplies == "grid": # lowest_coord = self.find_lowest_coords()
# highest_coord = self.find_highest_coords()
# # Add two rails to the side
# if OPTS.route_supplies == "side":
# supply_pins = {}
# # Find the lowest leftest pin for vdd and gnd
# for (pin_name, pin_index) in [("vdd", 0), ("gnd", 1)]:
# pin_width = 8 * getattr(self, "{}_width".format(grid_stack[2]))
# pin_space = 2 * getattr(self, "{}_space".format(grid_stack[2]))
# supply_pitch = pin_width + pin_space
# # Add side power rails on left from bottom to top
# # These have a temporary name and will be connected later.
# # They are here to reserve space now and ensure other pins go beyond
# # their perimeter.
# supply_height = highest_coord.y - lowest_coord.y
# supply_pins[pin_name] = self.add_layout_pin(text=pin_name,
# layer=grid_stack[2],
# offset=lowest_coord + vector(pin_index * supply_pitch, 0),
# width=pin_width,
# height=supply_height)
if not OPTS.route_supplies:
# Do not route the power supply (leave as must-connect pins)
return
elif OPTS.route_supplies == "grid":
from supply_grid_router import supply_grid_router as router from supply_grid_router import supply_grid_router as router
elif OPTS.route_supplies: else:
from supply_tree_router import supply_tree_router as router from supply_tree_router import supply_tree_router as router
rtr=router(grid_stack, self) rtr=router(grid_stack, self, side_pin=(OPTS.route_supplies == "side"))
rtr.route() rtr.route()
# Find the lowest leftest pin for vdd and gnd if OPTS.route_supplies == "side":
for pin_name in ["vdd", "gnd"]: # Find the lowest leftest pin for vdd and gnd
# Copy the pin shape(s) to rectangles for pin_name in ["vdd", "gnd"]:
for pin in self.get_pins(pin_name): # Copy the pin shape(s) to rectangles
for pin in self.get_pins(pin_name):
self.add_rect(pin.layer,
pin.ll(),
pin.width(),
pin.height())
# Remove the pin shape(s)
self.remove_layout_pin(pin_name)
# Get the lowest, leftest pin
pin = rtr.get_ll_pin(pin_name)
self.add_layout_pin(pin_name,
pin.layer,
pin.ll(),
pin.width(),
pin.height())
elif OPTS.route_supplies:
# Update these as we may have routed outside the region (perimeter pins)
lowest_coord = self.find_lowest_coords()
# Find the lowest leftest pin for vdd and gnd
for pin_name in ["vdd", "gnd"]:
# Copy the pin shape(s) to rectangles
for pin in self.get_pins(pin_name):
self.add_rect(pin.layer,
pin.ll(),
pin.width(),
pin.height())
# Remove the pin shape(s)
self.remove_layout_pin(pin_name)
# Get the lowest, leftest pin
pin = rtr.get_ll_pin(pin_name)
pin_width = 2 * getattr(self, "{}_width".format(pin.layer))
# Add it as an IO pin to the perimeter
route_width = pin.rx() - lowest_coord.x
pin_offset = vector(lowest_coord.x, pin.by())
self.add_rect(pin.layer, self.add_rect(pin.layer,
pin.ll(), pin_offset,
pin.width(), route_width,
pin.height()) pin.height())
# Remove the pin shape(s) self.add_layout_pin(pin_name,
self.remove_layout_pin(pin_name) pin.layer,
pin_offset,
# Get the lowest, leftest pin pin_width,
pin = rtr.get_ll_pin(pin_name) pin.height())
else:
# Add it as an IO pin to the perimeter # Grid is left with many top level pins
lowest_coord = self.find_lowest_coords() pass
pin_width = pin.rx() - lowest_coord.x
pin_offset = vector(lowest_coord.x, pin.by())
self.add_layout_pin(pin_name,
pin.layer,
pin_offset,
pin_width,
pin.height())
def route_escape_pins(self): def route_escape_pins(self):
""" """
@ -314,7 +371,7 @@ class sram_base(design, verilog, lef):
from signal_escape_router import signal_escape_router as router from signal_escape_router import signal_escape_router as router
rtr=router(layers=self.m3_stack, rtr=router(layers=self.m3_stack,
design=self, design=self,
margin=4 * self.m3_pitch) margin=8 * self.m3_pitch)
rtr.escape_route(pins_to_route) rtr.escape_route(pins_to_route)
def compute_bus_sizes(self): def compute_bus_sizes(self):

View File

@ -0,0 +1,102 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import unittest
from testutils import *
import sys, os
sys.path.append(os.getenv("OPENRAM_HOME"))
import globals
from globals import OPTS
from sram_factory import factory
import debug
class timing_sram_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
OPTS.spice_name="xyce"
OPTS.analytical_delay = False
OPTS.netlist_only = True
# This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload
import characterizer
reload(characterizer)
from characterizer import delay
from sram_config import sram_config
c = sram_config(word_size=4,
num_words=16,
num_banks=1)
c.words_per_row=1
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)
tempspice = OPTS.openram_temp + "temp.sp"
s.sp_write(tempspice)
probe_address = "1" * s.s.addr_size
probe_data = s.s.word_size - 1
debug.info(1, "Probe address {0} probe data bit {1}".format(probe_address, probe_data))
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
d = delay(s.s, tempspice, corner)
import tech
loads = [tech.spice["dff_in_cap"]*4]
slews = [tech.spice["rise_time"]*2]
data, port_data = d.analyze(probe_address, probe_data, slews, loads)
# Combine info about port into all data
data.update(port_data[0])
if OPTS.tech_name == "freepdk45":
golden_data = {'delay_hl': [0.24042560000000002],
'delay_lh': [0.24042560000000002],
'disabled_read0_power': [0.8981647999999998],
'disabled_read1_power': [0.9101543999999998],
'disabled_write0_power': [0.9270382999999998],
'disabled_write1_power': [0.9482969999999998],
'leakage_power': 2.9792199999999998,
'min_period': 0.938,
'read0_power': [1.1107930999999998],
'read1_power': [1.1143252999999997],
'slew_hl': [0.2800772],
'slew_lh': [0.2800772],
'write0_power': [1.1667769],
'write1_power': [1.0986076999999999]}
elif OPTS.tech_name == "scn4m_subm":
golden_data = {'delay_hl': [1.884186],
'delay_lh': [1.884186],
'disabled_read0_power': [20.86336],
'disabled_read1_power': [22.10636],
'disabled_write0_power': [22.62321],
'disabled_write1_power': [23.316010000000002],
'leakage_power': 13.351170000000002,
'min_period': 7.188,
'read0_power': [29.90159],
'read1_power': [30.47858],
'slew_hl': [2.042723],
'slew_lh': [2.042723],
'write0_power': [32.13199],
'write1_power': [28.46703]}
else:
self.assertTrue(False) # other techs fail
# Check if no too many or too few results
self.assertTrue(len(data.keys())==len(golden_data.keys()))
self.assertTrue(self.check_golden_data(data,golden_data,0.25))
globals.end_openram()
# run the test from the command line
if __name__ == "__main__":
(OPTS, args) = globals.parse_args()
del sys.argv[1:]
header(__file__, OPTS.tech_name)
unittest.main(testRunner=debugTestRunner())

View File

@ -0,0 +1,67 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import unittest
from testutils import *
import sys, os
sys.path.append(os.getenv("OPENRAM_HOME"))
import globals
from globals import OPTS
class timing_setup_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
OPTS.spice_name="Xyce"
OPTS.analytical_delay = False
OPTS.netlist_only = True
# This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload
import characterizer
reload(characterizer)
from characterizer import setup_hold
import tech
slews = [tech.spice["rise_time"]*2]
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
sh = setup_hold(corner)
data = sh.analyze(slews,slews)
if OPTS.tech_name == "freepdk45":
golden_data = {'hold_times_HL': [-0.0158691],
'hold_times_LH': [-0.0158691],
'setup_times_HL': [0.026855499999999997],
'setup_times_LH': [0.032959]}
elif OPTS.tech_name == "scn4m_subm":
golden_data = {'hold_times_HL': [-0.0805664],
'hold_times_LH': [-0.11718749999999999],
'setup_times_HL': [0.16357419999999998],
'setup_times_LH': [0.1757812]}
elif OPTS.tech_name == "sky130":
golden_data = {'hold_times_HL': [-0.05615234],
'hold_times_LH': [-0.03173828],
'setup_times_HL': [0.078125],
'setup_times_LH': [0.1025391]}
else:
self.assertTrue(False) # other techs fail
# Check if no too many or too few results
self.assertTrue(len(data.keys())==len(golden_data.keys()))
self.assertTrue(self.check_golden_data(data,golden_data,0.25))
globals.end_openram()
# run the test from the command line
if __name__ == "__main__":
(OPTS, args) = globals.parse_args()
del sys.argv[1:]
header(__file__, OPTS.tech_name)
unittest.main(testRunner=debugTestRunner())

View File

@ -3,6 +3,10 @@
// Word size: 2 // Word size: 2
module sram_2_16_1_freepdk45( module sram_2_16_1_freepdk45(
`ifdef USE_POWER_PINS
vdd,
gnd,
`endif
// Port 0: RW // Port 0: RW
clk0,csb0,web0,addr0,din0,dout0 clk0,csb0,web0,addr0,din0,dout0
); );
@ -15,6 +19,10 @@ module sram_2_16_1_freepdk45(
parameter VERBOSE = 1 ; //Set to 0 to only display warnings parameter VERBOSE = 1 ; //Set to 0 to only display warnings
parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary
`ifdef USE_POWER_PINS
inout vdd;
inout gnd;
`endif
input clk0; // clock input clk0; // clock
input csb0; // active low chip select input csb0; // active low chip select
input web0; // active low write control input web0; // active low write control

View File

@ -3,6 +3,10 @@
// Word size: 2 // Word size: 2
module sram_2_16_1_scn4m_subm( module sram_2_16_1_scn4m_subm(
`ifdef USE_POWER_PINS
vdd,
gnd,
`endif
// Port 0: RW // Port 0: RW
clk0,csb0,web0,addr0,din0,dout0 clk0,csb0,web0,addr0,din0,dout0
); );
@ -15,6 +19,10 @@ module sram_2_16_1_scn4m_subm(
parameter VERBOSE = 1 ; //Set to 0 to only display warnings parameter VERBOSE = 1 ; //Set to 0 to only display warnings
parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary
`ifdef USE_POWER_PINS
inout vdd;
inout gnd;
`endif
input clk0; // clock input clk0; // clock
input csb0; // active low chip select input csb0; // active low chip select
input web0; // active low write control input web0; // active low write control

View File

@ -71,9 +71,12 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa
global OPTS global OPTS
# Copy .magicrc file into the output directory # Copy .magicrc file into the output directory
magic_file = OPTS.openram_tech + "tech/.magicrc" magic_file = os.environ.get('OPENRAM_MAGICRC', None)
if not magic_file:
magic_file = OPTS.openram_tech + "tech/.magicrc"
if os.path.exists(magic_file): if os.path.exists(magic_file):
shutil.copy(magic_file, output_path) shutil.copy(magic_file, output_path + "/.magicrc")
else: else:
debug.warning("Could not locate .magicrc file: {}".format(magic_file)) debug.warning("Could not locate .magicrc file: {}".format(magic_file))

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,236 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="605.40302"
height="165.26472"
viewBox="0 0 605.40301 165.26473"
id="svg2"
version="1.1"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
sodipodi:docname="OpenRAM_logo_yellow_transparent.svg"
inkscape:export-filename="/home/mrg/openram/images/OpenRAM_logo_yellow_transparent.png"
inkscape:export-xdpi="150"
inkscape:export-ydpi="150">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2634"
inkscape:window-height="1333"
id="namedview3551"
showgrid="false"
inkscape:zoom="2.8592481"
inkscape:cx="232.75305"
inkscape:cy="82.632362"
inkscape:window-x="208"
inkscape:window-y="96"
inkscape:window-maximized="0"
inkscape:current-layer="svg2"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<defs
id="defs4" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<text
id="text3336"
y="113.18625"
x="173.17645"
style="font-style:normal;font-weight:normal;font-size:22.5px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#003c6c;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
id="tspan4140"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:80px;font-family:Futura;-inkscape-font-specification:'Futura Bold';fill:#003c6c;fill-opacity:1"
y="113.18625"
x="173.17645">OpenRAM</tspan></text>
<rect
y="39.336884"
x="36.581963"
height="87.463478"
width="89.973282"
id="rect4144"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:8.3992691;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path8080"
d="m 53.960768,13.421563 v 21.96078"
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 81.568617,13.421563 v 21.96078"
id="path8112"
inkscape:connector-curvature="0" />
<path
id="path8118"
d="m 109.17646,13.421563 v 21.96078"
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 53.960768,151.84317 V 129.88239"
id="path8137"
inkscape:connector-curvature="0" />
<path
id="path8143"
d="M 81.568617,151.84317 V 129.88239"
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 109.17646,151.84317 V 129.88239"
id="path8149"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 151.21568,56.715693 H 129.2549"
id="path8157"
inkscape:connector-curvature="0" />
<path
id="path8163"
d="M 151.21568,84.323543 H 129.2549"
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 151.21568,111.93138 H 129.2549"
id="path8169"
inkscape:connector-curvature="0" />
<path
id="path8177"
d="m 13.421548,56.715693 h 21.96078"
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 13.421548,84.323543 h 21.96078"
id="path8183"
inkscape:connector-curvature="0" />
<path
id="path8189"
d="m 13.421548,111.93138 h 21.96078"
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<ellipse
ry="5.9607844"
rx="5.647059"
cy="7.4607844"
cx="53.96077"
id="path8078"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="ellipse8110"
cx="81.568619"
cy="7.4607844"
rx="5.647059"
ry="5.9607844" />
<ellipse
ry="5.9607844"
rx="5.647059"
cy="7.4607844"
cx="109.17645"
id="ellipse8116"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
transform="scale(1,-1)"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="ellipse8135"
cx="53.96077"
cy="-157.80394"
rx="5.647059"
ry="5.9607844" />
<ellipse
transform="scale(1,-1)"
ry="5.9607844"
rx="5.647059"
cy="-157.80394"
cx="81.568619"
id="ellipse8141"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
transform="scale(1,-1)"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="ellipse8147"
cx="109.17645"
cy="-157.80394"
rx="5.647059"
ry="5.9607844" />
<ellipse
transform="rotate(90)"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="ellipse8155"
cx="56.715668"
cy="-157.17644"
rx="5.647059"
ry="5.9607844" />
<ellipse
transform="rotate(90)"
ry="5.9607844"
rx="5.647059"
cy="-157.17644"
cx="84.323517"
id="ellipse8161"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
transform="rotate(90)"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="ellipse8167"
cx="111.93137"
cy="-157.17644"
rx="5.647059"
ry="5.9607844" />
<ellipse
transform="matrix(0,1,1,0,0,0)"
ry="5.9607844"
rx="5.647059"
cy="7.4607844"
cx="56.715668"
id="ellipse8175"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
transform="matrix(0,1,1,0,0,0)"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="ellipse8181"
cx="84.323517"
cy="7.4607844"
rx="5.647059"
ry="5.9607844" />
<ellipse
transform="matrix(0,1,1,0,0,0)"
ry="5.9607844"
rx="5.647059"
cy="7.4607844"
cx="111.93137"
id="ellipse8187"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:#fdc700;fill-opacity:1;stroke:none;stroke-width:4.87900019;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect3553"
width="88.83606"
height="86.559578"
x="37.165073"
y="39.245544"
ry="4.3552427" />
</svg>

After

Width:  |  Height:  |  Size: 9.7 KiB