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
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
+ 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/
[Qflow]: http://opencircuitdesign.com/qflow/history.html
[Ngspice]: http://ngspice.sourceforge.net/
[Xyce]: http://xyce.sandia.gov/
[OSUPDK]: https://vlsiarch.ecen.okstate.edu/flow/
[FreePDK45]: https://www.eda.ncsu.edu/wiki/FreePDK45:Contents

View File

@ -2,7 +2,7 @@ TECH = scn4m_subm
CUR_DIR = $(shell pwd)
TEST_DIR = ${CUR_DIR}/tests
MAKEFLAGS += -j 1
#MAKEFLAGS += -j 1
# Library test
LIBRARY_TESTS = $(shell find ${TEST_DIR} -name 0[1-2]*_test.py)
@ -64,7 +64,43 @@ usage: ${USAGE_TESTS}
$(ALL_TESTS):
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:
find . -name \*.pyc -exec rm {} \;
find . -name \*~ -exec rm {} \;

View File

@ -93,9 +93,9 @@ class cell:
# It is assumed it is [nwell, pwell]
self._body_bias = body_bias
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_types['vpb'] = "GROUND"
self._port_types['vpb'] = "POWER"
@property
def port_types(self):
@ -176,7 +176,7 @@ class cell_properties():
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_2port"] = "row_cap_cell_2rw"
self.use_strap = False
self._ptx = _ptx(model_is_subckt=False,
bin_spice_models=False)

View File

@ -45,7 +45,8 @@ class _hierarchical_predecode:
bus_space_factor,
input_layer,
output_layer,
vertical_supply):
vertical_supply,
force_horizontal_input_contact):
# hierarchical_predecode
# 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
@ -59,6 +60,7 @@ class _hierarchical_predecode:
self.input_layer = input_layer
self.output_layer = output_layer
self.vertical_supply = vertical_supply
self.force_horizontal_input_contact = force_horizontal_input_contact
class _column_mux_array:
@ -152,7 +154,8 @@ class layer_properties():
bus_space_factor=1,
input_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",
select_pitch="m2_pitch",

View File

@ -48,11 +48,11 @@ class design(hierarchy_design):
self.add_pin_indices(prop.port_indices)
self.add_pin_names(prop.port_map)
self.add_pin_types(prop.port_types)
(width, height) = utils.get_libcell_size(self.cell_name,
GDS["unit"],
layer[prop.boundary_layer])
self.pin_map = utils.get_libcell_pins(self.pins,
self.cell_name,
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):
if subinst in self.graph_inst_exclude:
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.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)}
debug.info(3, "Instance name={}".format(inst_name))
for subinst, conns in zip(self.insts, self.conns):
subinst_name = inst_name + '.X' + subinst.name
subinst_name = inst_name + "{}x".format(OPTS.hier_seperator) + subinst.name
subinst_ports = self.translate_nets(conns, port_dict, inst_name)
for si_port, conn in zip(subinst_ports, conns):
# Only add for first occurrence
@ -166,7 +166,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
if conn in port_dict:
converted_conns.append(port_dict[conn])
else:
converted_conns.append("{}.{}".format(inst_name, conn))
converted_conns.append("{0}{2}{1}".format(inst_name, conn, OPTS.hier_seperator))
return converted_conns
def add_graph_edges(self, graph, port_nets):

View File

@ -20,6 +20,10 @@ from globals import OPTS
from vector import vector
from pin_layout import pin_layout
from utils import round_to_grid
try:
from tech import special_purposes
except ImportError:
special_purposes = {}
class layout():
@ -36,12 +40,13 @@ class layout():
# This gets set in both spice and layout so either can be called first.
self.name = name
self.cell_name = cell_name
self.gds_file = OPTS.openram_tech + "gds_lib/" + cell_name + ".gds"
self.width = 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
self.insts = []
# Set of names to check for duplicates
@ -57,7 +62,7 @@ class layout():
self.visited = []
# Flag for library cells
self.is_library_cell = False
self.gds_read()
try:
@ -65,7 +70,7 @@ class layout():
self.pwr_grid_layer = power_grid[0]
except ImportError:
self.pwr_grid_layer = "m3"
############################################################
# GDS layout
############################################################
@ -117,7 +122,7 @@ class layout():
if len(self.objs) > 0:
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)
if len(self.insts) > 0:
lowestx = min(min(inst.lx() for inst in self.insts), lowestx)
lowesty = min(min(inst.by() for inst in self.insts), lowesty)
@ -128,7 +133,7 @@ class layout():
continue
lowestx = min(min(pin.lx() for pin in pin_set), lowestx)
lowesty = min(min(pin.by() for pin in pin_set), lowesty)
return vector(lowestx, lowesty)
def find_highest_coords(self):
@ -137,7 +142,7 @@ class layout():
this layout
"""
highestx = highesty = -sys.maxsize - 1
if len(self.objs) > 0:
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)
@ -152,7 +157,7 @@ class layout():
continue
highestx = max(max(pin.rx() for pin in pin_set), highestx)
highesty = max(max(pin.uy() for pin in pin_set), highesty)
return vector(highestx, highesty)
def find_highest_layer_coords(self, layer):
@ -233,7 +238,7 @@ class layout():
# This is commented out for runtime reasons
# debug.info(4, "instance list: " + ",".join(x.name for x in self.insts))
return self.insts[-1]
def get_inst(self, name):
""" Retrieve an instance by name """
for inst in self.insts:
@ -331,7 +336,7 @@ class layout():
Return the pin or list of pins
"""
name = self.get_pin_name(text)
try:
if len(self.pin_map[name]) > 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)
"""
name = self.get_pin_name(text)
if name in self.pin_map.keys():
return self.pin_map[name]
else:
@ -359,12 +364,12 @@ class layout():
Create a mapping from internal pin names to external pin names.
"""
self.pin_names = pin_dict
self.original_pin_names = {y: x for (x, y) in self.pin_names.items()}
def get_pin_name(self, text):
""" Return the custom cell pin name """
if text in self.pin_names:
return self.pin_names[text]
else:
@ -372,7 +377,7 @@ class layout():
def get_original_pin_names(self):
""" Return the internal cell pin name """
# This uses the hierarchy_spice pins (in order)
return [self.get_original_pin_name(x) for x in self.pins]
@ -382,7 +387,7 @@ class layout():
return self.original_pin_names[text]
else:
return text
def get_pin_names(self):
"""
Return a pin list of all pins
@ -479,7 +484,7 @@ class layout():
offset=s.ll(),
width=s.width(),
height=s.height())
def replace_layout_pin(self, text, pin):
"""
Remove the old pin and replace with a new one
@ -494,7 +499,7 @@ class layout():
offset=pin.ll(),
width=pin.width(),
height=pin.height())
def add_layout_pin(self, text, layer, offset, width=None, height=None):
"""
Create a labeled pin
@ -776,7 +781,7 @@ class layout():
debug.info(3, "opening {}".format(self.gds_file))
self.gds = gdsMill.VlsiLayout(units=GDS["unit"])
reader = gdsMill.Gds2reader(self.gds)
reader.loadFromFile(self.gds_file)
reader.loadFromFile(self.gds_file, special_purposes)
else:
debug.info(3, "Creating layout structure {}".format(self.name))
self.gds = gdsMill.VlsiLayout(name=self.name, units=GDS["unit"])
@ -788,7 +793,7 @@ class layout():
debug.info(4, "Printing {}".format(gds_file))
arrayCellLayout = gdsMill.VlsiLayout(units=GDS["unit"])
reader = gdsMill.Gds2reader(arrayCellLayout, debugToTerminal=1)
reader.loadFromFile(gds_file)
reader.loadFromFile(gds_file, special_purposes)
def clear_visited(self):
""" Recursively clear the visited flag """
@ -1125,7 +1130,7 @@ class layout():
# self.add_inst(cr.name, cr)
# self.connect_inst([])
self.add_flat_inst(cr.name, cr)
def create_horizontal_channel_route(self, netlist, offset, layer_stack, directions=None):
"""
Wrapper to create a horizontal channel route
@ -1137,7 +1142,7 @@ class layout():
# self.add_inst(cr.name, cr)
# self.connect_inst([])
self.add_flat_inst(cr.name, cr)
def add_boundary(self, ll=vector(0, 0), ur=None):
""" Add boundary for debugging dimensions """
if OPTS.netlist_only:
@ -1161,6 +1166,59 @@ class layout():
height=ur.y - ll.y,
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):
"""
Add a layer that surrounds the given instances. Useful
@ -1203,22 +1261,24 @@ class layout():
height=ymax - ymin)
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.
If it is on M1, it will add a power via too.
"""
pins = inst.get_pins(name)
for pin in pins:
if new_name == "":
new_name = pin.name
if pin.layer == self.pwr_grid_layer:
self.add_layout_pin(name,
self.add_layout_pin(new_name,
pin.layer,
pin.ll(),
pin.width(),
pin.height())
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):
"""
@ -1264,16 +1324,18 @@ class layout():
width=width,
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
the given center location. The starting layer is specified to determine
which vias are needed.
"""
if new_name == "":
new_name = pin.name
if not loc:
loc = pin.center()
# Hack for min area
if OPTS.tech_name == "sky130":
min_area = drc["minarea_{}".format(self.pwr_grid_layer)]
@ -1282,9 +1344,9 @@ class layout():
else:
width = None
height = None
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,
offset=loc,
width=width,
@ -1299,7 +1361,7 @@ class layout():
width = via.width
if not 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,
offset=loc,
width=width,
@ -1341,7 +1403,182 @@ class layout():
layer=layer,
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
argument. Must have a supply_rail_width and supply_rail_pitch
@ -1350,7 +1587,7 @@ class layout():
modules..
"""
[ll, ur] = bbox
[ll, ur] = self.bbox
supply_rail_spacing = self.supply_rail_pitch - self.supply_rail_width
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.
self.name = name
self.cell_name = cell_name
self.sp_file = OPTS.openram_tech + "sp_lib/" + cell_name + ".sp"
# If we have a separate lvs directory, then all the lvs files
@ -570,6 +569,7 @@ class spice():
net = net.lower()
int_net = self.name_dict[net]['int_net']
int_mod = self.name_dict[net]['mod']
if int_mod.is_net_alias(int_net, alias, alias_mod, exclusion_set):
aliases.append(net)
return aliases

View File

@ -69,25 +69,31 @@ class lef:
def lef_write(self, lef_name):
""" 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:
debug.info(3, "Writing detailed LEF to {0}".format(lef_name))
self.detailed_lef_write(lef_name)
else:
debug.info(3, "Writing abstract LEF to {0}".format(lef_name))
# 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
self.abstract_lef_write(lef_name)
def abstract_lef_write(self, lef_name):
# To maintain the indent level easily
self.indent = ""
self.compute_abstract_blockages()
self.lef = open(lef_name, "w")
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
# minus the pin escape margin (hard coded to 4 x m3 pitch)
# 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 pin_name in self.pins:
pin = self.get_pin(pin_name)
inflated_pin = pin.inflated_pin(multiple=1)
for blockage in self.blockages[pin.layer]:
if blockage.overlaps(inflated_pin):
intersection_shape = blockage.intersection(inflated_pin)
# If it is zero area, don't add the pin
if intersection_shape[0][0]==intersection_shape[1][0] or intersection_shape[0][1]==intersection_shape[1][1]:
continue
# Remove the old blockage and add the new ones
self.blockages[pin.layer].remove(blockage)
intersection_pin = pin_layout("", intersection_shape, inflated_pin.layer)
new_blockages = blockage.cut(intersection_pin)
self.blockages[pin.layer].extend(new_blockages)
pins = self.get_pins(pin_name)
for pin in pins:
inflated_pin = pin.inflated_pin(multiple=2)
continue_fragmenting = True
while continue_fragmenting:
continue_fragmenting = False
old_blockages = list(self.blockages[pin.layer])
for blockage in old_blockages:
if blockage.overlaps(inflated_pin):
intersection_shape = blockage.intersection(inflated_pin)
# If it is zero area, don't split the blockage
if intersection_shape[0][0]==intersection_shape[1][0] or intersection_shape[0][1]==intersection_shape[1][1]:
continue
self.lef_write_pin(pin_name)
self.lef_write_obstructions(abstracted=True)
self.lef_write_footer()
self.lef.close()
def detailed_lef_write(self, lef_name):
# 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()
# Remove the old blockage and add the new ones
self.blockages[pin.layer].remove(blockage)
intersection_pin = pin_layout("", intersection_shape, inflated_pin.layer)
new_blockages = blockage.cut(intersection_pin)
self.blockages[pin.layer].extend(new_blockages)
# We split something so make another pass
continue_fragmenting = True
def lef_write_header(self):
""" Header of LEF file """
@ -155,8 +151,8 @@ class lef:
self.lef.write("{0}SYMMETRY X Y R90 ;\n".format(self.indent))
def lef_write_footer(self):
self.lef.write("{0}END {1}\n".format(self.indent, self.name))
self.indent = self.indent[:-3]
self.lef.write("{0}END {1}\n".format(self.indent, self.name))
self.lef.write("END LIBRARY\n")
def lef_write_pin(self, name):
@ -188,20 +184,20 @@ class lef:
self.indent = self.indent[:-3]
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 """
self.lef.write("{0}OBS\n".format(self.indent))
for layer in self.lef_layers:
self.lef.write("{0}LAYER {1} ;\n".format(self.indent, layer_names[layer]))
self.indent += " "
if abstracted:
blockages = self.blockages[layer]
for b in blockages:
self.lef_write_shape(b.rect)
else:
if detailed:
blockages = self.get_blockages(layer, True)
for b in blockages:
self.lef_write_shape(b)
else:
blockages = self.blockages[layer]
for b in blockages:
self.lef_write_shape(b.rect)
self.indent = self.indent[:-3]
self.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):
self._layer = layer_name
break
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._recompute_hash()
@ -396,6 +406,13 @@ class pin_layout:
try:
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:
label_purpose = purpose
@ -606,7 +623,9 @@ class pin_layout:
# Don't add the existing shape in if it overlaps the pin shape
if new_shape.contains(shape):
continue
new_shapes.append(new_shape)
# Only add non-zero shapes
if new_shape.area() > 0:
new_shapes.append(new_shape)
return new_shapes

View File

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

View File

@ -6,6 +6,7 @@
# All rights reserved.
#
import math
from tech import spice
class verilog:
@ -28,7 +29,21 @@ class verilog:
else:
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("`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:
if port in self.readwrite_ports:
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))
if self.write_size:
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))
elif port in self.write_ports:
self.vf.write(" clk{0},csb{0},".format(port))
if self.write_size:
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))
elif port in self.read_ports:
self.vf.write(" clk{0},csb{0},addr{0},dout{0}".format(port))
@ -56,7 +75,7 @@ class verilog:
if self.write_size:
self.num_wmasks = int(math.ceil(self.word_size / self.write_size))
self.vf.write(" parameter NUM_WMASKS = {0} ;\n".format(self.num_wmasks))
self.vf.write(" parameter DATA_WIDTH = {0} ;\n".format(self.word_size))
self.vf.write(" parameter 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 RAM_DEPTH = 1 << ADDR_WIDTH;\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("\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:
self.add_inputs_outputs(port)
@ -103,6 +127,10 @@ class verilog:
if port in self.write_ports:
if self.write_size:
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))
if port in self.write_ports:
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 self.write_size:
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:
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))
if self.write_size:
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))
if port in self.write_ports:
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(" begin : MEM_WRITE{0}\n".format(port))
if port in self.readwrite_ports:
if self.write_size:
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))
self.vf.write(" if ( !csb{0}_reg && !web{0}_reg ) begin\n".format(port))
else:
if self.write_size:
self.vf.write(" if (!csb{0}_reg) begin\n".format(port))
else:
self.vf.write(" if (!csb{0}_reg)\n".format(port))
self.vf.write(" if (!csb{0}_reg) begin\n".format(port))
if self.write_size:
remainder_bits = self.word_size % self.write_size
for mask in range(0, self.num_wmasks):
lower = mask * self.write_size
if (remainder_bits and mask == self.num_wmasks - 1):
upper = lower + remainder_bits - 1
else:
upper = lower + self.write_size - 1
upper = lower + self.write_size - 1
self.vf.write(" if (wmask{0}_reg[{1}])\n".format(port, mask))
self.vf.write(" mem[addr{0}_reg][{1}:{2}] = din{0}_reg[{1}:{2}];\n".format(port, upper, lower))
self.vf.write(" end\n")
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")
def add_read_block(self, port):

View File

@ -99,3 +99,8 @@ class bitcell_2port(bitcell_base.bitcell_base):
# Port 1 edges
graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], 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.mirror = prop.mirror
self.end_caps = prop.end_caps
def get_stage_effort(self, load):
parasitic_delay = 1
# This accounts for bitline being drained
@ -84,7 +83,7 @@ class bitcell_base(design.design):
return self.storage_nets
else:
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
def get_storage_net_offset(self):

View File

@ -49,3 +49,8 @@ class replica_bitcell_2port(bitcell_base.bitcell_base):
# Port 1 edges
graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], 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 .elmore import *
from .linear_regression import *
from .neural_network import *
from .setup_hold import *
from .functional import *
from .simulation import *
from .measurements import *
from .model_check import *
from .analytical_util import *
from .regression_model import *
debug.info(1, "Initializing characterizer...")
OPTS.spice_exe = ""
if not OPTS.analytical_delay:
debug.info(1, "Finding spice simulator.")
if OPTS.spice_name != "":
if OPTS.spice_name:
# Capitalize Xyce
if OPTS.spice_name == "xyce":
OPTS.spice_name = "Xyce"
OPTS.spice_exe=find_exe(OPTS.spice_name)
if OPTS.spice_exe=="" or OPTS.spice_exe==None:
debug.error("{0} not found. Unable to perform characterization.".format(OPTS.spice_name), 1)
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
if OPTS.spice_name == "ngspice":
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)
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:
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}
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
"""
@ -25,8 +25,18 @@ def get_data_names(file_name):
# reader is iterable not a list, probably a better way to do this
for row in csv_reader:
# 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):
"""
Returns data in CSV as lists of features
@ -35,24 +45,33 @@ def get_data(file_name):
with open(file_name, newline='') as csvfile:
csv_reader = csv.reader(csvfile, delimiter=' ', quotechar='|')
row_iter = 0
removed_items = 1
for row in csv_reader:
row_iter += 1
if row_iter == 1:
feature_names = row[0].split(',')
input_list = [[] for _ in feature_names]
scaled_list = [[] for _ in feature_names]
input_list = [[] for _ in range(len(feature_names)-removed_items)]
try:
# Save to remove area
area_ind = feature_names.index('area')
except ValueError:
area_ind = -1
try:
process_ind = feature_names.index('process')
except:
debug.error('Process not included as a feature.')
continue
data = []
split_str = row[0].split(',')
for i in range(len(split_str)):
if i == process_ind:
data.append(process_transform[split_str[i]])
elif i == area_ind:
continue
else:
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
self_scaled_data = scale_data_and_transform(all_data)
samples = np.asarray(self_scaled_data)
features, labels = samples[:, :-1], samples[:,-1:]
return features, labels
data_np = np.asarray(self_scaled_data)
return data_np
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)
if pos == None:
maxs,mins,avgs = [maxs[-1]],[mins[-1]],[avgs[-1]]
maxs,mins,avgs = maxs[-1],mins[-1],avgs[-1]
else:
maxs,mins,avgs = [maxs[pos]],[mins[pos]],[avgs[pos]]
maxs,mins,avgs = maxs[pos],mins[pos],avgs[pos]
unscaled_data = []
for data_row in data:
unscaled_row = []
for val, cur_max, cur_min in zip(data_row, maxs, mins):
unscaled_val = val*(cur_max-cur_min) + cur_min
unscaled_row.append(unscaled_val)
unscaled_data.append(unscaled_row)
unscaled_val = data_row*(maxs-mins) + mins
unscaled_data.append(unscaled_val)
return unscaled_data

View File

@ -11,21 +11,26 @@ import debug
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. """
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):
"""Parses a hspice output.lis file for a key value"""
lower_key = key.lower()
if OPTS.spice_name == "xa" :
# customsim has a different output file name
full_filename="{0}xa.meas".format(OPTS.openram_temp)
elif OPTS.spice_name == "spectre":
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:
# 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:
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.archive()
contents = f.read()
contents = f.read().lower()
f.close()
# val = re.search(r"{0}\s*=\s*(-?\d+.?\d*\S*)\s+.*".format(key), contents)
val = re.search(r"{0}\s*=\s*(-?\d+.?\d*[e]?[-+]?[0-9]*\S*)\s+.*".format(key), contents)
val = re.search(r"{0}\s*=\s*(-?\d+.?\d*[e]?[-+]?[0-9]*\S*)\s+.*".format(lower_key), contents)
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))
else:
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
# 3 digits = 1ps
# 4 digits = 0.1ps
# 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
# 3 digits = 1mv
# 4 digits = 0.1mv
# 5 digits = 0.01mv
# 6 digits = 1uv
# etc
return round(voltage,voltage_precision)
return round(voltage, voltage_precision)
def convert_to_float(number):
"""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
'p': lambda x: x * 0.000000000001, # pico
'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 not type(float_value)==float:
@ -92,9 +100,10 @@ def convert_to_float(number):
return float_value
def check_dict_values_is_float(dict):
"""Checks if all the values are floats. Useful for checking failed Spice measurements."""
for key, value in dict.items():
if type(value)!=float:
return False
if type(value)!=float:
return False
return True

View File

@ -69,7 +69,7 @@ class delay(simulation):
for meas in meas_list:
name = meas.name.lower()
debug.check(name not in name_set, ("SPICE measurements must have unique names. "
"Duplicate name={}").format(name))
"Duplicate name={0}").format(name))
name_set.add(name)
def create_read_port_measurement_objects(self):
@ -77,7 +77,7 @@ class delay(simulation):
self.read_lib_meas = []
self.clk_frmt = "clk{0}" # Unformatted clock name
targ_name = "{0}{1}_{2}".format(self.dout_name, "{}", self.probe_data) # Empty values are the port and probe data bit
targ_name = "{0}{{}}_{1}".format(self.dout_name, self.probe_data) # Empty values are the port and probe data bit
self.delay_meas = []
self.delay_meas.append(delay_measure("delay_lh", self.clk_frmt, targ_name, "RISE", "RISE", measure_scale=1e9))
self.delay_meas[-1].meta_str = sram_op.READ_ONE # Used to index time delay values when measurements written to spice file.
@ -166,7 +166,7 @@ class delay(simulation):
self.dout_volt_meas = []
for meas in self.delay_meas:
# Output voltage measures
self.dout_volt_meas.append(voltage_at_measure("v_{}".format(meas.name),
self.dout_volt_meas.append(voltage_at_measure("v_{0}".format(meas.name),
meas.targ_name_no_port))
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: []}
meas_cycles = (sram_op.READ_ZERO, sram_op.READ_ONE)
for cycle in meas_cycles:
meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name)
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)
for polarity, meas in single_bit_meas.items():
meas.meta_str = cycle
@ -200,7 +200,7 @@ class delay(simulation):
self.write_bit_meas = {bit_polarity.NONINVERTING: [], bit_polarity.INVERTING: []}
meas_cycles = (sram_op.WRITE_ZERO, sram_op.WRITE_ONE)
for cycle in meas_cycles:
meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name)
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)
for polarity, meas in single_bit_meas.items():
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)
storage_names = cell_inst.mod.get_storage_net_names()
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
"supported for characterization. Storage nets={}").format(storage_names))
"supported for characterization. Storage nets={0}").format(storage_names))
if OPTS.use_pex and OPTS.pex_exe[0] != "calibre":
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)
qbar_name = "bitcell_Q_bar_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col)
else:
q_name = cell_name + '.' + str(storage_names[0])
qbar_name = cell_name + '.' + str(storage_names[1])
q_name = cell_name + OPTS.hier_seperator + str(storage_names[0])
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
# but they is enforced externally. {} added to names to differentiate between ports allow the
# measurements are independent of the ports
q_meas = voltage_at_measure("v_q_{}".format(meas_tag), q_name)
qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name)
q_meas = voltage_at_measure("v_q_{0}".format(meas_tag), q_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}
@ -242,15 +242,15 @@ class delay(simulation):
# FIXME: There should be a default_read_port variable in this case, pathing is done with this
# but is never mentioned otherwise
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
# Isolate the s_en and bitline paths
debug.info(1, "self.bl_name = {}".format(self.bl_name))
debug.info(1, "self.graph.all_paths = {}".format(self.graph.all_paths))
debug.info(1, "self.bl_name = {0}".format(self.bl_name))
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]
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(bl_paths)==1, 'Found {} paths which contain the bitline net.'.format(len(bl_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 {0} paths which contain the bitline net.'.format(len(bl_paths)))
sen_path = sen_paths[0]
bitline_path = bl_paths[0]
@ -286,11 +286,11 @@ class delay(simulation):
# Create the measurements
path_meas = []
for i in range(len(path)-1):
cur_net, next_net = path[i], path[i+1]
cur_dir, next_dir = path_dirs[i], path_dirs[i+1]
meas_name = "delay_{}_to_{}".format(cur_net, next_net)
if i+1 != len(path)-1:
for i in range(len(path) - 1):
cur_net, next_net = path[i], path[i + 1]
cur_dir, next_dir = path_dirs[i], path_dirs[i + 1]
meas_name = "delay_{0}_to_{1}".format(cur_net, next_net)
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))
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))
@ -309,13 +309,13 @@ class delay(simulation):
# Convert to booleans based on function of modules (inverting/non-inverting)
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:
# Force the sense amp to be inverting for s_en->DOUT.
# bitline->DOUT is non-inverting, but the module cannot differentiate inputs.
s_en_index = path.index(self.sen_name)
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)
# 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:
variant_tuple = self.get_volt_at_measure_variants(port, measure_obj)
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
# the measurement has port as the last input. Could be implemented by measurement type or
# remove entirely from measurement classes.
@ -515,7 +515,7 @@ class delay(simulation):
elif delay_obj.meta_str == sram_op.READ_ONE:
meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]]
else:
debug.error("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.
if delay_obj.meta_add_delay:
@ -587,20 +587,20 @@ class delay(simulation):
# Output some comments to aid where cycles start and
# what is happening
for comment in self.cycle_comments:
self.sf.write("* {}\n".format(comment))
self.sf.write("* {0}\n".format(comment))
self.sf.write("\n")
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)
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)
def load_pex_net(self, net: str):
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:
return net
original_net = net
@ -640,26 +640,41 @@ class delay(simulation):
col = self.bitline_column
row = self.wordline_row
for port in set(self.targ_read_ports + self.targ_write_ports):
probe_nets.add("WEB{}".format(port))
probe_nets.add("{}.w_en{}".format(self.sram_instance_name, port))
probe_nets.add("{0}.Xbank0.Xport_data{1}.Xwrite_driver_array{1}.Xwrite_driver{2}.en_bar".format(
self.sram_instance_name, port, self.bitline_column))
probe_nets.add("{}.Xbank0.br_{}_{}".format(self.sram_instance_name, port,
self.bitline_column))
probe_nets.add("WEB{0}".format(port))
probe_nets.add("{0}{2}w_en{1}".format(self.sram_instance_name, port, OPTS.hier_seperator))
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,
port,
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:
continue
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(
"{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(
"{0}.vdd_Xbank0_Xport_data{1}_Xprecharge_array{1}_xpre_column_{2}".format(sram_name, port, col))
probe_nets.add("{0}.vdd_Xbank0_Xport_data{1}_Xwrite_driver_array{1}_xwrite_driver{2}".format(sram_name,
port, col))
"{0}{3}vdd_Xbank0_Xport_data{1}_Xprecharge_array{1}_xpre_column_{2}".format(sram_name,
port,
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)
for net in probe_nets:
debug.info(2, "Probe: {}".format(net))
self.sf.write(".plot V({}) \n".format(self.load_pex_net(net)))
debug.info(2, "Probe: {0}".format(net))
self.sf.write(".plot V({0}) \n".format(self.load_pex_net(net)))
def write_power_measures(self):
"""
@ -778,7 +793,7 @@ class delay(simulation):
if not self.check_bit_measures(self.write_bit_meas, port):
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 = {}
for measure in self.write_lib_meas:
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):
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.
if not self.check_sen_measure(port):
return (False, {})
@ -813,7 +828,7 @@ class delay(simulation):
result[port].update(read_port_dict)
self.check_path_measures()
self.path_delays = self.check_path_measures()
return (True, result)
@ -821,7 +836,7 @@ class delay(simulation):
"""Checks that the sen occurred within a half-period"""
sen_val = self.sen_meas.retrieve_measure(port=port)
debug.info(2, "s_en delay={}ns".format(sen_val))
debug.info(2, "s_en delay={0}ns".format(sen_val))
if self.sen_meas.meta_add_delay:
max_delay = self.period / 2
else:
@ -843,22 +858,22 @@ class delay(simulation):
elif self.br_name == meas.targ_name_no_port:
br_vals[meas.meta_str] = val
debug.info(2, "{}={}".format(meas.name, val))
debug.info(2, "{0}={1}".format(meas.name, val))
dout_success = True
bl_success = False
for meas in self.dout_volt_meas:
val = meas.retrieve_measure(port=port)
debug.info(2, "{}={}".format(meas.name, val))
debug.info(2, "{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:
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])
elif meas.meta_str == sram_op.READ_ZERO and val > self.vdd_voltage * 0.9:
dout_success = False
debug.info(1, "Debug measurement failed. Value {}V was read on read 0 cycle.".format(val))
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])
# 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 meas in meas_list:
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:
continue
meas_cycle = meas.meta_str
@ -896,8 +911,8 @@ class delay(simulation):
success = val < self.vdd_voltage / 2
if not success:
debug.info(1, ("Wrong value detected on probe bit during read/write cycle. "
"Check writes and control logic for bugs.\n measure={}, op={}, "
"bit_storage={}, V(bit)={}").format(meas.name, meas_cycle.name, polarity.name, val))
"Check writes and control logic for bugs.\n measure={0}, op={1}, "
"bit_storage={2}, V(bit)={3}").format(meas.name, meas_cycle.name, polarity.name, val))
return success
@ -912,7 +927,7 @@ class delay(simulation):
min_dicharge = v_discharged_bl < self.vdd_voltage * 0.9
min_diff = (v_charged_bl - v_discharged_bl) > self.vdd_voltage * 0.1
debug.info(1, "min_dicharge={}, min_diff={}".format(min_dicharge, min_diff))
debug.info(1, "min_dicharge={0}, min_diff={1}".format(min_dicharge, min_diff))
return (min_dicharge and min_diff)
def check_path_measures(self):
@ -921,13 +936,13 @@ class delay(simulation):
# Get and set measurement, no error checking done other than prints.
debug.info(2, "Checking measures in Delay Path")
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()
debug.info(2, '{}={}'.format(meas.name, val))
if type(val) != float or val > self.period/2:
debug.info(1,'Failed measurement:{}={}'.format(meas.name, val))
debug.info(2, '{0}={1}'.format(meas.name, val))
if type(val) != float or val > self.period / 2:
debug.info(1, 'Failed measurement:{}={}'.format(meas.name, val))
value_dict[meas.name] = val
#debug.info(0, "value_dict={}".format(value_dict))
return value_dict
def run_power_simulation(self):
@ -1100,14 +1115,14 @@ class delay(simulation):
# Set up to trim the netlist here if that is enabled
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)
else:
# 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
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
shutil.copy(self.sp_file, self.sim_sp_file)
@ -1120,7 +1135,7 @@ class delay(simulation):
self.create_measurement_names()
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.
"""
@ -1128,7 +1143,11 @@ class delay(simulation):
# Dict to hold all characterization values
char_sram_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.slew=max(slews)
@ -1148,11 +1167,19 @@ class delay(simulation):
leakage_offset = full_array_leakage - trim_array_leakage
# 4) At the minimum period, measure the delay, slew and power for all slew/load pairs.
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.
self.alter_lh_char_data(char_port_data)
return (char_sram_data, 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]['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"""
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.
self.targ_read_ports = self.read_ports
self.targ_write_ports = self.write_ports
for slew in slews:
for load in loads:
self.set_load_slew(load, slew)
# Find the delay, dynamic power, and leakage power of the trimmed array.
(success, delay_results) = self.run_delay_simulation()
debug.check(success, "Couldn't run a simulation. slew={0} load={1}\n".format(self.slew, self.load))
debug.info(1, "Simulation Passed: Port {0} slew={1} load={2}".format("All", self.slew, self.load))
# The results has a dict for every port but dicts can be empty (e.g. ports were not targeted).
for port in self.all_ports:
for mname, value in delay_results[port].items():
if "power" in mname:
# Subtract partial array leakage and add full array leakage for the power measures
measure_data[port][mname].append(value + leakage_offset)
else:
measure_data[port][mname].append(value)
for load, slew in load_slews:
self.set_load_slew(load, slew)
# Find the delay, dynamic power, and leakage power of the trimmed array.
(success, delay_results) = self.run_delay_simulation()
debug.check(success, "Couldn't run a simulation. slew={0} load={1}\n".format(self.slew, self.load))
debug.info(1, "Simulation Passed: Port {0} slew={1} load={2}".format("All", self.slew, self.load))
# The results has a dict for every port but dicts can be empty (e.g. ports were not targeted).
for port in self.all_ports:
for mname, value in delay_results[port].items():
if "power" in mname:
# 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)
else:
measure_data[port][mname].append(value)
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):
"""Determine dummy test address based on probe address and column mux size."""
@ -1218,13 +1262,13 @@ class delay(simulation):
if self.t_current == 0:
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,
data_ones,
wmask_ones,
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,
data_zeros,
wmask_ones,
@ -1235,11 +1279,11 @@ class delay(simulation):
self.measure_cycles[write_port]["disabled_write0"] = len(self.cycle_times) - 1
# This also ensures we will have a H->L transition on the next read
self.add_read("R data 1 address {} to set dout caps".format(inverse_address),
self.add_read("R data 1 address {0} to set dout caps".format(inverse_address),
inverse_address,
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,
read_port)
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_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,
data_ones,
wmask_ones,
@ -1259,7 +1303,7 @@ class delay(simulation):
self.add_noop_clock_one_port(write_port)
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,
data_zeros,
wmask_ones,
@ -1269,11 +1313,11 @@ class delay(simulation):
self.measure_cycles[read_port]["disabled_read1"] = len(self.cycle_times) - 1
# This also ensures we will have a L->H transition on the next read
self.add_read("R data 0 address {} to clear dout caps".format(inverse_address),
self.add_read("R data 0 address {0} to clear dout caps".format(inverse_address),
inverse_address,
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,
read_port)
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.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.
"""
@ -53,33 +53,29 @@ class elmore(simulation):
# Set delay/power for slews and loads
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)')
max_delay = 0.0
for slew in slews:
for load in loads:
# Calculate delay based on slew and load
path_delays = self.graph.get_timing(bl_path, self.corner, slew, load)
for load,slew in load_slews:
# Calculate delay based on slew and load
path_delays = self.graph.get_timing(bl_path, self.corner, slew, load)
total_delay = self.sum_delays(path_delays)
max_delay = max(max_delay, total_delay.delay)
debug.info(1,
'{}, {}, {}, {}'.format(slew,
load,
total_delay.delay / 1e3,
total_delay.slew / 1e3))
# Delay is only calculated on a single port and replicated for now.
for port in self.all_ports:
for mname in self.delay_meas_names + self.power_meas_names:
if "power" in mname:
port_data[port][mname].append(power.dynamic)
elif "delay" in mname and port in self.read_ports:
port_data[port][mname].append(total_delay.delay / 1e3)
elif "slew" in mname and port in self.read_ports:
port_data[port][mname].append(total_delay.slew / 1e3)
else:
debug.error("Measurement name not recognized: {}".format(mname), 1)
total_delay = self.sum_delays(path_delays)
max_delay = max(max_delay, total_delay.delay)
debug.info(1,
'{}, {}, {}, {}'.format(slew,
load,
total_delay.delay / 1e3,
total_delay.slew / 1e3))
# Delay is only calculated on a single port and replicated for now.
for port in self.all_ports:
for mname in self.delay_meas_names + self.power_meas_names:
if "power" in mname:
port_data[port][mname].append(power.dynamic)
elif "delay" in mname and port in self.read_ports:
port_data[port][mname].append(total_delay.delay / 1e3)
elif "slew" in mname and port in self.read_ports:
port_data[port][mname].append(total_delay.slew / 1e3)
# 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.
@ -92,11 +88,11 @@ class elmore(simulation):
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"""
# slews unused, only last load is used
load = loads[-1]
load = load_slews[-1][0]
power = self.sram.analytical_power(self.corner, load)
# convert from nW to mW
power.dynamic /= 1e6

View File

@ -32,13 +32,13 @@ class functional(simulation):
if not spfile:
# self.sp_file is assigned in base class
sram.sp_write(self.sp_file, trim=OPTS.trim_netlist)
if not corner:
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
if period:
self.period = period
if not output_path:
self.output_path = OPTS.openram_temp
else:
@ -54,7 +54,7 @@ class functional(simulation):
self.max_data = 2 ** self.word_size - 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 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))
@ -63,11 +63,11 @@ class functional(simulation):
self.addr_spare_index = self.addr_size
# If trim is set, specify the valid addresses
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:
for i in range(self.words_per_row):
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.set_corner(corner)
self.set_spice_constants()
@ -81,13 +81,17 @@ class functional(simulation):
self.create_graph()
self.set_internal_spice_names()
self.q_name, self.qbar_name = self.get_bit_name()
debug.info(2, "q name={}\nqbar name={}".format(self.q_name, self.qbar_name))
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
self.num_cycles = cycles
# This is to have ordered keys for random selection
self.stored_words = collections.OrderedDict()
self.stored_spares = collections.OrderedDict()
self.stored_spares = collections.OrderedDict()
self.read_check = []
self.read_results = []
@ -128,11 +132,12 @@ class functional(simulation):
name))
def create_random_memory_sequence(self):
# Select randomly, but have 3x more reads to increase probability
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"]
else:
rw_ops = ["noop", "write", "read"]
rw_ops = ["noop", "write", "read", "read"]
w_ops = ["noop", "write"]
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)
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.
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()
# 1. Write all the write ports 2x to seed a bunch of locations.
for i in range(3):
for port in self.write_ports:
addr = self.gen_addr()
(word, spare) = self.gen_data()
combined_word = self.combine_word(spare, word)
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
# read cycle uses all RW and R port to read from the same
# address simultaniously. This will test the viablilty of the
# transistor sizing in the bitcell.
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)
else:
(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)
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.t_current += self.period
self.check_lengths()
@ -197,9 +204,9 @@ class functional(simulation):
self.add_noop_one_port(port)
else:
(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)
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_spares[addr[:self.addr_spare_index]] = spare
w_addrs.append(addr)
@ -213,16 +220,16 @@ class functional(simulation):
(word, spare) = self.gen_data()
wmask = self.gen_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)
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_spares[addr[:self.addr_spare_index]] = spare
w_addrs.append(addr)
else:
(addr, word) = random.choice(list(self.stored_words.items()))
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
# 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
@ -232,8 +239,7 @@ class functional(simulation):
else:
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_check(word + spare, port)
self.add_read_check(spare + word, port)
self.cycle_times.append(self.t_current)
self.t_current += self.period
@ -258,19 +264,22 @@ class functional(simulation):
def add_read_check(self, word, port):
""" Add to the check array to ensure a read works. """
try:
self.check_count
except:
self.check_count = 0
self.read_check.append([word, "{0}{1}".format(self.dout_name, port), self.t_current + self.period, self.check_count])
self.check_count += 1
self.read_check.append([word,
"{0}{1}".format(self.dout_name, port),
self.t_current + self.period,
int(self.t_current/self.period)])
def read_stim_results(self):
# 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 = ""
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:
value = float(value)
if value > self.v_high:
@ -291,19 +300,24 @@ class functional(simulation):
eo_period)
return (0, error)
self.read_results.append([sp_read_value, dout_port, eo_period, check_count])
self.read_results.append([sp_read_value, dout_port, eo_period, cycle])
return (1, "SUCCESS")
def check_stim_results(self):
for i in range(len(self.read_check)):
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"
error = str.format(self.read_results[i][1],
self.read_results[i][0],
self.read_check[i][0],
int((self.read_results[i][2] - self.period) / self.period),
self.read_results[i][2])
output_name = self.read_check[i][1]
cycle = self.read_check[i][3]
read_val = self.format_value(self.read_results[i][0])
correct_val = self.format_value(self.read_check[i][0])
check_name = "v{0}_Xck{1}".format(output_name, cycle)
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(1, "SUCCESS")
@ -332,13 +346,18 @@ class functional(simulation):
def gen_data(self):
""" 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)
if self.num_spare_cols>0:
random_value = random.randint(0, self.max_col_data)
spare_bits = binary_repr(random_value, self.num_spare_cols)
else:
spare_bits = ""
# FIXME: Set these to 0 for now...
spare_bits = "0" * len(spare_bits)
return data_bits, spare_bits
def gen_addr(self):
@ -363,7 +382,7 @@ class functional(simulation):
self.stim_sp = "functional_stim.sp"
temp_stim = "{0}/{1}".format(self.output_path, self.stim_sp)
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)
# Write include statements
@ -387,16 +406,16 @@ class functional(simulation):
# Write important signals to stim file
self.sf.write("\n\n* Important signals for debug\n")
self.sf.write("* bl: {}\n".format(self.bl_name.format(port)))
self.sf.write("* br: {}\n".format(self.br_name.format(port)))
self.sf.write("* s_en: {}\n".format(self.sen_name))
self.sf.write("* q: {}\n".format(self.q_name))
self.sf.write("* qbar: {}\n".format(self.qbar_name))
self.sf.write("* bl:\t{0}\n".format(self.bl_name.format(port)))
self.sf.write("* br:\t{0}\n".format(self.br_name.format(port)))
self.sf.write("* s_en:\t{0}\n".format(self.sen_name))
self.sf.write("* q:\t{0}\n".format(self.q_name))
self.sf.write("* qbar:\t{0}\n".format(self.qbar_name))
# Write debug comments to stim file
self.sf.write("\n\n* Sequence of operations\n")
for comment in self.fn_cycle_comments:
self.sf.write("*{}\n".format(comment))
self.sf.write("*{0}\n".format(comment))
# Generate data input bits
self.sf.write("\n* Generation of data and address signals\n")
@ -414,10 +433,10 @@ class functional(simulation):
# Generate control signals
self.sf.write("\n * Generation of control signals\n")
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:
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
for port in self.write_ports:
@ -444,20 +463,21 @@ class functional(simulation):
self.stim.gen_pulse(sig_name="{0}{1}".format("clk", port),
v1=self.gnd_voltage,
v2=self.vdd_voltage,
offset=self.period,
offset=self.period - 0.5 * self.slew,
period=self.period,
t_rise=self.slew,
t_fall=self.slew)
# Generate dout value measurements
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
num_bits = self.word_size + self.num_spare_cols
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)
measure_name = "V{0}ck{1}".format(signal_name, cycle)
voltage_value = self.stim.get_voltage(word[num_bits - bit - 1])
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.sf.close()
#FIXME: Similar function to delay.py, refactor this
# FIXME: Similar function to delay.py, refactor this
def get_bit_name(self):
""" Get a bit cell name """
(cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, 0, 0)
storage_names = cell_inst.mod.get_storage_net_names()
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
"supported for characterization. Storage nets={}").format(storage_names))
q_name = cell_name + '.' + str(storage_names[0])
qbar_name = cell_name + '.' + str(storage_names[1])
"supported for characterization. Storage nets={0}").format(storage_names))
q_name = cell_name + OPTS.hier_seperator + str(storage_names[0])
qbar_name = cell_name + OPTS.hier_seperator + str(storage_names[1])
return (q_name, qbar_name)

View File

@ -5,7 +5,8 @@
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import os
import os,sys,re
import time
import debug
import datetime
from .setup_hold import *
@ -14,6 +15,7 @@ from .charutils import *
import tech
import numpy as np
from globals import OPTS
from tech import spice
class lib:
@ -21,10 +23,20 @@ class lib:
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.sram = sram
self.sp_file = sp_file
self.use_model = use_model
self.pred_time = None
self.set_port_indices()
self.prepare_tables()
@ -44,16 +56,32 @@ class lib:
def prepare_tables(self):
""" Determine the load/slews if they aren't specified in the config file. """
# These are the parameters to determine the table sizes
self.load_scales = np.array(OPTS.load_scales)
self.load = tech.spice["dff_in_cap"]
self.loads = self.load_scales * self.load
debug.info(1, "Loads: {0}".format(self.loads))
if OPTS.use_specified_load_slew == None:
self.load_scales = np.array(OPTS.load_scales)
self.load = tech.spice["dff_in_cap"]
self.loads = self.load_scales * self.load
self.slew_scales = np.array(OPTS.slew_scales)
self.slew = tech.spice["rise_time"]
self.slews = self.slew_scales * self.slew
self.slew_scales = np.array(OPTS.slew_scales)
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, "Loads: {0}".format(self.loads))
debug.info(1, "self.load_slews : {0}".format(self.load_slews))
def create_corners(self):
""" Create corners for characterization. """
# Get the corners from the options file
@ -74,7 +102,7 @@ class lib:
self.corners = []
self.lib_files = []
if OPTS.use_specified_corners == None:
# Nominal corner
corner_tuples = set()
@ -86,7 +114,7 @@ class lib:
for v in self.supply_voltages:
for t in self.temperatures:
corner_tuples.add((p, v, t))
else:
else:
nom_corner = (nom_process, nom_supply, nom_temperature)
corner_tuples.add(nom_corner)
if not OPTS.nominal_corner_only:
@ -104,7 +132,7 @@ class lib:
corner_tuples.remove(nom_corner)
else:
corner_tuples = OPTS.use_specified_corners
for corner_tuple in corner_tuples:
self.add_corner(*corner_tuple)
@ -124,7 +152,9 @@ class lib:
def characterize_corners(self):
""" Characterize the list of 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):
run_start = time.time()
debug.info(1,"Corner: " + str(self.corner))
(self.process, self.voltage, self.temperature) = self.corner
self.lib = open(lib_name, "w")
@ -132,7 +162,12 @@ class lib:
self.corner_name = lib_name.replace(self.out_dir,"").replace(".lib","")
self.characterize()
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):
""" Characterize the current corner. """
@ -249,8 +284,8 @@ class lib:
self.lib.write(" default_max_fanout : 4.0 ;\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 ( GND, 0 );\n\n")
self.lib.write(" voltage_map ( {0}, {1} );\n".format(self.vdd_name.upper(), self.voltage))
self.lib.write(" voltage_map ( {0}, 0 );\n\n".format(self.gnd_name.upper()))
def create_list(self,values):
""" Helper function to create quoted, line wrapped list """
@ -331,16 +366,16 @@ class lib:
self.lib.write(" base_type : array;\n")
self.lib.write(" data_type : bit;\n")
self.lib.write(" bit_width : {0};\n".format(self.sram.word_size))
self.lib.write(" bit_from : 0;\n")
self.lib.write(" bit_to : {0};\n".format(self.sram.word_size - 1))
self.lib.write(" bit_from : {0};\n".format(self.sram.word_size - 1))
self.lib.write(" bit_to : 0;\n")
self.lib.write(" }\n\n")
self.lib.write(" type (addr){\n")
self.lib.write(" base_type : array;\n")
self.lib.write(" data_type : bit;\n")
self.lib.write(" bit_width : {0};\n".format(self.sram.addr_size))
self.lib.write(" bit_from : 0;\n")
self.lib.write(" bit_to : {0};\n".format(self.sram.addr_size - 1))
self.lib.write(" bit_from : {0};\n".format(self.sram.addr_size - 1))
self.lib.write(" bit_to : 0;\n")
self.lib.write(" }\n\n")
if self.sram.write_size:
@ -348,8 +383,8 @@ class lib:
self.lib.write(" base_type : array;\n")
self.lib.write(" data_type : bit;\n")
self.lib.write(" bit_width : {0};\n".format(self.sram.num_wmasks))
self.lib.write(" bit_from : 0;\n")
self.lib.write(" bit_to : {0};\n".format(self.sram.num_wmasks - 1))
self.lib.write(" bit_from : {0};\n".format(self.sram.num_wmasks - 1))
self.lib.write(" bit_to : 0;\n")
self.lib.write(" }\n\n")
@ -582,12 +617,12 @@ class lib:
self.lib.write(" }\n")
def write_pg_pin(self):
self.lib.write(" pg_pin(vdd) {\n")
self.lib.write(" voltage_name : VDD;\n")
self.lib.write(" pg_pin({0}) ".format(self.vdd_name) + "{\n")
self.lib.write(" voltage_name : {};\n".format(self.vdd_name.upper()))
self.lib.write(" pg_type : primary_power;\n")
self.lib.write(" }\n\n")
self.lib.write(" pg_pin(gnd) {\n")
self.lib.write(" voltage_name : GND;\n")
self.lib.write(" pg_pin({0}) ".format(self.gnd_name) + "{\n")
self.lib.write(" voltage_name : {};\n".format(self.gnd_name.upper()))
self.lib.write(" pg_type : primary_ground;\n")
self.lib.write(" }\n\n")
@ -599,11 +634,13 @@ class lib:
from .linear_regression import linear_regression as model
elif model_name_lc == "elmore":
from .elmore import elmore as model
elif model_name_lc == "neural_network":
from .neural_network import neural_network as model
else:
debug.error("{} model not recognized. See options.py for available models.".format(OPTS.model_name))
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:
self.d = delay(self.sram, self.sp_file, self.corner)
@ -612,8 +649,21 @@ class lib:
else:
probe_address = "0" + "1" * (self.sram.addr_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
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):
""" Do the analysis if we haven't characterized a FF yet """
@ -621,35 +671,39 @@ class lib:
if not hasattr(self,"sh"):
self.sh = setup_hold(self.corner)
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:
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 """
if OPTS.output_datasheet_info:
datasheet_path = OPTS.output_path
else:
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_signal_from_ports(datasheet,
"din{1}[{0}:0]".format(self.sram.word_size - 1, '{}'),
self.write_ports,
"din{1}[{0}:0]".format(self.sram.word_size - 1, '{}'),
self.write_ports,
"setup_times_LH",
"setup_times_HL",
"setup_times_HL",
"hold_times_LH",
"hold_times_HL")
# self.write_signal_from_ports(datasheet,
# "dout{1}[{0}:0]".format(self.sram.word_size - 1, '{}'),
# self.read_ports,
# "dout{1}[{0}:0]".format(self.sram.word_size - 1, '{}'),
# self.read_ports,
# "delay_lh",
# "delay_hl",
# "delay_hl",
# "slew_lh",
# "slew_hl")
# "slew_hl")
for port in self.all_ports:
#dout timing
if port in self.read_ports:
@ -666,41 +720,41 @@ class lib:
min(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,
"csb{}",
self.all_ports,
"csb{}",
self.all_ports,
"setup_times_LH",
"setup_times_HL",
"setup_times_HL",
"hold_times_LH",
"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,
"web{}",
self.readwrite_ports,
"addr{1}[{0}:0]".format(self.sram.addr_size - 1, '{}'),
self.all_ports,
"setup_times_LH",
"setup_times_HL",
"setup_times_HL",
"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_model_params(datasheet)
self.write_model_params(datasheet, time)
datasheet.write("END\n")
datasheet.close()
def write_inp_params_datasheet(self, datasheet, corner, lib_name):
if OPTS.is_unit_test:
git_id = 'FFFFFFFFFFFFFFFFFFFF'
@ -720,7 +774,7 @@ class lib:
debug.warning("Failed to retrieve git id")
git_id = 'Failed to retrieve'
current_time = datetime.date.today()
# 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(
OPTS.output_name,
@ -748,8 +802,8 @@ class lib:
# write area
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:
datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},".format(
signal.format(port),
@ -766,8 +820,8 @@ class lib:
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
for port in self.all_ports:
name = ''
@ -806,31 +860,30 @@ class lib:
control_str += ' & csb{0}'.format(i)
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"""
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('slews', list(self.slews)))
datasheet.write("{0},{1},".format('loads', list(self.loads)))
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_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('fall_transition_{}'.format(port), self.char_port_results[port]["slew_hl"]))
for port in self.write_ports:
write1_power = np.mean(self.char_port_results[port]["write1_power"])
write0_power = np.mean(self.char_port_results[port]["write0_power"])
datasheet.write("{0},{1},".format('write_rise_power_{}'.format(port), write1_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:
read1_power = np.mean(self.char_port_results[port]["read1_power"])
read0_power = np.mean(self.char_port_results[port]["read0_power"])
datasheet.write("{0},{1},".format('read_rise_power_{}'.format(port), read1_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 sklearn.linear_model import Ridge
from globals import OPTS
import debug
@ -18,12 +19,16 @@ class linear_regression(regression_model):
def __init__(self, sram, spfile, corner):
super().__init__(sram, spfile, corner)
def get_model(self):
return Ridge()
def generate_model(self, features, labels):
"""
Supervised training of model.
"""
model = LinearRegression()
#model = LinearRegression()
model = self.get_model()
model.fit(features, labels)
return model

View File

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

View File

@ -5,18 +5,16 @@
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import sys,re,shutil
import debug
import tech
import math
from .stimuli import *
from .trim_spice import *
from .charutils import *
import utils
from globals import OPTS
from .delay import delay
from .measurements import *
class model_check(delay):
"""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"
def create_measurement_names(self, port):
"""Create measurement names. The names themselves currently define the type of measurement"""
#Create delay measurement names
wl_en_driver_delay_names = ["delay_wl_en_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
wl_driver_delay_names = ["delay_wl_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_driver_stages())]
sen_driver_delay_names = ["delay_sen_dvr_{}".format(stage) for stage in range(1,self.get_num_sen_driver_stages())]
"""
Create measurement names. The names themselves currently define the type of measurement
"""
wl_en_driver_delay_names = ["delay_wl_en_dvr_{0}".format(stage) for stage in range(1, self.get_num_wl_en_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:
dc_delay_names = ['delay_dc_out_final']
dc_delay_names = ["delay_dc_out_final"]
else:
dc_delay_names = ["delay_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)]
self.wl_delay_meas_names = wl_en_driver_delay_names+["delay_wl_en", "delay_wl_bar"]+wl_driver_delay_names+["delay_wl"]
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"]
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:
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.rbl_delay_meas_names = ["delay_gated_clk_nand"] + dc_delay_names
self.sae_delay_meas_names = ["delay_pre_sen"] + sen_driver_delay_names + ["delay_sen"]
# if self.custom_delaychain:
# 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:
self.delay_chain_indices = (len(self.rbl_delay_meas_names)-len(dc_delay_names), len(self.rbl_delay_meas_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_driver_slew_names = ["slew_wl_dvr_{}".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())]
self.delay_chain_indices = (len(self.rbl_delay_meas_names) - len(dc_delay_names), len(self.rbl_delay_meas_names))
# Create slew measurement names
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_{0}".format(stage) for stage in range(1, self.get_num_wl_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:
dc_slew_names = ['slew_dc_out_final']
dc_slew_names = ["slew_dc_out_final"]
else:
dc_slew_names = ["slew_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)]
self.wl_slew_meas_names = ["slew_wl_gated_clk_bar"]+wl_en_driver_slew_names+["slew_wl_en", "slew_wl_bar"]+wl_driver_slew_names+["slew_wl"]
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"]
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:
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.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar"] + dc_slew_names
self.sae_slew_meas_names = ["slew_replica_bl0", "slew_pre_sen"] + sen_driver_slew_names + ["slew_sen"]
self.bitline_meas_names = ["delay_wl_to_bl", "delay_bl_to_dout"]
self.power_meas_names = ['read0_power']
self.power_meas_names = ["read0_power"]
def create_signal_names(self, port):
"""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.
"""
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())]
wl_driver_signals = ["Xsram.Xbank0.Xwordline_driver{}.Xwl_driver_inv{}.Zb{}_int".format('{}', self.wordline_row, stage) for stage in range(1,self.get_num_wl_driver_stages())]
sen_driver_signals = ["Xsram.Xcontrol{}.Xbuf_s_en.Zb{}_int".format('{}',stage) for stage in range(1,self.get_num_sen_driver_stages())]
# Signal names are all hardcoded, need to update to make it work for probe address and different configurations.
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())]
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:
delay_chain_signal_names = []
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:
port_format = '{}'
else:
port_format = ''
self.wl_signal_names = ["Xsram.Xcontrol{}.gated_clk_bar".format('{}')]+\
wl_en_driver_signals+\
["Xsram.wl_en{}".format('{}'), "Xsram.Xbank0.Xwordline_driver{}.wl_bar_{}".format('{}',self.wordline_row)]+\
wl_driver_signals+\
["Xsram.Xbank0.wl{}_{}".format(port_format, self.wordline_row)]
pre_delay_chain_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 + \
["Xsram{0}wl_en{{}}".format(OPTS.hier_seperator),
"Xsram{1}Xbank0{1}Xwordline_driver{{}}{1}wl_bar_{0}".format(self.wordline_row,
OPTS.hier_seperator)] + \
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:
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+\
delay_chain_signal_names+\
["Xsram.Xcontrol{}.Xreplica_bitline.delayed_en".format('{}')]
self.rbl_en_signal_names = pre_delay_chain_names + \
delay_chain_signal_names + \
["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('{}')]+\
sen_driver_signals+\
["Xsram.s_en{}".format('{}')]
dout_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit
self.bl_signal_names = ["Xsram.Xbank0.wl{}_{}".format(port_format, self.wordline_row),\
"Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column),\
dout_name]
self.bl_signal_names = ["Xsram{2}Xbank0{2}wl{0}_{1}".format(port_format, self.wordline_row, OPTS.hier_seperator),
"Xsram{2}Xbank0{2}bl{0}_{1}".format(port_format, self.bitline_column, OPTS.hier_seperator),
"{0}{{}}_{1}".format(self.dout_name, self.probe_data)] # Empty values are the port and probe data bit
def create_measurement_objects(self):
"""Create the measurements used for read and write ports"""
@ -124,7 +128,7 @@ class model_check(delay):
self.create_sae_meas_objs()
self.create_bl_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):
"""Create power measurement object. Only one."""
@ -138,14 +142,14 @@ class model_check(delay):
targ_dir = "FALL"
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_signal_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],
trig_dir,
targ_dir,
measure_scale=1e9))
self.wl_meas_objs.append(slew_measure(self.wl_slew_meas_names[i-1],
self.wl_signal_names[i-1],
self.wl_meas_objs.append(slew_measure(self.wl_slew_meas_names[i - 1],
self.wl_signal_names[i - 1],
trig_dir,
measure_scale=1e9))
temp_dir = trig_dir
@ -155,9 +159,9 @@ class model_check(delay):
def create_bl_meas_objs(self):
"""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 = []
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_signal_names[0],
self.bl_signal_names[-1],
@ -171,22 +175,22 @@ class model_check(delay):
self.sae_meas_objs = []
trig_dir = "RISE"
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)):
self.sae_meas_objs.append(delay_measure(self.rbl_delay_meas_names[i-1],
self.rbl_en_signal_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],
trig_dir,
targ_dir,
measure_scale=1e9))
self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[i-1],
self.rbl_en_signal_names[i-1],
self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[i - 1],
self.rbl_en_signal_names[i - 1],
trig_dir,
measure_scale=1e9))
temp_dir = trig_dir
trig_dir = targ_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.rbl_en_signal_names[-2],
self.rbl_en_signal_names[-1],
@ -198,18 +202,18 @@ class model_check(delay):
trig_dir,
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"
targ_dir = "RISE"
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_signal_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],
trig_dir,
targ_dir,
measure_scale=1e9))
self.sae_meas_objs.append(slew_measure(self.sae_slew_meas_names[i-1],
self.sae_signal_names[i-1],
self.sae_meas_objs.append(slew_measure(self.sae_slew_meas_names[i - 1],
self.sae_signal_names[i - 1],
trig_dir,
measure_scale=1e9))
temp_dir = trig_dir
@ -231,16 +235,16 @@ class model_check(delay):
self.sf.write("* {}\n".format(comment))
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):
"""Get the measurement values that can either vary from simulation to simulation (vdd, address)
or port to port (time delays)"""
#Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port
#Assuming only read 0 for now
debug.info(3,"Power measurement={}".format(measure_obj))
# Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port
# Assuming only read 0 for now
debug.info(3, "Power measurement={}".format(measure_obj))
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)
elif type(measure_obj) is power_measure:
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):
"""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_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)
@ -280,8 +284,8 @@ class model_check(delay):
elif type(measure)is power_measure:
power_meas_list.append(measure_value)
else:
debug.error("Measurement object not recognized.",1)
return delay_meas_list, slew_meas_list,power_meas_list
debug.error("Measurement object not recognized.", 1)
return delay_meas_list, slew_meas_list, power_meas_list
def run_delay_simulation(self):
"""
@ -290,7 +294,7 @@ class model_check(delay):
works on the trimmed netlist by default, so powers do not
include leakage of all cells.
"""
#Sanity Check
# Sanity Check
debug.check(self.period > 0, "Target simulation period non-positive")
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
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:
#Parse and check the voltage measurements
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)
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)
return (True,wl_delay_result, sae_delay_result, wl_slew_result, sae_slew_result, bl_delay_result, bl_slew_result, power_result)
# Parse and check the voltage measurements
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)
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)
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):
"""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):
"""Takes in a list of measured delays and convert it to simple units to easily compare to model values."""
converted_values = []
#Calculate average
# Calculate average
total = 0
for meas_value in delay_list:
total+=meas_value
average = total/len(delay_list)
average = total / len(delay_list)
#Convert values
# Convert values
for meas_value in delay_list:
converted_values.append(meas_value/average)
converted_values.append(meas_value / average)
return converted_values
def min_max_normalization(self, value_list):
"""Re-scales input values on a range from 0-1 where min(list)=0, max(list)=1"""
scaled_values = []
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:
scaled_values.append((value-average)/(min_max_diff))
scaled_values.append((value - average) / (min_max_diff))
return scaled_values
def calculate_error_l2_norm(self, list_a, list_b):
"""Calculates error between two lists using the l2 norm"""
error_list = []
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
def compare_measured_and_model(self, measured_vals, model_vals):
"""First scales both inputs into similar ranges and then compares the error between both."""
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)
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)
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):
"""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()
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
self.targ_read_ports = [read_port]
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()
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 slews:\n\t {}".format(wl_slews[read_port]))
debug.info(1,"Measured SAE delays (ns):\n\t {}".format(sae_delays[read_port]))
debug.info(1,"Measured SAE slews:\n\t {}".format(sae_slews[read_port]))
debug.info(1,"Measured Bitline delays (ns):\n\t {}".format(bl_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 {0}".format(wl_slews[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 {0}".format(sae_slews[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.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.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)
debug.info(1,"Wordline model delays:\n\t {}".format(wl_model_delays))
debug.info(1,"SAE model delays:\n\t {}".format(sae_model_delays))
debug.info(1, "Wordline model delays:\n\t {0}".format(wl_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.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.")
# self.compare_measured_and_model(wl_delays[read_port], wl_model_delays)
# debug.info(1, "Comparing SAE measurements and model")
@ -430,17 +434,17 @@ class model_check(delay):
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."""
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.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.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.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:
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"]
return name_dict

View File

@ -9,10 +9,7 @@
from .regression_model import regression_model
from globals import OPTS
import debug
from tensorflow import keras
from tensorflow.keras import layers
import tensorflow as tf
from sklearn.neural_network import MLPRegressor
class neural_network(regression_model):
@ -20,21 +17,19 @@ class neural_network(regression_model):
def __init__(self, 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):
"""
Supervised training of model.
Training multilayer model
"""
model = keras.Sequential([
layers.Dense(32, activation=tf.nn.relu, input_shape=[features.shape[1]]),
layers.Dense(32, activation=tf.nn.relu),
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)
flat_labels = np.ravel(labels)
model = self.get_model()
model.fit(features, flat_labels)
return model
def model_prediction(self, model, features):
@ -44,5 +39,6 @@ class neural_network(regression_model):
"""
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
relative_data_path = "/sim_data"
relative_data_path = "sim_data"
data_file = "sim_data.csv"
data_fnames = ["rise_delay.csv",
"fall_delay.csv",
"rise_slew.csv",
@ -22,7 +23,8 @@ data_fnames = ["rise_delay.csv",
"write0_power.csv",
"read1_power.csv",
"read0_power.csv",
"leakage_data.csv"]
"leakage_data.csv",
"sim_time.csv"]
# Positions must correspond to data_fname list
lib_dnames = ["delay_lh",
"delay_hl",
@ -32,14 +34,15 @@ lib_dnames = ["delay_lh",
"write0_power",
"read1_power",
"read0_power",
"leakage_power"]
"leakage_power",
"sim_time"]
# Check if another data dir was specified
if OPTS.sim_data_path == None:
data_dir = OPTS.openram_tech+relative_data_path
else:
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):
@ -47,21 +50,25 @@ class regression_model(simulation):
super().__init__(sram, spfile, 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
"""
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)
model_inputs = [log_num_words,
OPTS.word_size,
OPTS.words_per_row,
self.sram.width * self.sram.height,
OPTS.words_per_row,
OPTS.local_array_size,
process_transform[self.process],
self.vdd_voltage,
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()
models = self.train_models()
@ -69,36 +76,35 @@ class regression_model(simulation):
port_data = self.get_empty_measure_data_dict()
debug.info(1, 'Slew, Load, Port, Delay(ns), Slew(ns)')
max_delay = 0.0
for slew in slews:
for load in loads:
# List returned with value order being delay, power, leakage, slew
sram_vals = self.get_predictions(model_inputs+[slew, load], models)
# Delay is only calculated on a single port and replicated for now.
for port in self.all_ports:
port_data[port]['delay_lh'].append(sram_vals['delay_lh'])
port_data[port]['delay_hl'].append(sram_vals['delay_hl'])
port_data[port]['slew_lh'].append(sram_vals['slew_lh'])
port_data[port]['slew_hl'].append(sram_vals['slew_hl'])
for load, slew in load_slews:
# List returned with value order being delay, power, leakage, slew
sram_vals = self.get_predictions(model_inputs+[slew, load], models)
# Delay is only calculated on a single port and replicated for now.
for port in self.all_ports:
port_data[port]['delay_lh'].append(sram_vals['rise_delay'])
port_data[port]['delay_hl'].append(sram_vals['fall_delay'])
port_data[port]['slew_lh'].append(sram_vals['rise_slew'])
port_data[port]['slew_hl'].append(sram_vals['fall_slew'])
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'])
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'])
debug.info(1, '{}, {}, {}, {}, {}'.format(slew,
load,
port,
sram_vals['delay_lh'],
sram_vals['slew_lh']))
debug.info(1, '{}, {}, {}, {}, {}'.format(slew,
load,
port,
sram_vals['rise_delay'],
sram_vals['rise_slew']))
# Estimate the period as double the delay with margin
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"]}
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
"""
#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_paths[data_name])])
#Scaled the inputs using first data file as a reference
scaled_inputs = np.asarray([scale_input_datapoint(model_inputs, data_path)])
predictions = {}
for dname in data_paths.keys():
path = data_paths[dname]
out_pos = 0
for dname in self.output_names:
m = models[dname]
features, labels = get_scaled_data(path)
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))
predictions[dname] = pred[0][0]
predictions[dname] = pred[0]
out_pos+=1
return predictions
def train_models(self):
"""
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 = {}
for dname, dpath in data_paths.items():
features, labels = get_scaled_data(dpath)
model = self.generate_model(features, labels)
models[dname] = model
for o_name in self.output_names:
output_label = labels[:,output_num]
model = self.generate_model(features, output_label)
models[o_name] = model
output_num+=1
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()
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
characterization.
"""
self.sf.write("\n* Generation of the data and clk signals\n")
if correct_value == 1:
@ -106,8 +106,11 @@ class setup_hold():
setup=0)
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",
# initial clk edge is right after the 0 time to initialize a flop
@ -128,16 +131,6 @@ class setup_hold():
else:
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")
trig_name = "clk"
targ_name = "Q"
@ -153,19 +146,6 @@ class setup_hold():
trig_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):
""" 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
@ -189,26 +169,28 @@ class setup_hold():
correct_value=correct_value)
self.stim.run_sim(self.stim_sp)
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"))
debug.info(2,"*** {0} CHECK: {1} Ideal Clk-to-Q: {2} Setup/Hold: {3}".format(mode, correct_value,ideal_clk_to_q,setuphold_time))
# We use a 1/2 speed clock for some reason...
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:
debug.error("Initial hold time fails for data value feasible bound {0} Clk-to-Q {1} Setup/Hold {2}".format(feasible_bound,
ideal_clk_to_q,
setuphold_time),
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,
ideal_clk_to_q,
setuphold_time),
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,
setuphold_time,
feasible_bound,
2 * self.period))
#raw_input("Press Enter to continue...")
while True:
target_time = (feasible_bound + infeasible_bound) / 2
@ -224,15 +206,14 @@ class setup_hold():
self.stim.run_sim(self.stim_sp)
clk_to_q = convert_to_float(parse_spice_list("timing", "clk2q_delay"))
setuphold_time = convert_to_float(parse_spice_list("timing", "setup_hold_time"))
if type(clk_to_q) == float and (clk_to_q < 1.1 * ideal_clk_to_q) and type(setuphold_time)==float:
if mode == "SETUP": # SETUP is clk-din, not din-clk
setuphold_time *= -1e9
else:
setuphold_time *= 1e9
debug.info(2, "PASS Clk-to-Q: {0} Setup/Hold: {1}".format(clk_to_q, setuphold_time))
# We use a 1/2 speed clock for some reason...
setuphold_time = (target_time - 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
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
else:
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))
break
debug.info(2, "Converged on {0} time {1}.".format(mode, passing_setuphold_time))
return passing_setuphold_time

View File

@ -37,6 +37,8 @@ class simulation():
self.read_ports = self.sram.read_ports
self.write_ports = self.sram.write_ports
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:
self.num_wmasks = int(math.ceil(self.word_size / self.write_size))
else:
@ -294,7 +296,8 @@ class simulation():
self.add_data(data, port)
self.add_address(address, port)
self.add_wmask(wmask, port)
self.add_spare_wen("1" * self.num_spare_cols, port)
# Disable spare writes for now
self.add_spare_wen("0" * self.num_spare_cols, port)
def add_read_one_port(self, comment, address, port):
""" Add the control values for a read cycle. Does not increment the period. """
@ -369,6 +372,38 @@ class simulation():
time_spacing,
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):
if op == "noop":
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"):
self.graph.get_all_paths('{}{}'.format("clk", port),
'{}{}_{}'.format(self.dout_name, port, self.probe_data))
sen_with_port = self.get_sen_name(self.graph.all_paths)
if sen_with_port.endswith(str(port)):
self.sen_name = sen_with_port[:-len(str(port))]
@ -481,38 +515,32 @@ class simulation():
self.sen_name = sen_with_port
debug.warning("Error occurred while determining SEN name. Can cause faults in simulation.")
debug.info(2, "s_en name = {}".format(self.sen_name))
column_addr = self.get_column_addr()
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))
if bl_name_port.endswith(str(port) + "_" + str(column_addr)):
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
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
else:
self.bl_name = bl_name_port
debug.warning("Error occurred while determining bitline names. Can cause faults in simulation.")
if br_name_port.endswith(str(port) + "_" + str(column_addr)):
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
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
else:
self.br_name = br_name_port
debug.warning("Error occurred while determining bitline names. Can cause faults in simulation.")
debug.info(2, "bl name={}, br name={}".format(self.bl_name, self.br_name))
else:
self.graph.get_all_paths('{}{}'.format("clk", port),
'{}{}_{}'.format(self.dout_name, port, self.probe_data))
self.sen_name = self.get_sen_name(self.graph.all_paths)
debug.info(2, "s_en name = {}".format(self.sen_name))
#debug.info(2, "s_en {}".format(self.sen_name))
self.bl_name = "bl{0}_{1}".format(port, OPTS.word_size - 1)
self.br_name = "br{0}_{1}".format(port, OPTS.word_size - 1)
debug.info(2, "bl name={}, br name={}".format(self.bl_name, self.br_name))
# 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):
"""
@ -535,8 +563,13 @@ class simulation():
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.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
self.graph = graph_util.timing_graph()
@ -576,7 +609,11 @@ class simulation():
"""
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_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))
if OPTS.use_pex and OPTS.pex_exe[0] != "calibre":
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]
def get_empty_measure_data_dict(self):
"""Make a dict of lists for each type of delay and power measurement to append results to"""
measure_names = self.delay_meas_names + self.power_meas_names
# 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]
return measure_data
return measure_data
def sum_delays(self, delays):
"""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)):
delay+=delays[i]
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
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}"
debug.check(len(clk_times)==len(data_values),
str.format(len(clk_times),
@ -169,7 +169,7 @@ class stimuli():
def gen_constant(self, sig_name, v_val):
""" 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))
def get_voltage(self, value):
if value == "0" or value == 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):
""" 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"
self.sf.write(measure_string.format(meas_name,
self.sf.write(measure_string.format(meas_name.lower(),
trig_name,
trig_val,
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):
""" 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"
self.sf.write(measure_string.format(meas_name,
self.sf.write(measure_string.format(meas_name.lower(),
targ_name,
trig_name,
trig_val,
@ -204,7 +204,7 @@ class stimuli():
def gen_meas_find_voltage_at_time(self, meas_name, targ_name, time_at):
""" Creates the .meas statement for voltage at time"""
measure_string=".meas tran {0} FIND v({1}) AT={2}n \n\n"
self.sf.write(measure_string.format(meas_name,
self.sf.write(measure_string.format(meas_name.lower(),
targ_name,
time_at))
@ -215,13 +215,14 @@ class stimuli():
power_exp = "power"
else:
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,
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)
def write_control(self, end_time, runlvl=4):
@ -236,18 +237,20 @@ class stimuli():
reltol = 0.005 # 0.5%
else:
reltol = 0.001 # 0.1%
timestep = 10 # ps, was 5ps but ngspice was complaining the timestep was too small in certain tests.
timestep = 10 # ps
self.sf.write(".TEMP {}\n".format(self.temperature))
if OPTS.spice_name == "ngspice":
self.sf.write(".TEMP {}\n".format(self.temperature))
# 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
# 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
# unless you figure out what these are.
self.sf.write(".OPTIONS POST=1 RELTOL={0} PROBE method=gear ACCT\n".format(reltol))
elif OPTS.spice_name == "spectre":
self.sf.write(".TEMP {}\n".format(self.temperature))
self.sf.write("simulator lang=spectre\n")
if OPTS.use_pex:
nestlvl = 1
@ -255,8 +258,7 @@ class stimuli():
else:
nestlvl = 10
spectre_save = "lvlpub"
self.sf.write('saveOptions options save={} nestlvl={} pwr=total \n'.format(
spectre_save, nestlvl))
self.sf.write('saveOptions options save={} nestlvl={} pwr=total \n'.format(spectre_save, nestlvl))
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 "
" 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 '
' annotate=status maxiters=5 \n'.format("5p", end_time))
self.sf.write("simulator lang=spice\n")
else:
self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep, end_time))
elif OPTS.spice_name in ["hspice", "xa"]:
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))
if OPTS.spice_name == "hspice": # for cadence plots
self.sf.write(".OPTIONS PSF=1 \n")
self.sf.write(".OPTIONS HIER_DELIM=1 \n")
self.sf.write(".OPTIONS PSF=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
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.spice_name in ["hspice", "xa"]:
self.sf.write(".probe V(*)\n")
else:
elif OPTS.spice_name != "Xyce":
self.sf.write(".plot V(*)\n")
else:
self.sf.write("*.probe V(*)\n")
@ -292,19 +304,27 @@ class stimuli():
self.sf.write("* {} process corner\n".format(self.process))
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]):
self.sf.write(".lib \"{0}\" {1}\n".format(item[0], item[1]))
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]
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))
def add_comment(self, msg):
self.sf.write(msg + "\n")
def write_supply(self):
""" Writes supply voltage statements """
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.
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):
""" Run hspice in batch mode and output rawfile to parse. """
@ -349,6 +372,20 @@ class stimuli():
temp_stim,
OPTS.openram_temp)
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:
# 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
@ -368,7 +405,7 @@ class stimuli():
spice_stdout = open("{0}spice_stdout.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)
spice_stdout.close()
@ -380,5 +417,3 @@ class stimuli():
end_time = datetime.datetime.now()
delta_time = round((end_time - start_time).total_seconds(), 1)
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)
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)
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)
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]
temperatures = [25]
route_supplies = True
route_supplies = "side"
check_lvsdrc = True
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
offset_int = int(recordLength[0]) # extract length
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
record = self.fileHandle.read(recordLength[0]-2) #read the rest of it (first 2 bytes were already read)
return record
@ -669,11 +669,11 @@ class Gds2reader:
else:
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.readGds2()
self.fileHandle.close()
self.layoutObject.initialize()
self.layoutObject.initialize(special_purposes)
##############################################

View File

@ -81,6 +81,42 @@ class VlsiLayout:
coordinatesRotate.extend((newX,newY))
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):
# take the root structure and copy it to a new structure with the new name
self.structures[newName] = self.structures[self.rootStructureName]
@ -211,13 +247,17 @@ class VlsiLayout:
del transformPath[-1]
return
def initialize(self):
def initialize(self, special_purposes={}):
self.deduceHierarchy()
# self.traverseTheHierarchy()
self.populateCoordinateMap()
# only ones with text
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 addToXyTree(startingStructureName = None,transformPath = None):
@ -422,7 +462,8 @@ class VlsiLayout:
self.structures[self.rootStructureName].texts.append(textToAdd)
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'
else:
return text
@ -696,7 +737,6 @@ class VlsiLayout:
return max_pins
def getAllPinShapes(self, pin_name):
"""
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
they enclose on the given layer.
"""
# Get the labels on a layer in the root level
labels = self.getTexts(lpp)
@ -727,16 +768,28 @@ class VlsiLayout:
label_coordinate = label.coordinates[0]
user_coordinate = [x*self.units[0] for x in label_coordinate]
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:
if self.labelInRectangle(user_coordinate, 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:
self.pins[label_text]
except KeyError:
@ -903,6 +956,16 @@ def sameLPP(lpp1, lpp2):
if lpp1[1] == None or lpp2[1] == None:
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]

View File

@ -66,7 +66,7 @@ def parse_args():
optparse.make_option("-m", "--sim_threads",
action="store",
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"),
optparse.make_option("-v",
"--verbose",
@ -238,8 +238,8 @@ def setup_bitcell():
OPTS.dummy_bitcell = "dummy_pbitcell"
OPTS.replica_bitcell = "replica_pbitcell"
else:
num_ports = OPTS.num_rw_ports + OPTS.num_w_ports + OPTS.num_r_ports
OPTS.bitcell = "bitcell_{}port".format(num_ports)
OPTS.num_ports = OPTS.num_rw_ports + OPTS.num_w_ports + OPTS.num_r_ports
OPTS.bitcell = "bitcell_{}port".format(OPTS.num_ports)
OPTS.dummy_bitcell = "dummy_" + 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")
try:
config = importlib.import_module(module_name)
except:
except ImportError:
debug.error("Unable to read configuration file: {0}".format(config_file), 2)
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
# 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):
debug.error("Write size needs to be an integer multiple of 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
# the whole word.
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))
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.".format(int(OPTS.word_size / 2)))
if not OPTS.tech_name:
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_ur = self.bitcell_array_inst.ur()
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()
def add_pins(self):
@ -227,7 +232,6 @@ class bank(design.design):
x_offset = self.m2_gap + self.port_address[port].width
self.port_address_offsets[port] = vector(-x_offset,
self.main_bitcell_array_bottom)
self.predecoder_height = self.port_address[port].predecoder_height + self.port_address_offsets[port].y
# LOWER LEFT QUADRANT
# Place the col decoder left aligned with wordline driver
@ -362,18 +366,13 @@ class bank(design.design):
# A space for wells or jogging m2
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):
""" 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
@ -394,6 +393,14 @@ class bank(design.design):
rows=self.num_rows)
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.bit_offsets = self.get_column_offsets()
for port in self.all_ports:
@ -426,7 +433,9 @@ class bank(design.design):
temp.append("vdd")
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)
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, "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.
# Other decoders already have them.
if self.col_addr_size == 1:
@ -1058,7 +1071,6 @@ class bank(design.design):
to_layer="m2",
offset=control_pos)
def graph_exclude_precharge(self):
"""
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.
"""
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,
col)
@ -1087,3 +1099,14 @@ class bank(design.design):
"""
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):
"""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:
temp.extend(self.get_rbl_wordline_names(1))
return temp
def add_layout_pins(self):
""" Add the layout pins """
def add_bitline_pins(self):
bitline_names = self.cell.get_all_bitline_names()
for col in range(self.column_size):
for port in self.all_ports:
@ -146,7 +145,7 @@ class bitcell_base_array(design.design):
offset=br_pin.ll().scale(1, 0),
width=br_pin.width(),
height=self.height)
def add_wl_pins(self):
wl_names = self.cell.get_all_wl_names()
for row in range(self.row_size):
for port in self.all_ports:
@ -157,13 +156,19 @@ class bitcell_base_array(design.design):
width=self.width,
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 col in range(self.column_size):
inst = self.cell_inst[row, col]
for pin_name in ["vdd", "gnd"]:
self.copy_layout_pin(inst, pin_name)
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):
tempx = xoffset
dir_y = False

View File

@ -230,3 +230,12 @@ class column_mux_array(design.design):
to_layer=self.sel_layer,
offset=br_out_offset_begin,
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
self.place_wlen_row(row)
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()
control_center_y = self.delay_inst.by()
# 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)
@ -387,19 +390,22 @@ class control_logic(design.design):
def place_delay(self, row):
""" 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 to the right of the control rows and routing channel
offset = vector(self.delay_chain.width, y_off)
self.delay_inst.place(offset, mirror="MY")
offset = vector(0, y_off)
self.delay_inst.place(offset, mirror="MX")
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
# Use pen since it is in every type of control logic
vdd_ypos = self.p_en_bar_nand_inst.get_pin("vdd").by()
# Use gated clock since it is in every type of control logic
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)
mid1 = vector(out_pos.x, in_pos.y)
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
# by routing in the supply rail track to avoid channel conflicts
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)
self.add_wire(self.m1_stack, [in_pos, mid_pos, rail_pos])
self.add_via_center(layers=self.m1_stack,
@ -794,3 +800,8 @@ class control_logic(design.design):
to_layer="m2",
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.
self.fanout_list = fanout_list
self.rows = len(self.fanout_list)
self.create_netlist()
if not OPTS.netlist_only:
@ -43,10 +44,10 @@ class delay_chain(design.design):
def create_layout(self):
# 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
self.width = (max(self.fanout_list) + 1) * self.inv.width
self.place_inverters()
self.route_inverters()
self.route_supplies()
@ -62,14 +63,19 @@ class delay_chain(design.design):
self.add_pin("gnd", "GROUND")
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)
def create_inverters(self):
""" Create the inverters and connect them based on the stage list """
self.driver_inst_list = []
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
cur_driver=self.add_inst(name="dinv{}".format(stage_num),
mod=self.inv)
@ -77,7 +83,7 @@ class delay_chain(design.design):
self.driver_inst_list.append(cur_driver)
# Hook up the driver
if stage_num + 1 == len(self.fanout_list):
if stage_num + 1 == self.rows:
stageout_name = "out"
else:
stageout_name = "dout_{}".format(stage_num + 1)
@ -101,7 +107,7 @@ class delay_chain(design.design):
def place_inverters(self):
""" 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:
inv_mirror = "MX"
inv_offset = vector(0, (stage_num + 1) * self.inv.height)
@ -185,24 +191,26 @@ class delay_chain(design.design):
def add_layout_pins(self):
# 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")
mid_loc = vector(a_pin.cx() - self.m3_pitch, a_pin.cy())
self.add_via_stack_center(from_layer=a_pin.layer,
to_layer="m2",
offset=a_pin.center())
self.add_layout_pin(text="in",
layer="m2",
offset=a_pin.ll().scale(1, 0),
height=a_pin.cy())
to_layer="m2",
offset=mid_loc)
self.add_path(a_pin.layer, [a_pin.center(), mid_loc])
self.add_layout_pin_rect_center(text="in",
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]
a_pin = self.load_inst_map[last_driver_inst][-1].get_pin("A")
self.add_via_stack_center(from_layer=a_pin.layer,
to_layer="m2",
to_layer="m1",
offset=a_pin.center())
mid_point = vector(a_pin.cx() + 3 * self.m2_width, a_pin.cy())
self.add_path("m2", [a_pin.center(), mid_point, mid_point.scale(1, 0)])
self.add_layout_pin_segment_center(text="out",
layer="m2",
start=mid_point,
end=mid_point.scale(1, 0))
self.add_layout_pin_rect_center(text="out",
layer="m1",
offset=a_pin.center())

View File

@ -109,7 +109,7 @@ class dff_buf(design.design):
except AttributeError:
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))

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
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:
mod.graph_exclude_bits(targ_row, local_col)
else:
# Otherwise, we exclude ALL of the rows/columns
mod.graph_exclude_bits()
# Otherwise, exclude the local array inst
self.graph_inst_exclude.add(inst)
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
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):
"""
@ -338,6 +338,7 @@ class global_bitcell_array(bitcell_base_array.bitcell_base_array):
"""
for mod in self.local_mods:
mod.clear_exclude_bits()
self.init_graph_params()
def graph_exclude_dffs(self):
"""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 vector import vector
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 drc
class hierarchical_decoder(design.design):
"""
@ -29,7 +31,7 @@ class hierarchical_decoder(design.design):
b = factory.create(module_type=OPTS.bitcell)
self.cell_height = b.height
self.predecode_bus_rail_pos = []
self.num_outputs = num_outputs
self.num_inputs = math.ceil(math.log(self.num_outputs, 2))
(self.no_of_pre2x4, self.no_of_pre3x8, self.no_of_pre4x16)=self.determine_predecodes(self.num_inputs)
@ -504,9 +506,9 @@ class hierarchical_decoder(design.design):
offset=vector(self.bus_pitch, 0),
names=input_bus_names,
length=self.height)
self.route_predecodes_to_bus()
self.route_bus_to_decoder()
self.route_predecodes_to_bus()
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)
x_offset = self.pre2x4_inst[pre_num].rx() + self.output_layer_pitch
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
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)
x_offset = self.pre3x8_inst[pre_num].rx() + self.output_layer_pitch
y_offset = self.pre3x8_inst[pre_num].by() + i * self.cell_height
self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset)
self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset, "pre3x8")
# FIXME: convert to connect_bus
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)
x_offset = self.pre4x16_inst[pre_num].rx() + self.output_layer_pitch
y_offset = self.pre4x16_inst[pre_num].by() + i * self.cell_height
self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset)
self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset, "pre4x16")
def route_bus_to_decoder(self):
"""
@ -649,8 +651,9 @@ class hierarchical_decoder(design.design):
to_layer=self.input_layer,
offset=pin_pos,
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
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_point2 = vector(x_offset, y_offset)
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])
if layer_props.hierarchical_decoder.vertical_supply:
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.output_layer, [pin_pos, mid_point1, mid_point2, rail_pos])
#if layer_props.hierarchical_decoder.vertical_supply:
# 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)
#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,
to_layer=self.output_layer,
offset=pin_pos)

View File

@ -12,6 +12,10 @@ from vector import vector
from sram_factory import factory
from globals import OPTS
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):
@ -29,7 +33,7 @@ class hierarchical_predecode(design.design):
self.cell_height = height
self.column_decoder = column_decoder
self.input_and_rail_pos = []
self.number_of_outputs = int(math.pow(2, self.number_of_inputs))
super().__init__(name)
@ -118,7 +122,7 @@ class hierarchical_predecode(design.design):
self.input_rails = self.create_vertical_bus(layer=self.bus_layer,
offset=offset,
names=input_names,
length=self.height - 2 * self.bus_pitch,
length=self.height - self.bus_pitch,
pitch=self.bus_pitch)
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,
offset=offset,
names=decode_names,
length=self.height - 2 * self.bus_pitch,
length=self.height - self.bus_pitch,
pitch=self.bus_pitch)
def create_input_inverters(self):
@ -183,15 +187,15 @@ class hierarchical_predecode(design.design):
def route(self):
self.route_input_inverters()
self.route_output_inverters()
self.route_inputs_to_rails()
self.route_input_ands()
self.route_output_inverters()
self.route_inputs_to_rails()
self.route_output_ands()
self.route_vdd_gnd()
def route_inputs_to_rails(self):
""" Route the uninverted inputs to the second set of rails """
top_and_gate = self.and_inst[-1]
for num in range(self.number_of_inputs):
if num == 0:
@ -211,13 +215,25 @@ class hierarchical_predecode(design.design):
in_pos = vector(self.input_rails[in_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_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):
"""
Route all conections of the outputs and gates
@ -274,8 +290,45 @@ class hierarchical_predecode(design.design):
# pins in the and gates.
inv_out_pos = inv_out_pin.rc()
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)
# 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_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.
"""
# This 2D array defines the connection mapping
and_input_line_combination = self.get_and_input_line_combination()
for k in range(self.number_of_outputs):
@ -316,6 +369,7 @@ class hierarchical_predecode(design.design):
to_layer=self.bus_layer,
offset=rail_pos,
directions=self.bus_directions)
self.input_and_rail_pos.append(rail_pos)
if gate_pin == "A":
direction = None
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
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
self.add_via_stack_center(from_layer=in_pin.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):
"""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):
"""

View File

@ -114,4 +114,4 @@ class bitcell_array(bitcell_base_array):
def get_cell_name(self, inst_name, row, col):
"""Gets the spice name of the target bitcell."""
return inst_name + '.x' + self.cell_inst[row, col].name, self.cell_inst[row, col]
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 vector import vector
from globals import OPTS
from tech import cell_properties
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.
"""
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)
self.port = port
@ -28,18 +29,23 @@ class port_data(design.design):
self.num_wmasks = int(math.ceil(self.word_size / self.write_size))
else:
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:
self.num_spare_cols = 0
if not bit_offsets:
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 = []
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:
self.bit_offsets = bit_offsets
if name == "":
name = "port_data_{0}".format(self.port)
super().__init__(name)
@ -117,7 +123,6 @@ class port_data(design.design):
for bit in range(self.num_spare_cols):
self.add_pin("sparebl_{0}".format(bit), "INOUT")
self.add_pin("sparebr_{0}".format(bit), "INOUT")
if self.port in self.read_ports:
for bit in range(self.word_size + self.num_spare_cols):
self.add_pin("dout_{}".format(bit), "OUTPUT")
@ -191,14 +196,19 @@ class port_data(design.design):
# and mirroring happens correctly
# 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:
# 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:
# 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",
columns=self.num_cols + self.num_spare_cols + 1,
offsets=precharge_bit_offsets,
@ -567,19 +577,32 @@ class port_data(design.design):
off = 1
else:
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,
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,
inst1_bls_template="{inst}_{bit}",
inst2=inst2,
num_bits=self.num_spare_cols,
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,
inst1_bls_template="{inst}_{bit}",
inst2=inst2,
num_bits=self.num_spare_cols,
inst1_start_bit=self.num_cols + off,
inst2_start_bit=self.word_size)
self.connect_bitlines(inst1=self.precharge_array_inst,
inst1_bls_template="{inst}_{bit}",
inst2=inst2,
num_bits=self.num_spare_cols,
inst1_start_bit=self.num_cols + off,
inst2_start_bit=self.word_size)
elif layer_props.port_data.channel_route_bitlines:
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"""
if 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,
bitcell_bl=self.bitcell_bl,
bitcell_br=self.bitcell_br)
self.add_mod(self.pc_cell)
self.cell = factory.create(module_type=OPTS.bitcell)
def add_layout_pins(self):

View File

@ -6,7 +6,7 @@
import debug
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 globals import OPTS
from sram_factory import factory
@ -553,7 +553,7 @@ class replica_bitcell_array(bitcell_base_array):
"""
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):
"""

View File

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

View File

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

View File

@ -117,9 +117,10 @@ class write_mask_and_array(design.design):
for i in range(self.num_wmasks):
# 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
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()
in_pos = vector(self.and2_insts[i].lx(),
in_pos = vector(inst.lx(),
a_pos.y)
self.add_via_stack_center(from_layer=a_pin.layer,
to_layer="m2",
@ -130,21 +131,31 @@ class write_mask_and_array(design.design):
self.add_path(a_pin.layer, [in_pos, a_pos])
# 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
en_pin = self.and2_insts[i].get_pin("B")
en_pin = inst.get_pin("B")
en_pos = en_pin.center()
self.add_via_stack_center(from_layer=en_pin.layer,
to_layer="m3",
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"]:
supply_pin = self.and2_insts[0].get_pin(supply)
supply_pin_yoffset = supply_pin.cy()
left_loc = vector(0, supply_pin_yoffset)
right_loc = vector(self.width, supply_pin_yoffset)
self.add_path(supply_pin.layer, [left_loc, right_loc])
self.copy_power_pin(supply_pin, loc=left_loc)
self.copy_power_pin(supply_pin, loc=right_loc)
self.add_path("m1", [left_loc, right_loc])
for loc in [left_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