mirror of https://github.com/VLSIDA/OpenRAM.git
Fixed conflicts in delay and elmore modules on merge with dev.
This commit is contained in:
commit
2f4f8ca912
|
|
@ -1 +1,2 @@
|
||||||
*.sp linguist-vendored
|
*.sp linguist-language=Spice
|
||||||
|
*.tf linquist-language=Tech File
|
||||||
|
|
|
||||||
13
README.md
13
README.md
|
|
@ -1,12 +1,9 @@
|
||||||
|

|
||||||
# OpenRAM
|
# OpenRAM
|
||||||
|
|
||||||
[](https://www.python.org/)
|
[](https://www.python.org/)
|
||||||
[](./LICENSE)
|
[](./LICENSE)
|
||||||
|
[](https://github.com/VLSIDA/OpenRAM/archive/stable.zip)
|
||||||
Master:
|
|
||||||
[](https://github.com/VLSIDA/OpenRAM/archive/master.zip)
|
|
||||||
|
|
||||||
Dev:
|
|
||||||
[](https://github.com/VLSIDA/OpenRAM/archive/dev.zip)
|
[](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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 = {}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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))
|
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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 = []
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 |
|
|
@ -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 |
Loading…
Reference in New Issue