Merge branch 'dev' into stable

This commit is contained in:
mrg 2021-07-29 11:42:15 -07:00
commit 0589a35f73
176 changed files with 5971 additions and 5147 deletions

3
.gitattributes vendored
View File

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

View File

@ -29,7 +29,7 @@ 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
+ Various Python packages (pip install -r requirements.txt) + Various Python packages (pip install -r requirements.txt)
@ -214,6 +214,7 @@ If I forgot to add you, please let me know!
[Netgen]: http://opencircuitdesign.com/netgen/ [Netgen]: http://opencircuitdesign.com/netgen/
[Qflow]: http://opencircuitdesign.com/qflow/history.html [Qflow]: http://opencircuitdesign.com/qflow/history.html
[Ngspice]: http://ngspice.sourceforge.net/ [Ngspice]: http://ngspice.sourceforge.net/
[Xyce]: http://xyce.sandia.gov/
[OSUPDK]: https://vlsiarch.ecen.okstate.edu/flow/ [OSUPDK]: https://vlsiarch.ecen.okstate.edu/flow/
[FreePDK45]: https://www.eda.ncsu.edu/wiki/FreePDK45:Contents [FreePDK45]: https://www.eda.ncsu.edu/wiki/FreePDK45:Contents

View File

@ -2,7 +2,7 @@ TECH = scn4m_subm
CUR_DIR = $(shell pwd) CUR_DIR = $(shell pwd)
TEST_DIR = ${CUR_DIR}/tests TEST_DIR = ${CUR_DIR}/tests
MAKEFLAGS += -j 1 #MAKEFLAGS += -j 1
# Library test # Library test
LIBRARY_TESTS = $(shell find ${TEST_DIR} -name 0[1-2]*_test.py) LIBRARY_TESTS = $(shell find ${TEST_DIR} -name 0[1-2]*_test.py)
@ -64,7 +64,43 @@ usage: ${USAGE_TESTS}
$(ALL_TESTS): $(ALL_TESTS):
python3 $@ -t ${TECH} python3 $@ -t ${TECH}
OPENRAM_TECHS = $(subst :, ,$(OPENRAM_TECH))
TECH_DIR := $(word 1, $(foreach dir,$(OPENRAM_TECHS),$(wildcard $(dir)/$(TECH))))
CONFIG_DIR = $(OPENRAM_HOME)/model_configs
MODEL_CONFIGS = $(wildcard $(CONFIG_DIR)/*.py)
SIM_DIR = $(OPENRAM_HOME)/model_data/$(TECH)
CSV_DIR = $(TECH_DIR)/sim_data
# Creates names of technology specific okay files for the configs
STAMPS=$(addprefix $(SIM_DIR)/, $(addsuffix .ok, $(notdir $(basename $(MODEL_CONFIGS)))))
OPTS =
# Characterize and perform DRC/LVS
OPTS += -c
# Do not characterize or perform DRC/LVS
OPTS += -n
# Verbosity
#OPTS += -v
# Spice
OPTS += -s hspice
.PHONY: model
model: $(STAMPS)
mkdir -p $(CSV_DIR)
python3 $(OPENRAM_HOME)/model_data_util.py $(SIM_DIR) $(CSV_DIR)
%.ok:
$(eval bname=$(basename $(notdir $@)))
$(eval config_path=$(CONFIG_DIR)/$(addsuffix .py, $(notdir $(basename $@))))
mkdir -p $(SIM_DIR)/$(bname)
-python3 $(OPENRAM_HOME)/openram.py $(OPTS) -p $(SIM_DIR)/$(bname) -o $(bname) -t $(TECH) $(config_path) 2>&1 > /dev/null
touch $@
clean_model:
rm -f -r $(SIM_DIR)/*.ok
clean: clean:
find . -name \*.pyc -exec rm {} \; find . -name \*.pyc -exec rm {} \;
find . -name \*~ -exec rm {} \; find . -name \*~ -exec rm {} \;

View File

@ -93,9 +93,9 @@ class cell:
# It is assumed it is [nwell, pwell] # It is assumed it is [nwell, pwell]
self._body_bias = body_bias self._body_bias = body_bias
self._port_map['vnb'] = body_bias[0] self._port_map['vnb'] = body_bias[0]
self._port_types['vnb'] = "POWER" self._port_types['vnb'] = "GROUND"
self._port_map['vpb'] = body_bias[1] self._port_map['vpb'] = body_bias[1]
self._port_types['vpb'] = "GROUND" self._port_types['vpb'] = "POWER"
@property @property
def port_types(self): def port_types(self):
@ -176,7 +176,7 @@ class cell_properties():
self.names["col_cap_bitcell_2port"] = "col_cap_cell_2rw" self.names["col_cap_bitcell_2port"] = "col_cap_cell_2rw"
self.names["row_cap_bitcell_1port"] = "row_cap_cell_1rw" self.names["row_cap_bitcell_1port"] = "row_cap_cell_1rw"
self.names["row_cap_bitcell_2port"] = "row_cap_cell_2rw" self.names["row_cap_bitcell_2port"] = "row_cap_cell_2rw"
self.use_strap = False
self._ptx = _ptx(model_is_subckt=False, self._ptx = _ptx(model_is_subckt=False,
bin_spice_models=False) bin_spice_models=False)

View File

@ -45,7 +45,8 @@ class _hierarchical_predecode:
bus_space_factor, bus_space_factor,
input_layer, input_layer,
output_layer, output_layer,
vertical_supply): vertical_supply,
force_horizontal_input_contact):
# hierarchical_predecode # hierarchical_predecode
# bus_layer, bus_directions, bus_pitch, bus_space, input_layer, output_layer, output_layer_pitch # bus_layer, bus_directions, bus_pitch, bus_space, input_layer, output_layer, output_layer_pitch
# m2, pref, m2_pitch, m2_space, m1, m1, m1_pitch # m2, pref, m2_pitch, m2_space, m1, m1, m1_pitch
@ -59,6 +60,7 @@ class _hierarchical_predecode:
self.input_layer = input_layer self.input_layer = input_layer
self.output_layer = output_layer self.output_layer = output_layer
self.vertical_supply = vertical_supply self.vertical_supply = vertical_supply
self.force_horizontal_input_contact = force_horizontal_input_contact
class _column_mux_array: class _column_mux_array:
@ -152,7 +154,8 @@ class layer_properties():
bus_space_factor=1, bus_space_factor=1,
input_layer="m1", input_layer="m1",
output_layer="m1", output_layer="m1",
vertical_supply=False) vertical_supply=False,
force_horizontal_input_contact=False)
self._column_mux_array = _column_mux_array(select_layer="m1", self._column_mux_array = _column_mux_array(select_layer="m1",
select_pitch="m2_pitch", select_pitch="m2_pitch",

View File

@ -48,11 +48,11 @@ class design(hierarchy_design):
self.add_pin_indices(prop.port_indices) self.add_pin_indices(prop.port_indices)
self.add_pin_names(prop.port_map) self.add_pin_names(prop.port_map)
self.add_pin_types(prop.port_types) self.add_pin_types(prop.port_types)
(width, height) = utils.get_libcell_size(self.cell_name, (width, height) = utils.get_libcell_size(self.cell_name,
GDS["unit"], GDS["unit"],
layer[prop.boundary_layer]) layer[prop.boundary_layer])
self.pin_map = utils.get_libcell_pins(self.pins, self.pin_map = utils.get_libcell_pins(self.pins,
self.cell_name, self.cell_name,
GDS["unit"]) GDS["unit"])

View File

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

View File

@ -20,6 +20,10 @@ from globals import OPTS
from vector import vector from vector import vector
from pin_layout import pin_layout from pin_layout import pin_layout
from utils import round_to_grid from utils import round_to_grid
try:
from tech import special_purposes
except ImportError:
special_purposes = {}
class layout(): class layout():
@ -36,12 +40,13 @@ class layout():
# This gets set in both spice and layout so either can be called first. # This gets set in both spice and layout so either can be called first.
self.name = name self.name = name
self.cell_name = cell_name self.cell_name = cell_name
self.gds_file = OPTS.openram_tech + "gds_lib/" + cell_name + ".gds" self.gds_file = OPTS.openram_tech + "gds_lib/" + cell_name + ".gds"
self.width = None self.width = None
self.height = None self.height = None
self.bounding_box = None self.bounding_box = None # The rectangle shape
self.bbox = None # The ll, ur coords
# Holds module/cell layout instances # Holds module/cell layout instances
self.insts = [] self.insts = []
# Set of names to check for duplicates # Set of names to check for duplicates
@ -57,7 +62,7 @@ class layout():
self.visited = [] self.visited = []
# Flag for library cells # Flag for library cells
self.is_library_cell = False self.is_library_cell = False
self.gds_read() self.gds_read()
try: try:
@ -65,7 +70,7 @@ class layout():
self.pwr_grid_layer = power_grid[0] self.pwr_grid_layer = power_grid[0]
except ImportError: except ImportError:
self.pwr_grid_layer = "m3" self.pwr_grid_layer = "m3"
############################################################ ############################################################
# GDS layout # GDS layout
############################################################ ############################################################
@ -117,7 +122,7 @@ class layout():
if len(self.objs) > 0: if len(self.objs) > 0:
lowestx = min(min(obj.lx() for obj in self.objs if obj.name != "label"), lowestx) lowestx = min(min(obj.lx() for obj in self.objs if obj.name != "label"), lowestx)
lowesty = min(min(obj.by() for obj in self.objs if obj.name != "label"), lowesty) lowesty = min(min(obj.by() for obj in self.objs if obj.name != "label"), lowesty)
if len(self.insts) > 0: if len(self.insts) > 0:
lowestx = min(min(inst.lx() for inst in self.insts), lowestx) lowestx = min(min(inst.lx() for inst in self.insts), lowestx)
lowesty = min(min(inst.by() for inst in self.insts), lowesty) lowesty = min(min(inst.by() for inst in self.insts), lowesty)
@ -128,7 +133,7 @@ class layout():
continue continue
lowestx = min(min(pin.lx() for pin in pin_set), lowestx) lowestx = min(min(pin.lx() for pin in pin_set), lowestx)
lowesty = min(min(pin.by() for pin in pin_set), lowesty) lowesty = min(min(pin.by() for pin in pin_set), lowesty)
return vector(lowestx, lowesty) return vector(lowestx, lowesty)
def find_highest_coords(self): def find_highest_coords(self):
@ -137,7 +142,7 @@ class layout():
this layout this layout
""" """
highestx = highesty = -sys.maxsize - 1 highestx = highesty = -sys.maxsize - 1
if len(self.objs) > 0: if len(self.objs) > 0:
highestx = max(max(obj.rx() for obj in self.objs if obj.name != "label"), highestx) highestx = max(max(obj.rx() for obj in self.objs if obj.name != "label"), highestx)
highesty = max(max(obj.uy() for obj in self.objs if obj.name != "label"), highesty) highesty = max(max(obj.uy() for obj in self.objs if obj.name != "label"), highesty)
@ -152,7 +157,7 @@ class layout():
continue continue
highestx = max(max(pin.rx() for pin in pin_set), highestx) highestx = max(max(pin.rx() for pin in pin_set), highestx)
highesty = max(max(pin.uy() for pin in pin_set), highesty) highesty = max(max(pin.uy() for pin in pin_set), highesty)
return vector(highestx, highesty) return vector(highestx, highesty)
def find_highest_layer_coords(self, layer): def find_highest_layer_coords(self, layer):
@ -233,7 +238,7 @@ class layout():
# This is commented out for runtime reasons # This is commented out for runtime reasons
# debug.info(4, "instance list: " + ",".join(x.name for x in self.insts)) # debug.info(4, "instance list: " + ",".join(x.name for x in self.insts))
return self.insts[-1] return self.insts[-1]
def get_inst(self, name): def get_inst(self, name):
""" Retrieve an instance by name """ """ Retrieve an instance by name """
for inst in self.insts: for inst in self.insts:
@ -331,7 +336,7 @@ class layout():
Return the pin or list of pins Return the pin or list of pins
""" """
name = self.get_pin_name(text) name = self.get_pin_name(text)
try: try:
if len(self.pin_map[name]) > 1: if len(self.pin_map[name]) > 1:
debug.error("Should use a pin iterator since more than one pin {}".format(text), -1) debug.error("Should use a pin iterator since more than one pin {}".format(text), -1)
@ -348,7 +353,7 @@ class layout():
Return a pin list (instead of a single pin) Return a pin list (instead of a single pin)
""" """
name = self.get_pin_name(text) name = self.get_pin_name(text)
if name in self.pin_map.keys(): if name in self.pin_map.keys():
return self.pin_map[name] return self.pin_map[name]
else: else:
@ -359,12 +364,12 @@ class layout():
Create a mapping from internal pin names to external pin names. Create a mapping from internal pin names to external pin names.
""" """
self.pin_names = pin_dict self.pin_names = pin_dict
self.original_pin_names = {y: x for (x, y) in self.pin_names.items()} self.original_pin_names = {y: x for (x, y) in self.pin_names.items()}
def get_pin_name(self, text): def get_pin_name(self, text):
""" Return the custom cell pin name """ """ Return the custom cell pin name """
if text in self.pin_names: if text in self.pin_names:
return self.pin_names[text] return self.pin_names[text]
else: else:
@ -372,7 +377,7 @@ class layout():
def get_original_pin_names(self): def get_original_pin_names(self):
""" Return the internal cell pin name """ """ Return the internal cell pin name """
# This uses the hierarchy_spice pins (in order) # This uses the hierarchy_spice pins (in order)
return [self.get_original_pin_name(x) for x in self.pins] return [self.get_original_pin_name(x) for x in self.pins]
@ -382,7 +387,7 @@ class layout():
return self.original_pin_names[text] return self.original_pin_names[text]
else: else:
return text return text
def get_pin_names(self): def get_pin_names(self):
""" """
Return a pin list of all pins Return a pin list of all pins
@ -479,7 +484,7 @@ class layout():
offset=s.ll(), offset=s.ll(),
width=s.width(), width=s.width(),
height=s.height()) height=s.height())
def replace_layout_pin(self, text, pin): def replace_layout_pin(self, text, pin):
""" """
Remove the old pin and replace with a new one Remove the old pin and replace with a new one
@ -494,7 +499,7 @@ class layout():
offset=pin.ll(), offset=pin.ll(),
width=pin.width(), width=pin.width(),
height=pin.height()) height=pin.height())
def add_layout_pin(self, text, layer, offset, width=None, height=None): def add_layout_pin(self, text, layer, offset, width=None, height=None):
""" """
Create a labeled pin Create a labeled pin
@ -776,7 +781,7 @@ class layout():
debug.info(3, "opening {}".format(self.gds_file)) debug.info(3, "opening {}".format(self.gds_file))
self.gds = gdsMill.VlsiLayout(units=GDS["unit"]) self.gds = gdsMill.VlsiLayout(units=GDS["unit"])
reader = gdsMill.Gds2reader(self.gds) reader = gdsMill.Gds2reader(self.gds)
reader.loadFromFile(self.gds_file) reader.loadFromFile(self.gds_file, special_purposes)
else: else:
debug.info(3, "Creating layout structure {}".format(self.name)) debug.info(3, "Creating layout structure {}".format(self.name))
self.gds = gdsMill.VlsiLayout(name=self.name, units=GDS["unit"]) self.gds = gdsMill.VlsiLayout(name=self.name, units=GDS["unit"])
@ -788,7 +793,7 @@ class layout():
debug.info(4, "Printing {}".format(gds_file)) debug.info(4, "Printing {}".format(gds_file))
arrayCellLayout = gdsMill.VlsiLayout(units=GDS["unit"]) arrayCellLayout = gdsMill.VlsiLayout(units=GDS["unit"])
reader = gdsMill.Gds2reader(arrayCellLayout, debugToTerminal=1) reader = gdsMill.Gds2reader(arrayCellLayout, debugToTerminal=1)
reader.loadFromFile(gds_file) reader.loadFromFile(gds_file, special_purposes)
def clear_visited(self): def clear_visited(self):
""" Recursively clear the visited flag """ """ Recursively clear the visited flag """
@ -1125,7 +1130,7 @@ class layout():
# self.add_inst(cr.name, cr) # self.add_inst(cr.name, cr)
# self.connect_inst([]) # self.connect_inst([])
self.add_flat_inst(cr.name, cr) self.add_flat_inst(cr.name, cr)
def create_horizontal_channel_route(self, netlist, offset, layer_stack, directions=None): def create_horizontal_channel_route(self, netlist, offset, layer_stack, directions=None):
""" """
Wrapper to create a horizontal channel route Wrapper to create a horizontal channel route
@ -1137,7 +1142,7 @@ class layout():
# self.add_inst(cr.name, cr) # self.add_inst(cr.name, cr)
# self.connect_inst([]) # self.connect_inst([])
self.add_flat_inst(cr.name, cr) self.add_flat_inst(cr.name, cr)
def add_boundary(self, ll=vector(0, 0), ur=None): def add_boundary(self, ll=vector(0, 0), ur=None):
""" Add boundary for debugging dimensions """ """ Add boundary for debugging dimensions """
if OPTS.netlist_only: if OPTS.netlist_only:
@ -1161,6 +1166,59 @@ class layout():
height=ur.y - ll.y, height=ur.y - ll.y,
width=ur.x - ll.x) width=ur.x - ll.x)
self.bbox = [self.bounding_box.ll(), self.bounding_box.ur()]
def get_bbox(self, side="all", big_margin=0, little_margin=0):
"""
Get the bounding box from the GDS
"""
gds_filename = OPTS.openram_temp + "temp.gds"
# 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.gds_write(gds_filename)
layout = gdsMill.VlsiLayout(units=GDS["unit"])
reader = gdsMill.Gds2reader(layout)
reader.loadFromFile(gds_filename)
top_name = layout.rootStructureName
if not self.bbox:
# The boundary will determine the limits to the size
# of the routing grid
boundary = layout.measureBoundary(top_name)
# These must be un-indexed to get rid of the matrix type
ll = vector(boundary[0][0], boundary[0][1])
ur = vector(boundary[1][0], boundary[1][1])
else:
ll, ur = self.bbox
ll_offset = vector(0, 0)
ur_offset = vector(0, 0)
if side in ["ring", "top", "all"]:
ur_offset += vector(0, big_margin)
else:
ur_offset += vector(0, little_margin)
if side in ["ring", "bottom", "all"]:
ll_offset += vector(0, big_margin)
else:
ll_offset += vector(0, little_margin)
if side in ["ring", "left", "all"]:
ll_offset += vector(big_margin, 0)
else:
ll_offset += vector(little_margin, 0)
if side in ["ring", "right", "all"]:
ur_offset += vector(big_margin, 0)
else:
ur_offset += vector(little_margin, 0)
bbox = (ll - ll_offset, ur + ur_offset)
size = ur - ll
debug.info(1, "Size: {0} x {1} with perimeter big margin {2} little margin {3}".format(size.x,
size.y,
big_margin,
little_margin))
return bbox
def add_enclosure(self, insts, layer="nwell", extend=0, leftx=None, rightx=None, topy=None, boty=None): def add_enclosure(self, insts, layer="nwell", extend=0, leftx=None, rightx=None, topy=None, boty=None):
""" """
Add a layer that surrounds the given instances. Useful Add a layer that surrounds the given instances. Useful
@ -1203,22 +1261,24 @@ class layout():
height=ymax - ymin) height=ymax - ymin)
return rect return rect
def copy_power_pins(self, inst, name, add_vias=True): def copy_power_pins(self, inst, name, add_vias=True, new_name=""):
""" """
This will copy a power pin if it is on the lowest power_grid layer. This will copy a power pin if it is on the lowest power_grid layer.
If it is on M1, it will add a power via too. If it is on M1, it will add a power via too.
""" """
pins = inst.get_pins(name) pins = inst.get_pins(name)
for pin in pins: for pin in pins:
if new_name == "":
new_name = pin.name
if pin.layer == self.pwr_grid_layer: if pin.layer == self.pwr_grid_layer:
self.add_layout_pin(name, self.add_layout_pin(new_name,
pin.layer, pin.layer,
pin.ll(), pin.ll(),
pin.width(), pin.width(),
pin.height()) pin.height())
elif add_vias: elif add_vias:
self.copy_power_pin(pin) self.copy_power_pin(pin, new_name=new_name)
def add_io_pin(self, instance, pin_name, new_name, start_layer=None): def add_io_pin(self, instance, pin_name, new_name, start_layer=None):
""" """
@ -1264,16 +1324,18 @@ class layout():
width=width, width=width,
height=height) height=height)
def copy_power_pin(self, pin, loc=None, directions=None): def copy_power_pin(self, pin, loc=None, directions=None, new_name=""):
""" """
Add a single power pin from the lowest power_grid layer down to M1 (or li) at Add a single power pin from the lowest power_grid layer down to M1 (or li) at
the given center location. The starting layer is specified to determine the given center location. The starting layer is specified to determine
which vias are needed. which vias are needed.
""" """
if new_name == "":
new_name = pin.name
if not loc: if not loc:
loc = pin.center() loc = pin.center()
# Hack for min area # Hack for min area
if OPTS.tech_name == "sky130": if OPTS.tech_name == "sky130":
min_area = drc["minarea_{}".format(self.pwr_grid_layer)] min_area = drc["minarea_{}".format(self.pwr_grid_layer)]
@ -1282,9 +1344,9 @@ class layout():
else: else:
width = None width = None
height = None height = None
if pin.layer == self.pwr_grid_layer: if pin.layer == self.pwr_grid_layer:
self.add_layout_pin_rect_center(text=pin.name, self.add_layout_pin_rect_center(text=new_name,
layer=self.pwr_grid_layer, layer=self.pwr_grid_layer,
offset=loc, offset=loc,
width=width, width=width,
@ -1299,7 +1361,7 @@ class layout():
width = via.width width = via.width
if not height: if not height:
height = via.height height = via.height
self.add_layout_pin_rect_center(text=pin.name, self.add_layout_pin_rect_center(text=new_name,
layer=self.pwr_grid_layer, layer=self.pwr_grid_layer,
offset=loc, offset=loc,
width=width, width=width,
@ -1341,7 +1403,182 @@ class layout():
layer=layer, layer=layer,
offset=peri_pin_loc) offset=peri_pin_loc)
def add_power_ring(self, bbox): def add_dnwell(self, bbox=None, inflate=1):
""" Create a dnwell, along with nwell moat at border. """
if "dnwell" not in techlayer:
return
if not bbox:
bbox = [self.find_lowest_coords(),
self.find_highest_coords()]
# Find the corners
[ll, ur] = bbox
# Possibly inflate the bbox
nwell_offset = vector(2 * self.nwell_width, 2 * self.nwell_width)
ll -= nwell_offset.scale(inflate, inflate)
ur += nwell_offset.scale(inflate, inflate)
# Other corners
ul = vector(ll.x, ur.y)
lr = vector(ur.x, ll.y)
# Add the dnwell
self.add_rect("dnwell",
offset=ll,
height=ur.y - ll.y,
width=ur.x - ll.x)
# Add the moat
self.add_path("nwell", [ll, lr, ur, ul, ll - vector(0, 0.5 * self.nwell_width)])
# Add the taps
layer_stack = self.active_stack
tap_spacing = 2
nwell_offset = vector(self.nwell_width, self.nwell_width)
# Every nth tap is connected to gnd
period = 5
# BOTTOM
count = 0
loc = ll + nwell_offset.scale(tap_spacing, 0)
end_loc = lr - nwell_offset.scale(tap_spacing, 0)
while loc.x < end_loc.x:
self.add_via_center(layers=layer_stack,
offset=loc,
implant_type="n",
well_type="n")
if count % period:
self.add_via_stack_center(from_layer="li",
to_layer="m1",
offset=loc)
else:
self.add_power_pin(name="vdd",
loc=loc,
start_layer="li")
count += 1
loc += nwell_offset.scale(tap_spacing, 0)
# TOP
count = 0
loc = ul + nwell_offset.scale(tap_spacing, 0)
end_loc = ur - nwell_offset.scale(tap_spacing, 0)
while loc.x < end_loc.x:
self.add_via_center(layers=layer_stack,
offset=loc,
implant_type="n",
well_type="n")
if count % period:
self.add_via_stack_center(from_layer="li",
to_layer="m1",
offset=loc)
else:
self.add_power_pin(name="vdd",
loc=loc,
start_layer="li")
count += 1
loc += nwell_offset.scale(tap_spacing, 0)
# LEFT
count = 0
loc = ll + nwell_offset.scale(0, tap_spacing)
end_loc = ul - nwell_offset.scale(0, tap_spacing)
while loc.y < end_loc.y:
self.add_via_center(layers=layer_stack,
offset=loc,
implant_type="n",
well_type="n")
if count % period:
self.add_via_stack_center(from_layer="li",
to_layer="m2",
offset=loc)
else:
self.add_power_pin(name="vdd",
loc=loc,
start_layer="li")
count += 1
loc += nwell_offset.scale(0, tap_spacing)
# RIGHT
count = 0
loc = lr + nwell_offset.scale(0, tap_spacing)
end_loc = ur - nwell_offset.scale(0, tap_spacing)
while loc.y < end_loc.y:
self.add_via_center(layers=layer_stack,
offset=loc,
implant_type="n",
well_type="n")
if count % period:
self.add_via_stack_center(from_layer="li",
to_layer="m2",
offset=loc)
else:
self.add_power_pin(name="vdd",
loc=loc,
start_layer="li")
count += 1
loc += nwell_offset.scale(0, tap_spacing)
# Add the gnd ring
self.add_ring([ll, ur])
def add_ring(self, bbox=None, width_mult=8, offset=0):
"""
Add a ring around the bbox
"""
# Ring size/space/pitch
wire_width = self.m2_width * width_mult
half_width = 0.5 * wire_width
wire_space = self.m2_space
wire_pitch = wire_width + wire_space
# Find the corners
if not bbox:
bbox = [self.find_lowest_coords(),
self.find_highest_coords()]
[ll, ur] = bbox
ul = vector(ll.x, ur.y)
lr = vector(ur.x, ll.y)
ll += vector(-offset * wire_pitch,
-offset * wire_pitch)
lr += vector(offset * wire_pitch,
-offset * wire_pitch)
ur += vector(offset * wire_pitch,
offset * wire_pitch)
ul += vector(-offset * wire_pitch,
offset * wire_pitch)
half_offset = vector(half_width, half_width)
self.add_path("m1", [ll - half_offset.scale(1, 0), lr + half_offset.scale(1, 0)], width=wire_width)
self.add_path("m1", [ul - half_offset.scale(1, 0), ur + half_offset.scale(1, 0)], width=wire_width)
self.add_path("m2", [ll - half_offset.scale(0, 1), ul + half_offset.scale(0, 1)], width=wire_width)
self.add_path("m2", [lr - half_offset.scale(0, 1), ur + half_offset.scale(0, 1)], width=wire_width)
# Find the number of vias for this pitch
supply_vias = 1
from sram_factory import factory
while True:
c = factory.create(module_type="contact",
layer_stack=self.m1_stack,
dimensions=(supply_vias, supply_vias))
if c.second_layer_width < wire_width and c.second_layer_height < wire_width:
supply_vias += 1
else:
supply_vias -= 1
break
via_points = [ll, lr, ur, ul]
for pt in via_points:
self.add_via_center(layers=self.m1_stack,
offset=pt,
size=(supply_vias,
supply_vias))
def add_power_ring(self):
""" """
Create vdd and gnd power rings around an area of the bounding box Create vdd and gnd power rings around an area of the bounding box
argument. Must have a supply_rail_width and supply_rail_pitch argument. Must have a supply_rail_width and supply_rail_pitch
@ -1350,7 +1587,7 @@ class layout():
modules.. modules..
""" """
[ll, ur] = bbox [ll, ur] = self.bbox
supply_rail_spacing = self.supply_rail_pitch - self.supply_rail_width supply_rail_spacing = self.supply_rail_pitch - self.supply_rail_width
height = (ur.y - ll.y) + 3 * self.supply_rail_pitch - supply_rail_spacing height = (ur.y - ll.y) + 3 * self.supply_rail_pitch - supply_rail_spacing

View File

@ -32,7 +32,6 @@ class spice():
# This gets set in both spice and layout so either can be called first. # This gets set in both spice and layout so either can be called first.
self.name = name self.name = name
self.cell_name = cell_name self.cell_name = cell_name
self.sp_file = OPTS.openram_tech + "sp_lib/" + cell_name + ".sp" self.sp_file = OPTS.openram_tech + "sp_lib/" + cell_name + ".sp"
# If we have a separate lvs directory, then all the lvs files # If we have a separate lvs directory, then all the lvs files
@ -570,6 +569,7 @@ class spice():
net = net.lower() net = net.lower()
int_net = self.name_dict[net]['int_net'] int_net = self.name_dict[net]['int_net']
int_mod = self.name_dict[net]['mod'] int_mod = self.name_dict[net]['mod']
if int_mod.is_net_alias(int_net, alias, alias_mod, exclusion_set): if int_mod.is_net_alias(int_net, alias, alias_mod, exclusion_set):
aliases.append(net) aliases.append(net)
return aliases return aliases

View File

@ -69,25 +69,31 @@ 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
# To maintain the indent level easily
self.indent = ""
if OPTS.detailed_lef: if OPTS.detailed_lef:
debug.info(3, "Writing detailed LEF to {0}".format(lef_name)) debug.info(3, "Writing detailed LEF to {0}".format(lef_name))
self.detailed_lef_write(lef_name)
else: else:
debug.info(3, "Writing abstract LEF to {0}".format(lef_name)) debug.info(3, "Writing abstract LEF to {0}".format(lef_name))
# Can possibly use magic lef write to create the LEF self.compute_abstract_blockages()
# if OPTS.drc_exe and OPTS.drc_exe[0] == "magic":
# self.magic_lef_write(lef_name)
# return
self.abstract_lef_write(lef_name)
def abstract_lef_write(self, lef_name):
# To maintain the indent level easily
self.indent = ""
self.lef = open(lef_name, "w") self.lef = open(lef_name, "w")
self.lef_write_header() self.lef_write_header()
for pin_name in self.pins:
self.lef_write_pin(pin_name)
self.lef_write_obstructions(OPTS.detailed_lef)
self.lef_write_footer()
self.lef.close()
def compute_abstract_blockages(self):
# Start with blockages on all layers the size of the block # Start with blockages on all layers the size of the block
# minus the pin escape margin (hard coded to 4 x m3 pitch) # minus the pin escape margin (hard coded to 4 x m3 pitch)
# These are a pin_layout to use their geometric functions # These are a pin_layout to use their geometric functions
@ -104,37 +110,27 @@ class lef:
# For each pin, remove the blockage and add the pin # For each pin, remove the blockage and add the pin
for pin_name in self.pins: for pin_name in self.pins:
pin = self.get_pin(pin_name) pins = self.get_pins(pin_name)
inflated_pin = pin.inflated_pin(multiple=1) for pin in pins:
for blockage in self.blockages[pin.layer]: inflated_pin = pin.inflated_pin(multiple=2)
if blockage.overlaps(inflated_pin): continue_fragmenting = True
intersection_shape = blockage.intersection(inflated_pin) while continue_fragmenting:
# If it is zero area, don't add the pin continue_fragmenting = False
if intersection_shape[0][0]==intersection_shape[1][0] or intersection_shape[0][1]==intersection_shape[1][1]: old_blockages = list(self.blockages[pin.layer])
continue for blockage in old_blockages:
# Remove the old blockage and add the new ones if blockage.overlaps(inflated_pin):
self.blockages[pin.layer].remove(blockage) intersection_shape = blockage.intersection(inflated_pin)
intersection_pin = pin_layout("", intersection_shape, inflated_pin.layer) # If it is zero area, don't split the blockage
new_blockages = blockage.cut(intersection_pin) if intersection_shape[0][0]==intersection_shape[1][0] or intersection_shape[0][1]==intersection_shape[1][1]:
self.blockages[pin.layer].extend(new_blockages) continue
self.lef_write_pin(pin_name) # Remove the old blockage and add the new ones
self.blockages[pin.layer].remove(blockage)
self.lef_write_obstructions(abstracted=True) intersection_pin = pin_layout("", intersection_shape, inflated_pin.layer)
self.lef_write_footer() new_blockages = blockage.cut(intersection_pin)
self.lef.close() self.blockages[pin.layer].extend(new_blockages)
# We split something so make another pass
def detailed_lef_write(self, lef_name): continue_fragmenting = True
# To maintain the indent level easily
self.indent = ""
self.lef = open(lef_name, "w")
self.lef_write_header()
for pin in self.pins:
self.lef_write_pin(pin)
self.lef_write_obstructions()
self.lef_write_footer()
self.lef.close()
def lef_write_header(self): def lef_write_header(self):
""" Header of LEF file """ """ Header of LEF file """
@ -155,8 +151,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):
@ -188,20 +184,20 @@ 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, abstracted=False): 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 += " "
if abstracted: if detailed:
blockages = self.blockages[layer]
for b in blockages:
self.lef_write_shape(b.rect)
else:
blockages = self.get_blockages(layer, True) blockages = self.get_blockages(layer, True)
for b in blockages: for b in blockages:
self.lef_write_shape(b) 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))

View File

@ -45,9 +45,19 @@ class pin_layout:
if self.same_lpp(layer_name_pp, lpp): if self.same_lpp(layer_name_pp, lpp):
self._layer = layer_name self._layer = layer_name
break break
else: else:
debug.error("Layer {} is not a valid routing layer in the tech file.".format(layer_name_pp), -1) try:
from tech import layer_override
from tech import layer_override_name
if layer_override[name]:
self.lpp = layer_override[name]
self.layer = "pwellp"
self._recompute_hash()
return
except:
debug.error("Layer {} is not a valid routing layer in the tech file.".format(layer_name_pp), -1)
self.lpp = layer[self.layer] self.lpp = layer[self.layer]
self._recompute_hash() self._recompute_hash()
@ -396,6 +406,13 @@ class pin_layout:
try: try:
from tech import label_purpose from tech import label_purpose
try:
from tech import layer_override_purpose
if pin_layer_num in layer_override_purpose:
layer_num = layer_override_purpose[pin_layer_num][0]
label_purpose = layer_override_purpose[pin_layer_num][1]
except:
pass
except ImportError: except ImportError:
label_purpose = purpose label_purpose = purpose
@ -606,7 +623,9 @@ class pin_layout:
# Don't add the existing shape in if it overlaps the pin shape # Don't add the existing shape in if it overlaps the pin shape
if new_shape.contains(shape): if new_shape.contains(shape):
continue continue
new_shapes.append(new_shape) # Only add non-zero shapes
if new_shape.area() > 0:
new_shapes.append(new_shape)
return new_shapes return new_shapes

View File

@ -14,7 +14,10 @@ import globals
import debug import debug
from vector import vector from vector import vector
from pin_layout import pin_layout from pin_layout import pin_layout
try:
from tech import special_purposes
except ImportError:
special_purposes = {}
OPTS = globals.OPTS OPTS = globals.OPTS
@ -88,7 +91,7 @@ def _get_gds_reader(units, gds_filename):
debug.info(4, "Creating VLSI layout from {}".format(gds_absname)) debug.info(4, "Creating VLSI layout from {}".format(gds_absname))
cell_vlsi = gdsMill.VlsiLayout(units=units) cell_vlsi = gdsMill.VlsiLayout(units=units)
reader = gdsMill.Gds2reader(cell_vlsi) reader = gdsMill.Gds2reader(cell_vlsi)
reader.loadFromFile(gds_absname) reader.loadFromFile(gds_absname, special_purposes)
_GDS_READER_CACHE[k] = cell_vlsi _GDS_READER_CACHE[k] = cell_vlsi
return cell_vlsi return cell_vlsi
@ -148,12 +151,21 @@ def get_gds_pins(pin_names, name, gds_filename, units):
cell[str(pin_name)] = [] cell[str(pin_name)] = []
pin_list = cell_vlsi.getPinShape(str(pin_name)) pin_list = cell_vlsi.getPinShape(str(pin_name))
for pin_shape in pin_list: for pin_shape in pin_list:
(lpp, boundary) = pin_shape if pin_shape != None:
rect = [vector(boundary[0], boundary[1]), (lpp, boundary) = pin_shape
vector(boundary[2], boundary[3])] rect = [vector(boundary[0], boundary[1]),
# this is a list because other cells/designs vector(boundary[2], boundary[3])]
# may have must-connect pins # this is a list because other cells/designs
cell[str(pin_name)].append(pin_layout(pin_name, rect, lpp)) # may have must-connect pins
if isinstance(lpp[1], list):
try:
from tech import layer_override
if layer_override[pin_name]:
lpp = layer_override[pin_name.textString]
except:
pass
lpp = (lpp[0], None)
cell[str(pin_name)].append(pin_layout(pin_name, rect, lpp))
_GDS_PINS_CACHE[k] = cell _GDS_PINS_CACHE[k] = cell
return dict(cell) return dict(cell)

View File

@ -6,6 +6,7 @@
# All rights reserved. # All rights reserved.
# #
import math import math
from tech import spice
class verilog: class verilog:
@ -28,7 +29,21 @@ class verilog:
else: else:
self.vf.write("\n") self.vf.write("\n")
try:
self.vdd_name = spice["power"]
except KeyError:
self.vdd_name = "vdd"
try:
self.gnd_name = spice["ground"]
except KeyError:
self.gnd_name = "gnd"
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(" {},\n".format(self.vdd_name))
self.vf.write(" {},\n".format(self.gnd_name))
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))
@ -40,11 +55,15 @@ class verilog:
self.vf.write(" clk{0},csb{0},web{0},".format(port)) self.vf.write(" clk{0},csb{0},web{0},".format(port))
if self.write_size: if self.write_size:
self.vf.write("wmask{},".format(port)) self.vf.write("wmask{},".format(port))
if self.num_spare_cols > 0:
self.vf.write("spare_wen{0},".format(port))
self.vf.write("addr{0},din{0},dout{0}".format(port)) self.vf.write("addr{0},din{0},dout{0}".format(port))
elif port in self.write_ports: elif port in self.write_ports:
self.vf.write(" clk{0},csb{0},".format(port)) self.vf.write(" clk{0},csb{0},".format(port))
if self.write_size: if self.write_size:
self.vf.write("wmask{},".format(port)) self.vf.write("wmask{},".format(port))
if self.num_spare_cols > 0:
self.vf.write("spare_wen{0},".format(port))
self.vf.write("addr{0},din{0}".format(port)) self.vf.write("addr{0},din{0}".format(port))
elif port in self.read_ports: elif port in self.read_ports:
self.vf.write(" clk{0},csb{0},addr{0},dout{0}".format(port)) self.vf.write(" clk{0},csb{0},addr{0},dout{0}".format(port))
@ -56,7 +75,7 @@ class verilog:
if self.write_size: if self.write_size:
self.num_wmasks = int(math.ceil(self.word_size / self.write_size)) self.num_wmasks = int(math.ceil(self.word_size / self.write_size))
self.vf.write(" parameter NUM_WMASKS = {0} ;\n".format(self.num_wmasks)) self.vf.write(" parameter NUM_WMASKS = {0} ;\n".format(self.num_wmasks))
self.vf.write(" parameter DATA_WIDTH = {0} ;\n".format(self.word_size)) self.vf.write(" parameter DATA_WIDTH = {0} ;\n".format(self.word_size + self.num_spare_cols))
self.vf.write(" parameter ADDR_WIDTH = {0} ;\n".format(self.addr_size)) self.vf.write(" parameter ADDR_WIDTH = {0} ;\n".format(self.addr_size))
self.vf.write(" parameter RAM_DEPTH = 1 << ADDR_WIDTH;\n") self.vf.write(" parameter RAM_DEPTH = 1 << ADDR_WIDTH;\n")
self.vf.write(" // FIXME: This delay is arbitrary.\n") self.vf.write(" // FIXME: This delay is arbitrary.\n")
@ -65,6 +84,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 {};\n".format(self.vdd_name))
self.vf.write(" inout {};\n".format(self.gnd_name))
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)
@ -103,6 +127,10 @@ class verilog:
if port in self.write_ports: if port in self.write_ports:
if self.write_size: if self.write_size:
self.vf.write(" reg [NUM_WMASKS-1:0] wmask{0}_reg;\n".format(port)) self.vf.write(" reg [NUM_WMASKS-1:0] wmask{0}_reg;\n".format(port))
if self.num_spare_cols > 1:
self.vf.write(" reg [{1}:0] spare_wen{0}_reg;".format(port, self.num_spare_cols - 1))
elif self.num_spare_cols == 1:
self.vf.write(" reg spare_wen{0}_reg;\n".format(port))
self.vf.write(" reg [ADDR_WIDTH-1:0] addr{0}_reg;\n".format(port)) self.vf.write(" reg [ADDR_WIDTH-1:0] addr{0}_reg;\n".format(port))
if port in self.write_ports: if port in self.write_ports:
self.vf.write(" reg [DATA_WIDTH-1:0] din{0}_reg;\n".format(port)) self.vf.write(" reg [DATA_WIDTH-1:0] din{0}_reg;\n".format(port))
@ -123,7 +151,9 @@ class verilog:
if port in self.write_ports: if port in self.write_ports:
if self.write_size: if self.write_size:
self.vf.write(" wmask{0}_reg = wmask{0};\n".format(port)) self.vf.write(" wmask{0}_reg = wmask{0};\n".format(port))
self.vf.write(" addr{0}_reg = addr{0};\n".format(port)) if self.num_spare_cols:
self.vf.write(" spare_wen{0}_reg = spare_wen{0};\n".format(port))
self.vf.write(" addr{0}_reg = addr{0};\n".format(port))
if port in self.read_ports: if port in self.read_ports:
self.add_write_read_checks(port) self.add_write_read_checks(port)
@ -162,6 +192,11 @@ class verilog:
self.vf.write(" input web{0}; // active low write control\n".format(port)) self.vf.write(" input web{0}; // active low write control\n".format(port))
if self.write_size: if self.write_size:
self.vf.write(" input [NUM_WMASKS-1:0] wmask{0}; // write mask\n".format(port)) self.vf.write(" input [NUM_WMASKS-1:0] wmask{0}; // write mask\n".format(port))
if self.num_spare_cols == 1:
self.vf.write(" input spare_wen{0}; // spare mask\n".format(port))
elif self.num_spare_cols > 1:
self.vf.write(" input [{1}:0] spare_wen{0}; // spare mask\n".format(port, self.num_spare_cols-1))
self.vf.write(" input [ADDR_WIDTH-1:0] addr{0};\n".format(port)) self.vf.write(" input [ADDR_WIDTH-1:0] addr{0};\n".format(port))
if port in self.write_ports: if port in self.write_ports:
self.vf.write(" input [DATA_WIDTH-1:0] din{0};\n".format(port)) self.vf.write(" input [DATA_WIDTH-1:0] din{0};\n".format(port))
@ -179,29 +214,29 @@ class verilog:
self.vf.write(" always @ (negedge clk{0})\n".format(port)) self.vf.write(" always @ (negedge clk{0})\n".format(port))
self.vf.write(" begin : MEM_WRITE{0}\n".format(port)) self.vf.write(" begin : MEM_WRITE{0}\n".format(port))
if port in self.readwrite_ports: if port in self.readwrite_ports:
if self.write_size: self.vf.write(" if ( !csb{0}_reg && !web{0}_reg ) begin\n".format(port))
self.vf.write(" if ( !csb{0}_reg && !web{0}_reg ) begin\n".format(port))
else:
self.vf.write(" if ( !csb{0}_reg && !web{0}_reg )\n".format(port))
else: else:
if self.write_size: self.vf.write(" if (!csb{0}_reg) begin\n".format(port))
self.vf.write(" if (!csb{0}_reg) begin\n".format(port))
else:
self.vf.write(" if (!csb{0}_reg)\n".format(port))
if self.write_size: if self.write_size:
remainder_bits = self.word_size % self.write_size
for mask in range(0, self.num_wmasks): for mask in range(0, self.num_wmasks):
lower = mask * self.write_size lower = mask * self.write_size
if (remainder_bits and mask == self.num_wmasks - 1): upper = lower + self.write_size - 1
upper = lower + remainder_bits - 1
else:
upper = lower + self.write_size - 1
self.vf.write(" if (wmask{0}_reg[{1}])\n".format(port, mask)) self.vf.write(" if (wmask{0}_reg[{1}])\n".format(port, mask))
self.vf.write(" mem[addr{0}_reg][{1}:{2}] = din{0}_reg[{1}:{2}];\n".format(port, upper, lower)) self.vf.write(" mem[addr{0}_reg][{1}:{2}] = din{0}_reg[{1}:{2}];\n".format(port, upper, lower))
self.vf.write(" end\n")
else: else:
self.vf.write(" mem[addr{0}_reg] = din{0}_reg;\n".format(port)) upper = self.word_size - self.num_spare_cols - 1
self.vf.write(" mem[addr{0}_reg][{1}:0] = din{0}_reg[{1}:0];\n".format(port, upper))
if self.num_spare_cols == 1:
self.vf.write(" if (spare_wen{0}_reg)\n".format(port))
self.vf.write(" mem[addr{0}_reg][{1}] = din{0}_reg[{1}];\n".format(port, self.word_size))
else:
for num in range(self.num_spare_cols):
self.vf.write(" if (spare_wen{0}_reg[{1}])\n".format(port, num))
self.vf.write(" mem[addr{0}_reg][{1}] = din{0}_reg[{1}];\n".format(port, self.word_size + num))
self.vf.write(" end\n")
self.vf.write(" end\n") self.vf.write(" end\n")
def add_read_block(self, port): def add_read_block(self, port):

View File

@ -99,3 +99,8 @@ class bitcell_2port(bitcell_base.bitcell_base):
# Port 1 edges # Port 1 edges
graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self) graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self)
graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self) graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self)
def is_non_inverting(self):
"""Return input to output polarity for module"""
return False

View File

@ -26,7 +26,6 @@ class bitcell_base(design.design):
self.nets_match = self.do_nets_exist(prop.storage_nets) self.nets_match = self.do_nets_exist(prop.storage_nets)
self.mirror = prop.mirror self.mirror = prop.mirror
self.end_caps = prop.end_caps self.end_caps = prop.end_caps
def get_stage_effort(self, load): def get_stage_effort(self, load):
parasitic_delay = 1 parasitic_delay = 1
# This accounts for bitline being drained # This accounts for bitline being drained
@ -84,7 +83,7 @@ class bitcell_base(design.design):
return self.storage_nets return self.storage_nets
else: else:
fmt_str = "Storage nodes={} not found in spice file." fmt_str = "Storage nodes={} not found in spice file."
debug.info(1, fmt_str.format(self.storage_nets)) debug.warning(fmt_str.format(self.storage_nets))
return None return None
def get_storage_net_offset(self): def get_storage_net_offset(self):

View File

@ -49,3 +49,8 @@ class replica_bitcell_2port(bitcell_base.bitcell_base):
# Port 1 edges # Port 1 edges
graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self) graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self)
graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self) graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self)
def is_non_inverting(self):
"""Return input to output polarity for module"""
return False

View File

@ -12,33 +12,47 @@ from .lib import *
from .delay import * from .delay import *
from .elmore import * from .elmore import *
from .linear_regression import * from .linear_regression import *
from .neural_network import *
from .setup_hold import * from .setup_hold import *
from .functional import * from .functional import *
from .simulation import * from .simulation import *
from .measurements import * from .measurements import *
from .model_check import * from .model_check import *
from .analytical_util import * from .analytical_util import *
from .regression_model import *
debug.info(1, "Initializing characterizer...") 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:
# Capitalize Xyce
if OPTS.spice_name != "": 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":
os.environ["NGSPICE_INPUT_DIR"] = "{0}".format(OPTS.openram_temp) os.environ["NGSPICE_INPUT_DIR"] = "{0}".format(OPTS.openram_temp)
if OPTS.spice_exe == "": if not OPTS.spice_exe:
debug.error("No recognizable spice version found. Unable to perform characterization.", 1) debug.error("No recognizable spice version found. Unable to perform characterization.", 1)
else:
debug.info(1, "Finding spice simulator: {} ({})".format(OPTS.spice_name, OPTS.spice_exe))
if OPTS.mpi_name:
debug.info(1, "MPI for spice simulator: {} ({})".format(OPTS.mpi_name, OPTS.mpi_exe))
debug.info(1, "Simulation threads: {}".format(OPTS.num_sim_threads))
else: else:
debug.info(1, "Analytical model enabled.") debug.info(1, "Analytical model enabled.")

View File

@ -14,7 +14,7 @@ import os
process_transform = {'SS':0.0, 'TT': 0.5, 'FF':1.0} process_transform = {'SS':0.0, 'TT': 0.5, 'FF':1.0}
def get_data_names(file_name): def get_data_names(file_name, exclude_area=True):
""" """
Returns just the data names in the first row of the CSV Returns just the data names in the first row of the CSV
""" """
@ -25,8 +25,18 @@ def get_data_names(file_name):
# reader is iterable not a list, probably a better way to do this # reader is iterable not a list, probably a better way to do this
for row in csv_reader: for row in csv_reader:
# Return names from first row # Return names from first row
return row[0].split(',') names = row[0].split(',')
break
if exclude_area:
try:
area_ind = names.index('area')
except ValueError:
area_ind = -1
if area_ind != -1:
names = names[:area_ind] + names[area_ind+1:]
return names
def get_data(file_name): def get_data(file_name):
""" """
Returns data in CSV as lists of features Returns data in CSV as lists of features
@ -35,24 +45,33 @@ def get_data(file_name):
with open(file_name, newline='') as csvfile: with open(file_name, newline='') as csvfile:
csv_reader = csv.reader(csvfile, delimiter=' ', quotechar='|') csv_reader = csv.reader(csvfile, delimiter=' ', quotechar='|')
row_iter = 0 row_iter = 0
removed_items = 1
for row in csv_reader: for row in csv_reader:
row_iter += 1 row_iter += 1
if row_iter == 1: if row_iter == 1:
feature_names = row[0].split(',') feature_names = row[0].split(',')
input_list = [[] for _ in feature_names] input_list = [[] for _ in range(len(feature_names)-removed_items)]
scaled_list = [[] for _ in feature_names] try:
# Save to remove area
area_ind = feature_names.index('area')
except ValueError:
area_ind = -1
try: try:
process_ind = feature_names.index('process') process_ind = feature_names.index('process')
except: except:
debug.error('Process not included as a feature.') debug.error('Process not included as a feature.')
continue continue
data = [] data = []
split_str = row[0].split(',') split_str = row[0].split(',')
for i in range(len(split_str)): for i in range(len(split_str)):
if i == process_ind: if i == process_ind:
data.append(process_transform[split_str[i]]) data.append(process_transform[split_str[i]])
elif i == area_ind:
continue
else: else:
data.append(float(split_str[i])) data.append(float(split_str[i]))
@ -227,9 +246,8 @@ def get_scaled_data(file_name):
# Data is scaled by max/min and data format is changed to points vs feature lists # Data is scaled by max/min and data format is changed to points vs feature lists
self_scaled_data = scale_data_and_transform(all_data) self_scaled_data = scale_data_and_transform(all_data)
samples = np.asarray(self_scaled_data) data_np = np.asarray(self_scaled_data)
features, labels = samples[:, :-1], samples[:,-1:] return data_np
return features, labels
def scale_data_and_transform(data): def scale_data_and_transform(data):
""" """
@ -275,16 +293,13 @@ def unscale_data(data, file_path, pos=None):
# Hard coded to only convert the last max/min (i.e. the label of the data) # Hard coded to only convert the last max/min (i.e. the label of the data)
if pos == None: if pos == None:
maxs,mins,avgs = [maxs[-1]],[mins[-1]],[avgs[-1]] maxs,mins,avgs = maxs[-1],mins[-1],avgs[-1]
else: else:
maxs,mins,avgs = [maxs[pos]],[mins[pos]],[avgs[pos]] maxs,mins,avgs = maxs[pos],mins[pos],avgs[pos]
unscaled_data = [] unscaled_data = []
for data_row in data: for data_row in data:
unscaled_row = [] unscaled_val = data_row*(maxs-mins) + mins
for val, cur_max, cur_min in zip(data_row, maxs, mins): unscaled_data.append(unscaled_val)
unscaled_val = val*(cur_max-cur_min) + cur_min
unscaled_row.append(unscaled_val)
unscaled_data.append(unscaled_row)
return unscaled_data return unscaled_data

View File

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

View File

@ -69,7 +69,7 @@ class delay(simulation):
for meas in meas_list: for meas in meas_list:
name = meas.name.lower() name = meas.name.lower()
debug.check(name not in name_set, ("SPICE measurements must have unique names. " debug.check(name not in name_set, ("SPICE measurements must have unique names. "
"Duplicate name={}").format(name)) "Duplicate name={0}").format(name))
name_set.add(name) name_set.add(name)
def create_read_port_measurement_objects(self): def create_read_port_measurement_objects(self):
@ -77,7 +77,7 @@ class delay(simulation):
self.read_lib_meas = [] self.read_lib_meas = []
self.clk_frmt = "clk{0}" # Unformatted clock name self.clk_frmt = "clk{0}" # Unformatted clock name
targ_name = "{0}{1}_{2}".format(self.dout_name, "{}", self.probe_data) # Empty values are the port and probe data bit targ_name = "{0}{{}}_{1}".format(self.dout_name, self.probe_data) # Empty values are the port and probe data bit
self.delay_meas = [] self.delay_meas = []
self.delay_meas.append(delay_measure("delay_lh", self.clk_frmt, targ_name, "RISE", "RISE", measure_scale=1e9)) self.delay_meas.append(delay_measure("delay_lh", self.clk_frmt, targ_name, "RISE", "RISE", measure_scale=1e9))
self.delay_meas[-1].meta_str = sram_op.READ_ONE # Used to index time delay values when measurements written to spice file. self.delay_meas[-1].meta_str = sram_op.READ_ONE # Used to index time delay values when measurements written to spice file.
@ -166,7 +166,7 @@ class delay(simulation):
self.dout_volt_meas = [] self.dout_volt_meas = []
for meas in self.delay_meas: for meas in self.delay_meas:
# Output voltage measures # Output voltage measures
self.dout_volt_meas.append(voltage_at_measure("v_{}".format(meas.name), self.dout_volt_meas.append(voltage_at_measure("v_{0}".format(meas.name),
meas.targ_name_no_port)) meas.targ_name_no_port))
self.dout_volt_meas[-1].meta_str = meas.meta_str self.dout_volt_meas[-1].meta_str = meas.meta_str
@ -186,7 +186,7 @@ class delay(simulation):
self.read_bit_meas = {bit_polarity.NONINVERTING: [], bit_polarity.INVERTING: []} self.read_bit_meas = {bit_polarity.NONINVERTING: [], bit_polarity.INVERTING: []}
meas_cycles = (sram_op.READ_ZERO, sram_op.READ_ONE) meas_cycles = (sram_op.READ_ZERO, sram_op.READ_ONE)
for cycle in meas_cycles: for cycle in meas_cycles:
meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name) meas_tag = "a{0}_b{1}_{2}".format(self.probe_address, self.probe_data, cycle.name)
single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data) single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data)
for polarity, meas in single_bit_meas.items(): for polarity, meas in single_bit_meas.items():
meas.meta_str = cycle meas.meta_str = cycle
@ -200,7 +200,7 @@ class delay(simulation):
self.write_bit_meas = {bit_polarity.NONINVERTING: [], bit_polarity.INVERTING: []} self.write_bit_meas = {bit_polarity.NONINVERTING: [], bit_polarity.INVERTING: []}
meas_cycles = (sram_op.WRITE_ZERO, sram_op.WRITE_ONE) meas_cycles = (sram_op.WRITE_ZERO, sram_op.WRITE_ONE)
for cycle in meas_cycles: for cycle in meas_cycles:
meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name) meas_tag = "a{0}_b{1}_{2}".format(self.probe_address, self.probe_data, cycle.name)
single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data) single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data)
for polarity, meas in single_bit_meas.items(): for polarity, meas in single_bit_meas.items():
meas.meta_str = cycle meas.meta_str = cycle
@ -219,20 +219,20 @@ class delay(simulation):
(cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, bit_row, bit_col) (cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, bit_row, bit_col)
storage_names = cell_inst.mod.get_storage_net_names() storage_names = cell_inst.mod.get_storage_net_names()
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes" debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
"supported for characterization. Storage nets={}").format(storage_names)) "supported for characterization. Storage nets={0}").format(storage_names))
if OPTS.use_pex and OPTS.pex_exe[0] != "calibre": if OPTS.use_pex and OPTS.pex_exe[0] != "calibre":
bank_num = self.sram.get_bank_num(self.sram.name, bit_row, bit_col) bank_num = self.sram.get_bank_num(self.sram.name, bit_row, bit_col)
q_name = "bitcell_Q_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col) q_name = "bitcell_Q_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col)
qbar_name = "bitcell_Q_bar_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col) qbar_name = "bitcell_Q_bar_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col)
else: else:
q_name = cell_name + '.' + str(storage_names[0]) q_name = cell_name + OPTS.hier_seperator + str(storage_names[0])
qbar_name = cell_name + '.' + str(storage_names[1]) qbar_name = cell_name + OPTS.hier_seperator + str(storage_names[1])
# Bit measures, measurements times to be defined later. The measurement names must be unique # Bit measures, measurements times to be defined later. The measurement names must be unique
# but they is enforced externally. {} added to names to differentiate between ports allow the # but they is enforced externally. {} added to names to differentiate between ports allow the
# measurements are independent of the ports # measurements are independent of the ports
q_meas = voltage_at_measure("v_q_{}".format(meas_tag), q_name) q_meas = voltage_at_measure("v_q_{0}".format(meas_tag), q_name)
qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name) qbar_meas = voltage_at_measure("v_qbar_{0}".format(meas_tag), qbar_name)
return {bit_polarity.NONINVERTING: q_meas, bit_polarity.INVERTING: qbar_meas} return {bit_polarity.NONINVERTING: q_meas, bit_polarity.INVERTING: qbar_meas}
@ -242,15 +242,15 @@ class delay(simulation):
# FIXME: There should be a default_read_port variable in this case, pathing is done with this # FIXME: There should be a default_read_port variable in this case, pathing is done with this
# but is never mentioned otherwise # but is never mentioned otherwise
port = self.read_ports[0] port = self.read_ports[0]
sen_and_port = self.sen_name+str(port) sen_and_port = self.sen_name + str(port)
bl_and_port = self.bl_name.format(port) # bl_name contains a '{}' for the port bl_and_port = self.bl_name.format(port) # bl_name contains a '{}' for the port
# Isolate the s_en and bitline paths # Isolate the s_en and bitline paths
debug.info(1, "self.bl_name = {}".format(self.bl_name)) debug.info(1, "self.bl_name = {0}".format(self.bl_name))
debug.info(1, "self.graph.all_paths = {}".format(self.graph.all_paths)) debug.info(2, "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, {})
@ -813,7 +828,7 @@ class delay(simulation):
result[port].update(read_port_dict) result[port].update(read_port_dict)
self.check_path_measures() self.path_delays = self.check_path_measures()
return (True, result) return (True, result)
@ -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,13 +936,13 @@ 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))
return value_dict return value_dict
def run_power_simulation(self): def run_power_simulation(self):
@ -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)
@ -1120,7 +1135,7 @@ class delay(simulation):
self.create_measurement_names() self.create_measurement_names()
self.create_measurement_objects() self.create_measurement_objects()
def analyze(self, probe_address, probe_data, slews, loads): def analyze(self, probe_address, probe_data, load_slews):
""" """
Main function to characterize an SRAM for a table. Computes both delay and power characterization. Main function to characterize an SRAM for a table. Computes both delay and power characterization.
""" """
@ -1128,7 +1143,11 @@ class delay(simulation):
# Dict to hold all characterization values # Dict to hold all characterization values
char_sram_data = {} char_sram_data = {}
self.analysis_init(probe_address, probe_data) self.analysis_init(probe_address, probe_data)
loads = []
slews = []
for load,slew in load_slews:
loads.append(load)
slews.append(slew)
self.load=max(loads) self.load=max(loads)
self.slew=max(slews) self.slew=max(slews)
@ -1148,11 +1167,19 @@ class delay(simulation):
leakage_offset = full_array_leakage - trim_array_leakage leakage_offset = full_array_leakage - trim_array_leakage
# 4) At the minimum period, measure the delay, slew and power for all slew/load pairs. # 4) At the minimum period, measure the delay, slew and power for all slew/load pairs.
self.period = min_period self.period = min_period
char_port_data = self.simulate_loads_and_slews(slews, loads, leakage_offset) char_port_data = self.simulate_loads_and_slews(load_slews, leakage_offset)
if OPTS.use_specified_load_slew != None and len(load_slews) > 1:
debug.warning("Path delay lists not correctly generated for characterizations of more than 1 load,slew")
# Get and save the path delays
bl_names, bl_delays, sen_names, sen_delays = self.get_delay_lists(self.path_delays)
# Removed from characterization output temporarily
#char_sram_data["bl_path_measures"] = bl_delays
#char_sram_data["sen_path_measures"] = sen_delays
#char_sram_data["bl_path_names"] = bl_names
#char_sram_data["sen_path_names"] = sen_names
# FIXME: low-to-high delays are altered to be independent of the period. This makes the lib results less accurate. # FIXME: low-to-high delays are altered to be independent of the period. This makes the lib results less accurate.
self.alter_lh_char_data(char_port_data) self.alter_lh_char_data(char_port_data)
return (char_sram_data, char_port_data) return (char_sram_data, char_port_data)
def alter_lh_char_data(self, char_port_data): def alter_lh_char_data(self, char_port_data):
@ -1163,30 +1190,47 @@ class delay(simulation):
char_port_data[port]['delay_lh'] = char_port_data[port]['delay_hl'] char_port_data[port]['delay_lh'] = char_port_data[port]['delay_hl']
char_port_data[port]['slew_lh'] = char_port_data[port]['slew_hl'] char_port_data[port]['slew_lh'] = char_port_data[port]['slew_hl']
def simulate_loads_and_slews(self, slews, loads, leakage_offset): def simulate_loads_and_slews(self, load_slews, leakage_offset):
"""Simulate all specified output loads and input slews pairs of all ports""" """Simulate all specified output loads and input slews pairs of all ports"""
measure_data = self.get_empty_measure_data_dict() measure_data = self.get_empty_measure_data_dict()
path_dict = {}
# Set the target simulation ports to all available ports. This make sims slower but failed sims exit anyways. # Set the target simulation ports to all available ports. This make sims slower but failed sims exit anyways.
self.targ_read_ports = self.read_ports self.targ_read_ports = self.read_ports
self.targ_write_ports = self.write_ports self.targ_write_ports = self.write_ports
for slew in slews: for load, slew in load_slews:
for load in loads: self.set_load_slew(load, slew)
self.set_load_slew(load, slew) # Find the delay, dynamic power, and leakage power of the trimmed array.
# Find the delay, dynamic power, and leakage power of the trimmed array. (success, delay_results) = self.run_delay_simulation()
(success, delay_results) = self.run_delay_simulation() debug.check(success, "Couldn't run a simulation. slew={0} load={1}\n".format(self.slew, self.load))
debug.check(success, "Couldn't run a simulation. slew={0} load={1}\n".format(self.slew, self.load)) debug.info(1, "Simulation Passed: Port {0} slew={1} load={2}".format("All", self.slew, self.load))
debug.info(1, "Simulation Passed: Port {0} slew={1} load={2}".format("All", self.slew, self.load)) # The results has a dict for every port but dicts can be empty (e.g. ports were not targeted).
# The results has a dict for every port but dicts can be empty (e.g. ports were not targeted). for port in self.all_ports:
for port in self.all_ports: for mname, value in delay_results[port].items():
for mname, value in delay_results[port].items(): if "power" in mname:
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)
return measure_data return measure_data
def get_delay_lists(self, value_dict):
"""Returns dicts for path measures of bitline and sen paths"""
sen_name_list = []
sen_delay_list = []
for meas in self.sen_path_meas:
sen_name_list.append(meas.name)
sen_delay_list.append(value_dict[meas.name])
bl_name_list = []
bl_delay_list = []
for meas in self.bl_path_meas:
bl_name_list.append(meas.name)
bl_delay_list.append(value_dict[meas.name])
return sen_name_list, sen_delay_list, bl_name_list, bl_delay_list
def calculate_inverse_address(self): def calculate_inverse_address(self):
"""Determine dummy test address based on probe address and column mux size.""" """Determine dummy test address based on probe address and column mux size."""
@ -1218,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,
@ -1235,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
@ -1249,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,
@ -1259,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,
@ -1269,11 +1313,11 @@ class delay(simulation):
self.measure_cycles[read_port]["disabled_read1"] = len(self.cycle_times) - 1 self.measure_cycles[read_port]["disabled_read1"] = len(self.cycle_times) - 1
# This also ensures we will have a L->H transition on the next read # This also ensures we will have a L->H transition on the next read
self.add_read("R data 0 address {} to clear dout caps".format(inverse_address), self.add_read("R data 0 address {0} to clear dout caps".format(inverse_address),
inverse_address, inverse_address,
read_port) read_port)
self.add_read("R data 1 address {} to check W1 worked".format(self.probe_address), self.add_read("R data 1 address {0} to check W1 worked".format(self.probe_address),
self.probe_address, self.probe_address,
read_port) read_port)
self.measure_cycles[read_port][sram_op.READ_ONE] = len(self.cycle_times) - 1 self.measure_cycles[read_port][sram_op.READ_ONE] = len(self.cycle_times) - 1

View File

@ -30,7 +30,7 @@ class elmore(simulation):
self.create_signal_names() self.create_signal_names()
self.add_graph_exclusions() self.add_graph_exclusions()
def get_lib_values(self, slews, loads): def get_lib_values(self, load_slews):
""" """
Return the analytical model results for the SRAM. Return the analytical model results for the SRAM.
""" """
@ -53,33 +53,29 @@ class elmore(simulation):
# Set delay/power for slews and loads # Set delay/power for slews and loads
port_data = self.get_empty_measure_data_dict() port_data = self.get_empty_measure_data_dict()
power = self.analytical_power(slews, loads) power = self.analytical_power(load_slews)
debug.info(1, 'Slew, Load, Delay(ns), Slew(ns)') debug.info(1, 'Slew, Load, Delay(ns), Slew(ns)')
max_delay = 0.0 max_delay = 0.0
for slew in slews: for load,slew in load_slews:
for load in loads: # Calculate delay based on slew and load
# Calculate delay based on slew and load path_delays = self.graph.get_timing(bl_path, self.corner, slew, load)
path_delays = self.graph.get_timing(bl_path, self.corner, slew, load)
total_delay = self.sum_delays(path_delays) total_delay = self.sum_delays(path_delays)
max_delay = max(max_delay, total_delay.delay) max_delay = max(max_delay, total_delay.delay)
debug.info(1, debug.info(1,
'{}, {}, {}, {}'.format(slew, '{}, {}, {}, {}'.format(slew,
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.
@ -92,11 +88,11 @@ class elmore(simulation):
return (sram_data, port_data) return (sram_data, port_data)
def analytical_power(self, slews, loads): def analytical_power(self, load_slews):
"""Get the dynamic and leakage power from the SRAM""" """Get the dynamic and leakage power from the SRAM"""
# slews unused, only last load is used # slews unused, only last load is used
load = loads[-1] load = load_slews[-1][0]
power = self.sram.analytical_power(self.corner, load) power = self.sram.analytical_power(self.corner, load)
# convert from nW to mW # convert from nW to mW
power.dynamic /= 1e6 power.dynamic /= 1e6

View File

@ -32,13 +32,13 @@ class functional(simulation):
if not spfile: if not spfile:
# self.sp_file is assigned in base class # self.sp_file is assigned in base class
sram.sp_write(self.sp_file, trim=OPTS.trim_netlist) sram.sp_write(self.sp_file, trim=OPTS.trim_netlist)
if not corner: if not corner:
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
if period: if period:
self.period = period self.period = period
if not output_path: if not output_path:
self.output_path = OPTS.openram_temp self.output_path = OPTS.openram_temp
else: else:
@ -54,7 +54,7 @@ class functional(simulation):
self.max_data = 2 ** self.word_size - 1 self.max_data = 2 ** self.word_size - 1
self.max_col_data = 2 ** self.num_spare_cols - 1 self.max_col_data = 2 ** self.num_spare_cols - 1
if self.words_per_row>1: if self.words_per_row > 1:
# This will truncate bits for word addressing in a row_addr_dff # This will truncate bits for word addressing in a row_addr_dff
# This makes one set of spares per row by using top bits of the address # This makes one set of spares per row by using top bits of the address
self.addr_spare_index = -int(math.log(self.words_per_row) / math.log(2)) self.addr_spare_index = -int(math.log(self.words_per_row) / math.log(2))
@ -63,11 +63,11 @@ class functional(simulation):
self.addr_spare_index = self.addr_size self.addr_spare_index = self.addr_size
# If trim is set, specify the valid addresses # If trim is set, specify the valid addresses
self.valid_addresses = set() self.valid_addresses = set()
self.max_address = 2**self.addr_size - 1 + (self.num_spare_rows * self.words_per_row) self.max_address = self.num_rows * self.words_per_row - 1
if OPTS.trim_netlist: if OPTS.trim_netlist:
for i in range(self.words_per_row): for i in range(self.words_per_row):
self.valid_addresses.add(i) self.valid_addresses.add(i)
self.valid_addresses.add(self.max_address - i) self.valid_addresses.add(self.max_address - i - 1)
self.probe_address, self.probe_data = '0' * self.addr_size, 0 self.probe_address, self.probe_data = '0' * self.addr_size, 0
self.set_corner(corner) self.set_corner(corner)
self.set_spice_constants() self.set_spice_constants()
@ -81,13 +81,17 @@ 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:\t\t{0}".format(self.q_name))
debug.info(2, "qbar:\t{0}".format(self.qbar_name))
debug.info(2, "s_en:\t{0}".format(self.sen_name))
debug.info(2, "bl:\t{0}".format(self.bl_name))
debug.info(2, "br:\t{0}".format(self.br_name))
# Number of checks can be changed # Number of checks can be changed
self.num_cycles = cycles self.num_cycles = cycles
# This is to have ordered keys for random selection # This is to have ordered keys for random selection
self.stored_words = collections.OrderedDict() self.stored_words = collections.OrderedDict()
self.stored_spares = collections.OrderedDict() self.stored_spares = collections.OrderedDict()
self.read_check = [] self.read_check = []
self.read_results = [] self.read_results = []
@ -128,11 +132,12 @@ class functional(simulation):
name)) name))
def create_random_memory_sequence(self): def create_random_memory_sequence(self):
# Select randomly, but have 3x more reads to increase probability
if self.write_size: if self.write_size:
rw_ops = ["noop", "write", "partial_write", "read"] rw_ops = ["noop", "write", "partial_write", "read", "read"]
w_ops = ["noop", "write", "partial_write"] w_ops = ["noop", "write", "partial_write"]
else: else:
rw_ops = ["noop", "write", "read"] rw_ops = ["noop", "write", "read", "read"]
w_ops = ["noop", "write"] w_ops = ["noop", "write"]
r_ops = ["noop", "read"] r_ops = ["noop", "read"]
@ -140,37 +145,39 @@ class functional(simulation):
comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.addr_size, "0" * self.num_wmasks, 0, self.t_current) comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.addr_size, "0" * self.num_wmasks, 0, self.t_current)
self.add_noop_all_ports(comment) self.add_noop_all_ports(comment)
# 1. Write all the write ports first to seed a bunch of locations.
for port in self.write_ports:
addr = self.gen_addr()
(word, spare) = self.gen_data()
combined_word = "{}+{}".format(word, spare)
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.stored_words[addr] = word
self.stored_spares[addr[:self.addr_spare_index]] = spare
# All other read-only ports are noops. # 1. Write all the write ports 2x to seed a bunch of locations.
for port in self.read_ports: for i in range(3):
if port not in self.write_ports: for port in self.write_ports:
self.add_noop_one_port(port) addr = self.gen_addr()
self.cycle_times.append(self.t_current) (word, spare) = self.gen_data()
self.t_current += self.period combined_word = self.combine_word(spare, word)
self.check_lengths() comment = self.gen_cycle_comment("write", combined_word, addr, "1" * self.num_wmasks, port, self.t_current)
self.add_write_one_port(comment, addr, spare + word, "1" * self.num_wmasks, port)
self.stored_words[addr] = word
self.stored_spares[addr[:self.addr_spare_index]] = spare
# All other read-only ports are noops.
for port in self.read_ports:
if port not in self.write_ports:
self.add_noop_one_port(port)
self.cycle_times.append(self.t_current)
self.t_current += self.period
self.check_lengths()
# 2. Read at least once. For multiport, it is important that one # 2. Read at least once. For multiport, it is important that one
# read cycle uses all RW and R port to read from the same # read cycle uses all RW and R port to read from the same
# address simultaniously. This will test the viablilty of the # address simultaniously. This will test the viablilty of the
# transistor sizing in the bitcell. # transistor sizing in the bitcell.
for port in self.all_ports: for port in self.all_ports:
if port in self.write_ports: if port in self.write_ports and port not in self.read_ports:
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 = self.combine_word(spare, word)
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(spare + word, port)
self.cycle_times.append(self.t_current) self.cycle_times.append(self.t_current)
self.t_current += self.period self.t_current += self.period
self.check_lengths() self.check_lengths()
@ -197,9 +204,9 @@ 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 = self.combine_word(spare, word)
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, spare + word, "1" * self.num_wmasks, port)
self.stored_words[addr] = word self.stored_words[addr] = word
self.stored_spares[addr[:self.addr_spare_index]] = spare self.stored_spares[addr[:self.addr_spare_index]] = spare
w_addrs.append(addr) w_addrs.append(addr)
@ -213,16 +220,16 @@ 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 = self.combine_word(spare, word)
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, spare + word, wmask, port)
self.stored_words[addr] = new_word self.stored_words[addr] = new_word
self.stored_spares[addr[:self.addr_spare_index]] = spare self.stored_spares[addr[:self.addr_spare_index]] = spare
w_addrs.append(addr) w_addrs.append(addr)
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 = self.combine_word(spare, word)
# 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
@ -232,8 +239,7 @@ class functional(simulation):
else: else:
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 + spare, port) self.add_read_check(spare + word, port)
self.cycle_times.append(self.t_current) self.cycle_times.append(self.t_current)
self.t_current += self.period self.t_current += self.period
@ -258,19 +264,22 @@ class functional(simulation):
def add_read_check(self, word, port): def add_read_check(self, word, port):
""" Add to the check array to ensure a read works. """ """ Add to the check array to ensure a read works. """
try: self.read_check.append([word,
self.check_count "{0}{1}".format(self.dout_name, port),
except: self.t_current + self.period,
self.check_count = 0 int(self.t_current/self.period)])
self.read_check.append([word, "{0}{1}".format(self.dout_name, port), self.t_current + self.period, self.check_count])
self.check_count += 1
def read_stim_results(self): def read_stim_results(self):
# Extract dout values from spice timing.lis # Extract dout values from spice timing.lis
for (word, dout_port, eo_period, check_count) in self.read_check: for (word, dout_port, eo_period, cycle) in self.read_check:
sp_read_value = "" sp_read_value = ""
for bit in range(self.word_size + self.num_spare_cols): for bit in range(self.word_size + self.num_spare_cols):
value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(), bit, check_count)) measure_name = "v{0}_{1}ck{2}".format(dout_port.lower(), bit, cycle)
value = parse_spice_list("timing", measure_name)
# FIXME: Ignore the spare columns for now
if bit >= self.word_size:
value = 0
try: try:
value = float(value) value = float(value)
if value > self.v_high: if value > self.v_high:
@ -291,19 +300,24 @@ class functional(simulation):
eo_period) eo_period)
return (0, error) return (0, error)
self.read_results.append([sp_read_value, dout_port, eo_period, cycle])
self.read_results.append([sp_read_value, dout_port, eo_period, check_count])
return (1, "SUCCESS") return (1, "SUCCESS")
def check_stim_results(self): def check_stim_results(self):
for i in range(len(self.read_check)): for i in range(len(self.read_check)):
if self.read_check[i][0] != self.read_results[i][0]: if self.read_check[i][0] != self.read_results[i][0]:
str = "FAILED: {0} read value {1} does not match written value {2} during cycle {3} at time {4}n" output_name = self.read_check[i][1]
error = str.format(self.read_results[i][1], cycle = self.read_check[i][3]
self.read_results[i][0], read_val = self.format_value(self.read_results[i][0])
self.read_check[i][0], correct_val = self.format_value(self.read_check[i][0])
int((self.read_results[i][2] - self.period) / self.period), check_name = "v{0}_Xck{1}".format(output_name, cycle)
self.read_results[i][2]) str = "FAILED: {0} read value {1} during cycle {3} at time {4}n ({5}) does not match written value ({2})"
error = str.format(output_name,
read_val,
correct_val,
cycle,
self.read_results[i][2],
check_name)
return(0, error) return(0, error)
return(1, "SUCCESS") return(1, "SUCCESS")
@ -332,13 +346,18 @@ class functional(simulation):
def gen_data(self): def gen_data(self):
""" Generates a random word to write. """ """ Generates a random word to write. """
random_value = random.randint(0, self.max_data) # Don't use 0 or max value
random_value = random.randint(1, self.max_data - 1)
data_bits = binary_repr(random_value, self.word_size) data_bits = binary_repr(random_value, self.word_size)
if self.num_spare_cols>0: if self.num_spare_cols>0:
random_value = random.randint(0, self.max_col_data) random_value = random.randint(0, self.max_col_data)
spare_bits = binary_repr(random_value, self.num_spare_cols) spare_bits = binary_repr(random_value, self.num_spare_cols)
else: else:
spare_bits = "" spare_bits = ""
# FIXME: Set these to 0 for now...
spare_bits = "0" * len(spare_bits)
return data_bits, spare_bits return data_bits, spare_bits
def gen_addr(self): def gen_addr(self):
@ -363,7 +382,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 +406,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:\t{0}\n".format(self.bl_name.format(port)))
self.sf.write("* br: {}\n".format(self.br_name.format(port))) self.sf.write("* br:\t{0}\n".format(self.br_name.format(port)))
self.sf.write("* s_en: {}\n".format(self.sen_name)) self.sf.write("* s_en:\t{0}\n".format(self.sen_name))
self.sf.write("* q: {}\n".format(self.q_name)) self.sf.write("* q:\t{0}\n".format(self.q_name))
self.sf.write("* qbar: {}\n".format(self.qbar_name)) self.sf.write("* qbar:\t{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 +433,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:
@ -444,20 +463,21 @@ class functional(simulation):
self.stim.gen_pulse(sig_name="{0}{1}".format("clk", port), self.stim.gen_pulse(sig_name="{0}{1}".format("clk", port),
v1=self.gnd_voltage, v1=self.gnd_voltage,
v2=self.vdd_voltage, v2=self.vdd_voltage,
offset=self.period, offset=self.period - 0.5 * self.slew,
period=self.period, period=self.period,
t_rise=self.slew, t_rise=self.slew,
t_fall=self.slew) t_fall=self.slew)
# Generate dout value measurements # Generate dout value measurements
self.sf.write("\n * Generation of dout measurements\n") self.sf.write("\n * Generation of dout measurements\n")
for (word, dout_port, eo_period, check) in self.read_check:
t_initial = eo_period - 0.01 * self.period for (word, dout_port, eo_period, cycle) in self.read_check:
t_initial = eo_period
t_final = eo_period + 0.01 * self.period t_final = eo_period + 0.01 * self.period
num_bits = self.word_size + self.num_spare_cols num_bits = self.word_size + self.num_spare_cols
for bit in range(num_bits): for bit in range(num_bits):
measure_name = "V{0}_{1}ck{2}".format(dout_port, bit, check)
signal_name = "{0}_{1}".format(dout_port, bit) signal_name = "{0}_{1}".format(dout_port, bit)
measure_name = "V{0}ck{1}".format(signal_name, cycle)
voltage_value = self.stim.get_voltage(word[num_bits - bit - 1]) voltage_value = self.stim.get_voltage(word[num_bits - bit - 1])
self.stim.add_comment("* CHECK {0} {1} = {2} time = {3}".format(signal_name, self.stim.add_comment("* CHECK {0} {1} = {2} time = {3}".format(signal_name,
@ -472,16 +492,14 @@ class functional(simulation):
self.stim.write_control(self.cycle_times[-1] + self.period) self.stim.write_control(self.cycle_times[-1] + self.period)
self.sf.close() self.sf.close()
#FIXME: Similar function to delay.py, refactor this # FIXME: Similar function to delay.py, refactor this
def get_bit_name(self): def get_bit_name(self):
""" Get a bit cell name """ """ Get a bit cell name """
(cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, 0, 0) (cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, 0, 0)
storage_names = cell_inst.mod.get_storage_net_names() storage_names = cell_inst.mod.get_storage_net_names()
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes" debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
"supported for characterization. Storage nets={}").format(storage_names)) "supported for characterization. Storage nets={0}").format(storage_names))
q_name = cell_name + '.' + str(storage_names[0]) q_name = cell_name + OPTS.hier_seperator + str(storage_names[0])
qbar_name = cell_name + '.' + str(storage_names[1]) qbar_name = cell_name + OPTS.hier_seperator + str(storage_names[1])
return (q_name, qbar_name) return (q_name, qbar_name)

View File

@ -5,7 +5,8 @@
# (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 os import os,sys,re
import time
import debug import debug
import datetime import datetime
from .setup_hold import * from .setup_hold import *
@ -14,6 +15,7 @@ from .charutils import *
import tech import tech
import numpy as np import numpy as np
from globals import OPTS from globals import OPTS
from tech import spice
class lib: class lib:
@ -21,10 +23,20 @@ class lib:
def __init__(self, out_dir, sram, sp_file, use_model=OPTS.analytical_delay): def __init__(self, out_dir, sram, sp_file, use_model=OPTS.analytical_delay):
try:
self.vdd_name = spice["power"]
except KeyError:
self.vdd_name = "vdd"
try:
self.gnd_name = spice["ground"]
except KeyError:
self.gnd_name = "gnd"
self.out_dir = out_dir self.out_dir = out_dir
self.sram = sram self.sram = sram
self.sp_file = sp_file self.sp_file = sp_file
self.use_model = use_model self.use_model = use_model
self.pred_time = None
self.set_port_indices() self.set_port_indices()
self.prepare_tables() self.prepare_tables()
@ -44,16 +56,32 @@ class lib:
def prepare_tables(self): def prepare_tables(self):
""" Determine the load/slews if they aren't specified in the config file. """ """ Determine the load/slews if they aren't specified in the config file. """
# These are the parameters to determine the table sizes # These are the parameters to determine the table sizes
self.load_scales = np.array(OPTS.load_scales) if OPTS.use_specified_load_slew == None:
self.load = tech.spice["dff_in_cap"] self.load_scales = np.array(OPTS.load_scales)
self.loads = self.load_scales * self.load self.load = tech.spice["dff_in_cap"]
debug.info(1, "Loads: {0}".format(self.loads)) self.loads = self.load_scales * self.load
self.slew_scales = np.array(OPTS.slew_scales)
self.slew = tech.spice["rise_time"] self.slew_scales = np.array(OPTS.slew_scales)
self.slews = self.slew_scales * self.slew self.slew = tech.spice["rise_time"]
self.slews = self.slew_scales * self.slew
self.load_slews = []
for slew in self.slews:
for load in self.loads:
self.load_slews.append((load, slew))
else:
debug.warning("Using the option \"use_specified_load_slew\" will make load slew,data in lib file inaccurate.")
self.load_slews = OPTS.use_specified_load_slew
self.loads = []
self.slews = []
for load,slew in self.load_slews:
self.loads.append(load)
self.slews.append(slew)
self.loads = np.array(self.loads)
self.slews = np.array(self.slews)
debug.info(1, "Slews: {0}".format(self.slews)) debug.info(1, "Slews: {0}".format(self.slews))
debug.info(1, "Loads: {0}".format(self.loads))
debug.info(1, "self.load_slews : {0}".format(self.load_slews))
def create_corners(self): def create_corners(self):
""" Create corners for characterization. """ """ Create corners for characterization. """
# Get the corners from the options file # Get the corners from the options file
@ -74,7 +102,7 @@ class lib:
self.corners = [] self.corners = []
self.lib_files = [] self.lib_files = []
if OPTS.use_specified_corners == None: if OPTS.use_specified_corners == None:
# Nominal corner # Nominal corner
corner_tuples = set() corner_tuples = set()
@ -86,7 +114,7 @@ class lib:
for v in self.supply_voltages: for v in self.supply_voltages:
for t in self.temperatures: for t in self.temperatures:
corner_tuples.add((p, v, t)) corner_tuples.add((p, v, t))
else: else:
nom_corner = (nom_process, nom_supply, nom_temperature) nom_corner = (nom_process, nom_supply, nom_temperature)
corner_tuples.add(nom_corner) corner_tuples.add(nom_corner)
if not OPTS.nominal_corner_only: if not OPTS.nominal_corner_only:
@ -104,7 +132,7 @@ class lib:
corner_tuples.remove(nom_corner) corner_tuples.remove(nom_corner)
else: else:
corner_tuples = OPTS.use_specified_corners corner_tuples = OPTS.use_specified_corners
for corner_tuple in corner_tuples: for corner_tuple in corner_tuples:
self.add_corner(*corner_tuple) self.add_corner(*corner_tuple)
@ -124,7 +152,9 @@ class lib:
def characterize_corners(self): def characterize_corners(self):
""" Characterize the list of corners. """ """ Characterize the list of corners. """
debug.info(1,"Characterizing corners: " + str(self.corners)) debug.info(1,"Characterizing corners: " + str(self.corners))
is_first_corner = True
for (self.corner,lib_name) in zip(self.corners,self.lib_files): for (self.corner,lib_name) in zip(self.corners,self.lib_files):
run_start = time.time()
debug.info(1,"Corner: " + str(self.corner)) debug.info(1,"Corner: " + str(self.corner))
(self.process, self.voltage, self.temperature) = self.corner (self.process, self.voltage, self.temperature) = self.corner
self.lib = open(lib_name, "w") self.lib = open(lib_name, "w")
@ -132,7 +162,12 @@ class lib:
self.corner_name = lib_name.replace(self.out_dir,"").replace(".lib","") self.corner_name = lib_name.replace(self.out_dir,"").replace(".lib","")
self.characterize() self.characterize()
self.lib.close() self.lib.close()
self.parse_info(self.corner,lib_name) if self.pred_time == None:
total_time = time.time()-run_start
else:
total_time = self.pred_time
self.parse_info(self.corner,lib_name, is_first_corner, total_time)
is_first_corner = False
def characterize(self): def characterize(self):
""" Characterize the current corner. """ """ Characterize the current corner. """
@ -249,8 +284,8 @@ class lib:
self.lib.write(" default_max_fanout : 4.0 ;\n") self.lib.write(" default_max_fanout : 4.0 ;\n")
self.lib.write(" default_connection_class : universal ;\n\n") self.lib.write(" default_connection_class : universal ;\n\n")
self.lib.write(" voltage_map ( VDD, {} );\n".format(self.voltage)) self.lib.write(" voltage_map ( {0}, {1} );\n".format(self.vdd_name.upper(), self.voltage))
self.lib.write(" voltage_map ( GND, 0 );\n\n") self.lib.write(" voltage_map ( {0}, 0 );\n\n".format(self.gnd_name.upper()))
def create_list(self,values): def create_list(self,values):
""" Helper function to create quoted, line wrapped list """ """ Helper function to create quoted, line wrapped list """
@ -331,16 +366,16 @@ class lib:
self.lib.write(" base_type : array;\n") self.lib.write(" base_type : array;\n")
self.lib.write(" data_type : bit;\n") self.lib.write(" data_type : bit;\n")
self.lib.write(" bit_width : {0};\n".format(self.sram.word_size)) self.lib.write(" bit_width : {0};\n".format(self.sram.word_size))
self.lib.write(" bit_from : 0;\n") self.lib.write(" bit_from : {0};\n".format(self.sram.word_size - 1))
self.lib.write(" bit_to : {0};\n".format(self.sram.word_size - 1)) self.lib.write(" bit_to : 0;\n")
self.lib.write(" }\n\n") self.lib.write(" }\n\n")
self.lib.write(" type (addr){\n") self.lib.write(" type (addr){\n")
self.lib.write(" base_type : array;\n") self.lib.write(" base_type : array;\n")
self.lib.write(" data_type : bit;\n") self.lib.write(" data_type : bit;\n")
self.lib.write(" bit_width : {0};\n".format(self.sram.addr_size)) self.lib.write(" bit_width : {0};\n".format(self.sram.addr_size))
self.lib.write(" bit_from : 0;\n") self.lib.write(" bit_from : {0};\n".format(self.sram.addr_size - 1))
self.lib.write(" bit_to : {0};\n".format(self.sram.addr_size - 1)) self.lib.write(" bit_to : 0;\n")
self.lib.write(" }\n\n") self.lib.write(" }\n\n")
if self.sram.write_size: if self.sram.write_size:
@ -348,8 +383,8 @@ class lib:
self.lib.write(" base_type : array;\n") self.lib.write(" base_type : array;\n")
self.lib.write(" data_type : bit;\n") self.lib.write(" data_type : bit;\n")
self.lib.write(" bit_width : {0};\n".format(self.sram.num_wmasks)) self.lib.write(" bit_width : {0};\n".format(self.sram.num_wmasks))
self.lib.write(" bit_from : 0;\n") self.lib.write(" bit_from : {0};\n".format(self.sram.num_wmasks - 1))
self.lib.write(" bit_to : {0};\n".format(self.sram.num_wmasks - 1)) self.lib.write(" bit_to : 0;\n")
self.lib.write(" }\n\n") self.lib.write(" }\n\n")
@ -582,12 +617,12 @@ class lib:
self.lib.write(" }\n") self.lib.write(" }\n")
def write_pg_pin(self): def write_pg_pin(self):
self.lib.write(" pg_pin(vdd) {\n") self.lib.write(" pg_pin({0}) ".format(self.vdd_name) + "{\n")
self.lib.write(" voltage_name : VDD;\n") self.lib.write(" voltage_name : {};\n".format(self.vdd_name.upper()))
self.lib.write(" pg_type : primary_power;\n") self.lib.write(" pg_type : primary_power;\n")
self.lib.write(" }\n\n") self.lib.write(" }\n\n")
self.lib.write(" pg_pin(gnd) {\n") self.lib.write(" pg_pin({0}) ".format(self.gnd_name) + "{\n")
self.lib.write(" voltage_name : GND;\n") self.lib.write(" voltage_name : {};\n".format(self.gnd_name.upper()))
self.lib.write(" pg_type : primary_ground;\n") self.lib.write(" pg_type : primary_ground;\n")
self.lib.write(" }\n\n") self.lib.write(" }\n\n")
@ -599,11 +634,13 @@ class lib:
from .linear_regression import linear_regression as model from .linear_regression import linear_regression as model
elif model_name_lc == "elmore": elif model_name_lc == "elmore":
from .elmore import elmore as model from .elmore import elmore as model
elif model_name_lc == "neural_network":
from .neural_network import neural_network as model
else: else:
debug.error("{} model not recognized. See options.py for available models.".format(OPTS.model_name)) debug.error("{} model not recognized. See options.py for available models.".format(OPTS.model_name))
m = model(self.sram, self.sp_file, self.corner) m = model(self.sram, self.sp_file, self.corner)
char_results = m.get_lib_values(self.slews,self.loads) char_results = m.get_lib_values(self.load_slews)
else: else:
self.d = delay(self.sram, self.sp_file, self.corner) self.d = delay(self.sram, self.sp_file, self.corner)
@ -612,8 +649,21 @@ class lib:
else: else:
probe_address = "0" + "1" * (self.sram.addr_size - 1) probe_address = "0" + "1" * (self.sram.addr_size - 1)
probe_data = self.sram.word_size - 1 probe_data = self.sram.word_size - 1
char_results = self.d.analyze(probe_address, probe_data, self.slews, self.loads) char_results = self.d.analyze(probe_address, probe_data, self.load_slews)
self.char_sram_results, self.char_port_results = char_results self.char_sram_results, self.char_port_results = char_results
if 'sim_time' in self.char_sram_results:
self.pred_time = self.char_sram_results['sim_time']
# Add to the OPTS to be written out as part of the extended OPTS file
# FIXME: Temporarily removed from characterization output
# if not self.use_model:
# OPTS.sen_path_delays = self.char_sram_results["sen_path_measures"]
# OPTS.sen_path_names = self.char_sram_results["sen_path_names"]
# OPTS.bl_path_delays = self.char_sram_results["bl_path_measures"]
# OPTS.bl_path_names = self.char_sram_results["bl_path_names"]
def compute_setup_hold(self): def compute_setup_hold(self):
""" Do the analysis if we haven't characterized a FF yet """ """ Do the analysis if we haven't characterized a FF yet """
@ -621,35 +671,39 @@ class lib:
if not hasattr(self,"sh"): if not hasattr(self,"sh"):
self.sh = setup_hold(self.corner) self.sh = setup_hold(self.corner)
if self.use_model: if self.use_model:
self.times = self.sh.analytical_setuphold(self.slews,self.loads) self.times = self.sh.analytical_setuphold(self.slews,self.slews)
else: else:
self.times = self.sh.analyze(self.slews,self.slews) self.times = self.sh.analyze(self.slews,self.slews)
def parse_info(self,corner,lib_name): def parse_info(self,corner,lib_name, is_first_corner, time):
""" Copies important characterization data to datasheet.info to be added to datasheet """ """ Copies important characterization data to datasheet.info to be added to datasheet """
if OPTS.output_datasheet_info: if OPTS.output_datasheet_info:
datasheet_path = OPTS.output_path datasheet_path = OPTS.output_path
else: else:
datasheet_path = OPTS.openram_temp datasheet_path = OPTS.openram_temp
datasheet = open(datasheet_path +'/datasheet.info', 'a+') # Open for write and truncate to not conflict with a previous run using the same name
if is_first_corner:
datasheet = open(datasheet_path +'/datasheet.info', 'w')
else:
datasheet = open(datasheet_path +'/datasheet.info', 'a+')
self.write_inp_params_datasheet(datasheet, corner, lib_name) self.write_inp_params_datasheet(datasheet, corner, lib_name)
self.write_signal_from_ports(datasheet, self.write_signal_from_ports(datasheet,
"din{1}[{0}:0]".format(self.sram.word_size - 1, '{}'), "din{1}[{0}:0]".format(self.sram.word_size - 1, '{}'),
self.write_ports, self.write_ports,
"setup_times_LH", "setup_times_LH",
"setup_times_HL", "setup_times_HL",
"hold_times_LH", "hold_times_LH",
"hold_times_HL") "hold_times_HL")
# self.write_signal_from_ports(datasheet, # self.write_signal_from_ports(datasheet,
# "dout{1}[{0}:0]".format(self.sram.word_size - 1, '{}'), # "dout{1}[{0}:0]".format(self.sram.word_size - 1, '{}'),
# self.read_ports, # self.read_ports,
# "delay_lh", # "delay_lh",
# "delay_hl", # "delay_hl",
# "slew_lh", # "slew_lh",
# "slew_hl") # "slew_hl")
for port in self.all_ports: for port in self.all_ports:
#dout timing #dout timing
if port in self.read_ports: if port in self.read_ports:
@ -666,41 +720,41 @@ class lib:
min(list(map(round_time,self.char_port_results[port]["slew_hl"]))), min(list(map(round_time,self.char_port_results[port]["slew_hl"]))),
max(list(map(round_time,self.char_port_results[port]["slew_hl"]))) max(list(map(round_time,self.char_port_results[port]["slew_hl"])))
)) ))
self.write_signal_from_ports(datasheet, self.write_signal_from_ports(datasheet,
"csb{}", "csb{}",
self.all_ports, self.all_ports,
"setup_times_LH", "setup_times_LH",
"setup_times_HL", "setup_times_HL",
"hold_times_LH", "hold_times_LH",
"hold_times_HL") "hold_times_HL")
self.write_signal_from_ports(datasheet,
"addr{1}[{0}:0]".format(self.sram.addr_size - 1, '{}'),
self.all_ports,
"setup_times_LH",
"setup_times_HL",
"hold_times_LH",
"hold_times_HL")
self.write_signal_from_ports(datasheet, self.write_signal_from_ports(datasheet,
"web{}", "addr{1}[{0}:0]".format(self.sram.addr_size - 1, '{}'),
self.readwrite_ports, self.all_ports,
"setup_times_LH", "setup_times_LH",
"setup_times_HL", "setup_times_HL",
"hold_times_LH", "hold_times_LH",
"hold_times_HL") "hold_times_HL")
self.write_signal_from_ports(datasheet,
"web{}",
self.readwrite_ports,
"setup_times_LH",
"setup_times_HL",
"hold_times_LH",
"hold_times_HL")
self.write_power_datasheet(datasheet) self.write_power_datasheet(datasheet)
self.write_model_params(datasheet) self.write_model_params(datasheet, time)
datasheet.write("END\n") datasheet.write("END\n")
datasheet.close() datasheet.close()
def write_inp_params_datasheet(self, datasheet, corner, lib_name): def write_inp_params_datasheet(self, datasheet, corner, lib_name):
if OPTS.is_unit_test: if OPTS.is_unit_test:
git_id = 'FFFFFFFFFFFFFFFFFFFF' git_id = 'FFFFFFFFFFFFFFFFFFFF'
@ -720,7 +774,7 @@ class lib:
debug.warning("Failed to retrieve git id") debug.warning("Failed to retrieve git id")
git_id = 'Failed to retrieve' git_id = 'Failed to retrieve'
current_time = datetime.date.today() current_time = datetime.date.today()
# write static information to be parser later # write static information to be parser later
datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},".format( datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},".format(
OPTS.output_name, OPTS.output_name,
@ -748,8 +802,8 @@ class lib:
# write area # write area
datasheet.write(str(self.sram.width * self.sram.height) + ',') datasheet.write(str(self.sram.width * self.sram.height) + ',')
def write_signal_from_ports(self, datasheet, signal, ports, time_pos_1, time_pos_2, time_pos_3, time_pos_4): def write_signal_from_ports(self, datasheet, signal, ports, time_pos_1, time_pos_2, time_pos_3, time_pos_4):
for port in ports: for port in ports:
datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},".format( datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},".format(
signal.format(port), signal.format(port),
@ -766,8 +820,8 @@ class lib:
max(list(map(round_time,self.times[time_pos_4]))) max(list(map(round_time,self.times[time_pos_4])))
)) ))
def write_power_datasheet(self, datasheet): def write_power_datasheet(self, datasheet):
# write power information # write power information
for port in self.all_ports: for port in self.all_ports:
name = '' name = ''
@ -806,31 +860,30 @@ class lib:
control_str += ' & csb{0}'.format(i) control_str += ' & csb{0}'.format(i)
datasheet.write("{0},{1},{2},".format('leak', control_str, self.char_sram_results["leakage_power"])) datasheet.write("{0},{1},{2},".format('leak', control_str, self.char_sram_results["leakage_power"]))
def write_model_params(self, datasheet): def write_model_params(self, datasheet, time):
"""Write values which will be used in the analytical model as inputs""" """Write values which will be used in the analytical model as inputs"""
datasheet.write("{0},{1},".format('sim_time', time))
datasheet.write("{0},{1},".format('words_per_row', OPTS.words_per_row)) datasheet.write("{0},{1},".format('words_per_row', OPTS.words_per_row))
datasheet.write("{0},{1},".format('slews', list(self.slews))) datasheet.write("{0},{1},".format('slews', list(self.slews)))
datasheet.write("{0},{1},".format('loads', list(self.loads))) datasheet.write("{0},{1},".format('loads', list(self.loads)))
for port in self.read_ports: for port in self.read_ports:
datasheet.write("{0},{1},".format('cell_rise_{}'.format(port), self.char_port_results[port]["delay_lh"])) datasheet.write("{0},{1},".format('cell_rise_{}'.format(port), self.char_port_results[port]["delay_lh"]))
datasheet.write("{0},{1},".format('cell_fall_{}'.format(port), self.char_port_results[port]["delay_hl"])) datasheet.write("{0},{1},".format('cell_fall_{}'.format(port), self.char_port_results[port]["delay_hl"]))
datasheet.write("{0},{1},".format('rise_transition_{}'.format(port), self.char_port_results[port]["slew_lh"])) datasheet.write("{0},{1},".format('rise_transition_{}'.format(port), self.char_port_results[port]["slew_lh"]))
datasheet.write("{0},{1},".format('fall_transition_{}'.format(port), self.char_port_results[port]["slew_hl"])) datasheet.write("{0},{1},".format('fall_transition_{}'.format(port), self.char_port_results[port]["slew_hl"]))
for port in self.write_ports: for port in self.write_ports:
write1_power = np.mean(self.char_port_results[port]["write1_power"]) write1_power = np.mean(self.char_port_results[port]["write1_power"])
write0_power = np.mean(self.char_port_results[port]["write0_power"]) write0_power = np.mean(self.char_port_results[port]["write0_power"])
datasheet.write("{0},{1},".format('write_rise_power_{}'.format(port), write1_power)) datasheet.write("{0},{1},".format('write_rise_power_{}'.format(port), write1_power))
#FIXME: should be write_fall_power #FIXME: should be write_fall_power
datasheet.write("{0},{1},".format('read_fall_power_{}'.format(port), write0_power)) datasheet.write("{0},{1},".format('write_fall_power_{}'.format(port), write0_power))
for port in self.read_ports: for port in self.read_ports:
read1_power = np.mean(self.char_port_results[port]["read1_power"]) read1_power = np.mean(self.char_port_results[port]["read1_power"])
read0_power = np.mean(self.char_port_results[port]["read0_power"]) read0_power = np.mean(self.char_port_results[port]["read0_power"])
datasheet.write("{0},{1},".format('read_rise_power_{}'.format(port), read1_power)) datasheet.write("{0},{1},".format('read_rise_power_{}'.format(port), read1_power))
#FIXME: should be read_fall_power #FIXME: should be read_fall_power
datasheet.write("{0},{1},".format('write_fall_power_{}'.format(port), read0_power)) datasheet.write("{0},{1},".format('read_fall_power_{}'.format(port), read0_power))

View File

@ -7,6 +7,7 @@
# #
from .regression_model import regression_model from .regression_model import regression_model
from sklearn.linear_model import Ridge
from globals import OPTS from globals import OPTS
import debug import debug
@ -18,12 +19,16 @@ class linear_regression(regression_model):
def __init__(self, sram, spfile, corner): def __init__(self, sram, spfile, corner):
super().__init__(sram, spfile, corner) super().__init__(sram, spfile, corner)
def get_model(self):
return Ridge()
def generate_model(self, features, labels): def generate_model(self, features, labels):
""" """
Supervised training of model. Supervised training of model.
""" """
model = LinearRegression() #model = LinearRegression()
model = self.get_model()
model.fit(features, labels) model.fit(features, labels)
return model return model

View File

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

View File

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

View File

@ -9,10 +9,7 @@
from .regression_model import regression_model from .regression_model import regression_model
from globals import OPTS from globals import OPTS
import debug import debug
from sklearn.neural_network import MLPRegressor
from tensorflow import keras
from tensorflow.keras import layers
import tensorflow as tf
class neural_network(regression_model): class neural_network(regression_model):
@ -20,21 +17,19 @@ class neural_network(regression_model):
def __init__(self, sram, spfile, corner): def __init__(self, sram, spfile, corner):
super().__init__(sram, spfile, corner) super().__init__(sram, spfile, corner)
def get_model(self):
return MLPRegressor(solver='lbfgs', alpha=1e-5,
hidden_layer_sizes=(40, 40, 40, 40), random_state=1)
def generate_model(self, features, labels): def generate_model(self, features, labels):
""" """
Supervised training of model. Training multilayer model
""" """
model = keras.Sequential([ flat_labels = np.ravel(labels)
layers.Dense(32, activation=tf.nn.relu, input_shape=[features.shape[1]]), model = self.get_model()
layers.Dense(32, activation=tf.nn.relu), model.fit(features, flat_labels)
layers.Dense(32, activation=tf.nn.relu),
layers.Dense(1)
])
optimizer = keras.optimizers.RMSprop(0.0099)
model.compile(loss='mean_squared_error', optimizer=optimizer)
model.fit(features, labels, epochs=100, verbose=0)
return model return model
def model_prediction(self, model, features): def model_prediction(self, model, features):
@ -44,5 +39,6 @@ class neural_network(regression_model):
""" """
pred = model.predict(features) pred = model.predict(features)
return pred reshape_pred = np.reshape(pred, (len(pred),1))
return reshape_pred

View File

@ -13,7 +13,8 @@ import debug
import math import math
relative_data_path = "/sim_data" relative_data_path = "sim_data"
data_file = "sim_data.csv"
data_fnames = ["rise_delay.csv", data_fnames = ["rise_delay.csv",
"fall_delay.csv", "fall_delay.csv",
"rise_slew.csv", "rise_slew.csv",
@ -22,7 +23,8 @@ data_fnames = ["rise_delay.csv",
"write0_power.csv", "write0_power.csv",
"read1_power.csv", "read1_power.csv",
"read0_power.csv", "read0_power.csv",
"leakage_data.csv"] "leakage_data.csv",
"sim_time.csv"]
# Positions must correspond to data_fname list # Positions must correspond to data_fname list
lib_dnames = ["delay_lh", lib_dnames = ["delay_lh",
"delay_hl", "delay_hl",
@ -32,14 +34,15 @@ lib_dnames = ["delay_lh",
"write0_power", "write0_power",
"read1_power", "read1_power",
"read0_power", "read0_power",
"leakage_power"] "leakage_power",
"sim_time"]
# Check if another data dir was specified # Check if another data dir was specified
if OPTS.sim_data_path == None: if OPTS.sim_data_path == None:
data_dir = OPTS.openram_tech+relative_data_path data_dir = OPTS.openram_tech+relative_data_path
else: else:
data_dir = OPTS.sim_data_path data_dir = OPTS.sim_data_path
data_paths = {dname:data_dir +'/'+fname for dname, fname in zip(lib_dnames, data_fnames)} data_path = data_dir + '/' + data_file
class regression_model(simulation): class regression_model(simulation):
@ -47,21 +50,25 @@ class regression_model(simulation):
super().__init__(sram, spfile, corner) super().__init__(sram, spfile, corner)
self.set_corner(corner) self.set_corner(corner)
def get_lib_values(self, slews, loads): def get_lib_values(self, load_slews):
""" """
A model and prediction is created for each output needed for the LIB A model and prediction is created for each output needed for the LIB
""" """
debug.info(1, "Characterizing SRAM using linear regression models.") debug.info(1, "Characterizing SRAM using regression models.")
log_num_words = math.log(OPTS.num_words, 2) log_num_words = math.log(OPTS.num_words, 2)
model_inputs = [log_num_words, model_inputs = [log_num_words,
OPTS.word_size, OPTS.word_size,
OPTS.words_per_row, OPTS.words_per_row,
self.sram.width * self.sram.height, OPTS.local_array_size,
process_transform[self.process], process_transform[self.process],
self.vdd_voltage, self.vdd_voltage,
self.temperature] self.temperature]
# Area removed for now
# self.sram.width * self.sram.height,
# Include above inputs, plus load and slew which are added below
self.num_inputs = len(model_inputs)+2
self.create_measurement_names() self.create_measurement_names()
models = self.train_models() models = self.train_models()
@ -69,36 +76,35 @@ class regression_model(simulation):
port_data = self.get_empty_measure_data_dict() port_data = self.get_empty_measure_data_dict()
debug.info(1, 'Slew, Load, Port, Delay(ns), Slew(ns)') debug.info(1, 'Slew, Load, Port, Delay(ns), Slew(ns)')
max_delay = 0.0 max_delay = 0.0
for slew in slews: for load, slew in load_slews:
for load in loads: # List returned with value order being delay, power, leakage, slew
# List returned with value order being delay, power, leakage, slew sram_vals = self.get_predictions(model_inputs+[slew, load], models)
sram_vals = self.get_predictions(model_inputs+[slew, load], models) # 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: port_data[port]['delay_lh'].append(sram_vals['rise_delay'])
port_data[port]['delay_lh'].append(sram_vals['delay_lh']) port_data[port]['delay_hl'].append(sram_vals['fall_delay'])
port_data[port]['delay_hl'].append(sram_vals['delay_hl']) port_data[port]['slew_lh'].append(sram_vals['rise_slew'])
port_data[port]['slew_lh'].append(sram_vals['slew_lh']) port_data[port]['slew_hl'].append(sram_vals['fall_slew'])
port_data[port]['slew_hl'].append(sram_vals['slew_hl'])
port_data[port]['write1_power'].append(sram_vals['write1_power'])
port_data[port]['write0_power'].append(sram_vals['write0_power'])
port_data[port]['read1_power'].append(sram_vals['read1_power'])
port_data[port]['read0_power'].append(sram_vals['read0_power'])
# Disabled power not modeled. Copied from other power predictions
port_data[port]['disabled_write1_power'].append(sram_vals['write1_power'])
port_data[port]['disabled_write0_power'].append(sram_vals['write0_power'])
port_data[port]['disabled_read1_power'].append(sram_vals['read1_power'])
port_data[port]['disabled_read0_power'].append(sram_vals['read0_power'])
port_data[port]['write1_power'].append(sram_vals['write1_power']) debug.info(1, '{}, {}, {}, {}, {}'.format(slew,
port_data[port]['write0_power'].append(sram_vals['write0_power']) load,
port_data[port]['read1_power'].append(sram_vals['read1_power']) port,
port_data[port]['read0_power'].append(sram_vals['read0_power']) sram_vals['rise_delay'],
sram_vals['rise_slew']))
# Disabled power not modeled. Copied from other power predictions
port_data[port]['disabled_write1_power'].append(sram_vals['write1_power'])
port_data[port]['disabled_write0_power'].append(sram_vals['write0_power'])
port_data[port]['disabled_read1_power'].append(sram_vals['read1_power'])
port_data[port]['disabled_read0_power'].append(sram_vals['read0_power'])
debug.info(1, '{}, {}, {}, {}, {}'.format(slew,
load,
port,
sram_vals['delay_lh'],
sram_vals['slew_lh']))
# Estimate the period as double the delay with margin # Estimate the period as double the delay with margin
period_margin = 0.1 period_margin = 0.1
sram_data = {"min_period": sram_vals['delay_lh'] * 2, sram_data = {"min_period": sram_vals['rise_delay'] * 2,
"leakage_power": sram_vals["leakage_power"]} "leakage_power": sram_vals["leakage_power"]}
debug.info(2, "SRAM Data:\n{}".format(sram_data)) debug.info(2, "SRAM Data:\n{}".format(sram_data))
@ -111,30 +117,92 @@ class regression_model(simulation):
Generate a model and prediction for LIB output Generate a model and prediction for LIB output
""" """
#Scaled the inputs using first data file as a reference #Scaled the inputs using first data file as a reference
data_name = lib_dnames[0] scaled_inputs = np.asarray([scale_input_datapoint(model_inputs, data_path)])
scaled_inputs = np.asarray([scale_input_datapoint(model_inputs, data_paths[data_name])])
predictions = {} predictions = {}
for dname in data_paths.keys(): out_pos = 0
path = data_paths[dname] for dname in self.output_names:
m = models[dname] m = models[dname]
features, labels = get_scaled_data(path)
scaled_pred = self.model_prediction(m, scaled_inputs) scaled_pred = self.model_prediction(m, scaled_inputs)
pred = unscale_data(scaled_pred.tolist(), path) pred = unscale_data(scaled_pred.tolist(), data_path, pos=self.num_inputs+out_pos)
debug.info(2,"Unscaled Prediction = {}".format(pred)) debug.info(2,"Unscaled Prediction = {}".format(pred))
predictions[dname] = pred[0][0] predictions[dname] = pred[0]
out_pos+=1
return predictions return predictions
def train_models(self): def train_models(self):
""" """
Generate and return models Generate and return models
""" """
self.output_names = get_data_names(data_path)[self.num_inputs:]
data = get_scaled_data(data_path)
features, labels = data[:, :self.num_inputs], data[:,self.num_inputs:]
output_num = 0
models = {} models = {}
for dname, dpath in data_paths.items(): for o_name in self.output_names:
features, labels = get_scaled_data(dpath) output_label = labels[:,output_num]
model = self.generate_model(features, labels) model = self.generate_model(features, output_label)
models[dname] = model models[o_name] = model
output_num+=1
return models return models
def score_model(self):
num_inputs = 9 #FIXME - should be defined somewhere else
self.output_names = get_data_names(data_path)[num_inputs:]
data = get_scaled_data(data_path)
features, labels = data[:, :num_inputs], data[:,num_inputs:]
output_num = 0
models = {}
debug.info(1, "Output name, score")
for o_name in self.output_names:
output_label = labels[:,output_num]
model = self.generate_model(features, output_label)
scr = model.score(features, output_label)
debug.info(1, "{}, {}".format(o_name, scr))
output_num+=1
def cross_validation(self, test_only=None):
"""Wrapper for sklean cross validation function for OpenRAM regression models.
Returns the mean accuracy for each model/output."""
from sklearn.model_selection import cross_val_score
untrained_model = self.get_model()
num_inputs = 9 #FIXME - should be defined somewhere else
self.output_names = get_data_names(data_path)[num_inputs:]
data = get_scaled_data(data_path)
features, labels = data[:, :num_inputs], data[:,num_inputs:]
output_num = 0
models = {}
debug.info(1, "Output name, mean_accuracy, std_dev")
model_scores = {}
if test_only != None:
test_outputs = test_only
else:
test_outputs = self.output_names
for o_name in test_outputs:
output_label = labels[:,output_num]
scores = cross_val_score(untrained_model, features, output_label, cv=10)
debug.info(1, "{}, {}, {}".format(o_name, scores.mean(), scores.std()))
model_scores[o_name] = scores.mean()
output_num+=1
return model_scores
# Fixme - only will work for sklearn regression models
def save_model(self, model_name, model):
try:
OPTS.model_dict
except AttributeError:
OPTS.model_dict = {}
OPTS.model_dict[model_name+"_coef"] = list(model.coef_[0])
debug.info(1,"Coefs of {}:{}".format(model_name,OPTS.model_dict[model_name+"_coef"]))
OPTS.model_dict[model_name+"_intercept"] = float(model.intercept_)

View File

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

View File

@ -37,6 +37,8 @@ class simulation():
self.read_ports = self.sram.read_ports self.read_ports = self.sram.read_ports
self.write_ports = self.sram.write_ports self.write_ports = self.sram.write_ports
self.words_per_row = self.sram.words_per_row self.words_per_row = self.sram.words_per_row
self.num_rows = self.sram.num_rows
self.num_cols = self.sram.num_cols
if self.write_size: if self.write_size:
self.num_wmasks = int(math.ceil(self.word_size / self.write_size)) self.num_wmasks = int(math.ceil(self.word_size / self.write_size))
else: else:
@ -294,7 +296,8 @@ class simulation():
self.add_data(data, port) self.add_data(data, port)
self.add_address(address, port) self.add_address(address, port)
self.add_wmask(wmask, port) self.add_wmask(wmask, port)
self.add_spare_wen("1" * self.num_spare_cols, port) # Disable spare writes for now
self.add_spare_wen("0" * self.num_spare_cols, port)
def add_read_one_port(self, comment, address, port): def add_read_one_port(self, comment, address, port):
""" Add the control values for a read cycle. Does not increment the period. """ """ Add the control values for a read cycle. Does not increment the period. """
@ -369,6 +372,38 @@ class simulation():
time_spacing, time_spacing,
comment)) comment))
def combine_word(self, spare, word):
if len(spare) > 0:
return spare + "+" + word
return word
def format_value(self, value):
""" Format in better readable manner """
def delineate(word):
# Create list of chars in reverse order
split_word = list(reversed([x for x in word]))
# Add underscore every 4th char
split_word2 = [x + '_' * (n != 0 and n % 4 == 0) for n, x in enumerate(split_word)]
# Join the word unreversed back together
new_word = ''.join(reversed(split_word2))
return(new_word)
# Split extra cols
if self.num_spare_cols > 0:
vals = value[self.num_spare_cols:]
spare_vals = value[:self.num_spare_cols]
else:
vals = value
spare_vals = ""
# Insert underscores
vals = delineate(vals)
spare_vals = delineate(spare_vals)
return self.combine_word(spare_vals, vals)
def gen_cycle_comment(self, op, word, addr, wmask, port, t_current): def gen_cycle_comment(self, op, word, addr, wmask, port, t_current):
if op == "noop": if op == "noop":
str = "\tIdle during cycle {0} ({1}ns - {2}ns)" str = "\tIdle during cycle {0} ({1}ns - {2}ns)"
@ -473,7 +508,6 @@ class simulation():
if not OPTS.use_pex or (OPTS.use_pex and OPTS.pex_exe[0] == "calibre"): if not OPTS.use_pex or (OPTS.use_pex and OPTS.pex_exe[0] == "calibre"):
self.graph.get_all_paths('{}{}'.format("clk", port), self.graph.get_all_paths('{}{}'.format("clk", port),
'{}{}_{}'.format(self.dout_name, port, self.probe_data)) '{}{}_{}'.format(self.dout_name, port, self.probe_data))
sen_with_port = self.get_sen_name(self.graph.all_paths) sen_with_port = self.get_sen_name(self.graph.all_paths)
if sen_with_port.endswith(str(port)): if sen_with_port.endswith(str(port)):
self.sen_name = sen_with_port[:-len(str(port))] self.sen_name = sen_with_port[:-len(str(port))]
@ -481,38 +515,32 @@ class simulation():
self.sen_name = sen_with_port self.sen_name = sen_with_port
debug.warning("Error occurred while determining SEN name. Can cause faults in simulation.") debug.warning("Error occurred while determining SEN name. Can cause faults in simulation.")
debug.info(2, "s_en name = {}".format(self.sen_name))
column_addr = self.get_column_addr() column_addr = self.get_column_addr()
bl_name_port, br_name_port = self.get_bl_name(self.graph.all_paths, port) bl_name_port, br_name_port = self.get_bl_name(self.graph.all_paths, port)
port_pos = -1 - len(str(column_addr)) - len(str(port)) port_pos = -1 - len(str(column_addr)) - len(str(port))
if bl_name_port.endswith(str(port) + "_" + str(column_addr)): if bl_name_port.endswith(str(port) + "_" + str(self.bitline_column)): # single port SRAM case, bl will not be numbered eg bl_0
self.bl_name = bl_name_port[:port_pos] + "{}" + bl_name_port[port_pos + len(str(port)):]
elif not bl_name_port[port_pos].isdigit(): # single port SRAM case, bl will not be numbered eg bl_0
self.bl_name = bl_name_port self.bl_name = bl_name_port
else: else:
self.bl_name = bl_name_port self.bl_name = bl_name_port
debug.warning("Error occurred while determining bitline names. Can cause faults in simulation.") debug.warning("Error occurred while determining bitline names. Can cause faults in simulation.")
if br_name_port.endswith(str(port) + "_" + str(column_addr)): if br_name_port.endswith(str(port) + "_" + str(self.bitline_column)): # single port SRAM case, bl will not be numbered eg bl_0
self.br_name = br_name_port[:port_pos] + "{}" + br_name_port[port_pos + len(str(port)):]
elif not br_name_port[port_pos].isdigit(): # single port SRAM case, bl will not be numbered eg bl_0
self.br_name = br_name_port self.br_name = br_name_port
else: else:
self.br_name = br_name_port self.br_name = br_name_port
debug.warning("Error occurred while determining bitline names. Can cause faults in simulation.") debug.warning("Error occurred while determining bitline names. Can cause faults in simulation.")
debug.info(2, "bl name={}, br name={}".format(self.bl_name, self.br_name))
else: else:
self.graph.get_all_paths('{}{}'.format("clk", port), self.graph.get_all_paths('{}{}'.format("clk", port),
'{}{}_{}'.format(self.dout_name, port, self.probe_data)) '{}{}_{}'.format(self.dout_name, port, self.probe_data))
self.sen_name = self.get_sen_name(self.graph.all_paths) self.sen_name = self.get_sen_name(self.graph.all_paths)
debug.info(2, "s_en name = {}".format(self.sen_name)) #debug.info(2, "s_en {}".format(self.sen_name))
self.bl_name = "bl{0}_{1}".format(port, OPTS.word_size - 1) self.bl_name = "bl{0}_{1}".format(port, OPTS.word_size - 1)
self.br_name = "br{0}_{1}".format(port, OPTS.word_size - 1) self.br_name = "br{0}_{1}".format(port, OPTS.word_size - 1)
debug.info(2, "bl name={}, br name={}".format(self.bl_name, self.br_name)) # debug.info(2, "bl name={0}".format(self.bl_name))
# debug.info(2, "br name={0}".format(self.br_name))
def get_sen_name(self, paths, assumed_port=None): def get_sen_name(self, paths, assumed_port=None):
""" """
@ -535,8 +563,13 @@ class simulation():
Creates timing graph to generate the timing paths for the SRAM output. Creates timing graph to generate the timing paths for the SRAM output.
""" """
#Make exclusions dependent on the bit being tested.
self.sram.clear_exclude_bits() # Removes previous bit exclusions self.sram.clear_exclude_bits() # Removes previous bit exclusions
self.sram.graph_exclude_bits(self.wordline_row, self.bitline_column) self.sram.graph_exclude_bits(self.wordline_row, self.bitline_column)
port=self.read_ports[0] #FIXME, port_data requires a port specification, assuming single port for now
if self.words_per_row > 1:
self.sram.graph_clear_column_mux(port)
self.sram.graph_exclude_column_mux(self.bitline_column, port)
# Generate new graph every analysis as edges might change depending on test bit # Generate new graph every analysis as edges might change depending on test bit
self.graph = graph_util.timing_graph() self.graph = graph_util.timing_graph()
@ -576,7 +609,11 @@ class simulation():
""" """
Gets the signal name associated with the bitlines in the bank. Gets the signal name associated with the bitlines in the bank.
""" """
cell_mod = factory.create(module_type=OPTS.bitcell) # FIXME: change to a solution that does not depend on the technology
if OPTS.tech_name == "sky130" and len(self.all_ports) == 1:
cell_mod = factory.create(module_type=OPTS.bitcell, version="opt1")
else:
cell_mod = factory.create(module_type=OPTS.bitcell)
cell_bl = cell_mod.get_bl_name(port) cell_bl = cell_mod.get_bl_name(port)
cell_br = cell_mod.get_br_name(port) cell_br = cell_mod.get_br_name(port)
@ -586,16 +623,16 @@ 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):
"""Make a dict of lists for each type of delay and power measurement to append results to""" """Make a dict of lists for each type of delay and power measurement to append results to"""
measure_names = self.delay_meas_names + self.power_meas_names measure_names = self.delay_meas_names + self.power_meas_names
# Create list of dicts. List lengths is # of ports. Each dict maps the measurement names to lists. # Create list of dicts. List lengths is # of ports. Each dict maps the measurement names to lists.
measure_data = [{mname: [] for mname in measure_names} for i in self.all_ports] measure_data = [{mname: [] for mname in measure_names} for i in self.all_ports]
return measure_data return measure_data
def sum_delays(self, delays): def sum_delays(self, delays):
"""Adds the delays (delay_data objects) so the correct slew is maintained""" """Adds the delays (delay_data objects) so the correct slew is maintained"""
@ -604,5 +641,3 @@ class simulation():
for i in range(1, len(delays)): for i in range(1, len(delays)):
delay+=delays[i] delay+=delays[i]
return delay return delay

View File

@ -146,7 +146,7 @@ class stimuli():
edge. The first clk_time should be 0 and is the initial time that corresponds edge. The first clk_time should be 0 and is the initial time that corresponds
to the initial value. to the initial value.
""" """
# the initial value is not a clock time
str = "Clock and data value lengths don't match. {0} clock values, {1} data values for {2}" str = "Clock and data value lengths don't match. {0} clock values, {1} data values for {2}"
debug.check(len(clk_times)==len(data_values), debug.check(len(clk_times)==len(data_values),
str.format(len(clk_times), str.format(len(clk_times),
@ -169,7 +169,7 @@ class stimuli():
def gen_constant(self, sig_name, v_val): def gen_constant(self, sig_name, v_val):
""" Generates a constant signal with reference voltage and the voltage value """ """ Generates a constant signal with reference voltage and the voltage value """
self.sf.write("V{0} {0} 0 DC {1}\n".format(sig_name, v_val)) self.sf.write("V{0} {0} 0 DC {1}\n".format(sig_name, v_val))
def get_voltage(self, value): def get_voltage(self, value):
if value == "0" or value == 0: if value == "0" or value == 0:
return 0 return 0
@ -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,14 @@ 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} FIND v({1}) AT={2}n\n\n".format(meas_name.lower(), dout, (t_initial + t_final) / 2)
#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):
@ -236,18 +237,20 @@ class stimuli():
reltol = 0.005 # 0.5% reltol = 0.005 # 0.5%
else: else:
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
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)) # Format: .tran tstep tstop < tstart < tmax >>
self.sf.write(".TRAN {0}p {1}n 0n {0}p 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
# which is more accurate, but slower than the default trapezoid method # which is more accurate, but slower than the default trapezoid method
# Do not remove this or it may not converge due to some "pa_00" nodes # Do not remove this or it may not converge due to some "pa_00" nodes
# 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 +258,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 +267,22 @@ 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(".TRAN {0}p {1}n UIC\n".format(timestep, end_time)) self.sf.write(".TEMP {}\n".format(self.temperature))
# Format: .tran tstep tstop < tstart < tmax >>
self.sf.write(".TRAN {0}p {1}n 0n {0}p 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(".OPTIONS TIMEINT RELTOL=1e-6 ABSTOL=1e-10 method=gear minorder=2\n")
# Format: .TRAN <initial step> <final time> <start time> <step ceiling>
self.sf.write(".TRAN {0}p {1}n 0n {0}p\n".format(timestep, end_time))
elif OPTS.spice_name:
debug.error("Unkown spice simulator {}".format(OPTS.spice_name), -1)
# 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 +290,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")
@ -292,19 +304,27 @@ class stimuli():
self.sf.write("* {} process corner\n".format(self.process)) self.sf.write("* {} process corner\n".format(self.process))
for item in self.device_libraries: for item in self.device_libraries:
if OPTS.spice_name:
item[0] = item[0].replace("SIMULATOR", OPTS.spice_name.lower())
else:
item[0] = item[0].replace("SIMULATOR", "ngspice")
if os.path.isfile(item[0]): if os.path.isfile(item[0]):
self.sf.write(".lib \"{0}\" {1}\n".format(item[0], item[1])) self.sf.write(".lib \"{0}\" {1}\n".format(item[0], item[1]))
else: else:
debug.error("Could not find spice library: {0}\nSet SPICE_MODEL_DIR to over-ride path.\n".format(item[0])) debug.error("Could not find spice library: {0}\nSet SPICE_MODEL_DIR to over-ride path.\n".format(item[0]), -1)
includes = self.device_models + [circuit] includes = self.device_models + [circuit]
for item in list(includes): for item in list(includes):
if OPTS.spice_name:
item = item.replace("SIMULATOR", OPTS.spice_name.lower())
else:
item = item.replace("SIMULATOR", "ngspice")
self.sf.write(".include \"{0}\"\n".format(item)) self.sf.write(".include \"{0}\"\n".format(item))
def add_comment(self, msg): def add_comment(self, msg):
self.sf.write(msg + "\n") self.sf.write(msg + "\n")
def write_supply(self): def write_supply(self):
""" Writes supply voltage statements """ """ Writes supply voltage statements """
gnd_node_name = "0" gnd_node_name = "0"
@ -312,7 +332,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 +372,20 @@ 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 = ""
# Xyce can save a raw file while doing timing, so keep it around
cmd = "{0} {1} -r {3}timing.raw -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
@ -368,7 +405,7 @@ class stimuli():
spice_stdout = open("{0}spice_stdout.log".format(OPTS.openram_temp), 'w') spice_stdout = open("{0}spice_stdout.log".format(OPTS.openram_temp), 'w')
spice_stderr = open("{0}spice_stderr.log".format(OPTS.openram_temp), 'w') spice_stderr = open("{0}spice_stderr.log".format(OPTS.openram_temp), 'w')
debug.info(3, cmd) debug.info(2, cmd)
retcode = subprocess.call(cmd, stdout=spice_stdout, stderr=spice_stderr, shell=True) retcode = subprocess.call(cmd, stdout=spice_stdout, stderr=spice_stderr, shell=True)
spice_stdout.close() spice_stdout.close()
@ -380,5 +417,3 @@ class stimuli():
end_time = datetime.datetime.now() end_time = datetime.datetime.now()
delta_time = round((end_time - start_time).total_seconds(), 1) delta_time = round((end_time - start_time).total_seconds(), 1)
debug.info(2, "*** Spice: {} seconds".format(delta_time)) debug.info(2, "*** Spice: {} seconds".format(delta_time))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,21 @@
# 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 = "ring"
#route_supplies = "left"
check_lvsdrc = True
uniquify = True
#perimeter_pins = False
#netlist_only = True
#analytical_delay = False
output_name = "{tech_name}_sram_{human_byte_size}_{ports_human}_{word_size}x{num_words}_{write_size}".format(**locals())
output_path = "macro/{output_name}".format(**locals())

View File

@ -79,7 +79,7 @@ class Gds2reader:
recordLength = struct.unpack(">h",recordLengthAscii) #gives us a tuple with a short int inside recordLength = struct.unpack(">h",recordLengthAscii) #gives us a tuple with a short int inside
offset_int = int(recordLength[0]) # extract length offset_int = int(recordLength[0]) # extract length
offset += offset_int # count offset offset += offset_int # count offset
if(self.debugToTerminal==1): if(self.debugToTerminal==1):
print("Offset: " + str(offset)) #print out the record numbers for de-bugging print("Offset: " + str(offset)) #print out the record numbers for de-bugging
record = self.fileHandle.read(recordLength[0]-2) #read the rest of it (first 2 bytes were already read) record = self.fileHandle.read(recordLength[0]-2) #read the rest of it (first 2 bytes were already read)
return record return record
@ -669,11 +669,11 @@ class Gds2reader:
else: else:
print("There was an error parsing the GDS header. Aborting...") print("There was an error parsing the GDS header. Aborting...")
def loadFromFile(self, fileName): def loadFromFile(self, fileName, special_purposes={}):
self.fileHandle = open(fileName,"rb") self.fileHandle = open(fileName,"rb")
self.readGds2() self.readGds2()
self.fileHandle.close() self.fileHandle.close()
self.layoutObject.initialize() self.layoutObject.initialize(special_purposes)
############################################## ##############################################

View File

@ -81,6 +81,42 @@ class VlsiLayout:
coordinatesRotate.extend((newX,newY)) coordinatesRotate.extend((newX,newY))
return coordinatesRotate return coordinatesRotate
def uniquify(self, prefix_name=None):
new_structures = {}
if self.rootStructureName[-1] == "\x00":
prefix = self.rootStructureName[0:-1] + "_"
else:
prefix = self.rootStructureName + "_"
for name in self.structures:
if name[-1] == "\x00":
base_name = name[0:-1]
else:
base_name = name
# Don't do library cells
if prefix_name and base_name.startswith(prefix_name):
new_name = name
elif name != self.rootStructureName:
new_name = self.padText(prefix + base_name)
else:
new_name = name
#print("Structure: {0} -> {1}".format(base_name, new_name))
new_structures[new_name] = self.structures[name]
new_structures[new_name].name = new_name
for sref in new_structures[new_name].srefs:
if sref.sName[-1] == "\x00":
base_sref_name = sref.sName[0:-1]
else:
base_sref_name = sref.sName
# Don't do library cells
if prefix_name and base_sref_name.startswith(prefix_name):
new_sref_name = sref.sName
else:
new_sref_name = self.padText(prefix + base_sref_name)
sref.sName = new_sref_name
#print("SREF: {0} -> {1}".format(base_sref_name, new_sref_name))
self.structures = new_structures
def rename(self,newName): def rename(self,newName):
# take the root structure and copy it to a new structure with the new name # take the root structure and copy it to a new structure with the new name
self.structures[newName] = self.structures[self.rootStructureName] self.structures[newName] = self.structures[self.rootStructureName]
@ -211,13 +247,17 @@ class VlsiLayout:
del transformPath[-1] del transformPath[-1]
return return
def initialize(self): def initialize(self, special_purposes={}):
self.deduceHierarchy() self.deduceHierarchy()
# self.traverseTheHierarchy() # self.traverseTheHierarchy()
self.populateCoordinateMap() self.populateCoordinateMap()
# only ones with text
for layerNumber in self.layerNumbersInUse: for layerNumber in self.layerNumbersInUse:
self.processLabelPins((layerNumber, None)) # if layerNumber not in no_pin_shape:
if layerNumber in special_purposes:
self.processLabelPins((layerNumber, special_purposes[layerNumber]))
else:
self.processLabelPins((layerNumber, None))
def populateCoordinateMap(self): def populateCoordinateMap(self):
def addToXyTree(startingStructureName = None,transformPath = None): def addToXyTree(startingStructureName = None,transformPath = None):
@ -422,7 +462,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 +737,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
@ -716,6 +756,7 @@ class VlsiLayout:
Find all text labels and create a map to a list of shapes that Find all text labels and create a map to a list of shapes that
they enclose on the given layer. they enclose on the given layer.
""" """
# Get the labels on a layer in the root level # Get the labels on a layer in the root level
labels = self.getTexts(lpp) labels = self.getTexts(lpp)
@ -727,16 +768,28 @@ class VlsiLayout:
label_coordinate = label.coordinates[0] label_coordinate = label.coordinates[0]
user_coordinate = [x*self.units[0] for x in label_coordinate] user_coordinate = [x*self.units[0] for x in label_coordinate]
pin_shapes = [] pin_shapes = []
# Remove the padding if it exists
if label.textString[-1] == "\x00":
label_text = label.textString[0:-1]
else:
label_text = label.textString
try:
from tech import layer_override
if layer_override[label_text]:
shapes = self.getAllShapes((layer_override[label_text][0], None))
if not shapes:
shapes = self.getAllShapes(lpp)
else:
lpp = layer_override[label_text]
except:
pass
for boundary in shapes: for boundary in shapes:
if self.labelInRectangle(user_coordinate, boundary): if self.labelInRectangle(user_coordinate, boundary):
pin_shapes.append((lpp, boundary)) pin_shapes.append((lpp, boundary))
label_text = label.textString
# Remove the padding if it exists
if label_text[-1] == "\x00":
label_text = label_text[0:-1]
try: try:
self.pins[label_text] self.pins[label_text]
except KeyError: except KeyError:
@ -903,6 +956,16 @@ def sameLPP(lpp1, lpp2):
if lpp1[1] == None or lpp2[1] == None: if lpp1[1] == None or lpp2[1] == None:
return lpp1[0] == lpp2[0] return lpp1[0] == lpp2[0]
if isinstance(lpp1[1], list):
for i in range(len(lpp1[1])):
if lpp1[0] == lpp2[0] and lpp1[1][i] == lpp2[1]:
return True
if isinstance(lpp2[1], list):
for i in range(len(lpp2[1])):
if lpp1[0] == lpp2[0] and lpp1[1] == lpp2[1][i]:
return True
return lpp1[0] == lpp2[0] and lpp1[1] == lpp2[1] return lpp1[0] == lpp2[0] and lpp1[1] == lpp2[1]

View File

@ -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",
@ -238,8 +238,8 @@ def setup_bitcell():
OPTS.dummy_bitcell = "dummy_pbitcell" OPTS.dummy_bitcell = "dummy_pbitcell"
OPTS.replica_bitcell = "replica_pbitcell" OPTS.replica_bitcell = "replica_pbitcell"
else: else:
num_ports = OPTS.num_rw_ports + OPTS.num_w_ports + OPTS.num_r_ports OPTS.num_ports = OPTS.num_rw_ports + OPTS.num_w_ports + OPTS.num_r_ports
OPTS.bitcell = "bitcell_{}port".format(num_ports) OPTS.bitcell = "bitcell_{}port".format(OPTS.num_ports)
OPTS.dummy_bitcell = "dummy_" + OPTS.bitcell OPTS.dummy_bitcell = "dummy_" + OPTS.bitcell
OPTS.replica_bitcell = "replica_" + OPTS.bitcell OPTS.replica_bitcell = "replica_" + OPTS.bitcell
@ -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 = {}
@ -607,14 +607,14 @@ def report_status():
# If a write mask is specified by the user, the mask write size should be the same as # If a write mask is specified by the user, the mask write size should be the same as
# the word size so that an entire word is written at once. # the word size so that an entire word is written at once.
if OPTS.write_size is not None: if OPTS.write_size is not None and OPTS.write_size != OPTS.word_size:
if (OPTS.word_size % OPTS.write_size != 0): if (OPTS.word_size % OPTS.write_size != 0):
debug.error("Write size needs to be an integer multiple of word size.") debug.error("Write size needs to be an integer multiple of word size.")
# If write size is more than half of the word size, # If write size is more than half of the word size,
# then it doesn't need a write mask. It would be writing # then it doesn't need a write mask. It would be writing
# the whole word. # the whole word.
if (OPTS.write_size < 1 or OPTS.write_size > OPTS.word_size/2): if (OPTS.write_size < 1 or OPTS.write_size > OPTS.word_size / 2):
debug.error("Write size needs to be between 1 bit and {0} bits/2.".format(OPTS.word_size)) debug.error("Write size needs to be between 1 bit and {0} bits.".format(int(OPTS.word_size / 2)))
if not OPTS.tech_name: if not OPTS.tech_name:
debug.error("Tech name must be specified in config file.") debug.error("Tech name must be specified in config file.")

View File

@ -0,0 +1,9 @@
word_size = 10
num_words = 64
words_per_row = 4
local_array_size = 21
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,7 @@
word_size = 128
num_words = 1024
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 12
num_words = 128
words_per_row = 4
local_array_size = 38
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 12
num_words = 16
words_per_row = 1
local_array_size = 1
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 12
num_words = 256
words_per_row = 8
local_array_size = 17
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 15
num_words = 512
words_per_row = 8
local_array_size = 85
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 16
num_words = 1024
words_per_row = 16
local_array_size = 40
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 17
num_words = 1024
words_per_row = 16
local_array_size = 86
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 17
num_words = 256
words_per_row = 16
local_array_size = 49
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 18
num_words = 128
words_per_row = 2
local_array_size = 7
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 18
num_words = 32
words_per_row = 1
local_array_size = 18
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 22
num_words = 512
words_per_row = 16
local_array_size = 249
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 23
num_words = 1024
words_per_row = 16
local_array_size = 118
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 26
num_words = 64
words_per_row = 4
local_array_size = 23
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 27
num_words = 1024
words_per_row = 4
local_array_size = 89
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 27
num_words = 256
words_per_row = 8
local_array_size = 191
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 27
num_words = 512
words_per_row = 4
local_array_size = 60
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,7 @@
word_size = 32
num_words = 1024
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,7 @@
word_size = 32
num_words = 2048
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,7 @@
word_size = 32
num_words = 256
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,7 @@
word_size = 32
num_words = 512
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 4
num_words = 16
words_per_row = 1
local_array_size = 4
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 4
num_words = 32
words_per_row = 2
local_array_size = 5
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 4
num_words = 64
words_per_row = 4
local_array_size = 14
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,7 @@
word_size = 64
num_words = 1024
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,7 @@
word_size = 64
num_words = 512
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 6
num_words = 16
words_per_row = 1
local_array_size = 1
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 7
num_words = 64
words_per_row = 2
local_array_size = 10
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,7 @@
word_size = 8
num_words = 1024
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,7 @@
word_size = 8
num_words = 256
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 8
num_words = 256
words_per_row = 1
local_array_size = 1
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,7 @@
word_size = 8
num_words = 512
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 9
num_words = 1024
words_per_row = 4
local_array_size = 3
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

View File

@ -0,0 +1,9 @@
word_size = 9
num_words = 256
words_per_row = 4
local_array_size = 15
output_extended_config = True
output_datasheet_info = True
netlist_only = True
nominal_corner_only = True

288
compiler/model_data_util.py Normal file
View File

@ -0,0 +1,288 @@
import os
import csv
import re
import sys
import csv
import importlib
# Use the HTML file to extra the data. Easier to do than LIB
data_file_ext = ".html"
extended_name = "_extended" # Name addon of extended config file
DEFAULT_LAS = 0
def gen_regex_float_group(num, separator):
if num <= 0:
return ''
float_regex = '([-+]?[0-9]*\.?[0-9]*)'
full_regex = float_regex
for i in range(num-1):
full_regex+=separator+float_regex
return full_regex
def import_module(mod_name, mod_path):
spec = importlib.util.spec_from_file_location(mod_name, mod_path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
def get_config_mods(openram_dir):
# Get dataset name used by all the files e.g. sram_1b_16
files_names = [name for name in os.listdir(openram_dir) if os.path.isfile(openram_dir+'/'+name)]
log = [name for name in files_names if '.log' in name][0]
dataset_name = log[:-4]
print("Extracting dataset:{}".format(dataset_name))
# Check that the config files exist (including special extended config)
dir_path = openram_dir+"/"
#sys.path.append(dir_path)
imp_mod = None
imp_mod_extended = None
if not os.path.exists(openram_dir+'/'+dataset_name+".py"):
print("Python module for {} not found.".format(dataset_name))
imp_mod = None
else:
imp_mod = import_module(dataset_name, openram_dir+"/"+dataset_name+".py")
if not os.path.exists(openram_dir+'/'+dataset_name+extended_name+".py"):
print("Extended Python module for {} not found.".format(dataset_name))
imp_mod_extended = None
else:
imp_mod_extended = import_module(dataset_name+extended_name, openram_dir+"/"+dataset_name+extended_name+".py")
datasheet_fname = openram_dir+"/"+dataset_name+data_file_ext
return dataset_name, imp_mod, imp_mod_extended, datasheet_fname
def get_corners(datafile_contents, dataset_name, tech):
"""Search through given datasheet to find all corners available"""
corner_regex = r"{}.*{},([-+]?[0-9]*\.?[0-9]*),([-+]?[0-9]*\.?[0-9]*),([tsfTSF][tsfTSF]),".format(dataset_name, tech)
corners = re.findall(corner_regex,datafile_contents)
return corners # List of corner tuples in order (T, V, P)
feature_names = ['num_words',
'word_size',
'words_per_row',
'local_array_size',
'area',
'process',
'voltage',
'temperature',
'slew',
'load']
output_names = ['rise_delay',
'fall_delay',
'rise_slew',
'fall_slew',
'write1_power',
'write0_power',
'read1_power',
'read0_power',
'leakage_power']
multivalue_names = ['cell_rise_0',
'cell_fall_0',
'rise_transition_0',
'fall_transition_0']
singlevalue_names = ['write_rise_power_0',
'write_fall_power_0',
'read_rise_power_0',
'read_fall_power_0']
def write_to_csv(dataset_name, csv_file, datasheet_fname, imp_mod, imp_mod_extended, mode):
writer = csv.writer(csv_file,lineterminator='\n')
# If the file was opened to write and not append then we write the header
if mode == 'w':
writer.writerow(feature_names+output_names)
try:
load_slews = imp_mod.use_specified_load_slew
except:
load_slews = None
if load_slews != None:
num_items = len(load_slews)
num_loads_or_slews = len(load_slews)
else:
# These are the defaults for openram
num_items = 9
num_loads_or_slews = 3
try:
f = open(datasheet_fname, "r")
except IOError:
print("Unable to open spice output file: {0}".format(datasheet_fname))
return None
print("Opened file",datasheet_fname)
contents = f.read()
f.close()
available_corners = get_corners(contents, dataset_name, imp_mod_extended.tech_name)
# Loop through corners, adding data for each corner
for (temp, voltage, process) in available_corners:
# Create a regex to search the datasheet for specified outputs
voltage_str = "".join(['\\'+i if i=='.' else i for i in str(voltage)])
area_regex = r"Area \(&microm<sup>2<\/sup>\)<\/td><td>(\d+)"
leakage_regex = r"leakage<\/td><td>([-+]?[0-9]*\.?[0-9]*)"
slew_regex = r"rise transition<\/td><td>([-+]?[0-9]*\.?[0-9]*)"
if load_slews == None:
float_regex = gen_regex_float_group(num_loads_or_slews, ', ')
inp_slews_regex = r"{},{}.*{},{},{},.*slews,\[{}".format(
dataset_name,
imp_mod.num_words,
str(temp),
voltage_str,
process,
float_regex)
loads_regex = r"{},{}.*{},{},{},.*loads,\[{}".format(
dataset_name,
imp_mod.num_words,
str(temp),
voltage_str,
process,
float_regex)
float_regex = gen_regex_float_group(num_items, ', ')
multivalue_regexs = []
for value_identifier in multivalue_names:
regex_str = r"{},{}.*{},{},{},.*{},\[{}".format(
dataset_name,
imp_mod.num_words,
str(temp),
voltage_str,
process,
value_identifier,
float_regex)
multivalue_regexs.append(regex_str)
singlevalue_regexs = []
for value_identifier in singlevalue_names:
regex_str = r"{},{}.*{},{},{},.*{},([-+]?[0-9]*\.?[0-9]*)".format(
dataset_name,
imp_mod.num_words,
str(temp),
voltage_str,
process,
value_identifier,
float_regex)
singlevalue_regexs.append(regex_str)
area_vals = re.search(area_regex,contents)
leakage_vals = re.search(leakage_regex,contents)
if load_slews == None:
inp_slew_vals = re.search(inp_slews_regex,contents)
load_vals = re.search(loads_regex,contents)
datasheet_multivalues = [re.search(r,contents) for r in multivalue_regexs]
datasheet_singlevalues = [re.search(r,contents) for r in singlevalue_regexs]
for dval in datasheet_multivalues+datasheet_singlevalues:
if dval == None:
print("Error occurred while searching through datasheet: {}".format(datasheet_fname))
return None
try:
las = imp_mod.local_array_size
except:
las = DEFAULT_LAS
# All the extracted values are delays but val[2] is the max delay
feature_vals = [imp_mod.num_words,
imp_mod.word_size,
imp_mod_extended.words_per_row,
las,
area_vals[1],
process,
voltage,
temp]
if load_slews == None:
c = 1
for i in range(num_loads_or_slews):
for j in range(num_loads_or_slews):
multi_values = [val[i+j+c] for val in datasheet_multivalues]
single_values = [val[1] for val in datasheet_singlevalues]
writer.writerow(feature_vals+[inp_slew_vals[i+1], load_vals[j+1]]+multi_values+single_values+[leakage_vals[1]])
c+=2
else:
# if num loads and num slews are not equal then this might break because of how OpenRAM formats
# the outputs
c = 1
for load,slew in load_slews:
multi_values = [val[c] for val in datasheet_multivalues]
single_values = [val[1] for val in datasheet_singlevalues]
writer.writerow(feature_vals+[slew, load]+multi_values+single_values+[leakage_vals[1]])
c+=1
def extract_data(openram_dir, out_dir, is_first):
"""Given an OpenRAM output dir, searches through datasheet files and ouputs
a CSV files with data used in model."""
# Get dataset name used by all the files e.g. sram_1b_16
dataset_name, inp_mod, imp_mod_extended, datasheet_fname = get_config_mods(openram_dir)
if inp_mod == None or imp_mod_extended == None:
print("Config file(s) for this run not found. Skipping...")
return
if is_first:
mode = 'w'
else:
mode = 'a+'
with open("{}/sim_data.csv".format(out_dir), mode, newline='\n') as data_file:
write_to_csv(dataset_name, data_file, datasheet_fname, inp_mod, imp_mod_extended, mode)
return out_dir
def gen_model_csv(openram_dir_path, out_dir):
if not os.path.isdir(input_dir_path):
print("Path does not exist: {}".format(input_dir_path))
return
if not os.path.isdir(out_path):
print("Path does not exist: {}".format(out_path))
return
is_first = True
oram_dirs = [openram_dir_path+'/'+name for name in os.listdir(openram_dir_path) if os.path.isdir(openram_dir_path+'/'+name)]
for dir in oram_dirs:
extract_data(dir, out_dir, is_first)
is_first = False
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: python model_data_util.py path_to_openram_dirs out_dir_path")
else:
input_dir_path = sys.argv[1]
out_path = sys.argv[2]
gen_model_csv(input_dir_path, out_path)

View File

@ -75,6 +75,11 @@ class bank(design.design):
self.bank_array_ll = self.offset_all_coordinates().scale(-1, -1) self.bank_array_ll = self.offset_all_coordinates().scale(-1, -1)
self.bank_array_ur = self.bitcell_array_inst.ur() self.bank_array_ur = self.bitcell_array_inst.ur()
self.bank_array_ul = self.bitcell_array_inst.ul() self.bank_array_ul = self.bitcell_array_inst.ul()
# These are used for other placements (e.g. address flops)
self.predecoder_top = self.port_address[0].predecoder_height + self.port_address_inst[0].by()
self.predecoder_bottom = self.port_address_inst[0].by()
self.DRC_LVS() self.DRC_LVS()
def add_pins(self): def add_pins(self):
@ -227,7 +232,6 @@ class bank(design.design):
x_offset = self.m2_gap + self.port_address[port].width x_offset = self.m2_gap + self.port_address[port].width
self.port_address_offsets[port] = vector(-x_offset, self.port_address_offsets[port] = vector(-x_offset,
self.main_bitcell_array_bottom) self.main_bitcell_array_bottom)
self.predecoder_height = self.port_address[port].predecoder_height + self.port_address_offsets[port].y
# LOWER LEFT QUADRANT # LOWER LEFT QUADRANT
# Place the col decoder left aligned with wordline driver # Place the col decoder left aligned with wordline driver
@ -362,18 +366,13 @@ class bank(design.design):
# A space for wells or jogging m2 # A space for wells or jogging m2
self.m2_gap = max(2 * drc("pwell_to_nwell") + drc("nwell_enclose_active"), self.m2_gap = max(2 * drc("pwell_to_nwell") + drc("nwell_enclose_active"),
3 * self.m2_pitch) 3 * self.m2_pitch,
drc("nwell_to_nwell"))
def add_modules(self): def add_modules(self):
""" Add all the modules using the class loader """ """ Add all the modules using the class loader """
self.port_address = []
for port in self.all_ports:
self.port_address.append(factory.create(module_type="port_address",
cols=self.num_cols + self.num_spare_cols,
rows=self.num_rows,
port=port))
self.add_mod(self.port_address[port])
local_array_size = OPTS.local_array_size local_array_size = OPTS.local_array_size
@ -394,6 +393,14 @@ class bank(design.design):
rows=self.num_rows) rows=self.num_rows)
self.add_mod(self.bitcell_array) self.add_mod(self.bitcell_array)
self.port_address = []
for port in self.all_ports:
self.port_address.append(factory.create(module_type="port_address",
cols=self.num_cols + self.num_spare_cols,
rows=self.num_rows,
port=port))
self.add_mod(self.port_address[port])
self.port_data = [] self.port_data = []
self.bit_offsets = self.get_column_offsets() self.bit_offsets = self.get_column_offsets()
for port in self.all_ports: for port in self.all_ports:
@ -426,7 +433,9 @@ class bank(design.design):
temp.append("vdd") temp.append("vdd")
temp.append("gnd") temp.append("gnd")
if 'vpb' in self.bitcell_array_inst.mod.pins and 'vnb' in self.bitcell_array_inst.mod.pins:
temp.append('vpb')
temp.append('vnb')
self.connect_inst(temp) self.connect_inst(temp)
def place_bitcell_array(self, offset): def place_bitcell_array(self, offset):
@ -610,6 +619,10 @@ class bank(design.design):
self.copy_power_pins(inst, "vdd", add_vias=False) self.copy_power_pins(inst, "vdd", add_vias=False)
self.copy_power_pins(inst, "gnd", add_vias=False) self.copy_power_pins(inst, "gnd", add_vias=False)
if 'vpb' in self.bitcell_array_inst.mod.pins and 'vnb' in self.bitcell_array_inst.mod.pins:
for pin_name, supply_name in zip(['vnb','vpb'],['gnd','vdd']):
self.copy_power_pins(self.bitcell_array_inst, pin_name, new_name=supply_name)
# If we use the pinvbuf as the decoder, we need to add power pins. # If we use the pinvbuf as the decoder, we need to add power pins.
# Other decoders already have them. # Other decoders already have them.
if self.col_addr_size == 1: if self.col_addr_size == 1:
@ -1058,7 +1071,6 @@ class bank(design.design):
to_layer="m2", to_layer="m2",
offset=control_pos) offset=control_pos)
def graph_exclude_precharge(self): def graph_exclude_precharge(self):
""" """
Precharge adds a loop between bitlines, can be excluded to reduce complexity Precharge adds a loop between bitlines, can be excluded to reduce complexity
@ -1071,7 +1083,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)
@ -1087,3 +1099,14 @@ class bank(design.design):
""" """
self.bitcell_array.clear_exclude_bits() self.bitcell_array.clear_exclude_bits()
def graph_exclude_column_mux(self, column_include_num, port):
"""
Excludes all columns muxes unrelated to the target bit being simulated.
"""
self.port_data[port].graph_exclude_column_mux(column_include_num)
def graph_clear_column_mux(self, port):
"""
Clear mux exclusions to allow different bit tests.
"""
self.port_data[port].graph_clear_column_mux()

View File

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

View File

@ -128,9 +128,8 @@ class bitcell_base_array(design.design):
if len(self.all_ports) > 1: if len(self.all_ports) > 1:
temp.extend(self.get_rbl_wordline_names(1)) temp.extend(self.get_rbl_wordline_names(1))
return temp return temp
def add_layout_pins(self): def add_bitline_pins(self):
""" Add the layout pins """
bitline_names = self.cell.get_all_bitline_names() bitline_names = self.cell.get_all_bitline_names()
for col in range(self.column_size): for col in range(self.column_size):
for port in self.all_ports: for port in self.all_ports:
@ -146,7 +145,7 @@ class bitcell_base_array(design.design):
offset=br_pin.ll().scale(1, 0), offset=br_pin.ll().scale(1, 0),
width=br_pin.width(), width=br_pin.width(),
height=self.height) height=self.height)
def add_wl_pins(self):
wl_names = self.cell.get_all_wl_names() wl_names = self.cell.get_all_wl_names()
for row in range(self.row_size): for row in range(self.row_size):
for port in self.all_ports: for port in self.all_ports:
@ -157,13 +156,19 @@ class bitcell_base_array(design.design):
width=self.width, width=self.width,
height=wl_pin.height()) height=wl_pin.height())
# Copy a vdd/gnd layout pin from every cell def add_supply_pins(self):
for row in range(self.row_size): for row in range(self.row_size):
for col in range(self.column_size): for col in range(self.column_size):
inst = self.cell_inst[row, col] inst = self.cell_inst[row, col]
for pin_name in ["vdd", "gnd"]: for pin_name in ["vdd", "gnd"]:
self.copy_layout_pin(inst, pin_name) self.copy_layout_pin(inst, pin_name)
def add_layout_pins(self):
""" Add the layout pins """
self.add_bitline_pins()
self.add_wl_pins()
self.add_supply_pins()
def _adjust_x_offset(self, xoffset, col, col_offset): def _adjust_x_offset(self, xoffset, col, col_offset):
tempx = xoffset tempx = xoffset
dir_y = False dir_y = False

View File

@ -230,3 +230,12 @@ class column_mux_array(design.design):
to_layer=self.sel_layer, to_layer=self.sel_layer,
offset=br_out_offset_begin, offset=br_out_offset_begin,
directions=self.via_directions) directions=self.via_directions)
def graph_exclude_columns(self, column_include_num):
"""
Excludes all columns muxes unrelated to the target bit being simulated.
Each mux in mux_inst corresponds to respective column in bitcell array.
"""
for i in range(len(self.mux_inst)):
if i != column_include_num:
self.graph_inst_exclude.add(self.mux_inst[i])

View File

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

View File

@ -31,6 +31,7 @@ class delay_chain(design.design):
# number of inverters including any fanout loads. # number of inverters including any fanout loads.
self.fanout_list = fanout_list self.fanout_list = fanout_list
self.rows = len(self.fanout_list)
self.create_netlist() self.create_netlist()
if not OPTS.netlist_only: if not OPTS.netlist_only:
@ -43,10 +44,10 @@ 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
self.place_inverters() self.place_inverters()
self.route_inverters() self.route_inverters()
self.route_supplies() self.route_supplies()
@ -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), self.add_layout_pin_rect_center(text="in",
height=a_pin.cy()) layer="m2",
offset=mid_loc)
# output is A pin of last load inverter # output is A pin of last load/fanout inverter
last_driver_inst = self.driver_inst_list[-1] last_driver_inst = self.driver_inst_list[-1]
a_pin = self.load_inst_map[last_driver_inst][-1].get_pin("A") a_pin = self.load_inst_map[last_driver_inst][-1].get_pin("A")
self.add_via_stack_center(from_layer=a_pin.layer, self.add_via_stack_center(from_layer=a_pin.layer,
to_layer="m2", to_layer="m1",
offset=a_pin.center()) offset=a_pin.center())
mid_point = vector(a_pin.cx() + 3 * self.m2_width, a_pin.cy()) self.add_layout_pin_rect_center(text="out",
self.add_path("m2", [a_pin.center(), mid_point, mid_point.scale(1, 0)]) layer="m1",
self.add_layout_pin_segment_center(text="out", offset=a_pin.center())
layer="m2",
start=mid_point,
end=mid_point.scale(1, 0))

View File

@ -109,7 +109,7 @@ class dff_buf(design.design):
except AttributeError: except AttributeError:
pass pass
well_spacing += self.well_extend_active well_spacing += 2 * self.well_extend_active
self.inv1_inst.place(vector(self.dff_inst.rx() + well_spacing, 0)) self.inv1_inst.place(vector(self.dff_inst.rx() + well_spacing, 0))

View File

@ -296,12 +296,12 @@ 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 = targ_col - self.col_offsets[i - 1] local_col = targ_col - self.col_offsets[i - 1]
for mod in self.local_mods: for mod, inst in zip(self.local_mods, self.local_insts):
if mod == local_array: if mod == local_array:
mod.graph_exclude_bits(targ_row, local_col) mod.graph_exclude_bits(targ_row, local_col)
else: else:
# Otherwise, we exclude ALL of the rows/columns # Otherwise, exclude the local array inst
mod.graph_exclude_bits() self.graph_inst_exclude.add(inst)
def graph_exclude_replica_col_bits(self): def graph_exclude_replica_col_bits(self):
""" """
@ -330,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):
""" """
@ -338,6 +338,7 @@ class global_bitcell_array(bitcell_base_array.bitcell_base_array):
""" """
for mod in self.local_mods: for mod in self.local_mods:
mod.clear_exclude_bits() mod.clear_exclude_bits()
self.init_graph_params()
def graph_exclude_dffs(self): def graph_exclude_dffs(self):
"""Exclude dffs from graph as they do not represent critical path""" """Exclude dffs from graph as they do not represent critical path"""

View File

@ -11,8 +11,10 @@ import math
from sram_factory import factory from sram_factory import factory
from vector import vector from vector import vector
from globals import OPTS from globals import OPTS
from tech import layer_indices
from tech import layer_stacks
from tech import layer_properties as layer_props from tech import layer_properties as layer_props
from tech import drc
class hierarchical_decoder(design.design): class hierarchical_decoder(design.design):
""" """
@ -29,7 +31,7 @@ class hierarchical_decoder(design.design):
b = factory.create(module_type=OPTS.bitcell) b = factory.create(module_type=OPTS.bitcell)
self.cell_height = b.height self.cell_height = b.height
self.predecode_bus_rail_pos = []
self.num_outputs = num_outputs self.num_outputs = num_outputs
self.num_inputs = math.ceil(math.log(self.num_outputs, 2)) self.num_inputs = math.ceil(math.log(self.num_outputs, 2))
(self.no_of_pre2x4, self.no_of_pre3x8, self.no_of_pre4x16)=self.determine_predecodes(self.num_inputs) (self.no_of_pre2x4, self.no_of_pre3x8, self.no_of_pre4x16)=self.determine_predecodes(self.num_inputs)
@ -504,9 +506,9 @@ class hierarchical_decoder(design.design):
offset=vector(self.bus_pitch, 0), offset=vector(self.bus_pitch, 0),
names=input_bus_names, names=input_bus_names,
length=self.height) length=self.height)
self.route_predecodes_to_bus()
self.route_bus_to_decoder() self.route_bus_to_decoder()
self.route_predecodes_to_bus()
def route_predecodes_to_bus(self): def route_predecodes_to_bus(self):
""" """
@ -521,7 +523,7 @@ class hierarchical_decoder(design.design):
pin = self.pre2x4_inst[pre_num].get_pin(out_name) pin = self.pre2x4_inst[pre_num].get_pin(out_name)
x_offset = self.pre2x4_inst[pre_num].rx() + self.output_layer_pitch x_offset = self.pre2x4_inst[pre_num].rx() + self.output_layer_pitch
y_offset = self.pre2x4_inst[pre_num].by() + i * self.cell_height y_offset = self.pre2x4_inst[pre_num].by() + i * self.cell_height
self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset) self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset, "pre2x4")
# FIXME: convert to connect_bus # FIXME: convert to connect_bus
for pre_num in range(self.no_of_pre3x8): for pre_num in range(self.no_of_pre3x8):
@ -531,7 +533,7 @@ class hierarchical_decoder(design.design):
pin = self.pre3x8_inst[pre_num].get_pin(out_name) pin = self.pre3x8_inst[pre_num].get_pin(out_name)
x_offset = self.pre3x8_inst[pre_num].rx() + self.output_layer_pitch x_offset = self.pre3x8_inst[pre_num].rx() + self.output_layer_pitch
y_offset = self.pre3x8_inst[pre_num].by() + i * self.cell_height y_offset = self.pre3x8_inst[pre_num].by() + i * self.cell_height
self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset) self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset, "pre3x8")
# FIXME: convert to connect_bus # FIXME: convert to connect_bus
for pre_num in range(self.no_of_pre4x16): for pre_num in range(self.no_of_pre4x16):
@ -541,7 +543,7 @@ class hierarchical_decoder(design.design):
pin = self.pre4x16_inst[pre_num].get_pin(out_name) pin = self.pre4x16_inst[pre_num].get_pin(out_name)
x_offset = self.pre4x16_inst[pre_num].rx() + self.output_layer_pitch x_offset = self.pre4x16_inst[pre_num].rx() + self.output_layer_pitch
y_offset = self.pre4x16_inst[pre_num].by() + i * self.cell_height y_offset = self.pre4x16_inst[pre_num].by() + i * self.cell_height
self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset) self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset, "pre4x16")
def route_bus_to_decoder(self): def route_bus_to_decoder(self):
""" """
@ -649,8 +651,9 @@ class hierarchical_decoder(design.design):
to_layer=self.input_layer, to_layer=self.input_layer,
offset=pin_pos, offset=pin_pos,
directions=("H", "H")) directions=("H", "H"))
self.predecode_bus_rail_pos.append(rail_pos)
def route_predecode_bus_inputs(self, rail_name, pin, x_offset, y_offset): def route_predecode_bus_inputs(self, rail_name, pin, x_offset, y_offset, predecode_type):
""" """
Connect the routing rail to the given metal1 pin using a jog Connect the routing rail to the given metal1 pin using a jog
to the right of the cell at the given x_offset. to the right of the cell at the given x_offset.
@ -661,14 +664,58 @@ class hierarchical_decoder(design.design):
mid_point1 = vector(x_offset, pin_pos.y) mid_point1 = vector(x_offset, pin_pos.y)
mid_point2 = vector(x_offset, y_offset) mid_point2 = vector(x_offset, y_offset)
rail_pos = vector(self.predecode_bus[rail_name].cx(), mid_point2.y) rail_pos = vector(self.predecode_bus[rail_name].cx(), mid_point2.y)
self.add_path(self.output_layer, [pin_pos, mid_point1, mid_point2, rail_pos]) #self.add_path(self.output_layer, [pin_pos, mid_point1, mid_point2, rail_pos])
if layer_props.hierarchical_decoder.vertical_supply: #if layer_props.hierarchical_decoder.vertical_supply:
above_rail = vector(self.predecode_bus[rail_name].cx(), mid_point2.y + (self.cell_height / 2)) # above_rail = vector(self.predecode_bus[rail_name].cx(), mid_point2.y + (self.cell_height / 2))
self.add_path(self.bus_layer, [rail_pos, above_rail], width=self.li_width + self.m1_enclose_mcon * 2) # self.add_path(self.bus_layer, [rail_pos, above_rail], width=self.li_width + self.m1_enclose_mcon * 2)
#pin_pos = pin.center()
#rail_pos = vector(self.predecode_bus[rail_name].cx(), pin_pos.y)
#self.add_path(self.output_layer, [pin_pos, rail_pos])
# create via for dimensions
from_layer = self.output_layer
to_layer = self.bus_layer
cur_layer = from_layer
from_id = layer_indices[cur_layer]
to_id = layer_indices[to_layer]
if from_id < to_id: # grow the stack up
search_id = 0
next_id = 2
else: # grow the stack down
search_id = 2
next_id = 0
curr_stack = next(filter(lambda stack: stack[search_id] == cur_layer, layer_stacks), None)
via = factory.create(module_type="contact",
layer_stack=curr_stack,
dimensions=[1, 1],
directions=self.bus_directions)
overlapping_pin_space = getattr(self, "{}_space".format(self.output_layer))
total_buffer_space = (overlapping_pin_space + via.height)
while(True):
drc_error = 0
for and_input in self.predecode_bus_rail_pos:
if and_input.x == rail_pos.x:
if (abs(y_offset - and_input.y) < total_buffer_space) or (abs(y_offset - and_input.y) < via.height):
drc_error = 1
if drc_error == 0:
break
else:
y_offset += drc["grid"]
rail_pos.y = y_offset
if predecode_type == "pre2x4":
right_pos = pin_pos
elif predecode_type =="pre3x8":
right_pos = pin_pos
elif predecode_type == "pre4x16":
right_pos = pin_pos
# else:
# error("invalid predcoder type {}".format(predecode_type))
self.add_path(self.output_layer, [pin_pos, right_pos, vector(right_pos.x, y_offset), rail_pos])
# pin_pos = pin.center()
# rail_pos = vector(self.predecode_bus[rail_name].cx(), pin_pos.y)
# self.add_path(self.output_layer, [pin_pos, rail_pos])
self.add_via_stack_center(from_layer=pin.layer, self.add_via_stack_center(from_layer=pin.layer,
to_layer=self.output_layer, to_layer=self.output_layer,
offset=pin_pos) offset=pin_pos)

View File

@ -12,6 +12,10 @@ from vector import vector
from sram_factory import factory from sram_factory import factory
from globals import OPTS from globals import OPTS
from tech import layer_properties as layer_props from tech import layer_properties as layer_props
from tech import layer_indices
from tech import layer_stacks
from tech import preferred_directions
from tech import drc
class hierarchical_predecode(design.design): class hierarchical_predecode(design.design):
@ -29,7 +33,7 @@ class hierarchical_predecode(design.design):
self.cell_height = height self.cell_height = height
self.column_decoder = column_decoder self.column_decoder = column_decoder
self.input_and_rail_pos = []
self.number_of_outputs = int(math.pow(2, self.number_of_inputs)) self.number_of_outputs = int(math.pow(2, self.number_of_inputs))
super().__init__(name) super().__init__(name)
@ -118,7 +122,7 @@ 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 - self.bus_pitch,
pitch=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)]
@ -128,7 +132,7 @@ 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 - self.bus_pitch,
pitch=self.bus_pitch) pitch=self.bus_pitch)
def create_input_inverters(self): def create_input_inverters(self):
@ -183,15 +187,15 @@ class hierarchical_predecode(design.design):
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_input_ands() self.route_input_ands()
self.route_output_inverters()
self.route_inputs_to_rails()
self.route_output_ands() self.route_output_ands()
self.route_vdd_gnd() self.route_vdd_gnd()
def route_inputs_to_rails(self): def route_inputs_to_rails(self):
""" Route the uninverted inputs to the second set of rails """ """ Route the uninverted inputs to the second set of rails """
top_and_gate = self.and_inst[-1] top_and_gate = self.and_inst[-1]
for num in range(self.number_of_inputs): for num in range(self.number_of_inputs):
if num == 0: if num == 0:
@ -211,13 +215,25 @@ class hierarchical_predecode(design.design):
in_pos = vector(self.input_rails[in_pin].cx(), y_offset) in_pos = vector(self.input_rails[in_pin].cx(), y_offset)
a_pos = vector(self.decode_rails[a_pin].cx(), y_offset) a_pos = vector(self.decode_rails[a_pin].cx(), y_offset)
self.add_path(self.input_layer, [in_pos, a_pos]) self.add_path(self.input_layer, [in_pos, a_pos])
self.add_via_stack_center(from_layer=self.input_layer,
to_layer=self.bus_layer,
offset=[self.input_rails[in_pin].cx(), y_offset])
self.add_via_stack_center(from_layer=self.input_layer,
to_layer=self.bus_layer,
offset=[self.decode_rails[a_pin].cx(), y_offset])
if(layer_props.hierarchical_predecode.force_horizontal_input_contact):
self.add_via_stack_center(from_layer=self.input_layer,
to_layer=self.bus_layer,
offset=[self.input_rails[in_pin].cx(), y_offset],
directions= ("H", "H"))
self.add_via_stack_center(from_layer=self.input_layer,
to_layer=self.bus_layer,
offset=[self.decode_rails[a_pin].cx(), y_offset],
directions=("H", "H"))
else:
self.add_via_stack_center(from_layer=self.input_layer,
to_layer=self.bus_layer,
offset=[self.input_rails[in_pin].cx(), y_offset])
self.add_via_stack_center(from_layer=self.input_layer,
to_layer=self.bus_layer,
offset=[self.decode_rails[a_pin].cx(), y_offset])
def route_output_ands(self): def route_output_ands(self):
""" """
Route all conections of the outputs and gates Route all conections of the outputs and gates
@ -274,8 +290,45 @@ class hierarchical_predecode(design.design):
# pins in the and gates. # pins in the and gates.
inv_out_pos = inv_out_pin.rc() inv_out_pos = inv_out_pin.rc()
y_offset = (inv_num + 1) * self.inv.height - self.output_layer_pitch y_offset = (inv_num + 1) * self.inv.height - self.output_layer_pitch
right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").rx(), 0)
rail_pos = vector(self.decode_rails[out_pin].cx(), y_offset) rail_pos = vector(self.decode_rails[out_pin].cx(), y_offset)
# create via for dimensions
from_layer = self.output_layer
to_layer = self.bus_layer
cur_layer = from_layer
from_id = layer_indices[cur_layer]
to_id = layer_indices[to_layer]
if from_id < to_id: # grow the stack up
search_id = 0
next_id = 2
else: # grow the stack down
search_id = 2
next_id = 0
curr_stack = next(filter(lambda stack: stack[search_id] == cur_layer, layer_stacks), None)
via = factory.create(module_type="contact",
layer_stack=curr_stack,
dimensions=[1, 1],
directions=self.bus_directions)
overlapping_pin_space = drc["{0}_to_{0}".format(self.output_layer)]
total_buffer_space = (overlapping_pin_space + via.height)
#FIXME: compute rail locations instead of just guessing and nudging
while(True):
drc_error = 0
for and_input in self.input_and_rail_pos:
if and_input.x == rail_pos.x:
if (abs(y_offset - and_input.y) < total_buffer_space) and (abs(y_offset - and_input.y) > via.height):
drc_error = 1
if drc_error == 0:
break
else:
y_offset += drc["grid"]
rail_pos.y = y_offset
right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").rx(), 0)
self.add_path(self.output_layer, [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos]) self.add_path(self.output_layer, [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos])
self.add_via_stack_center(from_layer=inv_out_pin.layer, self.add_via_stack_center(from_layer=inv_out_pin.layer,
@ -290,7 +343,7 @@ class hierarchical_predecode(design.design):
""" """
Route the different permutations of the NAND/AND decocer cells. Route the different permutations of the NAND/AND decocer cells.
""" """
# 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):
@ -316,6 +369,7 @@ class hierarchical_predecode(design.design):
to_layer=self.bus_layer, to_layer=self.bus_layer,
offset=rail_pos, offset=rail_pos,
directions=self.bus_directions) directions=self.bus_directions)
self.input_and_rail_pos.append(rail_pos)
if gate_pin == "A": if gate_pin == "A":
direction = None direction = None
else: else:

View File

@ -217,6 +217,12 @@ class local_bitcell_array(bitcell_base_array.bitcell_base_array):
y_offset += global_wl_pitch_factor * global_wl_pitch y_offset += global_wl_pitch_factor * global_wl_pitch
mid = vector(in_pin.cx(), y_offset) mid = vector(in_pin.cx(), y_offset)
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 # 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=local_wl_layer, to_layer=local_wl_layer,
@ -289,7 +295,7 @@ class local_bitcell_array(bitcell_base_array.bitcell_base_array):
def get_cell_name(self, inst_name, row, col): def get_cell_name(self, inst_name, row, col):
"""Gets the spice name of the target bitcell.""" """Gets the spice name of the target bitcell."""
return self.bitcell_array.get_cell_name(inst_name + '.x' + self.bitcell_array_inst.name, row, col) return self.bitcell_array.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bitcell_array_inst.name, row, col)
def clear_exclude_bits(self): def clear_exclude_bits(self):
""" """

View File

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

View File

@ -11,6 +11,7 @@ from sram_factory import factory
from collections import namedtuple from collections import namedtuple
from vector import vector from vector import vector
from globals import OPTS from globals import OPTS
from tech import cell_properties
from tech import layer_properties as layer_props from tech import layer_properties as layer_props
@ -20,7 +21,7 @@ class port_data(design.design):
Port 0 always has the RBL on the left while port 1 is on the right. Port 0 always has the RBL on the left while port 1 is on the right.
""" """
def __init__(self, sram_config, port, bit_offsets=None, name=""): def __init__(self, sram_config, port, num_spare_cols=None, bit_offsets=None, name="",):
sram_config.set_local_config(self) sram_config.set_local_config(self)
self.port = port self.port = port
@ -28,18 +29,23 @@ class port_data(design.design):
self.num_wmasks = int(math.ceil(self.word_size / self.write_size)) self.num_wmasks = int(math.ceil(self.word_size / self.write_size))
else: else:
self.num_wmasks = 0 self.num_wmasks = 0
if num_spare_cols is not None:
self.num_spare_cols = num_spare_cols + self.num_spare_cols
if self.num_spare_cols is None: if self.num_spare_cols is None:
self.num_spare_cols = 0 self.num_spare_cols = 0
if not bit_offsets: if not bit_offsets:
bitcell = factory.create(module_type=OPTS.bitcell) bitcell = factory.create(module_type=OPTS.bitcell)
if(cell_properties.use_strap == True and OPTS.num_ports == 1):
strap = factory.create(module_type=cell_properties.strap_module, version=cell_properties.strap_version)
precharge_width = bitcell.width + strap.width
else:
precharge_width = bitcell.width
self.bit_offsets = [] self.bit_offsets = []
for i in range(self.num_cols + self.num_spare_cols): for i in range(self.num_cols + self.num_spare_cols):
self.bit_offsets.append(i * bitcell.width) self.bit_offsets.append(i * precharge_width)
else: else:
self.bit_offsets = bit_offsets self.bit_offsets = bit_offsets
if name == "": if name == "":
name = "port_data_{0}".format(self.port) name = "port_data_{0}".format(self.port)
super().__init__(name) super().__init__(name)
@ -117,7 +123,6 @@ class port_data(design.design):
for bit in range(self.num_spare_cols): for bit in range(self.num_spare_cols):
self.add_pin("sparebl_{0}".format(bit), "INOUT") self.add_pin("sparebl_{0}".format(bit), "INOUT")
self.add_pin("sparebr_{0}".format(bit), "INOUT") self.add_pin("sparebr_{0}".format(bit), "INOUT")
if self.port in self.read_ports: if self.port in self.read_ports:
for bit in range(self.word_size + self.num_spare_cols): for bit in range(self.word_size + self.num_spare_cols):
self.add_pin("dout_{}".format(bit), "OUTPUT") self.add_pin("dout_{}".format(bit), "OUTPUT")
@ -191,14 +196,19 @@ class port_data(design.design):
# and mirroring happens correctly # and mirroring happens correctly
# Used for names/dimensions only # Used for names/dimensions only
self.cell = factory.create(module_type=OPTS.bitcell) cell = factory.create(module_type=OPTS.bitcell)
if(cell_properties.use_strap == True and OPTS.num_ports == 1):
strap = factory.create(module_type=cell_properties.strap_module, version=cell_properties.strap_version)
precharge_width = cell.width + strap.width
else:
precharge_width = cell.width
if self.port == 0: if self.port == 0:
# Append an offset on the left # Append an offset on the left
precharge_bit_offsets = [self.bit_offsets[0] - self.cell.width] + self.bit_offsets precharge_bit_offsets = [self.bit_offsets[0] - precharge_width] + self.bit_offsets
else: else:
# Append an offset on the right # Append an offset on the right
precharge_bit_offsets = self.bit_offsets + [self.bit_offsets[-1] + self.cell.width] precharge_bit_offsets = self.bit_offsets + [self.bit_offsets[-1] + precharge_width]
self.precharge_array = factory.create(module_type="precharge_array", self.precharge_array = factory.create(module_type="precharge_array",
columns=self.num_cols + self.num_spare_cols + 1, columns=self.num_cols + self.num_spare_cols + 1,
offsets=precharge_bit_offsets, offsets=precharge_bit_offsets,
@ -567,19 +577,32 @@ class port_data(design.design):
off = 1 off = 1
else: else:
off = 0 off = 0
if OPTS.num_ports > 1:
self.channel_route_bitlines(inst1=self.column_mux_array_inst,
inst1_bls_template="{inst}_out_{bit}",
inst2=inst2,
num_bits=self.word_size,
inst1_start_bit=start_bit)
self.channel_route_bitlines(inst1=self.column_mux_array_inst, self.channel_route_bitlines(inst1=self.precharge_array_inst,
inst1_bls_template="{inst}_out_{bit}", inst1_bls_template="{inst}_{bit}",
inst2=inst2, inst2=inst2,
num_bits=self.word_size, num_bits=self.num_spare_cols,
inst1_start_bit=start_bit) inst1_start_bit=self.num_cols + off,
inst2_start_bit=self.word_size)
else:
self.connect_bitlines(inst1=self.column_mux_array_inst,
inst1_bls_template="{inst}_out_{bit}",
inst2=inst2,
num_bits=self.word_size,
inst1_start_bit=start_bit)
self.channel_route_bitlines(inst1=self.precharge_array_inst, self.connect_bitlines(inst1=self.precharge_array_inst,
inst1_bls_template="{inst}_{bit}", inst1_bls_template="{inst}_{bit}",
inst2=inst2, inst2=inst2,
num_bits=self.num_spare_cols, num_bits=self.num_spare_cols,
inst1_start_bit=self.num_cols + off, inst1_start_bit=self.num_cols + off,
inst2_start_bit=self.word_size) inst2_start_bit=self.word_size)
elif layer_props.port_data.channel_route_bitlines: elif layer_props.port_data.channel_route_bitlines:
self.channel_route_bitlines(inst1=inst1, self.channel_route_bitlines(inst1=inst1,
@ -828,3 +851,17 @@ class port_data(design.design):
"""Precharge adds a loop between bitlines, can be excluded to reduce complexity""" """Precharge adds a loop between bitlines, can be excluded to reduce complexity"""
if self.precharge_array_inst: if self.precharge_array_inst:
self.graph_inst_exclude.add(self.precharge_array_inst) self.graph_inst_exclude.add(self.precharge_array_inst)
def graph_exclude_column_mux(self, column_include_num):
"""
Excludes all columns muxes unrelated to the target bit being simulated.
"""
if self.column_mux_array:
self.column_mux_array.graph_exclude_columns(column_include_num)
def graph_clear_column_mux(self):
"""
Clear mux exclusions to allow different bit tests.
"""
if self.column_mux_array:
self.column_mux_array.init_graph_params()

View File

@ -76,8 +76,8 @@ class precharge_array(design.design):
size=self.size, size=self.size,
bitcell_bl=self.bitcell_bl, bitcell_bl=self.bitcell_bl,
bitcell_br=self.bitcell_br) bitcell_br=self.bitcell_br)
self.add_mod(self.pc_cell) self.add_mod(self.pc_cell)
self.cell = factory.create(module_type=OPTS.bitcell) self.cell = factory.create(module_type=OPTS.bitcell)
def add_layout_pins(self): def add_layout_pins(self):

View File

@ -6,7 +6,7 @@
import debug import debug
from bitcell_base_array import bitcell_base_array from bitcell_base_array import bitcell_base_array
from tech import drc, spice from tech import drc, spice, cell_properties
from vector import vector from vector import vector
from globals import OPTS from globals import OPTS
from sram_factory import factory from sram_factory import factory
@ -553,7 +553,7 @@ class replica_bitcell_array(bitcell_base_array):
""" """
Gets the spice name of the target bitcell. Gets the spice name of the target bitcell.
""" """
return self.bitcell_array.get_cell_name(inst_name + '.x' + self.bitcell_array_inst.name, row, col) return self.bitcell_array.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bitcell_array_inst.name, row, col)
def clear_exclude_bits(self): def clear_exclude_bits(self):
""" """

View File

@ -238,5 +238,4 @@ class replica_column(bitcell_base_array):
for row, cell in enumerate(self.cell_inst): for row, cell in enumerate(self.cell_inst):
if row != self.replica_bit: if row != self.replica_bit:
self.graph_inst_exclude.add(cell) self.graph_inst_exclude.add(cell)

View File

@ -8,6 +8,7 @@
import design import design
from vector import vector from vector import vector
from sram_factory import factory from sram_factory import factory
from tech import cell_properties
import debug import debug
from globals import OPTS from globals import OPTS
@ -41,7 +42,6 @@ class sense_amp_array(design.design):
self.en_layer = "m3" self.en_layer = "m3"
else: else:
self.en_layer = "m1" self.en_layer = "m1"
self.create_netlist() self.create_netlist()
if not OPTS.netlist_only: if not OPTS.netlist_only:
self.create_layout() self.create_layout()
@ -109,15 +109,22 @@ class sense_amp_array(design.design):
self.en_name, "vdd", "gnd"]) self.en_name, "vdd", "gnd"])
def place_sense_amp_array(self): def place_sense_amp_array(self):
if self.bitcell.width > self.amp.width: cell = factory.create(module_type=OPTS.bitcell)
self.amp_spacing = self.bitcell.width if(cell_properties.use_strap == True and OPTS.num_ports == 1):
strap = factory.create(module_type=cell_properties.strap_module, version=cell_properties.strap_version)
precharge_width = cell.width + strap.width
else:
precharge_width = cell.width
if precharge_width > self.amp.width:
self.amp_spacing = precharge_width
else: else:
self.amp_spacing = self.amp.width self.amp_spacing = self.amp.width
if not self.offsets: if not self.offsets:
self.offsets = [] self.offsets = []
for i in range(self.num_cols + self.num_spare_cols): for i in range(self.num_cols + self.num_spare_cols):
self.offsets.append(i * self.bitcell.width) self.offsets.append(i * self.amp_spacing)
for i, xoffset in enumerate(self.offsets[0:self.num_cols:self.words_per_row]): for i, xoffset in enumerate(self.offsets[0:self.num_cols:self.words_per_row]):
if self.bitcell.mirror.y and (i * self.words_per_row + self.column_offset) % 2: if self.bitcell.mirror.y and (i * self.words_per_row + self.column_offset) % 2:
@ -128,13 +135,12 @@ class sense_amp_array(design.design):
amp_position = vector(xoffset, 0) amp_position = vector(xoffset, 0)
self.local_insts[i].place(offset=amp_position, mirror=mirror) self.local_insts[i].place(offset=amp_position, mirror=mirror)
# place spare sense amps (will share the same enable as regular sense amps) # place spare sense amps (will share the same enable as regular sense amps)
for i, xoffset in enumerate(self.offsets[self.num_cols:]): for i, xoffset in enumerate(self.offsets[self.num_cols:]):
index = self.word_size + i index = self.word_size + i
if self.bitcell.mirror.y and (index + self.column_offset) % 2: if self.bitcell.mirror.y and (index + self.column_offset) % 2:
mirror = "MY" mirror = "MY"
xoffset = xoffset + self.amp_width xoffset = xoffset + self.amp_spacing
else: else:
mirror = "" mirror = ""

View File

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

Some files were not shown because too many files have changed in this diff Show More