Merge branch 'dev' of github:VLSIDA/OpenRAM into CalibrePexFilesUpdate

This commit is contained in:
Bob Vanhoof 2020-08-03 09:32:27 +02:00
commit 487bb6c6e9
21 changed files with 484 additions and 305 deletions

View File

@ -12,6 +12,69 @@ from vector import vector
import design
class channel_net():
def __init__(self, net_name, pins, vertical):
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)
self.max_value = max(i.uy() for i in pins)
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
overlap if any pin overlaps.
"""
for pin1 in self.pins:
for pin2 in other.pins:
if self.pin_overlap(pin1, pin2, pitch):
return True
return False
def segment_overlap(self, other):
"""
Check if the horizontal span of the two nets overlaps eachother.
"""
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
@ -21,7 +84,8 @@ class channel_route(design.design):
offset,
layer_stack,
directions=None,
vertical=False):
vertical=False,
parent=None):
"""
The net list is a list of the nets with each net being a list of pins
to be connected. The offset is the lower-left of where the
@ -40,6 +104,8 @@ class channel_route(design.design):
self.layer_stack = layer_stack
self.directions = directions
self.vertical = vertical
# For debugging...
self.parent = parent
if not directions or directions == "pref":
# Use the preferred layer directions
@ -86,114 +152,139 @@ class channel_route(design.design):
# FIXME: This is O(n^2), so maybe optimize it.
for other_pin, conflicts in g.items():
if pin in conflicts:
conflicts.remove(pin)
g[other_pin]=conflicts
g[other_pin].remove(pin)
return g
def route(self):
# Create names for the nets for the graphs
nets = []
index = 0
# print(self.netlist)
for pin_list in self.netlist:
nets.append(channel_net("n{}".format(index), pin_list, self.vertical))
index += 1
def vcg_nets_overlap(self, net1, net2):
"""
Check all the pin pairs on two nets and return a pin
overlap if any pin overlaps.
"""
# Create the (undirected) horizontal constraint graph
hcg = collections.OrderedDict()
for net1 in nets:
for net2 in nets:
if net1.name == net2.name:
continue
if net1.segment_overlap(net2):
try:
hcg[net1.name].add(net2.name)
except KeyError:
hcg[net1.name] = set([net2.name])
try:
hcg[net2.name].add(net1.name)
except KeyError:
hcg[net2.name] = set([net1.name])
# Initialize the vertical conflict graph (vcg)
# and make a list of all pins
vcg = collections.OrderedDict()
# print("Nets:")
# for net_name in nets:
# print(net_name, [x.name for x in nets[net_name]])
# Find the vertical pin conflicts
# FIXME: O(n^2) but who cares for now
if self.vertical:
pitch = self.horizontal_nonpref_pitch
else:
pitch = self.vertical_nonpref_pitch
for pin1 in net1:
for pin2 in net2:
if self.vcg_pin_overlap(pin1, pin2, pitch):
return True
for net in nets:
vcg[net.name] = set()
return False
def route(self):
# FIXME: Must extend this to a horizontal conflict graph
# too if we want to minimize the
# number of tracks!
# hcg = {}
# Initialize the vertical conflict graph (vcg)
# and make a list of all pins
vcg = collections.OrderedDict()
# Create names for the nets for the graphs
nets = collections.OrderedDict()
index = 0
# print(netlist)
for pin_list in self.netlist:
net_name = "n{}".format(index)
index += 1
nets[net_name] = pin_list
# print("Nets:")
# for net_name in nets:
# print(net_name, [x.name for x in nets[net_name]])
# Find the vertical pin conflicts
# FIXME: O(n^2) but who cares for now
for net_name1 in nets:
if net_name1 not in vcg.keys():
vcg[net_name1] = []
for net_name2 in nets:
if net_name2 not in vcg.keys():
vcg[net_name2] = []
for net1 in nets:
for net2 in nets:
# Skip yourself
if net_name1 == net_name2:
if net1.name == net2.name:
continue
if self.vcg_nets_overlap(nets[net_name1],
nets[net_name2]):
vcg[net_name2].append(net_name1)
if net1.pins_overlap(net2, pitch):
vcg[net2.name].add(net1.name)
current_offset = self.offset
# Check if there are any cycles net1 <---> net2 in the VCG
# list of routes to do
while vcg:
# Some of the pins may be to the left/below the channel offset,
# so adjust if this is the case
self.min_value = min([n.min_value for n in nets])
self.max_value = min([n.max_value for n in nets])
if self.vertical:
real_channel_offset = vector(self.offset.x, min(self.min_value, self.offset.y))
else:
real_channel_offset = vector(min(self.min_value, self.offset.x), self.offset.y)
current_offset = real_channel_offset
# Sort nets by left edge value
nets.sort()
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():
# print(name, net.min_value, net.max_value, net.conflicts)
# print(current_offset)
# get a route from conflict graph with empty fanout set
net_name = None
for net_name, conflicts in vcg.items():
if len(conflicts) == 0:
vcg = self.remove_net_from_graph(net_name, vcg)
for net in nets:
# If it has no conflicts and the interval is to the right of the current offset in the track
if net.min_value >= current_offset_value and len(vcg[net.name]) == 0:
# print("Routing {}".format(net.name))
# Add the trunk routes from the bottom up for
# horizontal or the left to right for vertical
if self.vertical:
self.add_vertical_trunk_route(net.pins,
current_offset,
self.vertical_nonpref_pitch)
current_offset = vector(current_offset.x, net.max_value + self.horizontal_nonpref_pitch)
else:
self.add_horizontal_trunk_route(net.pins,
current_offset,
self.horizontal_nonpref_pitch)
current_offset = vector(net.max_value + self.vertical_nonpref_pitch, current_offset.y)
# Remove the net from other constriants in the VCG
vcg = self.remove_net_from_graph(net.name, vcg)
nets.remove(net)
break
else:
# FIXME: We don't support cyclic VCGs right now.
debug.error("Cyclic VCG in channel router.", -1)
# These are the pins we'll have to connect
pin_list = nets[net_name]
# print("Routing:", net_name, [x.name for x in pin_list])
# Remove the net from other constriants in the VCG
vcg = self.remove_net_from_graph(net_name, vcg)
# Add the trunk routes from the bottom up for
# horizontal or the left to right for vertical
if self.vertical:
self.add_vertical_trunk_route(pin_list,
current_offset,
self.vertical_nonpref_pitch)
# This accounts for the via-to-via spacings
current_offset += vector(self.horizontal_nonpref_pitch, 0)
else:
self.add_horizontal_trunk_route(pin_list,
current_offset,
self.horizontal_nonpref_pitch)
# This accounts for the via-to-via spacings
current_offset += vector(0, self.vertical_nonpref_pitch)
# If we made a full pass and the offset didn't change...
current_offset_value = current_offset.y if self.vertical else current_offset.x
initial_offset_value = real_channel_offset.y if self.vertical else real_channel_offset.x
if current_offset_value == initial_offset_value:
debug.info(0, "Channel offset: {}".format(real_channel_offset))
debug.info(0, "Current offset: {}".format(current_offset))
debug.info(0, "VCG {}".format(str(vcg)))
debug.info(0, "HCG {}".format(str(hcg)))
for net in nets:
debug.info(0, "{0} pin: {1}".format(net.name, str(net.pins)))
if self.parent:
debug.info(0, "Saving vcg.gds")
self.parent.gds_write("vcg.gds")
debug.error("Cyclic VCG in channel router.", -1)
# Increment the track and reset the offset to the start (like a typewriter)
if self.vertical:
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 = 0
self.height = current_offset.y
return current_offset.y + self.vertical_nonpref_pitch - self.offset.y
self.width = current_offset.x + self.horizontal_nonpref_pitch - self.offset.x
self.height = self.max_value + self.vertical_nonpref_pitch - self.offset.y
else:
self.width = current_offset.x
self.height = 0
return current_offset.x + self.horizontal_nonpref_pitch - self.offset.x
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:
@ -226,6 +317,17 @@ class channel_route(design.design):
self.add_path(self.vertical_layer,
[vector(min_x - half_layer_width, trunk_offset.y),
vector(max_x + half_layer_width, trunk_offset.y)])
# Route each pin to the trunk
for pin in pins:
if pin.cy() < trunk_offset.y:
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])
else:
# Add the horizontal trunk
self.add_path(self.horizontal_layer,
@ -269,6 +371,17 @@ class channel_route(design.design):
self.add_path(self.horizontal_layer,
[vector(trunk_offset.x, min_y - half_layer_width),
vector(trunk_offset.x, max_y + half_layer_width)])
# Route each pin to the trunk
for pin in pins:
# Find the correct side of the pin
if pin.cx() < trunk_offset.x:
pin_pos = pin.rc()
else:
pin_pos = pin.lc()
# No bend needed here
mid = vector(trunk_offset.x, pin_pos.y)
self.add_path(self.horizontal_layer, [pin_pos, mid])
else:
# Add the vertical trunk
self.add_path(self.vertical_layer,
@ -292,18 +405,4 @@ class channel_route(design.design):
to_layer=self.horizontal_layer,
offset=pin_pos)
def vcg_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

View File

@ -1018,7 +1018,7 @@ class layout():
Wrapper to create a vertical channel route
"""
import channel_route
cr = channel_route.channel_route(netlist, offset, layer_stack, directions, vertical=True)
cr = channel_route.channel_route(netlist, offset, layer_stack, directions, vertical=True, parent=self)
self.add_inst("vc", cr)
self.connect_inst([])
@ -1027,7 +1027,7 @@ class layout():
Wrapper to create a horizontal channel route
"""
import channel_route
cr = channel_route.channel_route(netlist, offset, layer_stack, directions, vertical=False)
cr = channel_route.channel_route(netlist, offset, layer_stack, directions, vertical=False, parent=self)
self.add_inst("hc", cr)
self.connect_inst([])

View File

@ -185,9 +185,8 @@ class delay(simulation):
self.sen_meas = delay_measure("delay_sen", self.clk_frmt, self.sen_name+"{}", "FALL", "RISE", measure_scale=1e9)
self.sen_meas.meta_str = sram_op.READ_ZERO
self.sen_meas.meta_add_delay = True
self.dout_volt_meas.append(self.sen_meas)
return self.dout_volt_meas
return self.dout_volt_meas + [self.sen_meas]
def create_read_bit_measures(self):
""" Adds bit measurements for read0 and read1 cycles """
@ -1351,7 +1350,7 @@ class delay(simulation):
Return the analytical model results for the SRAM.
"""
if OPTS.num_rw_ports > 1 or OPTS.num_w_ports > 0 and OPTS.num_r_ports > 0:
debug.warning("Analytical characterization results are not supported for multiport.")
debug.warning("In analytical mode, all ports have the timing of the first read port.")
# Probe set to 0th bit, does not matter for analytical delay.
self.set_probe('0'*self.addr_size, 0)

View File

@ -1,9 +1,9 @@
word_size = 2
num_words = 16
num_rw_ports = 1
num_rw_ports = 0
num_r_ports = 1
num_w_ports = 0
num_w_ports = 1
tech_name = "scn4m_subm"
nominal_corners_only = False

View File

@ -211,10 +211,11 @@ class bank(design.design):
self.port_data_offsets[port] = vector(self.main_bitcell_array_left - self.bitcell_array.cell.width, 0)
# UPPER LEFT QUADRANT
# To the left of the bitcell array
# To the left of the bitcell array above the predecoders and control logic
x_offset = self.m2_gap + self.port_address.width
self.port_address_offsets[port] = vector(-x_offset,
self.main_bitcell_array_bottom)
self.predecoder_height = self.port_address.predecoder_height + self.port_address_offsets[port].y
# LOWER LEFT QUADRANT
# Place the col decoder left aligned with wordline driver

View File

@ -314,24 +314,23 @@ class hierarchical_decoder(design.design):
for i in range(self.no_of_pre3x8):
self.place_pre3x8(i)
self.predecode_height = 0
if self.no_of_pre2x4 > 0:
self.predecode_height = self.pre2x4_inst[-1].uy()
if self.no_of_pre3x8 > 0:
self.predecode_height = self.pre3x8_inst[-1].uy()
def place_pre2x4(self, num):
""" Place 2x4 predecoder to the left of the origin """
if (self.num_inputs == 2):
base = vector(-self.pre2_4.width, 0)
else:
base= vector(-self.pre2_4.width, num * (self.pre2_4.height + self.predecoder_spacing))
base= vector(-self.pre2_4.width, num * (self.pre2_4.height + self.predecoder_spacing))
self.pre2x4_inst[num].place(base)
def place_pre3x8(self, num):
""" Place 3x8 predecoder to the left of the origin and above any 2x4 decoders """
if (self.num_inputs == 3):
offset = vector(-self.pre_3_8.width, 0)
else:
height = self.no_of_pre2x4 * (self.pre2_4.height + self.predecoder_spacing) + num * (self.pre3_8.height + self.predecoder_spacing)
offset = vector(-self.pre3_8.width, height)
height = self.no_of_pre2x4 * (self.pre2_4.height + self.predecoder_spacing) \
+ num * (self.pre3_8.height + self.predecoder_spacing)
offset = vector(-self.pre3_8.width, height)
self.pre3x8_inst[num].place(offset)
def create_row_decoder(self):

View File

@ -153,6 +153,8 @@ class port_address(design.design):
wordline_driver_offset = vector(self.row_decoder.width, 0)
self.wordline_driver_inst.place(wordline_driver_offset)
self.row_decoder_inst.place(row_decoder_offset)
# Pass this up
self.predecoder_height = self.row_decoder.predecoder_height
self.height = self.row_decoder.height
self.width = self.wordline_driver_inst.rx()

View File

@ -372,66 +372,78 @@ class pgate(design.design):
self.width = width
@staticmethod
def bin_width(tx_type, target_width):
def best_bin(tx_type, target_width):
"""
Determine the width transistor that meets the accuracy requirement and is larger than target_width.
"""
# Find all of the relavent scaled bins and multiples
scaled_bins = pgate.scaled_bins(tx_type, target_width)
for (scaled_width, multiple) in scaled_bins:
if abs(target_width - scaled_width) / target_width <= 1 - OPTS.accuracy_requirement:
break
else:
debug.error("failed to bin tx size {}, try reducing accuracy requirement".format(target_width), 1)
debug.info(2, "binning {0} tx, target: {4}, found {1} x {2} = {3}".format(tx_type,
multiple,
scaled_width / multiple,
scaled_width,
target_width))
return(scaled_width / multiple, multiple)
@staticmethod
def scaled_bins(tx_type, target_width):
"""
Determine a set of widths and multiples that could be close to the right size
sorted by the fewest number of fingers.
"""
if tx_type == "nmos":
bins = nmos_bins[drc("minwidth_poly")]
elif tx_type == "pmos":
bins = pmos_bins[drc("minwidth_poly")]
else:
debug.error("invalid tx type")
# Prune out bins that are too big, except for one bigger
bins = bins[0:bisect_left(bins, target_width) + 1]
# Determine multiple of target width for each bin
if len(bins) == 1:
selected_bin = bins[0]
scaling_factor = math.ceil(target_width / selected_bin)
scaled_bin = bins[0] * scaling_factor
scaled_bins = [(bins[0], math.ceil(target_width / bins[0]))]
else:
base_bins = []
scaled_bins = []
scaling_factors = []
# Add the biggest size as 1x multiple
scaled_bins.append((bins[-1], 1))
# Compute discrete multiple of other sizes
for width in reversed(bins[:-1]):
multiple = math.ceil(target_width / width)
scaled_bins.append((multiple * width, multiple))
for width in bins:
m = math.ceil(target_width / width)
base_bins.append(width)
scaling_factors.append(m)
scaled_bins.append(m * width)
select = -1
for i in reversed(range(0, len(scaled_bins))):
if abs(target_width - scaled_bins[i])/target_width <= 1-OPTS.accuracy_requirement:
select = i
break
if select == -1:
debug.error("failed to bin tx size {}, try reducing accuracy requirement".format(target_width), 1)
scaling_factor = scaling_factors[select]
scaled_bin = scaled_bins[select]
selected_bin = base_bins[select]
debug.info(2, "binning {0} tx, target: {4}, found {1} x {2} = {3}".format(tx_type, selected_bin, scaling_factor, selected_bin * scaling_factor, target_width))
return(selected_bin, scaling_factor)
def permute_widths(self, tx_type, target_width):
return(scaled_bins)
@staticmethod
def nearest_bin(tx_type, target_width):
"""
Determine the nearest width to the given target_width
while assuming a single multiple.
"""
if tx_type == "nmos":
bins = nmos_bins[drc("minwidth_poly")]
elif tx_type == "pmos":
bins = pmos_bins[drc("minwidth_poly")]
else:
debug.error("invalid tx type")
bins = bins[0:bisect_left(bins, target_width) + 1]
if len(bins) == 1:
scaled_bins = [(bins[0], math.ceil(target_width / bins[0]))]
else:
scaled_bins = []
scaled_bins.append((bins[-1], 1))
for width in bins[:-1]:
m = math.ceil(target_width / width)
scaled_bins.append((m * width, m))
debug.error("invalid tx type")
return(scaled_bins)
def bin_accuracy(self, ideal_width, width):
return 1-abs((ideal_width - width)/ideal_width)
# Find the next larger bin
bin_loc = bisect_left(bins, target_width)
if bin_loc < len(bins):
return bins[bin_loc]
else:
return bins[-1]
@staticmethod
def bin_accuracy(ideal_width, width):
return 1 - abs((ideal_width - width) / ideal_width)

View File

@ -14,15 +14,11 @@ from vector import vector
from math import ceil
from globals import OPTS
from utils import round_to_grid
from bisect import bisect_left
import logical_effort
from sram_factory import factory
from errors import drc_error
if(OPTS.tech_name == "sky130"):
from tech import nmos_bins, pmos_bins
class pinv(pgate.pgate):
"""
Pinv generates gds of a parametrically sized inverter. The
@ -164,8 +160,8 @@ class pinv(pgate.pgate):
else:
self.nmos_width = self.nmos_size * drc("minwidth_tx")
self.pmos_width = self.pmos_size * drc("minwidth_tx")
nmos_bins = self.permute_widths("nmos", self.nmos_width)
pmos_bins = self.permute_widths("pmos", self.pmos_width)
nmos_bins = self.scaled_bins("nmos", self.nmos_width)
pmos_bins = self.scaled_bins("pmos", self.pmos_width)
valid_pmos = []
for bin in pmos_bins:

View File

@ -13,6 +13,7 @@ from vector import vector
from globals import OPTS
from sram_factory import factory
class pinv_dec(pinv.pinv):
"""
This is another version of pinv but with layout for the decoder.
@ -50,9 +51,8 @@ class pinv_dec(pinv.pinv):
self.nmos_width = self.nmos_size * drc("minwidth_tx")
self.pmos_width = self.pmos_size * drc("minwidth_tx")
if OPTS.tech_name == "sky130":
(self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width)
(self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width)
return
self.nmos_width = self.nearest_bin("nmos", self.nmos_width)
self.pmos_width = self.nearest_bin("pmos", self.pmos_width)
# Over-ride the route input gate to call the horizontal version.
# Other top-level netlist and layout functions are not changed.

View File

@ -39,8 +39,8 @@ class pnand2(pgate.pgate):
self.tx_mults = 1
if OPTS.tech_name == "sky130":
(self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width)
(self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width)
self.nmos_width = self.nearest_bin("nmos", self.nmos_width)
self.pmos_width = self.nearest_bin("pmos", self.pmos_width)
# Creates the netlist and layout
pgate.pgate.__init__(self, name, height, add_wells)

View File

@ -42,8 +42,8 @@ class pnand3(pgate.pgate):
self.tx_mults = 1
if OPTS.tech_name == "sky130":
(self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width)
(self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width)
self.nmos_width = self.nearest_bin("nmos", self.nmos_width)
self.pmos_width = self.nearest_bin("pmos", self.pmos_width)
# Creates the netlist and layout
pgate.pgate.__init__(self, name, height, add_wells)

View File

@ -38,8 +38,8 @@ class pnor2(pgate.pgate):
self.tx_mults = 1
if OPTS.tech_name == "sky130":
(self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width)
(self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width)
self.nmos_width = self.nearest_bin("nmos", self.nmos_width)
self.pmos_width = self.nearest_bin("pmos", self.pmos_width)
# Creates the netlist and layout
pgate.pgate.__init__(self, name, height, add_wells)

View File

@ -9,11 +9,10 @@ import contact
import design
import debug
from pgate import pgate
from tech import parameter
from tech import parameter, drc
from vector import vector
from globals import OPTS
from sram_factory import factory
from tech import drc, layer
class precharge(design.design):
@ -81,7 +80,7 @@ class precharge(design.design):
Initializes the upper and lower pmos
"""
if(OPTS.tech_name == "sky130"):
(self.ptx_width, self.ptx_mults) = pgate.bin_width("pmos", self.ptx_width)
self.ptx_width = pgate.nearest_bin("pmos", self.ptx_width)
self.pmos = factory.create(module_type="ptx",
width=self.ptx_width,
mults=self.ptx_mults,

View File

@ -131,7 +131,7 @@ class ptx(design.design):
perimeter_sd = 2 * self.poly_width + 2 * self.tx_width
if OPTS.tech_name == "sky130" and OPTS.lvs_exe and OPTS.lvs_exe[0] == "calibre":
# sky130 simulation cannot use the mult parameter in simulation
(self.tx_width, self.mults) = pgate.bin_width(self.tx_type, self.tx_width)
(self.tx_width, self.mults) = pgate.best_bin(self.tx_type, self.tx_width)
main_str = "M{{0}} {{1}} {0} m={1} w={2} l={3} ".format(spice[self.tx_type],
self.mults,
self.tx_width,

View File

@ -100,9 +100,9 @@ class sram():
import verify
start_time = datetime.datetime.now()
# Output the extracted design if requested
sp_file = OPTS.output_path + "temp_pex.sp"
pexname = OPTS.output_path + self.s.name + ".pex.sp"
spname = OPTS.output_path + self.s.name + ".sp"
verify.run_pex(self.s.name, gdsname, spname, output=sp_file)
verify.run_pex(self.s.name, gdsname, spname, output=pexname)
print_time("Extraction", datetime.datetime.now(), start_time)
else:
# Use generated spice file for characterization

View File

@ -10,6 +10,8 @@ from vector import vector
from sram_base import sram_base
from contact import m2_via
from globals import OPTS
import channel_route
class sram_1bank(sram_base):
"""
@ -58,12 +60,14 @@ class sram_1bank(sram_base):
# the sense amps/column mux and cell array)
# The x-coordinate is placed to allow a single clock wire (plus an extra pitch)
# up to the row address DFFs.
control_pos = [None] * len(self.all_ports)
row_addr_pos = [None] * len(self.all_ports)
col_addr_pos = [None] * len(self.all_ports)
wmask_pos = [None] * len(self.all_ports)
spare_wen_pos = [None] * len(self.all_ports)
data_pos = [None] * len(self.all_ports)
self.control_pos = [None] * len(self.all_ports)
self.row_addr_pos = [None] * len(self.all_ports)
# DFFs are placd on their own
self.col_addr_pos = [None] * len(self.all_ports)
self.wmask_pos = [None] * len(self.all_ports)
self.spare_wen_pos = [None] * len(self.all_ports)
self.data_pos = [None] * len(self.all_ports)
# These positions utilize the channel route sizes.
# FIXME: Auto-compute these rather than manual computation.
@ -75,9 +79,11 @@ class sram_1bank(sram_base):
# Spare wen are on a separate layer so not included
# Start with 1 track minimum
self.data_bus_size = [1] * len(self.all_ports)
self.col_addr_bus_size = [1] * len(self.all_ports)
for port in self.all_ports:
# The column address wires are routed separately from the data bus and will always be smaller.
# All ports need the col addr flops
self.data_bus_size[port] += self.col_addr_size
self.col_addr_bus_size[port] = self.col_addr_size * self.m4_nonpref_pitch
# Write ports need the data input flops and write mask flops
if port in self.write_ports:
self.data_bus_size[port] += self.num_wmasks + self.word_size
@ -89,119 +95,152 @@ class sram_1bank(sram_base):
self.data_bus_size[port] *= self.m4_nonpref_pitch
# Add the gap in unit length
self.data_bus_size[port] += self.data_bus_gap
# Port 0
port = 0
# This includes 2 M2 pitches for the row addr clock line.
# The delay line is aligned with the bitcell array while the control logic is aligned with the port_data
# using the control_logic_center value.
control_pos[port] = vector(-self.control_logic_insts[port].width - 2 * self.m2_pitch,
self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y)
self.control_logic_insts[port].place(control_pos[port])
# The control and row addr flops are independent of any bus widths.
self.place_control()
self.place_row_addr_dffs()
# Place with an initial wide channel (from above)
self.place_dffs()
# Route the channel and set to the new data bus size
self.route_dffs(add_routes=False)
# Re-place with the new channel size
self.place_dffs()
# Now route the channel
self.route_dffs()
def place_row_addr_dffs(self):
"""
Must be run after place control logic.
"""
port = 0
# The row address bits are placed above the control logic aligned on the right.
x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width
# It is above the control logic but below the top of the bitcell array
y_offset = max(self.control_logic_insts[port].uy(), self.bank_inst.uy() - self.row_addr_dff_insts[port].height)
row_addr_pos[port] = vector(x_offset, y_offset)
self.row_addr_dff_insts[port].place(row_addr_pos[port])
y_offset = max(self.control_logic_insts[port].uy(), self.bank.predecoder_height)
self.row_addr_pos[port] = vector(x_offset, y_offset)
self.row_addr_dff_insts[port].place(self.row_addr_pos[port])
if len(self.all_ports)>1:
port = 1
# The row address bits are placed above the control logic aligned on the left.
x_offset = self.control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width
# If it can be placed above the predecoder and below the control logic, do it
y_offset = self.bank.bank_array_ll.y
self.row_addr_pos[port] = vector(x_offset, y_offset)
self.row_addr_dff_insts[port].place(self.row_addr_pos[port], mirror="XY")
def place_control(self):
port = 0
# This includes 2 M2 pitches for the row addr clock line.
# The delay line is aligned with the bitcell array while the control logic is aligned with the port_data
# using the control_logic_center value.
self.control_pos[port] = vector(-self.control_logic_insts[port].width - 2 * self.m2_pitch,
self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y)
self.control_logic_insts[port].place(self.control_pos[port])
if len(self.all_ports) > 1:
port = 1
# This includes 2 M2 pitches for the row addr clock line
# The delay line is aligned with the bitcell array while the control logic is aligned with the port_data
# using the control_logic_center value.
self.control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2 * self.m2_pitch,
self.bank.bank_array_ur.y
+ self.control_logic_insts[port].height
- self.control_logic_insts[port].height
+ self.control_logic_insts[port].mod.control_logic_center.y)
self.control_logic_insts[port].place(self.control_pos[port], mirror="XY")
def place_dffs(self):
"""
Place the col addr, data, wmask, and spare data DFFs.
This can be run more than once after we recompute the channel width.
"""
port = 0
# Add the col address flops below the bank to the right of the control logic
x_offset = self.control_logic_insts[port].rx() + self.dff.width
y_offset = - self.data_bus_size[port] - self.dff.height
# Place it a data bus below the x-axis, but at least as low as the control logic to not block
# the control logic signals
y_offset = min(-self.data_bus_size[port] - self.dff.height,
self.control_logic_insts[port].by())
if self.col_addr_dff:
col_addr_pos[port] = vector(x_offset,
y_offset)
self.col_addr_dff_insts[port].place(col_addr_pos[port])
self.col_addr_pos[port] = vector(x_offset,
y_offset)
self.col_addr_dff_insts[port].place(self.col_addr_pos[port])
x_offset = self.col_addr_dff_insts[port].rx()
else:
col_addr_pos[port] = vector(x_offset, 0)
self.col_addr_pos[port] = vector(x_offset, 0)
if port in self.write_ports:
if self.write_size:
# Add the write mask flops below the write mask AND array.
wmask_pos[port] = vector(x_offset,
y_offset)
self.wmask_dff_insts[port].place(wmask_pos[port])
self.wmask_pos[port] = vector(x_offset,
y_offset)
self.wmask_dff_insts[port].place(self.wmask_pos[port])
x_offset = self.wmask_dff_insts[port].rx()
# Add the data flops below the write mask flops.
data_pos[port] = vector(x_offset,
y_offset)
self.data_dff_insts[port].place(data_pos[port])
self.data_pos[port] = vector(x_offset,
y_offset)
self.data_dff_insts[port].place(self.data_pos[port])
x_offset = self.data_dff_insts[port].rx()
# Add spare write enable flops to the right of data flops since the spare columns
# will be on the right
if self.num_spare_cols:
spare_wen_pos[port] = vector(x_offset,
y_offset)
self.spare_wen_dff_insts[port].place(spare_wen_pos[port])
self.spare_wen_pos[port] = vector(x_offset,
y_offset)
self.spare_wen_dff_insts[port].place(self.spare_wen_pos[port])
x_offset = self.spare_wen_dff_insts[port].rx()
else:
wmask_pos[port] = vector(x_offset, y_offset)
data_pos[port] = vector(x_offset, y_offset)
spare_wen_pos[port] = vector(x_offset, y_offset)
self.wmask_pos[port] = vector(x_offset, y_offset)
self.data_pos[port] = vector(x_offset, y_offset)
self.spare_wen_pos[port] = vector(x_offset, y_offset)
if len(self.all_ports)>1:
# Port 1
if len(self.all_ports) > 1:
port = 1
# This includes 2 M2 pitches for the row addr clock line
# The delay line is aligned with the bitcell array while the control logic is aligned with the port_data
# using the control_logic_center value.
control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2 * self.m2_pitch,
self.bank.bank_array_ur.y
+ self.control_logic_insts[port].height
- self.control_logic_insts[port].height
+ self.control_logic_insts[port].mod.control_logic_center.y)
self.control_logic_insts[port].place(control_pos[port], mirror="XY")
# The row address bits are placed above the control logic aligned on the left.
x_offset = control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width
# It is below the control logic but below the bottom of the bitcell array
y_offset = min(self.control_logic_insts[port].by(), self.bank_inst.by() + self.row_addr_dff_insts[port].height)
row_addr_pos[port] = vector(x_offset, y_offset)
self.row_addr_dff_insts[port].place(row_addr_pos[port], mirror="XY")
# Add the col address flops below the bank to the right of the control logic
x_offset = self.control_logic_insts[port].lx() - 2 * self.dff.width
y_offset = self.bank.height + self.data_bus_size[port] + self.dff.height
# Place it a data bus below the x-axis, but at least as high as the control logic to not block
# the control logic signals
y_offset = max(self.bank.height + self.data_bus_size[port] + self.dff.height,
self.control_logic_insts[port].uy() - self.dff.height)
if self.col_addr_dff:
col_addr_pos[port] = vector(x_offset - self.col_addr_dff_insts[port].width,
y_offset)
self.col_addr_dff_insts[port].place(col_addr_pos[port], mirror="MX")
self.col_addr_pos[port] = vector(x_offset,
y_offset)
self.col_addr_dff_insts[port].place(self.col_addr_pos[port], mirror="XY")
x_offset = self.col_addr_dff_insts[port].lx()
else:
col_addr_pos[port] = vector(x_offset, y_offset)
self.col_addr_pos[port] = vector(x_offset, y_offset)
if port in self.write_ports:
# Add spare write enable flops to the right of the data flops since the spare
# columns will be on the left
if self.num_spare_cols:
spare_wen_pos[port] = vector(x_offset - self.spare_wen_dff_insts[port].width,
y_offset)
self.spare_wen_dff_insts[port].place(spare_wen_pos[port], mirror="MX")
self.spare_wen_pos[port] = vector(x_offset - self.spare_wen_dff_insts[port].width,
y_offset)
self.spare_wen_dff_insts[port].place(self.spare_wen_pos[port], mirror="MX")
x_offset = self.spare_wen_dff_insts[port].lx()
if self.write_size:
# Add the write mask flops below the write mask AND array.
wmask_pos[port] = vector(x_offset - self.wmask_dff_insts[port].width,
y_offset)
self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX")
self.wmask_pos[port] = vector(x_offset - self.wmask_dff_insts[port].width,
y_offset)
self.wmask_dff_insts[port].place(self.wmask_pos[port], mirror="MX")
x_offset = self.wmask_dff_insts[port].lx()
# Add the data flops below the write mask flops.
data_pos[port] = vector(x_offset - self.data_dff_insts[port].width,
y_offset)
self.data_dff_insts[port].place(data_pos[port], mirror="MX")
self.data_pos[port] = vector(x_offset - self.data_dff_insts[port].width,
y_offset)
self.data_dff_insts[port].place(self.data_pos[port], mirror="MX")
else:
wmask_pos[port] = vector(x_offset, y_offset)
data_pos[port] = vector(x_offset, y_offset)
spare_wen_pos[port] = vector(x_offset, y_offset)
self.wmask_pos[port] = vector(x_offset, y_offset)
self.data_pos[port] = vector(x_offset, y_offset)
self.spare_wen_pos[port] = vector(x_offset, y_offset)
def add_layout_pins(self):
"""
Add the top-level pins for a single bank SRAM with control.
@ -343,20 +382,42 @@ class sram_1bank(sram_base):
self.route_row_addr_dff()
def route_dffs(self, add_routes=True):
for port in self.all_ports:
self.route_dff(port)
self.route_dff(port, add_routes)
def route_dff(self, port):
def route_dff(self, port, add_routes):
route_map = []
# column mux dff
# column mux dff is routed on it's own since it is to the far end
# decoder inputs are min pitch M2, so need to use lower layer stack
if self.col_addr_size > 0:
dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)]
dff_pins = [self.col_addr_dff_insts[port].get_pin(x) for x in dff_names]
bank_names = ["addr{0}_{1}".format(port, x) for x in range(self.col_addr_size)]
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
route_map.extend(list(zip(bank_pins, dff_pins)))
if port == 0:
offset = vector(self.control_logic_insts[port].rx() + self.dff.width,
- self.data_bus_size[port] + 2 * self.m1_pitch)
else:
offset = vector(0,
self.bank.height + 2 * self.m1_space)
cr = channel_route.channel_route(netlist=route_map,
offset=offset,
layer_stack=self.m1_stack,
parent=self)
if add_routes:
self.add_inst("hc", cr)
self.connect_inst([])
else:
self.col_addr_bus_size[port] = cr.height
route_map = []
# wmask dff
if self.num_wmasks > 0 and port in self.write_ports:
@ -384,35 +445,46 @@ class sram_1bank(sram_base):
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
route_map.extend(list(zip(bank_pins, sram_pins)))
if self.num_wmasks > 0 and port in self.write_ports:
layer_stack = self.m3_stack
else:
layer_stack = self.m1_stack
if port == 0:
offset = vector(self.control_logic_insts[port].rx() + self.dff.width,
- self.data_bus_size[port] + 2 * self.m1_pitch)
else:
offset = vector(0,
self.bank.height + 2 * self.m1_space)
if len(route_map) > 0:
self.create_horizontal_channel_route(netlist=route_map,
offset=offset,
layer_stack=layer_stack)
# Route these separately because sometimes the pin pitch on the write driver is too narrow for M3 (FreePDK45)
# spare wen dff
if self.num_spare_cols > 0 and port in self.write_ports:
dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)]
dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names]
bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)]
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
route_map = zip(bank_pins, dff_pins)
self.create_horizontal_channel_route(netlist=route_map,
route_map.extend(list(zip(bank_pins, dff_pins)))
if len(route_map) > 0:
if self.num_wmasks > 0 and port in self.write_ports:
layer_stack = self.m3_stack
else:
layer_stack = self.m1_stack
if port == 0:
offset = vector(self.control_logic_insts[port].rx() + self.dff.width,
- self.data_bus_size[port] + 2 * self.m1_pitch)
cr = channel_route.channel_route(netlist=route_map,
offset=offset,
layer_stack=self.m1_stack)
layer_stack=layer_stack,
parent=self)
if add_routes:
self.add_inst("hc", cr)
self.connect_inst([])
else:
self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap
else:
offset = vector(0,
self.bank.height + 2 * self.m1_space)
cr = channel_route.channel_route(netlist=route_map,
offset=offset,
layer_stack=layer_stack,
parent=self)
if add_routes:
self.add_inst("hc", cr)
self.connect_inst([])
else:
self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap
def route_clk(self):
""" Route the clock network """

View File

@ -129,7 +129,7 @@ class sram_base(design, verilog, lef):
start_time = datetime.datetime.now()
# We only enable final verification if we have routed the design
self.DRC_LVS(final_verification=OPTS.route_supplies, force_check=True)
self.DRC_LVS(final_verification=OPTS.route_supplies, force_check=OPTS.check_lvsdrc)
if not OPTS.is_unit_test:
print_time("Verification", datetime.datetime.now(), start_time)

View File

@ -135,7 +135,7 @@ def write_calibre_lvs_script(cell_name, final_verification, gds_name, sp_name):
def write_calibre_pex_script(cell_name, extract, output, final_verification):
""" Write a pex script that can either just extract the netlist or the netlist+parasitics """
if output == None:
output = cell_name + ".pex.netlist"
output = cell_name + ".pex.sp"
# check if lvs report has been done
# if not run drc and lvs
@ -195,11 +195,14 @@ def run_drc(cell_name, gds_name, extract=False, final_verification=False):
filter_gds(cell_name, OPTS.openram_temp + "temp.gds", OPTS.openram_temp + cell_name + ".gds")
else:
# Copy file to local dir if it isn't already
if not os.path.isfile(gds_name):
shutil.copy(OPTS.output_path+os.path.basename(gds_name),gds_name)
if not os.path.isfile(OPTS.openram_temp + os.path.basename(gds_name)):
shutil.copy(gds_name, OPTS.openram_temp)
drc_runset = write_calibre_drc_script(cell_name, extract, final_verification, gds_name)
if not os.path.isfile(OPTS.openram_temp + os.path.basename(gds_name)):
shutil.copy(gds_name, OPTS.openram_temp + os.path.basename(gds_name))
(outfile, errfile, resultsfile) = run_script(cell_name, "drc")
# check the result for these lines in the summary:
@ -241,14 +244,10 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False):
lvs_runset = write_calibre_lvs_script(cell_name, final_verification, gds_name, sp_name)
# Copy file to local dir if it isn't already
# if os.path.dirname(gds_name)!=OPTS.openram_temp.rstrip('/'):
# shutil.copy(gds_name, OPTS.openram_temp)
# if os.path.dirname(sp_name)!=OPTS.openram_temp.rstrip('/'):
# shutil.copy(sp_name, OPTS.openram_temp)
if not os.path.isfile(gds_name):
shutil.copy(OPTS.output_path+os.path.basename(gds_name), gds_name)
if not os.path.isfile(sp_name):
shutil.copy(OPTS.output_path+os.path.basename(sp_name), sp_name)
if not os.path.isfile(OPTS.openram_temp + os.path.basename(gds_name)):
shutil.copy(gds_name, OPTS.openram_temp)
if not os.path.isfile(OPTS.openram_temp + os.path.basename(sp_name)):
shutil.copy(sp_name, OPTS.openram_temp)
(outfile, errfile, resultsfile) = run_script(cell_name, "lvs")
@ -329,13 +328,14 @@ def run_pex(cell_name, gds_name, sp_name, output=None, final_verification=False)
global num_pex_runs
num_pex_runs += 1
write_calibre_pex_script(cell_name,True,output,final_verification)
write_calibre_pex_script(cell_name, True, output, final_verification)
# Copy file to local dir if it isn't already
if not os.path.isfile(OPTS.openram_temp + os.path.basename(gds_name)):
shutil.copy(gds_name, OPTS.openram_temp + os.path.basename(gds_name))
shutil.copy(gds_name, OPTS.openram_temp)
if not os.path.isfile(OPTS.openram_temp + os.path.basename(sp_name)):
shutil.copy(sp_name, OPTS.openram_temp + os.path.basename(sp_name))
shutil.copy(sp_name, OPTS.openram_temp)
(outfile, errfile, resultsfile) = run_script(cell_name, "pex")

View File

@ -20,7 +20,7 @@ pex_warned = False
def run_drc(cell_name, gds_name, extract=False, final_verification=False):
global drc_warned
if not drc_warned:
debug.warning("DRC unable to run.")
debug.error("DRC unable to run.", -1)
drc_warned=True
# Since we warned, return a failing test.
return 1
@ -29,7 +29,7 @@ def run_drc(cell_name, gds_name, extract=False, final_verification=False):
def run_lvs(cell_name, gds_name, sp_name, final_verification=False):
global lvs_warned
if not lvs_warned:
debug.warning("LVS unable to run.")
debug.error("LVS unable to run.", -1)
lvs_warned=True
# Since we warned, return a failing test.
return 1
@ -38,7 +38,7 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False):
def run_pex(name, gds_name, sp_name, output=None, final_verification=False):
global pex_warned
if not pex_warned:
debug.warning("PEX unable to run.")
debug.error("PEX unable to run.", -1)
pex_warned=True
# Since we warned, return a failing test.
return 1