Merged with dev

This commit is contained in:
Hunter Nichols 2020-11-10 15:47:56 -08:00
commit 84ba5c55d1
375 changed files with 20170 additions and 15576 deletions

View File

@ -17,7 +17,7 @@ class channel_net():
self.name = net_name
self.pins = pins
self.vertical = vertical
# Keep track of the internval
if vertical:
self.min_value = min(i.by() for i in pins)
@ -25,34 +25,34 @@ class channel_net():
else:
self.min_value = min(i.lx() for i in pins)
self.max_value = max(i.rx() for i in pins)
# Keep track of the conflicts
self.conflicts = []
def __str__(self):
return self.name
def __repr__(self):
return self.name
def __lt__(self, other):
return self.min_value < other.min_value
def pin_overlap(self, pin1, pin2, pitch):
""" Check for vertical or horizontal overlap of the two pins """
# FIXME: If the pins are not in a row, this may break.
# However, a top pin shouldn't overlap another top pin,
# for example, so the extra comparison *shouldn't* matter.
# Pin 1 must be in the "BOTTOM" set
x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x - pin2.center().x) < pitch
# Pin 1 must be in the "LEFT" set
y_overlap = pin1.lx() < pin2.lx() and abs(pin1.center().y - pin2.center().y) < pitch
overlaps = (not self.vertical and x_overlap) or (self.vertical and y_overlap)
return overlaps
def pins_overlap(self, other, pitch):
"""
Check all the pin pairs on two nets and return a pin
@ -73,8 +73,8 @@ class channel_net():
min_overlap = self.min_value >= other.min_value and self.min_value <= other.max_value
max_overlap = self.max_value >= other.min_value and self.max_value <= other.max_value
return min_overlap or max_overlap
class channel_route(design.design):
unique_id = 0
@ -98,7 +98,7 @@ class channel_route(design.design):
name = "cr_{0}".format(channel_route.unique_id)
channel_route.unique_id += 1
super().__init__(name)
self.netlist = netlist
self.offset = offset
self.layer_stack = layer_stack
@ -106,7 +106,7 @@ class channel_route(design.design):
self.vertical = vertical
# For debugging...
self.parent = parent
if not directions or directions == "pref":
# Use the preferred layer directions
if self.get_preferred_direction(layer_stack[0]) == "V":
@ -154,7 +154,7 @@ class channel_route(design.design):
if pin in conflicts:
g[other_pin].remove(pin)
return g
def route(self):
# Create names for the nets for the graphs
nets = []
@ -180,7 +180,7 @@ class channel_route(design.design):
except KeyError:
hcg[net2.name] = set([net1.name])
# Initialize the vertical conflict graph (vcg)
# and make a list of all pins
vcg = collections.OrderedDict()
@ -204,12 +204,12 @@ class channel_route(design.design):
# Skip yourself
if net1.name == net2.name:
continue
if net1.pins_overlap(net2, pitch):
vcg[net2.name].add(net1.name)
# Check if there are any cycles net1 <---> net2 in the VCG
# Some of the pins may be to the left/below the channel offset,
# so adjust if this is the case
@ -226,7 +226,7 @@ class channel_route(design.design):
while len(nets) > 0:
current_offset_value = current_offset.y if self.vertical else current_offset.x
# from pprint import pformat
# print("VCG:\n", pformat(vcg))
# for name,net in vcg.items():
@ -253,7 +253,7 @@ class channel_route(design.design):
# Remove the net from other constriants in the VCG
vcg = self.remove_net_from_graph(net.name, vcg)
nets.remove(net)
break
else:
# If we made a full pass and the offset didn't change...
@ -276,7 +276,7 @@ class channel_route(design.design):
current_offset = vector(current_offset.x + self.horizontal_nonpref_pitch, real_channel_offset.y)
else:
current_offset = vector(real_channel_offset.x, current_offset.y + self.vertical_nonpref_pitch)
# Return the size of the channel
if self.vertical:
self.width = current_offset.x + self.horizontal_nonpref_pitch - self.offset.x
@ -284,7 +284,7 @@ class channel_route(design.design):
else:
self.width = self.max_value + self.horizontal_nonpref_pitch - self.offset.x
self.height = current_offset.y + self.vertical_nonpref_pitch - self.offset.y
def get_layer_pitch(self, layer):
""" Return the track pitch on a given layer """
try:
@ -307,10 +307,10 @@ class channel_route(design.design):
"""
max_x = max([pin.center().x for pin in pins])
min_x = min([pin.center().x for pin in pins])
# if we are less than a pitch, just create a non-preferred layer jog
non_preferred_route = max_x - min_x <= pitch
if non_preferred_route:
half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)]
# Add the horizontal trunk on the vertical layer!
@ -324,7 +324,7 @@ class channel_route(design.design):
pin_pos = pin.uc()
else:
pin_pos = pin.bc()
# No bend needed here
mid = vector(pin_pos.x, trunk_offset.y)
self.add_path(self.vertical_layer, [pin_pos, mid])
@ -361,10 +361,10 @@ class channel_route(design.design):
"""
max_y = max([pin.center().y for pin in pins])
min_y = min([pin.center().y for pin in pins])
# if we are less than a pitch, just create a non-preferred layer jog
non_preferred_route = max_y - min_y <= pitch
if non_preferred_route:
half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)]
# Add the vertical trunk on the horizontal layer!

View File

@ -33,8 +33,8 @@ class contact(hierarchy_design.hierarchy_design):
implant_type=None, well_type=None, name=""):
# This will ignore the name parameter since
# we can guarantee a unique name here
super().__init__(name)
super().__init__(name, name)
debug.info(4, "create contact object {0}".format(name))
self.add_comment("layers: {0}".format(layer_stack))
@ -51,8 +51,8 @@ class contact(hierarchy_design.hierarchy_design):
# Non-preferred directions
if directions == "nonpref":
first_dir = "H" if self.get_preferred_direction(layer_stack[0])=="V" else "V"
second_dir = "H" if self.get_preferred_direction(layer_stack[2])=="V" else "V"
first_dir = "H" if tech.preferred_directions[layer_stack[0]]=="V" else "V"
second_dir = "H" if tech.preferred_directions[layer_stack[2]]=="V" else "V"
self.directions = (first_dir, second_dir)
# Preferred directions
elif directions == "pref":
@ -80,7 +80,7 @@ class contact(hierarchy_design.hierarchy_design):
self.create_first_layer_enclosure()
self.create_second_layer_enclosure()
self.create_nitride_cut_enclosure()
self.height = max(self.first_layer_position.y + self.first_layer_height,
self.second_layer_position.y + self.second_layer_height)
self.width = max(self.first_layer_position.x + self.first_layer_width,
@ -99,7 +99,7 @@ class contact(hierarchy_design.hierarchy_design):
(first_layer, via_layer, second_layer) = self.layer_stack
self.first_layer_name = first_layer
self.second_layer_name = second_layer
# Contacts will have unique per first layer
if via_layer in tech.layer:
self.via_layer_name = via_layer
@ -115,7 +115,7 @@ class contact(hierarchy_design.hierarchy_design):
def setup_layout_constants(self):
""" Determine the design rules for the enclosure layers """
self.contact_width = drc("minwidth_{0}". format(self.via_layer_name))
contact_to_contact = drc("{0}_to_{0}".format(self.via_layer_name))
self.contact_pitch = self.contact_width + contact_to_contact
@ -126,7 +126,7 @@ class contact(hierarchy_design.hierarchy_design):
# DRC rules
# The extend rule applies to asymmetric enclosures in one direction.
# The enclosure rule applies to symmetric enclosure component.
self.first_layer_minwidth = drc("minwidth_{0}".format(self.first_layer_name))
self.first_layer_enclosure = drc("{0}_enclose_{1}".format(self.first_layer_name, self.via_layer_name))
# If there's a different rule for active
@ -171,7 +171,7 @@ class contact(hierarchy_design.hierarchy_design):
(self.second_layer_minwidth - self.contact_array_width) / 2)
else:
debug.error("Invalid secon layer direction: ".format(self.directions[1]), -1)
def create_contact_array(self):
""" Create the contact array at the origin"""
# offset for the via array
@ -210,7 +210,7 @@ class contact(hierarchy_design.hierarchy_design):
offset=self.second_layer_position - npc_enclose_offset,
width=self.second_layer_width + 2 * npc_enclose_poly,
height=self.second_layer_height + 2 * npc_enclose_poly)
def create_first_layer_enclosure(self):
# this is if the first and second layers are different
self.first_layer_position = vector(
@ -269,12 +269,12 @@ class contact(hierarchy_design.hierarchy_design):
offset=well_position,
width=self.well_width,
height=self.well_height)
def analytical_power(self, corner, load):
""" Get total power of a module """
return self.return_power()
# Set up a static for each layer to be used for measurements
for layer_stack in tech.layer_stacks:
(layer1, via, layer2) = layer_stack
@ -295,7 +295,7 @@ if "nwell" in tech.layer:
well_type="n")
module = sys.modules[__name__]
setattr(module, "nwell_contact", cont)
if "pwell" in tech.layer:
cont = factory.create(module_type="contact",
layer_stack=tech.active_stack,

View File

@ -7,13 +7,15 @@
#
from globals import OPTS
class _pins:
def __init__(self, pin_dict):
# make the pins elements of the class to allow "." access.
# For example: props.bitcell.cell_6t.pin.bl = "foobar"
for k,v in pin_dict.items():
for k, v in pin_dict.items():
self.__dict__[k] = v
class _cell:
def __init__(self, pin_dict):
pin_dict.update(self._default_power_pins())
@ -24,13 +26,27 @@ class _cell:
return self._pins
def _default_power_pins(self):
return { 'vdd' : 'vdd', 'gnd' : 'gnd' }
return {'vdd': 'vdd',
'gnd': 'gnd'}
class _mirror_axis:
def __init__(self, x, y):
self.x = x
self.y = y
class _ptx:
def __init__(self, model_is_subckt, bin_spice_models):
self.model_is_subckt = model_is_subckt
self.bin_spice_models = bin_spice_models
class _pgate:
def __init__(self, add_implants):
self.add_implants = add_implants
class _bitcell:
def __init__(self, mirror, cell_s8_6t, cell_6t, cell_1rw1r, cell_1w1r):
self.mirror = mirror
@ -42,27 +58,27 @@ class _bitcell:
def _default():
axis = _mirror_axis(True, False)
cell_s8_6t = _cell({'bl' : 'bl',
'br' : 'br',
cell_s8_6t = _cell({'bl': 'bl',
'br': 'br',
'wl': 'wl'})
cell_6t = _cell({'bl' : 'bl',
'br' : 'br',
'wl' : 'wl'})
cell_6t = _cell({'bl': 'bl',
'br': 'br',
'wl': 'wl'})
cell_1rw1r = _cell({'bl0' : 'bl0',
'br0' : 'br0',
'bl1' : 'bl1',
'br1' : 'br1',
'wl0' : 'wl0',
'wl1' : 'wl1'})
cell_1rw1r = _cell({'bl0': 'bl0',
'br0': 'br0',
'bl1': 'bl1',
'br1': 'br1',
'wl0': 'wl0',
'wl1': 'wl1'})
cell_1w1r = _cell({'bl0' : 'bl0',
'br0' : 'br0',
'bl1' : 'bl1',
'br1' : 'br1',
'wl0' : 'wl0',
'wl1' : 'wl1'})
cell_1w1r = _cell({'bl0': 'bl0',
'br0': 'br0',
'bl1': 'bl1',
'br1': 'br1',
'wl0': 'wl0',
'wl1': 'wl1'})
return _bitcell(cell_s8_6t=cell_s8_6t,
cell_6t=cell_6t,
@ -94,21 +110,25 @@ class _dff:
self.custom_type_list = custom_type_list
self.clk_pin = clk_pin
class _dff_buff:
def __init__(self, use_custom_ports, custom_buff_ports, add_body_contacts):
self.use_custom_ports = use_custom_ports
self.buf_ports = custom_buff_ports
self.add_body_contacts = add_body_contacts
class _dff_buff_array:
def __init__(self, use_custom_ports, add_body_contacts):
self.use_custom_ports = use_custom_ports
self.add_body_contacts = add_body_contacts
class _bitcell_array:
def __init__(self, use_custom_cell_arrangement):
self.use_custom_cell_arrangement = use_custom_cell_arrangement
class cell_properties():
"""
This contains meta information about the custom designed cells. For
@ -117,41 +137,69 @@ class cell_properties():
"""
def __init__(self):
self.names = {}
self.names["bitcell"] = "cell_6t"
self.names["bitcell_1rw_1r"] = "cell_1rw_1r"
self.names["bitcell_1w_1r"] = "cell_1w_1r"
self.names["dummy_bitcell"] = "dummy_cell_6t"
self.names["dummy_bitcell_1rw_1r"] = "dummy_cell_1rw_1r"
self.names["dummy_bitcell_1w_1r"] = "dummy_cell_1w_1r"
self.names["replica_bitcell"] = "replica_cell_6t"
self.names["replica_bitcell_1rw_1r"] = "replica_cell_1rw_1r"
self.names["replica_bitcell_1w_1r"] = "replica_cell_1w_1r"
self.names["col_cap_bitcell_6t"] = "col_cap_cell_6t"
self.names["col_cap_bitcell_1rw_1r"] = "col_cap_cell_1rw_1r"
self.names["col_cap_bitcell_1w_1r"] = "col_cap_cell_1w_1r"
self.names["row_cap_bitcell_6t"] = "row_cap_cell_6t"
self.names["row_cap_bitcell_1rw_1r"] = "row_cap_cell_1rw_1r"
self.names["row_cap_bitcell_1w_1r"] = "row_cap_cell_1w_1r"
self._bitcell = _bitcell._default()
self._dff = _dff(use_custom_ports = False,
custom_port_list = ["D", "Q", "clk", "vdd", "gnd"],
custom_type_list = ["INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"],
clk_pin= "clk")
self._dff_buff = _dff_buff(use_custom_ports = False,
custom_buff_ports = ["D", "qint", "clk", "vdd", "gnd"],
add_body_contacts = False)
self._dff_buff_array = _dff_buff_array(use_custom_ports = False,
add_body_contacts = False)
self._ptx = _ptx(model_is_subckt=False,
bin_spice_models=False)
self._pgate = _pgate(add_implants=False)
self._dff = _dff(use_custom_ports=False,
custom_port_list=["D", "Q", "clk", "vdd", "gnd"],
custom_type_list=["INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"],
clk_pin="clk")
self._dff_buff = _dff_buff(use_custom_ports=False,
custom_buff_ports=["D", "qint", "clk", "vdd", "gnd"],
add_body_contacts=False)
self._dff_buff_array = _dff_buff_array(use_custom_ports=False,
add_body_contacts=False)
self._write_driver = _cell({'din': 'din',
'bl' : 'bl',
'br' : 'br',
'en' : 'en'})
'bl': 'bl',
'br': 'br',
'en': 'en'})
self._sense_amp = _cell({'bl' : 'bl',
'br' : 'br',
'dout' : 'dout',
'en' : 'en'})
self._sense_amp = _cell({'bl': 'bl',
'br': 'br',
'dout': 'dout',
'en': 'en'})
self._bitcell_array = _bitcell_array(use_custom_cell_arrangement = [])
self._bitcell_array = _bitcell_array(use_custom_cell_arrangement=[])
@property
def bitcell(self):
return self._bitcell
@property
def ptx(self):
return self._ptx
@property
def pgate(self):
return self._pgate
@property
def dff(self):
return self._dff
@property
def dff_buff(self):
return self._dff_buff
@ -167,7 +215,7 @@ class cell_properties():
@property
def sense_amp(self):
return self._sense_amp
@property
def bitcell_array(self):
return self._bitcell_array

View File

@ -0,0 +1,193 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2020 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
class _bank:
def __init__(self, stack, pitch):
# bank
# column address route: stack, pitch
# m1_stack, m2_pitch (default)
# m2_stack, m3_pitch (sky130)
self.stack = stack
self.pitch = pitch
class _hierarchical_decoder:
def __init__(self,
bus_layer,
bus_directions,
input_layer,
output_layer,
vertical_supply):
# hierarchical_decoder
# bus_layer, bus_directions, bus_pitch, bus_space, input_layer, output_layer, output_layer_pitch
# m2, pref, m2_pitch, m2_space, m1, m3, m3_pitch
# m1, nonpref, m1_pitch, m2_space, m2, li, li_pitch (sky130)
#
# vertical vdd/gnd
# special jogging
self.bus_layer = bus_layer
self.bus_directions = bus_directions
self.input_layer = input_layer
self.output_layer = output_layer
self.vertical_supply = vertical_supply
class _hierarchical_predecode:
def __init__(self,
bus_layer,
bus_directions,
bus_space_factor,
input_layer,
output_layer,
vertical_supply):
# 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
# m1, nonpref, m1_pitch, 1`.5*m1_space, m2, li, li_pitch (sky130)
#
# vertical vdd/gnd
# special jogging
self.bus_layer = bus_layer
self.bus_directions = bus_directions
self.bus_space_factor = bus_space_factor
self.input_layer = input_layer
self.output_layer = output_layer
self.vertical_supply = vertical_supply
class _column_mux_array:
def __init__(self,
select_layer,
select_pitch,
bitline_layer):
# column_mux_array
# sel_layer, sel_pitch, bitline_layer
# m1, m2_pitch, m2
# m3, m3_pitch, m1 (sky130)
self.select_layer = select_layer
self.select_pitch= select_pitch
self.bitline_layer = bitline_layer
class _port_address:
def __init__(self,
supply_offset):
# port_adress
# special supply offset
self.supply_offset = supply_offset
class _port_data:
def __init__(self,
channel_route_bitlines,
enable_layer):
# port_data
# connect bitlines instead of chanel route
# sense_amp_array
# en_layer
# m1
# m3 (sky130)
# precharge_array
# en_bar_layer
# m1
# m3 (sky130)
self.channel_route_bitlines = channel_route_bitlines
self.enable_layer = enable_layer
class _replica_column:
def __init__(self,
even_rows):
# replica_column
# even row check (sky130)
self.even_rows = even_rows
class _wordline_driver:
def __init__(self,
vertical_supply):
# wordline_buffer_array
# vertical vdd/gnd (sky130)
# wordline_driver_array
# vertical vdd/gnd (sky130)
# wordline_driver
# vertical vdd/gnd (sky130)
self.vertical_supply = vertical_supply
class layer_properties():
"""
This contains meta information about the module routing layers. These
can be overriden in the tech.py file.
"""
def __init__(self):
self._bank = _bank(stack="m1_stack",
pitch="m2_pitch")
self._hierarchical_decoder = _hierarchical_decoder(bus_layer="m2",
bus_directions="pref",
input_layer="m1",
output_layer="m3",
vertical_supply=False)
self._hierarchical_predecode = _hierarchical_predecode(bus_layer="m2",
bus_directions="pref",
bus_space_factor=1,
input_layer="m1",
output_layer="m1",
vertical_supply=False)
self._column_mux_array = _column_mux_array(select_layer="m1",
select_pitch="m2_pitch",
bitline_layer="m2")
self._port_address = _port_address(supply_offset=False)
self._port_data = _port_data(channel_route_bitlines=True,
enable_layer="m1")
self._replica_column = _replica_column(even_rows=False)
self._wordline_driver = _wordline_driver(vertical_supply=False)
@property
def bank(self):
return self._bank
@property
def column_mux_array(self):
return self._column_mux_array
@property
def hierarchical_decoder(self):
return self._hierarchical_decoder
@property
def hierarchical_predecode(self):
return self._hierarchical_predecode
@property
def port_address(self):
return self._port_address
@property
def port_data(self):
return self._port_data
@property
def replica_column(self):
return self._replica_column
@property
def wordline_driver(self):
return self._wordline_driver

View File

@ -38,7 +38,7 @@ class delay_data():
assert isinstance(other, delay_data)
return delay_data(other.delay + self.delay,
self.slew)

View File

@ -8,22 +8,29 @@
from hierarchy_design import hierarchy_design
from utils import round_to_grid
import contact
from tech import preferred_directions
from tech import cell_properties as props
from globals import OPTS
import re
import debug
class design(hierarchy_design):
"""
This is the same as the hierarchy_design class except it contains
some DRC/layer constants and analytical models for other modules to reuse.
"""
def __init__(self, name):
super().__init__(name)
self.setup_drc_constants()
self.setup_layer_constants()
def __init__(self, name, cell_name=None):
# This allows us to use different GDS/spice circuits for hard cells instead of the default ones
# Except bitcell names are generated automatically by the globals.py setup_bitcells routines
# depending on the number of ports.
if name in props.names:
cell_name = props.names[name]
elif not cell_name:
cell_name = name
super().__init__(name, cell_name)
self.setup_multiport_constants()
def check_pins(self):
@ -31,106 +38,9 @@ class design(hierarchy_design):
pins = self.get_pins(pin_name)
for pin in pins:
print(pin_name, pin)
def setup_layer_constants(self):
"""
These are some layer constants used
in many places in the compiler.
"""
from tech import layer_indices
import tech
for layer in layer_indices:
key = "{}_stack".format(layer)
# Set the stack as a local helper
try:
layer_stack = getattr(tech, key)
setattr(self, key, layer_stack)
except AttributeError:
pass
# Skip computing the pitch for active
if layer == "active":
continue
# Add the pitch
setattr(self,
"{}_pitch".format(layer),
self.compute_pitch(layer, True))
# Add the non-preferrd pitch (which has vias in the "wrong" way)
setattr(self,
"{}_nonpref_pitch".format(layer),
self.compute_pitch(layer, False))
if False:
from tech import preferred_directions
print(preferred_directions)
from tech import layer, layer_indices
for name in layer_indices:
if name == "active":
continue
try:
print("{0} width {1} space {2}".format(name,
getattr(self, "{}_width".format(name)),
getattr(self, "{}_space".format(name))))
print("pitch {0} nonpref {1}".format(getattr(self, "{}_pitch".format(name)),
getattr(self, "{}_nonpref_pitch".format(name))))
except AttributeError:
pass
import sys
sys.exit(1)
def compute_pitch(self, layer, preferred=True):
"""
This is the preferred direction pitch
i.e. we take the minimum or maximum contact dimension
"""
# Find the layer stacks this is used in
from tech import layer_stacks
pitches = []
for stack in layer_stacks:
# Compute the pitch with both vias above and below (if they exist)
if stack[0] == layer:
pitches.append(self.compute_layer_pitch(stack, preferred))
if stack[2] == layer:
pitches.append(self.compute_layer_pitch(stack[::-1], True))
return max(pitches)
def compute_layer_pitch(self, layer_stack, preferred):
(layer1, via, layer2) = layer_stack
try:
if layer1 == "poly" or layer1 == "active":
contact1 = getattr(contact, layer1 + "_contact")
else:
contact1 = getattr(contact, layer1 + "_via")
except AttributeError:
contact1 = getattr(contact, layer2 + "_via")
if preferred:
if self.get_preferred_direction(layer1) == "V":
contact_width = contact1.first_layer_width
else:
contact_width = contact1.first_layer_height
else:
if self.get_preferred_direction(layer1) == "V":
contact_width = contact1.first_layer_height
else:
contact_width = contact1.first_layer_width
layer_space = getattr(self, layer1 + "_space")
#print(layer_stack)
#print(contact1)
pitch = contact_width + layer_space
return round_to_grid(pitch)
def setup_drc_constants(self):
@classmethod
def setup_drc_constants(design):
"""
These are some DRC constants used in many places
in the compiler.
@ -142,85 +52,191 @@ class design(hierarchy_design):
match = re.search(r"minwidth_(.*)", rule)
if match:
if match.group(1) == "active_contact":
setattr(self, "contact_width", drc(match.group(0)))
setattr(design, "contact_width", drc(match.group(0)))
else:
setattr(self, match.group(1) + "_width", drc(match.group(0)))
setattr(design, match.group(1) + "_width", drc(match.group(0)))
# Single layer area rules
match = re.search(r"minarea_(.*)", rule)
if match:
setattr(self, match.group(0), drc(match.group(0)))
setattr(design, match.group(0), drc(match.group(0)))
# Single layer spacing rules
match = re.search(r"(.*)_to_(.*)", rule)
if match and match.group(1) == match.group(2):
setattr(self, match.group(1) + "_space", drc(match.group(0)))
setattr(design, match.group(1) + "_space", drc(match.group(0)))
elif match and match.group(1) != match.group(2):
if match.group(2) == "poly_active":
setattr(self, match.group(1) + "_to_contact",
setattr(design, match.group(1) + "_to_contact",
drc(match.group(0)))
else:
setattr(self, match.group(0), drc(match.group(0)))
setattr(design, match.group(0), drc(match.group(0)))
match = re.search(r"(.*)_enclose_(.*)", rule)
if match:
setattr(self, match.group(0), drc(match.group(0)))
setattr(design, match.group(0), drc(match.group(0)))
match = re.search(r"(.*)_extend_(.*)", rule)
if match:
setattr(self, match.group(0), drc(match.group(0)))
setattr(design, match.group(0), drc(match.group(0)))
# Create the maximum well extend active that gets used
# by cells to extend the wells for interaction with other cells
from tech import layer
self.well_extend_active = 0
design.well_extend_active = 0
if "nwell" in layer:
self.well_extend_active = max(self.well_extend_active, self.nwell_extend_active)
design.well_extend_active = max(design.well_extend_active, design.nwell_extend_active)
if "pwell" in layer:
self.well_extend_active = max(self.well_extend_active, self.pwell_extend_active)
design.well_extend_active = max(design.well_extend_active, design.pwell_extend_active)
# The active offset is due to the well extension
if "pwell" in layer:
self.pwell_enclose_active = drc("pwell_enclose_active")
design.pwell_enclose_active = drc("pwell_enclose_active")
else:
self.pwell_enclose_active = 0
design.pwell_enclose_active = 0
if "nwell" in layer:
self.nwell_enclose_active = drc("nwell_enclose_active")
design.nwell_enclose_active = drc("nwell_enclose_active")
else:
self.nwell_enclose_active = 0
design.nwell_enclose_active = 0
# Use the max of either so that the poly gates will align properly
self.well_enclose_active = max(self.pwell_enclose_active,
self.nwell_enclose_active,
self.active_space)
design.well_enclose_active = max(design.pwell_enclose_active,
design.nwell_enclose_active,
design.active_space)
# These are for debugging previous manual rules
if False:
print("poly_width", self.poly_width)
print("poly_space", self.poly_space)
print("m1_width", self.m1_width)
print("m1_space", self.m1_space)
print("m2_width", self.m2_width)
print("m2_space", self.m2_space)
print("m3_width", self.m3_width)
print("m3_space", self.m3_space)
print("m4_width", self.m4_width)
print("m4_space", self.m4_space)
print("active_width", self.active_width)
print("active_space", self.active_space)
print("contact_width", self.contact_width)
print("poly_to_active", self.poly_to_active)
print("poly_extend_active", self.poly_extend_active)
print("poly_to_contact", self.poly_to_contact)
print("active_contact_to_gate", self.active_contact_to_gate)
print("poly_contact_to_gate", self.poly_contact_to_gate)
print("well_enclose_active", self.well_enclose_active)
print("implant_enclose_active", self.implant_enclose_active)
print("implant_space", self.implant_space)
print("poly_width", design.poly_width)
print("poly_space", design.poly_space)
print("m1_width", design.m1_width)
print("m1_space", design.m1_space)
print("m2_width", design.m2_width)
print("m2_space", design.m2_space)
print("m3_width", design.m3_width)
print("m3_space", design.m3_space)
print("m4_width", design.m4_width)
print("m4_space", design.m4_space)
print("active_width", design.active_width)
print("active_space", design.active_space)
print("contact_width", design.contact_width)
print("poly_to_active", design.poly_to_active)
print("poly_extend_active", design.poly_extend_active)
print("poly_to_contact", design.poly_to_contact)
print("active_contact_to_gate", design.active_contact_to_gate)
print("poly_contact_to_gate", design.poly_contact_to_gate)
print("well_enclose_active", design.well_enclose_active)
print("implant_enclose_active", design.implant_enclose_active)
print("implant_space", design.implant_space)
import sys
sys.exit(1)
@classmethod
def setup_layer_constants(design):
"""
These are some layer constants used
in many places in the compiler.
"""
from tech import layer_indices
import tech
for layer in layer_indices:
key = "{}_stack".format(layer)
# Set the stack as a local helper
try:
layer_stack = getattr(tech, key)
setattr(design, key, layer_stack)
except AttributeError:
pass
# Skip computing the pitch for active
if layer == "active":
continue
# Add the pitch
setattr(design,
"{}_pitch".format(layer),
design.compute_pitch(layer, True))
# Add the non-preferrd pitch (which has vias in the "wrong" way)
setattr(design,
"{}_nonpref_pitch".format(layer),
design.compute_pitch(layer, False))
if False:
from tech import preferred_directions
print(preferred_directions)
from tech import layer, layer_indices
for name in layer_indices:
if name == "active":
continue
try:
print("{0} width {1} space {2}".format(name,
getattr(design, "{}_width".format(name)),
getattr(design, "{}_space".format(name))))
print("pitch {0} nonpref {1}".format(getattr(design, "{}_pitch".format(name)),
getattr(design, "{}_nonpref_pitch".format(name))))
except AttributeError:
pass
import sys
sys.exit(1)
@staticmethod
def compute_pitch(layer, preferred=True):
"""
This is the preferred direction pitch
i.e. we take the minimum or maximum contact dimension
"""
# Find the layer stacks this is used in
from tech import layer_stacks
pitches = []
for stack in layer_stacks:
# Compute the pitch with both vias above and below (if they exist)
if stack[0] == layer:
pitches.append(design.compute_layer_pitch(stack, preferred))
if stack[2] == layer:
pitches.append(design.compute_layer_pitch(stack[::-1], True))
return max(pitches)
@staticmethod
def get_preferred_direction(layer):
return preferred_directions[layer]
@staticmethod
def compute_layer_pitch(layer_stack, preferred):
(layer1, via, layer2) = layer_stack
try:
if layer1 == "poly" or layer1 == "active":
contact1 = getattr(contact, layer1 + "_contact")
else:
contact1 = getattr(contact, layer1 + "_via")
except AttributeError:
contact1 = getattr(contact, layer2 + "_via")
if preferred:
if preferred_directions[layer1] == "V":
contact_width = contact1.first_layer_width
else:
contact_width = contact1.first_layer_height
else:
if preferred_directions[layer1] == "V":
contact_width = contact1.first_layer_height
else:
contact_width = contact1.first_layer_width
layer_space = getattr(design, layer1 + "_space")
#print(layer_stack)
#print(contact1)
pitch = contact_width + layer_space
return round_to_grid(pitch)
def setup_multiport_constants(self):
"""
"""
These are contants and lists that aid multiport design.
Ports are always in the order RW, W, R.
Port indices start from 0 and increment.
@ -258,11 +274,14 @@ class design(hierarchy_design):
self.read_ports.append(port_number)
self.readonly_ports.append(port_number)
port_number += 1
def analytical_power(self, corner, load):
""" Get total power of a module """
total_module_power = self.return_power()
for inst in self.insts:
total_module_power += inst.mod.analytical_power(corner, load)
return total_module_power
design.setup_drc_constants()
design.setup_layer_constants()

View File

@ -153,7 +153,7 @@ class geometry:
def center(self):
""" Return the center coordinate """
return vector(self.cx(), self.cy())
class instance(geometry):
"""
@ -227,7 +227,7 @@ class instance(geometry):
self.mod.gds_write_file(self.gds)
# now write an instance of my module/structure
new_layout.addInstance(self.gds,
self.mod.name,
self.mod.cell_name,
offsetInMicrons=self.offset,
mirror=self.mirror,
rotate=self.rotate)
@ -271,9 +271,9 @@ class instance(geometry):
p.transform(self.offset, self.mirror, self.rotate)
new_pins.append(p)
return new_pins
def calculate_transform(self, node):
#set up the rotation matrix
#set up the rotation matrix
angle = math.radians(float(node.rotate))
mRotate = np.array([[math.cos(angle),-math.sin(angle),0.0],
[math.sin(angle),math.cos(angle),0.0],
@ -285,7 +285,7 @@ class instance(geometry):
mTranslate = np.array([[1.0,0.0,translateX],
[0.0,1.0,translateY],
[0.0,0.0,1.0]])
#set up the scale matrix (handles mirror X)
scaleX = 1.0
if(node.mirror == 'MX'):
@ -295,7 +295,7 @@ class instance(geometry):
mScale = np.array([[scaleX,0.0,0.0],
[0.0,scaleY,0.0],
[0.0,0.0,1.0]])
return (mRotate, mScale, mTranslate)
def apply_transform(self, mtransforms, uVector, vVector, origin):
@ -312,13 +312,13 @@ class instance(geometry):
def apply_path_transform(self, path):
uVector = np.array([[1.0],[0.0],[0.0]])
vVector = np.array([[0.0],[1.0],[0.0]])
origin = np.array([[0.0],[0.0],[1.0]])
origin = np.array([[0.0],[0.0],[1.0]])
while(path):
instance = path.pop(-1)
mtransforms = self.calculate_transform(instance)
(uVector, vVector, origin) = self.apply_transform(mtransforms, uVector, vVector, origin)
return (uVector, vVector, origin)
def reverse_transformation_bitcell(self, cell_name):
@ -339,7 +339,7 @@ class instance(geometry):
cell_paths.append(copy.copy(path))
inst_name = path[-1].name
# get the row and col names from the path
row = int(path[-1].name.split('_')[-2][1:])
col = int(path[-1].name.split('_')[-1][1:])
@ -349,7 +349,7 @@ class instance(geometry):
normalized_storage_nets = node.mod.get_normalized_storage_nets_offset()
(normalized_bl_offsets, normalized_br_offsets, bl_names, br_names) = node.mod.get_normalized_bitline_offset()
for offset in range(len(normalized_bl_offsets)):
for port in range(len(bl_names)):
cell_bl_meta.append([bl_names[offset], row, col, port])
@ -369,18 +369,18 @@ class instance(geometry):
Q_bar_y = -1 * Q_bar_y
for pair in range(len(normalized_bl_offsets)):
normalized_bl_offsets[pair] = (normalized_bl_offsets[pair][0],
normalized_bl_offsets[pair] = (normalized_bl_offsets[pair][0],
-1 * normalized_bl_offsets[pair][1])
for pair in range(len(normalized_br_offsets)):
normalized_br_offsets[pair] = (normalized_br_offsets[pair][0],
normalized_br_offsets[pair] = (normalized_br_offsets[pair][0],
-1 * normalized_br_offsets[pair][1])
Q_offsets.append([Q_x, Q_y])
Q_offsets.append([Q_x, Q_y])
Q_bar_offsets.append([Q_bar_x, Q_bar_y])
bl_offsets.append(normalized_bl_offsets)
br_offsets.append(normalized_br_offsets)
@ -402,13 +402,13 @@ class instance(geometry):
def __str__(self):
""" override print function output """
return "( inst: " + self.name + " @" + str(self.offset) + " mod=" + self.mod.name + " " + self.mirror + " R=" + str(self.rotate) + ")"
return "( inst: " + self.name + " @" + str(self.offset) + " mod=" + self.mod.cell_name + " " + self.mirror + " R=" + str(self.rotate) + ")"
def __repr__(self):
""" override print function output """
return "( inst: " + self.name + " @" + str(self.offset) + " mod=" + self.mod.name + " " + self.mirror + " R=" + str(self.rotate) + ")"
return "( inst: " + self.name + " @" + str(self.offset) + " mod=" + self.mod.cell_name + " " + self.mirror + " R=" + str(self.rotate) + ")"
class path(geometry):
"""Represents a Path"""

View File

@ -2,13 +2,13 @@ import copy
from collections import defaultdict
import debug
class timing_graph():
"""
Implements a directed graph
Nodes are currently just Strings.
"""
def __init__(self):
self.graph = defaultdict(set)
self.all_paths = []
@ -17,7 +17,7 @@ class timing_graph():
def add_edge(self, src_node, dest_node, edge_mod):
"""Adds edge to graph. Nodes added as well if they do not exist.
Module which defines the edge must be provided for timing information."""
src_node = src_node.lower()
dest_node = dest_node.lower()
self.graph[src_node].add(dest_node)
@ -25,99 +25,99 @@ class timing_graph():
def add_node(self, node):
"""Add node to graph with no edges"""
node = node.lower()
if node not in self.graph:
self.graph[node] = set()
def remove_edges(self, node):
"""Helper function to remove edges, useful for removing vdd/gnd"""
node = node.lower()
self.graph[node] = set()
def get_all_paths(self, src_node, dest_node, remove_rail_nodes=True, reduce_paths=True):
"""Traverse all paths from source to destination"""
src_node = src_node.lower()
dest_node = dest_node.lower()
# Remove vdd and gnd by default
# Will require edits if separate supplies are implemented.
if remove_rail_nodes:
# Names are also assumed.
self.remove_edges('vdd')
self.remove_edges('gnd')
# Mark all the vertices as not visited
# Mark all the vertices as not visited
visited = set()
# Create an array to store paths
# Create an array to store paths
path = []
self.all_paths = []
# Call the recursive helper function to print all paths
# Call the recursive helper function to print all paths
self.get_all_paths_util(src_node, dest_node, visited, path)
debug.info(2, "Paths found={}".format(len(self.all_paths)))
if reduce_paths:
self.reduce_paths()
return self.all_paths
def reduce_paths(self):
""" Remove any path that is a subset of another path """
self.all_paths = [p1 for p1 in self.all_paths if not any(set(p1)<=set(p2) for p2 in self.all_paths if p1 is not p2)]
def get_all_paths_util(self, cur_node, dest_node, visited, path):
"""Recursive function to find all paths in a Depth First Search manner"""
# Mark the current node as visited and store in path
# Mark the current node as visited and store in path
visited.add(cur_node)
path.append(cur_node)
# If current vertex is same as destination, then print
# current path[]
# If current vertex is same as destination, then print
# current path[]
if cur_node == dest_node:
self.all_paths.append(copy.deepcopy(path))
else:
# If current vertex is not destination
# Recur for all the vertices adjacent to this vertex
# If current vertex is not destination
# Recur for all the vertices adjacent to this vertex
for node in self.graph[cur_node]:
if node not in visited:
self.get_all_paths_util(node, dest_node, visited, path)
# Remove current vertex from path[] and mark it as unvisited
# Remove current vertex from path[] and mark it as unvisited
path.pop()
visited.remove(cur_node)
def get_timing(self, path, corner, slew, load):
"""Returns the analytical delays in the input path"""
if len(path) == 0:
return []
delays = []
cur_slew = slew
for i in range(len(path) - 1):
path_edge_mod = self.edge_mods[(path[i], path[i + 1])]
# On the output of the current stage, get COUT from all other mods connected
cout = 0
for node in self.graph[path[i + 1]]:
output_edge_mod = self.edge_mods[(path[i + 1], node)]
cout+=output_edge_mod.get_cin()
# If at the last output, include the final output load
# If at the last output, include the final output load
if i == len(path) - 2:
cout += load
delays.append(path_edge_mod.analytical_delay(corner, cur_slew, cout))
cur_slew = delays[-1].slew
return delays
def __str__(self):
""" override print function output """
@ -132,4 +132,4 @@ class timing_graph():
""" override print function output """
return str(self)

View File

@ -20,9 +20,9 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
"""
name_map = []
def __init__(self, name):
self.gds_file = OPTS.openram_tech + "gds_lib/" + name + ".gds"
self.sp_file = OPTS.openram_tech + "sp_lib/" + name + ".sp"
def __init__(self, name, cell_name):
self.gds_file = OPTS.openram_tech + "gds_lib/" + cell_name + ".gds"
self.sp_file = OPTS.openram_tech + "sp_lib/" + cell_name + ".sp"
# If we have a separate lvs directory, then all the lvs files
# should be in there (all or nothing!)
@ -33,7 +33,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
lvs_dir = OPTS.openram_tech + lvs_subdir + "/"
if os.path.exists(lvs_dir):
self.lvs_file = lvs_dir + name + ".sp"
self.lvs_file = lvs_dir + cell_name + ".sp"
else:
self.lvs_file = self.sp_file
@ -41,8 +41,9 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
self.lvs_errors = "skipped"
self.name = name
hierarchy_spice.spice.__init__(self, name)
hierarchy_layout.layout.__init__(self, name)
self.cell_name = cell_name
hierarchy_spice.spice.__init__(self, name, cell_name)
hierarchy_layout.layout.__init__(self, name, cell_name)
self.init_graph_params()
def get_layout_pins(self, inst):
@ -55,7 +56,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
debug.error("Couldn't find instance {0}".format(inst.name), -1)
inst_map = inst.mod.pin_map
return inst_map
def DRC_LVS(self, final_verification=False, force_check=False):
"""Checks both DRC and LVS for a module"""
import verify
@ -76,23 +77,23 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
self.lvs_write(tempspice)
self.gds_write(tempgds)
# Final verification option does not allow nets to be connected by label.
self.drc_errors = verify.run_drc(self.name, tempgds, extract=True, final_verification=final_verification)
self.lvs_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification)
self.drc_errors = verify.run_drc(self.cell_name, tempgds, extract=True, final_verification=final_verification)
self.lvs_errors = verify.run_lvs(self.cell_name, tempgds, tempspice, final_verification=final_verification)
# force_check is used to determine decoder height and other things, so we shouldn't fail
# if that flag is set
if OPTS.inline_lvsdrc and not force_check:
debug.check(self.drc_errors == 0,
"DRC failed for {0} with {1} error(s)".format(self.name,
"DRC failed for {0} with {1} error(s)".format(self.cell_name,
self.drc_errors))
debug.check(self.lvs_errors == 0,
"LVS failed for {0} with {1} errors(s)".format(self.name,
"LVS failed for {0} with {1} errors(s)".format(self.cell_name,
self.lvs_errors))
if OPTS.purge_temp:
if not OPTS.keep_temp:
os.remove(tempspice)
os.remove(tempgds)
def DRC(self, final_verification=False):
"""Checks DRC for a module"""
import verify
@ -104,14 +105,14 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
if OPTS.netlist_only:
return
elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp, self.name)
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp, self.cell_name)
self.gds_write(tempgds)
num_errors = verify.run_drc(self.name, tempgds, final_verification=final_verification)
num_errors = verify.run_drc(self.cell_name, tempgds, final_verification=final_verification)
debug.check(num_errors == 0,
"DRC failed for {0} with {1} error(s)".format(self.name,
"DRC failed for {0} with {1} error(s)".format(self.cell_name,
num_errors))
if OPTS.purge_temp:
if not OPTS.keep_temp:
os.remove(tempgds)
def LVS(self, final_verification=False):
@ -125,30 +126,30 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
if OPTS.netlist_only:
return
elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
tempspice = "{0}/{1}.sp".format(OPTS.openram_temp, self.name)
tempspice = "{0}/{1}.sp".format(OPTS.openram_temp, self.cell_name)
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp, self.name)
self.lvs_write(tempspice)
self.gds_write(tempgds)
num_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification)
debug.check(num_errors == 0,
"LVS failed for {0} with {1} error(s)".format(self.name,
"LVS failed for {0} with {1} error(s)".format(self.cell_name,
num_errors))
if OPTS.purge_temp:
if not OPTS.keep_temp:
os.remove(tempspice)
os.remove(tempgds)
def init_graph_params(self):
"""
"""
Initializes parameters relevant to the graph creation
"""
# Only initializes a set for checking instances which should not be added
self.graph_inst_exclude = set()
def build_graph(self, graph, inst_name, port_nets):
"""
Recursively create graph from instances in module.
"""
# Translate port names to external nets
if len(port_nets) != len(self.pins):
debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,
@ -162,7 +163,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
subinst_name = inst_name + '.X' + subinst.name
subinst_ports = self.translate_nets(conns, port_dict, inst_name)
subinst.mod.build_graph(graph, subinst_name, subinst_ports)
def build_names(self, name_dict, inst_name, port_nets):
"""
Collects all the nets and the parent inst of that net.
@ -195,7 +196,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
else:
converted_conns.append("{}.{}".format(inst_name, conn))
return converted_conns
def add_graph_edges(self, graph, port_nets):
"""
For every input, adds an edge to every output.
@ -211,13 +212,13 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
for out in output_pins + inout_pins:
if inp != out: # do not add self loops
graph.add_edge(pin_dict[inp], pin_dict[out], self)
def __str__(self):
""" override print function output """
pins = ",".join(self.pins)
insts = [" {}".format(x) for x in self.insts]
objs = [" {}".format(x) for x in self.objs]
s = "********** design {0} **********".format(self.name)
s = "********** design {0} **********".format(self.cell_name)
s += "\n pins ({0})={1}\n".format(len(self.pins), pins)
s += "\n objs ({0})=\n{1}\n".format(len(self.objs), "\n".join(objs))
s += "\n insts ({0})=\n{1}\n".format(len(self.insts), "\n".join(insts))
@ -231,4 +232,4 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
for i in self.insts:
text+=str(i) + ",\n"
return text

View File

@ -31,8 +31,9 @@ class layout():
layout/netlist and perform LVS/DRC.
"""
def __init__(self, name):
def __init__(self, name, cell_name):
self.name = name
self.cell_name = cell_name
self.width = None
self.height = None
self.bounding_box = None
@ -67,13 +68,13 @@ class layout():
def offset_x_coordinates(self):
"""
This function is called after everything is placed to
shift the origin to the furthest left point.
shift the origin to the furthest left point.
Y offset is unchanged.
"""
offset = self.find_lowest_coords()
self.translate_all(offset.scale(1, 0))
return offset
def get_gate_offset(self, x_offset, height, inv_num):
"""
Gets the base offset and y orientation of stacked rows of gates
@ -215,7 +216,7 @@ class layout():
# Contacts are not really instances, so skip them
if "contact" not in mod.name:
# Check that the instance name is unique
debug.check(name not in self.inst_names, "Duplicate named instance in {0}: {1}".format(self.name, name))
debug.check(name not in self.inst_names, "Duplicate named instance in {0}: {1}".format(self.cell_name, name))
self.inst_names.add(name)
self.insts.append(geometry.instance(name, mod, offset, mirror, rotate))
@ -300,9 +301,9 @@ class layout():
tx_list.append(i)
except AttributeError:
pass
return tx_list
def get_pin(self, text):
"""
Return the pin or list of pins
@ -316,7 +317,7 @@ class layout():
return any_pin
except Exception:
self.gds_write("missing_pin.gds")
debug.error("No pin found with name {0} on {1}. Saved as missing_pin.gds.".format(text, self.name), -1)
debug.error("No pin found with name {0} on {1}. Saved as missing_pin.gds.".format(text, self.cell_name), -1)
def get_pins(self, text):
"""
@ -537,10 +538,6 @@ class layout():
position_list=coordinates,
widen_short_wires=widen_short_wires)
def get_preferred_direction(self, layer):
""" Return the preferred routing directions """
return preferred_directions[layer]
def add_via(self, layers, offset, size=[1, 1], directions=None, implant_type=None, well_type=None):
""" Add a three layer via structure. """
from sram_factory import factory
@ -617,24 +614,24 @@ class layout():
next_id = 0
curr_stack = next(filter(lambda stack: stack[search_id] == cur_layer, layer_stacks), None)
via = self.add_via_center(layers=curr_stack,
size=size,
offset=offset,
directions=directions,
implant_type=implant_type,
well_type=well_type)
if cur_layer != from_layer:
self.add_min_area_rect_center(cur_layer,
offset,
via.mod.first_layer_width,
via.mod.first_layer_height)
cur_layer = curr_stack[next_id]
return via
def add_min_area_rect_center(self,
layer,
offset,
@ -648,14 +645,14 @@ class layout():
min_area = drc("minarea_{}".format(layer))
if min_area == 0:
return
min_width = drc("minwidth_{}".format(layer))
if preferred_directions[layer] == "V":
height = max(min_area / width, min_width)
else:
width = max(min_area / height, min_width)
self.add_rect_center(layer=layer,
offset=offset,
width=width,
@ -739,7 +736,7 @@ class layout():
height = boundary[1][1] - boundary[0][1]
width = boundary[1][0] - boundary[0][0]
for boundary_layer in boundary_layers:
(layer_number, layer_purpose) = techlayer[boundary_layer]
gds_layout.addBox(layerNumber=layer_number,
@ -891,7 +888,7 @@ class layout():
new_pin = pin_layout(names[i],
[rect.ll(), rect.ur()],
layer)
pins[names[i]] = new_pin
else:
for i in range(len(names)):
@ -909,7 +906,7 @@ class layout():
new_pin = pin_layout(names[i],
[rect.ll(), rect.ur()],
layer)
pins[names[i]] = new_pin
return pins
@ -1047,7 +1044,7 @@ class layout():
cr = channel_route.channel_route(netlist, offset, layer_stack, directions, vertical=False, parent=self)
self.add_inst(cr.name, cr)
self.connect_inst([])
def add_boundary(self, ll=vector(0, 0), ur=None):
""" Add boundary for debugging dimensions """
if OPTS.netlist_only:
@ -1112,7 +1109,7 @@ class layout():
width=xmax - xmin,
height=ymax - ymin)
return rect
def copy_power_pins(self, inst, name, add_vias=True):
"""
This will copy a power pin if it is on the lowest power_grid layer.
@ -1171,7 +1168,7 @@ class layout():
bottom = ll.y
right = ur.x
top = ur.y
pin_loc = pin.center()
if side == "left":
peri_pin_loc = vector(left, pin_loc.y)
@ -1189,14 +1186,14 @@ class layout():
self.add_via_stack_center(from_layer=pin.layer,
to_layer=layer,
offset=pin_loc)
self.add_path(layer,
[pin_loc, peri_pin_loc])
return self.add_layout_pin_rect_center(text=name,
layer=layer,
offset=peri_pin_loc)
def add_power_ring(self, bbox):
"""
Create vdd and gnd power rings around an area of the bounding box

View File

@ -10,6 +10,7 @@ import re
import os
import math
import tech
from pprint import pformat
from delay_data import delay_data
from wire_spice_model import wire_spice_model
from power_data import power_data
@ -26,8 +27,9 @@ class spice():
Class consisting of a set of modules and instances of these modules
"""
def __init__(self, name):
def __init__(self, name, cell_name):
self.name = name
self.cell_name = cell_name
self.valid_signal_types = ["INOUT", "INPUT", "OUTPUT", "POWER", "GROUND"]
# Holds subckts/mods for this module
@ -63,7 +65,7 @@ class spice():
self.comments = []
self.comments.append(comment)
def add_pin(self, name, pin_type="INOUT"):
""" Adds a pin to the pins list. Default type is INOUT signal. """
self.pins.append(name)
@ -82,7 +84,7 @@ class spice():
"Invalid signaltype for {0}: {1}".format(pin,
pin_type))
self.add_pin(pin, pin_type)
elif len(pin_type)==len(pin_list):
for (pin, ptype) in zip(pin_list, pin_type):
debug.check(ptype in self.valid_signal_types,
@ -104,7 +106,7 @@ class spice():
\n Module names={}\
".format(self.name, self.pin_names, self.pins), 1)
self.pin_type = {pin: type for pin, type in zip(self.pin_names, type_list)}
def get_pin_type(self, name):
""" Returns the type of the signal pin. """
pin_type = self.pin_type[name]
@ -118,7 +120,7 @@ class spice():
return "INOUT"
else:
return self.pin_type[name]
def get_inputs(self):
""" These use pin types to determine pin lists. These
may be over-ridden by submodules that didn't use pin directions yet."""
@ -164,7 +166,6 @@ class spice():
num_pins = len(self.insts[-1].mod.pins)
num_args = len(args)
if (check and num_pins != num_args):
from pprint import pformat
if num_pins < num_args:
mod_pins = self.insts[-1].mod.pins + [""] * (num_args - num_pins)
arg_pins = args
@ -181,10 +182,9 @@ class spice():
self.conns.append(args)
if check and (len(self.insts)!=len(self.conns)):
from pprint import pformat
insts_string=pformat(self.insts)
conns_string=pformat(self.conns)
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name,
len(self.insts),
len(self.conns)))
@ -214,7 +214,7 @@ class spice():
f.close()
# find the correct subckt line in the file
subckt = re.compile("^.subckt {}".format(self.name), re.IGNORECASE)
subckt = re.compile("^.subckt {}".format(self.cell_name), re.IGNORECASE)
subckt_line = list(filter(subckt.search, self.spice))[0]
# parses line into ports and remove subckt
self.pins = subckt_line.split(" ")[2:]
@ -234,12 +234,12 @@ class spice():
# pins and subckt should be the same
# find the correct subckt line in the file
subckt = re.compile("^.subckt {}".format(self.name), re.IGNORECASE)
subckt = re.compile("^.subckt {}".format(self.cell_name), re.IGNORECASE)
subckt_line = list(filter(subckt.search, self.lvs))[0]
# parses line into ports and remove subckt
lvs_pins = subckt_line.split(" ")[2:]
debug.check(lvs_pins == self.pins, "LVS and spice file pin mismatch.")
def check_net_in_spice(self, net_name):
"""Checks if a net name exists in the current. Intended to be check nets in hand-made cells."""
# Remove spaces and lower case then add spaces.
@ -255,14 +255,14 @@ class spice():
if net_formatted in line:
return True
return False
def do_nets_exist(self, nets):
"""For handmade cell, checks sp file contains the storage nodes."""
nets_match = True
for net in nets:
nets_match = nets_match and self.check_net_in_spice(net)
return nets_match
def contains(self, mod, modlist):
for x in modlist:
if x.name == mod.name:
@ -279,7 +279,7 @@ class spice():
return
elif not self.spice:
# If spice isn't defined, we dynamically generate one.
# recursively write the modules
for i in self.mods:
if self.contains(i, usedMODS):
@ -293,18 +293,18 @@ class spice():
return
# write out the first spice line (the subcircuit)
sp.write("\n.SUBCKT {0} {1}\n".format(self.name,
sp.write("\n.SUBCKT {0} {1}\n".format(self.cell_name,
" ".join(self.pins)))
for pin in self.pins:
sp.write("* {1:6}: {0} \n".format(pin, self.pin_type[pin]))
for line in self.comments:
sp.write("* {}\n".format(line))
# every instance must have a set of connections, even if it is empty.
if len(self.insts) != len(self.conns):
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name,
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.cell_name,
len(self.insts),
len(self.conns)))
debug.error("Instances: \n" + str(self.insts))
@ -330,9 +330,9 @@ class spice():
else:
sp.write("X{0} {1} {2}\n".format(self.insts[i].name,
" ".join(self.conns[i]),
self.insts[i].mod.name))
self.insts[i].mod.cell_name))
sp.write(".ENDS {0}\n".format(self.name))
sp.write(".ENDS {0}\n".format(self.cell_name))
else:
# If spice is a hard module, output the spice file contents.
@ -343,7 +343,7 @@ class spice():
sp.write("\n".join(self.lvs))
else:
sp.write("\n".join(self.spice))
sp.write("\n")
def sp_write(self, spname):
@ -365,19 +365,19 @@ class spice():
self.sp_write_file(spfile, usedMODS, True)
del usedMODS
spfile.close()
def analytical_delay(self, corner, slew, load=0.0):
"""Inform users undefined delay module while building new modules"""
# FIXME: Slew is not used in the model right now.
# Can be added heuristically as linear factor
relative_cap = logical_effort.convert_farad_to_relative_c(load)
stage_effort = self.get_stage_effort(relative_cap)
# If it fails, then keep running with a valid object.
if not stage_effort:
return delay_data(0.0, 0.0)
abs_delay = stage_effort.get_absolute_delay()
corner_delay = self.apply_corners_analytically(abs_delay, corner)
SLEW_APPROXIMATION = 0.1
@ -390,27 +390,27 @@ class spice():
.format(self.__class__.__name__))
debug.warning("Class {0} name {1}"
.format(self.__class__.__name__,
self.name))
self.cell_name))
return None
def get_cin(self):
"""Returns input load in Femto-Farads. All values generated using
relative capacitance function then converted based on tech file parameter."""
# Override this function within a module if a more accurate input capacitance is needed.
# Input/outputs with differing capacitances is not implemented.
relative_cap = self.input_load()
return logical_effort.convert_relative_c_to_farad(relative_cap)
def input_load(self):
"""Inform users undefined relative capacitance functions used for analytical delays."""
debug.warning("Design Class {0} input capacitance function needs to be defined"
.format(self.__class__.__name__))
debug.warning("Class {0} name {1}"
.format(self.__class__.__name__,
self.name))
self.cell_name))
return 0
def cal_delay_with_rc(self, corner, r, c, slew, swing=0.5):
"""
Calculate the delay of a mosfet by
@ -420,7 +420,7 @@ class spice():
delay = swing_factor * r * c # c is in ff and delay is in fs
delay = self.apply_corners_analytically(delay, corner)
delay = delay * 0.001 # make the unit to ps
# Output slew should be linear to input slew which is described
# as 0.005* slew.
@ -439,7 +439,7 @@ class spice():
volt_mult = self.get_voltage_delay_factor(vdd)
temp_mult = self.get_temp_delay_factor(temp)
return delay * proc_mult * volt_mult * temp_mult
def get_process_delay_factor(self, proc):
"""Returns delay increase estimate based off process
Currently does +/-10 for fast/slow corners."""
@ -452,13 +452,13 @@ class spice():
elif mos_proc == 'S':
proc_factors.append(1.1)
return proc_factors
def get_voltage_delay_factor(self, voltage):
"""Returns delay increase due to voltage.
Implemented as linear factor based off nominal voltage.
"""
return tech.spice["nom_supply_voltage"] / voltage
def get_temp_delay_factor(self, temp):
"""Returns delay increase due to temperature (in C).
Determines effect on threshold voltage and then linear factor is estimated.
@ -478,7 +478,7 @@ class spice():
def generate_rc_net(self, lump_num, wire_length, wire_width):
return wire_spice_model(lump_num, wire_length, wire_width)
def calc_dynamic_power(self, corner, c, freq, swing=1.0):
"""
Calculate dynamic power using effective capacitance, frequency, and corner (PVT)
@ -486,16 +486,16 @@ class spice():
proc, vdd, temp = corner
net_vswing = vdd * swing
power_dyn = c * vdd * net_vswing * freq
# A pply process and temperature factors.
# Roughly, process and Vdd affect the delay which affects the power.
# No other estimations are currently used. Increased delay->slower freq.->less power
proc_div = max(self.get_process_delay_factor(proc))
temp_div = self.get_temp_delay_factor(temp)
power_dyn = power_dyn / (proc_div * temp_div)
return power_dyn
def return_power(self, dynamic=0.0, leakage=0.0):
return power_data(dynamic, leakage)
@ -519,7 +519,7 @@ class spice():
if int_mod.is_net_alias(int_net, alias, alias_mod, exclusion_set):
aliases.append(net)
return aliases
def is_net_alias(self, known_net, net_alias, mod, exclusion_set):
"""
Checks if the alias_net in input mod is the same as the input net for this mod (self).
@ -541,7 +541,7 @@ class spice():
return True
mod_set.add(subinst.mod)
return False
def is_net_alias_name_check(self, parent_net, child_net, alias_net, mod):
"""
Utility function for checking single net alias.

View File

@ -5,14 +5,9 @@
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import gdsMill
import tech
import globals
import math
import debug
import datetime
from collections import defaultdict
import pdb
from tech import layer_names
class lef:
"""
@ -20,13 +15,13 @@ class lef:
and write them to LEF file.
This is inherited by the sram_base class.
"""
def __init__(self,layers):
def __init__(self, layers):
# LEF db units per micron
self.lef_units = 2000
# These are the layers of the obstructions
self.lef_layers = layers
# Round to ensure float values are divisible by 0.0025 (the manufacturing grid)
self.round_grid = 4;
self.round_grid = 4
def lef_write(self, lef_name):
"""Write the entire lef of the object to the file."""
@ -34,14 +29,14 @@ class lef:
self.indent = "" # To maintain the indent level easily
self.lef = open(lef_name,"w")
self.lef = open(lef_name, "w")
self.lef_write_header()
for pin in self.pins:
self.lef_write_pin(pin)
self.lef_write_obstructions()
self.lef_write_footer()
self.lef.close()
def lef_write_header(self):
""" Header of LEF file """
self.lef.write("VERSION 5.4 ;\n")
@ -51,78 +46,80 @@ class lef:
self.lef.write("UNITS\n")
self.lef.write(" DATABASE MICRONS {0} ;\n".format(self.lef_units))
self.lef.write("END UNITS\n")
self.lef.write("{0}MACRO {1}\n".format(self.indent,self.name))
self.lef.write("{0}MACRO {1}\n".format(self.indent, self.name))
self.indent += " "
self.lef.write("{0}CLASS BLOCK ;\n".format(self.indent))
self.lef.write("{0}SIZE {1} BY {2} ;\n" .format(self.indent,
round(self.width,self.round_grid),
round(self.height,self.round_grid)))
round(self.width, self.round_grid),
round(self.height, self.round_grid)))
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.lef.write("{0}END {1}\n".format(self.indent, self.name))
self.indent = self.indent[:-3]
self.lef.write("END LIBRARY\n")
def lef_write_pin(self, name):
pin_dir = self.get_pin_dir(name)
pin_type = self.get_pin_type(name)
self.lef.write("{0}PIN {1}\n".format(self.indent,name))
self.lef.write("{0}PIN {1}\n".format(self.indent, name))
self.indent += " "
self.lef.write("{0}DIRECTION {1} ;\n".format(self.indent,pin_dir))
if pin_type in ["POWER","GROUND"]:
self.lef.write("{0}USE {1} ; \n".format(self.indent,pin_type))
self.lef.write("{0}DIRECTION {1} ;\n".format(self.indent, pin_dir))
if pin_type in ["POWER", "GROUND"]:
self.lef.write("{0}USE {1} ; \n".format(self.indent, pin_type))
self.lef.write("{0}SHAPE ABUTMENT ; \n".format(self.indent))
self.lef.write("{0}PORT\n".format(self.indent))
self.indent += " "
# We could sort these together to minimize different layer sections, but meh.
pin_list = self.get_pins(name)
for pin in pin_list:
self.lef.write("{0}LAYER {1} ;\n".format(self.indent,pin.layer))
self.lef.write("{0}LAYER {1} ;\n".format(self.indent, layer_names[pin.layer]))
self.lef_write_shape(pin.rect)
# End the PORT
self.indent = self.indent[:-3]
self.lef.write("{0}END\n".format(self.indent))
# End the PIN
self.indent = self.indent[:-3]
self.lef.write("{0}END {1}\n".format(self.indent,name))
self.lef.write("{0}END {1}\n".format(self.indent, name))
def lef_write_obstructions(self):
""" 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))
self.lef.write("{0}LAYER {1} ;\n".format(self.indent, layer_names[layer]))
self.indent += " "
# pdb.set_trace()
blockages = self.get_blockages(layer,True)
blockages = self.get_blockages(layer, True)
for b in blockages:
# if len(b) > 2:
# print(b)
self.lef_write_shape(b)
self.indent = self.indent[:-3]
self.lef.write("{0}END\n".format(self.indent))
def lef_write_shape(self, rect):
if len(rect) == 2:
if len(rect) == 2:
""" Write a LEF rectangle """
self.lef.write("{0}RECT ".format(self.indent))
self.lef.write("{0}RECT ".format(self.indent))
for item in rect:
# print(rect)
self.lef.write(" {0} {1}".format(round(item[0],self.round_grid), round(item[1],self.round_grid)))
self.lef.write(" {0} {1}".format(round(item[0],
self.round_grid),
round(item[1],
self.round_grid)))
self.lef.write(" ;\n")
else:
else:
""" Write a LEF polygon """
self.lef.write("{0}POLYGON ".format(self.indent))
self.lef.write("{0}POLYGON ".format(self.indent))
for item in rect:
self.lef.write(" {0} {1}".format(round(item[0],self.round_grid), round(item[1],self.round_grid)))
self.lef.write(" {0} {1}".format(round(item[0],
self.round_grid),
round(item[1],
self.round_grid)))
# for i in range(0,len(rect)):
# self.lef.write(" {0} {1}".format(round(rect[i][0],self.round_grid), round(rect[i][1],self.round_grid)))
self.lef.write(" ;\n")

View File

@ -33,7 +33,7 @@ class pin_layout:
# These are the valid pin layers
valid_layers = { x: layer[x] for x in layer_indices.keys()}
# if it's a string, use the name
if type(layer_name_pp) == str:
self._layer = layer_name_pp
@ -378,7 +378,7 @@ class pin_layout:
from tech import label_purpose
except ImportError:
label_purpose = purpose
newLayout.addBox(layerNumber=layer_num,
purposeNumber=pin_purpose,
offsetInMicrons=self.ll(),

View File

@ -14,12 +14,12 @@ from vector3d import vector3d
from sram_factory import factory
class route(design):
"""
"""
Object route (used by the router module)
Add a route of minimium metal width between a set of points.
The widths are the layer widths of the layer stack.
The widths are the layer widths of the layer stack.
(Vias are in numer of vias.)
The wire must be completely rectilinear and the
The wire must be completely rectilinear and the
z-dimension of the points refers to the layers.
The points are the center of the wire.
This can have non-preferred direction routing.
@ -45,12 +45,12 @@ class route(design):
def setup_layers(self):
(self.horiz_layer_name, self.via_layer, self.vert_layer_name) = self.layer_stack
(self.horiz_layer_width, self.num_vias, self.vert_layer_width) = self.layer_widths
if not self.vert_layer_width:
self.vert_layer_width = drc("minwidth_{0}".format(self.vert_layer_name))
if not self.horiz_layer_width:
self.horiz_layer_width = drc("minwidth_{0}".format(self.horiz_layer_name))
# offset this by 1/2 the via size
self.c=factory.create(module_type="contact",
layer_stack=self.layer_stack,
@ -58,7 +58,7 @@ class route(design):
def create_wires(self):
"""
"""
Add the wire segments of the route.
"""
@ -67,7 +67,7 @@ class route(design):
a, b = tee(iterable)
next(b, None)
return zip(a, b)
plist = list(pairwise(self.path))
for p0,p1 in plist:
if p0.z != p1.z: # via
@ -87,10 +87,10 @@ class route(design):
self.draw_corner_wire(plist[0][0])
self.draw_corner_wire(plist[-1][1])
def get_layer_width(self, layer_zindex):
"""
Return the layer width
Return the layer width
"""
if layer_zindex==0:
return self.horiz_layer_width
@ -109,11 +109,11 @@ class route(design):
return self.vert_layer_name
else:
debug.error("Incorrect layer zindex.",-1)
def draw_wire(self, p0, p1):
"""
This draws a straight wire with layer_minwidth
This draws a straight wire with layer_minwidth
"""
layer_width = self.get_layer_width(p0.z)
@ -145,8 +145,8 @@ class route(design):
offset=vector(offset.x,offset.y),
width=width,
height=height)
def draw_corner_wire(self, p0):
""" This function adds the corner squares since the center
line convention only draws to the center of the corner."""

View File

@ -4,10 +4,12 @@
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import os
import math
import gdsMill
import tech
import math
import globals
import debug
from vector import vector
@ -57,10 +59,11 @@ def auto_measure_libcell(pin_list, name, units, lpp):
Return these as a set of properties including the cell width/height too.
"""
cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds"
cell_vlsi = gdsMill.VlsiLayout(units=units)
reader = gdsMill.Gds2reader(cell_vlsi)
reader.loadFromFile(cell_gds)
cell_vlsi = _get_gds_reader(units, cell_gds)
# FIXME: This duplicates a lot of functionality of get_gds_size and
# get_gds_pins, it should probably just call those functions?
cell = {}
measure_result = cell_vlsi.getLayoutBorder(lpp[0])
if measure_result:
@ -73,22 +76,47 @@ def auto_measure_libcell(pin_list, name, units, lpp):
return cell
_GDS_READER_CACHE = {}
def _get_gds_reader(units, gds_filename):
gds_absname = os.path.realpath(gds_filename)
k = (units, gds_absname)
try:
return _GDS_READER_CACHE[k]
except KeyError:
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)
_GDS_READER_CACHE[k] = cell_vlsi
return cell_vlsi
_GDS_SIZE_CACHE = {}
def get_gds_size(name, gds_filename, units, lpp):
"""
Open a GDS file and return the size from either the
bounding box or a border layer.
"""
debug.info(4, "Creating VLSI layout for {}".format(name))
cell_vlsi = gdsMill.VlsiLayout(units=units)
reader = gdsMill.Gds2reader(cell_vlsi)
reader.loadFromFile(gds_filename)
k = (name, os.path.realpath(gds_filename), units, lpp)
try:
return _GDS_SIZE_CACHE[k]
except KeyError:
cell_vlsi = _get_gds_reader(units, gds_filename)
measure_result = cell_vlsi.getLayoutBorder(lpp)
if not measure_result:
debug.info(2, "Layout border failed. Trying to measure size for {}".format(name))
measure_result = cell_vlsi.measureSize(name)
# returns width,height
return measure_result
measure_result = cell_vlsi.getLayoutBorder(lpp)
if not measure_result:
debug.info(2, "Layout border failed. Trying to measure size for {}".format(name))
measure_result = cell_vlsi.measureSize(name)
_GDS_SIZE_CACHE[k] = measure_result
# returns width,height
return measure_result
def get_libcell_size(name, units, lpp):
@ -101,27 +129,34 @@ def get_libcell_size(name, units, lpp):
return(get_gds_size(name, cell_gds, units, lpp))
_GDS_PINS_CACHE = {}
def get_gds_pins(pin_names, name, gds_filename, units):
"""
Open a GDS file and find the pins in pin_names as text on a given layer.
Return these as a rectangle layer pair for each pin.
"""
cell_vlsi = gdsMill.VlsiLayout(units=units)
reader = gdsMill.Gds2reader(cell_vlsi)
reader.loadFromFile(gds_filename)
k = (tuple(pin_names), name, os.path.realpath(gds_filename), units)
try:
return dict(_GDS_PINS_CACHE[k])
except KeyError:
cell_vlsi = _get_gds_reader(units, gds_filename)
cell = {}
for pin_name in pin_names:
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))
return cell
cell = {}
for pin_name in pin_names:
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))
_GDS_PINS_CACHE[k] = cell
return dict(cell)
def get_libcell_pins(pin_list, name, units):
@ -132,7 +167,3 @@ def get_libcell_pins(pin_list, name, units):
cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds"
return(get_gds_pins(pin_list, name, cell_gds, units))

View File

@ -39,8 +39,8 @@ class vector():
return "v["+str(self.x)+","+str(self.y)+"]"
def __setitem__(self, index, value):
"""
override setitem function
"""
override setitem function
can set value by vector[index]=value
"""
if index==0:
@ -50,10 +50,10 @@ class vector():
else:
self.x=float(value[0])
self.y=float(value[1])
def __getitem__(self, index):
"""
override getitem function
override getitem function
can get value by value=vector[index]
"""
if index==0:
@ -61,7 +61,7 @@ class vector():
elif index==1:
return self.y
else:
return self
return self
def __add__(self, other):
"""
@ -109,7 +109,7 @@ class vector():
"""
Changes the coodrinate to match the grid settings
"""
grid = tech.drc["grid"]
grid = tech.drc["grid"]
# this gets the nearest integer value
off_in_grid = int(round(round((offset / grid), 2), 0))
offset = off_in_grid * grid
@ -150,8 +150,8 @@ class vector():
Override round function
"""
return vector(int(round(self.x)),int(round(self.y)))
def __eq__(self, other):
"""Override the default Equals behavior"""
if isinstance(other, self.__class__):

View File

@ -9,13 +9,13 @@ import debug
import math
class verilog:
"""
"""
Create a behavioral Verilog file for simulation.
This is inherited by the sram_base class.
"""
def __init__(self):
pass
def verilog_write(self,verilog_name):
""" Write a behavioral Verilog model. """
self.vf = open(verilog_name, "w")
@ -67,7 +67,7 @@ class verilog:
self.add_inputs_outputs(port)
self.vf.write("\n")
for port in self.all_ports:
self.register_inputs(port)
@ -79,8 +79,8 @@ class verilog:
self.add_write_block(port)
if port in self.read_ports:
self.add_read_block(port)
self.vf.write("\n")
self.vf.write("\n")
self.vf.write("endmodule\n")
self.vf.close()
@ -91,9 +91,9 @@ class verilog:
"""
self.add_regs(port)
self.add_flops(port)
def add_regs(self, port):
"""
"""
Create the input regs for the given port.
"""
self.vf.write(" reg csb{0}_reg;\n".format(port))
@ -107,7 +107,7 @@ class verilog:
self.vf.write(" reg [DATA_WIDTH-1:0] din{0}_reg;\n".format(port))
if port in self.read_ports:
self.vf.write(" reg [DATA_WIDTH-1:0] dout{0};\n".format(port))
def add_flops(self, port):
"""
Add the flop behavior logic for a port.
@ -125,7 +125,7 @@ class verilog:
self.vf.write(" addr{0}_reg = addr{0};\n".format(port))
if port in self.read_ports:
self.add_write_read_checks(port)
if port in self.write_ports:
self.vf.write(" din{0}_reg = din{0};\n".format(port))
if port in self.read_ports:
@ -150,7 +150,7 @@ class verilog:
self.vf.write(" $display($time,\" Writing %m addr{0}=%b din{0}=%b\",addr{0}_reg,din{0}_reg);\n".format(port))
self.vf.write(" end\n\n")
def add_inputs_outputs(self, port):
"""
@ -203,7 +203,7 @@ class verilog:
else:
self.vf.write(" mem[addr{0}_reg] = din{0}_reg;\n".format(port))
self.vf.write(" end\n")
def add_read_block(self, port):
"""
Add a read port block.
@ -231,12 +231,12 @@ class verilog:
wport_control = "!csb{0} && !web{0}".format(wport)
else:
wport_control = "!csb{0}".format(wport)
self.vf.write(" if ({1} && {3} && (addr{0} == addr{2}))\n".format(wport,wport_control,rport,rport_control))
self.vf.write(" $display($time,\" WARNING: Writing and reading addr{0}=%b and addr{1}=%b simultaneously!\",addr{0},addr{1});\n".format(wport,rport))
def add_write_read_checks(self, rport):
"""
"""
Add a warning if we read from an address that we are currently writing.
Can be fixed if we appropriately size the write drivers to do this .
"""

View File

@ -58,13 +58,13 @@ class wire(wire_path):
via_connect.first_layer_width)
self.horiz_layer_contact_width = max(via_connect.second_layer_height,
via_connect.first_layer_height)
self.node_to_node = [drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.width,
drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.height]
self.pitch = self.compute_pitch(self.layer_stack)
def compute_pitch(self, layer_stack):
"""
This is contact direction independent pitch,
i.e. we take the maximum contact dimension
@ -79,13 +79,13 @@ class wire(wire_path):
except AttributeError:
contact1 = getattr(contact, layer2 + "_via")
max_contact = max(contact1.width, contact1.height)
layer1_space = drc("{0}_to_{0}".format(layer1))
layer2_space = drc("{0}_to_{0}".format(layer2))
pitch = max_contact + max(layer1_space, layer2_space)
return pitch
# create a 1x1 contact
def create_vias(self):
""" Add a via and corner square at every corner of the path."""

View File

@ -15,7 +15,7 @@ def create_rectilinear_route(my_list):
""" Add intermediate nodes if it isn't rectilinear. Also skip
repeated nodes. Also, convert to vector if the aren't."""
pl = [snap_to_grid(x) for x in my_list]
my_list = []
for index in range(len(pl) - 1):
if pl[index] != pl[index + 1]:
@ -121,7 +121,7 @@ class wire_path():
"""
width = layer_width
height = length
height = length
if orientation == "horizontal":
width = length

View File

@ -12,7 +12,7 @@ class wire_spice_model():
"""
def __init__(self, lump_num, wire_length, wire_width):
self.lump_num = lump_num # the number of segment the wire delay has
self.wire_c = self.cal_wire_c(wire_length, wire_width) # c in each segment
self.wire_c = self.cal_wire_c(wire_length, wire_width) # c in each segment
self.wire_r = self.cal_wire_r(wire_length, wire_width) # r in each segment
def cal_wire_c(self, wire_length, wire_width):
@ -36,7 +36,7 @@ class wire_spice_model():
swing_factor = abs(math.log(1-swing)) # time constant based on swing
sum_factor = (1+self.lump_num) * self.lump_num * 0.5 # sum of the arithmetic sequence
delay = sum_factor * swing_factor * self.wire_r * self.wire_c
delay = sum_factor * swing_factor * self.wire_r * self.wire_c
slew = delay * 2 + slew
result= delay_data(delay, slew)
return result

View File

@ -6,11 +6,9 @@
# All rights reserved.
#
import debug
import utils
from tech import GDS, layer
from tech import cell_properties as props
import bitcell_base
from globals import OPTS
class bitcell(bitcell_base.bitcell_base):
"""
@ -20,8 +18,6 @@ class bitcell(bitcell_base.bitcell_base):
library.
"""
# If we have a split WL bitcell, if not be backwards
# compatible in the tech file
pin_names = [props.bitcell.cell_6t.pin.bl,
props.bitcell.cell_6t.pin.br,
props.bitcell.cell_6t.pin.wl,
@ -30,24 +26,12 @@ class bitcell(bitcell_base.bitcell_base):
type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
storage_nets = ['Q', 'Q_bar']
(width, height) = utils.get_libcell_size("cell_6t",
GDS["unit"],
layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "cell_6t", GDS["unit"])
def __init__(self, name=""):
# Ignore the name argument
bitcell_base.bitcell_base.__init__(self, "cell_6t")
def __init__(self, name):
super().__init__(name)
debug.info(2, "Create bitcell")
self.width = bitcell.width
self.height = bitcell.height
self.pin_map = bitcell.pin_map
self.add_pin_types(self.type_list)
self.nets_match = self.do_nets_exist(self.storage_nets)
#debug.check(OPTS.tech_name != "sky130", "sky130 does not yet support single port cells")
def get_all_wl_names(self):
""" Creates a list of all wordline pin names """
row_pins = [props.bitcell.cell_6t.pin.wl]

View File

@ -6,10 +6,7 @@
# All rights reserved.
#
import debug
import utils
from tech import GDS, layer, parameter, drc
from tech import cell_properties as props
import logical_effort
import bitcell_base
@ -29,33 +26,23 @@ class bitcell_1rw_1r(bitcell_base.bitcell_base):
props.bitcell.cell_1rw1r.pin.wl1,
props.bitcell.cell_1rw1r.pin.vdd,
props.bitcell.cell_1rw1r.pin.gnd]
type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT",
"INPUT", "INPUT", "POWER", "GROUND"]
storage_nets = ['Q', 'Q_bar']
(width, height) = utils.get_libcell_size("cell_1rw_1r",
GDS["unit"],
layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "cell_1rw_1r", GDS["unit"])
def __init__(self, name=""):
# Ignore the name argument
bitcell_base.bitcell_base.__init__(self, "cell_1rw_1r")
def __init__(self, name):
super().__init__(name)
debug.info(2, "Create bitcell with 1RW and 1R Port")
self.width = bitcell_1rw_1r.width
self.height = bitcell_1rw_1r.height
self.pin_map = bitcell_1rw_1r.pin_map
self.add_pin_types(self.type_list)
self.nets_match = self.do_nets_exist(self.storage_nets)
pin_names = bitcell_1rw_1r.pin_names
pin_names = self.pin_names
self.bl_names = [pin_names[0], pin_names[2]]
self.br_names = [pin_names[1], pin_names[3]]
self.wl_names = [pin_names[4], pin_names[5]]
def get_bitcell_pins(self, col, row):
"""
"""
Creates a list of connections in the bitcell,
indexed by column and row, for instance use in bitcell_array
"""

View File

@ -6,8 +6,6 @@
# All rights reserved.
#
import debug
import utils
from tech import GDS, layer
from tech import cell_properties as props
import bitcell_base
@ -31,28 +29,18 @@ class bitcell_1w_1r(bitcell_base.bitcell_base):
type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT",
"INPUT", "INPUT", "POWER", "GROUND"]
storage_nets = ['Q', 'Q_bar']
(width, height) = utils.get_libcell_size("cell_1w_1r",
GDS["unit"],
layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "cell_1w_1r", GDS["unit"])
def __init__(self, name=""):
# Ignore the name argument
bitcell_base.bitcell_base.__init__(self, "cell_1w_1r")
def __init__(self, name):
super().__init__(name)
debug.info(2, "Create bitcell with 1W and 1R Port")
self.width = bitcell_1w_1r.width
self.height = bitcell_1w_1r.height
self.pin_map = bitcell_1w_1r.pin_map
self.add_pin_types(self.type_list)
self.nets_match = self.do_nets_exist(self.storage_nets)
pin_names = bitcell_1w_1r.pin_names
pin_names = self.pin_names
self.bl_names = [pin_names[0], pin_names[2]]
self.br_names = [pin_names[1], pin_names[3]]
self.wl_names = [pin_names[4], pin_names[5]]
def get_bitcell_pins(self, col, row):
"""
Creates a list of connections in the bitcell,

View File

@ -8,18 +8,30 @@
import debug
import design
import utils
from globals import OPTS
import logical_effort
from tech import parameter, drc, layer
from tech import GDS, parameter, drc, layer
class bitcell_base(design.design):
"""
Base bitcell parameters to be over-riden.
"""
def __init__(self, name):
cell_size_layer = "boundary"
def __init__(self, name, hard_cell=True):
design.design.__init__(self, name)
if hard_cell:
(self.width, self.height) = utils.get_libcell_size(self.cell_name,
GDS["unit"],
layer[self.cell_size_layer])
self.pin_map = utils.get_libcell_pins(self.pin_names,
self.cell_name,
GDS["unit"])
self.add_pin_types(self.type_list)
def get_stage_effort(self, load):
parasitic_delay = 1
# This accounts for bitline being drained
@ -49,13 +61,13 @@ class bitcell_base(design.design):
def input_load(self):
""" Return the relative capacitance of the access transistor gates """
# FIXME: This applies to bitline capacitances as well.
# FIXME: sizing is not accurate with the handmade cell.
# Change once cell widths are fixed.
access_tx_cin = parameter["6T_access_size"] / drc["minwidth_tx"]
return 2 * access_tx_cin
def get_wl_cin(self):
"""Return the relative capacitance of the access transistor gates"""
# This is a handmade cell so the value must be entered
@ -82,7 +94,7 @@ class bitcell_base(design.design):
def get_storage_net_offset(self):
"""
Gets the location of the storage net labels to add top level
Gets the location of the storage net labels to add top level
labels for pex simulation.
"""
# If we generated the bitcell, we already know where Q and Q_bar are
@ -92,7 +104,7 @@ class bitcell_base(design.design):
for text in self.gds.getTexts(layer["m1"]):
if self.storage_nets[i] == text.textString.rstrip('\x00'):
self.storage_net_offsets.append(text.coordinates[0])
for i in range(len(self.storage_net_offsets)):
self.storage_net_offsets[i] = tuple([self.gds.info["units"][0] * x for x in self.storage_net_offsets[i]])
@ -116,7 +128,7 @@ class bitcell_base(design.design):
if bl_names[i] == text.textString.rstrip('\x00'):
self.bl_offsets.append(text.coordinates[0])
found_bl.append(bl_names[i])
continue
for i in range(len(br_names)):
@ -131,16 +143,16 @@ class bitcell_base(design.design):
self.bl_offsets[i] = tuple([self.gds.info["units"][0] * x for x in self.bl_offsets[i]])
for i in range(len(self.br_offsets)):
self.br_offsets[i] = tuple([self.gds.info["units"][0] * x for x in self.br_offsets[i]])
self.br_offsets[i] = tuple([self.gds.info["units"][0] * x for x in self.br_offsets[i]])
return(self.bl_offsets, self.br_offsets, found_bl, found_br)
def get_normalized_storage_nets_offset(self):
def get_normalized_storage_nets_offset(self):
"""
Convert storage net offset to be relative to the bottom left corner
of the bitcell. This is useful for making sense of offsets outside
of the bitcell. This is useful for making sense of offsets outside
of the bitcell.
"""
"""
if OPTS.bitcell is not "pbitcell":
normalized_storage_net_offset = self.get_storage_net_offset()

View File

@ -6,39 +6,25 @@
# All rights reserved.
#
import debug
import utils
from tech import GDS, layer
from tech import cell_properties as props
import bitcell_base
class col_cap_bitcell_1rw_1r(bitcell_base.bitcell_base):
"""
todo"""
Column end cap cell.
"""
pin_names = [props.bitcell.cell_1rw1r.pin.bl0,
props.bitcell.cell_1rw1r.pin.br0,
props.bitcell.cell_1rw1r.pin.bl1,
props.bitcell.cell_1rw1r.pin.br1,
props.bitcell.cell_1rw1r.pin.vdd]
type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT",
"POWER", "GROUND"]
(width, height) = utils.get_libcell_size("col_cap_cell_1rw_1r",
GDS["unit"],
layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names,
"col_cap_cell_1rw_1r",
GDS["unit"])
def __init__(self, name=""):
# Ignore the name argument
bitcell_base.bitcell_base.__init__(self, "col_cap_cell_1rw_1r")
def __init__(self, name="col_cap_cell_1rw_1r"):
bitcell_base.bitcell_base.__init__(self, name)
debug.info(2, "Create col_cap bitcell 1rw+1r object")
self.width = col_cap_bitcell_1rw_1r.width
self.height = col_cap_bitcell_1rw_1r.height
self.pin_map = col_cap_bitcell_1rw_1r.pin_map
self.add_pin_types(self.type_list)
self.no_instances = True

View File

@ -6,8 +6,6 @@
# All rights reserved.
#
import debug
import utils
from tech import GDS, layer
from tech import cell_properties as props
import bitcell_base
@ -24,19 +22,10 @@ class dummy_bitcell(bitcell_base.bitcell_base):
props.bitcell.cell_6t.pin.wl,
props.bitcell.cell_6t.pin.vdd,
props.bitcell.cell_6t.pin.gnd]
type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
(width, height) = utils.get_libcell_size("dummy_cell_6t",
GDS["unit"],
layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "dummy_cell_6t", GDS["unit"])
def __init__(self, name=""):
# Ignore the name argument
bitcell_base.bitcell_base.__init__(self, "dummy_cell_6t")
def __init__(self, name):
super().__init__(name)
debug.info(2, "Create dummy bitcell")
self.width = dummy_bitcell.width
self.height = dummy_bitcell.height
self.pin_map = dummy_bitcell.pin_map

View File

@ -6,8 +6,6 @@
# All rights reserved.
#
import debug
import utils
from tech import GDS, layer
from tech import cell_properties as props
import bitcell_base
@ -27,23 +25,11 @@ class dummy_bitcell_1rw_1r(bitcell_base.bitcell_base):
props.bitcell.cell_1rw1r.pin.wl1,
props.bitcell.cell_1rw1r.pin.vdd,
props.bitcell.cell_1rw1r.pin.gnd]
type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT",
"INPUT", "INPUT", "POWER", "GROUND"]
(width, height) = utils.get_libcell_size("dummy_cell_1rw_1r",
GDS["unit"],
layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names,
"dummy_cell_1rw_1r",
GDS["unit"])
def __init__(self, name=""):
# Ignore the name argument
bitcell_base.bitcell_base.__init__(self, "dummy_cell_1rw_1r")
def __init__(self, name):
super().__init__(name)
debug.info(2, "Create dummy bitcell 1rw+1r object")
self.width = dummy_bitcell_1rw_1r.width
self.height = dummy_bitcell_1rw_1r.height
self.pin_map = dummy_bitcell_1rw_1r.pin_map
self.add_pin_types(self.type_list)

View File

@ -6,8 +6,6 @@
# All rights reserved.
#
import debug
import utils
from tech import GDS, layer
from tech import cell_properties as props
import bitcell_base
@ -29,21 +27,11 @@ class dummy_bitcell_1w_1r(bitcell_base.bitcell_base):
props.bitcell.cell_1w1r.pin.gnd]
type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT",
"INPUT", "INPUT", "POWER", "GROUND"]
(width, height) = utils.get_libcell_size("dummy_cell_1w_1r",
GDS["unit"],
layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names,
"dummy_cell_1w_1r",
GDS["unit"])
def __init__(self, name=""):
# Ignore the name argument
bitcell_base.bitcell_base.__init__(self, "dummy_cell_1w_1r")
def __init__(self, name):
super().__init__(name)
debug.info(2, "Create dummy bitcell 1w+1r object")
self.width = dummy_bitcell_1w_1r.width
self.height = dummy_bitcell_1w_1r.height
self.pin_map = dummy_bitcell_1w_1r.pin_map
self.add_pin_types(self.type_list)

View File

@ -7,85 +7,86 @@
#
import debug
import design
from tech import drc, spice,parameter
from vector import vector
from globals import OPTS
from sram_factory import factory
class dummy_pbitcell(design.design):
"""
Creates a replica bitcell using pbitcell
"""
def __init__(self, name):
def __init__(self, name, cell_name=None):
self.num_rw_ports = OPTS.num_rw_ports
self.num_w_ports = OPTS.num_w_ports
self.num_r_ports = OPTS.num_r_ports
self.total_ports = self.num_rw_ports + self.num_w_ports + self.num_r_ports
design.design.__init__(self, name)
debug.info(1, "create a dummy bitcell using pbitcell with {0} rw ports, {1} w ports and {2} r ports".format(self.num_rw_ports,
self.num_w_ports,
self.num_r_ports))
self.create_netlist()
self.create_layout()
self.add_boundary()
def create_netlist(self):
self.add_pins()
self.add_modules()
self.create_modules()
def create_layout(self):
self.place_pbitcell()
self.route_rbc_connections()
self.DRC_LVS()
def add_pins(self):
for port in range(self.total_ports):
self.add_pin("bl{}".format(port))
self.add_pin("br{}".format(port))
for port in range(self.total_ports):
self.add_pin("wl{}".format(port))
self.add_pin("vdd")
self.add_pin("gnd")
def add_modules(self):
self.prbc = factory.create(module_type="pbitcell",dummy_bitcell=True)
self.prbc = factory.create(module_type="pbitcell",
dummy_bitcell=True)
self.add_mod(self.prbc)
self.height = self.prbc.height
self.width = self.prbc.width
def create_modules(self):
self.prbc_inst = self.add_inst(name="pbitcell",
mod=self.prbc)
temp = []
for port in range(self.total_ports):
temp.append("bl{}".format(port))
temp.append("br{}".format(port))
for port in range(self.total_ports):
temp.append("wl{}".format(port))
temp.append("wl{}".format(port))
temp.append("vdd")
temp.append("gnd")
self.connect_inst(temp)
def place_pbitcell(self):
self.prbc_inst.place(offset=vector(0,0))
def route_rbc_connections(self):
self.prbc_inst.place(offset=vector(0, 0))
def route_rbc_connections(self):
for port in range(self.total_ports):
self.copy_layout_pin(self.prbc_inst, "bl{}".format(port))
self.copy_layout_pin(self.prbc_inst, "br{}".format(port))
for port in range(self.total_ports):
self.copy_layout_pin(self.prbc_inst, "wl{}".format(port))
self.copy_layout_pin(self.prbc_inst, "wl{}".format(port))
self.copy_layout_pin(self.prbc_inst, "vdd")
self.copy_layout_pin(self.prbc_inst, "gnd")
def get_wl_cin(self):
"""Return the relative capacitance of the access transistor gates"""
#This module is made using a pbitcell. Get the cin from that module

View File

@ -26,11 +26,11 @@ class pbitcell(bitcell_base.bitcell_base):
self.num_w_ports = OPTS.num_w_ports
self.num_r_ports = OPTS.num_r_ports
self.total_ports = self.num_rw_ports + self.num_w_ports + self.num_r_ports
self.replica_bitcell = replica_bitcell
self.dummy_bitcell = dummy_bitcell
bitcell_base.bitcell_base.__init__(self, name)
bitcell_base.bitcell_base.__init__(self, name, hard_cell=False)
fmt_str = "{0} rw ports, {1} w ports and {2} r ports"
info_string = fmt_str.format(self.num_rw_ports,
self.num_w_ports,
@ -295,7 +295,7 @@ class pbitcell(bitcell_base.bitcell_base):
self.width = -2 * self.leftmost_xpos
self.height = self.topmost_ypos - self.botmost_ypos
self.center_ypos = 0.5 * (self.topmost_ypos + self.botmost_ypos)
def create_storage(self):
"""
Creates the crossed coupled inverters that act
@ -322,7 +322,7 @@ class pbitcell(bitcell_base.bitcell_base):
self.connect_inst(["vdd", self.Q, self.Q_bar, "vdd"])
def place_storage(self):
"""
"""
Places the transistors for the crossed
coupled inverters in the bitcell
"""
@ -406,7 +406,7 @@ class pbitcell(bitcell_base.bitcell_base):
contact_offset_left_output = vector(self.inverter_nmos_left.get_pin("D").rc().x \
+ 0.5 * contact.poly.height,
self.cross_couple_upper_ypos)
contact_offset_right_output = vector(self.inverter_nmos_right.get_pin("S").lc().x \
- 0.5*contact.poly.height,
self.cross_couple_lower_ypos)
@ -421,8 +421,8 @@ class pbitcell(bitcell_base.bitcell_base):
offset=self.gnd_position,
width=self.width)
self.add_power_pin("gnd", vector(0, gnd_ypos), directions=("H", "H"))
vdd_ypos = self.inverter_nmos_ypos \
+ self.inverter_nmos.active_height \
+ self.inverter_gap \
@ -433,7 +433,7 @@ class pbitcell(bitcell_base.bitcell_base):
offset=self.vdd_position,
width=self.width)
self.add_power_pin("vdd", vector(0, vdd_ypos), directions=("H", "H"))
def create_readwrite_ports(self):
"""
Creates read/write ports to the bit cell. A differential
@ -461,7 +461,7 @@ class pbitcell(bitcell_base.bitcell_base):
if self.dummy_bitcell:
bl_name += "_noconn"
br_name += "_noconn"
# add read/write transistors
self.readwrite_nmos_left[k] = self.add_inst(name="readwrite_nmos_left{}".format(k),
mod=self.readwrite_nmos)
@ -662,7 +662,7 @@ class pbitcell(bitcell_base.bitcell_base):
if self.dummy_bitcell:
bl_name += "_noconn"
br_name += "_noconn"
# add read-access transistors
self.read_access_nmos_left[k] = self.add_inst(name="read_access_nmos_left{}".format(k),
mod=self.read_nmos)
@ -897,7 +897,7 @@ class pbitcell(bitcell_base.bitcell_base):
[self.inverter_pmos_right.get_pin("D").uc(), vdd_pos_right])
def route_readwrite_access(self):
"""
"""
Routes read/write transistors to the storage
component of the bitcell
"""
@ -917,7 +917,7 @@ class pbitcell(bitcell_base.bitcell_base):
[self.readwrite_nmos_right[k].get_pin("S").uc(), mid, Q_bar_pos])
def route_write_access(self):
"""
"""
Routes read/write transistors to the storage
component of the bitcell
"""
@ -937,7 +937,7 @@ class pbitcell(bitcell_base.bitcell_base):
[self.write_nmos_right[k].get_pin("S").uc(), mid, Q_bar_pos])
def route_read_access(self):
"""
"""
Routes read access transistors to the storage
component of the bitcell
"""
@ -1016,7 +1016,7 @@ class pbitcell(bitcell_base.bitcell_base):
offset=offset,
width=well_width,
height=well_height)
# extend nwell to encompass inverter_pmos
# calculate offset of the left pmos well
if "nwell" in layer:
@ -1024,7 +1024,7 @@ class pbitcell(bitcell_base.bitcell_base):
- self.nwell_enclose_active
inverter_well_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height \
+ self.inverter_gap - self.nwell_enclose_active
# calculate width of the two combined nwells
# calculate height to encompass nimplant connected to vdd
well_width = 2 * (self.inverter_nmos.active_width + 0.5 * self.inverter_to_inverter_spacing) \
@ -1099,18 +1099,18 @@ class pbitcell(bitcell_base.bitcell_base):
Q_bar_pos = self.inverter_pmos_right.get_pin("S").center()
vdd_pos = self.inverter_pmos_right.get_pin("D").center()
self.add_path("m1", [Q_bar_pos, vdd_pos])
def get_storage_net_names(self):
"""
Returns names of storage nodes in bitcell in
[non-inverting, inverting] format.
"""
return self.storage_nets
def get_bl_name(self, port=0):
"""Get bl name by port"""
return "bl{}".format(port)
def get_br_name(self, port=0):
"""Get bl name by port"""
return "br{}".format(port)
@ -1119,7 +1119,7 @@ class pbitcell(bitcell_base.bitcell_base):
"""Get wl name by port"""
debug.check(port < 2, "Two ports for bitcell_1rw_1r only.")
return "wl{}".format(port)
def get_stage_effort(self, load):
parasitic_delay = 1
# This accounts for bitline being drained thought the access
@ -1128,7 +1128,7 @@ class pbitcell(bitcell_base.bitcell_base):
# Assumes always a minimum sizes inverter. Could be
# specified in the tech.py file.
cin = 3
# Internal loads due to port configs are halved.
# This is to account for the size already being halved
# for stacked TXs, but internal loads do not see this size
@ -1144,10 +1144,10 @@ class pbitcell(bitcell_base.bitcell_base):
load + read_port_load,
parasitic_delay,
False)
def input_load(self):
""" Return the relative capacitance of the access transistor gates """
# FIXME: This applies to bitline capacitances as well.
# pbitcell uses the different sizing for the port access tx's. Not accounted for in this model.
access_tx_cin = self.readwrite_nmos.get_cin()
@ -1155,10 +1155,10 @@ class pbitcell(bitcell_base.bitcell_base):
def build_graph(self, graph, inst_name, port_nets):
"""Adds edges to graph for pbitcell. Only readwrite and read ports."""
if self.dummy_bitcell:
return
pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
# Edges added wl->bl, wl->br for every port except write ports
rw_pin_names = zip(self.r_wl_names, self.r_bl_names, self.r_br_names)

View File

@ -5,15 +5,14 @@
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import design
import debug
import utils
from tech import GDS,layer,drc,parameter,cell_properties
import bitcell_base
from tech import cell_properties as props
from tech import parameter, drc
import logical_effort
from globals import OPTS
class replica_bitcell(design.design):
class replica_bitcell(bitcell_base.bitcell_base):
"""
A single bit cell (6T, 8T, etc.)
This module implements the single memory cell used in the design. It
@ -26,46 +25,33 @@ class replica_bitcell(design.design):
props.bitcell.cell_6t.pin.vdd,
props.bitcell.cell_6t.pin.gnd]
type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
if not OPTS.netlist_only:
(width,height) = utils.get_libcell_size("replica_cell_6t", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_6t", GDS["unit"])
else:
(width,height) = (0,0)
pin_map = []
def __init__(self, name=""):
# Ignore the name argument
design.design.__init__(self, "replica_cell_6t")
def __init__(self, name):
super().__init__(name)
debug.info(2, "Create replica bitcell object")
self.width = replica_bitcell.width
self.height = replica_bitcell.height
self.pin_map = replica_bitcell.pin_map
self.add_pin_types(self.type_list)
def get_stage_effort(self, load):
parasitic_delay = 1
size = 0.5 #This accounts for bitline being drained thought the access TX and internal node
cin = 3 #Assumes always a minimum sizes inverter. Could be specified in the tech.py file.
read_port_load = 0.5 #min size NMOS gate load
return logical_effort.logical_effort('bitline', size, cin, load+read_port_load, parasitic_delay, False)
size = 0.5 # This accounts for bitline being drained thought the access TX and internal node
cin = 3 # Assumes always a minimum sizes inverter. Could be specified in the tech.py file.
read_port_load = 0.5 # min size NMOS gate load
return logical_effort.logical_effort('bitline', size, cin, load + read_port_load, parasitic_delay, False)
def input_load(self):
"""Return the relative capacitance of the access transistor gates"""
# FIXME: This applies to bitline capacitances as well.
access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"]
return 2*access_tx_cin
access_tx_cin = parameter["6T_access_size"] / drc["minwidth_tx"]
return 2 * access_tx_cin
def analytical_power(self, corner, load):
"""Bitcell power in nW. Only characterizes leakage."""
from tech import spice
leakage = spice["bitcell_leakage"]
dynamic = 0 #temporary
dynamic = 0 # FIXME
total_power = self.return_power(dynamic, leakage)
return total_power
def build_graph(self, graph, inst_name, port_nets):
def build_graph(self, graph, inst_name, port_nets):
"""Adds edges based on inputs/outputs. Overrides base class function."""
self.add_graph_edges(graph, port_nets)

View File

@ -5,13 +5,14 @@
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import design
import debug
import utils
from tech import GDS,layer,drc,parameter
import bitcell_base
from tech import cell_properties as props
from tech import parameter, drc
import logical_effort
class replica_bitcell_1rw_1r(design.design):
class replica_bitcell_1rw_1r(bitcell_base.bitcell_base):
"""
A single bit cell which is forced to store a 0.
This module implements the single memory cell used in the design. It
@ -26,42 +27,33 @@ class replica_bitcell_1rw_1r(design.design):
props.bitcell.cell_1rw1r.pin.wl1,
props.bitcell.cell_1rw1r.pin.vdd,
props.bitcell.cell_1rw1r.pin.gnd]
type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"]
type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"]
(width,height) = utils.get_libcell_size("replica_cell_1rw_1r", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_1rw_1r", GDS["unit"])
def __init__(self, name=""):
# Ignore the name argument
design.design.__init__(self, "replica_cell_1rw_1r")
def __init__(self, name):
super().__init__(name)
debug.info(2, "Create replica bitcell 1rw+1r object")
self.width = replica_bitcell_1rw_1r.width
self.height = replica_bitcell_1rw_1r.height
self.pin_map = replica_bitcell_1rw_1r.pin_map
self.add_pin_types(self.type_list)
def get_stage_effort(self, load):
parasitic_delay = 1
size = 0.5 #This accounts for bitline being drained thought the access TX and internal node
cin = 3 #Assumes always a minimum sizes inverter. Could be specified in the tech.py file.
read_port_load = 0.5 #min size NMOS gate load
return logical_effort.logical_effort('bitline', size, cin, load+read_port_load, parasitic_delay, False)
size = 0.5 # This accounts for bitline being drained thought the access TX and internal node
cin = 3 # Assumes always a minimum sizes inverter. Could be specified in the tech.py file.
read_port_load = 0.5 # min size NMOS gate load
return logical_effort.logical_effort('bitline', size, cin, load + read_port_load, parasitic_delay, False)
def input_load(self):
"""Return the relative capacitance of the access transistor gates"""
# FIXME: This applies to bitline capacitances as well.
# FIXME: sizing is not accurate with the handmade cell. Change once cell widths are fixed.
access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"]
return 2*access_tx_cin
access_tx_cin = parameter["6T_access_size"] / drc["minwidth_tx"]
return 2 * access_tx_cin
def build_graph(self, graph, inst_name, port_nets):
"""Adds edges to graph. Multiport bitcell timing graph is too complex
to use the add_graph_edges function."""
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
pins = props.bitcell.cell_1rw1r.pin
#Edges hardcoded here. Essentially wl->bl/br for both ports.
# Edges hardcoded here. Essentially wl->bl/br for both ports.
# Port 0 edges
graph.add_edge(pin_dict[pins.wl0], pin_dict[pins.bl0], self)
graph.add_edge(pin_dict[pins.wl0], pin_dict[pins.br0], self)

View File

@ -5,13 +5,14 @@
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import design
import debug
import utils
from tech import GDS,layer,drc,parameter
import bitcell_base
from tech import cell_properties as props
from tech import parameter, drc
import logical_effort
class replica_bitcell_1w_1r(design.design):
class replica_bitcell_1w_1r(bitcell_base.bitcell_base):
"""
A single bit cell which is forced to store a 0.
This module implements the single memory cell used in the design. It
@ -26,43 +27,34 @@ class replica_bitcell_1w_1r(design.design):
props.bitcell.cell_1w1r.pin.wl1,
props.bitcell.cell_1w1r.pin.vdd,
props.bitcell.cell_1w1r.pin.gnd]
type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"]
type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"]
(width,height) = utils.get_libcell_size("replica_cell_1w_1r", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_1w_1r", GDS["unit"])
def __init__(self, name=""):
# Ignore the name argument
design.design.__init__(self, "replica_cell_1w_1r")
def __init__(self, name):
super().__init__(name)
debug.info(2, "Create replica bitcell 1w+1r object")
self.width = replica_bitcell_1w_1r.width
self.height = replica_bitcell_1w_1r.height
self.pin_map = replica_bitcell_1w_1r.pin_map
self.add_pin_types(self.type_list)
def get_stage_effort(self, load):
parasitic_delay = 1
size = 0.5 #This accounts for bitline being drained thought the access TX and internal node
cin = 3 #Assumes always a minimum sizes inverter. Could be specified in the tech.py file.
read_port_load = 0.5 #min size NMOS gate load
return logical_effort.logical_effort('bitline', size, cin, load+read_port_load, parasitic_delay, False)
size = 0.5 # This accounts for bitline being drained thought the access TX and internal node
cin = 3 # Assumes always a minimum sizes inverter. Could be specified in the tech.py file.
read_port_load = 0.5 # min size NMOS gate load
return logical_effort.logical_effort('bitline', size, cin, load + read_port_load, parasitic_delay, False)
def input_load(self):
"""Return the relative capacitance of the access transistor gates"""
# FIXME: This applies to bitline capacitances as well.
# FIXME: sizing is not accurate with the handmade cell. Change once cell widths are fixed.
access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"]
return 2*access_tx_cin
access_tx_cin = parameter["6T_access_size"] / drc["minwidth_tx"]
return 2 * access_tx_cin
def build_graph(self, graph, inst_name, port_nets):
"""Adds edges to graph. Multiport bitcell timing graph is too complex
to use the add_graph_edges function."""
debug.info(1,'Adding edges for {}'.format(inst_name))
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
debug.info(1, 'Adding edges for {}'.format(inst_name))
pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
pins = props.bitcell.cell_1w1r.pin
#Edges hardcoded here. Essentially wl->bl/br for the read port.
# Edges hardcoded here. Essentially wl->bl/br for the read port.
# Port 1 edges
graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.bl1], self)
graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.br1], self)

View File

@ -7,82 +7,85 @@
#
import debug
import design
from tech import drc, spice,parameter
from vector import vector
from globals import OPTS
from sram_factory import factory
class replica_pbitcell(design.design):
"""
Creates a replica bitcell using pbitcell
"""
def __init__(self, name):
def __init__(self, name, cell_name=None):
if not cell_name:
cell_name = name
self.num_rw_ports = OPTS.num_rw_ports
self.num_w_ports = OPTS.num_w_ports
self.num_r_ports = OPTS.num_r_ports
self.total_ports = self.num_rw_ports + self.num_w_ports + self.num_r_ports
design.design.__init__(self, name)
design.design.__init__(self, name, cell_name)
debug.info(1, "create a replica bitcell using pbitcell with {0} rw ports, {1} w ports and {2} r ports".format(self.num_rw_ports,
self.num_w_ports,
self.num_r_ports))
self.create_netlist()
self.create_layout()
self.add_boundary()
def create_netlist(self):
self.add_pins()
self.add_modules()
self.create_modules()
def create_layout(self):
self.place_pbitcell()
self.route_rbc_connections()
self.DRC_LVS()
def add_pins(self):
for port in range(self.total_ports):
self.add_pin("bl{}".format(port))
self.add_pin("br{}".format(port))
for port in range(self.total_ports):
self.add_pin("wl{}".format(port))
self.add_pin("vdd")
self.add_pin("gnd")
def add_modules(self):
self.prbc = factory.create(module_type="pbitcell",replica_bitcell=True)
self.prbc = factory.create(module_type="pbitcell",
replica_bitcell=True)
self.add_mod(self.prbc)
self.height = self.prbc.height
self.width = self.prbc.width
def create_modules(self):
self.prbc_inst = self.add_inst(name="pbitcell",
mod=self.prbc)
temp = []
for port in range(self.total_ports):
temp.append("bl{}".format(port))
temp.append("br{}".format(port))
for port in range(self.total_ports):
temp.append("wl{}".format(port))
temp.append("wl{}".format(port))
temp.append("vdd")
temp.append("gnd")
self.connect_inst(temp)
def place_pbitcell(self):
self.prbc_inst.place(offset=vector(0,0))
def route_rbc_connections(self):
self.prbc_inst.place(offset=vector(0, 0))
def route_rbc_connections(self):
for port in range(self.total_ports):
self.copy_layout_pin(self.prbc_inst, "bl{}".format(port))
self.copy_layout_pin(self.prbc_inst, "br{}".format(port))
for port in range(self.total_ports):
self.copy_layout_pin(self.prbc_inst, "wl{}".format(port))
self.copy_layout_pin(self.prbc_inst, "wl{}".format(port))
self.copy_layout_pin(self.prbc_inst, "vdd")
self.copy_layout_pin(self.prbc_inst, "gnd")

View File

@ -6,39 +6,22 @@
# All rights reserved.
#
import debug
import utils
from tech import GDS, layer
from tech import cell_properties as props
import bitcell_base
class row_cap_bitcell_1rw_1r(bitcell_base.bitcell_base):
"""
A single bit cell which is forced to store a 0.
This module implements the single memory cell used in the design. It
is a hand-made cell, so the layout and netlist should be available in
the technology library. """
Row end cap cell.
"""
pin_names = [props.bitcell.cell_1rw1r.pin.wl0,
props.bitcell.cell_1rw1r.pin.wl1,
props.bitcell.cell_1rw1r.pin.gnd]
type_list = ["INPUT", "INPUT", "GROUND"]
(width, height) = utils.get_libcell_size("row_cap_cell_1rw_1r",
GDS["unit"],
layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names,
"row_cap_cell_1rw_1r",
GDS["unit"])
def __init__(self, name=""):
# Ignore the name argument
bitcell_base.bitcell_base.__init__(self, "row_cap_cell_1rw_1r")
def __init__(self, name="row_cap_cell_1rw_1r"):
bitcell_base.bitcell_base.__init__(self, name)
debug.info(2, "Create row_cap bitcell 1rw+1r object")
self.width = row_cap_bitcell_1rw_1r.width
self.height = row_cap_bitcell_1rw_1r.height
self.pin_map = row_cap_bitcell_1rw_1r.pin_map
self.add_pin_types(self.type_list)
self.no_instances = True

View File

@ -30,10 +30,10 @@ if not OPTS.analytical_delay:
else:
(OPTS.spice_name,OPTS.spice_exe) = get_tool("spice",["hspice", "ngspice", "ngspice.exe", "xa"])
# set the input dir for spice files if using ngspice
# set the input dir for spice files if using ngspice
if OPTS.spice_name == "ngspice":
os.environ["NGSPICE_INPUT_DIR"] = "{0}".format(OPTS.openram_temp)
if OPTS.spice_exe == "":
debug.error("No recognizable spice version found. Unable to perform characterization.",1)
else:

View File

@ -11,4 +11,4 @@ from enum import Enum
class bit_polarity(Enum):
NONINVERTING = 0
INVERTING = 1

View File

@ -9,7 +9,7 @@ import re
import debug
from globals import OPTS
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)
@ -37,7 +37,7 @@ def parse_spice_list(filename, key):
return convert_to_float(val.group(1))
else:
return "Failed"
def round_time(time,time_precision=3):
# times are in ns, so this is how many digits of precision
# 3 digits = 1ps
@ -58,10 +58,10 @@ def convert_to_float(number):
"""Converts a string into a (float) number; also converts units(m,u,n,p)"""
if number == "Failed":
return False
# start out with a binary value
float_value = False
try:
try:
# checks if string is a float without letter units
float_value = float(number)
except ValueError:
@ -69,7 +69,7 @@ def convert_to_float(number):
unit = re.search(r"(-?\d+\.?\d*)e(\-?\+?\d+)", number)
if unit != None:
float_value=float(unit.group(1)) * (10 ^ float(unit.group(2)))
# see if it is in spice notation
unit = re.search(r"(-?\d+\.?\d*)(m?u?n?p?f?)", number)
if unit != None:

File diff suppressed because it is too large Load Diff

View File

@ -21,13 +21,24 @@ class functional(simulation):
for successful SRAM operation.
"""
def __init__(self, sram, spfile, corner, cycles=15):
def __init__(self, sram, spfile, corner=None, cycles=15, period=None, output_path=None):
super().__init__(sram, spfile, corner)
# Seed the characterizer with a constant seed for unit tests
if OPTS.is_unit_test:
random.seed(12345)
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:
self.output_path = output_path
if self.write_size:
self.num_wmasks = int(math.ceil(self.word_size / self.write_size))
else:
@ -50,7 +61,7 @@ class functional(simulation):
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))
# Number of checks can be changed
self.num_cycles = cycles
# This is to have ordered keys for random selection
@ -58,21 +69,20 @@ class functional(simulation):
self.read_check = []
self.read_results = []
def run(self, feasible_period=None):
if feasible_period: # period defaults to tech.py feasible period otherwise.
self.period = feasible_period
# Generate a random sequence of reads and writes
self.create_random_memory_sequence()
# Run SPICE simulation
# Write SPICE simulation
self.write_functional_stimulus()
self.stim.run_sim()
def run(self):
self.stim.run_sim(self.stim_sp)
# read dout values from SPICE simulation. If the values do not fall within the noise margins, return the error.
(success, error) = self.read_stim_results()
if not success:
return (0, error)
# Check read values with written values. If the values do not match, return an error.
return self.check_stim_results()
@ -94,7 +104,7 @@ class functional(simulation):
len(val),
port,
name))
def create_random_memory_sequence(self):
if self.write_size:
rw_ops = ["noop", "write", "partial_write", "read"]
@ -123,7 +133,7 @@ class functional(simulation):
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
@ -138,7 +148,7 @@ class functional(simulation):
self.cycle_times.append(self.t_current)
self.t_current += self.period
self.check_lengths()
# 3. Perform a random sequence of writes and reads on random
# ports, using random addresses and random words and random
# write masks (if applicable)
@ -151,7 +161,7 @@ class functional(simulation):
op = random.choice(w_ops)
else:
op = random.choice(r_ops)
if op == "noop":
self.add_noop_one_port(port)
elif op == "write":
@ -191,10 +201,10 @@ class functional(simulation):
comment = self.gen_cycle_comment("read", 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.cycle_times.append(self.t_current)
self.t_current += self.period
# Last cycle idle needed to correctly measure the value on the second to last clock edge
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)
@ -203,7 +213,7 @@ class functional(simulation):
""" Create the masked data word """
# Start with the new word
new_word = word
# When the write mask's bits are 0, the old data values should appear in the new word
# as to not overwrite the old values
for bit in range(len(wmask)):
@ -211,9 +221,9 @@ class functional(simulation):
lower = bit * self.write_size
upper = lower + self.write_size - 1
new_word = new_word[:lower] + old_word[lower:upper + 1] + new_word[upper + 1:]
return new_word
def add_read_check(self, word, port):
""" Add to the check array to ensure a read works. """
try:
@ -222,7 +232,7 @@ class functional(simulation):
self.check = 0
self.read_check.append([word, "{0}{1}".format(self.dout_name, port), self.t_current + self.period, self.check])
self.check += 1
def read_stim_results(self):
# Extract dout values from spice timing.lis
for (word, dout_port, eo_period, check) in self.read_check:
@ -247,12 +257,12 @@ class functional(simulation):
bit,
value,
eo_period)
return (0, error)
self.read_results.append([sp_read_value, dout_port, eo_period, check])
self.read_results.append([sp_read_value, dout_port, eo_period, check])
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]:
@ -307,14 +317,14 @@ class functional(simulation):
random_value = random.randint(0, ((2 ** (self.addr_size - 1) - 1)) + (self.num_spare_rows * self.words_per_row))
addr_bits = self.convert_to_bin(random_value, True)
return addr_bits
def get_data(self):
""" Gets an available address and corresponding word. """
# Used for write masks since they should be writing to previously written addresses
addr = random.choice(list(self.stored_words.keys()))
word = self.stored_words[addr]
return (addr, word)
def convert_to_bin(self, value, is_addr):
""" Converts addr & word to usable binary values. """
new_value = str.replace(bin(value), "0b", "")
@ -324,13 +334,14 @@ class functional(simulation):
expected_value = self.word_size + self.num_spare_cols
for i in range(expected_value - len(new_value)):
new_value = "0" + new_value
# print("Binary Conversion: {} to {}".format(value, new_value))
return new_value
def write_functional_stimulus(self):
""" Writes SPICE stimulus. """
temp_stim = "{0}/stim.sp".format(OPTS.openram_temp)
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.stim = stimuli(self.sf, self.corner)
@ -341,7 +352,7 @@ class functional(simulation):
# Write Vdd/Gnd statements
self.sf.write("\n* Global Power Supplies\n")
self.stim.write_supply()
# Instantiate the SRAM
self.sf.write("\n* Instantiation of the SRAM\n")
self.stim.inst_model(pins=self.pins,
@ -361,19 +372,19 @@ class functional(simulation):
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))
# 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))
# Generate data input bits
self.sf.write("\n* Generation of data and address signals\n")
for port in self.write_ports:
for bit in range(self.word_size + self.num_spare_cols):
sig_name="{0}{1}_{2} ".format(self.din_name, port, bit)
self.stim.gen_pwl(sig_name, self.cycle_times, self.data_values[port][bit], self.period, self.slew, 0.05)
# Generate address bits
for port in self.all_ports:
for bit in range(self.addr_size):
@ -384,7 +395,7 @@ class functional(simulation):
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)
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)
@ -417,7 +428,7 @@ class functional(simulation):
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:
@ -428,10 +439,10 @@ class functional(simulation):
dout="{0}_{1}".format(dout_port, bit),
t_intital=t_intital,
t_final=t_final)
self.stim.write_control(self.cycle_times[-1] + self.period)
self.sf.close()
#FIXME: Similar function to delay.py, refactor this
def get_bit_name(self):
""" Get a bit cell name """
@ -444,4 +455,4 @@ class functional(simulation):
return (q_name, qbar_name)

View File

@ -18,21 +18,21 @@ from globals import OPTS
class lib:
""" lib file generation."""
def __init__(self, out_dir, sram, sp_file, use_model=OPTS.analytical_delay):
self.out_dir = out_dir
self.sram = sram
self.sp_file = sp_file
self.sp_file = sp_file
self.use_model = use_model
self.set_port_indices()
self.prepare_tables()
self.create_corners()
self.characterize_corners()
def set_port_indices(self):
"""Copies port information set in the SRAM instance"""
self.total_port_num = len(self.sram.all_ports)
@ -40,7 +40,7 @@ class lib:
self.readwrite_ports = self.sram.readwrite_ports
self.read_ports = self.sram.read_ports
self.write_ports = self.sram.write_ports
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
@ -48,12 +48,12 @@ class lib:
self.load = tech.spice["dff_in_cap"]
self.loads = self.load_scales * self.load
debug.info(1, "Loads: {0}".format(self.loads))
self.slew_scales = np.array(OPTS.slew_scales)
self.slew = tech.spice["rise_time"]
self.slews = self.slew_scales * self.slew
debug.info(1, "Slews: {0}".format(self.slews))
def create_corners(self):
""" Create corners for characterization. """
# Get the corners from the options file
@ -71,7 +71,7 @@ class lib:
min_process = "FF"
nom_process = "TT"
max_process = "SS"
self.corners = []
self.lib_files = []
@ -103,15 +103,15 @@ class lib:
temp)
self.corner_name = self.corner_name.replace(".","p") # Remove decimals
lib_name = self.out_dir+"{}.lib".format(self.corner_name)
# A corner is a tuple of PVT
self.corners.append((proc, volt, temp))
self.lib_files.append(lib_name)
def characterize_corners(self):
""" Characterize the list of corners. """
debug.info(1,"Characterizing corners: " + str(self.corners))
debug.info(1,"Characterizing corners: " + str(self.corners))
for (self.corner,lib_name) in zip(self.corners,self.lib_files):
debug.info(1,"Corner: " + str(self.corner))
(self.process, self.voltage, self.temperature) = self.corner
@ -121,7 +121,7 @@ class lib:
self.characterize()
self.lib.close()
self.parse_info(self.corner,lib_name)
def characterize(self):
""" Characterize the current corner. """
@ -130,8 +130,8 @@ class lib:
self.compute_setup_hold()
self.write_header()
# Loop over all ports.
# Loop over all ports.
for port in self.all_ports:
# set the read and write port as inputs.
self.write_data_bus(port)
@ -143,7 +143,7 @@ class lib:
self.write_clk_timing_power(port)
self.write_footer()
def write_footer(self):
""" Write the footer """
self.lib.write(" }\n") #Closing brace for the cell
@ -154,13 +154,13 @@ class lib:
self.lib.write("library ({0}_lib)".format(self.corner_name))
self.lib.write("{\n")
self.lib.write(" delay_model : \"table_lookup\";\n")
self.write_units()
self.write_defaults()
self.write_LUT_templates()
self.lib.write(" default_operating_conditions : OC; \n")
self.write_bus()
self.lib.write("cell ({0})".format(self.sram.name))
@ -182,7 +182,7 @@ class lib:
control_str = 'csb0' #assume at least 1 port
for i in range(1, self.total_port_num):
control_str += ' & csb{0}'.format(i)
# Leakage is included in dynamic when macro is enabled
self.lib.write(" leakage_power () {\n")
# 'when' condition unnecessary when cs pin does not turn power to devices
@ -190,15 +190,15 @@ class lib:
self.lib.write(" value : {};\n".format(self.char_sram_results["leakage_power"]))
self.lib.write(" }\n")
self.lib.write(" cell_leakage_power : {};\n".format(self.char_sram_results["leakage_power"]))
def write_units(self):
""" Adds default units for time, voltage, current,...
Valid values are 1mV, 10mV, 100mV, and 1V.
Valid values are 1mV, 10mV, 100mV, and 1V.
For time: Valid values are 1ps, 10ps, 100ps, and 1ns.
For power: Valid values are 1mW, 100uW (for 100mW), 10uW (for 10mW),
For power: Valid values are 1mW, 100uW (for 100mW), 10uW (for 10mW),
1uW (for 1mW), 100nW, 10nW, 1nW, 100pW, 10pW, and 1pW.
"""
"""
self.lib.write(" time_unit : \"1ns\" ;\n")
self.lib.write(" voltage_unit : \"1V\" ;\n")
self.lib.write(" current_unit : \"1mA\" ;\n")
@ -214,7 +214,7 @@ class lib:
def write_defaults(self):
""" Adds default values for slew and capacitance."""
self.lib.write(" input_threshold_pct_fall : 50.0 ;\n")
self.lib.write(" output_threshold_pct_fall : 50.0 ;\n")
self.lib.write(" input_threshold_pct_rise : 50.0 ;\n")
@ -255,7 +255,7 @@ class lib:
formatted_rows = list(map(self.create_list,split_values))
formatted_array = ",\\\n".join(formatted_rows)
return formatted_array
def write_index(self, number, values):
""" Write the index """
quoted_string = self.create_list(values)
@ -267,10 +267,10 @@ class lib:
# indent each newline plus extra spaces for word values
indented_string = quoted_string.replace('\n', '\n' + indent +" ")
self.lib.write("{0}values({1});\n".format(indent,indented_string))
def write_LUT_templates(self):
""" Adds lookup_table format (A 1x1 lookup_table)."""
Tran = ["CELL_TABLE"]
for i in Tran:
self.lib.write(" lu_table_template({0})".format(i))
@ -278,8 +278,8 @@ class lib:
self.lib.write(" variable_1 : input_net_transition;\n")
self.lib.write(" variable_2 : total_output_net_capacitance;\n")
self.write_index(1,self.slews)
# Dividing by 1000 to all cap values since output of .sp is in fF,
# and it needs to be in pF for Innovus.
# Dividing by 1000 to all cap values since output of .sp is in fF,
# and it needs to be in pF for Innovus.
self.write_index(2,self.loads/1000)
self.lib.write(" }\n\n")
@ -292,12 +292,12 @@ class lib:
self.write_index(1,self.slews)
self.write_index(2,self.slews)
self.lib.write(" }\n\n")
# self.lib.write(" lu_table_template(CLK_TRAN) {\n")
# self.lib.write(" variable_1 : constrained_pin_transition;\n")
# self.write_index(1,self.slews)
# self.lib.write(" }\n\n")
# self.lib.write(" lu_table_template(TRAN) {\n")
# self.lib.write(" variable_1 : total_output_net_capacitance;\n")
# self.write_index(1,self.slews)
@ -311,10 +311,10 @@ class lib:
# #self.write_index(1,self.slews)
# self.write_index(1,[self.slews[0]])
# self.lib.write(" }\n\n")
def write_bus(self):
""" Adds format of data and addr bus."""
self.lib.write("\n\n")
self.lib.write(" type (data){\n")
self.lib.write(" base_type : array;\n")
@ -378,11 +378,11 @@ class lib:
self.lib.write(" direction : output; \n")
# This is conservative, but limit to range that we characterized.
self.lib.write(" max_capacitance : {0}; \n".format(max(self.loads)/1000))
self.lib.write(" min_capacitance : {0}; \n".format(min(self.loads)/1000))
self.lib.write(" min_capacitance : {0}; \n".format(min(self.loads)/1000))
self.lib.write(" memory_read(){ \n")
self.lib.write(" address : addr{0}; \n".format(read_port))
self.lib.write(" }\n")
self.lib.write(" pin(dout{0}[{1}:0]){{\n".format(read_port,self.sram.word_size-1))
self.lib.write(" timing(){ \n")
@ -402,7 +402,7 @@ class lib:
self.write_values(self.char_port_results[read_port]["slew_hl"],len(self.loads)," ")
self.lib.write(" }\n") # fall trans
self.lib.write(" }\n") # timing
self.lib.write(" }\n") # pin
self.lib.write(" }\n") # pin
self.lib.write(" }\n\n") # bus
def write_data_bus_input(self, write_port):
@ -416,10 +416,10 @@ class lib:
self.lib.write(" memory_write(){ \n")
self.lib.write(" address : addr{0}; \n".format(write_port))
self.lib.write(" clocked_on : clk{0}; \n".format(write_port))
self.lib.write(" }\n")
self.lib.write(" }\n")
self.lib.write(" pin(din{0}[{1}:0]){{\n".format(write_port,self.sram.word_size-1))
self.write_FF_setuphold(write_port)
self.lib.write(" }\n") # pin
self.lib.write(" }\n") # pin
self.lib.write(" }\n") #bus
def write_data_bus(self, port):
@ -431,7 +431,7 @@ class lib:
def write_addr_bus(self, port):
""" Adds addr bus timing results."""
self.lib.write(" bus(addr{0}){{\n".format(port))
self.lib.write(" bus_type : addr; \n")
self.lib.write(" direction : input; \n")
@ -439,9 +439,9 @@ class lib:
self.lib.write(" max_transition : {0};\n".format(self.slews[-1]))
self.lib.write(" pin(addr{0}[{1}:0])".format(port,self.sram.addr_size-1))
self.lib.write("{\n")
self.write_FF_setuphold(port)
self.lib.write(" }\n")
self.lib.write(" }\n")
self.lib.write(" }\n\n")
def write_wmask_bus(self, port):
@ -465,7 +465,7 @@ class lib:
ctrl_pin_names = ["csb{0}".format(port)]
if port in self.readwrite_ports:
ctrl_pin_names.append("web{0}".format(port))
for i in ctrl_pin_names:
self.lib.write(" pin({0})".format(i))
self.lib.write("{\n")
@ -508,12 +508,12 @@ class lib:
self.lib.write(" }\n")
self.lib.write(" }\n")
self.lib.write(" }\n\n")
def add_clk_control_power(self, port):
"""Writes powers under the clock pin group for a specified port"""
#Web added to read/write ports. Likely to change when control logic finished.
web_name = ""
if port in self.write_ports:
if port in self.read_ports:
web_name = " & !web{0}".format(port)
@ -556,7 +556,7 @@ class lib:
self.lib.write(" values(\"{0:.6e}\");\n".format(read0_power))
self.lib.write(" }\n")
self.lib.write(" }\n")
# Disabled power.
disabled_read1_power = np.mean(self.char_port_results[port]["disabled_read1_power"])
disabled_read0_power = np.mean(self.char_port_results[port]["disabled_read0_power"])
@ -585,7 +585,7 @@ class lib:
self.d = delay(self.sram, self.sp_file, self.corner)
if self.use_model:
char_results = self.d.analytical_delay(self.slews,self.loads)
self.char_sram_results, self.char_port_results = char_results
self.char_sram_results, self.char_port_results = char_results
else:
if (self.sram.num_spare_rows == 0):
probe_address = "1" * self.sram.addr_size
@ -593,8 +593,8 @@ class lib:
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)
self.char_sram_results, self.char_port_results = char_results
self.char_sram_results, self.char_port_results = char_results
def compute_setup_hold(self):
""" Do the analysis if we haven't characterized a FF yet """
# Do the analysis if we haven't characterized a FF yet
@ -604,8 +604,8 @@ class lib:
self.times = self.sh.analytical_setuphold(self.slews,self.loads)
else:
self.times = self.sh.analyze(self.slews,self.slews)
def parse_info(self,corner,lib_name):
""" Copies important characterization data to datasheet.info to be added to datasheet """
if OPTS.is_unit_test:
@ -617,9 +617,9 @@ class lib:
proc = subprocess.Popen(['git','rev-parse','HEAD'], cwd=os.path.abspath(os.environ.get("OPENRAM_HOME")) + '/', stdout=subprocess.PIPE)
git_id = str(proc.stdout.read())
try:
git_id = git_id[2:-3]
git_id = git_id[2:-3]
except:
pass
# check if git id is valid
@ -628,7 +628,7 @@ class lib:
git_id = 'Failed to retruieve'
datasheet = open(OPTS.openram_temp +'/datasheet.info', 'a+')
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(
@ -654,10 +654,10 @@ class lib:
# information of checks
# run it only the first time
datasheet.write("{0},{1},".format(self.sram.drc_errors, self.sram.lvs_errors))
# write area
datasheet.write(str(self.sram.width * self.sram.height) + ',')
# write timing information for all ports
for port in self.all_ports:
#din timings
@ -675,7 +675,7 @@ class lib:
min(list(map(round_time,self.times["hold_times_HL"]))),
max(list(map(round_time,self.times["hold_times_HL"])))
))
for port in self.all_ports:
@ -695,7 +695,7 @@ 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"])))
))
for port in self.all_ports:
@ -791,9 +791,9 @@ class lib:
control_str = 'csb0'
for i in range(1, self.total_port_num):
control_str += ' & csb{0}'.format(i)
datasheet.write("{0},{1},{2},".format('leak', control_str, self.char_sram_results["leakage_power"]))
datasheet.write("END\n")
datasheet.close()

View File

@ -17,7 +17,7 @@ class logical_effort():
min_inv_cin = 1+beta
pinv=parameter["min_inv_para_delay"]
tau = parameter['le_tau']
def __init__(self, name, size, cin, cout, parasitic, out_is_rise=True):
self.name = name
self.cin = cin
@ -26,31 +26,31 @@ class logical_effort():
self.electrical_effort = self.cout/self.cin
self.parasitic_scale = parasitic
self.is_rise = out_is_rise
def __str__(self):
return "Name={}, g={}, h={}, p={}*pinv, rise_delay={}".format(self.name,
self.logical_effort,
self.electrical_effort,
self.parasitic_scale,
self.is_rise
)
)
def get_stage_effort(self):
return self.logical_effort*self.electrical_effort
def get_parasitic_delay(self):
return logical_effort.pinv*self.parasitic_scale
def get_stage_delay(self):
return self.get_stage_effort()+self.get_parasitic_delay()
def get_absolute_delay(self):
return logical_effort.tau*self.get_stage_delay()
def calculate_delays(stage_effort_list):
"""Convert stage effort objects to list of delay values"""
return [stage.get_stage_delay() for stage in stage_effort_list]
def calculate_relative_delay(stage_effort_list):
"""Calculates the total delay of a given delay path made of a list of logical effort objects."""
total_rise_delay, total_fall_delay = calculate_relative_rise_fall_delays(stage_effort_list)
@ -62,7 +62,7 @@ def calculate_absolute_delay(stage_effort_list):
for stage in stage_effort_list:
total_delay+=stage.get_absolute_delay()
return total_delay
def calculate_relative_rise_fall_delays(stage_effort_list):
"""Calculates the rise/fall delays of a given delay path made of a list of logical effort objects."""
debug.info(2, "Calculating rise/fall relative delays")
@ -74,11 +74,11 @@ def calculate_relative_rise_fall_delays(stage_effort_list):
else:
total_fall_delay += stage.get_stage_delay()
return total_rise_delay, total_fall_delay
def convert_farad_to_relative_c(c_farad):
"""Converts capacitance in Femto-Farads to relative capacitance."""
return c_farad*parameter['cap_relative_per_ff']
def convert_relative_c_to_farad(c_relative):
"""Converts capacitance in logical effort relative units to Femto-Farads."""
return c_relative/parameter['cap_relative_per_ff']
return c_relative/parameter['cap_relative_per_ff']

View File

@ -19,63 +19,63 @@ class spice_measurement(ABC):
self.measure_scale = measure_scale
self.has_port = has_port #Needed for error checking
#Some meta values used externally. variables are added here for consistency accross the objects
self.meta_str = None
self.meta_str = None
self.meta_add_delay = False
@abstractmethod
def get_measure_function(self):
return None
return None
@abstractmethod
def get_measure_values(self):
return None
def write_measure(self, stim_obj, input_tuple):
measure_func = self.get_measure_function()
if measure_func == None:
debug.error("Did not set measure function",1)
measure_vals = self.get_measure_values(*input_tuple)
measure_func(stim_obj, *measure_vals)
def retrieve_measure(self, port=None):
self.port_error_check(port)
if port != None:
value = parse_spice_list("timing", "{0}{1}".format(self.name.lower(), port))
value = parse_spice_list("timing", "{0}{1}".format(self.name.lower(), port))
else:
value = parse_spice_list("timing", "{0}".format(self.name.lower()))
if type(value)!=float or self.measure_scale == None:
value = parse_spice_list("timing", "{0}".format(self.name.lower()))
if type(value)!=float or self.measure_scale == None:
return value
else:
return value*self.measure_scale
def port_error_check(self, port):
if self.has_port and port == None:
debug.error("Cannot retrieve measurement, port input was expected.",1)
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):
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)
def get_measure_function(self):
return stimuli.gen_meas_delay
def set_meas_constants(self, trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd):
"""Set the constants for this measurement: signal names, directions, and trigger scales"""
self.trig_dir_str = trig_dir_str
self.targ_dir_str = targ_dir_str
self.trig_val_of_vdd = trig_vdd
self.trig_val_of_vdd = trig_vdd
self.targ_val_of_vdd = targ_vdd
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
def get_measure_values(self, trig_td, targ_td, vdd_voltage, port=None):
def get_measure_values(self, trig_td, targ_td, vdd_voltage, port=None):
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
self.port_error_check(port)
trig_val = self.trig_val_of_vdd * vdd_voltage
@ -90,74 +90,74 @@ 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):
class slew_measure(delay_measure):
def __init__(self, measure_name, signal_name, slew_dir_str, measure_scale=None, has_port=True):
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
self.set_meas_constants(signal_name, slew_dir_str)
def set_meas_constants(self, signal_name, slew_dir_str):
"""Set the values needed to generate a Spice measurement statement based on the name of the measurement."""
self.trig_dir_str = slew_dir_str
self.targ_dir_str = slew_dir_str
if slew_dir_str == "RISE":
self.trig_val_of_vdd = 0.1
self.trig_val_of_vdd = 0.1
self.targ_val_of_vdd = 0.9
elif slew_dir_str == "FALL":
self.trig_val_of_vdd = 0.9
self.trig_val_of_vdd = 0.9
self.targ_val_of_vdd = 0.1
else:
debug.error("Unrecognised slew measurement direction={}".format(slew_dir_str),1)
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."""
def __init__(self, measure_name, power_type="", measure_scale=None, has_port=True):
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
self.set_meas_constants(power_type)
def get_measure_function(self):
return stimuli.gen_meas_power
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"
def get_measure_values(self, t_initial, t_final, port=None):
def get_measure_values(self, t_initial, t_final, port=None):
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
self.port_error_check(port)
if port != None:
meas_name = "{}{}".format(self.name, port)
else:
meas_name = self.name
return (meas_name,t_initial,t_final)
class voltage_when_measure(spice_measurement):
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."""
def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, trig_vdd, 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, trig_vdd)
def get_measure_function(self):
return stimuli.gen_meas_find_voltage
def set_meas_constants(self, trig_name, targ_name, trig_dir_str, trig_vdd):
"""Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)"""
self.trig_dir_str = trig_dir_str
self.trig_val_of_vdd = trig_vdd
self.trig_val_of_vdd = trig_vdd
self.trig_name_no_port = trig_name
self.targ_name_no_port = targ_name
def get_measure_values(self, trig_td, vdd_voltage, port=None):
def get_measure_values(self, trig_td, vdd_voltage, port=None):
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
self.port_error_check(port)
if port != None:
@ -169,25 +169,25 @@ 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
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):
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."""
def __init__(self, measure_name, targ_name, measure_scale=None, has_port=True):
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
self.set_meas_constants(targ_name)
def get_measure_function(self):
return stimuli.gen_meas_find_voltage_at_time
def set_meas_constants(self, targ_name):
"""Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)"""
self.targ_name_no_port = targ_name
def get_measure_values(self, time_at, port=None):
def get_measure_values(self, time_at, port=None):
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
self.port_error_check(port)
if port != None:
@ -196,6 +196,6 @@ class voltage_at_measure(spice_measurement):
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)
targ_name = self.targ_name_no_port
return (meas_name,targ_name,time_at)

View File

@ -30,14 +30,14 @@ class model_check(delay):
self.period = tech.spice["feasible_period"]
self.create_data_names()
self.custom_delaychain=custom_delaychain
def create_data_names(self):
self.wl_meas_name, self.wl_model_name = "wl_measures", "wl_model"
self.sae_meas_name, self.sae_model_name = "sae_measures", "sae_model"
self.wl_slew_name, self.sae_slew_name = "wl_slews", "sae_slews"
self.bl_meas_name, self.bl_slew_name = "bl_measures", "bl_slews"
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
@ -73,10 +73,10 @@ class model_check(delay):
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.bitline_meas_names = ["delay_wl_to_bl", "delay_bl_to_dout"]
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.
Names are re-harded coded here; i.e. the names are hardcoded in most of OpenRAM and are
@ -90,7 +90,7 @@ class model_check(delay):
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.Xcontrol{}.Xreplica_bitline.Xdelay_chain.dout_{}".format('{}', stage) for stage in range(1,self.get_num_delay_stages())]
if len(self.sram.all_ports) > 1:
port_format = '{}'
else:
@ -103,21 +103,21 @@ class model_check(delay):
pre_delay_chain_names = ["Xsram.Xcontrol{}.gated_clk_bar".format('{}')]
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('{}')]
self.rbl_en_signal_names = pre_delay_chain_names+\
delay_chain_signal_names+\
["Xsram.Xcontrol{}.Xreplica_bitline.delayed_en".format('{}')]
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]
def create_measurement_objects(self):
"""Create the measurements used for read and write ports"""
self.create_wordline_meas_objs()
@ -125,101 +125,101 @@ class model_check(delay):
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
def create_power_meas_objs(self):
"""Create power measurement object. Only one."""
self.power_meas_objs = []
self.power_meas_objs.append(power_measure(self.power_meas_names[0], "FALL", measure_scale=1e3))
def create_wordline_meas_objs(self):
"""Create the measurements to measure the wordline path from the gated_clk_bar signal"""
self.wl_meas_objs = []
trig_dir = "RISE"
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_signal_names[i],
trig_dir,
targ_dir,
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],
trig_dir,
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
trig_dir = targ_dir
targ_dir = temp_dir
self.wl_meas_objs.append(slew_measure(self.wl_slew_meas_names[-1], self.wl_signal_names[-1], trig_dir, measure_scale=1e9))
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.
self.bl_meas_objs = []
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],
trig_dir,
targ_dir,
self.bl_meas_objs.append(delay_measure(self.bitline_meas_names[0],
self.bl_signal_names[0],
self.bl_signal_names[-1],
trig_dir,
targ_dir,
measure_scale=1e9))
def create_sae_meas_objs(self):
"""Create the measurements to measure the sense amp enable path from the gated_clk_bar signal. The RBL splits this path into two."""
self.sae_meas_objs = []
trig_dir = "RISE"
targ_dir = "FALL"
#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.rbl_en_signal_names[i],
trig_dir,
targ_dir,
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],
trig_dir,
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
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],
"RISE",
"RISE",
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],
"RISE",
"RISE",
measure_scale=1e9)
self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[-1],
self.rbl_en_signal_names[-1],
trig_dir,
self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[-1],
self.rbl_en_signal_names[-1],
trig_dir,
measure_scale=1e9))
#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_signal_names[i],
trig_dir,
targ_dir,
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],
trig_dir,
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
trig_dir = targ_dir
targ_dir = temp_dir
self.sae_meas_objs.append(slew_measure(self.sae_slew_meas_names[-1],
self.sae_signal_names[-1],
trig_dir,
self.sae_meas_objs.append(slew_measure(self.sae_slew_meas_names[-1],
self.sae_signal_names[-1],
trig_dir,
measure_scale=1e9))
def write_delay_measures(self):
"""
Write the measure statements to quantify the delay and power results for all targeted ports.
@ -229,12 +229,12 @@ class model_check(delay):
# Output some comments to aid where cycles start and what is happening
for comment in self.cycle_comments:
self.sf.write("* {}\n".format(comment))
for read_port in self.targ_read_ports:
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)
"""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
@ -246,15 +246,15 @@ class model_check(delay):
return self.get_power_measure_variants(port, measure_obj, "read")
else:
debug.error("Measurement not recognized by the model checker.",1)
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
t_initial = self.cycle_times[self.measure_cycles[port]["read0"]]
t_final = self.cycle_times[self.measure_cycles[port]["read0"]+1]
return (t_initial, t_final, port)
def write_measures_read_port(self, port):
"""
Write the measure statements for all nodes along the wordline path.
@ -263,16 +263,16 @@ class model_check(delay):
for measure in self.all_measures:
measure_variant_inp_tuple = self.get_delay_measure_variants(port, measure)
measure.write_measure(self.stim, measure_variant_inp_tuple)
def get_measurement_values(self, meas_objs, port):
"""Gets the delays and slews from a specified port from the spice output file and returns them as lists."""
delay_meas_list = []
delay_meas_list = []
slew_meas_list = []
power_meas_list=[]
for measure in meas_objs:
measure_value = measure.retrieve_measure(port=port)
if type(measure_value) != float:
debug.error("Failed to Measure Value:\n\t\t{}={}".format(measure.name, measure_value),1)
debug.error("Failed to Measure Value:\n\t\t{}={}".format(measure.name, measure_value),1)
if type(measure) is delay_measure:
delay_meas_list.append(measure_value)
elif type(measure)is slew_measure:
@ -282,7 +282,7 @@ class model_check(delay):
else:
debug.error("Measurement object not recognized.",1)
return delay_meas_list, slew_meas_list,power_meas_list
def run_delay_simulation(self):
"""
This tries to simulate a period and checks if the result works. If
@ -291,8 +291,8 @@ class model_check(delay):
include leakage of all cells.
"""
#Sanity Check
debug.check(self.period > 0, "Target simulation period non-positive")
debug.check(self.period > 0, "Target simulation period non-positive")
wl_delay_result = [[] for i in self.all_ports]
wl_slew_result = [[] for i in self.all_ports]
sae_delay_result = [[] for i in self.all_ports]
@ -304,44 +304,44 @@ class model_check(delay):
self.write_delay_stimulus()
self.stim.run_sim() #running sim prodoces spice output file.
#Retrieve the results from the output file
for port in self.targ_read_ports:
for port in self.targ_read_ports:
#Parse and check the voltage measurements
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."""
return self.sram.control_logic_rw.get_wl_sen_delays()
def get_num_delay_stages(self):
"""Gets the number of stages in the delay chain from the control logic"""
return len(self.sram.control_logic_rw.replica_bitline.delay_fanout_list)
def get_num_delay_fanout_list(self):
"""Gets the number of stages in the delay chain from the control logic"""
return self.sram.control_logic_rw.replica_bitline.delay_fanout_list
def get_num_delay_stage_fanout(self):
"""Gets fanout in each stage in the delay chain. Assumes each stage is the same"""
return self.sram.control_logic_rw.replica_bitline.delay_fanout_list[0]
def get_num_wl_en_driver_stages(self):
"""Gets the number of stages in the wl_en driver from the control logic"""
return self.sram.control_logic_rw.wl_en_driver.num_stages
def get_num_sen_driver_stages(self):
"""Gets the number of stages in the sen driver from the control logic"""
return self.sram.control_logic_rw.s_en_driver.num_stages
def get_num_wl_driver_stages(self):
"""Gets the number of stages in the wordline driver from the control logic"""
return self.sram.bank.wordline_driver.inv.num_stages
return self.sram.bank.wordline_driver.inv.num_stages
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 = []
@ -350,12 +350,12 @@ class model_check(delay):
for meas_value in delay_list:
total+=meas_value
average = total/len(delay_list)
#Convert values
for meas_value in delay_list:
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 = []
@ -364,14 +364,14 @@ class model_check(delay):
for value in value_list:
scaled_values.append((value-average)/(min_max_diff))
return scaled_values
def calculate_error_l2_norm(self, list_a, list_b):
def calculate_error_l2_norm(self, list_a, list_b):
"""Calculates error between two lists using the l2 norm"""
error_list = []
for val_a, val_b in zip(list_a, list_b):
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)
@ -380,7 +380,7 @@ class model_check(delay):
debug.info(1, "Scaled model:\n{}".format(scaled_model))
errors = self.calculate_error_l2_norm(scaled_meas, scaled_model)
debug.info(1, "Errors:\n{}\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."""
self.load=max(loads)
@ -390,7 +390,7 @@ class model_check(delay):
self.create_measurement_names(port)
self.create_measurement_objects()
data_dict = {}
read_port = self.read_ports[0] #only test the first read port
read_port = port
self.targ_read_ports = [read_port]
@ -398,33 +398,33 @@ class model_check(delay):
debug.info(1,"Model test: corner {}".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.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]))
data_dict[self.wl_meas_name] = wl_delays[read_port]
data_dict[self.sae_meas_name] = sae_delays[read_port]
data_dict[self.wl_slew_name] = wl_slews[read_port]
data_dict[self.sae_slew_name] = sae_slews[read_port]
data_dict[self.bl_meas_name] = bl_delays[read_port]
data_dict[self.power_name] = powers[read_port]
if not OPTS.use_tech_delay_chain_size: #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))
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
# 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")
# self.compare_measured_and_model(sae_delays[read_port], sae_model_delays)
return data_dict
def get_all_signal_names(self):
@ -438,12 +438,12 @@ class model_check(delay):
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
if not OPTS.use_tech_delay_chain_size:
name_dict[self.wl_model_name] = name_dict["wl_measures"] #model uses same names as measured.
name_dict[self.sae_model_name] = name_dict["sae_measures"]
return name_dict

View File

@ -27,23 +27,22 @@ class setup_hold():
self.model_location = OPTS.openram_tech + "sp_lib/dff.sp"
self.period = tech.spice["feasible_period"]
debug.info(2,"Feasible period from technology file: {0} ".format(self.period))
debug.info(2, "Feasible period from technology file: {0} ".format(self.period))
self.set_corner(corner)
def set_corner(self,corner):
def set_corner(self, corner):
""" Set the corner values """
self.corner = corner
(self.process, self.vdd_voltage, self.temperature) = corner
self.gnd_voltage = 0
def write_stimulus(self, mode, target_time, correct_value):
"""Creates a stimulus file for SRAM setup/hold time calculation"""
# creates and opens the stimulus file for writing
temp_stim = OPTS.openram_temp + "stim.sp"
self.stim_sp = "sh_stim.sp"
temp_stim = OPTS.openram_temp + self.stim_sp
self.sf = open(temp_stim, "w")
self.stim = stimuli(self.sf, self.corner)
@ -60,11 +59,10 @@ class setup_hold():
self.write_clock()
self.write_measures(mode=mode,
self.write_measures(mode=mode,
correct_value=correct_value)
self.stim.write_control(4*self.period)
self.stim.write_control(4 * self.period)
self.sf.close()
@ -79,7 +77,6 @@ class setup_hold():
self.sf.write("\n* Global Power Supplies\n")
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
initialize it to the opposite polarity. Second period is used for
@ -102,24 +99,22 @@ class setup_hold():
data_values=[init_value, start_value, end_value],
period=target_time,
slew=self.constrained_input_slew,
setup=0)
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."""
self.stim.gen_pwl(sig_name="clk",
# initial clk edge is right after the 0 time to initialize a flop
# initial clk edge is right after the 0 time to initialize a flop
# without using .IC on an internal node.
# Return input to value after one period.
# The second pulse is the characterization one at 2*period
clk_times=[0, 0.1*self.period,self.period,2*self.period],
clk_times=[0, 0.1 * self.period, self.period, 2 * self.period],
data_values=[0, 1, 0, 1],
period=2*self.period,
period=2 * self.period,
slew=self.constrained_input_slew,
setup=0)
setup=0)
def write_measures(self, mode, correct_value):
""" Measure statements for setup/hold with right phases. """
@ -139,7 +134,6 @@ class setup_hold():
else:
din_rise_or_fall = "RISE"
self.sf.write("\n* Measure statements for pass/fail verification\n")
trig_name = "clk"
targ_name = "dout"
@ -152,9 +146,9 @@ class setup_hold():
targ_val=targ_val,
trig_dir="RISE",
targ_dir=dout_rise_or_fall,
trig_td=1.9*self.period,
targ_td=1.9*self.period)
trig_td=1.9 * self.period,
targ_td=1.9 * self.period)
targ_name = "data"
# Start triggers right after initialize value is returned to normal
# at one period
@ -165,16 +159,13 @@ class setup_hold():
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)
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
depending on whether we are doing setup or hold.
depending on whether we are doing setup or hold.
"""
# NOTE: The feasible bound is always feasible. This is why they are different for setup and hold.
@ -182,86 +173,85 @@ class setup_hold():
# this time. They are also unbalanced so that the average won't be right on the clock edge in the
# first iteration.
if mode == "SETUP":
feasible_bound = 1.25*self.period
infeasible_bound = 2.5*self.period
feasible_bound = 1.25 * self.period
infeasible_bound = 2.5 * self.period
else:
infeasible_bound = 1.5*self.period
feasible_bound = 2.75*self.period
infeasible_bound = 1.5 * self.period
feasible_bound = 2.75 * self.period
# Initial check if reference feasible bound time passes for correct_value, if not, we can't start the search!
self.write_stimulus(mode=mode,
target_time=feasible_bound,
self.write_stimulus(mode=mode,
target_time=feasible_bound,
correct_value=correct_value)
self.stim.run_sim()
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))
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),2)
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))
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
self.write_stimulus(mode=mode,
target_time=target_time,
target_time = (feasible_bound + infeasible_bound) / 2
self.write_stimulus(mode=mode,
target_time=target_time,
correct_value=correct_value)
debug.info(2,"{0} value: {1} Target time: {2} Infeasible: {3} Feasible: {4}".format(mode,
correct_value,
target_time,
infeasible_bound,
feasible_bound))
debug.info(2, "{0} value: {1} Target time: {2} Infeasible: {3} Feasible: {4}".format(mode,
correct_value,
target_time,
infeasible_bound,
feasible_bound))
self.stim.run_sim()
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 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))
debug.info(2, "PASS Clk-to-Q: {0} Setup/Hold: {1}".format(clk_to_q, setuphold_time))
passing_setuphold_time = setuphold_time
feasible_bound = target_time
else:
debug.info(2,"FAIL Clk-to-Q: {0} Setup/Hold: {1}".format(clk_to_q,setuphold_time))
debug.info(2, "FAIL Clk-to-Q: {0} Setup/Hold: {1}".format(clk_to_q, setuphold_time))
infeasible_bound = target_time
#raw_input("Press Enter to continue...")
if relative_compare(feasible_bound, infeasible_bound, error_tolerance=0.001):
debug.info(3,"CONVERGE {0} vs {1}".format(feasible_bound,infeasible_bound))
debug.info(3, "CONVERGE {0} vs {1}".format(feasible_bound, infeasible_bound))
break
debug.info(2,"Converged on {0} time {1}.".format(mode,passing_setuphold_time))
debug.info(2, "Converged on {0} time {1}.".format(mode, passing_setuphold_time))
return passing_setuphold_time
def setup_LH_time(self):
"""Calculates the setup time for low-to-high transition for a DFF
"""
return self.bidir_search(1, "SETUP")
def setup_HL_time(self):
"""Calculates the setup time for high-to-low transition for a DFF
"""
return self.bidir_search(0, "SETUP")
def hold_LH_time(self):
"""Calculates the hold time for low-to-high transition for a DFF
"""
@ -272,7 +262,6 @@ class setup_hold():
"""
return self.bidir_search(0, "HOLD")
def analyze(self, related_slews, constrained_slews):
"""main function to calculate both setup and hold time for the
DFF and returns a dictionary that contains 4 lists for both
@ -283,7 +272,7 @@ class setup_hold():
HL_setup = []
LH_hold = []
HL_hold = []
#For debugging, skips characterization and returns dummy values.
# i = 1.0
# for self.related_input_slew in related_slews:
@ -293,18 +282,18 @@ class setup_hold():
# LH_hold.append(i+2.0)
# HL_hold.append(i+3.0)
# i+=4.0
# times = {"setup_times_LH": LH_setup,
# "setup_times_HL": HL_setup,
# "hold_times_LH": LH_hold,
# "hold_times_HL": HL_hold
# }
# return times
for self.related_input_slew in related_slews:
for self.constrained_input_slew in constrained_slews:
debug.info(1, "Clock slew: {0} Data slew: {1}".format(self.related_input_slew,self.constrained_input_slew))
debug.info(1, "Clock slew: {0} Data slew: {1}".format(self.related_input_slew,
self.constrained_input_slew))
LH_setup_time = self.setup_LH_time()
debug.info(1, " Setup Time for low_to_high transition: {0}".format(LH_setup_time))
HL_setup_time = self.setup_HL_time()
@ -317,7 +306,7 @@ class setup_hold():
HL_setup.append(HL_setup_time)
LH_hold.append(LH_hold_time)
HL_hold.append(HL_hold_time)
times = {"setup_times_LH": LH_setup,
"setup_times_HL": HL_setup,
"hold_times_LH": LH_hold,
@ -325,22 +314,22 @@ class setup_hold():
}
return times
def analytical_setuphold(self,related_slews, constrained_slews):
def analytical_setuphold(self, related_slews, constrained_slews):
""" Just return the fixed setup/hold times from the technology.
"""
LH_setup = []
HL_setup = []
LH_hold = []
HL_hold = []
for self.related_input_slew in related_slews:
for self.constrained_input_slew in constrained_slews:
# convert from ps to ns
LH_setup.append(tech.spice["dff_setup"]/1e3)
HL_setup.append(tech.spice["dff_setup"]/1e3)
LH_hold.append(tech.spice["dff_hold"]/1e3)
HL_hold.append(tech.spice["dff_hold"]/1e3)
LH_setup.append(tech.spice["dff_setup"] / 1e3)
HL_setup.append(tech.spice["dff_setup"] / 1e3)
LH_hold.append(tech.spice["dff_hold"] / 1e3)
HL_hold.append(tech.spice["dff_hold"] / 1e3)
times = {"setup_times_LH": LH_setup,
"setup_times_HL": HL_setup,
"hold_times_LH": LH_hold,

View File

@ -17,7 +17,7 @@ class simulation():
def __init__(self, sram, spfile, corner):
self.sram = sram
self.name = self.sram.name
self.word_size = self.sram.word_size
self.addr_size = self.sram.addr_size
@ -28,7 +28,7 @@ class simulation():
else:
self.num_spare_cols = self.sram.num_spare_cols
self.sp_file = spfile
self.all_ports = self.sram.all_ports
self.readwrite_ports = self.sram.readwrite_ports
self.read_ports = self.sram.read_ports
@ -53,7 +53,7 @@ class simulation():
self.v_high = self.vdd_voltage - tech.spice["nom_threshold"]
self.v_low = tech.spice["nom_threshold"]
self.gnd_voltage = 0
def create_signal_names(self):
self.addr_name = "a"
self.din_name = "din"
@ -66,12 +66,12 @@ class simulation():
"Number of pins generated for characterization \
do not match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(self.sram.pins,
self.pins))
def set_stimulus_variables(self):
# Clock signals
self.cycle_times = []
self.t_current = 0
# control signals: only one cs_b for entire multiported sram, one we_b for each write port
self.csb_values = {port: [] for port in self.all_ports}
self.web_values = {port: [] for port in self.readwrite_ports}
@ -81,13 +81,13 @@ class simulation():
self.data_value = {port: [] for port in self.write_ports}
self.wmask_value = {port: [] for port in self.write_ports}
self.spare_wen_value = {port: [] for port in self.write_ports}
# Three dimensional list to handle each addr and data bits for each port over the number of checks
self.addr_values = {port: [[] for bit in range(self.addr_size)] for port in self.all_ports}
self.data_values = {port: [[] for bit in range(self.word_size + self.num_spare_cols)] for port in self.write_ports}
self.wmask_values = {port: [[] for bit in range(self.num_wmasks)] for port in self.write_ports}
self.spare_wen_values = {port: [[] for bit in range(self.num_spare_cols)] for port in self.write_ports}
# For generating comments in SPICE stimulus
self.cycle_comments = []
self.fn_cycle_comments = []
@ -104,13 +104,13 @@ class simulation():
web_val = 0
elif op != "noop":
debug.error("Could not add control signals for port {0}. Command {1} not recognized".format(port, op), 1)
# Append the values depending on the type of port
self.csb_values[port].append(csb_val)
# If port is in both lists, add rw control signal. Condition indicates its a RW port.
if port in self.readwrite_ports:
self.web_values[port].append(web_val)
def add_data(self, data, port):
""" Add the array of data values """
debug.check(len(data)==(self.word_size + self.num_spare_cols), "Invalid data word size.")
@ -129,7 +129,7 @@ class simulation():
def add_address(self, address, port):
""" Add the array of address values """
debug.check(len(address)==self.addr_size, "Invalid address size.")
self.addr_value[port].append(address)
bit = self.addr_size - 1
for c in address:
@ -170,7 +170,7 @@ class simulation():
else:
debug.error("Non-binary spare enable signal string", 1)
bit -= 1
def add_write(self, comment, address, data, wmask, port):
""" Add the control values for a write cycle. """
debug.check(port in self.write_ports,
@ -182,18 +182,18 @@ class simulation():
self.cycle_times.append(self.t_current)
self.t_current += self.period
self.add_control_one_port(port, "write")
self.add_data(data, port)
self.add_address(address, port)
self.add_wmask(wmask, port)
self.add_wmask(wmask, port)
self.add_spare_wen("1" * self.num_spare_cols, port)
#Add noops to all other ports.
for unselected_port in self.all_ports:
if unselected_port != port:
self.add_noop_one_port(unselected_port)
def add_read(self, comment, address, port):
""" Add the control values for a read cycle. """
debug.check(port in self.read_ports,
@ -206,8 +206,8 @@ class simulation():
self.cycle_times.append(self.t_current)
self.t_current += self.period
self.add_control_one_port(port, "read")
self.add_address(address, port)
self.add_address(address, port)
# If the port is also a readwrite then add
# the same value as previous cycle
if port in self.write_ports:
@ -220,7 +220,7 @@ class simulation():
except:
self.add_wmask("0" * self.num_wmasks, port)
self.add_spare_wen("0" * self.num_spare_cols, port)
#Add noops to all other ports.
for unselected_port in self.all_ports:
if unselected_port != port:
@ -234,7 +234,7 @@ class simulation():
self.cycle_times.append(self.t_current)
self.t_current += self.period
for port in self.all_ports:
self.add_noop_one_port(port)
@ -251,7 +251,7 @@ class simulation():
self.add_address(address, port)
self.add_wmask(wmask, port)
self.add_spare_wen("1" * 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. """
debug.check(port in self.read_ports,
@ -259,7 +259,7 @@ class simulation():
self.read_ports))
debug.info(2, comment)
self.fn_cycle_comments.append(comment)
self.add_control_one_port(port, "read")
self.add_address(address, port)
@ -275,16 +275,16 @@ class simulation():
except:
self.add_wmask("0" * self.num_wmasks, port)
self.add_spare_wen("0" * self.num_spare_cols, port)
def add_noop_one_port(self, port):
""" Add the control values for a noop to a single port. Does not increment the period. """
""" Add the control values for a noop to a single port. Does not increment the period. """
self.add_control_one_port(port, "noop")
try:
self.add_address(self.addr_value[port][-1], port)
except:
self.add_address("0" * self.addr_size, port)
# If the port is also a readwrite then add
# the same value as previous cycle
if port in self.write_ports:
@ -297,7 +297,7 @@ class simulation():
except:
self.add_wmask("0" * self.num_wmasks, port)
self.add_spare_wen("0" * self.num_spare_cols, port)
def add_noop_clock_one_port(self, port):
""" Add the control values for a noop to a single port. Increments the period. """
debug.info(2, 'Clock only on port {}'.format(port))
@ -324,7 +324,7 @@ class simulation():
time,
time_spacing,
comment))
def gen_cycle_comment(self, op, word, addr, wmask, port, t_current):
if op == "noop":
str = "\tIdle during cycle {0} ({1}ns - {2}ns)"
@ -355,22 +355,22 @@ class simulation():
int(t_current / self.period),
t_current,
t_current + self.period)
return comment
def gen_pin_names(self, port_signal_names, port_info, abits, dbits):
"""Creates the pins names of the SRAM based on the no. of ports."""
# This may seem redundant as the pin names are already defined in the sram. However, it is difficult
# to extract the functionality from the names, so they are recreated. As the order is static, changing
# This may seem redundant as the pin names are already defined in the sram. However, it is difficult
# to extract the functionality from the names, so they are recreated. As the order is static, changing
# the order of the pin names will cause issues here.
pin_names = []
(addr_name, din_name, dout_name) = port_signal_names
(total_ports, write_index, read_index) = port_info
for write_input in write_index:
for i in range(dbits):
pin_names.append("{0}{1}_{2}".format(din_name, write_input, i))
for port in range(total_ports):
for i in range(abits):
pin_names.append("{0}{1}_{2}".format(addr_name, port, i))
@ -389,25 +389,25 @@ class simulation():
for port in write_index:
for bit in range(self.num_wmasks):
pin_names.append("WMASK{0}_{1}".format(port, bit))
if self.num_spare_cols:
for port in write_index:
for bit in range(self.num_spare_cols):
pin_names.append("SPARE_WEN{0}_{1}".format(port, bit))
for read_output in read_index:
for i in range(dbits):
pin_names.append("{0}{1}_{2}".format(dout_name, read_output, i))
pin_names.append("{0}".format("vdd"))
pin_names.append("{0}".format("gnd"))
return pin_names
def add_graph_exclusions(self):
"""
Exclude portions of SRAM from timing graph which are not relevant
"""
# other initializations can only be done during analysis when a bit has been selected
# for testing.
self.sram.bank.graph_exclude_precharge()
@ -415,29 +415,29 @@ class simulation():
self.sram.graph_exclude_data_dff()
self.sram.graph_exclude_ctrl_dffs()
self.sram.bank.bitcell_array.graph_exclude_replica_col_bits()
def set_internal_spice_names(self):
"""
Sets important names for characterization such as Sense amp enable and internal bit nets.
"""
port = self.read_ports[0]
if not OPTS.use_pex:
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))]
else:
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))
bl_name_port, br_name_port = self.get_bl_name(self.graph.all_paths, port)
port_pos = -1 - len(str(self.probe_data)) - len(str(port))
if bl_name_port.endswith(str(port) + "_" + str(self.probe_data)):
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
@ -445,7 +445,7 @@ class simulation():
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(self.probe_data)):
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
@ -457,22 +457,22 @@ class simulation():
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))
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))
def get_sen_name(self, paths, assumed_port=None):
"""
Gets the signal name associated with the sense amp enable from input paths.
Only expects a single path to contain the sen signal name.
"""
sa_mods = factory.get_mods(OPTS.sense_amp)
# Any sense amp instantiated should be identical, any change to that
# Any sense amp instantiated should be identical, any change to that
# will require some identification to determine the mod desired.
debug.check(len(sa_mods) == 1, "Only expected one type of Sense Amp. Cannot perform s_en checks.")
enable_name = sa_mods[0].get_enable_name()
@ -480,15 +480,15 @@ class simulation():
if OPTS.use_pex:
sen_name = sen_name.split('.')[-1]
return sen_name
def create_graph(self):
"""
Creates timing graph to generate the timing paths for the SRAM output.
"""
self.sram.clear_exclude_bits() # Removes previous bit exclusions
self.sram.graph_exclude_bits(self.wordline_row, self.bitline_column)
# Generate new graph every analysis as edges might change depending on test bit
self.graph = graph_util.timing_graph()
self.sram_instance_name = "X{}".format(self.sram.name)
@ -498,14 +498,14 @@ class simulation():
"""
Gets the mods as a set which should be excluded while searching for name.
"""
# Exclude the RBL as it contains bitcells which are not in the main bitcell array
# so it makes the search awkward
return set(factory.get_mods(OPTS.replica_bitline))
def get_alias_in_path(self, paths, internal_net, mod, exclusion_set=None):
"""
Finds a single alias for the internal_net in given paths.
Finds a single alias for the internal_net in given paths.
More or less hits cause an error
"""
net_found = False
@ -520,7 +520,7 @@ class simulation():
net_found = True
if not net_found:
debug.error("Could not find {} net in timing paths.".format(internal_net), 1)
return path_net_name
def get_bl_name(self, paths, port):
@ -530,7 +530,7 @@ class simulation():
cell_mod = factory.create(module_type=OPTS.bitcell)
cell_bl = cell_mod.get_bl_name(port)
cell_br = cell_mod.get_br_name(port)
bl_names = []
exclude_set = self.get_bl_name_search_exclusions()
for int_net in [cell_bl, cell_br]:
@ -540,5 +540,5 @@ class simulation():
bl_names[i] = bl_names[i].split('.')[-1]
return bl_names[0], bl_names[1]

View File

@ -31,16 +31,27 @@ class stimuli():
self.tx_length = tech.drc["minlength_channel"]
self.sf = stim_file
(self.process, self.voltage, self.temperature) = corner
found = False
self.device_libraries = []
self.device_models = []
try:
self.device_libraries = tech.spice["fet_libraries"][self.process]
except:
self.device_models = tech.spice["fet_models"][self.process]
self.device_libraries += tech.spice["fet_libraries"][self.process]
found = True
except KeyError:
pass
try:
self.device_models += tech.spice["fet_models"][self.process]
found = True
except KeyError:
pass
if not found:
debug.error("Must define either fet_libraries or fet_models.", -1)
def inst_model(self, pins, model_name):
""" Function to instantiate a generic model with a set of pins """
if OPTS.use_pex:
self.inst_pex_model(pins, model_name)
else:
@ -48,7 +59,7 @@ class stimuli():
for pin in pins:
self.sf.write("{0} ".format(pin))
self.sf.write("{0}\n".format(model_name))
def inst_pex_model(self, pins, model_name):
self.sf.write("X{0} ".format(model_name))
for pin in pins:
@ -88,7 +99,7 @@ class stimuli():
def create_buffer(self, buffer_name, size=[1, 3], beta=2.5):
"""
Generates buffer for top level signals (only for sim
purposes). Size is pair for PMOS, NMOS width multiple.
purposes). Size is pair for PMOS, NMOS width multiple.
"""
self.sf.write(".SUBCKT test_{2} in out {0} {1}\n".format(self.vdd_name,
@ -113,23 +124,23 @@ class stimuli():
self.sf.write(".ENDS test_{0}\n\n".format(buffer_name))
def gen_pulse(self, sig_name, v1, v2, offset, period, t_rise, t_fall):
"""
"""
Generates a periodic signal with 50% duty cycle and slew rates. Period is measured
from 50% to 50%.
"""
self.sf.write("* PULSE: period={0}\n".format(period))
pulse_string="V{0} {0} 0 PULSE ({1} {2} {3}n {4}n {5}n {6}n {7}n)\n"
self.sf.write(pulse_string.format(sig_name,
self.sf.write(pulse_string.format(sig_name,
v1,
v2,
offset,
t_rise,
t_fall,
t_fall,
0.5*period-0.5*t_rise-0.5*t_fall,
period))
def gen_pwl(self, sig_name, clk_times, data_values, period, slew, setup):
"""
"""
Generate a PWL stimulus given a signal name and data values at each period.
Automatically creates slews and ensures each data occurs a setup before the clock
edge. The first clk_time should be 0 and is the initial time that corresponds
@ -141,7 +152,7 @@ class stimuli():
str.format(len(clk_times),
len(data_values),
sig_name))
# shift signal times earlier for setup time
times = np.array(clk_times) - setup * period
values = np.array(data_values) * self.voltage
@ -174,7 +185,7 @@ class stimuli():
return 1
else:
debug.error("Invalid value to get an inverse of: {0}".format(value))
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"
@ -187,7 +198,7 @@ class stimuli():
targ_val,
targ_dir,
targ_td))
def gen_meas_find_voltage(self, meas_name, trig_name, targ_name, trig_val, trig_dir, trig_td):
""" Creates the .meas statement for the measurement of delay """
measure_string=".meas tran {0} FIND v({1}) WHEN v({2})={3}v {4}=1 TD={5}n \n\n"
@ -197,7 +208,7 @@ class stimuli():
trig_val,
trig_dir,
trig_td))
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"
@ -216,15 +227,15 @@ class stimuli():
power_exp,
t_initial,
t_final))
def gen_meas_value(self, meas_name, dout, t_intital, t_final):
measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name, dout, t_intital, t_final)
self.sf.write(measure_string)
def write_control(self, end_time, runlvl=4):
""" Write the control cards to run and end the simulation """
# These are guesses...
# These are guesses...
if runlvl==1:
reltol = 0.02 # 2%
elif runlvl==2:
@ -234,7 +245,7 @@ class stimuli():
else:
reltol = 0.001 # 0.1%
timestep = 10 # ps, was 5ps but ngspice was complaining the timestep was too small in certain tests.
# UIC is needed for ngspice to converge
self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep, end_time))
self.sf.write(".TEMP {}\n".format(self.temperature))
@ -249,7 +260,7 @@ class stimuli():
# create plots for all signals
self.sf.write("* probe is used for hspice/xa, while plot is used in ngspice\n")
if OPTS.debug_level>0:
if OPTS.verbose_level>0:
if OPTS.spice_name in ["hspice", "xa"]:
self.sf.write(".probe V(*)\n")
else:
@ -265,21 +276,16 @@ class stimuli():
"""Writes include statements, inputs are lists of model files"""
self.sf.write("* {} process corner\n".format(self.process))
if OPTS.tech_name == "sky130":
for item in self.device_libraries:
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]))
includes = [circuit]
else:
includes = self.device_models + [circuit]
for item in list(includes):
if os.path.isfile(item):
self.sf.write(".include \"{0}\"\n".format(item))
for item in self.device_libraries:
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 model: {0}\nSet SPICE_MODEL_DIR to over-ride path.\n".format(item))
debug.error("Could not find spice library: {0}\nSet SPICE_MODEL_DIR to over-ride path.\n".format(item[0]))
includes = self.device_models + [circuit]
for item in list(includes):
self.sf.write(".include \"{0}\"\n".format(item))
def write_supply(self):
""" Writes supply voltage statements """
@ -290,13 +296,13 @@ class stimuli():
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))
def run_sim(self):
def run_sim(self, name):
""" Run hspice in batch mode and output rawfile to parse. """
temp_stim = "{0}stim.sp".format(OPTS.openram_temp)
temp_stim = "{0}{1}".format(OPTS.openram_temp, name)
import datetime
start_time = datetime.datetime.now()
debug.check(OPTS.spice_exe != "", "No spice simulator has been found.")
if OPTS.spice_name == "xa":
# Output the xa configurations here. FIXME: Move this to write it once.
xa_cfg = open("{}xa.cfg".format(OPTS.openram_temp), "w")
@ -331,13 +337,13 @@ 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)
retcode = subprocess.call(cmd, stdout=spice_stdout, stderr=spice_stderr, shell=True)
spice_stdout.close()
spice_stderr.close()
if (retcode > valid_retcode):
debug.error("Spice simulation error: " + cmd, -1)
else:
@ -345,4 +351,4 @@ class stimuli():
delta_time = round((end_time - start_time).total_seconds(), 1)
debug.info(2, "*** Spice: {} seconds".format(delta_time))

View File

@ -11,33 +11,33 @@ import re
class trim_spice():
"""
A utility to trim redundant parts of an SRAM spice netlist.
A utility to trim redundant parts of an SRAM spice netlist.
Input is an SRAM spice file. Output is an equivalent netlist
that works for a single address and range of data bits.
"""
def __init__(self, spfile, reduced_spfile):
self.sp_file = spfile
self.reduced_spfile = reduced_spfile
self.reduced_spfile = reduced_spfile
debug.info(1,"Trimming non-critical cells to speed-up characterization: {}.".format(reduced_spfile))
# Load the file into a buffer for performance
sp = open(self.sp_file, "r")
self.spice = sp.readlines()
sp.close()
for i in range(len(self.spice)):
self.spice[i] = self.spice[i].rstrip(" \n")
self.sp_buffer = self.spice
def set_configuration(self, banks, rows, columns, word_size):
""" Set the configuration of SRAM sizes that we are simulating.
Need the: number of banks, number of rows in each bank, number of
Need the: number of banks, number of rows in each bank, number of
columns in each bank, and data word size."""
self.num_banks = banks
self.num_rows = rows
self.num_rows = rows
self.num_columns = columns
self.word_size = word_size
@ -80,8 +80,8 @@ class trim_spice():
debug.info(1,wl_msg)
self.sp_buffer.insert(0, "* It should NOT be used for LVS!!")
self.sp_buffer.insert(0, "* WARNING: This is a TRIMMED NETLIST.")
wl_regex = r"wl\d*_{}".format(wl_address)
bl_regex = r"bl\d*_{}".format(int(self.words_per_row*data_bit + col_address))
self.remove_insts("bitcell_array",[wl_regex,bl_regex])
@ -92,7 +92,7 @@ class trim_spice():
# 3. Keep column muxes basd on BL
self.remove_insts("column_mux_array",[bl_regex])
# 4. Keep write driver based on DATA
data_regex = r"data_{}".format(data_bit)
self.remove_insts("write_driver_array",[data_regex])
@ -100,18 +100,18 @@ class trim_spice():
# 5. Keep wordline driver based on WL
# Need to keep the gater too
#self.remove_insts("wordline_driver",wl_regex)
# 6. Keep precharges based on BL
self.remove_insts("precharge_array",[bl_regex])
# Everything else isn't worth removing. :)
# Finally, write out the buffer as the new reduced file
sp = open(self.reduced_spfile, "w")
sp.write("\n".join(self.sp_buffer))
sp.close()
def remove_insts(self, subckt_name, keep_inst_list):
"""This will remove all of the instances in the list from the named
subckt that DO NOT contain a term in the list. It just does a
@ -121,7 +121,7 @@ class trim_spice():
removed_insts = 0
#Expects keep_inst_list are regex patterns. Compile them here.
compiled_patterns = [re.compile(pattern) for pattern in keep_inst_list]
start_name = ".SUBCKT {}".format(subckt_name)
end_name = ".ENDS {}".format(subckt_name)

View File

@ -6,7 +6,7 @@
# All rights reserved.
#
import design
from tech import GDS, layer, spice, parameter
from tech import GDS, layer, spice
from tech import cell_properties as props
import utils
@ -23,39 +23,42 @@ class dff(design.design):
pin_names = props.dff.custom_port_list
type_list = props.dff.custom_type_list
clk_pin = props.dff.clk_pin
cell_size_layer = "boundary"
(width, height) = utils.get_libcell_size("dff",
GDS["unit"],
layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "dff", GDS["unit"])
def __init__(self, name="dff"):
design.design.__init__(self, name)
super().__init__(name)
self.width = dff.width
self.height = dff.height
self.pin_map = dff.pin_map
(width, height) = utils.get_libcell_size(self.cell_name,
GDS["unit"],
layer[self.cell_size_layer])
pin_map = utils.get_libcell_pins(self.pin_names,
self.cell_name,
GDS["unit"])
self.width = width
self.height = height
self.pin_map = pin_map
self.add_pin_types(self.type_list)
def analytical_power(self, corner, load):
"""Returns dynamic and leakage power. Results in nW"""
c_eff = self.calculate_effective_capacitance(load)
freq = spice["default_event_frequency"]
power_dyn = self.calc_dynamic_power(corner, c_eff, freq)
power_leak = spice["dff_leakage"]
total_power = self.return_power(power_dyn, power_leak)
return total_power
def calculate_effective_capacitance(self, load):
"""Computes effective capacitance. Results in fF"""
from tech import parameter
c_load = load
c_para = spice["dff_out_cap"]#ff
transition_prob = 0.5
return transition_prob*(c_load + c_para)
return transition_prob*(c_load + c_para)
def build_graph(self, graph, inst_name, port_nets):
"""Adds edges based on inputs/outputs. Overrides base class function."""
self.add_graph_edges(graph, port_nets)

View File

@ -9,28 +9,31 @@ import design
from tech import GDS, layer, spice, parameter
import logical_effort
import utils
import debug
class inv_dec(design.design):
"""
INV for address decoders.
"""
pin_names = ["A", "Z", "vdd", "gnd"]
type_list = ["INPUT", "OUTPUT", "POWER", "GROUND"]
(width, height) = utils.get_libcell_size("inv_dec",
GDS["unit"],
layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "inv_dec", GDS["unit"])
def __init__(self, name="inv_dec", height=None):
design.design.__init__(self, name)
cell_size_layer = "boundary"
self.width = inv_dec.width
self.height = inv_dec.height
self.pin_map = inv_dec.pin_map
def __init__(self, name="inv_dec", height=None):
super().__init__(name)
(width, height) = utils.get_libcell_size(self.cell_name,
GDS["unit"],
layer[self.cell_size_layer])
pin_map = utils.get_libcell_pins(self.pin_names,
self.cell_name,
GDS["unit"])
self.width = width
self.height = height
self.pin_map = pin_map
self.add_pin_types(self.type_list)
def analytical_power(self, corner, load):
@ -39,10 +42,10 @@ class inv_dec(design.design):
freq = spice["default_event_frequency"]
power_dyn = self.calc_dynamic_power(corner, c_eff, freq)
power_leak = spice["inv_leakage"]
total_power = self.return_power(power_dyn, power_leak)
return total_power
def calculate_effective_capacitance(self, load):
"""Computes effective capacitance. Results in fF"""
c_load = load
@ -57,7 +60,7 @@ class inv_dec(design.design):
units relative to the minimum width of a transistor
"""
return self.nmos_size + self.pmos_size
def get_stage_effort(self, cout, inp_is_rise=True):
"""
Returns an object representing the parameters for delay in tau units.

View File

@ -15,21 +15,25 @@ class nand2_dec(design.design):
"""
2-input NAND decoder for address decoders.
"""
pin_names = ["A", "B", "Z", "vdd", "gnd"]
type_list = ["INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"]
(width, height) = utils.get_libcell_size("nand2_dec",
GDS["unit"],
layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "nand2_dec", GDS["unit"])
def __init__(self, name="nand2_dec", height=None):
design.design.__init__(self, name)
cell_size_layer = "boundary"
self.width = nand2_dec.width
self.height = nand2_dec.height
self.pin_map = nand2_dec.pin_map
def __init__(self, name="nand2_dec", height=None):
super().__init__(name)
(width, height) = utils.get_libcell_size(self.cell_name,
GDS["unit"],
layer[self.cell_size_layer])
pin_map = utils.get_libcell_pins(self.pin_names,
self.cell_name,
GDS["unit"])
self.width = width
self.height = height
self.pin_map = pin_map
self.add_pin_types(self.type_list)
# FIXME: For now...
@ -39,17 +43,17 @@ class nand2_dec(design.design):
self.pmos_size = parameter["beta"] * size
self.nmos_width = self.nmos_size * drc("minwidth_tx")
self.pmos_width = self.pmos_size * drc("minwidth_tx")
def analytical_power(self, corner, load):
"""Returns dynamic and leakage power. Results in nW"""
c_eff = self.calculate_effective_capacitance(load)
freq = spice["default_event_frequency"]
power_dyn = self.calc_dynamic_power(corner, c_eff, freq)
power_leak = spice["nand2_leakage"]
total_power = self.return_power(power_dyn, power_leak)
return total_power
def calculate_effective_capacitance(self, load):
"""Computes effective capacitance. Results in fF"""
c_load = load
@ -61,7 +65,7 @@ class nand2_dec(design.design):
def input_load(self):
"""Return the relative input capacitance of a single input"""
return self.nmos_size + self.pmos_size
def get_stage_effort(self, cout, inp_is_rise=True):
"""
Returns an object representing the parameters for delay in tau units.
@ -82,4 +86,4 @@ class nand2_dec(design.design):
Overrides base class function.
"""
self.add_graph_edges(graph, port_nets)

View File

@ -15,21 +15,25 @@ class nand3_dec(design.design):
"""
3-input NAND decoder for address decoders.
"""
pin_names = ["A", "B", "C", "Z", "vdd", "gnd"]
type_list = ["INPUT", "INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"]
(width, height) = utils.get_libcell_size("nand3_dec",
GDS["unit"],
layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "nand3_dec", GDS["unit"])
def __init__(self, name="nand3_dec", height=None):
design.design.__init__(self, name)
cell_size_layer = "boundary"
self.width = nand3_dec.width
self.height = nand3_dec.height
self.pin_map = nand3_dec.pin_map
def __init__(self, name="nand3_dec", height=None):
super().__init__(name)
(width, height) = utils.get_libcell_size(self.cell_name,
GDS["unit"],
layer[self.cell_size_layer])
pin_map = utils.get_libcell_pins(self.pin_names,
self.cell_name,
GDS["unit"])
self.width = width
self.height = height
self.pin_map = pin_map
self.add_pin_types(self.type_list)
# FIXME: For now...
@ -39,17 +43,17 @@ class nand3_dec(design.design):
self.pmos_size = parameter["beta"] * size
self.nmos_width = self.nmos_size * drc("minwidth_tx")
self.pmos_width = self.pmos_size * drc("minwidth_tx")
def analytical_power(self, corner, load):
"""Returns dynamic and leakage power. Results in nW"""
c_eff = self.calculate_effective_capacitance(load)
freq = spice["default_event_frequency"]
power_dyn = self.calc_dynamic_power(corner, c_eff, freq)
power_leak = spice["nand3_leakage"]
total_power = self.return_power(power_dyn, power_leak)
return total_power
def calculate_effective_capacitance(self, load):
"""Computes effective capacitance. Results in fF"""
c_load = load
@ -61,7 +65,7 @@ class nand3_dec(design.design):
def input_load(self):
"""Return the relative input capacitance of a single input"""
return self.nmos_size + self.pmos_size
def get_stage_effort(self, cout, inp_is_rise=True):
"""
Returns an object representing the parameters for delay in tau units.
@ -82,4 +86,4 @@ class nand3_dec(design.design):
Overrides base class function.
"""
self.add_graph_edges(graph, port_nets)

View File

@ -15,21 +15,25 @@ class nand4_dec(design.design):
"""
2-input NAND decoder for address decoders.
"""
pin_names = ["A", "B", "C", "D", "Z", "vdd", "gnd"]
type_list = ["INPUT", "INPUT", "INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"]
(width, height) = utils.get_libcell_size("nand4_dec",
GDS["unit"],
layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "nand4_dec", GDS["unit"])
def __init__(self, name="nand4_dec", height=None):
design.design.__init__(self, name)
cell_size_layer = "boundary"
self.width = nand4_dec.width
self.height = nand4_dec.height
self.pin_map = nand4_dec.pin_map
def __init__(self, name="nand4_dec", height=None):
super().__init__(name)
(width, height) = utils.get_libcell_size(self.cell_name,
GDS["unit"],
layer[self.cell_size_layer])
pin_map = utils.get_libcell_pins(self.pin_names,
self.cell_name,
GDS["unit"])
self.width = width
self.height = height
self.pin_map = pin_map
self.add_pin_types(self.type_list)
# FIXME: For now...
@ -39,17 +43,17 @@ class nand4_dec(design.design):
self.pmos_size = parameter["beta"] * size
self.nmos_width = self.nmos_size * drc("minwidth_tx")
self.pmos_width = self.pmos_size * drc("minwidth_tx")
def analytical_power(self, corner, load):
"""Returns dynamic and leakage power. Results in nW"""
c_eff = self.calculate_effective_capacitance(load)
freq = spice["default_event_frequency"]
power_dyn = self.calc_dynamic_power(corner, c_eff, freq)
power_leak = spice["nand4_leakage"]
total_power = self.return_power(power_dyn, power_leak)
return total_power
def calculate_effective_capacitance(self, load):
"""Computes effective capacitance. Results in fF"""
c_load = load
@ -61,7 +65,7 @@ class nand4_dec(design.design):
def input_load(self):
"""Return the relative input capacitance of a single input"""
return self.nmos_size + self.pmos_size
def get_stage_effort(self, cout, inp_is_rise=True):
"""
Returns an object representing the parameters for delay in tau units.
@ -82,4 +86,4 @@ class nand4_dec(design.design):
Overrides base class function.
"""
self.add_graph_edges(graph, port_nets)

View File

@ -10,7 +10,6 @@ import debug
import utils
from tech import GDS, layer, parameter, drc
from tech import cell_properties as props
from globals import OPTS
import logical_effort
@ -28,12 +27,24 @@ class sense_amp(design.design):
props.sense_amp.pin.vdd,
props.sense_amp.pin.gnd]
type_list = ["INPUT", "INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
if not OPTS.netlist_only:
(width, height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"])
else:
(width, height) = (0, 0)
pin_map = []
cell_size_layer = "boundary"
def __init__(self, name="sense_amp"):
super().__init__(name)
debug.info(2, "Create sense_amp")
(width, height) = utils.get_libcell_size(self.cell_name,
GDS["unit"],
layer[self.cell_size_layer])
pin_map = utils.get_libcell_pins(self.pin_names,
self.cell_name,
GDS["unit"])
self.width = width
self.height = height
self.pin_map = pin_map
self.add_pin_types(self.type_list)
def get_bl_names(self):
return props.sense_amp.pin.bl
@ -49,26 +60,17 @@ class sense_amp(design.design):
def en_name(self):
return props.sense_amp.pin.en
def __init__(self, name):
super().__init__(name)
debug.info(2, "Create sense_amp")
self.width = sense_amp.width
self.height = sense_amp.height
self.pin_map = sense_amp.pin_map
self.add_pin_types(self.type_list)
def get_cin(self):
# FIXME: This input load will be applied to both the s_en timing and bitline timing.
# Input load for the bitlines which are connected to the source/drain of a TX. Not the selects.
from tech import spice
# Default is 8x. Per Samira and Hodges-Jackson book:
# "Column-mux transistors driven by the decoder must be sized for optimal speed"
bitline_pmos_size = 8 # FIXME: This should be set somewhere and referenced. Probably in tech file.
return spice["min_tx_drain_c"] * bitline_pmos_size # ff
def get_stage_effort(self, load):
# Delay of the sense amp will depend on the size of the amp and the output load.
parasitic_delay = 1
@ -82,14 +84,14 @@ class sense_amp(design.design):
# Power in this module currently not defined. Returns 0 nW (leakage and dynamic).
total_power = self.return_power()
return total_power
def get_enable_name(self):
"""Returns name used for enable net"""
# FIXME: A better programmatic solution to designate pins
enable_name = self.en_name
debug.check(enable_name in self.pin_names, "Enable name {} not found in pin list".format(enable_name))
return enable_name
def build_graph(self, graph, inst_name, port_nets):
"""Adds edges based on inputs/outputs. Overrides base class function."""
self.add_graph_edges(graph, port_nets)

View File

@ -8,43 +8,51 @@
import debug
import design
import utils
from tech import GDS,layer
from tech import GDS, layer
class tri_gate(design.design):
"""
This module implements the tri gate cell used in the design forS
bit-line isolation. It is a hand-made cell, so the layout and
netlist should be available in the technology library.
netlist should be available in the technology library.
"""
pin_names = ["in", "out", "en", "en_bar", "vdd", "gnd"]
type_list = ["INPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"]
(width,height) = utils.get_libcell_size("tri_gate", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "tri_gate", GDS["unit"])
cell_size_layer = "boundary"
unique_id = 1
def __init__(self, name=""):
if name=="":
name = "tri{0}".format(tri_gate.unique_id)
tri_gate.unique_id += 1
design.design.__init__(self, name)
super().__init__(self, name)
debug.info(2, "Create tri_gate")
self.width = tri_gate.width
self.height = tri_gate.height
self.pin_map = tri_gate.pin_map
(width, height) = utils.get_libcell_size(self.cell_name,
GDS["unit"],
layer[self.cell_size_layer])
pin_map = utils.get_libcell_pins(self.pin_names,
self.cell_name,
GDS["unit"])
self.width = width
self.height = height
self.pin_map = pin_map
self.add_pin_types(self.type_list)
def analytical_power(self, corner, load):
"""Returns dynamic and leakage power. Results in nW"""
#Power in this module currently not defined. Returns 0 nW (leakage and dynamic).
total_power = self.return_power()
total_power = self.return_power()
return total_power
def get_cin(self):
return 9*spice["min_tx_gate_c"]
def build_graph(self, graph, inst_name, port_nets):
def build_graph(self, graph, inst_name, port_nets):
"""Adds edges based on inputs/outputs. Overrides base class function."""
self.add_graph_edges(graph, port_nets)
self.add_graph_edges(graph, port_nets)

View File

@ -8,13 +8,13 @@
import debug
import design
import utils
from globals import OPTS
from tech import GDS,layer
from tech import GDS, layer
from tech import cell_properties as props
class write_driver(design.design):
"""
Tristate write driver to be active during write operations only.
Tristate write driver to be active during write operations only.
This module implements the write driver cell used in the design. It
is a hand-made cell, so the layout and netlist should be available in
the technology library.
@ -28,20 +28,23 @@ class write_driver(design.design):
props.write_driver.pin.gnd]
type_list = ["INPUT", "OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
if not OPTS.netlist_only:
(width,height) = utils.get_libcell_size("write_driver", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "write_driver", GDS["unit"])
else:
(width,height) = (0,0)
pin_map = []
cell_size_layer = "boundary"
def __init__(self, name):
design.design.__init__(self, name)
super().__init__(name)
debug.info(2, "Create write_driver")
self.width = write_driver.width
self.height = write_driver.height
self.pin_map = write_driver.pin_map
(width, height) = utils.get_libcell_size(self.cell_name,
GDS["unit"],
layer[self.cell_size_layer])
pin_map = utils.get_libcell_pins(self.pin_names,
self.cell_name,
GDS["unit"])
self.width = width
self.height = height
self.pin_map = pin_map
self.add_pin_types(self.type_list)
def get_bl_names(self):
@ -63,6 +66,6 @@ class write_driver(design.design):
# This is approximated from SCMOS. It has roughly 5 3x transistor gates.
return 5*3
def build_graph(self, graph, inst_name, port_nets):
def build_graph(self, graph, inst_name, port_nets):
"""Adds edges based on inputs/outputs. Overrides base class function."""
self.add_graph_edges(graph, port_nets)
self.add_graph_edges(graph, port_nets)

View File

@ -25,16 +25,16 @@ def parse_html(file, comment):
end_tag = comment+'-->'
with open(file, 'r') as f:
file_string = f.read()
with open(file, 'w') as f:
file_string = file_string.replace(start_tag,"")
file_string = file_string.replace(end_tag,"")
f.write(file_string)
def uncomment(comments):
comment_files = []
for datasheet in datasheet_list:

View File

@ -112,7 +112,7 @@ def parse_characterizer_csv(f, pages):
DATETIME = row[col]
col += 1
ANALYTICAL_MODEL = row[col]
col += 1
@ -121,7 +121,7 @@ def parse_characterizer_csv(f, pages):
LVS = row[col]
col += 1
AREA = row[col]
col += 1
@ -565,7 +565,7 @@ def parse_characterizer_csv(f, pages):
for element in row[col_start:col-1]:
sheet.description.append(str(element))
break
# parse initial power and leakage information
# parse initial power and leakage information
while(True):
start = col
if(row[col].startswith('power')):

View File

@ -9,6 +9,7 @@ import os
import inspect
import globals
import sys
import pdb
# the debug levels:
# 0 = minimum output (default)
@ -26,9 +27,9 @@ def check(check, str):
log("ERROR: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
if globals.OPTS.debug_level > 0:
import pdb
if globals.OPTS.debug:
pdb.set_trace()
assert 0
@ -40,9 +41,9 @@ def error(str, return_value=0):
log("ERROR: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
if globals.OPTS.debug_level > 0 and return_value != 0:
import pdb
if globals.OPTS.debug:
pdb.set_trace()
assert return_value == 0
@ -96,7 +97,7 @@ log.create_file = True
def info(lev, str):
from globals import OPTS
if (OPTS.debug_level >= lev):
if (OPTS.verbose_level >= lev):
frm = inspect.stack()[1]
mod = inspect.getmodule(frm[0])
# classname = frm.f_globals['__name__']

View File

@ -46,7 +46,7 @@ class design_rules(dict):
def keys(self):
return self.rules.keys()
def add_layer(self, name, width, spacing, area=0):
# Minimum width
self.add("minwidth_{}".format(name), width)
@ -54,7 +54,7 @@ class design_rules(dict):
self.add("{0}_to_{0}".format(name), spacing)
# Minimum area
self.add("minarea_{}".format(name), area)
def add_enclosure(self, name, layer, enclosure, extension=None):
self.add("{0}_enclose_{1}".format(name, layer), enclosure)
# Reserved for asymmetric enclosures
@ -62,4 +62,4 @@ class design_rules(dict):
self.add("{0}_extend_{1}".format(name, layer), extension)
else:
self.add("{0}_extend_{1}".format(name, layer), enclosure)

View File

@ -44,7 +44,7 @@ class drc_lut():
if k1 < k2:
return False
return True

View File

@ -19,7 +19,7 @@ class drc_value():
Return the value.
"""
return self.value

View File

@ -2,7 +2,7 @@ word_size = 32
num_words = 128
tech_name = "scn4m_subm"
nominal_corners_only = False
nominal_corner_only = False
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]

View File

@ -6,7 +6,7 @@ num_r_ports = 1
num_w_ports = 0
tech_name = "scn4m_subm"
nominal_corners_only = False
nominal_corner_only = False
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]

View File

@ -0,0 +1,21 @@
word_size = 4
num_words = 16
write_size = 2
num_rw_ports = 1
num_r_ports = 0
num_w_ports = 1
tech_name = "scn4m_subm"
nominal_corner_only = False
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]
route_supplies = False
check_lvsdrc = True
output_path = "temp"
output_name = "sram_1rw_1r_{0}_{1}_{2}".format(word_size,
num_words,
tech_name)

View File

@ -7,7 +7,7 @@ num_r_ports = 0
num_w_ports = 0
tech_name = "scn4m_subm"
nominal_corners_only = False
nominal_corner_only = False
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]

View File

@ -6,7 +6,7 @@ num_r_ports = 1
num_w_ports = 1
tech_name = "scn4m_subm"
nominal_corners_only = False
nominal_corner_only = False
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]

View File

@ -0,0 +1,21 @@
word_size = 2
num_words = 16
num_rw_ports = 2
num_r_ports = 0
num_w_ports = 0
tech_name = "scn4m_subm"
nominal_corner_only = False
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]
route_supplies = False
check_lvsdrc = True
output_path = "temp"
output_name = "sram_1w_1r_{0}_{1}_{2}".format(word_size,
num_words,
tech_name)

View File

@ -2,14 +2,14 @@ word_size = 2
num_words = 16
tech_name = "freepdk45"
nominal_corners_only = False
nominal_corner_only = False
process_corners = ["TT"]
supply_voltages = [1.0]
temperatures = [25]
route_supplies = False
check_lvsdrc = True
# nominal_corners_only = True
# nominal_corner_only = True
load_scales = [0.5, 1, 4]
slew_scales = [0.5, 1]

View File

@ -2,7 +2,7 @@ word_size = 2
num_words = 16
tech_name = "scn4m_subm"
nominal_corners_only = False
nominal_corner_only = False
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]

View File

@ -2,7 +2,7 @@ word_size = 64
num_words = 1024
tech_name = "scn4m_subm"
nominal_corners_only = False
nominal_corner_only = False
process_corners = ["TT"]
supply_voltages = [ 5.0 ]
temperatures = [ 25 ]

View File

@ -2,7 +2,7 @@ word_size = 16
num_words = 256
tech_name = "scn4m_subm"
nominal_corners_only = False
nominal_corner_only = False
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]

View File

@ -9,7 +9,7 @@ num_r_ports = 1
num_w_ports = 0
tech_name = "freepdk45"
nominal_corners_only = True
nominal_corner_only = True
route_supplies = False
check_lvsdrc = False

View File

@ -9,7 +9,7 @@ num_r_ports = 1
num_w_ports = 0
tech_name = "scn4m_subm"
nominal_corners_only = True
nominal_corner_only = True
route_supplies = False
check_lvsdrc = False

View File

@ -7,7 +7,7 @@ num_r_ports = 1
num_w_ports = 0
tech_name = "scn4m_subm"
nominal_corners_only = True
nominal_corner_only = True
route_supplies = True
check_lvsdrc = True

View File

@ -7,7 +7,7 @@ num_r_ports = 1
num_w_ports = 0
tech_name = "scn4m_subm"
nominal_corners_only = True
nominal_corner_only = True
route_supplies = True
check_lvsdrc = True

View File

@ -9,7 +9,7 @@ num_r_ports = 1
num_w_ports = 0
tech_name = "scn4m_subm"
nominal_corners_only = True
nominal_corner_only = True
route_supplies = False
check_lvsdrc = False

View File

@ -7,7 +7,7 @@ num_r_ports = 1
num_w_ports = 0
tech_name = "scn4m_subm"
nominal_corners_only = True
nominal_corner_only = True
route_supplies = True
check_lvsdrc = True

View File

@ -9,7 +9,7 @@ num_r_ports = 1
num_w_ports = 0
tech_name = "scn4m_subm"
nominal_corners_only = True
nominal_corner_only = True
route_supplies = False
check_lvsdrc = False

View File

@ -0,0 +1,26 @@
word_size = 32
num_words = 256
write_size = 8
local_array_size = 16
num_rw_ports = 1
num_r_ports = 0
num_w_ports = 0
tech_name = "sky130"
nominal_corner_only = True
route_supplies = False
check_lvsdrc = True
perimeter_pins = False
#netlist_only = True
#analytical_delay = False
output_path = "macros/sram_1rw_{0}_{1}_{2}_{3}".format(word_size,
num_words,
write_size,
tech_name)
output_name = "sram_1rw_{0}_{1}_{2}_{3}".format(word_size,
num_words,
write_size,
tech_name)

View File

@ -9,11 +9,11 @@ num_r_ports = 1
num_w_ports = 0
tech_name = "sky130"
nominal_corners_only = True
nominal_corner_only = True
route_supplies = True
route_supplies = False
check_lvsdrc = True
perimeter_pins = True
perimeter_pins = False
#netlist_only = True
#analytical_delay = False
output_path = "macros/sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size,

View File

@ -0,0 +1,26 @@
word_size = 32
num_words = 512
write_size = 8
local_array_size = 16
num_rw_ports = 1
num_r_ports = 0
num_w_ports = 0
tech_name = "sky130"
nominal_corner_only = True
route_supplies = False
check_lvsdrc = True
perimeter_pins = False
#netlist_only = True
#analytical_delay = False
output_path = "macros/sram_1rw_{0}_{1}_{2}_{3}".format(word_size,
num_words,
write_size,
tech_name)
output_name = "sram_1rw_{0}_{1}_{2}_{3}".format(word_size,
num_words,
write_size,
tech_name)

View File

@ -9,11 +9,11 @@ num_r_ports = 1
num_w_ports = 0
tech_name = "sky130"
nominal_corners_only = True
nominal_corner_only = True
route_supplies = True
route_supplies = False
check_lvsdrc = True
perimeter_pins = True
perimeter_pins = False
#netlist_only = True
#analytical_delay = False
output_path = "macros/sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size,

View File

@ -0,0 +1,26 @@
word_size = 32
num_words = 1024
write_size = 8
local_array_size = 16
num_rw_ports = 1
num_r_ports = 0
num_w_ports = 0
tech_name = "sky130"
nominal_corner_only = True
route_supplies = False
check_lvsdrc = True
perimeter_pins = False
#netlist_only = True
#analytical_delay = False
output_path = "macros/sram_1rw_{0}_{1}_{2}_{3}".format(word_size,
num_words,
write_size,
tech_name)
output_name = "sram_1rw_{0}_{1}_{2}_{3}".format(word_size,
num_words,
write_size,
tech_name)

View File

@ -9,11 +9,11 @@ num_r_ports = 1
num_w_ports = 0
tech_name = "sky130"
nominal_corners_only = True
nominal_corner_only = True
route_supplies = True
route_supplies = False
check_lvsdrc = True
perimeter_pins = True
perimeter_pins = False
#netlist_only = True
#analytical_delay = False
output_path = "macros/sram_1rw1r_{0}_{1}_{2}_{3}".format(word_size,

View File

@ -1,19 +0,0 @@
word_size = 2
num_words = 16
tech_name = "scn4m_subm"
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]
#netlist_only = True
route_supplies = True
check_lvsdrc = True
output_name = "sram_{0}_{1}_{2}".format(word_size,
num_words,
tech_name)
drc_name = "magic"
lvs_name = "netgen"
pex_name = "magic"

View File

@ -1,19 +0,0 @@
word_size = 8
num_words = 128
tech_name = "scn4m_subm"
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]
route_supplies = True
check_lvsdrc = True
netlist_only = True
output_name = "sram_{0}_{1}_{2}".format(word_size,
num_words,
tech_name)
drc_name = "magic"
lvs_name = "netgen"
pex_name = "magic"

View File

@ -1,18 +0,0 @@
word_size = 16
num_words = 256
tech_name = "scn4m_subm"
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]
route_supplies = True
check_lvsdrc = True
netlist_only = True
output_name = "sram_{0}_{1}_{2}".format(word_size,
num_words,
tech_name)
drc_name = "magic"
lvs_name = "netgen"
pex_name = "magic"

View File

@ -1,18 +0,0 @@
word_size = 32
num_words = 128
tech_name = "scn4m_subm"
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]
route_supplies = True
check_lvsdrc = True
netlist_only = True
output_name = "sram_{0}_{1}_{2}".format(word_size,
num_words,
tech_name)
drc_name = "magic"
lvs_name = "netgen"
pex_name = "magic"

View File

@ -1,19 +0,0 @@
word_size = 64
num_words = 128
tech_name = "scn4m_subm"
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]
route_supplies = True
check_lvsdrc = True
output_path = "/home/jesse/thesis/outputs/run5"
output_name = "sram_{0}_{1}_{2}".format(word_size,
num_words,
tech_name)
drc_name = "magic"
lvs_name = "netgen"
pex_name = "magic"

View File

@ -1,31 +0,0 @@
word_size = 16
num_words = 16
num_rw_ports = 1
num_r_ports = 1
num_w_ports = 0
tech_name = "sky130"
accuracy_requirement = 0.05
magic_exe = ("magic", "magic")
nominal_corners_only = False
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]
netlist_only = False
route_supplies = "grid"
check_lvsdrc = False
#replica_bitcell_array = "/home/jesse/openram/technology/sky130/modules/replica_bitcell_array.py"
output_path = "sram_" + str(accuracy_requirement)
output_name = "sram_{0}_{1}_{2}_{3}".format(word_size,
num_words,
tech_name,
accuracy_requirement
)
write_size=8

View File

@ -6,7 +6,7 @@ class GdsStreamer:
"""
def __init__(self, workingDirectory = "."):
self.workingDirectory = os.path.abspath(workingDirectory)
def createStreamOutTemplate(self, sourceLibraryName, sourceCellName, gdsDestinationPath):
templateFile = open(self.workingDirectory+"/partStreamOut.tmpl","w")
templateFile.write("streamOutKeys = list(nil\n")
@ -70,7 +70,7 @@ class GdsStreamer:
templateFile = open(self.workingDirectory+"/partStreamIn.tmpl","w")
templateFile.write("streamInKeys = list(nil\n")
templateFile.write("'runDir \".\"\n")
templateFile.write("'inFile \""+inputGdsPath+"\"\n")
templateFile.write("'inFile \""+inputGdsPath+"\"\n")
templateFile.write("'primaryCell \"\"\n")
templateFile.write("'libName \""+sourceLibraryName+"\"\n")
templateFile.write("'techFileName \"\"\n")
@ -88,7 +88,7 @@ class GdsStreamer:
templateFile.write("'convertNode \"ignore\"\n")
templateFile.write("'keepPcell nil\n")
templateFile.write("'replaceBusBitChar nil\n")
templateFile.write("'skipUndefinedLPP nil\n")
templateFile.write("'skipUndefinedLPP nil\n")
templateFile.write("'ignoreBox nil\n")
templateFile.write("'mergeUndefPurposToDrawing nil\n")
templateFile.write("'reportPrecision nil\n")
@ -109,10 +109,10 @@ class GdsStreamer:
templateFile.write("'propSeparator \",\"\n")
templateFile.write("'userSkillFile \"\"\n")
templateFile.write("'rodDir \"\"\n")
templateFile.write("'refLibOrder \"\"\n")
templateFile.write("'refLibOrder \"\"\n")
templateFile.write(")\n")
templateFile.close()
def streamFromCadence(self, cadenceLibraryContainerPath, libraryName, cellName, outputPath):
#change into the cadence directory
outputPath = os.path.abspath(outputPath)
@ -132,7 +132,7 @@ class GdsStreamer:
os.remove(self.workingDirectory+"/partStreamOut.tmpl")
#and go back to whever it was we started from
os.chdir(currentPath)
def streamToCadence(self,cadenceLibraryContainerPath, libraryName, inputPath):
#change into the cadence directory
inputPath = os.path.abspath(inputPath)

View File

@ -11,19 +11,19 @@ class pdfLayout:
self.layout = theLayout
self.layerColors=dict()
self.scale = 1.0
def setScale(self,newScale):
self.scale = float(newScale)
def hexToRgb(self,hexColor):
"""
Takes a hexadecimal color string i.e. "#219E1C" and converts it to an rgb float triplet ranging 0->1
"""
red = int(hexColor[1:3],16)
green = int(hexColor[3:5],16)
blue = int(hexColor[5:7],16)
blue = int(hexColor[5:7],16)
return (float(red)/255,float(green)/255,float(blue)/255)
def randomHexColor(self):
"""
Generates a random color in hex using the format #ABC123
@ -50,26 +50,26 @@ class pdfLayout:
xyPoint = tMatrix * xyPoint
xyCoordinates += [(xyPoint[0],xyPoint[1])]
return xyCoordinates
def drawBoundary(self,boundary,origin,uVector,vVector):
#get the coordinates in the correct coordinate space
coordinates = self.transformCoordinates(boundary.coordinates,origin,uVector,vVector)
#method to draw a boundary with an XY offset
#method to draw a boundary with an XY offset
x=(coordinates[0][0])/self.scale
y=(coordinates[0][1])/self.scale
shape = pyx.path.path(pyx.path.moveto(x, y))
for index in range(1,len(coordinates)):
x=(coordinates[index][0])/self.scale
y=(coordinates[index][1])/self.scale
shape.append(pyx.path.lineto(x,y))
y=(coordinates[index][1])/self.scale
shape.append(pyx.path.lineto(x,y))
self.canvas.stroke(shape, [pyx.style.linewidth.thick])
if(boundary.drawingLayer in self.layerColors):
layerColor = self.hexToRgb(self.layerColors[boundary.drawingLayer])
self.canvas.fill(shape, [pyx.color.rgb(layerColor[0],layerColor[1],layerColor[2]), pyx.color.transparency(0.5)])
def drawPath(self,path,origin,uVector,vVector):
#method to draw a path with an XY offset
boundaryCoordinates = self.transformCoordinates(path.equivalentBoundaryCoordinates(),origin,uVector,vVector)
def drawPath(self,path,origin,uVector,vVector):
#method to draw a path with an XY offset
boundaryCoordinates = self.transformCoordinates(path.equivalentBoundaryCoordinates(),origin,uVector,vVector)
shape = pyx.path.path(pyx.path.moveto((boundaryCoordinates[0][0])/self.scale,(boundaryCoordinates[0][1])/self.scale))
for coordinate in boundaryCoordinates[1::]:
shape.append(pyx.path.lineto((coordinate[0])/self.scale,(coordinate[1])/self.scale))
@ -77,7 +77,7 @@ class pdfLayout:
if(path.drawingLayer in self.layerColors):
layerColor = self.hexToRgb(self.layerColors[path.drawingLayer])
self.canvas.fill(shape, [pyx.color.rgb(layerColor[0],layerColor[1],layerColor[2]), pyx.color.transparency(0.5)])
def drawLayout(self):
#use the layout xyTree and structureList
#to draw ONLY the geometry in each structure
@ -89,6 +89,6 @@ class pdfLayout:
self.drawBoundary(boundary,element[1],element[2], element[3])
for path in structureToDraw.paths:
self.drawPath(path,element[1],element[2], element[3])
def writeToFile(self,filename):
self.canvas.writePDFfile(filename)

View File

@ -28,7 +28,7 @@ import unit
#
class bbox_pt:
"""class for bounding boxes
This variant requires points in the constructor, and is used for internal

View File

@ -47,7 +47,7 @@ class canvasitem:
- the PS code corresponding to the canvasitem has to be written in the
stream file, which provides a write(string) method
- writer is the PSwriter used for the output
- context is an instance of pswriter.context which is used for keeping
- context is an instance of pswriter.context which is used for keeping
track of the graphics state (current linewidth, colorspace and font))
- registry is used for tracking resources needed by the canvasitem
- bbox has to be updated to include the bounding box of the canvasitem
@ -63,7 +63,7 @@ class canvasitem:
- writer is the PDFwriter used for the output, which contains properties
like whether streamcompression is used
- context is an instance of pdfwriter.context which is used for keeping
track of the graphics state, in particular for the emulation of PS
track of the graphics state, in particular for the emulation of PS
behaviour regarding fill and stroke styles, for keeping track of the
currently selected font as well as of text regions.
- registry is used for tracking resources needed by the canvasitem
@ -145,8 +145,8 @@ class _canvas(canvasitem):
attr.checkattrs(attrs, [trafo.trafo_pt, clip, style.strokestyle, style.fillstyle])
# We have to reverse the trafos such that the PostScript concat operators
# are in the right order. Correspondingly, we below multiply the current self.trafo
# from the right.
# Note that while for the stroke and fill styles the order doesn't matter at all,
# from the right.
# Note that while for the stroke and fill styles the order doesn't matter at all,
# this is not true for the clip operation.
attrs = attrs[:]
attrs.reverse()

View File

@ -1188,7 +1188,7 @@ class parallel(deformer): # <<<
intsparams = np[nsp_i][nspitem_i].intersect(np[nsp_j][nspitem_j], epsilon)
if intsparams:
for intsparam_i, intsparam_j in intsparams:
if ( (abs(intsparam_i) < epsilon and abs(1-intsparam_j) < epsilon) or
if ( (abs(intsparam_i) < epsilon and abs(1-intsparam_j) < epsilon) or
(abs(intsparam_j) < epsilon and abs(1-intsparam_i) < epsilon) ):
continue
npp_i = normpath.normpathparam(np, nsp_i, float(nspitem_i)+intsparam_i)

View File

@ -467,7 +467,7 @@ class font:
return fontinfo
def __str__(self):
return "font %s designed at %g TeX pts used at %g TeX pts" % (self.name,
return "font %s designed at %g TeX pts used at %g TeX pts" % (self.name,
16.0*self.d/16777216L,
16.0*self.q/16777216L)
__repr__ = __str__
@ -510,7 +510,7 @@ class font:
def _convert_tfm_to_ds(self, length):
return (16*long(round(length*float(self.q)*self.tfmconv))/16777216) * self.pyxconv * 1000 / self.getsize_pt()
def _convert_tfm_to_pt(self, length):
return (16*long(round(length*float(self.q)*self.tfmconv))/16777216) * self.pyxconv
@ -528,7 +528,7 @@ class font:
def getitalic_dvi(self, charcode):
return self._convert_tfm_to_dvi(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
# routines returning lengths as integers in design size (AFM) units
# routines returning lengths as integers in design size (AFM) units
def getwidth_ds(self, charcode):
return self._convert_tfm_to_ds(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
@ -767,7 +767,7 @@ class dvifile:
if fontslant is not None:
fontslant = float(fontslant)
# XXX we currently misuse use self.activefont as metric
# XXX we currently misuse use self.activefont as metric
font = type1font.font(fontbasefontname, fontfilename, fontencoding, fontslant, self.activefont)
self.activetext = type1font.text_pt(self.pos[_POS_H] * self.pyxconv, -self.pos[_POS_V] * self.pyxconv, font)
@ -973,14 +973,14 @@ class dvifile:
den = afile.readuint32()
self.mag = afile.readuint32()
# For the interpretation of the lengths in dvi and tfm files,
# For the interpretation of the lengths in dvi and tfm files,
# three conversion factors are relevant:
# - self.tfmconv: tfm units -> dvi units
# - self.pyxconv: dvi units -> (PostScript) points
# - self.conv: dvi units -> pixels
self.tfmconv = (25400000.0/num)*(den/473628672.0)/16.0
# calculate conv as described in the DVIType docu using
# calculate conv as described in the DVIType docu using
# a given resolution in dpi
self.resolution = 300.0
self.conv = (num/254000.0)*(self.resolution/den)

View File

@ -319,7 +319,7 @@ class epsfile(canvas.canvasitem):
try:
epsfile=open(self.filename,"rb")
except:
raise IOError, "cannot open EPS file '%s'" % self.filename
raise IOError, "cannot open EPS file '%s'" % self.filename
file.write("BeginEPSF\n")
@ -330,7 +330,7 @@ class epsfile(canvas.canvasitem):
self.trafo.processPS(file, writer, context, registry, bbox)
file.write("%%%%BeginDocument: %s\n" % self.filename)
file.write(epsfile.read())
file.write(epsfile.read())
file.write("%%EndDocument\n")
file.write("EndEPSF\n")

View File

@ -166,4 +166,4 @@ def realpolyroots(*cs):
# else:
# rs.append(r)
# return rs
#
#

View File

@ -710,9 +710,9 @@ class arct_pt(pathitem):
# Negative (positive) angles alpha corresponds to a turn to the right (left)
# as seen from currentpoint.
if dx1*dy2-dy1*dx2 > 0:
alpha = acos(dx1*dx2+dy1*dy2)
alpha = acos(dx1*dx2+dy1*dy2)
else:
alpha = -acos(dx1*dx2+dy1*dy2)
alpha = -acos(dx1*dx2+dy1*dy2)
try:
# two tangent points
@ -744,7 +744,7 @@ class arct_pt(pathitem):
return [line, arcn_pt(mx_pt, my_pt, self.r_pt, phi-deltaphi, phi+deltaphi)]
except ZeroDivisionError:
# in the degenerate case, we just return a line as specified by the PS
# in the degenerate case, we just return a line as specified by the PS
# language reference
return [lineto_pt(self.x1_pt, self.y1_pt)]

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