mirror of https://github.com/VLSIDA/OpenRAM.git
Merged with dev
This commit is contained in:
commit
84ba5c55d1
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ class delay_data():
|
|||
assert isinstance(other, delay_data)
|
||||
return delay_data(other.delay + self.delay,
|
||||
self.slew)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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__):
|
||||
|
|
|
|||
|
|
@ -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 .
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -11,4 +11,4 @@ from enum import Enum
|
|||
class bit_polarity(Enum):
|
||||
NONINVERTING = 0
|
||||
INVERTING = 1
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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')):
|
||||
|
|
|
|||
|
|
@ -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__']
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class drc_lut():
|
|||
if k1 < k2:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class drc_value():
|
|||
Return the value.
|
||||
"""
|
||||
return self.value
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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 ]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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,
|
||||
|
|
@ -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)
|
||||
|
|
@ -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,
|
||||
|
|
@ -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)
|
||||
|
|
@ -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,
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -166,4 +166,4 @@ def realpolyroots(*cs):
|
|||
# else:
|
||||
# rs.append(r)
|
||||
# return rs
|
||||
#
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue