mirror of https://github.com/VLSIDA/OpenRAM.git
Merge branch 'dev' of github:VLSIDA/OpenRAM into CalibrePexFilesUpdate
This commit is contained in:
commit
487bb6c6e9
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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([])
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 """
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in New Issue