mirror of https://github.com/VLSIDA/OpenRAM.git
Merge branch 'dev' into stable
This commit is contained in:
commit
0589a35f73
|
|
@ -1 +1,2 @@
|
|||
*.sp linguist-vendored
|
||||
*.sp linguist-language=Spice
|
||||
*.tf linquist-language=Tech 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
|
||||
|
|
|
|||
|
|
@ -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 {} \;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"])
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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.")
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -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_)
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
"""
|
||||
Pseudo-dual port (independent read and write ports), 8bit word, 1 kbyte SRAM.
|
||||
|
||||
Useful as a byte FIFO between two devices (the reader and the writer).
|
||||
"""
|
||||
word_size = 8 # Bits
|
||||
num_words = 1024
|
||||
human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8)
|
||||
|
||||
# Allow byte writes
|
||||
#write_size = 8 # Bits
|
||||
|
||||
# Dual port
|
||||
num_rw_ports = 0
|
||||
num_r_ports = 1
|
||||
num_w_ports = 1
|
||||
ports_human = '1r1w'
|
||||
|
||||
import os
|
||||
exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read())
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
"""
|
||||
Dual port (1 read/write + 1 read only) 1 kbytes SRAM with byte write.
|
||||
|
||||
FIXME: What is this useful for?
|
||||
FIXME: Why would you want byte write on this?
|
||||
"""
|
||||
word_size = 32 # Bits
|
||||
num_words = 256
|
||||
human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8)
|
||||
|
||||
# Allow byte writes
|
||||
write_size = 8 # Bits
|
||||
|
||||
# Dual port
|
||||
num_rw_ports = 1
|
||||
num_r_ports = 1
|
||||
num_w_ports = 0
|
||||
ports_human = '1rw1r'
|
||||
|
||||
import os
|
||||
exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read())
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
"""
|
||||
Dual port (1 read/write + 1 read only) 1 kbytes SRAM with byte write.
|
||||
|
||||
FIXME: What is this useful for?
|
||||
FIXME: Why would you want byte write on this?
|
||||
"""
|
||||
word_size = 8 # Bits
|
||||
num_words = 1024
|
||||
human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8)
|
||||
|
||||
# Allow byte writes
|
||||
write_size = 8 # Bits
|
||||
|
||||
# Dual port
|
||||
num_rw_ports = 1
|
||||
num_r_ports = 1
|
||||
num_w_ports = 0
|
||||
ports_human = '1rw1r'
|
||||
|
||||
import os
|
||||
exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read())
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
"""
|
||||
Single port, 1 kbytes SRAM, with byte write, useful for RISC-V processor main
|
||||
memory.
|
||||
"""
|
||||
word_size = 32 # Bits
|
||||
num_words = 256
|
||||
human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8)
|
||||
|
||||
# Allow byte writes
|
||||
write_size = 8 # Bits
|
||||
|
||||
# Single port
|
||||
num_rw_ports = 1
|
||||
num_r_ports = 0
|
||||
num_w_ports = 0
|
||||
ports_human = '1rw'
|
||||
|
||||
import os
|
||||
exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read())
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
"""
|
||||
Dual port (1 read/write + 1 read only), 2 kbytes SRAM (with byte write).
|
||||
|
||||
FIXME: What is this useful for?
|
||||
FIXME: Why would you want byte write on this?
|
||||
"""
|
||||
word_size = 32 # Bits
|
||||
num_words = 512
|
||||
human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8)
|
||||
|
||||
# Allow byte writes
|
||||
write_size = 8 # Bits
|
||||
|
||||
# Dual port
|
||||
num_rw_ports = 1
|
||||
num_r_ports = 1
|
||||
num_w_ports = 0
|
||||
ports_human = '1rw1r'
|
||||
|
||||
import os
|
||||
exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read())
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
"""
|
||||
Single port, 2 kbytes SRAM, with byte write, useful for RISC-V processor main
|
||||
memory.
|
||||
"""
|
||||
word_size = 32 # Bits
|
||||
num_words = 512
|
||||
human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8)
|
||||
|
||||
# Allow byte writes
|
||||
write_size = 8 # Bits
|
||||
|
||||
# Single port
|
||||
num_rw_ports = 1
|
||||
num_r_ports = 0
|
||||
num_w_ports = 0
|
||||
ports_human = '1rw'
|
||||
|
||||
import os
|
||||
exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read())
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
"""
|
||||
Dual port (1 read/write + 1 read only), 4 kbytes SRAM (with byte write).
|
||||
|
||||
FIXME: What is this useful for?
|
||||
FIXME: Why would you want byte write on this?
|
||||
"""
|
||||
|
||||
word_size = 32 # Bits
|
||||
num_words = 1024
|
||||
human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8)
|
||||
|
||||
# Allow byte writes
|
||||
write_size = 8 # Bits
|
||||
|
||||
# Dual port
|
||||
num_rw_ports = 1
|
||||
num_r_ports = 1
|
||||
num_w_ports = 0
|
||||
ports_human = '1rw1r'
|
||||
|
||||
import os
|
||||
exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read())
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
"""
|
||||
Single port, 4 kbytes SRAM, with byte write, useful for RISC-V processor main
|
||||
memory.
|
||||
"""
|
||||
|
||||
word_size = 32 # Bits
|
||||
num_words = 1024
|
||||
human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8)
|
||||
|
||||
# Allow byte writes
|
||||
write_size = 8 # Bits
|
||||
|
||||
# Single port
|
||||
num_rw_ports = 1
|
||||
num_r_ports = 0
|
||||
num_w_ports = 0
|
||||
ports_human = '1rw'
|
||||
|
||||
import os
|
||||
exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read())
|
||||
|
|
@ -0,0 +1,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())
|
||||
|
|
@ -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)
|
||||
|
||||
##############################################
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 \(µm<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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue